2021年11月25日,在第9期 BeWater Live 上,Dapper Labs中国技术负责人唐博皞分享了 《面向资源编程:Flow Cadence智能合约语言的全新范式》
这场深度且系统的分享,核心内容:
1、Flow概览
2、中央账本与Solidity智能合约面临的风险和挑战
3、Cadence面向资源的编程范式基础介绍
- 来自Rust和Move的启发
- 资源数据
- 存储形式
- 访问模型
- 资源数据转移
4、 Cadence相关工具链导览:
- 开发者工具
- 学习路径
BeWater
一、Flow概览
我们先来介绍一下关于flow和我们 Dapper Labs, Dapper Labs 的前世今生大家其实已经非常熟悉非常了解。
现在整个市面上最主流的nft标准,在以太坊包括其他任何二层或者这样的以太坊链上都非常熟悉 erc721 这样一个NFT标准, Dapper Labs就是 erc721的编写者,Flow的 CTO 在17年的时候制定了 erc721 的标准,并在当年制作了加密猫,应该说是第一款把以太网打爆的这样一个 NFT 项目。
加密猫之后,我们其实沉寂了接几乎两年的时间,我们在思考的过程中发现了以太坊上种种问题,然后觉得自身的一些对 NFT 未来的畅想无法在以太坊这样一个网络上能完成,所以花了两年的时间进行了非常深邃的研究和开发之后,做出了 Flow。
在2020年的时候上线了Flow链,同期在年末的时候也在此基础上制作了NBA TOP shot。我们完成了在Flow链上的,包括最第一次的开发的功能性的完善,同时也将其产品化,也让 NFT 更早的进入了大众的视野之中。
Flow 在半年的时间已经达成了非常多的合作。Flow应该是做的是整个 web3 世界中在 web2 世界拓展里边做的最好的,我们现在希望以把 web2 世界引入 web3 世界为我们的宗旨来推广我们的品牌。
Flow在其他的一些产品上也有它非常有特色的东西。
一个比较重要的尝试是我们的Dapper Wallet,它是以信用卡支付的方式来让 web2 世界的用户进行NFT的消费和使用。在这个过程中我们隐藏了全部的加密的技术。当一个消费者玩NBA TS的时候,他根本接触不到加密或者说区块链相关信息,你就把它当成一个投资品或者说是一个消费品使用就可以了,这也是让消费者以无门槛的形式进入到区块链世界的一个例子。
然后第二个很大的特色,是我们的Flow本身的区块链,它应该说是目前世界上所有区块链中唯一一个以多功能节点架构设计的区块链模式。它让可扩展性这件事情变得非常容易。最核心的设计理念是将共识算法和计算节点两个功能完全分离。
我们一共有4大类的功能性节点:分别为 collection节点,它是负责收集节点信息的;然后共识节点完成一块区块的打包;计算节点完成所有交易的计算;验证节点完成对所有交易结果的验证。最后将这两块信息封装在一个块中送去,这件事情是以一种流水线的形式完成的。因为让每个不同的节点类型,负责了自己独特的一套功能,所以他非常容易的进行针对性的扩展。目前大多数的区块链,它基本上都是某功能涵盖了所有的内容,因此就只能往上堆资源,或者说多牺牲某些特性,来提高它的性能。Flow因为它的分离共识和计算节点这样一套多节点的架构,使这件事情可扩展性变得非常容易。
二、中央账本与Solidity智能合约面临的风险和挑战
今天其实更多的想讲的是后面大部分内容想讲的是我们在设计为什么 Flow 的时候为什么没有选用EVM。因为我们发现了很多中央账本的问题,
首先第一个问题在哪里?
我们先举一个例子看看什么是以账本为中心的实现,它存储的内容始终是在中央账本中的。这里是一个很简单的Token合约的实例,他记录了一个账户里边每个账户兑了多少钱。他是一个map的形式,将一个balance给记录下来,同时有一个mint的方法,给这个地址 A receiver 添加了多少钱,这就是一个中央账目的形式。转账的时候, 我们就将 A 用户的 msg.sender 就是我交易发送方的金额,在我的中央账本中减去,同时receiver在接收方的中央在账本里面加上这个钱。
这时候我们再看一下 Solidity 的陷阱在哪,我们在所有的过程中其实没有强加任何数值上的约束。如果一个黑客用一个代码,将后面的balance给注释掉,接收方就永远接收不到钱了,然后这里金额在这个合约里边其实就永远消失掉了。从代码层从语言层上面我们无法阻止这件事情。所以我们其实经常听到谁又黑了多少钱。
因为在整个 Solidity 设计机制中,只要以中央账本为设计模型那就不可避免的会因为一些黑客的原因或者是代码输入的原因,将你这边的金额扣掉。编译器不会对这类的语法做任何的错误检查,也不会有任何的机制来阻止这些情况的发生。
还会碰到很多类似的潜在的问题,一方面从心理上这对智能合约的开发人员也有非常大的责任和压力。另一方面还有很多底层代码需要处理(尽管 OpenZeppelin 等工具库解决了一部分)。比如管理数值上下溢出的问题,管理所有权问题,管理多签问题。这些都是我们在做 EVM 一些生态的时候会碰到的问题。
三、Cadence面向资源的编程范式基础介绍
1、来自Rust和Move的启发
18年的时候 Libra 把一个新的概念引入进来:resource概念。它更加的专注于 FT(fungible token)的管理,将资金的转移做得非常细化。之后19年 substrate 在 currency 的设计结构上也使用了 resource 的设计理念。
Flow 它在整个的设计理念的时候,把 resource 引用到了整个语言层面上的设计,完全基于 resource 来编程。
当然这部分大家如果熟悉 Rust 的话,可能也会比较熟悉,它其实是一种对所有权更加深入的使用。
2、Cadence
下面我们引出 Cadence ,一个面向资源 resource 的智能合约语言,它这边有一个很独特的箭头,是 resource 的一个 instance 所独有的赋值标记号,表示的是转移,这个转移是 resource 最核心的一个赋值的形式。
3、Cadence vs Solidity
资源数据
我们再看看资源类型的一个公共形式。我们这边的 withdraw 他在调用的时候,创建了一个全新的 vault 资源,而 vault 资源里具有的金额是我们传进来的金额,这里已经减掉了,但是我返回的结果必须是一个全新的 vault ,为什么?因为我们的方法强制指定了你必须返回一个资源,如果没有返回这样一个资源,代码编译不通过,这就是在代码层面上,我们其实就保护了我们 withdraw ,你不可以说我只减了钱,不给另外一个人加钱,虽然我现在还没加,但是我现在已经返回出来了一个固定的资源,固定资源一定是有这个 amount 的钱。至于你这里边的钱未来怎么用,外面会有其他代码来解决。
当我deposit的时候,就必须要传入一个有金额的这样一个资源,因为指定类型 balance 新的 from 资源里面,必须是有这样一个 balance,它是个强类型的东西。
这时候我的函数方法中没有返回任何东西,它意味着什么?意味着我们传进来这个资源必须被销毁,因为资源在代码层面上只有两种可选行为,一是你这个资源返回出去,到函数之外或者保存到用户的账户里;二是销毁掉。只要这样一个资源类型,它的定义为资源,就必定会有一个最终的归属。如果没有最终的归属,这个合约是无法通过。
我们在资源传入的临时资源销毁之后,整个资源的生命周期就已经结束了,这种设计模式中,一切对资源不正确的使用都无法将该合约正常进行,都会导致编译器报错。
存储形式
再回到刚才的我们非常简单的例子, Solidity 它在存储的形式上到底有什么不一样?我们将 Cadence 里面的 resource 依然是做一个 vault , vault 是来管理刚才的内容的,我们可以把 resource 想象是一种定义。
当我们创建 vault 之后,我们创建 vault 的实例不是保存在中央账本中的,而是将这个 vault 保存在了用户的存储的空间中,存储的空间它是不是在合约里面。合约里只保存了这个定义文件,真正的实例保存在用户的存储空间之中。
在 Solidity 中,我们所有的记账模型是以key-value map的形式保存用户的余额。但是在 Cadence 中我们刚才创建了资源出来之后存到的地方,不是我们的合约,而是 account 里边 storage 指定的一个地方,所以资源永久的存在了用户的账户之中,它是一种以人为本的账户模型,让你真正拥有了自数字资产的所有权,而不是将我数字资产的所有权放在一个别人账户的记账本里面的一行记录,相当于资产真正意义上的属于了你。
同时在中心化账本里面,我们非常难统计到一个特定用户拥有的所有资产信息。用过 Solidity 的人都知道,我要以一个账户扫描我这个账户里面所有的 ERC20 或者是 NFT 是件非常艰难的事情。首先要有一个常见的 ERC20 的合约列表,然通过一些形式对于 ERC20 的合约,对它的中心化账本里查询,我才能收集到一个特定用户的所有资产信息。但是 Cadence 不一样,Cadence 模型下我们的所有数字资产全都在他账户自己的空间里,所以对于开发者来说,我要查用户的某些数字资产,那就非常容易的能通过查询该账户,该账户下面所有的路径,所有的资源,然后一个列表直接查询出来。
访问模型
对资源的访问,solidity 需要创建另外一个字段来标记一下它怎么来使用,这是非常中心化账本的模式。
而 Cadence 我刚才也说到就是说存储的地方是它有一个 Storage ,然后在账户的空间下一共有三类的存储模型,分别为storage、public、private 这三类的存储路径,其实分别代表着对于资源的保存路径。storage 保存的是我们原始的资源信息。我们可以将这些资源的接口,一些只读的接口,软链接到一个叫 public 的空间中,这个空间里面其实我任意一个账户都是可以去访问到的,对于合约来说很多地方都使用了这块。
还有一部分就是我可能我希望我来控制你账户里面的一些钱,他就会把部分的能力以接口的形式做了一个软链接,保存到了 private 空间中。软链接就相当于是一个快捷方式,用这样的形式将我的部分能力交付给了另外一个人。这样通过更加直观的交付部分能力的形式,将我的资源的使用权限借出去。
资源数据的转移
为什么以太坊或者说其他这种智能合约的交易调一个方法就可以解决,因为他们是中央账本,他们的交易可能就是一个合约中释放出来的某个方法,这个方法里边传入某些参数执行的一些事情。
但 Cadence 里摒弃了中央账本的模式,所以我们在对交易的体现上,交易也改造成了一个富逻辑的代码片段,而不是 Solidity 对于一个函数的简单执行。同样的交易本身也是 Cadence 语言的一部分,所有的使用所有的代码编写,都需要对资源正确的使用,才能正常执行。
我们可以看到这个例子,这是一个非常典型的将某个人的钱转给另外一个人的实现方式。在这边 my vault 里边取出来的10块钱,到了一个临时的 vault 资源,临时资源在执行的过程中转移到了接收方的 deposit 方法。临时资源进去之后,充到receive的账户里面之后就被销毁了,在整个交易的过程中这个资源正常的被安排到了某个地方,这个交易才能正常执行。如果 deposit 最后返回了一些内容的话,这个交易其实是连编译都没法通过的。整个的过程它始终要保持资源的正确才可以使用。这也是Cadence 从始至终贯彻的,对于资源需要非常慎重非常严格。
2、Cadence的解决方法
所以我们 Cadence 是怎么解决这些问题的?我们资产转移的过程永远是安全的,如果资源没有被正确的使用,Cadence编译器将抛出错误。
如果接收者没有 Vault,无论是因为地址错误还是该账户不想接收这些类型的数据,交易将会被回滚。所以 Flow 上不存在 0 地址销毁。Flow上的资源要销毁的话,必须在交易中显示的显示的destroy。这样的话就不存在数据因为发送地址拼写错误而丢失。
五、Cadence相关工具链导览
1、开发者工具
这是我们现在有的一些开发工具,我大概可以给大家过一下
第一步入门,playground 是一个非常和友好的面向初学者快速去学习 Cadence 的一个网站。还有一个有比较有趣的叫 CryptoDappy 的视频,这个是要有一定的基础的开发者去学习可能会好一点。
第二步可能就要进入到我们的本地开发,本地开发的时候就会涉及到我们的命令行工具,我们的模拟器和我们的 VSCode 。模拟器我觉得最值得说的一个点就是他在做其他的合约,或者说其他的链的开发的时候,就可能先跑一个测试节点,跑一个测试链。然后 Flow 为了方便开发者开发其实就规避了这件事情。
我们直接做了一个本地化的工具直接模拟出了区块链的一些节点内容,它具备大多数区块链相同的功能,当你发消息过去的时候,它会将你发过去交易打包成块,这样的话好处就是我们不需要有一个非常大的节点始终在那边跑着,只需要有一个非常类似于节点的环境跑着,然后当我有需要的时候发一笔交易,那边给你一个反馈就行了,因为本质上区块链就是一个状态管理的数据库。
第三步完成了功能测试之后,会要使用的一些工具,包括我们的区块链浏览flowscan,包括我们的前端接入库和后端接入库。这里面其实我还要特地要说一下 FCL 这个东西,就是大家接触以太坊的人都知道,现在并没有一个非常标准的规范来说我怎么来方便的去接入不同的用户,作为一个应用方,我想接入这些钱包其实是一个很痛苦的事情。所以Flow设计了一套标准FCL。这套FCL的标准不仅仅定义了前端该怎么接,它同时也定义了钱包该怎么接。
当我的钱包它提供了足够的API和对于 FCL 兼容的响应请求的内容呈现之后,FCL 就成为了一个连接钱包应用和区块链三位一体连接在中心的一个标准化的框架。在这样一个框架下,前端应用是不需要关心我后面到底接的是哪个类型的钱包。FCL 定义好了前端的标准,我只要调用 FCL 里面的特定的一些内容,然后传一个参数告诉你我要使用哪类型的钱包就行了。
而钱包在接入 FCL 的时候,只要完成了 FCL 标准定义下的那些要求,钱包也能快速的接入。这样最大的好处有两件事情。对于应用方来说,应用方不需要关心我到底是要接哪个钱包,未来只要是能适配到 FCL 的都可以用。而对于钱包开发方来说,只要有足够支持 FCL 的应用,这个应该就可以使用他的钱包。
2、学习路径图
以上就是我今天的所有分享内容,谢谢大家。