原文標題: 《Smart Contract Development—Move vs.Rust》

作者:Krešimir Klas,Move 語言創始人

編譯:郭倩雯,鏈捕手

1. 介紹

最近,關於Aptos 和Sui 的討論如火如荼,兩者是新興的高性能L1 公鏈,Move 智能合約編程語言是這些新鏈必不可缺的組成部分。一些開發人員正在積極轉向Move,宣稱它是智能合約發展的未來。其他人更加謹慎,認為Move 與現有編程語言相比,不能提供更多太新的東西。

加密投資者也在好奇這些L1 公鏈的獨特之處,如何能與Solana 抗衡,後者是目前高性能L1 的主要玩家,以使用Rust 作為智能合約編程而著稱。

但目前我們看到的討論並沒有達到一定深度,能夠真正參透這些新科技對我們的影響。這在討論的兩極都適用——Move 的質疑者將Move 貶低得一無是處,無法欣賞到它更細微(但十分重要)的一面,但Move 的支持者,過度鼓吹Move,也沒有能看透究竟是什麼使其偉大。這就帶來巨大的中間地帶與模糊不清,致使外界看客、加密開發人員、投資人員,關注此話題,但又無法對自己的觀點確信。

在這篇文章中,我將對Move、其新穎的編程模型、Sui 區塊鍊和它如何利用Move 的功能,以及它與Solana 及其編程模型的比較進行深入的技術挖掘。為了突出Move 的特點,我將把Solana/Rust 與Sui/Move 進行比較。因為當你把一個東西與另一個你已熟悉的東西進行比較時,理解就會更容易。

Move 還有其他變種,如Aptos Move,它們在某些方面略有不同。本文的重點不是討論Move 不同變體之間的細微差別,而是展示Move 的普遍優勢,以及它與Solana 編程模型的比較。因此為了簡單起見,我在本文中只使用一個變體(Sui Move)。因此,我在本文中介紹的某些Move概念(即對象和相關功能)只適用於Move 的Sui 變體,而不適用於其他變體。雖然Move 的其他變體不一定有這些概念,但它們使用不同的機制(例如全局存儲)能夠實現同樣功能。但即便如此,本文所討論的所有Move的主要優點都適用於所有Move集成(原生設定上支持Move字節碼Move bytecode),包括Aptos。我選擇Sui,只是因為我對它更熟悉,且我覺得它更直觀一些,更容易以文章的形式呈現。

2. Solana編程模型

在Solana上,程序(智能合約)是無狀態的,它們自己不能訪問(讀或寫)任何在整個交易中持續存在的狀態。為了訪問或保持狀態,程序需要使用賬戶。每個賬戶都有一個唯一的地址(Ed25519密鑰對的公鑰),可以存儲任意的數據。

我們可以把Solana的賬戶空間看作是一個全球鍵值存儲,其中鍵是賬戶地址(pubkey),值是賬戶數據。程序通過讀取和修改其值在該鍵值存儲上進行操作。

賬戶有一個所有權的概念。每個賬戶由一個(且只有一個)程序擁有。當一個賬戶被一個程序擁有時,該程序被允許修改其數據。程序不能修改所不擁有的賬戶(但允許讀取這些賬戶)。運行期間,比較程序執行前後的賬戶狀態,就能夠進行這種動態檢查,若有非法改動,則交易失敗。

每個賬戶也有一個與之相關的私鑰(相應的公鑰是它的地址),能夠訪問這個私鑰的用戶可以用它來簽署交易。利用這種機制,我們在Solana智能合約中實現了權限和所有權的功能--例如,為了獲取某些資金,智能合約可以要求用戶提供必要的簽名。

在其他做程序調用時,客戶需要指定這個程序在調用時將訪問哪些賬戶。這樣一來,交易處理運行時間就可以安排不重迭的交易並行執行,同時保證數據一致性。這是Solana的設計特點之一,使其具有高吞吐量。

程序可以通過CPI調用來調用其他程序。這些調用的工作原理與來自客戶端的調用基本相同——調用者程序需要指定被調用者程序將訪問的賬戶,被調用者程序將進行輸入檢查,就和從客戶端調用是一樣的(因為它不信任調用者程序)。

PDA賬戶是一種特殊賬戶,使程序能在不擁有或儲存私鑰的情況下提供賬戶簽名。 PDA保證只有為其生成PDA的程序可以為其創建一個簽名(而其他用戶和程序不行)。當一個程序需要通過CPI調用與另一個程序進行交互並提供授權時,這是很有用的(例如,實施一個金庫)。 PDA保證除了程序之外沒有人可以直接訪問程序資源。 PDA也可用於在確定地址創建賬戶。

這些是Solana上安全智能合約編程的基本構件。在某種程度上,你可以把Solana程序看作是操作系統中的程序,而賬戶則是文件,任何人都可以自由執行任何程序,甚至部署自己的程序。當程序(智能合約)運行時,它們將讀取和寫入文件(賬戶)。所有文件都可被所有程序讀取,但只有對文件有所有權權限的程序才可以對其進行改寫。程序也可以執行其他程序,但它們彼此之間沒有任何信任——無論誰執行程序,它都需要假設輸入是潛在惡意的。由於該操作系統是任何人在全球範圍內都訪問的,所以在程序中加入了原生簽名驗證支持,以便為用戶實現權限和所有權功能......這不是一個完美的比喻,但還是挺有趣的。

3. Move的編程模型

在Move中,智能合約是以模塊形式發布的。模塊由函數和自定義類型(結構/struct)組成。結構由字段組成,可以是原始類型(u8,u64,bool...)或其他結構。函數可以調用其他函數——可以是同一模塊,也可以是其他公開的模塊。

在Solana中,這就相當於所有智能合約都作為模塊發佈在一個程序中。這意味著所有的智能合約(模塊)都包含在同一類系統中,可以直接相互調用,而不需要通過中間的API或接口。這一點非常重要,其影響將在本文中徹底討論。

3.1. 對象

