美文网首页
EOS 合约基础教程 - 智能合约简介

EOS 合约基础教程 - 智能合约简介

作者: 金牛茶馆 | 来源:发表于2019-01-21 22:01 被阅读59次

1 EOS智能合约和以太坊合约的区别

EOS的智能合约里面有一个action(动作)和transaction(交易)的概念。
在以太坊中,基本上只有transaction的概念,如果我只要执行一种操作,而且是只读操作,就不需要签名。如果需要划资金,有一些写的操作,那就需要用户用公钥对这个操作去签名,然后pos的一个transaction,这是以太坊的概念。

对于EOS,它多了一个action的概念,action其实它也是对一个智能合约中的某个函数的调用。transaction是由一个或者多个action组合而成的关系,就是在一个transaction里,可以包含多个action,这样你可以在一个transaction里签一次名,就可以调多个函数,做一组操作。

2 EOS 合约

2.1 基础知识

EOS智能合约通过messages 及 共享内存数据库(比如只要一个合约被包含在transaction的读取域中with an async vibe,它就可以读取另一个合约的数据库)相互通信。异步通信导致的spam问题将由资源限制算法来解决。下面是两个在合约里可定义的通信模型:

  • Inline 内联通信模型,采用请求其他操作的形式,需要作为调用操作的一部分执行。Inline保证执行当前的transaction或unwind;无论成功或失败都不会有通知。Inline 操作的scopes和authorities和原来的transaction一样。
  • Deferred 延迟通信模型,采用发送到对等交易的动作通知的形式, 根据生产者的判断,延迟的操作最多可以安排在稍后的时间运行由区块生产者来安排。无法保证将执行延期操作,结果可能是传递通信结果或者只是超时。延期交易具有发送合约的权限,交易可以取消延期交易。

2.2 目录结构

2.2.1 创建智能合约文件

eoscpp将创造三个智能合约文件,他们是你起步开发的框架。

 eoscpp -n ${contract}

以上将在'./${project}'文件夹下创建一个新项目,包含三个文件:

${contract}.abi ${contract}.hpp ${contract}.cpp

2.2.2 HPP

HPP是包含CPP文件所引用的变量、常量、函数的头文件。

2.2.3 CPP

CPP文件是包含合约功能的源文件。

2.2.4 WAST

想要部署到EOS.IO区块链上的任何程序都需要先编译成WASM格式。这是区块链接受的唯一格式。
一旦您完成了CPP文件的开发,您可以用eoscpp工具将它编译成一个文本版本的WASM (.wast) 文件。

$ eoscpp -o ${contract}.wast ${contract}.cpp

2.2.5 ABI

Application Binary Interface (ABI)是一个基于JSON的描述文件,是关于转换JSON和二进制格式的用户actions的。ABI还描述了如何将数据库状态和JSON的互相转换。一旦您通过ABI描述了您的合约,开发者和用户就能够用JSON和您的合约无缝交互了。
ABI文件可通过eoscpp工具从HPP文件生成:

$ eoscpp -g ${contract}.abi ${contract}.hpp

这里是一个合约的骨架ABI的例子:

{
  "types": [{
      "new_type_name": "account_name",
      "type": "name"
    }
  ],
  "structs": [{
      "name": "transfer",
      "base": "",
      "fields": {
        "from": "account_name",
        "to": "account_name",
        "quantity": "uint64"
      }
    },{
      "name": "account",
      "base": "",
      "fields": {
        "account": "name",
        "balance": "uint64"
      }
    }
  ],
  "actions": [{
      "action": "transfer",
      "type": "transfer"
    }
  ],
  "tables": [{
      "table": "account",
      "type": "account",
      "index_type": "i64",
      "key_names" : ["account"],
      "key_types" : ["name"]
    }
  ]
}

您肯定注意到了这个ABI 定义了一个叫transfer的action,它的类型也是transfer。这就告诉EOS.IO当${account}->transfer的message发生时,它的payload是transfer类型的。 transfer类型是在structs的列表中定义的,其中有个对象,name属性是transfer。

