您正在查看: EOS-智能合约 分类下的文章

智能合约之 eosio.cdt 我们需要知道的那些事

eosio.cdt 在 1.2.x 和 1.3.x 的改动比较大, 虽然虚拟机是向后兼容的, 但是为了避免意外情况, 我们都会将陆续将合约代码升级。下面来介绍一下大致的改动

# 安装 eosio.cdt, 因为 llvm 库比较大, 所以执行 clone 的时候比较慢
$ git clone https://github.com/EOSIO/eosio.cdt.git
$ git submodule update --init --recursive
$ ./build.sh
$ sudo ./install.sh

1.2.x 和 1.3.x 的区别

eoslib C API

uint64_t 别名 account_name, permission_name, scope_name, table_name, action_name 全部移除, 新增 typedef capi_name uint64_t
symbol_name 别名移除,用 symbol_code 代替
移除 time , weight_type typedefs
移除 transaction_id_type, block_id_type typedefs
checksum160 -> capi_checksum160, checksum256 -> capi_checksum256, checksum512 -> capi_checksum512, public_key -> capi_public_key, signature -> capi_signature
移除掉未实现的 api : require_write_lock 和 require_read_lock

eoslib C++ API

移除 bytes typedefs
移除文件 eosiolib/types.hpp
移除文件 eosiolib/optional.hpp, 用 std::optional 代替
移除 eosiolib/core_symbol.hpp 文件, 以后合约需要自行声明 core_symbol
增加文件 eosiolib/name.hpp

eoslib/types.hpp

将 typedef eosio::extensions_types 移到 eosiolib/transaction.hpp
移除掉对 checksum struct 的 == 和 != 的重载
移除掉 API eosio::char_to_symbol, eosio::string_to_name, eosio::name_suffix, 都整合进了 name struct
移除掉宏命令 N(X), 重载运算符 ""_n ,例如 "foo"_n 或者 name("foo") 来转换成 name struct 类型
将 eosio::name struct 的定义 和 ""_n 运算符 移至 eosiolib/name.hpp
ps: 读者可以使用 #define N(X) name(#X) 来减少代码的改动了哈。

eosiolib/name.hpp

移除name 显式 隐式转换成 uint64_t
添加 enum class eosio::name::raw : uint64_t 用于从 name struct 隐式转换成 raw
添加 bool 类型转换,会返回 name struct 转化成 uint64_t 是否为 0
构造函数都用 constexpr, 确保 name struct 实例化时,都会给 value 赋初值
添加新的 constexpr 方法 eosio::name::length, eosio::name::suffix
添加 name struct 的比较函数

eosiolib/symbol.hpp

移除 eosio::symbol_type strcut , 用 eosio::symbol class 代替
添加 eosio::symbol_code struct
移除掉 eosio::string_to_symbol, eosio::is_valid_symbol, eosio::symbol_name_length 方法,都整合进了 symbol_code struct
移除宏命令#define S(P,X) ::eosio::string_to_symbol(P,#X), 直接实例化 symbol class eg: symbol(symbol_code("SYS"), 4) or symbol("SYS", 4)
重构 eosio::extended_symbol struct

eosiolib/asset.hpp

构造器现需要显式传入 quantity 和 symbol, 不再有默认值

eosiolib/contract.hpp

Rename EOSIO_ABO to EOSIO_DISPATCH, 更加明确的表达该宏指令的作用
根据 contract 的改动重构 EOSIO_DISPATCH

eosiolib/multi_index.hpp

索引不能直接用 name struct 需要使用 eosio::name::raw
multi_index code 不再使用 uint64_t, 使用 eosio::name

eosiolib/singleton.hpp

同 multi_index, 用 eosio::name 替代 uint64_t

eosiolib/action.hpp

添加 inline function: eosio::require_auth, eosio::has_auth, eosio::is_account
重构 eosio::permission_level, 用 eosio::name 替换 uint64_t
移除 宏命令 ACTION,整合到了 eosio.hpp
新增 action_wrapper struct, 它的出现,让我们对inline action 的使用更加便利化,相当于把 inline action 封装成一个 struct,直接实例化便可以发送一个 inline action, 下面会写例子。

eosiolib/permission.hpp

