如何在 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:ServiceContract
、StorageContract
、QuizContract
和 SignatureContract
。您在 class 之间建立了一种关系,称为 依赖性 。并且这种依赖是特定类型的:usage(由 «use» 关键字表示)。这在 UML 中是什么意思?
UML 中的依赖关系被定义为一种关系,其中“如果没有供应商,客户的语义是不完整的”(第 7.7.3.1 节 的 UML 规范)。此外,使用依赖性被定义为一种关系,其中“一个 NamedElement 需要另一个 NamedElement(或一组 NamedElement)来实现其完整实现或操作”(第 7.7.3.2 节).
因此,如果我们将这些定义应用于您提出的图表,您可以将 ServiceContract
和 StorageContract
之间的关系解读为“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,所以我在每个操作名称的开头包含了+
修饰符。
为了准确起见,在我看来 ServiceContract
将 StorageContract
、QuizContract
和 SignatureContract
组合在一起以提供共同的 Class访问某些操作(函数)的访问器。如果是这样的话,那么我们就是在谈论聚合。在 UML 中,聚合被定义为一种关联,其中“一个实例用于将一组实例组合在一起”(第 9.5.3 节)。
聚合可以有两种类型:共享(或者在规范的早期版本中通常称为聚合),以及composite(或者通常称为规范先前版本中的 composition)。
UML 规范提供了或多或少特定的语义,说明聚合类型 复合 的含义:“复合对象有责任对于组合对象的存在和存储".
假设在您的情况下,StorageContract
、QuizContract
和 SignatureContract
的 存在和存储 是ServiceContract
。那么在那种情况下,你有一个 composite 聚合,用黑色菱形表示:
它被读作“ServiceContract
由一个拥有 属性 的 classifier 类型 StorageContract
组成,称为 storeC
”,依此类推。
请记住,使用 复合 类型的聚合,您是说 ServiceContract
对象负责 存在和存储。这意味着只要 ServiceContract
的实例是 removed/destroyed,关联的 StorageContract
、QuizContract
和 SignatureContract
也必须被销毁。
如果不是这种情况,并且假设关联仍然与聚合定义匹配,那么唯一可用的其他选项是聚合必须 共享。 UML 规范明确没有提供 shared 聚合的精确语义,让应用程序领域和建模者负责提供这些语义。
因此,如果 StorageContract
、QuizContract
和 SignatureContract
独立于 ServiceContract
存在,并且如果您同意 ServiceContract
根据 UML 规范中给出的定义聚合这些对象,您必须使用 shared 聚合。
A shared 聚合由聚合其他 Classifier 的 Classifier 关联末尾的空心菱形表示。这就是它的样子:
这张图可以读作:
- 有四个 Class 符:
ServiceContract
、StorageContract
、QuizContract
和 SignatureContract
。
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
上使用
例如对于 USDT
https://etherscan.io/viewsvg?t=1&a=0xdAC17F958D2ee523a2206206994597C13D831ec7
(见下图)
所以不要花时间手动画线,使用更智能的工具为您更快地画线。
我正在寻找一种使用 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:ServiceContract
、StorageContract
、QuizContract
和 SignatureContract
。您在 class 之间建立了一种关系,称为 依赖性 。并且这种依赖是特定类型的:usage(由 «use» 关键字表示)。这在 UML 中是什么意思?
UML 中的依赖关系被定义为一种关系,其中“如果没有供应商,客户的语义是不完整的”(第 7.7.3.1 节 的 UML 规范)。此外,使用依赖性被定义为一种关系,其中“一个 NamedElement 需要另一个 NamedElement(或一组 NamedElement)来实现其完整实现或操作”(第 7.7.3.2 节).
因此,如果我们将这些定义应用于您提出的图表,您可以将 ServiceContract
和 StorageContract
之间的关系解读为“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,所以我在每个操作名称的开头包含了+
修饰符。
为了准确起见,在我看来 ServiceContract
将 StorageContract
、QuizContract
和 SignatureContract
组合在一起以提供共同的 Class访问某些操作(函数)的访问器。如果是这样的话,那么我们就是在谈论聚合。在 UML 中,聚合被定义为一种关联,其中“一个实例用于将一组实例组合在一起”(第 9.5.3 节)。
聚合可以有两种类型:共享(或者在规范的早期版本中通常称为聚合),以及composite(或者通常称为规范先前版本中的 composition)。
UML 规范提供了或多或少特定的语义,说明聚合类型 复合 的含义:“复合对象有责任对于组合对象的存在和存储".
假设在您的情况下,StorageContract
、QuizContract
和 SignatureContract
的 存在和存储 是ServiceContract
。那么在那种情况下,你有一个 composite 聚合,用黑色菱形表示:
它被读作“ServiceContract
由一个拥有 属性 的 classifier 类型 StorageContract
组成,称为 storeC
”,依此类推。
请记住,使用 复合 类型的聚合,您是说 ServiceContract
对象负责 存在和存储。这意味着只要 ServiceContract
的实例是 removed/destroyed,关联的 StorageContract
、QuizContract
和 SignatureContract
也必须被销毁。
如果不是这种情况,并且假设关联仍然与聚合定义匹配,那么唯一可用的其他选项是聚合必须 共享。 UML 规范明确没有提供 shared 聚合的精确语义,让应用程序领域和建模者负责提供这些语义。
因此,如果 StorageContract
、QuizContract
和 SignatureContract
独立于 ServiceContract
存在,并且如果您同意 ServiceContract
根据 UML 规范中给出的定义聚合这些对象,您必须使用 shared 聚合。
A shared 聚合由聚合其他 Classifier 的 Classifier 关联末尾的空心菱形表示。这就是它的样子:
这张图可以读作:
- 有四个 Class 符:
ServiceContract
、StorageContract
、QuizContract
和SignatureContract
。 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
上使用例如对于 USDT https://etherscan.io/viewsvg?t=1&a=0xdAC17F958D2ee523a2206206994597C13D831ec7 (见下图)
所以不要花时间手动画线,使用更智能的工具为您更快地画线。