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

2022-11-07 11:03:41 浏览数 (1)

本文作者:MoveMoon[1]

接着上一篇[2], 我们介绍了 Solana 编程模型及 Move 编程模型,以及 Move 如何确保安全性, 这一篇,我们继续更深入的对比 Solana 上编程与 Move 编程,这一篇我们会介绍 Solana 上一写可组合性的缺陷,以及相同的功能使用在 Solana 下因为需要进行账号检查,导致代码冗长,并讨论了在 Solana 上使用 Move 开发的一些可能性, 文章非常深入且精彩。

5. Solana 与 Move

现在我们已经了解了 Move 编程的工作原理以及它从根本上安全的原因,让我们从可组合性、人机工程学和安全性的角度更深入地了解它对智能合约编程的影响。在这里,我将比较 Move/Sui 开发与 EVM 和 Rust/Solana/Anchor,以帮助了解 Move 的编程模型带来的好处。

5.1. 闪电贷

让我们从闪电贷示例开始。闪电贷是 DeFi 中的一种贷款,借出的金额必须在借出的同一笔交易中偿还。这样做的主要好处是,由于交易是原子的,可以进行完全无抵押贷款。这可以实现例如无需本金即可在资产之间进行套利。

实现这一点的主要困难是——你如何从闪电贷智能合约中保证借出的金额将在同一笔交易中得到偿还?为了使贷款能够无抵押,交易需要是原子的——即如果借出的金额没有在同一笔交易中归还,整个交易需要失败。

EVM 闪电贷

EVM 具有动态调度,因此可以使用可重入实现,如下所示:

  • 闪电贷用户创建并上传自定义智能合约,调用时将通过调用将控制权传递给闪电贷智能合约
  • 闪电贷智能合约将请求的借款金额发送到自定义智能合约并调用自定义智能合约中的 executeOperation()回调函数
  • 然后,自定义智能合约将使用收到的借出金额来执行其所需的操作(例如套利)
  • 自定义智能合约完成操作后,需要将借出的金额返还给闪电贷智能合约
  • 至此,自定义智能合约的 executionOperation() 将完成,控制权将返回到闪电贷智能合约,该合约将检查借出金额是否已正确归还
  • 如果自定义智能合约没有正确返回借出的金额,整个交易将失败

这很好地实现了所需的功能,但问题是它依赖于可重入性,我们非常希望在智能合约编程中禁止它成为可能。这是因为重入本质上是非常危险的[3],并且是许多漏洞的根本原因,包括臭名昭著的 DAO hack[4]

Solana 闪电贷

Solana 在这里做得更好,因为它不允许重入。但是如果没有可重入性和闪电贷智能合约回调自定义智能合约的可能性,你如何在 Solana 上实施闪电贷?好吧,这要归功于指令内省[5]。在 Solana 上,每笔交易都包含多条指令(智能合约调用),你可以从任何指令中检查同一交易中存在的其他指令(它们的程序 ID、指令数据和账户)。这使得实施闪电贷款成为可能,如下所示:

  • 闪电贷智能合约实现借款和还款指令
  • 用户通过在同一笔交易中将借入和还款指令调用堆叠在一起来创建闪电贷交易。借入指令在执行时将使用指令自省检查是否在同一交易中稍后安排还款指令。如果还款指令调用不存在或无效,则此阶段交易将失败
  • 在借入和还款调用之间,借入的资金可以被介于两者之间的任何其他指令任意使用
  • 在交易结束时,还款指令调用会将资金返还给闪电贷智能合约(在借入指令中使用自省检查该指令的存在)

