一、目标
本文档从 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
的新文件夹来存储您的智能合约文件,并进入目录:
mkdir hello
cd hello
创立新文件hello.cpp
:
touch hello.cpp
应用您的文本编辑器关上它后,能够开始编写智能合约代码。
2、编写代码
编写智能合约代码,次要包含以下四个步骤:
(1)应用 include
导入 EOSIO 根底库
#include <eosio/eosio.hpp>
eosio.hpp
中蕴含编写一个智能合约所须要的根底类,例如eosio::contract
。
(2)创立一个类,并使它继承eosio::contract
应用 [[eosio::contract]]
属性告诉 EOSIO.CDT 这是一个智能合约。
增加一行代码:
class [[eosio::contract]] hello : public eosio::contract {};
这表明 hello
作为一个智能合约,私有的继承了 eosio::contract
类型。
(3)增加公共拜访说明符和应用申明
在 C++
的类中,能够申明私有、公有、爱护三种类型的类成员和类成员函数。其中,构造函数是在类被创立时须要运行的类成员函数。咱们创立的 hello
类作为 eosio::contract
的派生类(子类),能够继承基类(父类)的接口和实现。
应用 using
增加一行代码示意申明了 eosio::contract
的默认基类构造函数:
public:
using eosio::contract::contract;
(4)增加动作hi
应用 [[eosio::action]]
告诉 EOSIO.CDT 这是一个合约动作。
增加以下代码:
[[eosio::action]] void hi(eosio::name user){print("Hello,", user);
}
在 EOSIO 中,智能合约的动作 action
就相似于 C ++ 中的类成员函数。此处增加的 hi
动作性能为:接管一个类型为 eosio::name
的参数,打印与该参数打招呼的信息。其中 eosio::print
是蕴含在 eosio/eosio.hpp
中的函数,能够间接应用。
(5)保留文件
当初,hello.cpp
文件应该如下所示:
#include <eosio/eosio.hpp>
class [[eosio::contract]] hello : public eosio::contract {
public:
using eosio::contract::contract;
[[eosio::action]] void hi(eosio::name user) {print( "Hello,", user);
}
};
(三)编译并部署合约
胜利创立智能合约后,要对合约进行编译和部署。
1、编译
应用 eosio-cpp
命令编译 hello.cpp
文件
要将智能合约部署到区块链上,首先应用 eosio-cpp
工具编译智能合约。编译构建一个 WebAssembly 文件 .wasm
和一个相应的应用程序二进制接口文件.abi
。
WebAssenbly 并不是一种编程语言,而是一种编译器的编译指标,能够把 .wasm
文件当成是 .cpp
文件通过编译当前生成的文件。.wasm
文件是区块链中的 WebAssembly 引擎执行的二进制代码。WebAssembly 引擎托管在 nodeos 守护过程中并执行智能合约代码。.abi
文件定义了数据如何编组进出 WASM 引擎。
在与合约程序雷同的文件夹中运行以下命令,或在其余地位应用相对或相对路径来援用该文件:
eosio-cpp -abigen -o hello.wasm hello.cpp
此时文件夹中创立了两个新文件:hello.wasm
和hello.abi
。
2、部署
将 hello
合约部署到同名账户
应用以下命令将编译好的 hello.wasm
和hello.abi
文件部署到区块链上的 hello 账户:
cleos set contract hello ./hello -p hello@active
cleos set contract
命令后必须追随部署合约的账户名,此处为同名账户 hello
。
如果您没有 hello 账户,请参考 中移链(基于 EOS)测试环境搭建中的 (六)创立开发账户
运行此步骤前,请确保您的账户中有处于解锁状态的钱包。cleos 会寻找一个解锁状态的钱包以获取您应用的权限的私钥。在本例中,应用的权限为-p hello@active
,即 hello 账户的 active 权限。
胜利部署后,会失去相似下图的返回信息:
Reading WASM from ./hello/hello.wasm...
Skipping set abi because the new abi is the same as the existing abi
Publishing contract...
executed transaction: 5f49530dcef3221d51f3160deb3f9ba0911cc6b93b2ce0a6560dff271178f13b 14288 bytes 23172 us
# eosio <= eosio::setcode "00000000001aa36a0000ab8c020061736d0100000001d4012260000060037f7f7f017f60037f7e7f017e60047f7f7f7f006...
(四)调用动作
应用 cleos push action
命令,用已有账户调用 hello
合约中的 hi
动作:
cleos push action hello hi '["bob"]' -p bob@active
应该产生:
executed transaction: 5be02a763ba87db15a22ae2135e7fc6ec5b0ee8fed3a9a012f65f5ebde706b3e 104 bytes 2459 us
# hello <= hello::hi {"user":"bob"}
>> Hello, bob
能够看到,该合约可能容许任何账户向任何用户打招呼。例如,将账户更换为 alice 后:
cleos push action hello hi '["bob"]' -p alice@active
产生后果如下,仍旧能够实现对 bob 的动作:
executed transaction: bfb8787117257f8e7d3f659509678f24a7bdb040a6b1a846fa090d428bc06540 104 bytes 271 us
# hello <= hello::hi {"user":"bob"}
>> Hello, bob
(五)动作的受权
EOSIO 区块链应用非对称密码学来验证推送交易的账户是否已应用匹配的私钥签订了交易。应用账户权限表来查看账户是否具备执行操作所需的权限。应用受权是爱护智能合约的第一步。
本文档提供了四种形式在合约代码中进行受权查看。
1、has_auth(name n) 函数
验证指定账户是否与调用动作的账户相符,返回 bool
值。
以 hi
动作为例,如果咱们心愿动作只能向调用账户进行问好,而不相符的账户名进行音讯提醒,能够将动作局部的代码做如下调整:
[[eosio::action]] void hi(eosio::name user) {if(has_auth( user)){print("Hello,", eosio::name{user} );
}else{print("This is not",eosio::name{user} );
}
}
从新编译并部署合约:
eosio-cpp -abigen -o hello.wasm hello.cpp
cleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active
应用 alice 账户对 bob 账户和 alice 账户别离调用 hi
动作。后果别离如下:
cleos push action hello hi '["bob"]' -p alice@active
executed transaction: 20a297df95fe19840893305a8f18e6380ee3482a3773885224f6381487559105 104 bytes 601 us
# hello <= hello::hi {"user":"bob"}
>> This is not bob
cleos push action hello hi '["alice"]' -p alice@active
executed transaction: 3f12347d3458204be643a2c4cef5ef3542994ed3bb320466aac423e1a983e8d7 104 bytes 204 us
# hello <= hello::hi {"user":"alice"}
>> Hello, alice
2、require_auth(name n) 函数
验证指定账户是否与调用动作的账户相符,不相符则间接报错失败。
与 has_auth
函数不同,此函数会在验证失败时间接进行运行。同样以 hi
动作为例,如果咱们心愿动作只能向调用账户进行问好,而对不相符的账户间接显示失败,能够将动作局部的代码做如下调整:
[[eosio::action]] void hi(eosio::name user) {require_auth( user);
print("Hello,", eosio::name{user} );
}
从新编译并部署合约:
eosio-cpp -abigen -o hello.wasm hello.cpp
cleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active
应用 alice 账户对 bob 账户和 alice 账户别离调用 hi
动作。后果别离如下:
cleos push action hello hi '["bob"]' -p alice@active
Error 3090004: Missing required authority
Ensure that you have the related authority inside your transaction!;
If you are currently using 'cleos push action' command, try to add the relevant authority using -p option.
Error Details:
missing authority of bob
pending console output:
cleos push action hello hi '["alice"]' -p alice@active
executed transaction: 495b0e8d6fa3610f5b91ffd28eb4013d1a53a1e1e13046b6a3069566bfeaf65f 104 bytes 168 us
# hello <= hello::hi {"user":"alice"}
>> Hello, alice
3、require_auth2(capi_name name, capi_name permission) 函数
验证指定账户和权限是否与调用动作的账户和权限相符,不相符则间接报错失败。
此函数比之前减少了对账户权限的限度。同样以 hi
动作为例,如果咱们心愿动作只能由账户的 active
权限进行调用,而对不相符的账户间接显示失败,能够将动作局部的代码做如下调整:
[[eosio::action]] void hi(eosio::name user) {require_auth2(user.value, "active"_n.value);
print("Hello,", eosio::name{user} );
}
从新编译并部署合约:
eosio-cpp -abigen -o hello.wasm hello.cpp
cleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active
应用 alice 账户的 family 和 active 权限别离对 alice 账户调用 hi
动作。后果别离如下:
cleos push action hello hi '["alice"]' -p alice@family
Error 3090003: Provided keys, permissions, and delays do not satisfy declared authorizations
Ensure that you have the related private keys inside your wallet and your wallet is unlocked.
Error Details:
transaction declares authority '{"actor":"alice","permission":"family"}', but does not have signatures for it.
cleos push action hello hi '["alice"]' -p alice@active
executed transaction: 055228767c6f23c283 910ebc348d8a76d444fa1165454c9886dd58b940023ef5 104 bytes 395 us
# hello <= hello::hi {"user":"alice"}
>> Hello, alice
4、check(bool pred, …) 函数
断言,如果 pred
为假,则应用提供的音讯进行反馈。例如:
eosio::check(a == b, "a does not equal b");
因而,之前咱们实现的在参数与账户不相符时打印错误信息的代码能够优化为:
[[eosio::action]] void hi(eosio::name user) {eosio::check(has_auth(user), "User is not authorized to perform this action.");
print("Hello,", eosio::name{user} );
}
从新编译并部署合约:
eosio-cpp -abigen -o hello.wasm hello.cpp
cleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active
应用 alice 账户对 bob 账户和 alice 账户别离调用 hi
动作。后果别离如下:
cleos push action hello hi '["bob"]' -p alice@active
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: User is not authorized to perform this action.
pending console output:
cleos push action hello hi '["alice"]' -p alice@active
executed transaction: 86887c76f45d9c0f9abc017f2cfc49132bd5d0c1d6bbd7f41aeb7bc1675ab42c 104 bytes 172 us
# hello <= hello::hi {"user":"alice"}
>> Hello, alice
对于上链
在之后的代码实际中,开发人员能够通过以上这四种受权查看代码的搭配来实现合约中动作的受权治理。
对于触发 Error 导致办法运行中断的状况,交易记录不会上链。只有当交易 ID 产生,动作失常运行实现,此次记录才会记录在链上。
对于上文的举例来说,第一种代码的谬误案例实现了上链。因为交易失常完结,并打印出了错误信息。而前面三种别离触发了Error 3090003
、Error 3090004
、Error 3050003
,导致动作中断,数据不会上链。
五、常见问题
(一)部署合约时遇到谬误
<3>error 2022-08-08T08:44:27.913 cleos main.cpp:4371 operator()] Failed with error: unspecified (0)
Unable to resolve path './hello'
遇到 Unable to resolve path
谬误,能够将合约地址改为绝对路径,防止因相对路径产生的报错。
(二)require_auth2 编译谬误
/home/xxx/biosboot/genesis/hello/hello.cpp:6:10: error: use of undeclared identifier 'require_auth2'; did you mean 'eosio::internal_use_do_not_use::require_auth2'?
require_auth2(user.value, "active"_n.value);
^~~~~~~~~~~~~
eosio::internal_use_do_not_use::require_auth2
惯例状况中可根据报错提示信息将 require_auth2
补充为 eosio::internal_use_do_not_use::require_auth2
解决。但本例中该前缀曾经表明这是一个不举荐应用的办法。
故举荐的解决办法为引入 action
头文件。
#include <../capi/eosio/action.h>
(三)编译正告:短少李嘉图合约
合约编译后会遇到正告,不会影响到合约部署后的失常运行。
正告信息如下:
Warning, empty ricardian clause file
Warning, action <hi> does not have a ricardian contract
此正告阐明合约中的动作不具备李嘉图合约。
从 EOSIO.CDT v1.4.0 开始,ABI 生成器将尝试主动将合约和条款导入到生成的 ABI 中。对此有一些正告,包含文件的严格命名策略和用于标记每个李嘉图合约和每个条款的 HTML 标记。
李嘉图合约应该寄存在一个名为 .contracts.md
的文件中,条款名为.clauses.md
,在同一文件夹中。