深度解析智能合約的發展:Move與Rust對比研究
原文標題:《Smart Contract Development—Move vs.Rust》
原文編譯:郭倩雯,鏈捕手
原文編譯:郭倩雯,鏈捕手
一級標題
原文標題:《
原文編譯:郭倩雯,鏈捕手
原文編譯:郭倩雯,鏈捕手
一級標題
一級標題1. 介紹但目前我們看到的討論並沒有達到一定深度,能夠真正參透這些新科技對我們的影響。這在討論的兩極都適用——Move 的質疑者將Move 貶低得一無是處,無法欣賞到它更細微(但十分重要)的一面,但Move 的支持者,過度鼓吹Move,也沒有能看透究竟是什麼使其偉大。這就帶來巨大的中間地帶與模糊不清,致使外界看客、加密開發人員、投資人員,關注此話題,但又無法對自己的觀點確信。
一級標題
在這篇文章中,我將對Move、其新穎的編程模型、Sui 區塊鍊和它如何利用Move 的功能,以及它與Solana 及其編程模型的比較進行深入的技術挖掘。為了突出Move 的特點,我將把Solana/Rust 與Sui/Move 進行比較。因為當你把一個東西與另一個你已熟悉的東西進行比較時,理解就會更容易。
Move 還有其他變種,如Aptos Move,它們在某些方面略有不同。本文的重點不是討論Move 不同變體之間的細微差別,而是展示Move 的普遍優勢,以及它與Solana 編程模型的比較。因此為了簡單起見,我在本文中只使用一個變體(Sui Move)。因此,我在
但即便如此,本文所討論的所有Move的主要優點都適用於所有Move集成(原生設定上支持Move字節碼Move bytecode),包括Aptos。我選擇Sui,只是因為我對它更熟悉,且我覺得它更直觀一些,更容易以文章的形式呈現。
一級標題
一級標題
2. Solana編程模型
在Solana上,程序(智能合約)是無狀態的,它們自己不能訪問(讀或寫)任何在整個交易中持續存在的狀態。為了訪問或保持狀態,程序需要使用賬戶。每個賬戶都有一個唯一的地址(Ed25519密鑰對的公鑰),可以存儲任意的數據。
每個賬戶也有一個與之相關的私鑰(相應的公鑰是它的地址),能夠訪問這個私鑰的用戶可以用它來簽署交易。利用這種機制,我們在Solana智能合約中實現了權限和所有權的功能--例如,為了獲取某些資金,智能合約可以要求用戶提供必要的簽名。
一級標題
在其他做程序調用時,客戶需要指定這個程序在調用時將訪問哪些賬戶。這樣一來,交易處理運行時間就可以安排不重迭的交易並行執行,同時保證數據一致性。這是Solana的設計特點之一,使其具有高吞吐量。
一級標題
二級標題
一級標題
3. Move的編程模型
在Solana中,這就相當於所有智能合約都作為模塊發佈在一個程序中。這意味著所有的智能合約(模塊)都包含在同一類系統中,可以直接相互調用,而不需要通過中間的API或接口。這一點非常重要,其影響將在本文中徹底討論。
二級標題
二級標題
3.1 對象
要注意的是,下面的對象概念針對於Move的Sui變體。而在Move的其他集成中(例如Aptos或Diem/core Move),情況可能略有不同。不過,在其他Move變體中也有類似的解決方案,可以實現同樣的事情(狀態的持久性),這些解決方案並沒有太大區別。
這裡介紹Sui變體的主要原因是,文章後面的代碼樣本都基於Move的Sui變體,同時其對像比如core Move中的全局存儲機制更直觀易懂一點。重要的是,本文所討論的Move的所有主要優點都適用於所有Move集成(原生支持Move字節碼),包括Aptos。
對像是由運行時存儲的結構實例(struct instance),並在事務中持續保持狀態。
有三種不同類型的對象(在Sui中):
自有對像是屬於用戶的對象。只有擁有該對象的用戶才能在交易中使用它。所有權元數據是完全透明的,由運行處理。它使用公鑰加密技術實現——每個自有對像都與一個公鑰關聯(運行時存儲在對像元數據中),任何時候你想在交易中使用對象,你都需要提供相應簽名(現在支持Ed25519,即將支持ECDSA和K-of-N多簽名)。
一級標題
4. Move的安全性
共享對像類似於自有對象,但它們沒有一個與之相關的所有者。因此,你不需要擁有任何私鑰就可以在交易中使用它們(任何人都可以使用它們)。任何自有對像都可以被共享(由其所有者),一旦一個對像被共享,它將永遠保持共享——永遠不能被轉移或再次成為自有對象。
Move編程模型非常直觀和簡單。每個智能合約是一個模塊,由函數和結構定義組成。結構在函數中被實例化,並可以通過函數調用傳遞到其他模塊。為了使一個結構能夠在跨交易中保持持久,我們把它變成一個可以被擁有、共享或不可改變的對象(僅限於Sui,在其他Move變體中略有不同)。
一級標題
一級標題
4. Move的安全性
任何人都可以發布一個(潛在的敵對)模塊
二級標題
不存在模塊擁有結構的概念,這將使所有者模塊擁有改變結構的唯一權力,就像Solana賬戶的情況一樣——結構可以流入其他模塊,也可以嵌入其他結構中。