要注意的是,下面的對象概念針對於Move的Sui變體。而在Move的其他集成中(例如Aptos或Diem/core Move),情況可能略有不同。不過,在其他Move變體中也有類似的解決方案,可以實現同樣的事情(狀態的持久性),這些解決方案並沒有太大區別。

這裡介紹Sui變體的主要原因是,文章後面的代碼樣本都基於Move的Sui變體,同時其對像比如core Move中的全局存儲機制更直觀易懂一點。重要的是,本文所討論的Move的所有主要優點都適用於所有Move集成(原生支持Move字節碼),包括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的新穎之處......讓我們來談談資源。

4.1. 結構

定義一個結構(struct)類型和你所期望的差不多:

智能合約發展:全方位對比Move與Rust

到目前為止還不錯——這也是你在Rust中定義一個結構的方式。但在Move中,結構有其獨特之處與傳統編程語言相比,Move模塊在如何使用類型上擁有更多空間。在上面的代碼片斷中定義的結構將受以下限制:

-它只能在定義該結構的模塊中被實例化(“打包”)和銷毀(“解包”)——也就是說,你不能從任何其他模塊的任何函數中實例化或銷毀一個結構實例。

-結構實例的字段只能在其模塊中被訪問(因此也可以被改動)。

-不能在其模塊之外克隆或複制結構實例

-不能將一個結構實例存儲在其他結構實例的字段中

這意味著,如果在其他模塊的函數中處理這個結構的實例,我們將無法改動其字段、克隆它、將其存儲在其他結構的字段中,或將其丟棄(必須通過函數調用將其傳遞到其他地方)。情況是這樣的:該結構的模塊實施了能從我們模塊中調用的函數,來完成這些事情。但除此之外,我們無法直接為外部類型做這些事情。這使模塊可以完全控制如何使用和不使用其類型。

由於這些限制,我們似乎失去很多靈活性。這也是事實——在傳統編程中,處理這樣的結構會非常麻煩,但事實上,這正是我們在智能合約中想要的。智能合約開發畢竟是關於數字資產(資源)的編程。如果你看一下上面描述的結構,這正是它的本質——它是一種資源。它不能隨意被憑空創造,不能被複製,也不能被意外地銷毀。因此,我們確實在這裡失去了一些靈活性,但我們失去的靈活性正是我們所希望的,因為這使對資源的操作變得直觀而安全。

此外,Move允許我們通過向結構添加能力(capability)來放寬其中一些限制。有四種能力:鍵、存儲、複製和刪除。你可以將這些能力的任何組合添加到一個結構中。

智能合約發展:全方位對比Move與Rust

下面是它們的作用:

- 鍵- 允許一個結構成為一個對象(專屬Sui,core Move情況略有不同)。如前所述,對像是持久化的,如果是自有對象,需要用戶簽名才能在智能合約調用中使用。當使用鍵能力時,結構的第一個字段必須是具有UID類型的對象ID。這將給它一個全球唯一的ID,能夠用它進行引用。

- 存儲- 允許將該結構作為一個字段嵌入另一個結構中

- 複製- 允許從任何地方任意複製/克隆該結構

- 刪除- 允許從任何地方任意銷毀該結構

從本質上講,Move中的每個結構都是默認的資源。能力給了我們權力,可以精細地放寬這些限制,使其表現得更像傳統結構。

4.2. 幣(Coin)

幣在Sui中實現了類似ERC20/SPL代幣的功能,是Sui Move Library的一部分。它的定義是這樣的:

智能合約發展:全方位對比Move與Rust

你可以在Sui代碼庫中找到完整的模塊實現( 鏈接)。

幣類型具有鍵和存儲的功能。鍵意味著它可以作為一個對象使用。這允許用戶直接擁有幣(作為一個頂層對象)。當你擁有一個幣時,除你之外,其他人甚至不能在交易中引用它(更不用說使用它)。存儲意味著,幣可以作為一個字段嵌入到另一個結構中,這對於可組合性很有用。

由於沒有丟棄功能,幣不能在函數中被意外丟棄(銷毀)。這是一個非常好的特性——它意味著你不會意外地丟失一個幣。如果你正在實現以接收硬幣為參數的函數,在函數結束時,你需要明確地對它做一些事情——把它轉移給用戶,把它嵌入另一個對象,或者通過調用把它送入另一個函數(同樣需要對它做一些事情)。當然,通過調用幣模塊中的coin::burn函數來銷毀一個幣是可能的,但你需要有目的地這樣做(你不會這樣意外操作的)。

沒有克隆能力意味著沒有人可以復制幣,從而憑空創造新的供應。創造新的供應可以通過coin::mint函數來完成,而且只能由該幣的國庫能力對象(treasury capability)的所有者調用。

另外,由於泛型(generics)的存在,每個不同的硬幣都是獨特類型。由於兩個幣只能通過coin::join函數加在一起(而不是直接訪問它們的字段),這意味著不可能把不同類型的幣值加在一起(幣A+幣B)——因為沒有這種簽名的函數。類型系統能夠保護我們免受壞賬影響。

在Move中,資源的安全性由其類型定義。考慮到Move有全局類型系統,這使編程模型更自然和更安全,資源可以直接傳入和傳出不受信任的代碼。

4.3. 字節碼驗證

如前所述,移動智能合約是作為模塊發布的。任何人都被允許創建並上傳任何任意模塊到區塊鏈上,由任何人執行。我們也已經看到,Move對結構體的使用方式有一定規則。

那麼,是什麼保證了這些規則被任意模塊所遵守?是什麼阻止了人們上傳具有特殊製作字節碼的模塊,例如接收一個幣對象,然後直接改變其內部字段來繞過這些規則?通過這樣做,可以非法地增加所有幣的數量。光是字節碼的語法就肯定允許這樣做。

字節碼驗證可有防止這種類型的濫用。 Move驗證器是一個靜態分析工具,它分析Move字節碼並確定它是否遵守所需的類型、內存和資源安全規則。所有上傳到鏈上的代碼都需要通過驗證器。當你試圖上傳一個Move模塊到鏈上時,節點和驗證器將首先通過驗證器運行,然後才允許提交。如果任何模塊試圖繞過Move的安全規則,它將被驗證器拒絕,並且不會被發布。

