前序博客有:
定位为高性能L1的Aptos和Sui,均采用Move合约编程语言。Solana也定位为高性能L1,但其采用Rust合约编程语言。本文重点对比Sui/Move和Solana/Rust合约编程语言。【Aptos/Move为不同的Move变种,有细微的差别。不过只要原生支持Move bytecode,则所有主要Move优势适于所有Move变种。】
Solana中的智能合约为programs,Move中的智能合约为modules。
Solana的智能合约编程模型是基于programs和accounts。Solana中,通常将智能合约称为“programs”。
Solana中的program是无状态的,需依赖account中的data数组来实现state transition:
program分为:
默认情况下,所有的accounts初始owner都为System program:
注意:Solana中,client在发起program call时,需提前指定该调用中所访问的accounts,并确定是读还是读写相应的account。这对交易处理性能来说至关重要。由于runtime现在知道了每笔交易总哪些accounts会被modify,可规划可并行执行的non-overlapping transactions,从而保证数据一致性。这也是Solana具有高吞吐量的关键原因之一。所谓的“EVM doesn’t scale”,是指EVM无法对交易进行并行处理。更多关于Solana交易处理runtime可参看Anatoly Yakovenko 2019年博客Sealevel — Parallel Processing Thousands of Smart Contracts。
可将Solana合约看成是操作系统中的程序,accounts看成是文件,任何人都可自由部署和运行任何合约,当合约运行时,可读写文件(accounts)。文件对所有合约都是可读的,但仅具有某文件ownership的合约才有权限对该文件进行写操作。合约之间不信任但可相互调用,在实际执行时,都需要假设输入具有潜在恶意。由于操作系统可供任何人全局访问,会为合约提供原生的验签支持,为用户提供authority和ownership functionality。
Solana的account具有ownership的概念。 每个account严格由且仅由一个program拥有。 即意味着只有拥有该account的program才可对其进行修改。其它program无法对该account修改。account ownership信息存储在account的metadata中,且无法由user programs修改。
client在发起program call时,可附加account签名(注意每个account关联一组Ed25519密钥对)。当有某account的私钥时,可用其对交易签名,然后在program runtime中会将该account标记为“signer”,program可利用该信息实现ownership and authority functionality。事实上,这也是Solana钱包的实现原理。每个account关联有一定数量的SOL(也存储在account metadata中),当需要转账时,需使用account的私钥对转账交易进行签名。因此,所谓的Solana钱包,是指你拥有相应私钥的account。
也通过CPI(cross-program invocation)接口实现跨合约调用——由合约A调用合约B。CPI calls与client program calls非常类似,以program ID来表示想要调用的program,并传入相应的参数和所需的accounts。内部,CPI calls通过syscall——runtime将以 与client直接调用 相同的方式执行指定的program。
program本身在CPI calls中也具备提供account signatures的能力——通过PDA(program derived address)accounts来实现。
PDA accounts是特殊类型的accounts,仅可由program来签名。【program可不拥有或存储相应的私钥】
PDA accounts为program specific的,每个program可生成其想要的任意数量的PDA accounts。用户 或 其它program,无法为特定program的PDA accounts 提供签名。
PDA accounts的重要性在于,program具备了authority和ownership能力。如,program可own tokens,为“某token vaults仅由某program具有取款功能,而任何user或其它program均无相应权限” 的原理——该vault由program logic完全保护——即,若program是正确的,则vault是安全的。
PDA 还可用于创建具有指定address的accounts。
以上4点为Solana智能合约安全性和可组合型的基石。
只要能编译为SBF(Solana Bytecode Format,改良版的eBPF),Solana合约可 以任意语言编写,通常,会选择Rust语言。
Solana的所有合约都会定义如下的entrypoint:
当合约被调用时,runtime会调用相应的entrypoint函数,传入的参数有:
AccountInfo
结构体类型,包含了account data以及相关的metadata(如哪个合约是其owner、该account是否可mutated、交易是否已被其私钥签名等等)。合约会根据以上参数进行处理并执行相应的逻辑。
注意:Solana中每笔交易可包含多个sequential program calls(对同一或不同合约)——被称为“instructions”。交易是atomic的,若instructions(program calls)中有任何fail,则整笔交易将fail,对全局状态无任何影响。
由于client可传入任意accounts和任意instruction data,合约必须仔细处理这些输入,确保恶意编写的输入不会以意外的方式影响程序执行。
大多数时候,都希望在同一合约内实现多个不同的instruction calls——如Token Program会实现InitializeAccount、IntializeMint、Transfer
等多个接口,每个接口具有不同的参数。因此,通常instruction_data的第一个字节用来标识所调用的instruction(最多支持256个不同的接口),后面的字节为对相应参数的编码。
Solana合约的粗略流程为:
Rust语言并为提供相应的工具来对以上某些流程进行streamline处理。而Anchor可以。
Anchor为用Rust语言编写的Solana合约开发框架——填补了raw Rust的gaps,并提供了安全性,使得Solana合约开发更人体工学、更安全。
在Move中,合约发布为modules(模块)。modules中包含:
具有的主要优势为:
Sui/Move变种与Aptos或Diem等变种不同之处在于,其引入了objects的概念。
Objects为runtime和跨交易persist state 所存储的struct instances。
Sui有3种不同类型的objects:
整个Sui/Move编程模型非常直观和简单。每个合约都是一个模块,包含了函数和结构体定义。
结构体:
为实现跨交易的结构体留存,需将结构体转换为object(可为owned、shared或immutable类型)。
Sui/Move中:
那么问题来了,如何保证其安全性呢?当某人发布了一个恶意模块,获取了某shared object(类似某AMM pool)并将该object发送到恶意模块中试图drain of its funds,如何解决这种情况呢?
在Solana中,引入了account ownership的概念,仅拥有该account的program才可修改该account。但在Move中,不存在模块粒度的owned objects,可将objects(不只包含object reference,还包含整个object本身的值)发送给任意模块。因此,runtime无法做特定的检查来确保该object不会被不信任的模块非法修改。如何来保证object的安全性呢?
传统的Rust结构体定义为:
struct Foo {
x: u64,
y: bool
}
Move中的structs有如下约束:
即意味着,在其它模块的某函数内,对该struct实例:
这些限制会失去一些灵活性,但智能合约开发的本质是对数字资产(resources)进行编程。
为此,Move中的结构体定义可附加相应的capabilities:key、store、copy、drop,类似为:
struct Foo has key, store, copy, drop {
id: UID,
x: u64,
y: bool
}
也就是说,Move中的struct默认为resource,capabilities给了相应的权限来放松以上限制,使得其功能更像传统的结构体。
Move合约以模块形式发布,任何人都可创建并上传任意模块到链上供任何人执行。如何保证模块遵循相应的规则呢?
Move verifier为静态分享工具,可分析Move bytecode,并确定其是否遵循了相应的类型、内存、resoucre安全准则。所有上传到链上的代码均需经该verifier验证通过。
在将某Move模块上传到链上时,节点和validators会先运行verifier,只有验证通过后才会将该模块commit。若该模块未遵循Move安全准则,则会被verifier拒绝,不会上传到链上。
Move bytecode 和 verifier 是Move的核心创新。正是它使以resource为中心的直观编程模型成为可能,否则这是不可能的。关键的是,它允许结构化类型跨信任边界传递,而不会丢失其完整性。
Solana中的智能合约为programs,Move中的智能合约为modules。
二者的主要区别在于:
如何让Solana支持Move呢?
可能的实现方式有3种:
1)添加Move MV,将其与SBF VM一起,作为native loader:在runtime中额外增加Move loader。Move合约存储为链上Move bytecode并由Move VM执行(与Sui类似),但这意味着Solana中同时又SBF合约生态和Move合约生态。但:
详细讨论见:
2)以program来运行Move VM(类似Neon):以Solana合约来运行Move VM。类似Neon以Solana合约来运行EVM。
3)将Move编译为SBF(类似Solang):需为Move构建一个LLVM frontend。这样runtime就无需感知Move语言。
Move的最大优势在于其bytecode verification:
The distinguishing feature of Move is an executable bytecode representation with resource safety guarantees for all programs. This is crucially important given the open deployment model for contracts — recall that any contract must tolerate arbitrary interactions with untrusted code. Source-level linearity has limited value if it can be violated by untrusted code at the executable level (e.g., untrusted code that duplicates a source-level linear type).
[1] Klas 2022年9月博客 Smart Contract Development — Move vs. Rust
[2] 2022年9月 twitter Smart Contract Development — Move vs. Rust
[3] Klas 2022年7月博客 Solana for Non Smart Contract Developers