問題是,這種做法為什麼是安全的?是什麼阻止了人們發布惡意模塊,獲取共享對象(如AMM池),並將其發送到惡意模塊中,然後繼續耗盡其資金?
這就是Move的新穎之處......讓我們來談談資源。
二級標題
二級標題
4.1 結構
定義一個結構(struct)類型和你所期望的差不多:
到目前為止還不錯——這也是你在Rust中定義一個結構的方式。但在Move中,結構有其獨特之處與傳統編程語言相比,Move模塊在如何使用類型上擁有更多空間。在上面的代碼片斷中定義的結構將受以下限制:
它只能在定義該結構的模塊中被實例化(“打包”)和銷毀(“解包”)——也就是說,你不能從任何其他模塊的任何函數中實例化或銷毀一個結構實例。

結構實例的字段只能在其模塊中被訪問(因此也可以被改動)。
不能在其模塊之外克隆或複制結構實例
不能將一個結構實例存儲在其他結構實例的字段中
這意味著,如果在其他模塊的函數中處理這個結構的實例,我們將無法改動其字段、克隆它、將其存儲在其他結構的字段中,或將其丟棄(必須通過函數調用將其傳遞到其他地方)。情況是這樣的:該結構的模塊實施了能從我們模塊中調用的函數,來完成這些事情。但除此之外,我們無法直接為外部類型做這些事情。這使模塊可以完全控制如何使用和不使用其類型。
由於這些限制,我們似乎失去很多靈活性。這也是事實——在傳統編程中,處理這樣的結構會非常麻煩,但事實上,這正是我們在智能合約中想要的。智能合約開發畢竟是關於數字資產(資源)的編程。如果你看一下上面描述的結構,這正是它的本質——它是一種資源。它不能隨意被憑空創造,不能被複製,也不能被意外地銷毀。因此,我們確實在這裡失去了一些靈活性,但我們失去的靈活性正是我們所希望的,因為這使對資源的操作變得直觀而安全。
鍵- 允許一個結構成為一個對象(專屬Sui,core Move情況略有不同)。如前所述,對像是持久化的,如果是自有對象,需要用戶簽名才能在智能合約調用中使用。當使用鍵能力時,結構的第一個字段必須是具有UID類型的對象ID。這將給它一個全球唯一的ID,能夠用它進行引用。
二級標題
存儲- 允許將該結構作為一個字段嵌入另一個結構中