修改 eosio::check_transaction_authorization 参数类型 std::set to std::set , 使得能和 eosio 的 public_key 兼容。
eosio::check_permission_authorization 参数 account, permission 类型从 uint64_t 修改成 eosio::name

eosiolib/ignore.hpp

新增 ignore struct, 会让ABI 生成对应的类型, 但datastream 不会去序列化它
新增 ignore_wrapper, 方便其他合约调用声明的 action。

下面我们挑些改动比较大的地方来说下。

1.移除 uint64_t 的多数别名,只留下了一个 capi_name。

其中最大的地方当属 去掉了 uint64_t 的别名,需要用 name struct 来代替, 不应该用新的别名 capi_name。 不说了,笔者改代码改到想哭了。但为什么要做这个改动呢, 目前对于 account_name 等所使用的都是 implicit, 这意味着可能有一些 bad implicit。
Eg:

//@abi action
void hi(){
  name acc = name{10};
  print(acc==10);
}

我本意是要判断 两个 name struct 是否相等, 但是隐式转换使我直接比较整数 10 也能返回 true。
所以重构了 name struct,消除了这种风险。
这次的改动也变得比较偏面向对象思维, 像 eosio::char_to_symbol, eosio::string_to_name, eosio::name_suffix 都被整合进了 name struct 里面。
symbol 和 symbol_code 也被重构了。宏命令 S 被移除,不能直接用 S(4, SYS) 去声明一个 token symbol, 要用 symbol(symbol_code("SYS"), 4) or symbol("SYS", 4)去实例化一个symbol 对象, 也将一些针对 symbol 的函数整合进了 class。

2.重构了contract.hpp , EOSIO_ABI 修改成 EOSIO_DISPATCH

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

构造函数增加 code 和 ds 参数。增加 ds 参数是为了方便我们手动解析数据。 这跟后面要说到的 ignore struct 有比较大的关系。
这种改动也意味着我们重写 apply 的方式要改动.
Eg:


extern "C" {
  void apply( uint64_t receiver, uint64_t code, uint64_t action ) {
    auto self = receiver;
    // 拦截 失败的 deferred_trx
    if( code == "eosio"_n.value && action == "onerror"_n.value ) {
      const auto act_data = unpack_action_data<onerror>();
      auto sender = uint64_t( act_data.sender_id >> 64);
      if( sender == self){
        test bos(eosio::name(receiver), eosio::name(code),datastream<const char*>(nullptr, 0));
        bos.resend( act_data.unpack_sent_trx(), uint64_t( act_data.sender_id) );
      }
    // 拦截 eosio.token 的 EOS 转账操作
    } else if ( code == "eosio.token"_n.value ){
      test bos(eosio::name(receiver), eosio::name(code),datastream<const char*>(nullptr, 0));
      const auto t = unpack_action_data<transfer_args>();
      if(t.from.value != self && t.to.value == self){
        bos._transfer(t.from, t.to, t.quantity, t.memo);   
      }
    }else if ( code == self || action == "onerror"_n.value ) {
      switch( action ) {
        EOSIO_DISPATCH_HELPER( test, (hi))
      }
    }
  }
}

3. ignore struct , ignore_wrapper 和 action_wrapper 的使用

在 action 的参数加上 ignore struct, 会告诉虚拟机,不要解析此数据, 让自己手动解析。
使用 action_wrapper 把 hello:hi action 包装起来。
使用inline action 时,用 ignore_wrapper 表明该参数是一个 ignore 类型。
Eg:

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

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

   ACTION hi( name user, ignore<uint64_t>, ignore<std::string>) {
     print_f( "Hello % from hello", user );

     // 读取 ignore 数据。
     uint64_t test;
     _ds >> test;
     printui(test);
     std::string str;
     _ds >> str;
     prints_l(str.c_str(),str.size());
   }

   // 用 action_wrapper , 把 hello::hi action 包装起来
   using hi_action = action_wrapper<"hi"_n, &hello::hi>;

   ACTION inlineaction( name user, name inlinecode ){
     print_f( "Hello % from send_inline", user );
     // constructor takes two arguments (the code the contract is deployed on and the set of permissions)
     // 实例化 hi_action, 并进行调用。
     // inlinecode 参数及对应的 hi action 的合约账号。
     hello::hi_action hi(inlinecode, {_self, "active"_n});
     hi.send(user,ignore_wrapper(22),ignore_wrapper("asdfa"));
   }

};
EOSIO_DISPATCH( hello, (hi)(inlineaction) )

