智能合约开发语言 — Move 与 Rust 的对比(#1)

翻译一篇Move与Solana上智能合约开发对比的文章,Move是一种用于智能合约开发的新颖编程语言, 我与Solana上使用的基于Rust的开发的模型进行了一些比较。

翻译一篇 Move 与 Solana 上智能合约开发对比的文章, 原文 非常长, 我计划很为两个部分,这篇包含前 4 节, 以下是原文翻译。

Move是一种用于智能合约开发的新颖编程语言,随着研究 Move的深入,我与 Solana 上使用的基于 Rust 的开发的模型进行了一些比较。

以下这个图,是我对他们的生动对比。

1_X1l36LWT5KKWQlKUJsExPw

1. 背景介绍

最近几周和几个月,围绕 Aptos 和 Sui、新兴的高性能 L1链 以及这些新链背后的 Move 智能合约编程语言引起了很多关注。 一些开发人员已经开始积极转向 Move,他们认为这是智能合约开发的未来,而另一些开发人员则偏谨慎,他们觉得 Move 只是另一种智能合约编程语言而已,因为从根本上说,它并没有提供比现有编程模型更多的功能。 而加密货币投资者则想知道这些新的 L1链 有什么特别之处,以及它们如何与 Solana 抗衡,后者目前是高性能 L1 类别的主要竞争者,尤其是Solana使用 Rust 作为智能合约编程的基础。

但是到目前为止,我们所看到的讨论并没有深入到充分理解这项新技术, 对持有两个不同观点的人都是如此——Move 怀疑论者淡化了 Move,没有充分理解它的一些微妙但重要的方面,而 Move 粉丝则称赞 Move 没有尽头,但未能充分阐明究竟是什么让它如此伟大。这留下了一个很大的中间立场和很多模棱两可的地方,因此一直在关注这个讨论的外部旁观者、加密货币开发人员和投资者无法自信地形成他们的观点。

我最近对 Move 进行了深入研究,并在 Solana 上拥有智能合约开发经验(当然这不在本文讨论之中)。

在本文中,我将深入探讨 Move、其新颖的编程模型、Sui 区块链以及它如何利用 Move 的功能,以及它与 Solana 及其编程模型的比较。

为了突出 Move 的特性,我将 Solana/Rust 与 Sui/Move 进行比较。这样做的主要原因是,当你将某件事与你已经熟悉的另一件事进行比较而不是尝试自己全新理解它时,更容易理解它。值得注意的是,Move 还有其他版本,例如 Aptos Move,它们在某些事情上略有不同。本文的重点不是讨论 Move 不同版本之间的细微差别,而是展示 Move 的一般优势以及它与 Solana 编程模型的比较,因此为简单起见,我决定通篇只使用一种版本( Sui Move) 。因此,我在本文中介绍的某些 Move 概念(即对象和相关功能)仅适用于 Move 的 Sui 版本,而不适用于其他版本。虽然 Move 的其他版本不一定具有这些概念,但它们使用不同的机制(例如全局存储)实现相同的功能。但即便如此,本文中讨论的所有主要 Move 优势都适用于所有 Move 集成方案(原生支持 Move 字节码),包括 Aptos。我选择 Sui 的原因很简单,因为我对它更熟悉,而且我觉得它更直观,更容易以文章的形式呈现。

重申一下,尽管我在本文中对 Solana 和 Sui 进行了比较,但我的意图并不是贬低任何人或任何事情,而只是强调这些技术的不同面以及它们的各种优点和权衡,以便于理解

2. Solana 编程模型

为了充分理解本文中的要点,需要对 Solana 编程模型有一定的了解。 如果你不熟悉 Solana 的编程模型,我建议你阅读我关于 Solana 智能合约编程的文章,其中涵盖了此模型所需的所有概念。

我在这里做一个简短的总结,如果你已经熟悉 Solana 的编程模型,则可以跳过本章

在 Solana 上,程序(智能合约)是无状态的(stateless),它们不能自行访问(读取或写入)在交易中的任何状态。 要访问或保持状态,程序需要使用帐户(accounts)。 每个账户都有一个唯一的地址(Ed25519 密钥对的公钥),可以存储任意数据。

我们可以将 Solana 的帐户空间视为全局键值存储,其中键是帐户地址(公钥),值是帐户数据。 然后程序的运行通过读取和修改此键值存储之上的值。

账户有所有权(ownership)的概念。 每个帐户由一个(并且只有一个)程序拥有。 当帐户归程序所有时,程序可以更改其数据。 程序不允许改变他们不拥有的帐户(但允许读取)。 这些检查由运行时动态完成,通过比较程序执行前后的帐户状态,如果发生非法变更,则交易失败。

每个账户还有一个与之关联的私钥(对应的公钥是它的地址),有权访问该私钥的用户可以用它签署交易。 使用这种机制,在 Solana 智能合约中实现了权限和所有权功能——例如为了访问某些资金,智能合约可以要求用户提供必要的签名。

另外,在进行程序调用时,客户端需要指定该程序在调用期间将访问哪些帐户。 这样交易处理运行时可以调度未重叠交易并行执行,同时保证数据一致性。 这是 Solana 实现高吞吐量的设计特点之一。

程序(智能合约)可以通过 CPI 调用(CPI call)来调用其他程序。 这些调用的工作方式与来自客户端的调用几乎一样,调用程序需要指定被调用程序将访问的帐户,并且被调用程序将执行所有相同的输入检查,就好像它是从客户端调用的一样( 它不信任调用程序)。

PDA (程序衍生地址)帐户是一种特殊的帐户,它使程序能够在不拥有或存储私钥的情况下提供帐户签名。 PDA 保证只有为其生成 PDA 的程序才能为其创建签名(而不是其他用户和程序)。 当一个程序需要通过 CPI 调用与另一个程序交互并提供权限(例如,实现一个金库Vault)时,这很有用。 PDA 保证除了程序之外没有人可以直接访问程序的资源。 PDA 也可用于在确定的地址创建帐户。

这些是 Solana 上安全智能合约编程的基本构建模块。 同样,如果你觉得这些概念中的任何一个不清楚,我强烈建议你阅读我关于 Solana 智能合约编程的文章。里面更深入地介绍了这些概念。

在某种程度上,你可以将 Solana 程序视为操作系统中的程序,将帐户视为文件,任何人都可以在其中自由执行任何程序,甚至部署自己的程序。 当程序(智能合约)运行时,它们将读取和写入文件(帐户)。 所有文件都可供所有程序读取,但只有对文件具有所有权权限的程序才能写入。 程序也可以执行其他程序,但它们不以任何方式相互信任——无论谁执行程序,它都需要假设输入有潜在的破坏性。 由于任何人都可以在全局范围内访问此操作系统,因此将本机签名验证支持添加到了程序中,以便为用户启用权限和所有权功能……这不是一个完美的类比,但它是一个有趣的类比。

3. Move 编程模型

在 Move 中,智能合约作为模块(module)发布。 模块由函数和自定义类型(结构 struct)组成。 结构由可以是原始类型(u8、u64、bool…)或其他结构作为字段组成。 函数可以调用其他函数——可以在同一个模块中,也可以在其他模块中(如果它们是公开可访问的)。

如果将其放在 Solana 的上下文中,就好像所有智能合约都作为模块发布在单个程序中。 意味着所有智能合约(模块)都包含在同一类型系统中,并且可以直接相互调用,而无需通过中间 API 或接口。 这是非常重要的,其含义将在整篇文章中详细讨论。

3.1 对象

在我们继续之前,要提醒注意的是以下对象概念适用于 Move 的 Sui 改编版本,在 Move 的其他集成项目中可能会略有不同(例如 Aptos 或 Diem/core Move)。 即便如此,在其他 Move 版本中,也有类似的解决方案可以实现相同的目标(状态持久性),但并不太相似。 我在这里介绍 Sui 对象的主要原因是因为本文后面的代码示例基于 Move 的 Sui 版本,还因为其对象概念更易于理解(对比 Diem/core Move 中的全局存储机制)。 但重要的是,本文讨论的 Move 的所有主要优点都适用于所有 Move 集成(原生支持 Move 字节码)项目,包括 Aptos。 要了解 Sui 和 Aptos 版本的不同之处,我们为什么创建 Sui Move文章和 Move on Aptos 文档页面是一个好的开始。

对象是由运行时存储的结构实例(struct instance),并跨交易保持状态。

存在三种不同类型的对象(在 Sui 中):

  • 拥有者对象(owned objects)
  • 共享对象(shared objects)
  • 不可变对象(immutable objects)

拥有者对象是属于用户的对象。 只有拥有该对象的用户才能在交易中使用它。 所有权元数据是完全透明的并由运行时处理。 它是使用公钥加密实现的——每个拥有者对象都与一个公钥相关联(存储在运行时对象的元数据中),并且任何时候你想在交易中使用一个对象,你都需要提供相应的签名(现在支持 Ed25519 ECDSA 和 K-of-N 多重签名支持即将推出)。

共享对象类似于拥有者对象,但它们没有与之关联的所有者。 因此,你不必拥有任何私钥就可以在交易中使用它们(任何人都可以使用它们)。 任何拥有者对象都可以(由其所有者)共享,并且一旦共享了一个对象,它将永远保持共享状态——它永远不会被转移或再次成为拥有者对象。

不可变对象: 一旦一个对象被标记为不可变,它的字段就不能再被修改。 与共享对象类似,它们没有所有者,任何人都可以使用。

Move 编程模型非常直观和简单。 每个智能合约都是一个模块,由函数和结构定义组成。 结构在函数中实例化,可以通过函数调用传递给其他模块。 为了使结构在交易中持久化,我们将其转换为可以拥有者、共享或不可变对象而已(特定于 Sui,这在其他 Move 变体版本中略有不同) !

4. Move的安全性

所以我们已经在 Move 中看到了:

  • 你可以将你拥有(或共享)的任何对象传递给任何模块中的任何函数
  • 任何人都可以发布(可能是破坏性的)模块
  • 模块没有拥有结构的概念(这将赋予所有者模块有唯一的权力来修改它,就像Solana的账户一样),结构可以流入其他模块并嵌入到其他结构中

现在的问题是,是什么让它安全? 有什么会阻止某人发布破坏性模块、获取共享对象(如 AMM 池)并将其发送到破坏性模块,然后该模块将耗尽资金?

在 Solana 中,有一个帐户所有权的概念,其中只有拥有帐户的程序才被允许对其进行更改。 但是在 Move 中,没有模块拥有对象的概念,你可以将对象发送到任意模块中——不仅是对对象的引用,而且是整个对象本身,按值的传递方式。 并且运行时没有进行特定检查以确保该对象在通过不受信任的模块时没有被非法修改。 那么是什么保证了这个对象的安全呢? 如何保证不可信代码不会滥用该对象?

好吧,这就是 Move 的新颖性……让我们谈谈资源(resources)。

4.1. Struct (结构)

定义一个结构类型和其他语言类似:

struct Foo {
  x: u64,
  y: bool
}

这也是你在 Rust 中定义结构的方式。 但是 Move 中的结构有一些独特之处,那就是在 Move 中,与传统编程语言相比,模块对如何使用和不能使用它们的类型有更多的控制。 如上面代码片段中定义的结构将具有以下限制:

  • 它只能在定义结构的模块内实例化(“打包”)和销毁(“解包”) - 即你不能从任何其他模块的任何函数内部实例化或销毁结构实例
  • 结构实例的字段只能从其模块内访问(及更改)
  • 不能在其模块之外克隆或复制结构实例
  • 不能将结构实例存储在其他结构实例的字段中

这意味着如果你在另一个模块的函数中处理此结构的实例,你将无法改变其字段、克隆它、将其存储在另一个结构的字段中或删除它(你必须通过函数调用将其传递给其他地方)。 结构所在的模块可以实现一些函数,这些函数可以从我们的模块中调用,但除此之外,我们不能直接为外部类型做任何这些事情。 这使模块可以完全控制它们的类型如何使用和不能如何使用。

现在,似乎有了这些限制,失去了很多灵活性。 这是真的——在传统编程中处理这样的结构会非常麻烦,但事实上,这正是我们在智能合约中想要的。 毕竟,智能合约开发是关于对数字资产(资源)进行编程。 如果你看一下上面描述的结构,这正是它的本质——它是一种资源。 不能凭空随意创造,不能复制,不能随意破坏。 所以我们确实在这里失去了一些灵活性,但失去的灵活性正是我们想要失去的那种灵活性。 这使得使用资源变得直观且安全。

此外,Move 允许我们通过向结构添加能力(abilities)来放松其中的一些限制。 有四种能力:key、store、copy 和 drop。 你可以将这些能力的任意组合添加到结构中(作为一中修饰限制符):

struct Foo has key, store, copy, drop {
  id: UID,
  x: u64,
  y: bool
}

他们的含义是:

  • key -- 允许结构成为对象(特定于 Sui,core Move 略有不同)。 如前所述,对象是持久化的,并且在拥有者对象的情况下,需要在智能合约调用中使用用户签名。 使用 key 能力时,struct 的第一个字段必须是 UID 类型的对象 ID。 以便为其提供一个全局唯一 ID,用于引用它。
  • store -- 允许将结构作为字段嵌入到另一个结构中
  • copy -- 允许从任何地方任意复制/克隆结构
  • drop -- 允许从任何地方任意销毁结构

本质上,Move 中的每个结构都是默认的资源(resource)。 能力使我们能够细化地放宽这些限制,并使它们的行为更像传统的结构。

4.2 Coin

为了更好地说明这一点,让我们以 Coin 类型为例。 Coin 在 Sui 中实现了类似 ERC20 / SPL Token 的功能,并且它是 Sui Move Library 的一部分。 它是这样定义的:

// coin.move
struct Coin<phantom T> has key, store {
    id: UID,
    balance: Balance<T>
}
// balance.move
struct Balance<phantom T> has store {
    value: u64
}

你可以在 Sui 代码库中找到完整的模块实现。

Coin 类型具有keystore 能力。 key 意味着它可以用作对象。 这允许用户直接拥有Coin(作为顶级对象)。 当你拥有一个Coin时,除了你之外没有人可以在交易中引用它(更别说使用它了)。 store 意味着 Coin 可以作为字段嵌入到另一个结构中。 这对于可组合性很有用。

由于没有drop能力,因此 Coin 不会在函数中意外销毁。 这是一个非常好的功能——这意味着你不会意外丢失Coin。 如果你正在实现一个接收 Coin 作为参数的函数,那么在函数结束时,你需要明确地对它做一些事情——要么将其传输给用户,将其嵌入到另一个对象中,要么将通过调用将其发送到另一个函数(再次需要对它做些什么)。 当然可以通过调用 coin 模块中的 coin::burn 函数来销毁 Coin,但是你需要有目的地这样做(你不会意外地这样使用)。

没有克隆能力(copy)意味着没有人可以复制代币,从而凭空创造新的供应。 创建新供应可以通过 coin::mint 函数完成,并且只能由该币的具有财政能力对象的所有者调用(该对象最初转移给货币创建者)。

另外,请注意,由于泛型,每个不同的代币都会有自己独特的类型。 而且由于两个代币只能通过 coin::join 函数相加(而不是直接访问它们的字段),这意味着根本不可能添加不同类型代币的值(代币 A + 代币 B) - 没有这样签名的函数功能。 类型系统在这里保护我们免于做坏账。

在 Move 中,资源的资源安全性由其类型定义。 考虑到 Move 具有全局类型系统,这可以实现更自然和更安全的编程模型,其中资源可以直接传入和传出不受信任的代码,同时保持其安全性。 乍一看,这似乎没什么大不了的,但实际上,这对智能合约的可组合性、人机工程学和安全性有很大的好处。 这将在第 5 节中更深入地讨论。

4.3. 字节码验证

如前所述,Move 智能合约是作为模块发布的。 并且任何人都可以创建任意模块并将其上传到区块链以供任何人执行。 我们还看到 Move 对如何使用结构有一定的规则。

那么是什么保证了这些规则被任意模块所遵守呢? 有什么阻止某人上传带有特制字节码的模块,例如接收一个 Coin 对象,然后通过直接改变其内部字段来绕过这些规则? 通过这样做,你可以非法夸大你拥有的代币数量。 字节码语法允许做到这一点。

字节码验证可以防止这种滥用。 Move 验证器是一个静态分析工具,它分析 Move 字节码并确定它是否遵守所需的类型、内存和资源安全规则。 所有上传到链上的代码都需要通过验证器。 当你尝试将 Move 模块上传到链上时,节点和验证器将首先通过验证器运行它,然后才能被允许提交。 如果任何模块试图绕过 Move 的安全规则,它将被验证器拒绝并且不会被发布。 这就是为什么用特制的字节码不可能破坏类型或资源安全规则的原因——验证器会阻止你将这样的模块上传到链上!

Move 字节码和验证器是 Move 的核心新颖性。 这使得以资源为中心的直观编程模型成为可能。 关键是它允许结构化类型跨越信任边界传递而没有失去其完整性

在 Solana 上,智能合约是程序,而在 Move 中,它们是模块。 这似乎只是语义上的差异,但事实并非如此,它具有巨大的意义。 不同之处在于,在 Solana 上没有跨程序边界的类型安全——每个程序通过从原始帐户数据手动解码来加载实例,就需要手动进行关键安全检查。 也没有本地资源安全。 相反,资源安全必须由每个智能合约单独实施。 这确实允许足够的可编程性,但与 Move 的模型相比,它极大地阻碍了可组合性和人机工程学,Move 的模型对资源有原生支持,并且它们可以安全地流入和流出不受信任的代码。

在 Move 中,类型确实存在于模块之间——类型系统是全局的。 这意味着不需要 CPI 调用、帐户编码/解码、帐户所有权检查等——你只需使用参数直接调用另一个模块中的函数。 跨智能合约的类型和资源安全由编译/发布时的字节码验证来保证,不需要在智能合约级别实现,然后在 Solana 上那样在运行时进行检查。

下一篇,会使用一些开发实例来对两者进行对比。

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 2022-09-08 16:07
  • 阅读 ( 1588 )
  • 学分 ( 168 )
  • 分类:Sui

0 条评论

请先 登录 后评论
MoveMoon
MoveMoon

7 篇文章, 651 学分