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 四 00:00>

Last updated: <2018-11-11 日 19:16>

Creator: Emacs 25.3.1 (Org mode 9.1.4)