出于好奇心,这里有一个原型实现(链接[6]

该解决方案运行良好,但仍不理想。指令自省在某种程度上是一种特殊情况,而不是在 Solana 中常用的东西,因此它的使用在开发人员需要掌握的概念数量和实现本身的技术方面都有开销,因为有一些需要适当考虑的小差别。还有一个技术限制——因为 repay 指令需要在交易中静态存在,所以不可能在交易执行期间通过 CPI 调用动态调用 repay。这个(几乎)不会破坏交易,但在将其与其他智能合约集成时,它在一定程度上限制了代码的灵活性,并且还将更多的复杂性推到了客户端。

还有另一种方法来实现没有指令内省的闪电借贷--你可以让借贷智能合约中的闪电借贷指令带着资金做一个 CPI 调用到你的任意智能合约中,然后在 CPI 调用返回后会检查资金是否已经正确返回。这种方法是由 SPL 借贷程序实现的(链接[7])。但这有一个不同的问题--没有通用的方法可以在一次调用中聚合(合并)多个贷款。

Move 闪电贷

Move 禁止动态调度和重入,但与 Solana 不同,它有一个非常简单和自然的闪电贷解决方案。Move 的线性类型系统允许创建保证在交易执行期间仅使用一次的结构。这就是所谓的“烫手山芋(hot-potato)[8]”模式——一种没有 key、store、copy 和 drop 能力的结构。这种模式的模块通常同时具有一个实例化函数和一个销毁函数。由于“烫手山芋”结构没有 drop、key 或 store 能力,因此它保证了需要调用“销毁”函数来使用掉它(注:因为不能凭空多出来一个实例)。即使我们可以将它传递给任何模块中的任意数量的其他函数,最终它还是需要在“销毁”函数中结束。这是因为没有其他方法可以摆脱它,并且验证者要求在交易结束时对其进行处理(它不能随意销毁,因为没有销毁能力)。

让我们看看如何利用它来实施闪电贷:

  • 闪电贷智能合约实现一个 “烫手山芋” Receipt 收据结构
  • 当通过调用贷款函数进行贷款时,它将向调用者发送两个对象——请求的资金(一个代币)和一个收据(Receipt),它是要偿还的贷款金额的记录
  • 然后借款人可以将收到的资金用于其所需的操作(例如套利)
  • 借款人完成其预期操作后,需要调用 repay 函数,该函数将接收借入资金和收据作为参数。这个函数保证在同一个交易中被调用,因为调用者没有其他方法可以销毁 Receipt 实例(它不允许被销毁或嵌入到另一个对象中,这是由验证者断言的)
  • 还款函数通过读取收据中嵌入的贷款信息来检查是否退回了正确的金额。

可以在这里找到一个示例[9]实现。

Move 的资源安全特性使 Move 中的闪电贷成为可能,而无需使用重入或内省。他们保证收据不能被不受信任的代码修改,并且需要在交易结束时返回到还款函数。有了这个,我们可以保证在同一笔交易中返回正确数量的资金。

闪电贷功能是完全使用基本语言原语实现的,Move 实现不会像 Solana 实现需要特殊处理的交易那样增添额外的集成开销。此外,也没有把复杂性被推给客户端。

5.2. 铸币权限锁

为了进一步突出 Move 编程模型的优势,我在 Solana (Anchor) 和 Sui Move 中都实现了“Mint Authority Lock”智能合约进行比较。

“铸币权限锁”智能合约所做的是将代币铸币的功能扩展为允许多个白名单方铸币,而不仅仅是一个铸币者。智能合约所需的功能如下(用 Solana 和 Sui 实现):

  • 原始代币铸币者创建了一个“铸币锁”,这将使智能合约能够规范代币的铸币。调用者是”铸币锁“的管理员。
  • 然后,管理员可以给第三方添加额外的铸币权限,并允许他们在需要时使用锁铸币。
  • 每个铸币权限都有每日可铸币数量的限制。
  • 管理员可以随时禁止(和取消禁止)任何铸币权限。
  • 管理员权限可以转移给另一方。

该智能合约可用于例如原始铸币权限(管理员)仍保留铸币厂控制权的情况下,将代币的铸币功能提供给其他用户或智能合约。如果没有这个,我们必须将铸币的完全控制权交给另一方,这并不理想,因为我们必须相信它不会滥用它。并且不可能向多方授予许可。

可以在此处 (Solana[10]) 和此处 (Sui[11]) 找到这些智能合约的完整实现。

现在让我们看一下代码,看看实现有何不同。以下是此智能合约的完整 Solana 和 Sui 实现的并排代码屏幕截图:

你会立刻注意到,对于相同的功能,Solana 实现的大小是 Sui 的两倍多(230 对 104 行)。这很重要,因为更少的代码通常意味着更少的错误和更短的开发时间。

那么这些额外的代码行是从哪里来的 Solana 呢?如果我们仔细查看 Solana 代码,我们可以将其分为两部分——指令实现(智能合约逻辑)和账户检查。指令实现与我们在 Sui 上的实现有点接近——136 行 与 Sui 上的 104。额外代码行可归因于两个 CPI 调用的样板文件(每个约 10 行)。最显着的区别是由于帐户检查(上面屏幕截图中标记为红色的部分)在 Solana 上是必需的(实际上很关键),但在 Move 中不需要。账户检查占约该智能合约(91 行)的 40%。

Move 不需要帐户检查。这不仅是有益的,因为代码减少了。消除进行帐户检查的必要性非常重要,因为事实证明正确实现这些检查非常棘手,而且如果你在那里犯了一个错误,通常会导致严重的漏洞和用户资金的损失。事实上,一些最大(就资金损失而言)的 Solana 智能合约漏洞是由不正确的账户检查引起的账户替换攻击

  • Wormhole (资金损失 $336M ) -- https://rekt.news/wormhole-rekt/
  • Cashio (资金损失 $48M) -- https://rekt.news/cashio-rekt/
  • Crema Finance (资金损失 $8.8M) -- https://rekt.news/crema-finance-rekt/

显然,移除这些检测将是一件大事。

那么 Move 是如何做到没有这些检查而又同样安全的呢?让我们仔细看看这些检查的实际作用。以下是 "mint_to "指令所需的账户检查(授权者通过锁控制来铸造代币)。

有 6 个检查(用红色突出显示):

  1. 检查所提供的锁账户是否为该智能合约所拥有,并且是 MintLock 类型。有必要传入锁,因为它被用于 CPI 调用代币程序进行铸币(它存储了权限)。
  2. 检查所提供的铸币授权账户是否属于所提供的锁。铸币厂授权账户持有授权状态(其公钥,是否被禁止,等等)。
  3. 检查指令调用者是否拥有该权限的所需密钥(所需权限签署了该交易)。
  4. 需要传入代币目标账户,因为代币程序将在 CPI 调用中修改它(增加余额)。铸币厂检查在这里并不是严格必要的,因为如果传入了错误的账户,CPI 调用就会失败,但还是要做检查,这是很好的做法。
  5. 与 4 类似。
  6. 检查代币项目账户是否正确传入。

我们可以看到,账户检查(在这个例子中)分为这五类:

  • 帐户所有权检查(1,2,4,5)
  • 帐户类型检查(1、2、4、5)
  • 账户实例检查(某种账户类型的正确实例是否被传入)(2,5)
  • 账户签名检查(3)
  • 程序账户地址检查(6)

注意。这并没有涵盖所有类型的账户检查,但足以说明问题。

在 Move 中,虽然没有账户检查或任何类似的要求,只是有函数签名:

mint_balance函数只需要四个参数。在这四个参数中,只有lockcap代表对象(有点类似于账户)。

那么,在 Solana 中,我们需要声明 6 个账户,还需要手动实现对它们的各种检查,而在 Move 中,我们只需要传入 2 个对象,而且不需要明确的检查,这怎么可能呢?

好吧,在 Move 中,有些检查是由运行时透明地完成的,有些是由验证器在编译时静态地完成的,而有些则是在结构上根本不需要的

  • 账户所有权检查 -- 由于 Move 的类型系统的设计而没有必要。一个 Move 结构只能通过在其模块中定义的函数来进行修改,而不能直接修改。字节码验证保证了结构实例可以自由地流入不受信任的代码(其他模块)而不被非法修改。
  • 账户类型检查 -- 没有必要,因为类型是跨合约存在的。类型定义被嵌入到模块二进制文件中(这就是在区块链上发布并由虚拟机执行的内容)。当我们的函数在编译/发布期间被调用时,验证器将检查是否有正确的类型被传递。
  • 账户实例检查 -- 在 Move 中(有时在 Solana 上也是如此),你会在函数主体中做这个。在这个特殊的例子中,这是没有必要的,因为lockcap参数类型的通用类型参数T强制要求传入的cap(铸币能力/权限)对象正确匹配其锁(每个 Coin 类型T只能有一个锁)。
  • 账户签名检查 -- 我们在 Sui 中不直接处理签名问题。对象可以由用户拥有。铸币厂的权限是由铸币厂能力对象的所有权授予的(由管理员创建)。在mint_balance函数中传递对该对象的引用将允许我们进行铸币。拥有的对象只能由其所有者在交易中使用。换句话说,对象的签名检查是由运行时透明地完成的。

本质上,Move 利用字节码验证,以使数字资产的编程模型更加自然。Solana 的模型围绕着账户所有权、签名、CPI 调用、PDA 等。但是,如果我们退一步想一想,我们就会发现,我们并不真的想要处理这些问题。它们与数字资产本身没有任何关系--相反,我们必须使用它们,因为这使我们能够在 Solana 的编程模型中实现所需功能。

在 Solana 上,由于没有字节码验证来保证更细粒度的类型或资源安全,你不能允许任何程序修改任何账户,所以引入账户所有权的概念是必要的。由于类似的原因(没有跨程序调用的类型/资源安全),也没有可以进出程序的用户拥有的对象的概念,相反,我们用账户签名来证明权限。由于有时程序也需要能够提供账户签名,所以我们有 PDA......

虽然你可以在 Solana 上拥有与 Move 相同的跨程序类型和资源安全,但你必须使用低级构建模块(账户签名、PDA...)来手动实现。最终,我们所做的是使用低级原语(primitive)来模拟可编程的资源(线性类型)。而这就是账户检查 -- 它们是实现类型安全和手动建模资源的开销

Move 对资源有一个原生的抽象,并允许我们直接与资源合作,而不需要引入任何低级构建块,如 PDA。跨智能合约边界的类型和资源安全保证由验证器保证,不需要手动实现

5.3. Solana 可组合性的局限性

我想再举一个例子,强调 Solana 上智能合约可合成性的一些痛点。

在 Mint Authority Lock 的例子中看到,与 Sui 相比,我们需要在 Solana 上声明更多的输入(Solana 上的 6 个账户与 Sui 上的 2 个对象的mint_to调用)。显然,处理 6 个账户比处理 2 个对象更麻烦,特别是如果你考虑到还需要为账户实现账户检查。可以说这仍然是可控的,但是当我们开始在一个单一的调用中把多个不同的智能合约组合在一起时会发生什么?

假设我们想创建一个智能合约,做以下事情:

  • 它从 Token Mint Lock 程序(如上一节所述)中获取某个代币铸币授权
  • 当它被调用时,它将使用其授权来铸造用户指定数量的代币,使用 AMM 将其兑换为不同的代币,并在同一指令中将其发送给用户

这个例子的重点是说明 Mint Authority Lock 智能合约和 AMM 智能合约将如何共同组合。这纯粹是一个假设性的例子,可能在现实生活中没有任何用处,但可以用来说明问题 -- 现实生活中组合智能合约的例子与此没有太大区别。

做到这一点的指令调用的账户检查可能看起来像这样:

这就是 17 个账户,每个 CPI 调用(铸币和兑换)大约有 5-6 个加上程序账户。

在 Sui 上,一个等效的函数签名将是这样的:

这只是 3 个对象而已。

那么,我们在 Sui 上传递的对象与在 Solana 上传递的账户相比,怎么会少这么多呢(3 对 17)?

从根本上说,原因是在 Move 中我们能够嵌入(包裹 wrap)它们。类型系统的安全保证使我们能够这样做。

下面是一个 Solana 账户和 Sui 对象之间的比较,各自持有一个 AMM 池的状态,看起来像这样:

我们可以看到,在 Solana 上存储了其他账户的地址(Pubkeys),这就像指针一样,并不存储实际的数据。为了访问这些账户,它们需要被单独传入,我们还需要手动检查是否有正确的账户被传入。在 Move 中,我们能够将结构嵌入到彼此之中,并直接访问它们的值。我们可以混合和匹配来自任何模块的类型,同时它们保留其资源和类型安全保证。这又是由于 Move 的全局类型系统和由字节码验证实现的资源安全而得以实现。

但是,传递大量账户的主要问题甚至不是开发者的效率问题。在组成多个智能合约时,必须传递(从而检查)许多账户,这就造成了相当大的实现复杂性,并具有很大安全隐患。这些账户之间的关系可能相当错综复杂,在某一点上,跟踪所有必要的账户检查以及它们是否已经正确完成变得相当困难。

事实上,这就是我认为在 Cashio 漏洞中发生的情况(4800 万美元)。这里是该漏洞的主要账户检查的细目(不充分)[12]。正如你所看到的,这些账户检查可以变得有些错综复杂。开发者可能有最好的意图来做正确的检查,但在某一点上,精神上的开销变得太大,变得非常容易失误。账户越多,出现错误的机会就越大。

Move 的全局类型系统和更自然的编程模型意味着我们可以在遇到心理开销的极限之前,以更大的安全性推动智能合约的组合

作为一个附带说明,在 Move 与 Rust/Anchor 相比的安全性方面,还有一件事值得考虑,可能不是很明显。这就是 Move 的 TCB(trusted computing base 可信计算基础)比 Rust/Anchor 小得多。较小的 TCB 意味着被信任的、需要进入智能合约编译和执行的组件更少。这就减少了可能影响智能合约的漏洞的表面积--TCB 之外的 bug 不会影响智能合约的安全或保障。

Move 的设计考虑到了减少 TCB 的问题--为了尽可能减少 TCB,做出了许多决定。字节码验证器将 Move 编译器执行的许多检查从 TCB 中移除,而在 Rust/Anchor 中,有更多的组件需要被信任,安全关键错误的表面积要大得多。

6. Solana 上的 Move

很明显,Move 是很了不起的。我们也知道,Solana 的设计允许其他编程语言可为其开发智能合约。现在的问题是--我们能否在 Solana 上实现 Move,以及如何实现?

6.1. Anchor 有全局类型安全?

在我们开始研究 Move on Solana 之前,让我们简单看看 Anchor 并做个小的思想实验。也许我们可以以某种方式升级 Anchor,以提供我们从 Move 中得到的一些好处?也许我们可以获得对跨程序调用的类型安全提供本地支持?毕竟,Anchor 的指令已经类似于 Move 的入口函数。

代码语言:javascript复制
// Function signatures of the "mint to" function from the
// "Mint Authority Lock" smart contract from the previous chapter.

// Sui Move
public entry fun mint_balance<T>(
  lock: &mut TreasuryLock<T>,
  cap: &mut MintCap<T>,
  amount: u64,
  ctx: &mut TxContext
) {

// Anchor
pub fn mint_to(ctx: Context<MintTo>, amount: u64) -> Result<()> {

也许如果我们以某种方式扩展 Anchor,允许账户直接在指令参数中传递。

代码语言:javascript复制
// Anchor modified
pub fn mint_to(
  lock: &mut MintLock,
  authority: &mut MintAuthority,
  amount: u64
) -> Result<()> {

我们就可以避免做账户检查?

在这种情况下,我们希望类型检查由运行时而不是程序来完成--运行时将读取 Anchor 的账户判别器(或类似的方式),并能够检查传入的账户是否符合所需的判别器(Anchor 账户的前 8 个字节)。

但是请记住,Solana 并不对同一程序的不同指令调用进行区分,这是由程序手动实现的(在这种情况下,繁重的工作由 Anchor 完成)。因此,为了做到这一点,运行时必须以某种方式了解不同的指令、它们的签名,并且也有关于类型的信息。

Solana 程序编译为 SBF(Solana Bytecode Format,eBPF 的一种修改),并以这种方式上传到链上(和执行)。而 SBF 本身并没有嵌入任何可以帮助类型或函数信息。但也许我们可以修改 SBF 以允许指令和类型信息被嵌入二进制文件中?那么所需的指令和签名信息就可以由运行时从二进制文件中读取。

我们确实可以这样做。这将是一个相当大的工程,特别是如果你考虑到还需要保持与旧程序的向后兼容,但以下是将得到的好处:

  • 账户所有权和类型检查由运行时完成,而不是在程序(智能合约)中。
  • 对于在编译时已知地址的账户(例如程序账户),我们可以避免从客户端传入它们,而让这些可以由运行时注入。
  • 如果我们也设法将账户约束嵌入到二进制文件中,我们可以进一步减少必须由客户端传入的账户的数量,通过动态加载它们与运行时的递归(基于嵌入的约束信息)。

仍然没有得到好处则有:

  • 嵌入账户:我们仍然必须使用 Pubkeys 来引用其他账户,而不是能够直接嵌入它们。这意味着我们没有摆脱第 5.3 节中描述的账户臃肿的问题。
  • 当进行跨程序调用时,账户类型检查仍然需要在运行时动态进行,而不是像 Move 中那样在编译时静态进行。

注: 请记住,这只是一个思想实验。我并不是说这可以安全地完成,也不是说实现起来有多困难,更不是说这样做的好处值得付出工程努力。

虽然这些好处确实不错,但从智能合约开发的角度来看,它们并没有从根本上改变什么。在运行时而不是程序中进行类型检查可能有一些性能上的好处,而且不必在编译时从客户端手动传递已知地址的账户,在一定程度上改善了工效(这也可以通过工具化来缓解)。但是,虽然这些人机工程学和性能改进确实有帮助,但我们最终仍然在处理 Solana 的编程模型,它本身并没有对理数字资产提供多少帮助--我们仍然没有本地资源安全,我们不能嵌入账户,所以仍然有账户膨胀问题,我们仍然在处理账户签名和 PDA......

理想情况下,我们希望所有的智能合约都生活在一个单一的类型系统中,并且能够像 Move 中那样自由地将对象传入和传出。但由于其他智能合约不能被信任,我们不能直接这样做。为了绕过这一点,Solana 有程序分离和账户所有权--每个程序管理自己的账户,它们通过 CPI 调用进行交互。这很安全,并允许足够的可编程性,但由此产生的编程模型并不理想--没有全局类型系统,没有它也就没有有意义的资源安全。

我们希望有一个自然的编程模型,但同时,我们要处理的是不被信任的代码。虽然在 Solana 上我们可以安全地处理不受信任的代码,但它会对编程模型做出妥协。字节码验证使我们有可能同时拥有这两者。所以看起来没有它,我们确实无法改善编程模型。

6.2. Solana 字节码格式

如前所述,SBF(Solana 字节码格式),即 Solana 智能合约的编译和链上存储格式,是基于 eBPF 的。在 Solana 上使用 eBPF 而不是任何其他字节码格式(如 WASM)的主要动机是,Solana 对安全和高性能的智能合约执行的要求,与 eBPF 设计的内核内的沙盒程序执行的要求是一致的(它也需要安全和高性能)。你可以在这篇Anatoly Yakovenko 的文章[13]中阅读更多关于 Solana 智能合约环境背后的初始设计决策(注意,这篇文章是 2018 年的,其中讨论的一些东西已经过时了)。

从纸面上看,eBPF 似乎是一个可靠的选择。它是高性能的,它是围绕安全设计的,程序的大小和指令的数量是有限的,它有一个字节码验证器......看起来很有前途

但让我们看看这在实践中意味着什么。也许我们可以以某种方式利用 eBPF 验证器来提高我们智能合约的安全性?以下是 eBPF 验证器所做的一些事情:

  • 不允许无边界的循环
  • 检查程序是否是一个 DAG
  • 不允许越界跳转
  • 在进行各种辅助函数调用时检查参数类型(辅助函数在内核中定义,例如用于修改网络数据包)。

好吧,禁止越界跳转似乎很有用,但其他的东西就没那么有用。事实上,强制要求程序必须是一个 DAG 并且没有无边界循环是有问题的,因为它大大限制了程序的可操作性(没有图灵完备性)。在 eBPF 程序中需要这样做的原因是,验证者需要确定程序在一定数量的指令内终止(以便程序不能停止内核;这就是著名的停止问题[14]),而 Gas 计量不是一个选项,因为它对性能阻碍太大。

虽然这种权衡对于实现高性能的防火墙来说是很好的,但是对于智能合约的开发来说就不是那么好了,而且大部分的 eBPF 验证器都不能被重用在 Solana 程序中。事实上,Solana 甚至根本就没有使用原始的 eBPF 验证器。它使用一个(更基础的)自定义验证器[15],主要是检查指令是否正确和越界跳转。

除了验证器,还有一些其他的 eBPF 细节,对于编译智能合约来说是有点问题的。比如 eBPF 在设计上最多允许 5 个参数被传递给一个函数调用。这实际上意味着 Rust 标准库不能直接编译到 eBPF。或者堆栈大小被限制在 512 字节,这减少了我们可以传递给一个函数的参数的数量(不需要堆分配的参数)。

因此,即使通过 Rust 编译到 LLVM(eBPF 后端支持的 LLVM),甚至支持 Rust 编译器以 eBPF 为目标,你仍然无法使 Solana 智能合约编译到 eBPF。这就是为什么 Solana 团队不得不对 Rust 代码库和 eBPF LLVM 后端(例如支持通过堆栈传递参数[16])进行多次修改。

由于其中的一些修改本身就不能上游(upstreaming)(无论是对 Rust 还是 LLVM),Solana 团队目前维护着 Rust 和 LLVM 的分叉,并对这些修改进行了维护。当你执行cargo build-bpf(构建 Solana 智能合约的典型命令)时,Cargo 会拉动这个 Solana 特定版本的 rustc 来进行智能合约的编译(原来的 rustc 无法工作)。

这就是 SBF 的诞生过程 -- Solana 需要的一些要求与 eBPF 不兼容。Solana 团队目前正在努力将 SBF 作为一个独立的 LLVM 后端上游,并将其加入作为一个 Rust 目标,以避免维护单独的分叉。

如果你对这个话题有更多的兴趣,这里有一些相关的 GitHub 讨论:

  • 定义我们的 Rust 和 LLVM 分叉的上游策略[17]
  • SBFv2[18]
  • BPF VM 堆栈帧大小的限制对程序来说太有局限性[19]
  • 重命名工具链目标三要素(triple)为 solana 的具体名称[20]

因此,虽然 eBPF 可以作为智能合约的一种格式,但它并不像纸面上看起来那么理想。它需要进行一些修补,而且原来的验证器也没有很大的用处。

在关于 Move 和 Solana/SBF 的讨论中,我还看到一个误解,我觉得这个误解非常重要。有人评论说,Move 的主要思想应该适用于 SBF,因为它是基于 eBPF 的,它的验证器有可能被利用,使账户修改检查在静态编译时进行,而不是在运行时动态进行的。

在我看来,这是一个令人怀疑的说法。即使有可能证明程序不会在 eBPF 中修改他们不拥有的账户,而且 Move 确实做了那种事情,这肯定不是 Move 的主要**想法。

Move 的主要思想是使一个以资源为中心的编程模型能够以一种自然的方式与不受信任的代码进行交互。

在实践中,这意味着:

  • 全局类型安全
  • 资源安全(key, clone, store, drop)。
  • 可嵌入的资源
  • 资源安全地流入和流出不受信任的代码
  • ...

第五节闪电贷中说明了这在智能合约的安全性、可组合性和人机工程学方面是多么重要。它的重要性远远超出了跳过一些运行时安全检查来提高跨程序调用的性能

现在,如果你想知道将 Move 的主要思想引入 eBPF/SBF 有多难,答案是--非常难。如果不对 eBPF 进行重大修改,强制执行 "这个不受信任的代码不应该能够丢弃T"这样的属性将是不可能的。事实上,这需要大量的修改,以至于你最终会得到一个新的字节码,它看起来更像 Move 而不是 eBPF。这当然会是一个很大的研究工作。

事实上,类似的思路是导致 Move 诞生的首要原因。Move 团队(当时在 Diem)最初考虑从其他格式出发,如 WASM、JVM 或 CLR,但事后添加这个实在太难了。所以 Move 是从头开始设计的,其想法是通过轻量级的验证器通道来有效地执行这些检查。

如果你仔细想想,这其实并不令人惊讶。归根结底,智能合约编程不是系统编程、后台编程或其他任何一种常规编程。这是一种完全不同的编程类型,所以现有字节码和指令格式的功能不能在这里重用也就不奇怪了,因为它们在设计时考虑到是完全不同的使用情况。

说得很清楚,我不是在批评 Solana 使用 eBPF。事实上,我认为这是一个非常可靠的选择,考虑到当时的情况,团队做出了很好的判断。可以说,事后看来,团队可能会选择 WASM 而不是 eBPF,这样就可以避免前面提到的将智能合约编译为 eBPF 的问题,因为 WASM 在 Rust 中有一流的支持(不过 WASM 可能有不同的问题),但我可以理解团队如何认为 eBPF 是一个更安全的选择,因为它强调性能。另外,在做出这些设计选择的时候,Move 甚至还没有发布,对于一个初创公司来说,从头开始创建一种新的语言肯定不是一个合理的选择。在那时,Solana 设法提供了一个成功的高性能 L1,这才是最终重要的。

6.3. 在 Solana 上运行 Move

扩展 eBPF/SBF 以支持 Move 功能似乎很困难,而且无论如何我们最终可能会得到与 Move 字节码类似的东西。与其尝试改进 SBF,也许我们应该以某种方式让 Move 直接在 Solana 上运行?毕竟,Solana 对于支持多种编程语言的智能合约开发是非常开放的,甚至 Anatoly 在他的一些推文中也鼓励整合 Move。

似乎有三种方案可以让 Move 在 Solana 上运行:

  1. 添加 Move 虚拟机作为一个本地加载器(与 SBF 虚拟机一起)。
  2. 将 Move VM 作为一个程序运行(如 Neon)。
  3. 将 Move 编译为 SBF(如 Solang)。

我们先讨论方案(3),这里的想法是为 Move 建立一个 LLVM 前端,以便将其编译为 SBF。编译成 SBF 的 Move 智能合约可以透明地执行,就像用 Rust(或任何其他可以编译成 SBF 的语言)构建的智能合约一样,而且运行时不需要对 Move 有任何区分或了解。从运行时的角度来看,这将是一个非常优雅的解决方案,因为它不需要改变它或它的安全假设。

但从智能合约方面来看,这并没有什么好处。虽然有真正的理由为 Move 建立一个 LLVM 前端,例如为了性能或可移植性,但在我看来,使用 SBF 运行时来执行 Move 智能合约并没有带来多大的好处。事实上,我认为以这种方式开发智能合约会比直接使用 Anchor 更糟。

你通过(3)得到的是 Solana 编程模型中的 Move 语法。这意味着第五章中讨论的 Move 的所有重要优势。(全局类型安全、全局资源安全、可嵌入对象......)将不复存在。相反,我们仍将不得不处理账户检查、CPI 调用、PDA 等问题,就像在 Rust 中一样。而且,由于 Move 不支持宏,实现一个像 Anchor 这样的框架和一个 eDSL 来简化其中的一些工作是不可能的,所以代码将与原始 Rust 相似(但可能更糟糕)。Rust 标准库和生态系统也是不可用的,所以像账户序列化和反序列化这样的事情必须在 Move 中重新实现。

Move 不是很适合与其他编程模型一起使用。这是因为它被特别设计为能够编译成 Move 字节码并通过验证器验证。所有围绕能力和借贷检查器的自定义规则,都需要它,字节码验证是如此具体,以至于其他语言几乎没有机会编译成 Move 字节码并通过验证器。因为 Move 是围绕这个非常特殊的字节码验证而设计的,所以它不像 Rust 那样灵活。

剥离字节码就放弃了 Move 的所有主要优势。正如"资源:货币的安全语言抽象[21] "论文中所说的那样。

Move 的显著特征是一个可执行的字节码表示,对所有程序都有资源安全保证。考虑到合约的开放部署模式,这一点至关重要--回顾一下,任何合约都必须容忍与不受信任的代码进行任意的交互。如果源码级的线性可以被可执行级的不可信任的代码所违反,那么源码级的线性的价值是有限的(例如,可信任的代码复制源码级线性类型)。

虽然 Move 的类型、资源和内存安全特性可以在程序级别上会被保留下来,但它们不会被全局保留下来。而程序级的安全并没有带来多少新的东西--我们在很大程度上已经有了 Rust。

Move 的智能合约生态系统也不能在 Solana 上使用 -- 编程模型是如此不同,以至于智能合约的重要部分必须被重写。

考虑到所有这些,我预计 Move 的使用与(3)实现不会被接受--与 Anchor 相比,它只是使用起来太麻烦了。在某些方面,它甚至可能比原始 Rust 更麻烦。

至于方案(1),这里的想法是(与 SBF 加载器一起)在运行时添加对 Move 加载器的支持。Move 智能合约将被存储为链上的 Move 字节码,并由 Move VM 执行(就像在 Sui 中一样)。这意味着我们将有一个 SBF 智能合约的生态系统和一个 Move 智能合约的生态系统,前者将在当前的 Solana 编程模型上运行,而后者则在一个(可以说是优越的)Move 模型上运行。

有了这种方法,就有可能保持 Move 智能合约之间相互作用的所有好处,但这里的一个大困难是让 Move 智能合约能够与 SBF 智能合约进行交互,反之亦然。实现这将是一个挑战 -- 你需要一个对 Move 和 Solana 有深刻理解的人。验证器也将不得不被调整。

还有一个缺点是需要在运行时维护两个不同的加载器。这有安全方面的影响,因为它意味着攻击面是两倍的大小--任何一个加载器的错误都可能意味着整个链条被利用。顺便提一下,实际上早在 2019 年,Solana 就加入了对 Move VM 的早期支持(#5150[22]),但后来由于安全问题而被删除(#11184[23])(见本主题[24])。

至于方案(2),其想法是将整个 Move VM 作为 Solana 程序(智能合约)来运行。Move VM 是用 Rust 实现的,所以也许可以将其编译为 SBF(除非它使用线程或其他不支持的 API)。虽然这听起来很疯狂,但Neon[25]已经实现了类似的方法,将 EVM 作为一个 Solana 程序运行。这种方法的好处是,不需要对运行时进行修改,而且可以保持相同的安全假设。

我不熟悉 Move VM 的技术细节,所以我不能对这种做法的可行性以及它的局限性做太多的评论。我想到的第一件事是,验证器也必须作为一个程序运行,这意味着在计算预算内。这种方法也会受到 SBF 和 Move 智能合约之间相同的互操作性问题的影响,如同方案(1)。

没有直接的方法可以将 Move 的主要功能带到 Solana。虽然有可能建立一个 LLVM 前端,并将 Move 编译为 SBF,但这不会有什么作用,因为编程模型将保持不变。正如第 6.1 节中的思想实验所说明的那样,如果没有某种字节码验证,就没有什么可以改善编程模型的。改变 eBPF/SBF 以支持字节码验证将是非常困难的。看起来唯一合理的选择是以某种方式让 Move VM 运行。但这意味着将有两个生态系统在不同的编程模型上运行,而让它们正确地互操作是非常具有挑战性的。

6.4. Move 的性能

Move 的字节码不是一种通用的字节码语言。它有一个非常有主见的类型系统,为了允许所有必要的验证,它相当高级。这可能意味着与其他字节码格式(如 eBPF/SBF)相比,性能较低,因为后者更接近于原生代码,人们可能会认为这将是在高性能 L1 中使用的一个问题。

但是,到目前为止,智能合约的执行在 Solana(在写这篇文章的时候,平均有 3k TPS)和 Sui(基于团队所做的最初 e2e 基准)上都没有成为瓶颈。提高交易处理性能的关键是并行执行。Solana 和 Sui 都实现了这一点,它们要求事先声明依赖关系,并对依赖不同对象/账户集的交易的执行进行并行调度。由于这一点,并且因为在其他地方存在瓶颈,即网络层,在 TPS 方面交易的执行离它出现在影响性能的关键路径上还有几个数量级的距离。

此外,一旦 TX 执行出现在关键路径上,没有任何东西可以阻止 Move 被 AOT 编译或 JIT 化以提高性能。这就是为 Move 构建一个 LLVM 前端的好处所在。另外,由于 Move 本身对静态分析的适应性,Move 特有的进一步优化也有可能。

考虑到所有这些,我希望 Move 的性能在可预见的未来不会成为一个重要的障碍。

7. 其他 Move 特性

在本章中,我将描述 Move 的一些其他特性,这些特性也许不是本文讨论的核心,但仍然是相关的。

7.1. Prover

Move 有一个用于智能合约的形式化验证工具,叫做 Move Prover。通过这个工具,你能够断言不同的不变量对你的智能合约是否成立。在幕后,验证条件被翻译成 SMT 公式,然后使用 SMT 解算器进行检查。这与模糊测试有很大的不同,例如,模糊测试是通过遍历输入空间来试错。例如,如果模糊测试和单元/集成测试未能测试特定的输入或输入的组合,从而表明程序有一个错误,那么它们仍然可以提供一个假阳性。另一方面,验证器本质上提供了形式上的证明,即指定的不变量对所提供的程序是成立的。这就像针对所有可能的输入检查程序一样,但不需要这样做。

Move 验证器的速度非常快,所以它可以像类型检查器或 linter 那样被整合到常规的开发工作流程中。

下面是一个关于验证器规范的例子(摘自"用 Move Prover 对智能合约进行快速可靠的形式化验证[26] "白皮书)。

这增加了 transfer 函数的规范,一个用于规范的辅助函数bal,以及两个全局内存不变性(invariant)。第一个不变量指出,余额永远不能低于某个最小值。第二个不变式(invariant)指的是全局内存的更新,有前和后的状态:一个账户的余额永远不能在一个步骤中减少超过一定数量。

关于验证器的更多细节,我推荐以下白皮书:

  • The Move Prover[27]
  • 用 Move Prover 对智能合约进行快速可靠的形式化验证[28]

7.2. 钱包安全

由于 Sui 要求交易要访问的所有对象都在函数参数中传递(没有从全局状态中动态加载),而且 Move 函数签名连同类型信息都存储在字节码本身中,所以我们可以让钱包在用户签名之前向用户提供更有意义的信息,说明交易将做什么。

例如,如果我们有一个具有以下签名的函数:

代码语言:javascript复制
public entry fun foo(
  asset1: &Asset,
  asset2: &mut Asset,
  asset3: Asset
)

我们可以从函数签名中得知,这个交易将访问用户的 3 个资产(资产类型)。不仅如此,根据&&mut关键字(或没有关键字),我们还可以知道asset1可以被读取,asset2可以被修改(但不能转移或销毁),而asset3有可能被修改、转移或销毁。

钱包可以向用户显示这些信息,用户可以更有意义地了解交易可能对资产做什么。如果有什么不对劲的地方,例如,来自 web3 应用程序的交易调用正在接触一些不应该接触的资产或代币,用户可以观察到这一点并决定不继续进行交易。有一篇出色的文章来自 Ethos Wallet[29] 涵盖了这个话题(链接[30])。

钱包也可以额外模拟交易,这将给用户提供更多关于其效果的信息。Sui 以对象为中心的编程模型,以及类型信息是运行时的原生信息这一事实,意味着有可能解释对象的变化,而无需对智能合约有任何具体的应用层面的知识。

例如,这在 Solana 上是不可能的,因为从运行时的角度来看,账户包含任意的数据。你需要对账户外部描述(特定于应用程序)才能解释它们,而智能合约发布者可能提供也可能不提供这些描述。另外,资产所有权的概念在 Solana 运行时中并不存在,每个智能合约都需要手动实现这种语义(通常使用账户签名和 PDA),这意味着没有通用的方法来跟踪这一点。

7.3 简单和复杂交易

具体到 Sui,在共识层面上有一个有趣的优化,允许某些类型的交易放弃完全的共识,而是使用基于拜占庭一致广播的更简单的算法来提交。这样做的好处是,这些交易可以在共识层面上被并行化,消除了线性头阻塞,并以近乎即时的最终确定性 -- 基本上实现了 web2 的可扩展性。

这是由于 Sui 对拥有者对象和共享对象进行了区分(见 3.1 节)。只涉及拥有者对象的交易(被称为简单交易)不需要在 Sui 上达成完全的共识。由于拥有者对象在交易中不能被其他人使用,只能由发送者使用,而且发送者一次只能发送一个交易,这本质上意味着这些交易不需要参照其他交易进行排序(总排序与因果排序[31])-- 我们知道一个事实,即交易中引用的对象不能被其他交易影响,同时这个交易也不能影响其他对象。因此,我们并不关心这个交易相对于链上平行发生的其他交易的排序 -- 这实际上是不相关的。Sui 能够利用这一事实,大大优化简单交易的处理,在几百毫秒内实现最终确定性。另一方面,涉及任何数量的共享对象的交易(被称为复杂交易),总是需要完全的共识。

考虑到拥有者对象的创建、转移和修改可以完全通过简单交易完成,某些类型的应用可以很好地利用简单交易。很好的例子是 NFT(包括大规模铸币)和 web3 游戏。这些使用场景从低延迟的最终确定性和消除线性头阻塞中获益良多,实现了更好的用户体验和可扩展性。一个更全面的有利于单个写入友好的应用程序列表可以在这里[32]找到。

其他一些类型的应用程序必须依靠复杂的交易。这包括大多数 DeFi 应用程序。例如,AMM 流动性池将需要成为一个共享对象,因为任何种类的交易所订单执行都需要完全的共识和总排序。这是因为,从根本上说,如果多个订单同时来自不同的用户,我们需要就谁的订单先被执行达成一致,这就决定了每个用户会得到什么样的执行价格。

还有一些应用可以混合使用简单和复杂的交易。这些应用需要复杂的交易才能实现他们想要的功能,但在某些操作上可以利用简单的交易来获得更好的效率。例如,一个价格预言机可以被设计成这样。我们可以让多个发布者使用简单交易为一个市场提交价格数据,然后由一个权威机构使用复杂交易对价格进行汇总(例如,股权加权中值)。在某些时候不依靠复杂交易是不可能实现价格预言机的(根本原因是在其他交易中使用发布的价格需要就排序达成一致,从而达成完全的共识),但至少我们可以用简单交易优化发布者的写入。

Sui 文档有关于简单和复杂交易的更多细节:

  • https://docs.sui.io/devnet/learn/sui-compared[33]
  • https://docs.sui.io/devnet/learn/how-sui-works#system-overview

8. 结束语

本文深入探讨了 Solana 和 Sui 的编程模型,它们如何进行比较,以及 Move 编程语言。

第二节是对 Solana 编程模型的总结,而第三节则介绍了 Sui Move 及其编程模型。第 4 节接着解释了 Move 中的类型和资源安全是如何工作的。Move 的功能对智能合约开发的意义并不是立竿见影的,所以在第 5 节中,我们用现实生活中的例子对 Solana 和 Sui Move 进行了更彻底的比较。第 6 节讨论了 eBPF/SBF,并表明让 Move 功能或 Move 本身在 Solana 上工作并不是一件容易的事。第 7 节涉及到 Sui 的一些 Move 相关功能。

智能合约编程是关于数字资产的编程。而且可以说这是一种新的编程类型,与我们目前看到的其他类型的编程(如系统、后台......)截然不同。正因为如此,现有的编程语言和编程模型不能很好地适应这种使用场景也就不奇怪了。

问题的关键在于,我们希望有一个编程模型,能够自然地与资源打交道,但同时我们又要与不受信任的代码交互。Solana 在这里做了一个妥协,它确实能够让智能合约在不受信任的环境中具有必要的可编程性,但它的编程模型对与资源的编程来说不是很自然。字节码验证是使其有可能同时拥有这两者。在某种程度上,它把不可信任的代码变成了可信任的。

Move 是一种用于智能合约开发的新型编程语言。它的核心创新之处在于它的字节码,它被特意设计为可被验证的。虽然字节码验证本身并不是一个新的概念,但 Move 所做的那种验证才是。通过其字节码和验证,Move 实现了一个智能合约编程模型,它对资源有一流的支持,并能保证在一个不受信任的环境中安全编程。

这对智能合约开发的影响乍一看并不明显,但正如第 5 节所说明的,它们在智能合约的人机工程学、可组合性和安全性方面确实非常重要。与 Solana 的基于 Rust 的编程模型相比,Move 提出了一个重大的提升。

以至于我认为 Move 对智能合约开发的作用就像 React 对前端开发的作用一样,说 "你能用 Move 做的事你能用 Rust 做"就像说 "你能用 React 做的事你能用 jQuery 做 "一样。当然,有可能实现一个基于 jQuery 的应用,相当于一个 React 应用,但这并不实际。React 引入了虚拟 DOM 的概念,这对开发者来说是完全透明的,但却能使前端的开发速度更快、可扩展、更简单。以类似的方式,Move 的字节码验证是一种底层技术,对开发者来说也是完全透明的,但它提供了一个更符合人体工程学、可组合和更安全的智能合约开发。由于其安全性和更直观的编程模型,Move 也大大降低了智能合约开发者的准入门槛。

如果 Move 设法获得推广(有早期迹象表明它将),它可能对 Solana 构成相当大的威胁。这是因为两个原因。

首先是 Move 智能合约的开发时间要快得多。看起来,在 Move 中从头开始开发一个智能合约可能比在 Rust 中快 2-5 倍。当智能合约组合时尤其如此,这在 Move 中是微不足道的,但在 Solana 上可能很复杂。正因为如此,Move 生态系统的发展可以超过 Solana。由于区块链的开放和无许可性质,没有强烈的锁定效应,流动性可以很容易地移动。Solana 的开发者可能纯粹是因为经济原因而被迫采用 Move -- 你要么转到 Move,要么被 Move 的开发者超越,因为他们更快地开发出更安全的智能合约。如果你要雇佣一个智能合约开发者,你可以雇佣一个 Rust 开发者,它将建立一个智能合约,或者雇佣一个 Move 开发者,它将在同样的时间内建立两个更安全的智能合约。这类似于 React 对前端开发的影响。

第二个原因是,Move 的入门门槛比 Rust 或 Solidity 低得多。因为 Move 的语法更简单,编程模型更直观,有一整类开发人员无法在 Rust 或 Solidity 中进行智能合约开发,但在 Move 中可能会有。由于需要学习的概念较少,让非智能合约开发者进入 Move 要比让他们进入 Rust(Rust 本身就是一种复杂的语言,再加上 Solana 的概念,如 PDA,给初学者带来很多困惑)或 Solidity(你需要熟悉语言中非常微妙的细节,如重入,以便能够开发安全的智能合约)容易得多。即使现有的 Solana 和 Solidity 开发者不转向 Move,尚未进入该领域的开发者市场也比该领域现有的开发者数量多出好几个数量级。由于 Move 的进入门槛较低,并允许更快的开发,它比 Rust 或 Solidity 有更好的产品市场契合度,并可以从这块蛋糕上分得更大的一杯羹。如果新的开发者开始大量涌入,我希望他们从 Move 开始,而不是 Rust 或 Solidity。这也类似于 React 在 web 行业的情况。

正因为如此,我完全期待在中长期内,Solana 会加入对 Move 作为一等公民的支持。但这并不是一件容易的事。为了获得 Move 的主要好处,Move 字节码需要得到本地支持。这意味着简单地将 Move 编译成 eBPF/SBF 是不可能的(见第 6.3 节)。为了保持现有的生态系统,两种运行时都需要被支持。主要的技术挑战是如何在运行时之间实现适当的互操作性。这需要对 Move 和 Solana 的深入了解,所以我希望这需要 Solana 团队在 Move 团队的支持下直接推动。

Move 起源于 Meta(前 Facebook)的 Diem 项目。由 Sam Blackshear 领导的 Move 团队的任务是弄清楚如何处理智能合约的问题。在仔细研究了这个问题之后,他们发现智能合约的编程都是关于数字资产(资源)的编程,但现有的语言都不支持这个使用场景。于是决定从头开始建立一种新的编程语言。

我想强调的是,创建一个新的语言的决定根本不是一个明显的决定,因为它需要多年的工程努力才能落地,在大多数情况下,使用现有的解决方案会更好。Move 团队正确地预见到,一种安全的、对资源有一流支持的、同时又足够灵活的智能合约语言是可以建立的,仅此一点就显示出高度的专业性。这是团队和支持该项目的 Novi/Meta 的工程领导层的一个大胆举动(这样的决定会有一些董事和副总裁参与)。Meta 后来关闭了他们的 Diem 努力,并在最后没有能够收获其在 Move 的投资成果,但尽管如此,它对更广泛的加密货币社区是一个出色的贡献。

总而言之,Move 是一项了不起的技术,我相信它将对我们如何开发智能合约产生巨大的影响。Move 团队做得很棒!

参考资料

[1]

MoveMoon: https://learnblockchain.cn/people/11436

[2]

上一篇: https://learnblockchain.cn/article/4692

[3]

重入本质上是非常危险的: https://consensys.github.io/smart-contract-best-practices/attacks/reentrancy/

[4]

DAO hack: https://blog.ethereum.org/2016/06/17/critical-update-re-dao-vulnerability

[5]

指令内省: https://docs.solana.com/implemented-proposals/instruction_introspection

[6]

链接: https://github.com/2501babe/adobe/blob/master/programs/adobe/src/lib.rs

[7]

链接: https://github.com/solana-labs/solana-program-library/blob/8d88fb6c88a1bd5d0b55a47690943a173d7dd86e/token-lending/program/src/processor.rs#L1505

[8]

烫手山芋(hot-potato): https://examples.sui.io/patterns/hot-potato.html

[9]

示例: https://github.com/MystenLabs/sui/blob/7af627ea406f0d838b4a26ec9c7ce123ed59fc08/sui_programmability/examples/defi/sources/flash_lender.move#L91-L104

[10]

Solana: https://github.com/kklas/token-mint-lock-example/blob/master/solana/programs/token-mint-lock/src/lib.rs

[11]

Sui: https://github.com/kklas/token-mint-lock-example/blob/master/sui/sources/treasury_lock.move

[12]

这里是该漏洞的主要账户检查的细目(不充分): https://twitter.com/samczsun/status/1507056113023275023

[13]

Anatoly Yakovenko的文章: https://medium.com/solana-labs/high-performance-memory-management-for-smart-contracts-aa9b3bc950fb

[14]

停止问题: https://en.wikipedia.org/wiki/Halting_problem

[15]

自定义验证器: https://github.com/solana-labs/rbpf/blob/2e6d5715df4de50a60e50234bdfafe5887f8f78a/src/verifier.rs

[16]

支持通过堆栈传递参数: https://github.com/solana-labs/llvm-project/commit/da9785178b02fd790a113aed6c429e35b5751af9

[17]

定义我们的Rust和LLVM分叉的上游策略: https://github.com/solana-labs/solana/issues/21483

[18]

SBFv2: https://github.com/solana-labs/solana/issues/20323

[19]

BPF VM堆栈帧大小的限制对程序来说太有局限性: https://github.com/solana-labs/solana/issues/13391

[20]

重命名工具链目标三要素(triple)为solana的具体名称: https://github.com/solana-labs/solana/issues/19113

[21]

资源:货币的安全语言抽象: https://arxiv.org/pdf/2004.05106.pdf

[22]

#5150: https://github.com/solana-labs/solana/pull/5150

[23]

#11184: https://github.com/solana-labs/solana/pull/11184

[24]

本主题: https://twitter.com/aeyakovenko/status/1549764416274513922

[25]

Neon: https://docs.neon-labs.org/docs/about/introduction

[26]

用Move Prover对智能合约进行快速可靠的形式化验证: https://arxiv.org/abs/2110.08362

[27]

The Move Prover: https://www-cs.stanford.edu/~yoniz/cav20.pdf

[28]

用Move Prover对智能合约进行快速可靠的形式化验证: https://arxiv.org/abs/2110.08362

[29]

Ethos Wallet: https://medium.com/u/e52d5d41e89f?source=post_page-----4d8f84754a8f--------------------------------

[30]

链接: https://medium.com/illumination/how-to-properly-use-links-in-your-medium-stories-a303f89697bf

[31]

总排序与因果排序: https://www.scattered-thoughts.net/writing/causal-ordering/

[32]

这里: https://docs.sui.io/devnet/learn/single-writer-apps

[33]

https://docs.sui.io/devnet/learn/sui-compared: https://docs.sui.io/devnet/learn/sui-compared#sequential-writes-in-the-simple-case

0 人点赞