如何在 UML 中对智能合约进行建模?

How to modelize smart contracts in UML?

我正在寻找一种使用 UML 等建模语言对以太坊智能合约交互进行建模的方法。

我有以下服务合同:

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);
    }

}

我用这个class图建模了,对吗?

您只是与这三个class有关联:

(我只画了一个关系)

右侧的角色名称与点一起表明它是左侧 class 的所有者 属性。不确定可见性(如果默认情况下是私有的,请将 + 替换为 -)。

[编辑以进一步说明]

建模系统是使用建模语言以正式的方式描述它,并且在某些情况下遵循一些通用准则。在这种情况下,您建议使用 UML(参见 UML Specification)。

UML图可以分为三类:

  • 结构:通用结构、值、class修饰符和包都属于这一类
  • 行为:常见的行为、动作、状态机、活动和交互都属于这一类。
  • 补充:用例、部署和信息流属于此类。

作为建模者,您可以决定为要应用的目标使用哪些图表。

在您的问题中,您说您正在寻找一种对交互进行建模的方法。那属于 行为 类别。但是,您提供了示例代码和建议的 class 图,它属于 结构类别

话虽如此,你提出的图表是否正确?我会说它不准确且不完整(但不一定不正确)。让我进一步解释一下。

在您提出的图表中,您有四个 classes:ServiceContractStorageContractQuizContractSignatureContract。您在 class 之间建立了一种关系,称为 依赖性 。并且这种依赖是特定类型的:usage(由 «use» 关键字表示)。这在 UML 中是什么意思?

UML 中的依赖关系被定义为一种关系,其中“如果没有供应商,客户的语义是不完整的”(第 7.7.3.1 节 的 UML 规范)。此外,使用依赖性被定义为一种关系,其中“一个 NamedElement 需要另一个 NamedElement(或一组 NamedElement)来实现其完整实现或操作”(第 7.7.3.2 节).

因此,如果我们将这些定义应用于您提出的图表,您可以将 ServiceContractStorageContract 之间的关系解读为“ServiceContract 使用 StorageContract” .但没有别的。有了这张图,你不知道 ServiceContract 如何使用 StorageContract,如果它使用多个 StorageContract 的实例,等等。

既然您知道这些 class 之间的关系,您应该使用更准确和完整的图表。

第一步是使用关联而不是依赖。在 UML 中,关联 被定义为“类型化实例之间可能出现的语义关系”。而且您知道您在 class 图表中建模的 class 实体之间的 语义关系 。因此,使用 关联 .

更有意义

一个关联用实线表示(实际上UML规范说它可以画成菱形,但是对于二元关联它说通常它只是用实线绘制)。因此,让我们开始将您的图表更改为新图表。下图中可以看到有关联关系的四个class(还是不完整):

现在我们有了关联,我们需要进一步定义它。协会有名字吗?联想可以双向解读吗?我们知道关联每一端的多重值吗?关联的末端是否有约束?

在这个例子中,我们不需要关联的名称,似乎可以用两种方式读取它,而且所有端点的重数值恰好为 1。然后我们不要在图表中添加与这些问题相关的任何内容。但是约束呢?

我们来看看源码。当你把这个:

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

您可以将其表示为“ServiceContract 拥有(拥有)一个名为 storeC 的 属性,其类型为 StoreContract”,等等。协会中的所有权由一个小实心圆圈(称为 )表示,位于该线与所拥有的 Classifer 相交的点。您还可以添加拥有所有权的 属性 的名称(第 11.5.4 节)。此时的图是这样的:

