EVM开发者在Sui上构建的心智模型 - 第一部分

本文是为EVM开发者准备的Sui开发指南的第一部分,旨在帮助他们理解Sui的独特之处,包括对象模型、资产作为资源的处理方式以及所有权的概念。文章强调了Sui中状态存储在对象中而非合约中、对象的所有权决定了谁可以操作它们,以及资产被建模为资源以确保安全性的关键概念,从而使得开发者能够更好地理解Sui的开发模式。

Image

在 Sui 上构建的 EVM 开发者心智模型 - 第一部分

乍看之下,Move 可能会让人感到陌生。它看起来不像 Solidity,行为也不像 Solidity,而且你多年来内化的许多假设突然间不再适用。这可能会让人望而却步,但也可能会让人耳目一新。

本文是分为两部分的系列文章的第一部分,它不是作为参考手册,而是作为从 Solidity 心智模型到 Sui 思维方式的引导。我们将从大的思路开始,然后逐步叠加细节,以便一切都有落脚之处。今天,我们将介绍在 Sui 中开发的基础知识,从对象模型到作为资源的资产。第 2 部分将涵盖安全性、并行性、开发者工具等。

💧 OpenZeppelin 和 Sui 正在构建智能合约库和入门应用程序,作为 Sui 未来应用程序的基础。本指南提供了一个跳板,让你可以在使用新的智能合约库进行构建之前入门。

作者:Eric Nordelo

1. 第一个大转变:状态的存储位置

当你编写 Solidity 时,有一个假设是如此根本,以至于你很少质疑它:

状态存在于合约内部。

余额、所有权、权限。一切最终都是合约存储中的一些变量。用户实际上并不直接拥有资产;合约拥有,并且它们会跟踪谁拥有什么。

从账户到对象模型

在 Sui 上,状态存在于对象中。

不是询问合约“Alice 的余额是多少?”,而是 Alice 实际拥有一个代表其余额的对象。如果她想转移它,她会转移对象本身。

没有全局存储可供查看。虽然 Sui 维护着一组全局对象,但 Move 代码无法动态发现或获取它们。事务可能读取或修改的每个对象都必须作为输入显式传递进来,这让验证者提前知道事务将影响哪些状态。这有助于实现更安全的执行、更少的隐藏依赖关系和更好的并行性,所有这些都不会牺牲可组合性。

一旦理解了这一点,Move 和 Sui 的大多数内容突然间会变得更加连贯。

2. 对象:一切的构建块

一旦你接受了状态存在于对象中,下一个问题就很明显了:

Sui 上的对象到底是什么?

从高层次来看,对象只是一段结构化数据,区块链知道如何跟踪、版本化和分配所有权。但与 Solidity 中合约存储中的任何东西都可以像状态一样运行不同,Sui 对于什么是对象以及什么不是对象更加明确。

关键的 Ability:是什么使某些东西成为对象

在 Move 中,并非每个 struct 默认都会成为链上对象。要在 Sui 上存储、拥有和转移,struct 必须声明 key ability。

Move 中的 Ability 是编译时标记,用于描述类型允许执行的操作。在这种情况下,key 告诉系统此类型的值可以作为具有身份和所有权的链上对象存在。

添加 has key 告诉系统:此 struct 的实例是真实的链上对象。目前,只需知道 store ability 允许此对象作为字段嵌入到其他对象中,而不是仅存在于顶层。

这里有两条重要的规则:

  • 对象必须具有 key ability

  • 对象必须包含一个 UID 字段作为其第一个成员,命名为 id,用于在链上唯一标识该对象

该 UID 不是你自己发明的。它是由网络使用事务上下文创建的。你不需要担心语法的具体细节。重要的是对象转移由 Sui 框架提供的转移助手处理,并且事务上下文是显式传入的。

这就是为什么对象创建始终需要 TxContext:它允许 Sui 生成唯一的对象 ID 并跟踪所有权变更。与 Solidity 的 msg.sender 或其他隐式全局变量不同,Move 使事务上下文成为事务的显式输入。我们稍后会回到细节。