Move字節碼和驗證器是Move的核心創新之處。它實現了一個以資源為中心的直觀編程模型,在其他處是無法實現的。最關鍵的是,它允許結構化類型跨越信任邊界而不失去其完整性。

在Solana上,智能合約是程序,而在Move中,它們是模塊。這似乎只是一個語義上的差異,但事實並非如此,具有重大意義。區別在於,在Solana上,跨程序邊界是沒有類型安全的——每個程序通過手動從原始賬戶數據解碼來加載實例,這需要手動進行關鍵的安全檢查,也沒有本地資源安全。相反,資源安全必須由每個智能合約單獨實現。這確實能夠實現足夠的可編程性,但與Move的模式相比,它在很大程度上阻礙了可組合性和人機工程學,因為Move的模式對資源有原生支持,它們可以安全地流入和流出不信的代碼。

在Move中,類型確實存在於各個模塊中——類型系統是全局的。這意味著不需要CPI調用,賬戶編碼/解碼,賬戶所有權檢查等——你只需直接調用另一個模塊中的函數與參數。整個智能合約的類型和資源安全由編譯/發佈時的字節碼驗證來保證,不需要像Solana那樣在智能合約層面上實現,然後在運行時檢查。

5. Solana與Move

現在我們已經看到了Move編程如何工作,根本安全的原因。那麼讓我們從可組合性、人機工程學和安全性的角度深入了解一下這對智能合約編程有什麼樣的影響。在這裡,我將把Move/Sui的開發與EVM和Rust/Solana/Anchor進行比較,以幫助理解Move的編程模型所帶來的好處。

5.1. 閃電貸

閃電貸是DeFi中的一種貸款類型,貸款金額必須在藉入的同一交易中償還。這樣做的主要好處是,由於交易是原子性的,貸款可以完全沒有抵押。這可以用來在資產之間進行套利,而不需要有本金。

實現這一目標的主要困難是——你如何從閃電貸智能合約中,保證貸款金額將在同一交易中得到償還?為了使貸款無需抵押,交易需要是原子性的——也就是說,如果貸款金額沒有在同一交易中被償還,整個交易需要失敗。

EVM有動態調度,所以可以使用重入性(reentrancy)來實現這一點,如下所示:

-閃電貸用戶創建並上傳自定義智能合約,當調用該合約時,將通過調用將控制權傳遞給閃電貸智能合約

-然後,閃電貸智能合約將向自定義智能合約發送請求的貸款金額,並調用自定義智能合約中的executeOperation()回調函數。

-然後,自定義智能合約將使用收到的貸款金額來執行它所需要的操作(如套利)。

-在自定義智能合約完成其操作後,它需要將藉出的金額返回給閃電貸智能合約。

-這樣,自定義智能合約的executionOperation()就完成了,控制權將返回給閃電貸智能合約,它將檢查貸款金額是否已經正確返回。

-如果自定義智能合約沒有正確返回貸款金額,整個交易將失敗。

這很好地實現了所需的功能,但問題是,它依賴於可重入性,我們非常希望它不要出現在在智能合約編程中。因為可重入性本質上非常危險,是許多漏洞的根本原因,包括臭名昭著的DAO黑客襲擊。

Solana在這方面做得更好,因為它不允許重入。但是,如果沒有可重入性,如果閃電貸款智能合約無法回調到自定義智能合約,如該何在Solana上實現閃電貸款?多虧了指令自省( instruction introspection)。在Solana上,每個交易由多個指令(智能合約調用)組成,從任何指令中你都可以檢查同一交易中存在的其他指令(它們的程序ID、指令數據和賬戶)。這使得實現閃存貸款成為可能,具體如下:

閃電貸款智能合約實現借款(borrow)和還款(repay)指令

用戶通過在同一交易中把借款和還款指令的調用堆迭在一起,創建一個閃電貸交易。借款指令在執行時,將使用指令自省檢查償還指令是否安排在同一交易的後期。如果償還指令的調用不存在或無效,交易將在這個階段失敗。

在藉款和還款的調用之間,借來的資金可以被任何其他處於兩者之間的指令任意使用。

在交易結束時,還款指令調用將把資金返還給閃電放款人智能合約(該指令的存在將在藉款指令的反思中進行檢查)

這個解決方案足夠好,但仍不理想。指令自省在某種程度上是一個特例,在Solana中並不常用,它的使用要求開發者掌握大量概念,其實現本身也有很大技術要求,因為有一些細微差別需要適當考慮。還有一個技術上的限——-償還指令需要靜態地存在於交易中,因此不可能在交易執行期間通過CPI調用動態地調用償還。這並不是什麼大問題,但在與其他智能合約整合時,它在一定程度上限制了代碼的靈活性,也將更多複雜性推向客戶端。

Move也禁止動態調度和重入,但與Solana不同的是,它有一個非常簡單和自然的閃電貸解決方案。 Move的線性類型系統允許創建結構,保證在交易執行過程中正好被消耗一次。這就是所謂的“燙手山芋”(Hot Potato)模式——一個沒有鍵、存儲、刪除或克隆功能的結構。實現這種模式的模塊通常會有一個實例化結構的函數和一個銷毀結構的函數。由於”燙手山芋”結構沒有丟棄、鍵或存儲功能,因此可以保證它的銷毀(destroy)函數能被調用,以此來消耗它。儘管我們可以將其傳遞給任何模塊中的任何其他函數,但最終它還是需要在銷毀函數結束。因為沒有其他方法來處理它,而且驗證器要求在交易結束時對它進行處理(它不能被任意丟棄,因為沒有丟棄功能)。

讓我們看看如何利用這一點來實現閃電貸。

-閃電貸智能合約實現了一個“燙手山芋”的收據(Receipt)結構

-當通過調用貸款函數進行貸款時,它將向調用者發送兩個對象——請求的資金(一個幣)和一個收據,是需要償還貸款金額的記錄。

-然後,借款人可以將收到的資金用於其需要的操作(如套利)。