(见

由于我们无法从源中推断属性的可见性,我们可以将其设为未定义(否则我们可以在 属性 的名称前使用 + 符号作为 public 属性,一个 - 标志代表私有 属性,一个 # 代表一个保护 属性,一个 ~ 代表一个包)。

我们还可以在 ServiceContract 的 Class 标识符中显示属性,而不是在关联中拥有的 Class 标识符的末尾。这看起来像这样:

UML 规范(第 9.5.3 节)允许这两种样式,并且它也不强制执行任何约定。然而,它提到了一般建模场景的约定类型为Class的属性是一个关联端,而一个属性 其类型是一种 DataType 不是 ".

这张图是正确的,因为它符合 UML 规范,并且它描述了一个系统,您在其中拥有:

  • 一个名为 ServiceContract 的 Classifier 拥有三个属性:
    • 一个名为 storeC 的 属性 类型是一个名为 StorageContract.
    • 的 Classifier
    • 一个名为 quizC 的 属性 类型是一个名为 QuizContract.
    • 的 Classifier
    • 一个名为 signC 的 属性 类型是一个名为 SignatureContract.
    • 的 Classifier

请记住,作为建模者,这是否足以满足您的目标,这是您的选择。

从源头上我可以说之前的图表仍然不完整和不准确。为什么?

  • 因为源包含图中未表示的三个操作(函数)。这在完整性方面可以改进。
  • 因为你无法从图表中判断 ServiceContract 所拥有的 Classifiers 是否拥有将所拥有的 Classifiers 的一组实例组合在一起.鉴于这种情况,拥有的 Classifiers 是否共享相同的范围。这在准确性方面可以提高。

首先我们要将操作(函数)添加到图中:

[注意:您还可以将_constructor_ 添加到操作中。]

我猜函数是public,所以我在每个操作名称的开头包含了+修饰符。

为了准确起见,在我看来 ServiceContractStorageContractQuizContractSignatureContract 组合在一起以提供共同的 Class访问某些操作(函数)的访问器。如果是这样的话,那么我们就是在谈论聚合。在 UML 中,聚合被定义为一种关联,其中“一个实例用于将一组实例组合在一起”(第 9.5.3 节)。

聚合可以有两种类型:共享(或者在规范的早期版本中通常称为聚合),以及composite(或者通常称为规范先前版本中的 composition)。

UML 规范提供了或多或少特定的语义,说明聚合类型 复合 的含义:“复合对象有责任对于组合对象的存在和存储".

假设在您的情况下,StorageContractQuizContractSignatureContract 存在和存储 ServiceContract。那么在那种情况下,你有一个 composite 聚合,用黑色菱形表示:

它被读作“ServiceContract 由一个拥有 属性 的 classifier 类型 StorageContract 组成,称为 storeC ”,依此类推。

请记住,使用 复合 类型的聚合,您是说 ServiceContract 对象负责 存在和存储。这意味着只要 ServiceContract 的实例是 removed/destroyed,关联的 StorageContractQuizContractSignatureContract 也必须被销毁。

如果不是这种情况,并且假设关联仍然与聚合定义匹配,那么唯一可用的其他选项是聚合必须 共享。 UML 规范明确没有提供 shared 聚合的精确语义,让应用程序领域和建模者负责提供这些语义。

因此,如果 StorageContractQuizContractSignatureContract 独立于 ServiceContract 存在,并且如果您同意 ServiceContract根据 UML 规范中给出的定义聚合这些对象,您必须使用 shared 聚合。

A shared 聚合由聚合其他 Classifier 的 Classifier 关联末尾的空心菱形表示。这就是它的样子:

这张图可以读作:

  • 有四个 Class 符:ServiceContractStorageContractQuizContractSignatureContract
  • ServiceContract 聚合三个拥有的属性:
    • storeC,类型 StorageContract
    • quizC,类型 QuizContract
    • signC,类型 SignatureContract
  • ServiceContract 有一个需要三个参数的构造函数:
    • _storeC 类型 address
    • _quizC 类型 address
    • _signC 类型 address
  • ServiceContract 具有三个 public 函数:
    • storeData,这需要一个类型为 bytes32 的参数,称为 data 和 returns 什么都不需要。
    • getAnswer,这需要一个类型为 bytes32 的参数,称为 question 和 returns 一种 bytes32 数据类型。
    • sign,这需要一个类型为 bytes32 的参数称为数据和 returns 一个 bytes32 数据类型。

请记住,对于您想要的目标来说,这张最终图表可能过于详细了。作为建模者,您有责任决定是否在图表中包含一些细节。

虽然花一些时间了解 UML(inheritance、组合等)中的特定 Solidity 关系应该使用什么确切的箭头可能是件好事,但总的趋势是让标准工具来关心这一点。

有 sol2uml UML 生成器https://github.com/naddison36/sol2uml

已在 https://etherscan.io

上使用

例如对于 USDT https://etherscan.io/viewsvg?t=1&a=0xdAC17F958D2ee523a2206206994597C13D831ec7 (见下图)

所以不要花时间手动画线,使用更智能的工具为您更快地画线。