EOS Smart Contract

Table of Contents

1. EOS 智能合约

An EOSIO Smart Contract is software registered on the blockchain and executed on EOSIO nodes, that implements the semantics of a "contract" whose ledger of action requests are being stored on the blockchain. The Smart Contract defines the interface (actions, parameters, data structures) and the code that implements the interface.

智能合约是注册在链上的代码,用户通过智能合约对链上数据进行修改。比如,从账号 account1 转账 7 个 SYS(在测试网络中系统币的名称默认是 SYS,而不是 EOS)到账号 account2,可以通过执行智能合约 eosio.token 中的 transfer 方法来实现:

$ cleos push action eosio.token transfer '["account1", "account2", "7.0000 SYS", "transfer test"]' -p account1@active

2. 编写智能合约

2.1. 智能合约基本结构

每个 EOS 智能合约都必需实现 apply( uint64_t receiver, uint64_t code, uint64_t action ) 函数,它可以看作是执行智能合约的入口函数,它会根据用户指定的 action 名字调用真正的实现函数。 下面是一段演示 apply 函数的伪代码:

void foo() {
}

void bar() {
}

// 用户执行智能合约时,最终会调用到apply函数。apply函数根据用户指定的action名字执行真正的函数
void apply( uint64_t receiver, uint64_t code, uint64_t action ) {
    // ......

    if (action == N(foo)) {        // 如果参数action为字符串foo对应数字编码,则调用foo函数
        foo();
    } else if (action == N(bar)) { // 如果参数action为字符串bar对应数字编码,则调用bar函数
        bar();
    } else {
        print("Action name is invalid.");
    }
}

编译智能合约时,我们不用自己实现 apply ,直接使用现成的宏 EOSIO_ABI 即可,它会展开为 apply 函数,其实现如下:

// 源码摘自 eosiolib/dispatcher.hpp
#define  EOSIO_ABI( TYPE, MEMBERS ) \
extern "C" { \
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
      auto self = receiver; \
      if( action == N(onerror)) { \
         /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
         eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
      } \
      if( code == self || action == N(onerror) ) { \
         TYPE thiscontract( self ); \
         switch( action ) { \
            EOSIO_API( TYPE, MEMBERS ) \
         } \
         /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
      } \
   } \
} \
 /// @}  dispatcher

2.2. 智能合约实例

下面演示了 Hello World 智能合约:

#include <eosiolib/eosio.hpp>
using namespace eosio;

class hello : public eosio::contract {              // 继承eosio::contract
  public:
      using contract::contract;

      /// @abi action
      void hi( account_name user ) {
         print( "Hello, ", name{user} );
      }
};

EOSIO_ABI( hello, (hi) )                            // 使用宏EOSIO_ABI声明你想实现的智能合约方法

2.3. 编译智能合约

使用工具 eosiocpp 可以编译智能合约。如:

$ eosiocpp -o helloworld.wast helloworld.cpp  # 会生成helloworld.wast和helloworld.wasm
$ eosiocpp -g helloworld.abi helloworld.cpp   # 会生成helloworld.abi

2.4. 发布智能合约

当智能合约缩写完成后,使用 cleos set contract 可以发布智能合约到 eos 链。如:

$ cleos set contract account11 helloworld/ -p account11@active
##                       ^           ^
##                       |           |
##                 合约发布者   合约代码所在目录

注:使用某个账户作为合约发布者后,该账户就拥有了此合约的操作权。对该合约的调用不用写合约名字,直接使用该账户加上合约内部 action 函数即可。 一个账户可以发布个合约,但是以最后一次有效,每次部署新的合约会覆盖原有的合约代码。

2.5. 执行智能合约

使用 cleos push action 可以执行智能合约。如:

$ cleos push action account11  hi    '["user1"]' -p account11@active
##                     ^       ^           ^
##                     |       |           |
##              合约发布者  合约中函数名  参数

2.6. 内置的系统智能合约

在 eos 源码的 contracts/子目录中,内置了一些系统智能合约。如:

$ ls contracts/eosio.          # 再按tab补全下列出下面内容
eosio.bios/   eosio.msig/   eosio.sudo/   eosio.system/ eosio.token/