-在藉款人完成其預期的操作後,它需要調用還款函數,該函數將收到借款資金和收據作為參數。這個函數被保證在同一個交易中被調用,因為調用者沒有其他辦法擺脫收據實例(它不允許被丟棄或嵌入到另一個對像中,這是驗證器所要求的)。

-還款函數通過讀取嵌入在收據中的貸款信息來檢查是否已返回正確的金額。

Move的資源安全特性使Move中的閃電貸成為可能,而無需使用重入或自省。它們保證了收據不能被不受信任的代碼所修改,並且它需要在交易結束時被返回給還款函數。這樣,我們可以保證在同一個交易中返回正確的資金數額。

該功能完全使用基本的語言原語實現,Move的實現不會像Solana的實現那樣受到集成問題的影響,因為後者需要交易是精心設置的。沒有任何復雜性被推到客戶端。

閃電貸很好展示Move的線性類型系統和資源安全保障如何使我們以其他編程語言無法實現的方式去表達功能。

5.2. 鑄幣權限鎖(Mint Authority Lock)

“鑄幣權限鎖”智能合約擴展了代幣鑄造的功能,允許多個白名單方(authority)鑄造代幣。該智能合約的所需功能如下(同時適用於Solana和Sui的實現):

-原始的代幣鑄幣權限方創建一個“鑄幣鎖”,這將使我們的智能合約能夠監管鑄幣。調用者成為該鑄幣鎖的管理員。

-管理員可以為該鎖創建額外的鑄幣授權,可以授權給其他各方,並允許他隨時使用該鎖來鑄造代幣。

-每個鑄幣授權都有每日可以鑄造的代幣數量限制。

-管理員可以在任何時候禁止(和解除)任何鑄幣權限方。

-管理員的能力可以轉讓給另一方。

這個智能合約可用於,例如將代幣的鑄幣能力交給其他用戶或智能合約,而原來的鑄幣權限方(管理員)仍然保留對鑄幣的控制權。不然,我們將不得不把鑄幣的全部控制權交給另一方,這並不理想,因為我們只得相信它不會濫用該權力。而且給多方提供許可也是不可能的。

這些智能合約的完整實現可以在這裡(Solana)和這裡(Sui)找到。

注意:請不要在生產中使用這段代碼! 這是示例代碼,僅用於教育目的。雖然我已經測試了它的功能,但我還沒有做徹底的審計或安全測試。

現在讓我們來看看這些代碼,看看實現方式有什麼不同。下面是這個智能合約的完整Solana和Sui實現的並排代碼截圖。

智能合約發展:全方位對比Move與Rust

可以注意到的是,對於相同的功能,Solana的實現的規模是Sui的兩倍多(230 LOC vs 104)。這是一個大問題,因為更少代碼通常意味著更少錯誤和更短開發時間。

那麼,Solana的這些額外行數是怎麼來的呢?如果我們仔細看Solana的代碼,我們可以把它分為兩個部分——指令實現(智能合約邏輯)和賬戶檢查。指令實現與我們在Sui上的情況比較接近-——Solana136行,Sui上104行。額外的行數源於兩個CPI調用的引用(每個大約10個LOC)。最重要的差異是在賬戶檢查(在上面的截圖中標為紅色的部分),這在Solana上是必須的(事實上是關鍵的),但在Move中不是。帳戶檢查佔這個智能合約的大約40%(91 LOC)。

Move不需要賬戶檢查。 LOC的減少能夠帶來利處,但同時去除做賬戶檢查也十分必要。因為事實證明,正確實施這些檢查是非常棘手的,如果你在犯了哪怕一個錯誤,往往會導致重大漏洞和用戶資金的損失。事實上,一些最大的(就用戶資金損失而言)Solana智能合約漏洞就是由不當的賬戶檢查引起的賬戶替換攻擊。

- Wormhole(3.36億美元) - https://rekt.news/wormhole-rekt/

- Cashio (4800萬美元) - https://rekt.news/cashio-rekt/

- Crema Finance (880萬美元) - https://rekt.news/crema-finance-rekt/

那麼,Move是如何做到沒有這些檢查又同樣安全的呢?讓我們仔細看看這些檢查的實際作用。這裡是mint_to指令所需的賬戶檢查(權限持有人通過調用這個指令來鑄造代幣):

智能合約發展:全方位對比Move與Rust

有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中,沒有賬戶檢查或類似的東西,只有功能簽名:

智能合約發展:全方位對比Move與Rust

mint_balance函數只需要四個參數。在這四個參數中,只有lock和cap代表對象(有點類似於賬戶)。

在Solana中,我們需要聲明6個賬戶,並手動實現對它們的各種檢查,而在Move中,我們只需要傳入2個對象,而且不需要明確的檢查,這是如何實現的?

在Move中,這些檢查有些是由運行透明地完成的,有些是由驗證器在編譯時靜態地完成的,而有些則是在構造上根本不需要的。

賬戶所有權檢查——Move有類型系統,因此這種設計不必要。一個Move結構只能通過其模塊中定義的函數進行改動,而不能直接改動。字節碼驗證保證了結構實例可以自由地流入不受信任的代碼(其他模塊)而不被非法改動。

賬戶類型檢查——沒有必要,因為Move類型存在於整個智能合約中。類型定義被嵌入到模塊二進製文件中(在區塊鏈上發布並由虛擬機執行)。驗證器將檢查,編譯/發布期間,我們的函數被調用時,正確的類型是否被傳遞。

賬戶實例檢查——在Move中(有時在Solana上也是如此),你會在函數主體中做這件事。在這個特例中,這是沒有必要的,因為鎖和cap參數類型的通用類型參數T強制要求對cap(鑄幣能力/權限)對象的傳入要正確匹配其鎖(每個幣類型T只能有一個鎖)。

帳戶簽名檢查——我們在Sui中不直接處理簽名。對象可以由用戶擁有。造幣權限由造幣權限能力對象的所有權授予(由管理員創建)。在mint_balance函數中傳遞對該對象的引用將允許我們進行鑄幣。自有對像只能由其所有者在交易中使用。換句話說,對象的簽名檢查是由運行透明地完成的。