这可能起初会让人觉得冗长,但这是故意的:没有什么会凭空出现。

对象不是存储槽

一个微妙但重要的区别:定义一个 key struct 本身不会创建存储。

struct 定义只是一个类型。只有在创建了一个实际对象并被某人拥有之后,存储才存在。这强化了存储不是环境或全局的概念 - 它始终与独立于任何单个模块存在的具体对象相关联。

有了这个基础,我们可以转移到 Sui 上对象最重要的属性:所有权。

3. 所有权:谁可以触碰什么(以及为什么重要)

现在我们知道了对象是什么,下一个自然的问题是:

谁被允许使用它们?

在 Sui 上,所有权不是一个抽象的概念或由代码强制执行的约定。它是一个一等公民的概念,系统本身可以理解和执行。Sui 上的每个对象都有一个清晰的所有权模型,该模型决定了谁可以将该对象包含在事务中,涉及该对象的事务是否会与其他事务冲突,以及系统可以在执行期间安全地利用多少并行性。

从高层次来看,Sui 对象分为三个基本的所有权类别:

  • 拥有的对象:由单个地址控制。只有所有者才能在事务中使用它们。

  • 共享对象:任何人都可以访问,更新会进行协调,以确保并发下的正确性。

  • 不可变对象:永久冻结,所有人都可以读取,没有更改的可能性。

除了这些之外,你可能会遇到一些派生的所有权模式:

  • 包装的对象:作为另一个对象的成员的对象,允许将复杂的状态组合在一起并作为一个单元移动。

  • 动态字段(子)对象:逻辑上附加到父对象的对象,并通过父对象而不是直接访问。

  • 参与方对象:一种共享对象,其访问权限仅限于一组预定义的参与者,从而实现协调的更新,而无需向所有人开放该对象。

这些模式建立在相同的原始所有权之上。这与 Solidity 非常不同,在 Solidity 中,所有权通常作为合约内部的逻辑来实现。

为简单起见,本指南侧重于三个核心的所有权类型,这些类型足以理解大多数 Sui 程序和性能特征。

关于 Sui 中地址的快速说明

在继续之前,有助于澄清地址在 Sui 上的含义。如果你来自以太坊,你可以大致以相同的方式看待帐户地址:它们代表用户或帐户,它们由私钥控制,并且钱包管理它们。这些是拥有对象并授权事务的地址。

但是,Sui 还使用相同的地址格式来表示对象 ID。换句话说,你看到的并非每个地址都对应于用户帐户;某些地址唯一标识链上的对象。在本节中,当我们谈论地址拥有对象时,我们特别指的是帐户地址,而不是对象 ID。

这种区别在概念上很重要,但在实践中,它强化了相同的想法:Sui 上的所有权始终是明确且明确的。对象要么由地址拥有,要么是共享的,要么是不可变的,并且系统直接强制执行这一点。

还值得注意的是,由于对象 ID 是地址,因此对象地址可以通过转移到对象 (TTO) 接收其他对象。以后可以通过 transfer::receive 接口显式访问这些对象。这是一种强大的组合机制,但它超出了本指南的范围,本指南侧重于核心所有权模型。

拥有的对象:默认情况

Sui 上最常见的所有权形式是地址拥有的对象。拥有的对象属于一个地址,并且只允许该地址将其用作事务的输入。

如果 Alice 拥有一把剑,只有 Alice 才能:

  • 将其包含在事务中

  • 将其转移给其他人

  • 改变它(如果规则允许)

此时代码中不需要访问检查。如果 Alice 不拥有该对象,则事务根本无法通过验证。

此模型简单、易于推理且速度极快。仅接触拥有的对象的事务不需要经过共识,可以立即执行。

对于许多用例,例如用户余额、NFT、库存物品,这就是你所需要的全部。

共享对象:当多个用户需要访问时

有时,单个所有者是不够的。你可能希望多个用户与同一状态进行交互:流动性池、游戏板或注册表。对于这些情况,Sui 提供了共享对象。

共享对象被显式标记为共享,并且可以由任何人读取或修改。共享对象是不可逆的;一旦共享,它就无法恢复为单个所有者对象。