...
  "structs": [{
      "name": "transfer",
      "base": "",
      "fields": {
        "from": "account_name",
        "to": "account_name",
        "quantity": "uint64"
      }
    },{
...

这部分包括from, toquantity等字段。这些字段都有对应的类型:account_nameuint64account_name 是一个用base32编码来表示uint64的内置类型。

{
  "types": [{
      "new_type_name": "account_name",
      "type": "name"
    }
  ],
...

上述types列表内,我们定义了一系列现有类型的别名。这里,我们把account_name定义为name的别名。

2.3 合约内容结构

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

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

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

参数说明

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

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

2.3.2 导出合约中的动作

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

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

#define EOSIO_DISPATCH( TYPE, MEMBERS ) ...

参数说明

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

2.3.3 范例

#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...

2.3.4 合约调用

2.3.4.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.3.4.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

2.3.4.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

2.3.4.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*> 一般情况下都是空的,也就是说,没有任何数据

2.3.5 合约基础类 eosio::contract

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

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

2.3.5.1 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 关键字来引用基础类的构造方法

2.3.5.2 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))

3 EOS 动作action

动作 ( action ) 是 EOS 合约基础组成单位。一个动作,在 C++ 合约类中,表示如下

  • 一个动作必须是 C++ 合约类的成员方法
  • 成为动作的成员方法,必须使用 [[eosio::action]] C++11 特性修饰,否则就是一个普通的类成员函数
  • 成为动作的成员方法,访问级别必须是公开的 public
  • 成为动作的成员方法,必须没有任何返回值,也不能返回任何值,也就是说,必须使用 void 作为返回值
  • 成为动作的成员方法,可以接受任意数量的参数
  • 成为动作的成员方法,必须在 EOSIO_DISPATCH 中导出

一个 EOS 链是由多个块 ( block ) 组成的:


image.png

每个块中包含多笔交易:


image.png

交易由action组成:


image.png

4 调试智能合约

现在user官方网站推荐的一个调试方法就是print,把信息打印出来。这个必须要我们搭建本地节点,因为如果没有本地节点,相当于你print打印在别人的节点上,你根本看不到这个打印信息是什么,所以说你必须要搭建一个本地节点。搭建本地节点后,你运行智能合约,就会看到print出来的输出结果。

4.1 debug.hpp

#include <eoslib/eos.hpp>
#include <eoslib/db.hpp>

namespace debug {
    struct foo {
        account_name from;
        account_name to;
        uint64_t amount;
        void print() const {
            eosio::print("Foo from ", eosio::name(from), " to ",eosio::name(to), " with amount ", amount, "\n");
        }
    };
}

4.2 debug.cpp

#include <debug.hpp>

extern "C" {

    void apply( uint64_t receiver, uint64_t code, uint64_t action ) {
        if (code == N(debug)) {
            eosio::print("Code is debug\n");
            if (action == N(foo)) {
                 eosio::print("Action is foo\n");
                debug::foo f = eosio::unpack_action_data<debug::foo>();
               if (f.amount >= 100) {
                    eosio::print("Amount is larger or equal than 100\n");
                } else {
                    eosio::print("Amount is smaller than 100\n");
                    eosio::print("Increase amount by 10\n");
                    f.amount += 10;
                    eosio::print(f);
                }
            }
        }
    }
} // extern "C"

4.3 编译运行

$ eosiocpp -o debug.wast debug.cpp
$ cleos set contract debug debug.wast debug.abi

实战

部署智能合约

$ cleos set contract eosio build/contracts/eosio.bios -p eosio
  • eosio是要部署的账号,就是你用哪个账号去部署智能合约;
  • build/contracts/eosio.bios表示的是路径;
  • eos.bios是生成一个智能合约的目录。

运行Token合约

相关文章

网友评论

      本文标题:EOS 合约基础教程 - 智能合约简介

      本文链接:https://www.haomeiwen.com/subject/crotdqtx.html