從本質上講,Move利用字節碼驗證,以使數字資產的編程模型更加自然。 Solana的模型圍繞賬戶所有權、簽名、CPI調用、PDA等。但我們退一步想一想,就會發現,我們並不想處理這些問題。它們與數字資產本身沒有任何關係——相反,我們不得不使用它們,因為這使我們能夠在Solana的編程模型中實現所需功能。

在Solana上,沒有字節碼驗證來保證更細化的類型或資源安全,你不能允許任何程序改動任何賬戶,所以引入賬戶所有權的概念是必要的。由於類似原因(沒有跨程序調用的類型/資源安全),也沒有可以進出程序的用戶所有對象的概念,相反,我們用賬戶簽名來證明權限。由於有時程序也需要能夠提供賬戶簽名,所以我們有PDA......

雖然你可以在Solana上擁有與Move相同的跨程序類型和資源安全,但你必須使用低級別的構建模塊(賬戶簽名、PDA...)手動實現它。歸根結底,我們正在用低級別的基元來構建可編程的資源(線性類型)。而這就是賬戶檢查的作用——它們是實現類型安全和手動建模資源需進行的開支。

Move對資源進行原生的抽象,允許我們直接處理資源,而不需要引入任何低級的構建塊,如PDA。跨越智能合約邊界的類型和資源安全保障是由驗證者確保的,不需要手動實現。

5.3 Solana可組合性的局限性

我想再舉一個例子,強調Solana上智能合約可合成性的一些痛點。

我們在鑄幣權限鎖的例子中看到,與Sui相比,我們需要在Solana上聲明更多的輸入(Solana上的6個賬戶vs. Sui上的2個對象的mint_to調用)。顯然,處理6個賬戶比處理2個對象更麻煩,特別是如果考慮到還需要為賬戶實現賬戶檢查。理論上來說這部分是可控的,但當我們開始在單一的調用中把多個不同智能合約組合在一起時會發生什麼?

假設我們想創建一個智能合約,能夠做以下事情:

它從鑄幣權限鎖程序中擁有某個代幣的鑄幣權,可以進行鑄幣

當它被調用時,它將使用其權限來鑄造用戶指定數量的代幣,使用AMM將其交換為不同的代幣,並在同一指令中將其發送給用戶

這個例子的重點是為說明鑄幣權限鎖智能合約和AMM智能合約將如何被組合在一起。指令調用的賬戶檢查可能看起來像這樣:

智能合約發展:全方位對比Move與Rust

17個賬戶。每個CPI調用(鑄幣和交換)5-6程序,加上程序賬戶。

在Sui上,一個相當的函數的簽名是這樣的:

智能合約發展:全方位對比Move與Rust

只有3個對象。

為什麼我們在Sui上傳遞的對象與Solana上的賬戶相比要少得多(3比17)?從根本上說,是因為在Move中我們能夠嵌入(包裹)它們。類型系統的安全保障使我們能夠做到這一點。

下面是一個Solana賬戶和Sui對象之間的比較,它們持有一個AMM池的狀態。

智能合約發展:全方位對比Move與Rust

我們可以看到,在Solana上我們存儲了其他賬戶的地址(Pubkeys),它們就像指針一樣,並不存儲實際的數據。為了訪問這些賬戶,它們需要被單獨傳入,我們還需手動檢查正確的賬戶是否被傳入。在Move中,我們能夠將結構相互嵌入並直接訪問其值。我們可以混合和匹配來自任何模塊的類型,同時它們保留其資源和類型的安全保證,這都得益於Move的全局類型系統和資源安全,它們都由字節碼驗證所驅動。

但是,在組成多個智能合約時,不得不傳遞(並因此檢查)許多賬戶,這造成了相當大的實施複雜性,並具有安全影響。這些賬戶之間的關係可能相當錯綜複雜,在某種程度上,難以跟踪所有必要的賬戶檢查及其是否正確實施。

其實,這就是我認為在Cashio漏洞中發生的情況(4800萬美元)。下面是該(不充分)賬戶檢查的分解,也由此導致了該漏洞。如你所見,這些賬戶檢查變得有些複雜。開發者充滿好的意圖進行正確檢查,但在某某程度上,精神壓力變得太大,就會非常容易出錯。賬戶越多,越容易出現錯誤。

Move的全局類型系統和更自然的編程模型,意味著我們可以在達到心理承受壓力的極限之前,以更大的安全性推動智能合約的構成。

附帶說明一下,Move的TCB(可信計算基礎)要比Rust/Anchor小得多。較小的TCB意味著需要進入智能合約編譯執行、被信任的的組件較少。這就減少了可能影響智能合約的漏洞表面積——TCB之外的漏洞不會影響智能合約的安全。

Move的設計考慮到了減少TCB——為盡可能減少TCB,Move做了許多決定。字節碼驗證器將許多由Move編譯器執行的檢查從TCB中移除,而在Rust/Anchor中,有更多的組件需要被信任,因此致命安全錯誤的表面積要更大。

6. Solana上的Move

我們能否在Solana上擁有Move,以及如何擁有?

6.1. 有全局類型安全的Anchor?

在我們開始研究之前,讓我們簡單看看Anchor,並做個小的思想實驗。也許我們可以以某種方式升級Anchor,來提供我們從Move中得到的一些好處?也許我們可以獲得對跨程序調用的類型安全的本地支持?畢竟,Anchor指令已經類似於Move的入口函數:

智能合約發展:全方位對比Move與Rust

也許我們可以延伸Anchor,使賬戶能直接被傳入指令參數。

智能合約發展:全方位對比Move與Rust

我們可以避免賬戶檢查?

在這種情況下,我們希望類型檢查由運行而不是程序來完成——運行將讀取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設計的內核沙盒程序執行要求一致(它也需要安全和高性能)

從表面上看,eBPF確實是一個可靠的選擇。高性能、圍繞安全設計,程序的大小和指令的數量是有限的,有一個字節碼驗證器......看起來很有不錯。

但讓我們看看這在實踐中意味著什麼。也許我們可以以某種方式利用eBPF驗證器來提高我們智能合約的安全性?以下是eBPF驗證器所做的一些事情:

-不允許無限循環