共享对象:

  • 可以由任何人读取和修改,

  • 当多个事务尝试更新它们时,可能需要协调,

  • 如果被大量访问,可能会变得有争议。

重要的是要注意,共享对象本身并不比拥有的对象更昂贵。但是,由于它们可以被任何人访问,因此它们更有可能遇到拥塞。当许多事务尝试接触同一个共享对象时,排序变得必要,并且成本可能会因此而增加。

因此,应有意识地使用共享对象,仅用于系统中真正需要多个参与方共享访问权限的部分。

不可变对象:写入一次,永久读取

最终的核心所有权模型是不可变对象。

一旦对象变得不可变,就永远无法再次修改。

不可变对象:

  • 没有所有者

  • 可以由任何人阅读

  • 永远不需要共识

  • 保证永远不会改变

它们非常适合配置数据、规则、常量或参考信息。由于不可变对象无法修改,因此它们与地址拥有的对象一样快速且无冲突,并且通常更易于推理。

简要说明:Sui 上的“共识”是什么意思

如果你来自以太坊,即使是一个简单的 ETH 转移也会与所有其他内容争夺相同的全局执行顺序。Sui 采用不同的方法。仅当事务接触到相同的对象时,共识才需要对事务进行排序。这是关键的想法:

  • 每个事务都显式声明它将读取或修改的对象。

  • 如果两个事务在输入上没有重叠,则不需要相对于彼此进行排序。

  • 验证者可以独立地并行验证和执行这些事务,即使它们是同一共识流程的一部分。

所有权使这一切成为可能。仅接触地址拥有的或不可变对象的事务在构造上是无冲突的。对于谁可以改变对象没有歧义,也没有竞争性更新的风险。因此,这些事务可以在整个网络中并行验证和执行。当事务涉及共享对象时,排序可能变得必要,因为多个参与方可能尝试更新相同的状态。在这些情况下,共识建立了一个单一的、双方都认可的更新顺序,以确保正确性。早期版本的 Sui 描述了单独的“快速路径”用于处理拥有的对象事务。这种区别不再存在。相反,所有事务都通过共识,但仍然利用所有权和输入可见性来最大化并行性并消除整类冲突,例如对象等效。关键的要点是:

在 Sui 上,共识是普遍的,但排序是有条件的。

作为一名开发人员,这为你提供了一个强大的杠杆。通过选择如何对所有权进行建模,你正在塑造事务何时可以独立进行以及你的应用程序可以解锁多少并行性。

4. 资产和资源:通过构建实现安全

到目前为止,我们已经看到,在 Sui 上:

  • 状态存在于对象中

  • 对象具有显式的所有权

  • 所有权决定了谁可以触碰什么,以及何时需要共识

下一个自然的问题可能是:

好的,但是资产是如何运作的?余额、token 和 NFT 如何适应此模型?

这就是 Move 的设计真正开始发挥作用的地方。

资产通常如何在 Solidity 中建模

在 Solidity 中,资产几乎总是间接表示的。

典型的 ERC-20 token 保持如下余额:

从编译器的角度来看,这只是数据:

  • 余额是存储中的数字

  • 转移是算术更新

  • 正确性完全取决于周围的逻辑

随着时间的推移,生态系统已经开发出强大的约定和模式来确保安全。这些包括 checks-effects-interactions、SafeMath(历史上)、仔细的审计等等,但语言本身并不知道这些数字代表稀缺资产。

它们只是整数。

Move 中的资产作为资源

Move 采用不同的方法。

在 Sui 上,资产被建模为资源:具有由类型系统本身强制执行的特殊规则的值。因此,Sui 资产:

  • 不能被复制

  • 不能被隐式销毁

  • 必须始终移动到某个地方

这些规则不是约定或最佳实践。它们在编译时和 VM 级别强制执行。如果你忘记处理资源,代码将无法编译。

这是一个微妙的转变,但很重要:

  • 资产不再是入账的,而是直接处理的

Coin是你拥有的Object

我们来看看 coin 在 Sui 上的运作方式。

