EOS 合约基础教程 - 合约 ( contract )

经过上一章节 Hello World 合约的学习,我们已经对 EOS 合约的基本构成有一个大概的了解。知道一个 EOS 合约就是一个 C++ 类

EOS 合约的基本条件

虽然 EOS 合约是一个 C++ 类,但,一个 EOS 合约又不仅仅是一个普通的 C++ 类,它必须符合一定的条件才能成为合约:

  1. 类的构造方法必须接受且只能接受三个参数

    contractName( name receiver, name code, datastream<const char*> ds );
    

    参数说明

    参数 类型 说明
    receiver eosio::name 合约的名字,准确的说,是部署当前合约的账号
    code eosio::name 调用合约 动作的账号,一般是当前合约所在的账号
    ds datastream<const char*> 保存了那些使用 eosio::ignore 忽略的字段

    注意: ds 参数目前没啥大的作用,但是,如果合约想要对未由调度程序处理的操作字段进行进一步反序列化(例如,要重新使用 ignore 忽略的字段),这会派上用场

    从参数中可以看出,一般情况下,receivercode 是相同的。那有没有不同的时候呢 ? 有,以后我们会提到的通知,通知的接收合约中就会遇到 receivercode 是不相同的

  2. 任何一个合约,都必须定义一个 EOSIO_DISPATCH( hello, (hi)) 用于导出合约中的动作,这样,EOS 执行环境才知道该合约可以接受哪些动作

    EOSIO_DISPATCH() 是一个宏定义,它的原型如下

    #define EOSIO_DISPATCH( TYPE, MEMBERS ) ...
    

    参数说明

    参数 说明
    TYPE 合约的名字,注意,是合约的名字,而不是合约所在的账号的名字
    MEMBERS 由小括号扩起来的 0 个或多个动作名,比如 (hi) ,每个动作都是一个 C++ public 方法

合约的构造函数

刚刚我们已经介绍了,一个 EOS 合约的构造函数必须能够接受三个参数,它的基本原型如下

contractName( name receiver, name code, datastream<const char*> ds );

我们写一个基本的范例来输出下这三个参数的值

#include <eosiolib/eosio.hpp>

using namespace eosio;
using namespace std;

class hello
{
public:
    hello( name receiver, name code, datastream<const char*> ds )
    {
        print("receiver:");
        print(receiver);
        print("    code:");
        print(code);
        print("    ds length:");
        print(ds.remaining());

        if( ds.remaining() > 0 ){
            std::string data;
            ds >> data;
            print("    ds:");
            print(std::string(data));
        }
    }

    [[eosio::action]]
    void hi(){
    }
};

EOSIO_DISPATCH(hello,(hi))

然后使用下面的命令来编译

eosio-cpp -o hello.wasm hello.cpp --abigen

编译结果如下

Warning, empty ricardian clause file
Warning, empty ricardian clause file
Warning, action <hi> does not have a ricardian contract

上面这几个警告是可以忽略的,因为我们没有给合约和方法添加 李嘉图 (Ricardian) 说明文件

李嘉图 (Ricardian) 说明文件如何编写,我们会在以后的章节中讲解

接着使用下面的命令来部署合约

cleos set contract hello ../hello -p hello

运行结果如下

Reading WASM from ../hello/hello.wasm...
Skipping set abi because the new abi is the same as the existing abi
Publishing contract...
executed transaction: 683503d8936d27ffc6fdd1b604782757c8bedcae899c327b34475272ade24b00  2808 bytes  588 us
#         eosio <= eosio::setcode               {"account":"hello","vmtype":0,"vmversion":0,"code":"0061736d0100000001681260017f006000006000017f6002...

最后使用下面的命令来运行合约

1. 使用当前合约账号来执行合约,且不传递任何参数

cleos push action hello hi '[]' -p hello
executed transaction: 41ad0f8d064a6980151d979666df4103fe0364033fdfee6945cd40b7f39df70b  96 bytes  212 us
#         hello <= hello::hi                    ""
>> receiver:hello    code:hello    ds length:0

