9

I am looking for a way to modelize ethereum smart contracts interaction using a modeling language like UML.

I have the following serivce Contract:

contract ServiceContract {


    constructor (address _storeC, address _quizC, address _signC) {

        StorageContract storeC = StoreContract(_storeC);
        QuizContract quizC = QuizContract(_quizC);
        SignatureContract signC = SignatureContract(_signC);
    }


    function storeData (bytes32 data) public {
        storeC.save(data);
    }

    function getAnswer( bytes32 question) public constant returns (bytes32) {
       return quizC.get(question);
    }

    function sign (bytes32 data) public returns (bytes32) {
        return signC.sign(data);
    }

}

I modelized it with this class diagram, is it correct?

enter image description here

maroodb
  • 1,026
  • 2
  • 16
  • 28

3 Answers3

8

[Edited for extra clarification]

Modelling a system is describing it in a formal way using a modelling language, and in some cases following some common guidelines. In this case you suggest the use of UML (See UML Specification).

UML diagrams can be divided into three categories:

  • Structural: The common structure, the values, the classifiers and the packages are in this category
  • Behavioral: The common behavior, the actions, state machines, the activities and the interactions are in this category.
  • Suplemental: The use cases, the deployments and the information flows are in this category.

As a modeler you decide which diagrams do you you need for what target you want to apply.

In your question you say that you are looking for a way to modelize an interaction. That is within the behavioral category. However you provide a sample code and a proposed class diagram, which is within the structural category.

That being said, is it your proposed diagram correct? I would say that it is inaccurate and incomplete (but not necessarily incorrect). Let me explain this a bit further.

In your proposed diagram you have four classes: ServiceContract, StorageContract, QuizContract and SignatureContract. You have drawn a relationship between the classes that is known as a dependency. And this dependency is of a specific type: usage (represented by the «use» keyword). What does this mean in UML?

A dependency in UML is defined as a relation where "the semantics of the clients are not complete without the suppliers" (Section 7.7.3.1 of the UML specification). Moreover, a usage dependency is defined as a relation where "one NamedElement requires another NamedElement (or set of NamedElements) for its full implementation or operation" (Section 7.7.3.2).

Hence, if we apply those defintions to your proposed diagram, you may read the relation between the ServiceContract and the StorageContract as "ServiceContract uses StorageContract". But nothing else. With this diagram you don't know how ServiceContract uses StorageContract, if it uses more than one instance of StorageContract, and so on.

Since you know how those classes are related, you should use a more accurate and complete diagram.

The first step is to use an association instead of a dependency. In UML an association is defined as "a semantic relationship that can occur between typed instances". And you know the semantic relationship between the classes that you are modelling in your class diagram. Therefore it makes more sense to use an association.

An association is represented with a solid line (indeed the UML specification says that it may be drawn as a diamond, but for binary associations it says that normally it is drawn just with a solid line). So let's start changing your diagram to the new one. In the next figure you can see the four classes with the association relationship (still incomplete):

enter image description here

Now that we have the association, we need to define it further. Has the association a name? Can the association be read in both ways? Do we know the multiplicity values for each end of the association? Do the ends of the associations have contraints?

In this example we don't need a name for the association, it seems that it can be read in both ways, and also that the multiplicity values are exactly 1 for all the ends. Then we do not to add anything to the diagram related to these questions. But what about the constraints?

Let's take a look at the source code. When you put this:

contract ServiceContract {
    constructor (address _storeC, address _quizC, address _signC) {
        StorageContract storeC = StoreContract(_storeC);
        QuizContract quizC = QuizContract(_quizC);
        SignatureContract signC = SignatureContract(_signC);
    }
}

you can express it as "the ServiceContract has (owns) a property named storeC that is of a type of StoreContract", and so on. An ownership in an association is represented by a small filled circle (called a dot), at the point where the line meets the Classifer that is owned. Also you can add the name of the property that holds the ownership (Section 11.5.4). At this point the diagram is like this:

enter image description here

(See the answer from Thomas Kilian)