结论:两个版本的主要改动是消除隐式转换的风险。 也重构了一些模块的代码, 变得更加直观。 新增了 action_wrapper struct, 使得 inline action 的使用更加方便快捷。

转载自 https://segmentfault.com/a/1190000017092129#articleHeader15

EOS nodejs 与链上合约交互事例

链上部署合约部分代码

Table
struct [[eosio::table("users"), eosio::contract("some.game")]] user{
        capi_name account_name;
        uint64_t qualifications;
        auto primary_key() const { return account_name; }
    };
Action
ACTION addqualify(capi_name account);
void some_contract::addqualify(capi_name account){
    require_auth( _self.value );
    user_tables user_table(_self, _self.value);
    auto itr = user_table.find(account);
    eosio_assert(itr != user_table.end(), "account not find" );
    user_table.modify( itr, _self, [&]( auto& u ) {
        u.qualifications += 1;
    });
}

NodeJs 代码部分

EosService.js

const Eos = require('eosjs');
const config = require('../config/EosNetwork');

class EosService {
  constructor() {
    this.eosApi = Eos({
      httpEndpoint: `${config.network.protocol}://${config.network.host}${config.network.port ? ':' : ''}${config.network.port}`,
      chainId: config.network.chainId,
      keyProvider: config.self.privateKey,
      fetchConfiguration: {
        timeout: config.network.timeout,
      },
    });
    this.authorization = [`${config.contract.account}@active`];
    this.transactionOptions = { authorization: this.authorization };
    this.contractResult = null;
  }

  async init() {
    try {
      this.contractResult = await this.eosApi.contract(config.contract.code);
    } catch (e) {
      console.log(e); // TODO throw
    }
  }

  api() {
    return this.eosApi;
  }

  async pushAction(action, params) {
    if (this.contractResult === null) {
      await this.init();
    }
    try {
      this.contractResult[action](params, this.transactionOptions);
    } catch (e) {
      console.log(e); // TODO throw
    }
  }
}

module.exports = EosService;

chain.js

const EosService = require('../services/EosService');

class Chain {
  constructor() {
    this.eosService = new EosService();
  }

  async addQualify(account) {
    await this.eosService.pushAction('addqualify', account);
  }
}

module.exports = Chain;

game.js

const Chain = require('./chain');
const chain = new Chain();
....
await chain.addQualify(item.challenger); //此时,链上users 对应账户的qualifications将会+1

EOS 新建账户合约,转账到执行账户,创建账户

查看主要代码

//signdappplay.cpp

#include "signdappplay.hpp"