不是合约保留余额的内部映射,而是每个 coin 都是一个 object,由地址拥有。如果你有余额,那是因为你拥有一个或多个 coin 对象。

铸造新 coin 如下所示:

这里发生了一些重要的事情:

  • 没有更新全局余额映射

  • 系统创建了一个真正的 coin 对象

  • 该对象的所有权被显式转移给接收者

💡在撰写本文时,Sui 正在积极探索一种更符合人体工程学的模型,称为地址余额,该模型为每个(地址、货币)对引入了一个规范余额,同时保留 Sui 以对象为中心的执行模型和并行性。有关更多详细信息,请参阅SIP-58:Sui 地址余额

合约不拥有用户余额。用户拥有。

合约控制的唯一事情是谁被允许铸造,这是通过拥有 TreasuryCap 来强制执行的。这是一个特殊的 object,称为 capability,它代表 token 的铸造权限。谁拥有该 object 谁就可以铸造该类型的新 coin。

稍后我们将更深入地讨论 capability。现在,只需将它们识别为 Sui 安全模型背后的核心构建块之一就足够了。

为什么这么重要

将资产建模为资源有一些非常实际的后果:

  • 资产不能被静默删除或丢失

  • 转移必须是明确和完整的

  • 不变量更容易推理

Solidity 中常见的许多 Bug 在这里根本没有存在的空间。不是因为开发者更加小心,而是因为该语言拒绝表达这些错误。

对于 Solidity 开发者来说,起初这可能会让人感到受限制。但是一旦你习惯了它,你就会意识到你已经内化的许多“最佳实践”现在默认情况下是保证。

一个微妙但重要的转变

在 EVM 世界中,合约负责:

  • 跟踪余额

  • 强制执行不变量

  • 谨防滥用

在 Sui 上,合约(或者更确切地说,模块)主要定义对象上的规则:

  • 如何创建它们

  • 如何转换它们

  • 谁被允许执行某些操作

数据本身是独立存在的,由用户拥有或显式共享。

一旦你以这种方式看待资产,作为对象和资源而不是映射中的条目,那么 Sui 的许多设计就开始感觉不那么“不同”,而更像是不可避免的。

Capabilities 而不是权限

让我们现在回到 CAPABILITIES。如果你习惯了 Solidity,那么访问控制可能看起来像这样:

mapping(address => bool) public isAdmin;

modifier onlyAdmin() {
    require(isAdmin[msg.sender], "Not an admin");
    _;
}

function doSomething() public onlyAdmin {
    // ...
}

在这里,权限是从地址推断出来的,并且通过合约内部的有条件逻辑来强制执行。

Move 采用不同的方法。

在 Move 和 Sui 中,特权操作通常使用 capabilities 来保护。capability 是一个特殊的 object,它表示执行特定操作的权利。如果你拥有 capability,你就可以执行该操作。如果你没有,就不能。没有回退逻辑或替代路径。

我们之前看到的 TreasuryCap 是这个想法的具体例子。

要铸造 coin,你必须拥有相应的 TreasuryCap 对象。如果你没有,你根本无法调用铸造函数。有:

  • 没有地址检查会被忘记

  • 没有修饰符可以绕过

  • 没有办法“伪造”授权

事务将在任何代码执行之前失败。

这种模式被称为基于 capability 的访问控制,它贯穿于 Sui:

  • 铸造和销毁资产

  • 管理员或升级权限

  • 协议配置

  • 一次性或有限的权限

系统不检查你是谁,而是检查你持有什么。

对于 Solidity 开发者来说,一个有用的思考方式是:

Capabilities 就像表示为对象的不可伪造的密钥。

它们可以被转移、共享或有意识地销毁,但它们不能被复制或猜测。

5. 下一步

现在,资产已扎根于对象和所有权模型中,你现在拥有了解 Sui 上开发看起来像什么所需的基础;Sui 是一款以其速度、安全性和开发者体验而闻名的区块链。

请继续关注我们本文的第 2 部分,我们将通过演练与 Sui 开发相关的安全、并行性和可用工具方法的相关注意事项来完成所有内容。

  • 原文链接: x.com/OpenZeppelin/statu...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。