Jolestar | 智能合约编程语⾔,还可能有哪些创新点?
9月4日的 BeWater DevCon 2021 全球开发者大会上,Westar 首席架构师,Starcoin 核心开发者 Jolestar(王渊命) ,就智能合约编程语言,还可能有哪些创新点和大家展开了讨论。
MOVE 智能合约编程语言,存在3个创新点:
BeWater
大家好,我是王渊命,今天跟大家分享一些工程师比较关心的话题。
在介绍话题之前我先自我介绍一下,我是一个老程序员了,各种语言也用过,所以我今天分享的角度是从一个工程师的角度。我们工程师视角和语言学家视角不一样的,我们是侠客,要找一把趁手的武器去行走江湖。
我最早是在互联网行业,2017 年进入区块链,当时我们组织了一个叫 BFTF 的技术社区定期组织一些 meetup 来学习区块链相关的技术,后来我们开始成立 Westar 实验室,做一个新的公链项目 Starcoin,BFTF 社区就没有顾上了。一方面是忙,另外一方面是自己已经是项目方的角色,很难组织中立的社区,所以我很高兴看到 BeWater 这样中立的技术社区出现,让我们大家纯粹的在一个平台上互相探讨。
Starcoin 在今年 5 月 18 号上线,是 POW 的链。回应下上午嘉宾提到的几个点——一个是 POW,一个是链上治理。
关于 POW:
区块链未来怎么发展各链有不同的假设,我们的假设是:一层不解决吞吐的问题,二层解决吞吐的问题,所以一层使用了中本聪共识的改进版,更关键是我们要思考「状态怎么去处理」这个难题,这个与一二层机制的设计是非常相关的。
关于链上治理:
我们引入链上治理机制来实现链和系统合约的治理升级,并且 DAO 本身的合约也可以升级,DAO 是可以自举的。从主网上线以来已经经历了五次治理升级。
还有一点是我们引入了 Move 语言,我们用 Move 语言最关键的是通过它,解决了链上状态拆分的关键痛点。
关于我自己的主张,我自己总结叫做 BlockChain Maximalism,这个是从 Bitcoin Maximalism 演化而来。Bitcoin Maximalism, 比特币最大主义者,或者通常翻译叫「比特神教」。对比特神教的人问一个关于未来的问题,他会给一个比特币的答案,而 BlockChain Maximalism 往往会给一个区块链的答案。
早上其它嘉宾也说了,我也认同未来会是个是个多链的世界。我们已经看到区块链未来的蓝图,但是现在我们还没找到具体的路,每个方向都有人在探索,我们选了一个方向,但是别人选另外一个方向。我并不会说你选的不对,因为有人尝试了一个你没有功夫去尝试的方向,你应该是鼓励他的,他帮你去探索另外一条方向,总体上增加了未来蓝图实现的可能。
BeWater
一、编程语言的演化
1、传统编程语言的演化
在探讨智能合约编程语言有没有新的创新点之前,我们先要回归到传统的编程语言。
我们可以看一下,我们常用的这些编程语言的进化发展,总是在这两个点上在权衡,要么去照顾一下开发效率,要么去照顾一下执行效率。
C++ 执行效率好,开发效率差;后来有了其他语言来做运行时垃圾回收,动态执行等提高开发效率的事情;最后 Rust 又出现了,更偏向于执行效率,但它实现了编译期的垃圾回收,不需要手动释放内存,也兼顾了开发效率。
程序员的视角来看,所有编程语言都是图灵等价的。一个电商网站用什么语言写,最终用户会有感觉吗?对业务能力表达也没多大影响。所以,我们经常会有一种观念,就是说在图灵机器的角度,编程语言不重要,这是我们经常说的观点。
2、智能合约带来的改变
但是我们如果回归到智能合约,我们发现不一样了:
1)我们应用程序的状态被链托管了。应用程序不用关心程序的状态,语言里和处理状态的特性,无论是文件系统还是网络,在智能合约编程语言里都被干掉了。智能合约只关心业务逻辑,不关心状态怎么存储。
2)语言本身的特性也会有很多的变化。最简单的,在智能合约编程语言里,对最终用户有感知了。智能合约里面的账号地址是一个类型。以前的语言里,我们在代码里定义了某一种类型来标志一个用户,但是用户的状态和权限,并没有在编程语言层去约束。
3)还有一点是服务之间的依赖关系不一样了。在以前,依赖别的程序,要么依赖它的一个算法库,要么通过远程调用依赖它的一个服务,但现在这两个合二为一了。这个到后面我们说合约间的调用的时候可能更明白一些。
智能合约编程语言的几个关键问题
现在我有两个大家经常讨论的反共识的点:
1)有没有必要发明新的语言?
这也是所有从互联网圈子里过来的朋友问的第一个问题,为什么你们不能用已有的我熟悉的语言去写智能合约,用Java写不好吗,不香吗,或者PHP更简单一些。
这个我们前面已经说了,实际上如果把传统编程语言里的很多特性裁掉,智能合约中并发不存在了,状态处理不存在了,线程,文件系统,网络等和操作系统相关的库都干掉之后,你发现传统的编程语言库里也没有多少能用的。所以如果你用传统编程语言也是裁减过的语言,传统编程语言在这里没多少优势。
2)Solidity 和 EVM 是不是智能合约的终点?
早上也有老师分享了这个观点,认为 Solidity 已经是个事实的标准了,跟当初的 javascript 一样,新的链要么兼容 EVM,要么就是在竞争中处于劣势,这是大家认为的一种观点。但是这点我是不同意的。我认为 Solidity 和 EVM 是智能合约的起点,智能合约语言的演化才刚刚开始。
从上面的分析可以看出,智能合约其实让编程语言的特性发挥了更大的作用。我们通过下面这个例子再说明一下。
简单的看一下,在传统的系统中,不同的机构之间通过 RPC 调用串在一起。它有各种墙,网络的墙,还有人工的墙(比如必须人工介入的流程)。我们的程序在后面,被各种墙保护着。
但是区块链系统里不一样了,所有智能合约在一个进程内,相当于银行、交易市场这些机构的程序在同一个进程里互相之间调用,相当于把远程调用变成了系统内部的一种调用方式,墙被拆的只剩很薄的一层,安全机制就依赖于编程语言的安全。
传统编程语言里,比如说有一个方法,设定成 public 或者 private,对程序影响有多大?它主要影响的是程序的优雅性,你把不该暴露的东西暴露给别人了,不优雅,可能导致升级时候的 break。
但对你的系统安全性有影响吗?影响不大,因为你的安全不是靠程序的边界。但在智能合约里不一样了,private 设成 public 那可能你的钱就没了,这个就是智能合约让编程语言的特性发挥出最大的作用。
BeWater
二、智能合约编程语⾔的创新点
从这个角度我们来看智能合约还有哪些创新点?
1、状态存储
现在链已经托管了状态,但是链怎么样托管状态,这个事情我们是可以去创新的。
我们简单的以EVM Solidity为例,下面这个是Token的例子。
代码文档,可在“阅读原文”中复制
这个合约里声明一些属性,直接进行读写。整个合约里看不到处理状态相关的代码,比如读写文件,操作数据库啥的。第一次从互联网应用切过来的程序员会觉得很惊讶。这个在我们以前写程序做梦都想这样的,我不需要调数据库去写去读,链把这个属性自动映射到存储里去托管掉了,对程序完全透明。
但这种处理方式带来的问题是什么呢?
一个是它这样映射到存储里之后,它的存储里存的是什么东西是很难推断的。如果直接读以太坊的外部存储,想知道里面存的什么东西其实是非常难的,你必须通过合约读一遍。
另外一个问题是它的状态全部在合约里了,每个用户的数据都存在合约内部,比如上面中的余额,并没有把用户的状态拆分出来。这样很难进行状态计费,也很难解决状态爆炸的问题。
我们再看看 Move 的处理方式。
代码文档,可在“阅读原文”中复制
Move 并没有完全透明地映射状态和存储,而是提供了一些新的操作原语:move_to、move_from。开发者要通过智能合约明确告诉链,某个对象要存在哪个账号下。
每个用户的状态相当于一个通过类型做 Key 的 Map。外部存储可以知道这个 Key 下存的二进制数据是什么类型,可以直接通过类型解析出来,用起来非常容易。最关键的是这个状态是分散到用户空间的,不需要集中地存在整个合约的状态下。
2、类型系统
另外一个创新点,就是类型系统。类型系统到底能用来干什么?编程语言的类型系统其实理论已经非常早了,我们有好多种类型系统,线性(Linear)类型、仿射(Affine)类型,这两种是大家常谈的。
如 Rust 使用了仿射类型,它要求每个变量最多使用一次。这个规则有什么用呢?这个规则用来映射 Rust 里的 move 语义—— 这个变量我如果给你了我就不能再用了。这样解决垃圾回收的问题——我可以在编译期追踪这个对象的生命周期,自动的把垃圾回收掉,不需要动态的用一个垃圾回收机制做这个事情。但是这样的东西在传统编程语言里顶多就发挥这样一个价值,就是帮你做垃圾回收。
其实有非常多类型系统的玩法,但这些玩法在传统编程语言里找不到价值。在智能合约语言里能不能找到价值呢?
Move 引入了一套 ability 的机制,它在类型系统里加了几种约束,类型可不可以被 copy,可不可以被drop,可不可以被 store,可不可以做一个全局存储的 key,四种 ability。
不可以被 drop 的类型,在传统编程语言里找不到场景。你 New 出来个对象,但不允许直接丢弃,要表达什么东西呢?程序重启的时候内存的东西不就都丢了?
在智能合约语言里,我们可以用它来表达资产。给一个类型,代表 Token,或者 NFT,它不能被 drop,必须存下,或者显示的销毁。
这是一个 NFT 的例子,这在 Move 里是一个 Struct 结构。
然后它有两个泛型参数来提供扩展能力。一种是表达它的元数据,元数据是可以 copy、store、drop 的,但还有一种是 Body,只能 store,不能被 drop。
这用来存什么呢?在我看来 NFT 是一个标准化的箱子,我们在箱子里要放东西进去,放什么东西由用户自己来定义,比如我放 Token,放一个宝石,这个东西是不能被丢弃的,这样的话就可以把各种资源统一封装成 NFT 展示出来。
我们提供了一个 NFTGallery 的模块去存 NFT。在以太坊里转让 NFT,只能提供 transfer 的方法,把 NFT 从一个账号转到另一个账号,指定 NFT 的 ID。但在以太坊里没法提供 withdraw,就是把这个 NFT 作为一个对象拿出来,从用户的 NFTGallary 里拿出来。拿出来能干什么呢?可以把NFT封装在另外一个结构体里存下来,这样 NFT 之上的扩展能力和表达能力变得非常强。
为什么能做到这点呢?就是刚才说的,NFT 这个类型可以 store,但不能被 drop。编程语言和虚拟机可以保证这个 NFT 无论存到系统的哪里,都不会凭空消失掉。当然还有可能有一种,不能 store 也不能drop 的类型,这种类型用来做什么呢?这个大家可以思考下,后面讲闪电贷的部分会讲到。
来源:https://starcoin.org/zh/developer/sips/sip-22/
代码文档,可在“阅读原文”中复制
3、合约之间的依赖与调用
另外一个例子是合约间的依赖与调用。
来源:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/9b3710465583284b8c4c5d2245749246bb2e0094/contracts/token/ERC20/IERC20.sol
我们看以太坊的例子,以太坊一般设计个 Interface,通过 Interface 对另外一个合约 address 进行调用。
它是怎么转换的?我的代码是在当前合约里的,合约之间的调用是内部的交易,你可以理解成另外启了一个虚拟机,然后去执行另外一个合约的方法,然后再通过序列化和反序列化将结果传递回来。这个和传统的 RPC 封装实际上非常类似。为什么这样做呢?主要是因为安全隔离的问题,因为不同的合约实际上代表了不同的组织与机构。
这样的调用方式其实有几个难题:
比如,身份的传递。用户调用了一个合约,然后合约内又调用了另外一个合约,那后面这个合约拿到的 sender 是谁呢?能不能拿到最终用户?所以它提供了两种方式,直接 call,sender 变成合约地址。
另外一个是 delegatecall 调用,这种调用会把原始的用户信息传过去。但传过去之后其实就牵扯另外一个难题,后面这个合约是恶意合约怎么办?
它现在的 delegated call 实现会把被调用合约的代码拿过来在调用方的上下文里执行,它修改的状态是当前合约的状态,并不是被调用合约的状态。这个很违反直觉。这是因为 Solidity 没有提供静态调用方式,所以通过这种方式来模拟。以太坊上的很多安全的漏洞其实都来源于比较复杂的合约间的调用方式。
如果在 Move 里合约间是怎么调用呢?一方面 Move 里的方法其实都相当于静态方法,所以合约之间的依赖比较简单。另外一方面,因为 Move 的类型可以在合约间共享,可以通过类型共享来消除对动态调用的依赖。例如我刚才举的 Token 的例子:
我在银行里存了钱,然后我需要去商场里买东西,我有两种办法付款,一种办法是让银行授权给商场,它可以从我的账号上扣钱,转到商场在银行的账号上。银行需要和商场协商调用的协议,这是一种方式,以太坊是这种方式。
另一种方式是现金的方式,我去银行把现金提出来,到商场直接支付现金,商场可以存银行,也可以存自己的保险箱里。这样银行和商场之间两个之间就不需协商交互协议,也不需要动态调用,只要都能识别"现金"这种类型就行。
这样合约之间的依赖关系全部是静态的方式,避免了上述的动态调用的安全问题。动态调用如果有多个层次的话,其实很难搞清楚它会不会产生重入,或者复杂的安全问题,甚至循环依赖。
来源:
https://github.com/starcoinorg/starcoin/blob/master/vm/stdlib/modules/Treasury.move
代码文档,可在“阅读原文”中复制
闪电贷的例子
看一看闪电贷的例子。闪电贷是我给人讲 DeFi 的时候喜欢举的例子。你给互联网的或者其他外部朋友讲 DeFi 的时候很难讲清楚有什么根本的不一样。
你说什么东西他说我们 CeFi 也有,最后你给他讲一个闪电贷他就举不出来了,不可能在传统金融里找到一个闪电贷的例子。
闪电贷可以做到无抵押给任意人借无限的资金(上限是资金池),只要能在同一个交易中把资金还回来。
它充分利用了以太坊的动态调用特性。闪电贷提供一个入口方法,用户需要在交易中调用该方法,并且通过参数传递自己实现的回调合约(Borrower)。用户回调合约中实现对贷款的支配逻辑(套利,清算等),以及还款逻辑。闪电贷合约先给用户转账,再调用用户的回调合约,然后检查自己的余额,用户是否还回来,如果没换回来就报错,导致交易失败,整个交易的状态回滚。
例子来源:
https://github.com/alcueca/ERC3156/tree/main/contracts
代码文档,可在“阅读原文”中复制
那 Move 中不支持动态调用,如何实现这种特性呢?Move 可以利用前面提到的 ability 特性, 声明一个 FlashLoan 类型,这个类型没有任何 ability,也就是既不能 store,也不能 copy,还不能 drop,它内部有一个字段保存 Token。
FlashLoan 相当于一个箱子给里面装了 Token,然后我直接给你。你可以把箱子里面的钱拿出去花,但是这个这个箱子你不能 store,也不能 drop,你必须给我还回来,由我去销毁掉。而我在销毁的时候要检查箱子里有没有把原始的 Token 给我还回来,如果数不对就报错。这样,就不需代理也实现了闪电贷贷逻辑,还更灵活方便。
BeWater
三、Move 语言的创新点
我们总结一下,Move 带来的一些创新的点:
1)它有一套状态操作协议,把状态所有权明确了;2)通过类型机制,实现了类型系统在不同的合约之间共享;3)因为它的这种方式让合约之间的依赖关系不一样了。
可以通过静态的方式依赖远程的代码,可以实现更复杂的项目,更工程化。很难遇到合约太大了,没法部署上去的问题。
我们从三个角度看编程语言的创新,合约的状态机制,编程语言的特性,合约之间的依赖与调用。那有没有其他的创新点?
编程语言本身特性上的扩充比较容易想到。比如说可见性。Move 后来加了一种的可见性,叫 friend,类似于 java 里的可见性 protect,module 之间可以用 friend 声明方法的可见性,方便拆解 moudle ,构建更复杂的项目。
我随便再开几个脑洞,我最近也在琢磨的。互联网时代一直想要的对象数据库这种东西,实际上没有用起来,关键是整个系统没有把应用的状态给接管了。但是在区块链时代有没有可能呢?能不能通过对象数据库的方式来处理区块链的状态?对象数据库跟区块链解决状态的方式是非常像的。
再比如说编程语言的继承,结构体/类的继承,如果放在智能合约里面表达出来什么呢?最近大家玩的 NFT 会发现 NFT 有各种演化的玩法。比如有一个原始NFT,后面衍生出不同的附加的特性,就很像是一种继承的机制等玩法。
再比如能不能在程序中声明所有权?比如说定义一个Map,能不能规定里 Map 里的某个 Key 只能由某个账号去操作?在 Rust 里所说的所有权是说程序方法对变量的所有权,而不是说用户对某个数据的所有权。那能否把这套所有权的概念,跟外部的用户影射起来在程序程序中表达和追踪数据状态的所有权?
最后总结一下,我觉得现在区块链世界已经从 hodl 时代进入了 buidl 时代了。现在每天都有新的东西冒出来,有五花八门各种的想法值得我们去去构建去尝试。而新的编程语言的出现和爆发标志着开发者新的黄金时代的开启(前一次是早期互联网时代)。
我的分享到这里,谢谢大家。
问答环节
提问1:王老师你觉得在不改变原有语言基础上,加一些注释,向 EVM 添加属性,你觉得是一个好的实现方法吗?
王渊命:我最早的时候一直在想这个路线,如果不需要动 VM 的情况下,改编程语言是相对简单的。
这里面第一个问题是它的状态,必须有办法把状态拆分出来。链跟状态之间这套的处理机制现在没有标准,传统编程语言里使用了操作系统标准,但是在链上还没有一套统一的标准,状态机制不一样,靠语法糖是弥补不了的。
第二个是合约间的调用。如果想废弃合约间的动态调用,实现合约之间静态调用并且共享类型,要考虑安全性,其实非常难。需要在编译期和运行时同时保证。所以从这个角度看,在以太坊上做这种尝试成本太高了,万一试验失败了对以太坊影响也太大了,不如在一个新的链上去尝试新的东西。
提问2:为什么要考虑用一门新的语言,因为我感觉到 Move 语言各种改进,其实更多的像是对于链自身数据结构的修改,我理解类似于内核态的修改而已,不同内核态上面语言层的兼容可以提供API的模式,就像您刚才也说的,比方说 msg.sender,它是可以感知到这些的。我们可以针对这些做一些修改,类似于提供新的API的形式给Solidity。所以我们为什么必须用Move 呢?
王渊命:可以这样改,例如把 sender 去扩展一些方法, sender.move_to, sender.move_from,让开发者把状态通过 sender.move_to 保存,不要定义成合约的属性,逻辑上讲是可以的。但是这样完全破坏了 Solidity 本身的状态处理机制了,相当于把它原来的状态处理机制都废弃了。
第二,这种方式只解决了状态分散在不同用户下的情况。但如果考虑到安全,其实这是个挺复杂的事情。比如如何控制 move_to/move_from 的权限?我能不能在一个合约中通过 move_from 去读另外一个合约中的类型?如果那样可以安全就有问题了。
所以这套机制从安全性上来讲,它要从编程语言源码层,到最后的 VM 的运行时一起来保障,不然的话其实很难保障安全性。
————————————————
历史文章:
- 闭门会报名 -
第六期主题:有关 Loot、元宇宙、Gamefi
时间:2021.9.10(周五) 晚19:30
地点:北京 - 朝阳望京
人数:6~8 人
面向人群:开发者、行业研究者