void signdappplay::transfer(account_name from, account_name to, asset quantity, string memo)
{
    if (from == _self || to != _self) {
        return;
    }

    // don't do anything on transfers from our reference account
    if (from == N(signdappplay)) {
      return;
    }

    eosio_assert(quantity.symbol == CORE_SYMBOL, "only core token allowed"); //string_to_symbol(4, "EOS")
    eosio_assert(quantity.is_valid(), "Invalid token transfer");
    eosio_assert(quantity.amount > 0, "Quantity must be positive");

    //memo "your_account_name-your_owner_public_key-your_active_public_key" 分隔符支持“-” “:” 空格
    //去掉memo前面的空格
    memo.erase(memo.begin(), find_if(memo.begin(), memo.end(), [](int ch) {
        return !isspace(ch);
    }));
    //去掉memo后面的空格
    memo.erase(find_if(memo.rbegin(), memo.rend(), [](int ch) {
        return !isspace(ch);
    }).base(), memo.end());

    eosio_assert(memo.length() == 120 || memo.length() == 66, "Malformed Memo (not right length)");
    const string account_string = memo.substr(0, 12);
    const account_name new_account_name = string_to_name(account_string.c_str());
    eosio_assert(memo[12] == ':' || memo[12] == '-' || memo[12] == ' ', "Malformed Memo [12] == : or - or space");

    const string owner_key_str = memo.substr(13, 53);
    string active_key_str;

    if(memo[66] == ':' || memo[66] == '-' || memo[66] == ' ') {
      // active key provided
      active_key_str = memo.substr(67, 53);
    } else {
      // active key is the same as owner
      active_key_str = owner_key_str;
    }

    const abieos::public_key owner_pubkey =
        abieos::string_to_public_key(owner_key_str);
    const abieos::public_key active_pubkey =
        abieos::string_to_public_key(active_key_str);

    array<char, 33> owner_pubkey_char;
    copy(owner_pubkey.data.begin(), owner_pubkey.data.end(),
         owner_pubkey_char.begin());

    array<char, 33> active_pubkey_char;
    copy(active_pubkey.data.begin(), active_pubkey.data.end(),
         active_pubkey_char.begin());

    key_weight owner_pubkey_weight = {
        .key = {0, owner_pubkey_char},
        .weight = 1
    };

    key_weight active_pubkey_weight = {
        .key = {0, owner_pubkey_char},
        .weight = 1
    };

    authority owner = authority{
        .threshold = 1,
        .keys = {owner_pubkey_weight},
        .accounts = {},
        .waits = {}
    };

    authority active = authority{
        .threshold = 1,
        .keys = {active_pubkey_weight},
        .accounts = {},
        .waits = {}
    };

    newaccount new_account = newaccount{
        .creator = _self,
        .name = new_account_name,
        .owner = owner,
        .active = active
    };

    asset stake_net(1000, CORE_SYMBOL);
    asset stake_cpu(1000, CORE_SYMBOL);
    asset buy_ram = quantity - stake_net - stake_cpu;
    eosio_assert(buy_ram.amount > 0, "Not enough balance to buy ram");

    // create account
    action(
            permission_level{ _self, N(active) },
            N(eosio),
            N(newaccount),
            new_account
    ).send();
    // buy ram
    action(
            permission_level{ _self, N(active)},
            N(eosio),
            N(buyram),
            make_tuple(_self, new_account_name, buy_ram)
    ).send();
    // delegate and transfer cpu and net
    action(
            permission_level{ _self, N(active)},
            N(eosio),
            N(delegatebw),
            make_tuple(_self, new_account_name, stake_net, stake_cpu, true)
    ).send();
}

开始部署

  • 下载代码
    git clone https://github.com/cppfuns/signdappplay.git
  • 编译代码
    //进入源码目录
    cd signdappplay/
    //生成abi文件
    eosiocpp -g signdappplay.abi signdappplay.cpp
    //生成wast文件
    eosiocpp -o signdappplay.wast signdappplay.cpp
  • 部署合约
    //提前创建好signdappplay 将合约部署到此账号
    cleos set contract signdappplay signdappplay/ -p signdappplay
  • signdappplay@active里添加signdappplay@eosio.code授权
    cleos set account permission signdappplay active '{"threshold": 1,"keys": [{"key": "EOS7nK2w6ZT8hKdrxr48xdt3CFj1MXpaDV6jagRHKayFhqJBX5GEf","weight": 1}],"accounts": [{"permission":{"actor":"signdappplay","permission":"eosio.code"},"weight":1}]}' owner -p signdappplay

开始转账创建账号

signdappplay账号转账,并按规则(新建账户名:owner公钥:active公钥)填写memo。(分隔符支持"-" ":"或者空格,如果owner公钥与active公钥一致,可以只填写一个,如实例)。

