一、目标

本文档从 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 hellocd 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.wasmhello.abi

2、部署

hello合约部署到同名账户

应用以下命令将编译好的hello.wasmhello.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 abiPublishing 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.cppcleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active

应用alice账户对bob账户和alice账户别离调用hi动作。后果别离如下:

cleos push action hello hi '["bob"]' -p alice@activeexecuted transaction: 20a297df95fe19840893305a8f18e6380ee3482a3773885224f6381487559105  104 bytes  601 us#         hello <= hello::hi                    {"user":"bob"}>> This is not bob
cleos push action hello hi '["alice"]' -p alice@activeexecuted 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.cppcleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active

应用alice账户对bob账户和alice账户别离调用hi动作。后果别离如下:

cleos push action hello hi '["bob"]' -p alice@activeError 3090004: Missing required authorityEnsure 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 bobpending console output: 
cleos push action hello hi '["alice"]' -p alice@activeexecuted 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.cppcleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active

应用alice账户的family和active权限别离对alice账户调用hi动作。后果别离如下:

cleos push action hello hi '["alice"]' -p alice@familyError 3090003: Provided keys, permissions, and delays do not satisfy declared authorizationsEnsure 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@activeexecuted 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.cppcleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active

应用alice账户对bob账户和alice账户别离调用hi动作。后果别离如下:

cleos push action hello hi '["bob"]' -p alice@activeError 3050003: eosio_assert_message assertion failureError Details:assertion failure with message: User is not authorized to perform this action.pending console output: 
cleos push action hello hi '["alice"]' -p alice@activeexecuted transaction: 86887c76f45d9c0f9abc017f2cfc49132bd5d0c1d6bbd7f41aeb7bc1675ab42c  104 bytes  172 us#         hello <= hello::hi                    {"user":"alice"}>> Hello, alice

对于上链

在之后的代码实际中,开发人员能够通过以上这四种受权查看代码的搭配来实现合约中动作的受权治理。

对于触发Error导致办法运行中断的状况,交易记录不会上链。只有当交易ID产生,动作失常运行实现,此次记录才会记录在链上。

对于上文的举例来说,第一种代码的谬误案例实现了上链。因为交易失常完结,并打印出了错误信息。而前面三种别离触发了Error 3090003Error 3090004Error 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 fileWarning, action <hi> does not have a ricardian contract

此正告阐明合约中的动作不具备李嘉图合约。

从 EOSIO.CDT v1.4.0 开始,ABI 生成器将尝试主动将合约和条款导入到生成的 ABI 中。对此有一些正告,包含文件的严格命名策略和用于标记每个李嘉图合约和每个条款的 HTML 标记。

李嘉图合约应该寄存在一个名为.contracts.md的文件中,条款名为.clauses.md,在同一文件夹中。