Skip to main content

合约语言

背景信息#

与合约模型的概念相对应的,我们可以衍生出合约语言(框架)的概念(编写能运行于平台上的代码)。在这里不简单的使用合约语言,而是使用合约语言(框架)这种描述,是由于在实际使用中,一些合约平台设计了一门新语言来编写合约(例如Solidity),而一些合约平台在现有语言的基础上(例如Rust、C++、AssemblyScript)编写一个库或者框架来让一门已存在的语言具备编写合约的功能,甚至有的平台将一门现有的语言改成这门语言的“方言”以支持编写合约功能。

运行平台类别
Solidity编译结果运行于EVM上一门新的语言
ink!编译结果运行于Frame Contract Pallet上在已有的语言(Rust)上编写一个库或者框架
Vyper编译结果运行于EVM上Python 的方言

Vyper是一种面向合约的Python化的编程语言,编译目标为运行以太坊虚拟机 (EVM)上的字节码。

对于在现有语言上增加一个库或者框架的场景,多数情况下需要使用到编译器处理语法树的功能,因此我们更倾向于称其为语言框架,而不是一个常规意义下的库或者框架。

合约语言与合约语言框架是针对不同合约平台的描述,为方便后文介绍,在描述概念性质上的部分时,统一使用合约语言代替合约语言与合约语言框架两个概念,在描述具体对象时会使用相应的概念。

合约语言与合约模型的对应关系#

现有模型的抽象表述#

如上图所示,上半部分描述了EVM与Solidity之间的关系。由于EVM和Solidity提出的时间较早,且其模型与通常的计算机虚拟机与语言的模型关系是一致的。而下半部分是在分离了合约模型后,用更抽象的维度表述合约平台与语言部分的对应关系。

在编译型语言的体系中,为方便后文介绍,先简单定义以下名词(非严谨说法):

  • S语言:程序员编写代码的原语言(Source),例如将C++编译成汇编,那么C++就是原语言S。
  • T语言:原语言通过编译器编译后产生的语言(Target),例如将C++编译成汇编,那么汇编就是目标语言T。

前文介绍了运行合约体系的环境可以在逻辑上拆分为合约模型和合约虚拟机。合约模型管理合约的业务逻辑,合约虚拟机管理如何运行合约的代码,那么相对应的,合约语言功能部分同样可以拆分为合约语言框架和编译到合约虚拟机的S语言。

根据这种定义,显然Solidity是一种同时具备合约语言功能和S语言特性的合约语言。

  1. Solidity具备图灵完备的语言体系,因此Solidity是一门语言(相对于BTC脚本而言)。

  2. Solidity的语法中具备很多合约特性的关键字,对应于合约语言的概念。例如:

    关键字说明
    mapping合约存储的典型案例
    msg.sender、msg.value与合约调用相关的变量
    view、pure修饰符,起修饰作用
    call、delegate_call与合约调用相关关键字

其中第2点中的概念并不是为了支持Solidity语言能执行逻辑而存在的,而是为了服务于以太坊合约虚拟机的业务逻辑而存在的。因此这部分可以归结为合约语言特有的功能。

合约语言实现的目的是为了与合约模型的业务逻辑相对应,什么样的合约模型就需要什么样的合约语言。

例如:

  • EVM底层是Key-Value类型的存储,因此Solidity中设计的mapping不能遍历,除非附带多余存储。
  • EVM合约交互的合约模型设计为合约调用合约的模式,那么Solidity中就提供了calldelegate_call关键字。

对应到其他合约体系也同理。

合约语言框架和合约语言的优劣势对比#

劣势#

对于合约语言框架而言,其是架设到S语言上的附加功能,因此合约语言框架提供的功能最终也是编译到S语言对应的T语言上。而区块链所需要的一些特性会在这个层面上做约束,例如执行一致性的要求和不允许使用操作系统调用等。因此合约语言框架除了本身对S语言提供了合约模型的功能以外,还需要给语言本身产生一定的约束,这一步也是开发和理解合约语言中比较困难的地方。同时也正是因为这个原因,合约开发者在使用合约模型框架的过程中会有诸多不适。

合约语言框架最后能做到的成果与这门语言提供的拓展语法树的能力相关。如果语言本身提供了越灵活修改或添加语法树的接口(宏、插件等),那么合约语言就可以实现越多的功能。如果语言本身提供这类可扩展性的功能较少,那么合约语言只能考虑修改编译器和扩展需要的语法来支持合约模型,这样最后的语言就变成了原语言的一种方言了。