其中,eosio.bios 实现了 setcode, setabi 等等 action,它们是发布智能合约时所调用的 action;eosio.msig 是支持多签名时需要的;eosio.token 是发行代币所需要的;eosio.system 是限制资源使用所需要的(部署 eosio.system 合约后,无法再使用 cleos create account 创建帐号了,而应该使用 cleos system newaccount 创建帐号)。

参考:EOS特殊智能合约eosio

3. Multi Index Table (Persistence API)

Multi index tables support create, read, update and delete (CRUD) operations, something which the blockchain doesn't (it only supports create and read.)

Multi Index Tables provide a fast to access data store and are a practical way to store data for use in your smart contract. The blockchain records the transactions, but you should use Multi Index Tables to store application data.

参考:Adding Multi Index Tables to your smart contract

3.1. Multi Index Table 使用实例

Multi Index Table 的结构如下:

see: contracts/eosiolib/db.h

 *  EOSIO organizes data according to the following broad structure:
 *  - **code** - the account name which has write permission
 *     - **scope** - an area where the data is stored
 *        - **table** - a name for the table that is being stored
 *           - **record** - a row in the table

要定位一个 table,需要 3 个参数:code(合约的发布者),scope(自定义的一个名字,如果你的合约中只有一个 table,则往往把它设置为合约的发布者),table(用户自定义的 table 名字)。下面是简单例子:

/// @abi table
struct mystruct
{
   uint64_t     key;
   uint64_t     secondid;
   std::string  name;
   std::string  account;

   uint64_t primary_key() const { return key; } // getter for primary key
   uint64_t by_id() const {return secondid; }   // getter for additional key
};

typedef eosio::multi_index<N(tblname), mystruct> datastore;  // 定义一个datastore类型
                    //          ^
                    //          |
                    //   这里指定table名字

// ...... //

datastore ds;
ds(_self, _self);
 //   ^      ^
 //   |      |
 //  code    |
 //        scope

参考:Exploring the EOS multi_index database

3.2. 获取表中数据

EOS 智能合约中 action 实现函数的返回值必需是 void ,这意味着你无法通过实现一个 action 函数来返回 Multi Index Table 中的数据。

如果需要在前端读取 Multi Index Table 中的数据,可以使用 cleos get table CONTRACT_NAME SCOPE_NAME TABLE_NAME ,或者 get_table_rows RPC API。

cleos get table 使用实例:

$ cleos -u 'http://193.93.219.219:8888' get table eosio.token tester444444 accounts
{
  "rows": [{
      "balance": "40000.0000 EOS"
    },{
      "balance": "40000.0000 JUNGLE"
    }
  ],
  "more": false
}

get_table_rows RPC API 使用实例:

$ curl --request POST -d '{"code":"eosio.token", "scope": "tester444444", "table": "accounts", "json":"true"}' --url http://193.93.219.219:8888/v1/chain/get_table_rows
{"rows":[
         {"balance":"40000.0000 EOS"},
         {"balance":"40000.0000 JUNGLE"}
        ],
 "more":false}

4. 内置的掷骰子游戏——dice 智能合约

在 EOS 源码 contracts/dice 目录中,实现了一个掷骰子智能合约。

文件 contracts/dice/README.md 中有它的玩法说明,大概过程是两个玩家都往合约账号充钱(对应函数 deposit),合约记录下各玩家所充值的数目;玩家可以拿出部分钱和一个随机数的哈希(记为 a)进行下注(对应函数 offerbet),另一个玩家也拿出相同的钱和一个随机数的哈希(记为 b)进行下注;如果开盅(对应函数 reveal)时,计算 concat(a,b)的哈希,按照这个结果哈希值的第 0 个字节和第 1 个字节的大小关系决定谁赢得游戏。此外,玩家可以从合约账号中取回自己的钱(对应函数 withdraw)。

参考:https://blog.csdn.net/zhongdahong/article/details/80137953

Author: cig01

Created: <2018-08-09 Thu>

Last updated: <2018-11-11 Sun>

Creator: Emacs 27.1 (Org mode 9.4)