2. 使用当前合约账号来执行合约,且一些参数

cleos push action hello hi '["hello","ni","hao"]' -p hello
executed transaction: 4723aa8ec187e47ead46580d415ebe5b17fd0849b234fd54e092e08f088c6534  96 bytes  220 us
#         hello <= hello::hi                    ""
>> receiver:hello    code:hello    ds length:0

3. 使用其它账号来执行合约,不传递任何参数

cleos push action hello hi '[]' -p hi
executed transaction: c0c15799a02387078e7e9c9f0ba09f5987cc7b949b6d44ca4be78a606e58afa2  96 bytes  226 us
#         hello <= hello::hi                    ""
>> receiver:hello    code:hello    ds length:0

4. 使用其它账号执行合约,传递一些参数

cleos push action hello hi '["hello","ni","hao"]' -p hi
executed transaction: 27a9ade415ecb2f49cb82e2fc44e2c36116d4b5a6fa5fc5c1dfed30a5eab698f  96 bytes  242 us
#         hello <= hello::hi                    ""
>> receiver:hello    code:hello    ds length:0

从上面的执行结果中可以看出,

  • 如果直接执行合约中的方法,那么 codereceiver 都是一样的,都是当前合约所在的账号
  • datastream<const char*> 一般情况下都是空的,也就是说,没有任何数据

合约基础类 eosio::contract

如果我们每写一个合约就要自己动手写一个长长的构造函数,肯定是不开心的,作为程序员,能偷懒就偷懒才是我们的理想。

为此,我们可能希望把包含了三个参数的构造方法写在一个基础的合约里,比如,我们定义一个名为 contract 的基础合约

basic.cpp

#include <eosiolib/eosio.hpp>

using namespace eosio;
using namespace std;

class basic
{
public:
    basic( name receiver, name code, datastream<const char*> ds ):
    _self(receiver),_code(code),_ds(ds) {}

    name _self;
    name _code;
    datastream<const char*> _ds;
};

然后其它合约在扩展自这个合约,并使用 using 关键字来引用基础类的构造方法

hello.cpp

#include <eosiolib/eosio.hpp>

#include "basic.cpp"

using namespace eosio;
using namespace std;

class hello:public basic {
public:
    using basic::basic;

    [[eosio::action]]
    void hi(){
        print("receiver:");
        print(_self);
        print("    code:");
        print(_code);
        print("    ds length:");
        print(_ds.remaining());

        if( _ds.remaining() > 0 ){
            std::string data;
            _ds >> data;
            print("    ds:");
            print(std::string(data));
        }
    }
};

EOSIO_DISPATCH(hello,(hi))

然后编译、部署、执行合约,会得到相同的结果

cleos push action hello hi '["hello","ni","hao"]' -p hi
executed transaction: 040514a09eb7fe268d5ce7d23a38080505ab73cdf72e3bdf47eff7228f2e1747  96 bytes  225 us
#         hello <= hello::hi                    ""
>> receiver:hello    code:hello    ds length:0

EOS 官方也考虑到了这一点,创建了一个合约基础类 contract,并放在命名空间 eosio 下,头文件为 <eosiolib/contract.hpp>

该文件在 Github 上的地址为 https://github.com/EOSIO/eosio.cdt/blob/master/libraries/eosiolib/contract.hpp

同时,#include <eosiolib/eosio.hpp> 头文件也默认包含了该头文件,因此,只要包含了 #include <eosiolib/eosio.hpp> 就可以了

我们把上面的范例改改,改成官方的合约基础类

#include <eosiolib/eosio.hpp>

using namespace eosio;
using namespace std;

class hello:public eosio::contract {
public:
    using eosio::contract::contract;

    [[eosio::action]]
    void hi(){
        print("receiver:");
        print(_self);
        print("    code:");
        print(_code);
        print("    ds length:");
        print(_ds.remaining());

        if( _ds.remaining() > 0 ){
            std::string data;
            _ds >> data;
            print("    ds:");
            print(std::string(data));
        }
    }
};

EOSIO_DISPATCH(hello,(hi))
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.