root@iZj6cbx3duprxf6dasczbpZ:~# cleos push action eosio.token transfer '["dapp.exec", "signdappplay","100.0000 EOS","dingtet12345:EOS8Hdw6vismBgoYPzfLhr2rtHrdsR3F8UYAL23LSc9wdV8eNhNH8"]' -p dapp.exec
executed transaction: e6d94f39de7e6508e06b469327eb63eb6100ce129deb152a484acc0399bae292  192 bytes  12152 us
#   eosio.token <= eosio.token::transfer        {"from":"dapp.exec","to":"signdappplay","quantity":"100.0000 EOS","memo":"dingtet12345:EOS8Hdw6vismB...
#     dapp.exec <= eosio.token::transfer        {"from":"dapp.exec","to":"signdappplay","quantity":"100.0000 EOS","memo":"dingtet12345:EOS8Hdw6vismB...
#  signdappplay <= eosio.token::transfer        {"from":"dapp.exec","to":"signdappplay","quantity":"100.0000 EOS","memo":"dingtet12345:EOS8Hdw6vismB...
#         eosio <= eosio::newaccount            {"creator":"signdappplay","name":"dingtet12345","owner":{"threshold":1,"keys":[{"key":"EOS8Hdw6vismB...
#         eosio <= eosio::buyram                {"payer":"signdappplay","receiver":"dingtet12345","quant":"99.8000 EOS"}
#   eosio.token <= eosio.token::transfer        {"from":"signdappplay","to":"eosio.ram","quantity":"99.3010 EOS","memo":"buy ram"}
#  signdappplay <= eosio.token::transfer        {"from":"signdappplay","to":"eosio.ram","quantity":"99.3010 EOS","memo":"buy ram"}
#     eosio.ram <= eosio.token::transfer        {"from":"signdappplay","to":"eosio.ram","quantity":"99.3010 EOS","memo":"buy ram"}
#   eosio.token <= eosio.token::transfer        {"from":"signdappplay","to":"eosio.ramfee","quantity":"0.4990 EOS","memo":"ram fee"}
#  signdappplay <= eosio.token::transfer        {"from":"signdappplay","to":"eosio.ramfee","quantity":"0.4990 EOS","memo":"ram fee"}
#  eosio.ramfee <= eosio.token::transfer        {"from":"signdappplay","to":"eosio.ramfee","quantity":"0.4990 EOS","memo":"ram fee"}
#         eosio <= eosio::delegatebw            {"from":"signdappplay","receiver":"dingtet12345","stake_net_quantity":"0.1000 EOS","stake_cpu_quanti...
#   eosio.token <= eosio.token::transfer        {"from":"signdappplay","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
#  signdappplay <= eosio.token::transfer        {"from":"signdappplay","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
#   eosio.stake <= eosio.token::transfer        {"from":"signdappplay","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
warning: transaction executed locally, but may not be confirmed by the network yet

账户向signdappplay账号转一定量的EOS,合约将会自动创建新账户,为CPU和NET各抵押0.1EOS,剩余EOS全部为新账户购买RAM(0.5%手续费)。

参考:

EOS delay transaction 延迟消息

交易可以设置delay_sec来指定延迟执行的时间,在合约中也提供了相应的API。

transaction out; //构造交易
out.actions.emplace_back(permission_level{_self, N(active)}, _self, N(test), std::make_tuple()); //将指定行为绑定到该交易上
out.delay_sec = 5; //设置延迟时间,单位为秒
out.send(_next_id(), _self, false); //发送交易,第一个参数为该次交易发送id,每次需不同。如果两个发送id相同,则视第三个参数replace_existing来定是覆盖还是直接失败。

另外需要注意的是可以在合约的某行为中发自身的延迟消息,于是可以达到定时任务的循环执行。
转载自(github

eosio::token.get_balance 合约内获取帐户代币余额

开发的DAPP需要获取另一个帐户的EOS余额,看遍了EOS源码eosiolib下的头文件,觉得只有currency.hpp最靠谱,尝试使用该头文件下的get_balance方法,但是一直没能成功,官方又没有文档,最终放弃。后来又尝试了eosio.token/eosio.token.hpp竟然让我成功了,这里介绍一下用法:
安装eos的时候并不会把eosio.token放到你的依赖库路径下,所以需要我们自己手动链接一下依赖库

ln -s /your-eos-path/contracts/eosio.token /usr/local/include/

用的时候需要额外带上一个头文件

#include <eosio.token/eosio.token.hpp>

用起来还是很简单的

auto eos_token = eosio::token(N(eosio.token)/* 合约账户名 */);
auto balance = eos_token.get_balance(owner/* 要查的账户名 */, symbol_type(S(4, EOS)).name());
print("eos balance: ", balance.amount);

需要注意的是get_balance的第二个参数为symbol_name,而不是symbol,所以需要先symbol_type(S(4, EOS))构造出symbol后调用name()方法获取。否则会一直提示你找不到对应的余额,主要原因是在于S(4, EOS)构造出的64位无符号数中最后八位代表的是精度,而余额表中的键值不需要用到精度,所以存的时候去掉了最后8位,所以如果直接传入S(4, EOS)会查不出来。

转载自:github