而对于合约语言而言,类似Solidity、Move等语言是针对合约平台开发的新语言,虽然在语法上有模拟其他语言的痕迹,但是在做约束的方面,其本来就没有会产生歧义的指令,在对合约功能的支持性上,也可以直接设计对应的关键字,因此对合约开发者也更友好。例如Solidity本身定位是服务于写合约而设计出来的语言,因此上章节中提到的与合约相关的指令均可以设计为关键字,您可直接使用。

所以合约语言框架和合约语言相比,在提供对合约的支持与运行合约的约束方面很难处理的很好,这也是造成合约开发者较难使用框架功能的原因之一。

以ink!为例:

  • 在区块链中应该避免使用float,因为浮点数可能产生不确定性行为。因此在合约runtime开发中,如果需要使用浮点数,或者出现溢出数字乘除的时,需要引入定点数来处理。因此在ink!的合约中可以引入Substrate runtime提供的定点数的库来处理。

  • 由于pallet-contracts的合约模型与EVM基本相同,因此pallet-contracts的合约存储也是由Key-Value模式构成。那么合约模型框架就需要处理标准库里提供的各类集合类型。因此在ink!中将标准库中可能用到的集合类型重写了一遍,添加了能将集合元素类型处理成Key-Value模式数据的过程。因此在ink!的合约存储中,如果设计了集合类型,那么只能使用ink!标准库中提供的类型。而另一方面由于ink!的返回值需要导出metadata令第三方处理,而当前的metadata的接口实现只给标准库中的集合实现,因此ink!方法的返回值的集合只能使用标准库的集合类型。代码示例如下。

    #[ink::contract]mod test {    // 引入 ink 实现的 Vec    use ink_storage::collections::Vec as StorageVec;    // 引入标准库的Vec    use ink_prelude::vec::Vec;    #[ink(storage)]    pub struct Test {        owners: StorageVec<AccountId>, // 只能使用 ink的Vec    }    impl Test {        #[ink(message)]        pub fn get_owners(&self) -> Vec<AccountId> {            // 将 ink 实现的 Vec 转换为 标准库实现的 Vec            self.owners.iter().map(Clone::clone).collect()        }    }}

优势#

虽然使用合约语言框架会有诸多限制,但是在已有的语言上设计框架和设计一门新语言相比,还是有很多优势。

合约语言框架合约语言
对开发者吸引力比较容易吸引这门语言生态中的开发者。说服开发者使用新语言是十分困难的。
语言抽象度语言设计经过工程检验,抽象维度高,语言表达力强。语言设计比较简陋,在语言表达力的抽象上比较弱,难以编写复杂的业务逻辑。
生态繁荣度很多情况下可以复用该语言生态中已有的基础库及工具库。几乎要重写所有的基础组件。
工程代价设计一个库比较简单。设计一门完备的语言十分复杂。

综上所述,作为提供合约平台设计者,选择使用合约语言框架,是在一门现有的语言上“做减法”。选择使用合约语言,是在从零开始“做加法”。两种选择各具优劣。设计者会根据自己的场景做出选择,以最大程度吸引开发者,利于自己生态的发展。

总结#

综上所述,在模型结构上:

  • 合约语言与合约模型是对应关系,合约语言的特性会与合约模型一 一匹配。
  • 合约语言为S语言提供了针对合约模型的业务功能,同时也针对合约业务逻辑的需求对S语言本身有约束。
  • 根据合约平台设计者的需求,最终会选择使用合约语言框架或设计一门合约语言来供开发者编写合约。两种方式各有优劣。

pallet-contracts与对应的合约语言#

有了合约语言的模型的概念后,我们可以把合约模型框架嵌套在Substrate的Wasm合约系统上了。

ink!是一个合约语言框架,其整套系统的实现,是与pallet-contracts的合约模型相对应的。ink! 3.0通过过程宏(2.0通过声明宏)的系统,将对应于pallet-contracts的功能逻辑引入到了Rust中。因此这套系统里的S语言就是Rust,而T语言就是Wasm字节码。ink!借助于辅助工具cargo-contract,把使用了ink!框架的Rust代码编译成为了合约的Wasm字节码。而Wasm字节码在链上运行的环境就是wasmi,将来也会引入wasmer等JIT形式的运行环境。

而因为pallet-contracts的执行环境是Wasm字节码,所以能够编译成Wasm字节码的的语言配套上符合pallet-contracts合约模型的合约语言框架,都可以产生能运行于pallet-contracts这个合约平台上的合约。对pallet-contracts而言,完全可以设计出不同语言框架的合约体系供您选择,您可以使用不同的语言开发Wasm合约。

更多信息#

当前支持运行于pallet-contracts的合约语言除了对于Rust的ink!之外,还有以下项目:

  • Ask!: 由Patract主导开发,在AssemblyScript语言上的合约语言(正在开发中)。
  • Solang:由hyperledger-labs主导开发,支持将Solidity编译到pallet-contracts的Wasm的工具。