-檢查程序是否是一個DAG(有向無環圖)

-不允許越界跳轉(out-of-bounds jump)

-在進行各種輔助(helper)函數調用時檢查參數類型(輔助函數在內核中進行定義,例如用於修改網絡數據包)。

好吧,禁止越界跳轉似乎很有用,但其他作用有限。事實上,強制要求程序必須是一個DAG並且沒有無限循環是有問題的,因為它大大限制了程序的可操作性(我們沒有圖靈完備性)。在eBPF程序中需要這樣做的原因是,驗證器需要確定程序在一定數量的指令內終止(這樣程序就不會使內核終止;這就是著名的停機問題),而氣體計量(gas metering)不是一個選項,因為它將太過影響性能。

雖然這種取捨對實現高性能的防火牆來說是很好的,但對於智能合約的開發來說就不那麼好了。 eBPF驗證器的絕大部分都不能被重用在Solana程序上。事實上,Solana根本就沒有使用原始的eBPF驗證器,它使用的是一個(更基本的)自定義驗證器,主要是檢查指令是否正確和是否有越界跳轉。

同時,eBPF在設計上最多允許5個參數被傳遞給一個函數進行調用。這意味著Rust標準庫不能直接編譯到eBPF。或棧的大小被限制在512字節,這減少了我們可以傳遞給一個函數的參數的大小而不需要堆分配(heap allocation)。

因此,即使Rust編譯到LLVM,有LLVM的eBPF後端,甚至支持Rust編譯器針對eBPF使用,你仍然無法使Solana智能合約以其本來的樣子編譯到eBPF上。這就是為什麼Solana團隊不得不對Rust代碼庫和eBPF LLVM後端(例如,通過棧傳遞參數)進行多次修改。

由於其中一些修改本身是支持上游(無論是Rust還是LLVM),所以Solana團隊目前在維護Rust和LLVM的分叉時都做了這些修改。當你執行cargo build-bpf(構建Solana智能合約的典型命令)時,Cargo會拉出這個Solana特定版本的rustc(Rust編程語言的編譯器)來進行智能合約的編譯(原來的rustc不起作用) 。

這就是SBF的誕生過程——Solana需要的一些要求與eBPF不兼容。 Solana團隊目前正在努力將SBF作為一個獨立的LLVM後端上流,並將其作為一個Rust目標加入,以避免維護單獨分叉。

因此,雖然eBPF可以作為智能合約的一種格式,但它並不像表上看起來那麼理想。它需要進行一些修改,而且原來的驗證器也沒有很大的用處。

在關於Move和Solana/SBF的討論中,一個誤解就是,一些人認為Move的主要思想應該適用於SBF,因為它是基於eBPF的,也許可以利用其驗證器做靜態的賬戶改動檢查,而不是在運行時做動態檢查。

在我看來,這是一個令人懷疑的說法。即使有可能證明,程序不會在eBPF中改動他們不擁有的賬戶,這也確實是Move在做的事情,但這肯定不是Move的主要想法。

Move的主要思想是創造一個以資源為中心的編程模型,能夠自然地與不可信代碼互動。

在實踐中,這意味著:

-全局類型安全

-資源安全(鍵、克隆、存儲、丟棄)

-可嵌入的資源

-資源安全地流入和流出不受信任的代碼

...

將主要的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,這才是最重要的。

有三種方法可以在Solana上獲得Move:

-將Move虛擬機作為一個本地加載器添加(與SBF虛擬機一起)

-將Move虛擬機作為一個程序運行(如Neon)

-將Move編譯為SBF(像Solang)

讓我們先來討論(3)。這裡的想法是為Move建立一個LLVM前端,以便將其編譯為SBF。編譯成SBF的Move智能合約被透明地執行,就像用Rust(或其他任何可以編譯成SBF的語言)構建的智能合約一樣,而且運行時不需要對Move有任何區分或了解。從運行角度來看,這將是一個非常優雅的解決方案,因為它不需要改變它或它的安全假設。

但我認為以這種方式開發智能合約會比直接使用Anchor更糟。你通過(3)得到的是Solana編程模型中的Move語法。這意味著第五章中討論的Move的所有重要優勢(全局類型安全、全局資源安全、可嵌入對象......)將不復存在。相反,我們仍將不得不處理賬戶檢查、CPI調用、PDA等問題,就像在Rust中一樣。而且,由於Move不支持宏(macro),因此使用eDSL實現一個像Anchor這樣的框架,來簡化其中的一些工作是不可能的,所以代碼將與原始Rust相似(但可能更糟糕)。 Rust標準庫和生態系統也是不可用的,所以像賬戶序列化和反序列化這樣的事情必須在Move中重新實現。

Move不是很適合與其他編程模型一起使用。這是因為它被特別設計為能夠編譯成Move字節碼,並通過驗證器。考慮到圍繞能力和借貸檢查器的自定義規則,這是必要的。其字節碼驗證十分特殊具體,以至於其他語言幾乎沒有機會編譯成Move字節碼並通過驗證器。因為Move圍繞這種非常特殊的字節碼驗證而設,所以它不像Rust等語言那樣靈活。

剝離字節碼就放棄了Move的所有主要優勢。雖然Move的類型、資源和內存安全特性會在程序級別上被保留,但它們不會被全局保留。而程序級的安全並沒有帶來多少新的結果——通過Rust我們已經實現了這些結果。

Move的智能合約生態系統也不能在Solana上使用——編程模型不同,以至於智能合約的重要部分必須被重寫。考慮到所有這些,我預計用(3)實現Move的做法不會被接受。

至於(1),這裡的想法是(與SBF加載器一起)在運行時添加對Move加載器的支持。 Move智能合約將被存儲為鏈上的Move字節碼,並由Move VM執行(就像在Sui中一樣)。這意味著我們將有一個SBF智能合約的生態系統和一個Move智能合約的生態系統,前者將在當前的Solana編程模型上運行,而後者則在一個(可以說是更高級的)Move模型上運行。

有了這種方法,就有可能保持Move智能合約之間相互作用的所有好處,但這裡的一個挑戰是讓Move智能合約能夠與SBF智能合約進行互動,反之亦然——你需要一個對Move和Solana有深刻理解的人,驗證器也必須進行調整。