二級標題鏈接)。
鏈接
二級標題
4.2 幣(Coin)
你可以在Sui代碼庫中找到完整的模塊實現(
幣類型具有鍵和存儲的功能。鍵意味著它可以作為一個對象使用。這允許用戶直接擁有幣(作為一個頂層對象)。當你擁有一個幣時,除你之外,其他人甚至不能在交易中引用它(更不用說使用它)。存儲意味著,幣可以作為一個字段嵌入到另一個結構中,這對於可組合性很有用。
二級標題
由於沒有丟棄功能,幣不能在函數中被意外丟棄(銷毀)。這是一個非常好的特性——它意味著你不會意外地丟失一個幣。如果你正在實現以接收硬幣為參數的函數,在函數結束時,你需要明確地對它做一些事情——把它轉移給用戶,把它嵌入另一個對象,或者通過調用把它送入另一個函數(同樣需要對它做一些事情)。當然,通過調用幣模塊中的coin::burn函數來銷毀一個幣是可能的,但你需要有目的地這樣做(你不會這樣意外操作的)。
沒有克隆能力意味著沒有人可以復制幣,從而憑空創造新的供應。創造新的供應可以通過coin::mint函數來完成,而且只能由該幣的國庫能力對象(treasury capability)的所有者調用。
在Move中,資源的安全性由其類型定義。考慮到Move有全局類型系統,這使編程模型更自然和更安全,資源可以直接傳入和傳出不受信任的代碼。
二級標題
二級標題
那麼,是什麼保證了這些規則被任意模塊所遵守?是什麼阻止了人們上傳具有特殊製作字節碼的模塊,例如接收一個幣對象,然後直接改變其內部字段來繞過這些規則?通過這樣做,可以非法地增加所有幣的數量。光是字節碼的語法就肯定允許這樣做。
一級標題
在Move中,類型確實存在於各個模塊中——類型系統是全局的。這意味著不需要CPI調用,賬戶編碼/解碼,賬戶所有權檢查等——你只需直接調用另一個模塊中的函數與參數。整個智能合約的類型和資源安全由編譯/發佈時的字節碼驗證來保證,不需要像Solana那樣在智能合約層面上實現,然後在運行時檢查。
二級標題
一級標題
一級標題
現在我們已經看到了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的線性類型系統和資源安全保障如何使我們以其他編程語言無法實現的方式去表達功能。
二級標題
二級標題
5.2 鑄幣權限鎖(Mint Authority Lock)
“鑄幣權限鎖”智能合約擴展了代幣鑄造的功能,允許多個白名單方(authority)鑄造代幣。該智能合約的所需功能如下(同時適用於Solana和Sui的實現):
原始的代幣鑄幣權限方創建一個“鑄幣鎖”,這將使我們的智能合約能夠監管鑄幣。調用者成為該鑄幣鎖的管理員。
管理員可以為該鎖創建額外的鑄幣授權,可以授權給其他各方,並允許他隨時使用該鎖來鑄造代幣。
每個鑄幣授權都有每日可以鑄造的代幣數量限制。

管理員可以在任何時候禁止(和解除)任何鑄幣權限方。
管理員的能力可以轉讓給另一方。
這個智能合約可用於,例如將代幣的鑄幣能力交給其他用戶或智能合約,而原來的鑄幣權限方(管理員)仍然保留對鑄幣的控制權。不然,我們將不得不把鑄幣的全部控制權交給另一方,這並不理想,因為我們只得相信它不會濫用該權力。而且給多方提供許可也是不可能的。
這些智能合約的完整實現可以在這裡(Solana)和這裡(Sui)找到。
注意:請不要在生產中使用這段代碼! 這是示例代碼,僅用於教育目的。雖然我已經測試了它的功能,但我還沒有做徹底的審計或安全測試。
現在讓我們來看看這些代碼,看看實現方式有什麼不同。下面是這個智能合約的完整Solana和Sui實現的並排代碼截圖。
可以注意到的是,對於相同的功能,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指令所需的賬戶檢查(權限持有人通過調用這個指令來鑄造代幣):
有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函數只需要四個參數。在這四個參數中,只有lock和cap代表對象(有點類似於賬戶)。
在Solana中,我們需要聲明6個賬戶,並手動實現對它們的各種檢查,而在Move中,我們只需要傳入2個對象,而且不需要明確的檢查,這是如何實現的?
在Move中,這些檢查有些是由運行透明地完成的,有些是由驗證器在編譯時靜態地完成的,而有些則是在構造上根本不需要的。
賬戶所有權檢查——Move有類型系統,因此這種設計不必要。一個Move結構只能通過其模塊中定義的函數進行改動,而不能直接改動。字節碼驗證保證了結構實例可以自由地流入不受信任的代碼(其他模塊)而不被非法改動。
帳戶簽名檢查——我們在Sui中不直接處理簽名。對象可以由用戶擁有。造幣權限由造幣權限能力對象的所有權授予(由管理員創建)。在mint_balance函數中傳遞對該對象的引用將允許我們進行鑄幣。自有對像只能由其所有者在交易中使用。換句話說,對象的簽名檢查是由運行透明地完成的。
二級標題
從本質上講,Move利用字節碼驗證,以使數字資產的編程模型更加自然。 Solana的模型圍繞賬戶所有權、簽名、CPI調用、PDA等。但我們退一步想一想,就會發現,我們並不想處理這些問題。它們與數字資產本身沒有任何關係——相反,我們不得不使用它們,因為這使我們能夠在Solana的編程模型中實現所需功能。
在Solana上,沒有字節碼驗證來保證更細化的類型或資源安全,你不能允許任何程序改動任何賬戶,所以引入賬戶所有權的概念是必要的。由於類似原因(沒有跨程序調用的類型/資源安全),也沒有可以進出程序的用戶所有對象的概念,相反,我們用賬戶簽名來證明權限。由於有時程序也需要能夠提供賬戶簽名,所以我們有PDA......
Move對資源進行原生的抽象,允許我們直接處理資源,而不需要引入任何低級的構建塊,如PDA。跨越智能合約邊界的類型和資源安全保障是由驗證者確保的,不需要手動實現。
二級標題
二級標題
5.3 Solana可組合性的局限性

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

假設我們想創建一個智能合約,能夠做以下事情:
它從鑄幣權限鎖程序中擁有某個代幣的鑄幣權,可以進行鑄幣
當它被調用時,它將使用其權限來鑄造用戶指定數量的代幣,使用AMM將其交換為不同的代幣,並在同一指令中將其發送給用戶

這個例子的重點是為說明鑄幣權限鎖智能合約和AMM智能合約將如何被組合在一起。指令調用的賬戶檢查可能看起來像這樣:
17個賬戶。每個CPI調用(鑄幣和交換)5-6程序,加上程序賬戶。
在Sui上,一個相當的函數的簽名是這樣的:
只有3個對象。
為什麼我們在Sui上傳遞的對象與Solana上的賬戶相比要少得多(3比17)?從根本上說,是因為在Move中我們能夠嵌入(包裹)它們。類型系統的安全保障使我們能夠做到這一點。
但是,在組成多個智能合約時,不得不傳遞(並因此檢查)許多賬戶,這造成了相當大的實施複雜性,並具有安全影響。這些賬戶之間的關係可能相當錯綜複雜,在某種程度上,難以跟踪所有必要的賬戶檢查及其是否正確實施。
一級標題
Move的設計考慮到了減少TCB——為盡可能減少TCB,Move做了許多決定。字節碼驗證器將許多由Move編譯器執行的檢查從TCB中移除,而在Rust/Anchor中,有更多的組件需要被信任,因此致命安全錯誤的表面積要更大。
二級標題
一級標題

一級標題

我們能否在Solana上擁有Move,以及如何擁有?
二級標題
二級標題
6.1 有全局類型安全的Anchor?
在我們開始研究之前,讓我們簡單看看Anchor,並做個小的思想實驗。也許我們可以以某種方式升級Anchor,來提供我們從Move中得到的一些好處?也許我們可以獲得對跨程序調用的類型安全的本地支持?畢竟,Anchor指令已經類似於Move的入口函數:
也許我們可以延伸Anchor,使賬戶能直接被傳入指令參數。
我們可以避免賬戶檢查?
在這種情況下,我們希望類型檢查由運行而不是程序來完成——運行將讀取Anchor賬戶判別器(或其等價物),並能夠檢查賬戶傳入是否符合所需的判別器(Anchor賬戶的前8個字節)。
Solana不對同一程序的不同指令調用進行區分,這是由程序手動實現的(在這種情況下,繁重的工作由Anchor完成)。因此,為了做到這一點,運行必須以某種方式了解不同指令、它們的簽名、類型信息。
Solana程序編譯為SBF(Solana Bytecode Format,eBPF的一種變體),並以這種方式上傳到鏈上(和執行)。 SBF本身並沒有嵌入任何可以幫助我們的類型或函數信息。但也許我們可以修改SBF,以允許指令和類型信息被嵌入二進製文件中?這樣所需的指令和簽名信息就可以由運行從二進製文件中讀取。
我們確實可以這樣做。這將要求相當大的工程量,特別是考慮到我們需要保持與舊程序的向後兼容,但這是我們能獲得的好處:
-賬戶所有權和類型檢查由運行而不是程序完成
-對於在編譯時已知地址的賬戶(例如程序賬戶),我們可以避免從客戶端傳入它們,現在可以由運行傳入。
-如果我們設法將賬戶約束嵌入到二進製文件中,我們可以進一步減少必須由客戶端傳入的賬戶數量,用運行對其進行動態遞歸在加載(基於嵌入的約束信息)。
-當進行跨程序調用時,賬戶類型檢查仍然需要在運行時動態進行,而不是像Move中那樣在編譯時靜態進行。
二級標題
注意:這只是一個思想實驗。並不說明其可以安全完成,也不是代表其實現困難,更不標榜這些好處值得付出工程量般的努力。
這些好處確實不錯,但從智能合約開發的角度來看,它們並沒有從根本上改變什麼。在運行時而不是程序中做類型檢查可能能帶來一些性能上的好處,而且不必在編譯時從客戶端手動傳遞地址賬戶,在一定程度上提升工效(這也可以通過工具化來緩解)。但我們最終仍然在處理Solana的編程模型,它本身在處理數字資產上提供更多幫助——我們仍然沒有原生的資源安全,我們不能嵌入賬戶,所以仍然有賬戶膨脹問題,我們仍然在處理賬戶簽名和PDA......
我們希望有一個自然的編程模型,但與此同時,我們也在處理不受信任的代碼。雖然在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我們已經實現了這些結果。
還有一個缺點是需要在運行時維護兩個不同的加載器。這會對安全有影響,因為它意味著攻擊面會翻倍——任何一個加載器的錯誤都可能意味著整個鏈被利用。實際上早在2019年,Solana就加入了對MoveVM的早期支持(#5150),但後來由於安全問題而被移除(#11184)。
二級標題
6.4. Move的性能
至於(2),想法是將整個Move VM作為一個Solana程序(智能合約)運行。 Move VM是用Rust實現的,所以可能會把它編譯成SBF(除非它使用線程或其他不支持的API)。雖然這聽起來很瘋狂,但Neon已經實現了類似的方法,將EVM作為一個Solana程序來運行。這種方法的好處是,不需要對運行進行修改,而且可以保持相同的安全假設。
沒有直接的方法可以將Move的主要功能帶到Solana。雖然有可能建立一個LLVM前端,並將Move編譯為SBF,但這不會起太多作用,因為編程模型將保持不變。正如第6.1節中的思想實驗所說明的那樣,如果沒有某種字節碼驗證,就無法改善編程模型。改變eBPF/SBF以支持字節碼驗證將是非常困難的。似乎唯一合理的選擇就是以某種方式讓MoveVM運行。但這意味著將有兩個生態系統在不同的編程模型上運行,而讓它們正確地互操作是極具挑戰性。
但是,到目前為止,智能合約的執行在Solana(在寫這篇文章的時候,平均有3k TPS)和Sui(基於團隊所做的最初e2e基準)上都還未成為瓶頸。提高交易處理性能的主要方式就是並行執行。 Solana和Sui都實現了這一點,它們要求事先聲明依賴關係,並對依賴不同對象/賬戶集的事務執行進行並行調度。
7. Move其他功能
二級標題
考慮到所有這些,我希望Move的性能在可預見的未來不會成為一個重要的障礙。
一級標題
二級標題

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

下面是一個驗證器的例子(摘自《用Move Prover對智能合約進行快速可靠的形式驗證白皮書》)。
二級標題
二級標題
例如,如果我們有一個具有以下簽名的函數:
二級標題
我們可以從函數的簽名中看出,這個交易將訪問用戶的3個資產(資產類型)。不僅如此,根據&和&mut關鍵字(或沒有關鍵字),我們還可以知道資產1可以被讀取,資產2可以被改動(但不能轉移或銷毀),而資產3有可能被改動、轉移或銷毀。
錢包可以向用戶顯示這些信息,然後用戶可以更加了解交易可能對資產做出什麼動作。如果有什麼異樣,例如,來自Web3應用程序的交易調用正在接觸一些不應接觸的資產或幣,用戶可以觀察到這一點,決定不繼續進行交易。
這在Solana上是不可能的,因為從運行的角度來看,賬戶包含任意數據。你需要賬戶的外部描述(特定於應用程序)才能對其進行解釋,而智能合約發布者未必提供這些信息。另外,Solana運行時中不存在資產所有權的概念,每個智能合約都需要手動實現這一語義(通常使用賬戶簽名和PDA),這意味著沒有通用方法來對此進行追踪。
二級標題
二級標題
這是由於Sui對自有和共享對象的區分(見3.1節)。只涉及自有對象的交易(被稱為簡單交易)不需要在Sui上達成完全共識。由於自有對象除了發送者外不能在交易中使用,且發送者一次只能發送一個交易,這本身就意味著這些交易不需參照其他交易進行排序(總排序與因果排序)——我們知道交易中引用的對像不能被其他交易影響,且該交易也不能影響其他對象。因此,我們並不關心該事務相對於鏈上平行發生的其他事務的排序——這實際上是不相關的。 Sui能夠利用這一事實,大大優化簡單事務的處理,在幾百毫秒內實現最終性。但缺點是,發送者一次只能發送一個交易。另一方面,涉及任何數量共享對象的交易(被稱為複雜交易),總是需要完全共識。
https://docs.sui.io/devnet/learn/sui-compared
https://docs.sui.io/devnet/learn/how-sui-works#system-overview
一級標題
考慮到自有對象的創建、轉移和修改可以完全通過簡單事務完成,某些類型的應用可以很好地利用簡單事務。很好的例子是NFT(包括大規模造幣)和web3遊戲。這些用例從低延遲的最終性和消除對頭阻塞中獲益良多,實現了更好的用戶體驗和可擴展性。
但其他類型的應用程序必須依賴複雜交易。這包括大多數DeFi應用程序。例如,AMM流動性池需要成為一個共享對象,因為任何種類的交易所訂單執行都需完全共識和總排序。因為從根本上說,如果多個訂單同時來自不同用戶,我們需要就先執行誰的訂單達成一致,這就決定了每個用戶會得到什麼樣的執行價格。
Sui文檔有關於簡單和復雜交易的更多細節。
一級標題
一級標題
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也大大降低了智能合約開發者的准入門檻。