Since we cannot infer the visibility of the properties from the source, we can just let it as undefined (otherwise we can use a + sign before the name of the property for a public property, a - sign for a private property, a # for a protected property, and a ~ for a package).

Also we can show the properties within the Classifier for ServiceContract instead of at the end of the owned Classifier in the association. This will look like this:

enter image description here

Both styles are allowed by the UML specification (Section 9.5.3), and it also does not enforce any convention. However it mentions the convention for general modelling scenarios "that a Property whose type is a kind of Class is an Association end, while a property whose type is a kind of DataType is not".

This diagram is correct in the sense that it complies with the UML specification, and that it describes a system in which you have:

  • A Classifier named ServiceContract that owns three properties:
    • A Property named storeC whose type is a Classifier named StorageContract.
    • A Property named quizC whose type is a Classifier named QuizContract.
    • A Property named signC whose type is a Classifier named SignatureContract.

And remember, it is your choice, as a modeler, if this is enough for your target or not.

From the source I can say that the previous diagram is still incomplete and inaccurate. Why?

  • Because the source includes three Operations (the functions) that are not represented in the diagram. This can be improved in terms of completeness.
  • Because you cannot say from the diagram if the Classifiers that are owned by the ServiceContract are owned to group together a set of instances of the owned Classifiers or not. And given the case, if the owned Classifiers share the same scope or not. This can be improved in terms of accuracy.

First we are going to add the operations (the functions) to the diagram:

enter image description here

[NOTE: You may also add the _constructor_ to the operations.]

I guess that the functions are public, so I have included the + modifier at the beginning of each operation name.

Now for the accuracy, it seems to me that the ServiceContract groups together the StorageContract, the QuizContract and the SignatureContract in order to provide a common Classifier to access to certain operations (functions). If that is the case, then we are talking about aggregation. In UML aggregation is defined as an association where "one instance is used to group together a set of instances" (Section 9.5.3).

An aggregation can be of two types: shared (or just commonly known as aggregation from previous versions of the specification), and composite (or just commonly known as composition from previous versions of the specification).

The UML specification provides a more or less specific semantics for what it means for an aggregation to be of the type composite: "the composite object has responsibility for the existence and storage of the composed objects".

Let's say that in your case the existence and storage of the StorageContract, the QuizContract and the SignatureContract is responsability of the ServiceContract. Then in that case you have a composite aggregation, that is represented by a black diamond:

enter image description here

And it is read as "ServiceContract is composed by an owned property of classifier type StorageContract called storeC", and so on.

Keep in mind that using a composite type of aggregation you are saying that the ServiceContract object is responsible for the existence and storage. That means that whenever an instance of the ServiceContract is removed/destroyed, the associated StorageContract, QuizContract and SignatureContract must be destroyed also.

If that is not the case, and given that still the assocation matches the aggregation definition, then the only other option available is that the aggregation must be shared. The UML specification explictly does not provide a precise semantics of what a shared aggregation is, leaving the application area and the modeler with the responsability of giving those semantics.

So, if the StorageContract, the QuizContract, and the SignatureContract exist independently of the ServiceContract, and if you agree that the ServiceContract aggregates those objects according to definition given in the UML specification, you must use a shared aggregation.

A shared aggregation is represented by a hollow diamond at the end of the association of the Classifier that aggregates other Classifiers. And this it's how it looks:

enter image description here

And this diagram can be read as:

  • There are four Classifiers: ServiceContract, StorageContract, QuizContract and SignatureContract.
  • ServiceContract aggregates three owned properties:
    • storeC, of type StorageContract.
    • quizC, of type QuizContract.
    • signC, of type SignatureContract.
  • ServiceContract has one constructor that requires three arguments:
    • _storeC of type address.
    • _quizC of type address.
    • _signC of type address.
  • ServiceContract has three public functions:
    • storeData, that requires one argument of type bytes32 called data and returns nothing.
    • getAnswer, that requires one argument of type bytes32 called question and returns a bytes32 data type.
    • sign, that requires one argument of type bytes32 called data and returns a bytes32 data type.

Keep in mind that maybe for your desired target this final diagram is too detailed. It is your responsability as modeler to decide wether to include some details or not into the diagram.

  • thank you Mr for your clear answer, I really apreciate it. can u just give an example when we have to use <> instead of aggregation ? – maroodb Aug 24 '18 at 16:25
  • Sure @maroodb. According to the specification "A Usage is a Dependency in which one NamedElement requires another NamedElement (or set of NamedElements) for its full implementation or operation. The Usage does not specify how the client uses the supplier other than the fact that the supplier is used by the definition or implementation of the client". This means that your proposal is also correct, but as I stated before, it is inaccurate, because your code makes it clear that the relation between the classes, in your implementation, is an aggregation composition. – Néstor Lucas Martínez Aug 25 '18 at 01:20
  • Maybe an example of an usage that is not an aggregation (nor a composition) can be useful for understanding the concept. Let's say that instead of passing the references of the `StorageContract` to the `ServiceContract` constructor, you decide to pass it to the `storeData` function. In this example you will not have an object of the type `StorageContract` defined inside the `ServiceContract`, and therefore it will not be neither an aggregation, nor a composition. But it is, for sure, an usage, thus you could use the «use» dependency in the diagram. – Néstor Lucas Martínez Aug 25 '18 at 01:28
  • The use of shared aggregation is bad (in almost all contexts). UML 2.5 explicitly states that it has no commonly defined semantics. So it does not add any value. For that reason I down vote your answer. Also I disregard local properties notation compared to owned properties denoted in role names. See also https://bellekens.com/2010/12/20/uml-composition-vs-aggregation-vs-association/ – qwerty_so Aug 25 '18 at 06:06
  • @ThomasKilian I understand your point, but the UML specification just states that it does not define a _precise semantics_ of shared aggregation (**Section 9.5.3, page 113**). Regarding the properties notation, the UML specification explicitly does not enforce a modeling convention on when a property is of the AssociationEnd type, even though it recognizes a _useful_ one like the one you use (**Section 9.5.3, page 112**). – Néstor Lucas Martínez Aug 25 '18 at 13:49
  • Yes. My down vote is only related to the shared aggregation. Usage of roles is a recommendation (I share Geert's referenced opinion from a long time experience). – qwerty_so Aug 25 '18 at 13:57
  • Regarding the shared aggregation, all that matters is if you consider that this association has one end with aggregation or not. If it has aggregation, it has to be either _shared_ or _composite_ (**Section 9.9.1**). As the objects being part of the whole exist beyond the scope of the whole, it cannot be a _composite_, and therefore it must be _shared_, even though a _shared_ aggregation has not _precise semantics_. This is what Geert discusses in the post you have linked, and even provides examples (see the Linkedin example in the link). BTW, I don't mind the downvote. – Néstor Lucas Martínez Aug 25 '18 at 14:45
  • Pls. read the specs carefully on p. 110: _Indicates that the Property has shared aggregation semantics. Precise semantics of shared aggregation varies by application area and modeler._ So the general use is disregarded unless you have good and documented reason for its use. – qwerty_so Aug 25 '18 at 16:05
  • Where does the UML Specification state that it _disregards_ the use of a shared aggregation? Indeed, it defines **explicitly** that an aggregation only can be of three types: _none_ (that means no aggregation), _shared_ (with no precise semantics, left to the application area and the modeler), and _composite_. Therefore, if an association has one end with aggregation, it must be either _shared_ or _composite_ (it cannot be _none_). If the aggregation does not match the semantics in the specification for a _composite_ type, then it **must** be _shared_ (there is no other possible type). – Néstor Lucas Martínez Aug 25 '18 at 16:48
  • That's why I said before that what matters in this discussion is if this association has one end with aggregation or not. Because if it has one end with aggregation, then, as explained before, it must be _shared_ (as it cannot be _none_, nor _composite_). How does the UML Specification define _aggregation_: when "one instance is used to group together a set of instances" (**Section 9.5.3, p. 112**). In this case, to my understanding, the `ServiceContract` object groups together a set of instances of one `StorageContract`, one `QuizContract` and one `SignaturaContract`. And this is aggregation. – Néstor Lucas Martínez Aug 25 '18 at 17:03
  • Happy to see you reduced the number of shared aggregation uses. However, aggregation is about lifetime of objects. In almost all cases this notation is just superfluous. It (the composite) has e meaning when you emphasize the lifetime. This can be done for security or memory management purpose. For standard associations its just ballast since the memory management of the target languge will handle it anyway. – qwerty_so Aug 27 '18 at 19:32
  • Please, see how aggregation is defined in the UML Specification: "_Sometimes a Property is used to model circumstances in which one instance is used to group together a set of instances; **this is called aggregation**_" (p. 113). You have a good example of a shared aggregation in https://bellekens.com/2010/12/20/uml-composition-vs-aggregation-vs-association/ (you already cited this link). In the LinkedIn example included there you have the class _Group_ that aggregates _User_ classes as _groupMember_. And has nothing to do with lifetimes. – Néstor Lucas Martínez Aug 27 '18 at 20:44
  • This answer deserves a medal! Extremely helpful to walk the OP through the thought process of how to model his setup! Amazing @NéstorLucasMartínez – dlock Feb 08 '22 at 14:54
5

You simply have associations to these three classes:

enter image description here

(I just drew a single relation)

The role name to the right tells in conjunction with the dot that it's a owned property of the class to the left. Not sure about the visibility (if that's private per default replace the + with a -).

qwerty_so
  • 35,448
  • 8
  • 62
  • 86
  • I draw a business model diagram, you can take a look to the source code to understand the relations between contracts, I didn't say what I do is correct am asking if it is correct or no – maroodb Aug 24 '18 at 09:00
  • Ah. I did not look carefully. Will make a correction. – qwerty_so Aug 24 '18 at 11:05
0

While it may be goodness to spend some time to learn what exact arrow should used for particular Solidity relationship in UML (inheritance, composition etc), general trend is to let standard tool to care about this.

There is sol2uml UML generator https://github.com/naddison36/sol2uml

that is already used on https://etherscan.io

e.g. for USDT https://etherscan.io/viewsvg?t=1&a=0xdAC17F958D2ee523a2206206994597C13D831ec7 (See image below)

So don't spend time manually drawing lines, use wiser tools to do it quicker for you.

Paul Verest
  • 60,022
  • 51
  • 208
  • 332