中移链合约常用开发介绍 (一)开发基本流程
一、目的
本文档从 C++ 及智能合约基本概念出发再到实战运行智能合约,介绍了中移链合约开发的基本流程,同时对常见问题做出梳理。本文档将以hello
合约作为示例介绍智能合约如何在链上工作,适合刚接触合约开发的开发人员用来了解 EOS 智能合约如何编写、编译、部署、动作调用以及管理授权,帮助其快速了解以及上手智能合约。
二、智能合约介绍
区块链作为一种分布式可信计算平台,去中心化是其最本质的特征。每笔交易的记录不可篡改地存储在区块链上。智能合约中定义可以在区块链上执行的动作action
和交易transaction
的代码。可以在区块链上执行,并将合约执行状态作为该区块链实例不可变历史的一部分。
因此,开发人员可以依赖该区块链作为可信计算环境,其中智能合约的输入、执行和结果都是独立的,不受外部影响。
三、术语解释
EOSIO.CDT(合约开发工具包)
EOSIO.CDT 是 WebAssembly(WASM)的工具链,也是用于促进 EOSIO 平台智能合约开发的一组工具。使用 C++编程语言创建 EOSIO 智能合约。EOSIO.CDT 提供构建智能合约所需的库和工具。
WebAssembly(WASM)
用于执行可移植二进制代码格式的虚拟机,托管在 nodeos 中。
应用程序二进制接口(ABI)
定义如何将数据编组进出 WebAssembly 虚拟机的接口。
动作(Action)
智能合约公开的功能,通过批准的交易将正确的参数传递给 EOSIO 网络来执行。
李嘉图合约(Ricardian Contract)
在基于 EOSIO 的区块链环境中,李嘉图合约是一个伴随智能合约的数字文档,定义了智能合约与其用户之间交互的条款和条件,以人类可读的文本编写,然后对其进行加密签署和验证。对于人和程序来说,它都很容易阅读,并有助于为智能合约和其用户之间的交互中可能出现的任何情况提供清晰的理解。
四、开发流程
(一)知识和环境准备
1、了解 C++ 语言
EOSIO 的智能合约都由 C++ 代码编写,因此,您应当具有一定的 C++ 编程能力。不过,智能合约对 C++ 的使用并不会过于复杂。 C++ 是一种静态类型的、编译式的编程语言,支持过程化编程、面向对象编程和泛型编程。因此, C++ 是在编译时执行类型检查,而不是在运行时执行类型检查。您所编写的.cpp
文件只有在通过编译后,才能尝试运行。
2、代码编辑器或 IDE
任何支持 C++的 IDE 都可以用于编写智能合约,本文中智能合约的编译和部署都将在终端命令中进行。因此您使用的编辑器甚至可以不具备编译 C++代码的功能。若仅用于编写代码,.txt
文本文件就可以胜任。但为了方便编写和检查,还是建议使用例如 VSCode 的常规 IDE,或智能合约开发 IDE,EOS Stuido。
3、完全配置的本地开发环境
具体可参考 中移链(基于EOS)测试环境搭建
(二)编写合约
1、创建文件
创建智能合约的过程中,通常会创建两个文件,分别是包含智能合约类声明的头文件.hpp
,以及包含智能合约操作实现的文件.cpp
。如果代码内容十分简单时,也可以直接在一个.cpp
文件中编写声明和实现。此处我们仅用一个.cpp
文件来展示hello
样例。但往后的程序都应采用规范的声明实现分离写法。
创建一个名为hello
的新文件夹来存储您的智能合约文件,并进入目录:
创建新文件hello.cpp
:
使用您的文本编辑器打开它后,可以开始编写智能合约代码。
2、编写代码
编写智能合约代码,主要包括以下四个步骤:
(1)使用include
导入 EOSIO 基础库
eosio.hpp
中包含编写一个智能合约所需要的基础类,例如eosio::contract
。
(2)创建一个类,并使它继承eosio::contract
使用[[eosio::contract]]
属性通知 EOSIO.CDT 这是一个智能合约。
添加一行代码:
这表明hello
作为一个智能合约,公有的继承了eosio::contract
类型。
(3)添加公共访问说明符和使用声明
在C++
的类中,可以声明公有、私有、保护三种类型的类成员和类成员函数。其中,构造函数是在类被创建时需要运行的类成员函数。我们创建的hello
类作为eosio::contract
的派生类(子类),可以继承基类(父类)的接口和实现。
使用using
添加一行代码表示声明了eosio::contract
的默认基类构造函数:
(4)添加动作hi
使用[[eosio::action]]
通知 EOSIO.CDT 这是一个合约动作。
添加以下代码:
在 EOSIO 中,智能合约的动作action
就类似于 C++中的类成员函数。此处添加的hi
动作功能为:接收一个类型为eosio::name
的参数,打印与该参数打招呼的信息。其中eosio::print
是包含在eosio/eosio.hpp
中的函数,可以直接使用。
(5)保存文件
现在,hello.cpp
文件应该如下所示:
(三)编译并部署合约
成功创建智能合约后,要对合约进行编译和部署。
1、编译
使用eosio-cpp
命令编译hello.cpp
文件
要将智能合约部署到区块链上,首先使用eosio-cpp
工具编译智能合约。编译构建一个 WebAssembly 文件.wasm
和一个相应的应用程序二进制接口文件.abi
。
WebAssenbly 并不是一种编程语言,而是一种编译器的编译目标,可以把.wasm
文件当成是.cpp
文件通过编译以后生成的文件。.wasm
文件是区块链中的 WebAssembly 引擎执行的二进制代码。WebAssembly 引擎托管在 nodeos 守护进程中并执行智能合约代码。.abi
文件定义了数据如何编组进出 WASM 引擎。
在与合约程序相同的文件夹中运行以下命令,或在其他位置使用绝对或相对路径来引用该文件:
此时文件夹中创建了两个新文件:hello.wasm
和hello.abi
。
2、部署
将hello
合约部署到同名账户
使用以下命令将编译好的hello.wasm
和hello.abi
文件部署到区块链上的 hello 账户:
cleos set contract
命令后必须跟随部署合约的账户名,此处为同名账户hello
。如果您没有 hello 账户,请参考 中移链(基于EOS)测试环境搭建中的 (六)创建开发账户
运行此步骤前,请确保您的账户中有处于解锁状态的钱包。cleos 会寻找一个解锁状态的钱包以获取您使用的权限的私钥。在本例中,使用的权限为-p hello@active
,即 hello 账户的 active 权限。
成功部署后,会得到类似下图的返回信息:
(四)调用动作
使用cleos push action
命令,用已有账户调用hello
合约中的hi
动作:
应该产生:
可以看到,该合约能够允许任何账户向任何用户打招呼。例如,将账户更换为 alice 后:
产生结果如下,依旧可以实现对 bob 的动作:
(五)动作的授权
EOSIO 区块链使用非对称密码学来验证推送交易的账户是否已使用匹配的私钥签署了交易。使用账户权限表来检查账户是否具有执行操作所需的权限。使用授权是保护智能合约的第一步。
本文档提供了四种方式在合约代码中进行授权检查。
1、has_auth(name n) 函数
验证指定账户是否与调用动作的账户相符,返回bool
值。
以hi
动作为例,如果我们希望动作只能向调用账户进行问好,而不相符的账户名进行消息提示,可以将动作部分的代码做如下调整:
重新编译并部署合约:
使用 alice 账户对 bob 账户和 alice 账户分别调用hi
动作。结果分别如下:
2、require_auth(name n) 函数
验证指定账户是否与调用动作的账户相符,不相符则直接报错失败。
与has_auth
函数不同,此函数会在验证失败时直接停止运行。同样以hi
动作为例,如果我们希望动作只能向调用账户进行问好,而对不相符的账户直接显示失败,可以将动作部分的代码做如下调整:
重新编译并部署合约:
使用 alice 账户对 bob 账户和 alice 账户分别调用hi
动作。结果分别如下:
3、require_auth2(capi_name name, capi_name permission) 函数
验证指定账户和权限是否与调用动作的账户和权限相符,不相符则直接报错失败。
此函数比之前增加了对账户权限的限制。同样以hi
动作为例,如果我们希望动作只能由账户的active
权限进行调用,而对不相符的账户直接显示失败,可以将动作部分的代码做如下调整:
重新编译并部署合约:
使用 alice 账户的 family 和 active 权限分别对 alice 账户调用hi
动作。结果分别如下:
4、check(bool pred, ...) 函数
断言,如果pred
为假,则使用提供的消息进行反馈。例如:
因此,之前我们实现的在参数与账户不相符时打印错误信息的代码可以优化为:
重新编译并部署合约:
使用 alice 账户对 bob 账户和 alice 账户分别调用hi
动作。结果分别如下:
关于上链
在之后的代码实践中,开发人员可以通过以上这四种授权检查代码的搭配来实现合约中动作的授权管理。
对于触发 Error 导致方法运行中断的情况,交易记录不会上链。只有当交易 ID 产生,动作正常运行完成,此次记录才会记录在链上。
对于上文的举例来说,第一种代码的错误案例完成了上链。因为交易正常结束,并打印出了错误信息。而后面三种分别触发了Error 3090003
、Error 3090004
、Error 3050003
,导致动作中断,数据不会上链。
五、常见问题
(一)部署合约时遇到错误
遇到Unable to resolve path
错误,可以将合约地址改为绝对路径,避免因相对路径产生的报错。
(二)require_auth2 编译错误
常规情况中可依据报错提示信息将require_auth2
补充为eosio::internal_use_do_not_use::require_auth2
解决。但本例中该前缀已经表明这是一个不推荐使用的方法。
故推荐的解决方法为引入action
头文件。
(三)编译警告:缺少李嘉图合约
合约编译后会遇到警告,不会影响到合约部署后的正常运行。
警告信息如下:
此警告说明合约中的动作不具备李嘉图合约。
从 EOSIO.CDT v1.4.0 开始,ABI 生成器将尝试自动将合约和条款导入到生成的 ABI 中。对此有一些警告,包括文件的严格命名策略和用于标记每个李嘉图合约和每个条款的 HTML 标记。
李嘉图合约应该存放在一个名为.contracts.md
的文件中,条款名为.clauses.md
,在同一文件夹中。
评论