還有一個缺點是需要在運行時維護兩個不同的加載器。這會對安全有影響,因為它意味著攻擊面會翻倍——任何一個加載器的錯誤都可能意味著整個鏈被利用。實際上早在2019年,Solana就加入了對MoveVM的早期支持(#5150),但後來由於安全問題而被移除(#11184)。

至於(2),想法是將整個Move VM作為一個Solana程序(智能合約)運行。 Move VM是用Rust實現的,所以可能會把它編譯成SBF(除非它使用線程或其他不支持的API)。雖然這聽起來很瘋狂,但Neon已經實現了類似的方法,將EVM作為一個Solana程序來運行。這種方法的好處是,不需要對運行進行修改,而且可以保持相同的安全假設。

我不熟悉Move VM的技術細節,所以我不能對這種做法的可行性以及它的局限性做太多評論。我的第一個反應是,驗證器也必須作為一個程序運行,這意味著在計算預算內。這種方法也會像(1)一樣,受到SBF和Move智能合約之間互操作性問題的影響。

沒有直接的方法可以將Move的主要功能帶到Solana。雖然有可能建立一個LLVM前端,並將Move編譯為SBF,但這不會起太多作用,因為編程模型將保持不變。正如第6.1節中的思想實驗所說明的那樣,如果沒有某種字節碼驗證,就無法改善編程模型。改變eBPF/SBF以支持字節碼驗證將是非常困難的。似乎唯一合理的選擇就是以某種方式讓MoveVM運行。但這意味著將有兩個生態系統在不同的編程模型上運行,而讓它們正確地互操作是極具挑戰性。

6.4. Move的性能

Move的字節碼不是一種通用的字節碼語言。它有一個非常有“主見”的類型系統,為允許所有必要驗證,它是相當高級的。這意味著與其他字節碼格式(如eBPF/SBF)相比,其性能較低,因為後者更接近於本地代碼,人們可能會認為這對於其在高性能L1中的使用是一個問題。

但是,到目前為止,智能合約的執行在Solana(在寫這篇文章的時候,平均有3k TPS)和Sui(基於團隊所做的最初e2e基準)上都還未成為瓶頸。提高交易處理性能的主要方式就是並行執行。 Solana和Sui都實現了這一點,它們要求事先聲明依賴關係,並對依賴不同對象/賬戶集的事務執行進行並行調度。

此外,一旦TX執行出現在關鍵路徑上,沒有任何東西可以阻止Move被AOT編譯或JIT化以提高性能。這就是為Move構建一個LLVM前端的好處所在。另外,由於Move本身對靜態分析的適應性,Move也可能取得特有的進一步優化。

考慮到所有這些,我希望Move的性能在可預見的未來不會成為一個重要的障礙。

7. Move其他功能

7.1. 驗證器

Move有一個用於智能合約的形式化驗證工具,叫做Move Prover。通過這個工具,你能夠判斷不同的不變量對你的智能合約是否成立。在幕後,驗證條件被翻譯成SMT公式,然後使用SMT求解器進行檢查。這與模糊測試有很大不同,例如,模糊測試是通過走查輸入空間來試錯。例如,如果模糊測試和單元/集成測試未能測試出特定的輸入或輸入組合,顯示程序有誤,那麼它們仍然可以提供一個假陽性。另一方面,驗證器本質上提供了形式上的證明,即指定的不變量對所提供的程序成立。這就像針對所有可能的輸入檢查程序一樣,但不需要這樣做。

移動驗證器的速度非常快,使它可以像類型檢查器或linter那樣被整合到常規開發工作流程中。

下面是一個驗證器的例子(摘自《用Move Prover對智能合約進行快速可靠的形式驗證白皮書》)。

智能合約發展:全方位對比Move與Rust

7.2. 錢包安全

由於Sui要求所有交易將訪問的對像都在函數參數中傳遞(不從全局狀態中動態加載),並且移動函數簽名連同類型信息都存儲在字節碼本身中,我們可以讓錢包在用戶簽名之前向用戶提供更有意義的信息,說明交易的內容。

例如,如果我們有一個具有以下簽名的函數:

智能合約發展:全方位對比Move與Rust

我們可以從函數的簽名中看出,這個交易將訪問用戶的3個資產(資產類型)。不僅如此,根據&和&mut關鍵字(或沒有關鍵字),我們還可以知道資產1可以被讀取,資產2可以被改動(但不能轉移或銷毀),而資產3有可能被改動、轉移或銷毀。

錢包可以向用戶顯示這些信息,然後用戶可以更加了解交易可能對資產做出什麼動作。如果有什麼異樣,例如,來自Web3應用程序的交易調用正在接觸一些不應接觸的資產或幣,用戶可以觀察到這一點,決定不繼續進行交易。

錢包也可以另外模擬交易,這將給用戶提供更多關於其結果的信息。 Sui編程模型以對象為中心,類型信息對運行原生,這意味無需對智能合約有任何具體的應用級知識,就能解釋對象的變化。

這在Solana上是不可能的,因為從運行的角度來看,賬戶包含任意數據。你需要賬戶的外部描述(特定於應用程序)才能對其進行解釋,而智能合約發布者未必提供這些信息。另外,Solana運行時中不存在資產所有權的概念,每個智能合約都需要手動實現這一語義(通常使用賬戶簽名和PDA),這意味著沒有通用方法來對此進行追踪。

7.3 簡單交易和復雜交易

具體到Sui,在共識層面上有一個有趣的優化,允許某些類型的交易放棄完全的共識,轉而使用基於拜占庭一致廣播(Byzantine Consistent Broadcast)的更簡單算法。這樣的好處是,這些交易可以在共識層面上並行,消除隊頭阻塞(head-of-line blocking),達成幾近即時的最終性——基本上實現了web2的可擴展性。

這是由於Sui對自有和共享對象的區分(見3.1節)。只涉及自有對象的交易(被稱為簡單交易)不需要在Sui上達成完全共識。由於自有對象除了發送者外不能在交易中使用,且發送者一次只能發送一個交易,這本身就意味著這些交易不需參照其他交易進行排序(總排序與因果排序)——我們知道交易中引用的對像不能被其他交易影響,且該交易也不能影響其他對象。因此,我們並不關心該事務相對於鏈上平行發生的其他事務的排序——這實際上是不相關的。 Sui能夠利用這一事實,大大優化簡單事務的處理,在幾百毫秒內實現最終性。但缺點是,發送者一次只能發送一個交易。另一方面,涉及任何數量共享對象的交易(被稱為複雜交易),總是需要完全共識。

考慮到自有對象的創建、轉移和修改可以完全通過簡單事務完成,某些類型的應用可以很好地利用簡單事務。很好的例子是NFT(包括大規模造幣)和web3遊戲。這些用例從低延遲的最終性和消除對頭阻塞中獲益良多,實現了更好的用戶體驗和可擴展性。

但其他類型的應用程序必須依賴複雜交易。這包括大多數DeFi應用程序。例如,AMM流動性池需要成為一個共享對象,因為任何種類的交易所訂單執行都需完全共識和總排序。因為從根本上說,如果多個訂單同時來自不同用戶,我們需要就先執行誰的訂單達成一致,這就決定了每個用戶會得到什麼樣的執行價格。

還有一些應用程序可以混合使用簡單和復雜交易。這些應用需要復雜交易才能實現它們所需功能,但在某些操作上可以利用簡單交易來獲得更好效率。例如,一個價格預言機就可以如此設計。我們可以讓多個發布者使用簡單交易,為市場提交價格數據,然後由一個權威機構使用複雜交易對價格進行匯總(例如,股權加權中值)。在某些時候不依靠複雜交易是不可能實現價格預言機的(根本原因是在其他交易中使用發布價格需要就排序達成一致,從而達成完全共識),但至少我們可以用簡單交易優化發布者的寫入。

Sui文檔有關於簡單和復雜交易的更多細節。

https://docs.sui.io/devnet/learn/sui-compared

https://docs.sui.io/devnet/learn/how-sui-works#system-overview

8. 結束語

本文深入探討並比較Solana和Sui的編程模型,也對Move編程語言進行探討,

第二章是對Solana編程模型的總結,而第三章則介紹了Sui Move及其編程模型。第4章接著解釋了Move中的類型和資源安全如何運作。 Move功能對智能合約開發的意義無法立竿見影,所以在第5章中,我利用現實生活中的例子對Solana和SuiMove進行了更徹底的比較。第6章討論了eBPF/SBF,表明讓Move功能或Move本身在Solana上工作並不容易。第7章討論了Sui的一些Move相關功能。

智能合約編程是關於數字資產的編程。可以說這是一種新的編程類型,與我們目前看到的其他類型編程(如係統、後台......)截然不同。正因如此,現有編程語言和編程模型自然不能很好適應這種用例。

問題的關鍵在於,我們希望有一個編程模型,能夠自然地與資源打交道,但同時又與不受信的代碼互動。 Solana在這裡做了妥協,它使智能合約在一個不信任環境中具備了必要的可編程性,但其編程模型對於用資源編程來說並不自然。字節碼驗證使其有可能同時擁有這兩種特性。在某種程度上,它把不受信代碼變成了受信代碼。

Move是一種用於智能合約開發的新型編程語言。它的核心創新之處在於它的字節碼,被特意設計為可被驗證。雖然字節碼驗證本身並不是一個新概念,但Move所做的驗證確實是一種創新。通過其字節碼和驗證,Move實現了一個智能合約編程模型,對資源能夠有一流支持,並能保證在一個不受信任的環境中安全編程。

我認為Move對智能合約開發的作用就像React對前端開發的作用一樣。說“用Move做的事能用Rust做”就像說“用React做的事能用jQuery做”一樣。當然有可能實現基於jQuery的應用,能夠與React應用相當,但這並不實際。 React引入了虛擬DOM的概念,這對開發者來說是完全易懂的的,但使前台的開發速度更快、可擴展、更簡單。同樣,Move的字節碼驗證是一種底層技術,對開發者來說也易於理解,但它提供了一個更符合人體工效學、可組合、更安全的智能合約開發。由於其安全性和更直觀的編程模型,Move也大大降低了智能合約開發者的准入門檻。

如果Move能設法獲得影響(有早期跡象表明它會的),它可能對Solana構成極大威脅。有兩點原因。

首先Move智能合約的開發時間要快得多。在Move中從頭開始開發一個智能合約可能比在Rust中快2-5倍。因此,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在網絡行業的情況。

正因如此,我完全可以預料,在中長期內,Solana會加入對Move進行一流支持。但這並容易。為了獲得Move的主要好處,Move字節碼需要得到本地支持,這意味著簡單地將Move編譯成eBPF/SBF是不可能的(見第6.3節)。為了保持現有生態系統,兩種運行都需要得到支持。主要的技術挑戰是如何在運行之間實現適當的互操。這需要對Move和Solana的深入了解,所以我希望Solana團隊能在Move團隊的支持下對此進行直接推動。

Move起源於Meta(née Facebook)的Diem項目。 Move團隊由Sam Blackshear領導,其任務是弄清楚如何處理智能合約。在仔細研究了這個問題後,他們發現智能合約的編程都是關於數字資產(資源),但現有語言都不支持該用例,於是決定從頭開始建立一種新的編程語言。

我想強調的是,創建一門新語言並不是突然做出的決定,它需要多年的努力才能落地,因為在大多數情況下,使用現有解決方案會更好。 Move團隊正確預見到,一種安全、對資源有一流支持、同時又足夠靈活的智能合約語言是可以建立的,僅此一點就顯示出他們高度的專業性。這是團隊和支持該項目的Novi/Meta領導層的一個大膽舉動(會有一些董事和副總裁參與)。 Meta後來停止了他們在Diem上的努力,並最終沒能夠收穫其在Move上的投資成果。但它為更廣泛加密貨幣社區做出了偉大貢獻。

總而言之,Move是一項了不起的技術,我相信它會對我們如何開發智能合約產生巨大影響。