您正在查看: EOS-新手教程 分类下的文章

EOS合约内 string 转换为 capi_checksum256, capi_signature, capi_public_key

源代码

#include <eosiolib/crypto.h>
#include <eosiolib/asset.hpp>
#include <eosiolib/eosio.hpp>
#include <eosiolib/singleton.hpp>
#include <eosiolib/time.hpp>
#include <eosiolib/types.h>

using namespace eosio;
using namespace std;

string uint64_string(uint64_t input) {
    string result;
    uint8_t base = 10;
    do {
        char c = input % base;
        input /= base;
        if (c < 10)
            c += '0';
        else
            c += 'A' - 10;
        result = c + result;
    } while (input);
    return result;
}

uint8_t from_hex(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    eosio_assert(false, "Invalid hex character");
    return 0;
}

size_t from_hex(const string& hex_str, char* out_data, size_t out_data_len) {
    auto i = hex_str.begin();
    uint8_t* out_pos = (uint8_t*)out_data;
    uint8_t* out_end = out_pos + out_data_len;
    while (i != hex_str.end() && out_end != out_pos) {
        *out_pos = from_hex((char)(*i)) << 4;
        ++i;
        if (i != hex_str.end()) {
            *out_pos |= from_hex((char)(*i));
            ++i;
        }
        ++out_pos;
    }
    return out_pos - (uint8_t*)out_data;
}

string to_hex(const char* d, uint32_t s) {
    std::string r;
    const char* to_hex = "0123456789abcdef";
    uint8_t* c = (uint8_t*)d;
    for (uint32_t i = 0; i < s; ++i)
        (r += to_hex[(c[i] >> 4)]) += to_hex[(c[i] & 0x0f)];
    return r;
}

string sha256_to_hex(const capi_checksum256& sha256) {
    return to_hex((char*)sha256.hash, sizeof(sha256.hash));
}

string sha1_to_hex(const capi_checksum160& sha1) {
    return to_hex((char*)sha1.hash, sizeof(sha1.hash));
}

// copied from boost https://www.boost.org/
template <class T>
inline void hash_combine(std::size_t& seed, const T& v) {
    std::hash<T> hasher;
    seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

uint64_t uint64_hash(const string& hash) {
    return std::hash<string>{}(hash);
}

uint64_t uint64_hash(const capi_checksum256& hash) {
    return uint64_hash(sha256_to_hex(hash));
}

capi_checksum256 hex_to_sha256(const string& hex_str) {
    eosio_assert(hex_str.length() == 64, "invalid sha256");
    capi_checksum256 checksum;
    from_hex(hex_str, (char*)checksum.hash, sizeof(checksum.hash));
    return checksum;
}

capi_checksum160 hex_to_sha1(const string& hex_str) {
    eosio_assert(hex_str.length() == 40, "invalid sha1");
    capi_checksum160 checksum;
    from_hex(hex_str, (char*)checksum.hash, sizeof(checksum.hash));
    return checksum;
}

size_t sub2sep(const string& input,
               string* output,
               const char& separator,
               const size_t& first_pos = 0,
               const bool& required = false) {
    eosio_assert(first_pos != string::npos, "invalid first pos");
    auto pos = input.find(separator, first_pos);
    if (pos == string::npos) {
        eosio_assert(!required, "parse memo error");
        return string::npos;
    }
    *output = input.substr(first_pos, pos - first_pos);
    return pos;
}

// Copied from https://github.com/bitcoin/bitcoin

/** All alphanumeric characters except for "0", "I", "O", and "l" */
static const char* pszBase58 =
    "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
static const int8_t mapBase58[256] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0,  1,  2,  3,  4,  5,  6,  7,
    8,  -1, -1, -1, -1, -1, -1, -1, 9,  10, 11, 12, 13, 14, 15, 16, -1, 17, 18,
    19, 20, 21, -1, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1,
    -1, -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, 47, 48,
    49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1,
};

bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch) {
    // Skip leading spaces.
    while (*psz && isspace(*psz)) psz++;
    // Skip and count leading '1's.
    int zeroes = 0;
    int length = 0;
    while (*psz == '1') {
        zeroes++;
        psz++;
    }
    // Allocate enough space in big-endian base256 representation.
    int size = strlen(psz) * 733 / 1000 + 1;  // log(58) / log(256), rounded up.
    std::vector<unsigned char> b256(size);
    // Process the characters.
    static_assert(
        sizeof(mapBase58) / sizeof(mapBase58[0]) == 256,
        "mapBase58.size() should be 256");  // guarantee not out of range
    while (*psz && !isspace(*psz)) {
        // Decode base58 character
        int carry = mapBase58[(uint8_t)*psz];
        if (carry == -1)  // Invalid b58 character
            return false;
        int i = 0;
        for (std::vector<unsigned char>::reverse_iterator it = b256.rbegin();
             (carry != 0 || i < length) && (it != b256.rend());
             ++it, ++i) {
            carry += 58 * (*it);
            *it = carry % 256;
            carry /= 256;
        }
        assert(carry == 0);
        length = i;
        psz++;
    }
    // Skip trailing spaces.
    while (isspace(*psz)) psz++;
    if (*psz != 0) return false;
    // Skip leading zeroes in b256.
    std::vector<unsigned char>::iterator it = b256.begin() + (size - length);
    while (it != b256.end() && *it == 0) it++;
    // Copy result into output vector.
    vch.reserve(zeroes + (b256.end() - it));
    vch.assign(zeroes, 0x00);
    while (it != b256.end()) vch.push_back(*(it++));
    return true;
}

bool decode_base58(const string& str, vector<unsigned char>& vch) {
    return DecodeBase58(str.c_str(), vch);
}

// Copied from https://github.com/bitcoin/bitcoin

capi_signature str_to_sig(const string& sig, const bool& checksumming = true) {
    const auto pivot = sig.find('_');
    eosio_assert(pivot != string::npos, "No delimiter in signature");
    const auto prefix_str = sig.substr(0, pivot);
    eosio_assert(prefix_str == "SIG", "Signature Key has invalid prefix");
    const auto next_pivot = sig.find('_', pivot + 1);
    eosio_assert(next_pivot != string::npos, "No curve in signature");
    const auto curve = sig.substr(pivot + 1, next_pivot - pivot - 1);
    eosio_assert(curve == "K1" || curve == "R1", "Incorrect curve");
    const bool k1 = curve == "K1";
    auto data_str = sig.substr(next_pivot + 1);
    eosio_assert(!data_str.empty(), "Signature has no data");
    vector<unsigned char> vch;

    eosio_assert(decode_base58(data_str, vch), "Decode signature failed");

    eosio_assert(vch.size() == 69, "Invalid signature");

    if (checksumming) {
        array<unsigned char, 67> check_data;
        copy_n(vch.begin(), 65, check_data.begin());
        check_data[65] = k1 ? 'K' : 'R';
        check_data[66] = '1';

        capi_checksum160 check_sig;
        ripemd160(reinterpret_cast<char*>(check_data.data()), 67, &check_sig);

        eosio_assert(memcmp(&check_sig.hash, &vch.end()[-4], 4) == 0, "Signature checksum mismatch");
    }

    capi_signature _sig;
    unsigned int type = k1 ? 0 : 1;
    _sig.data[0] = (uint8_t)type;
    for (int i = 1; i < sizeof(_sig.data); i++) {
        _sig.data[i] = vch[i - 1];
    }
    return _sig;
}

capi_public_key str_to_pub(const string& pubkey, const bool& checksumming = true) {
    string pubkey_prefix("EOS");
    auto base58substr = pubkey.substr(pubkey_prefix.length());
    vector<unsigned char> vch;
    eosio_assert(decode_base58(base58substr, vch), "Decode public key failed");
    eosio_assert(vch.size() == 37, "Invalid public key");
    if (checksumming) {

        array<unsigned char, 33> pubkey_data;
        copy_n(vch.begin(), 33, pubkey_data.begin());

        capi_checksum160 check_pubkey;
        ripemd160(reinterpret_cast<char*>(pubkey_data.data()), 33, &check_pubkey);

        eosio_assert(memcmp(&check_pubkey, &vch.end()[-4], 4) == 0, "Public key checksum mismatch");
    }
    capi_public_key _pub_key;
    unsigned int type = 0;
    _pub_key.data[0] = (char)type;
    for (int i = 1; i < sizeof(_pub_key.data); i++) {
        _pub_key.data[i] = vch[i - 1];
    }
    return _pub_key;
}

测试

ACTION test()
{
        capi_checksum256 digest = hex_to_sha256("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08");
        capi_signature sig =  str_to_sig("SIG_K1_KkSbKuDSV7x87FeexJ3goinHsd3MhPBCH91MRqhyS3Z7H1v4HtUZoJc6AkgYWW5mEan7UbdmDAzDpCzUwheDPxRxtzuD8s");
        capi_public_key pk = str_to_pub("EOS62M5kVouCEU31xP736Txb4pe82FoncprqevPuagE6boCLxwsC8");

        assert_recover_key(&digest, (const char *)&sig, sizeof(sig), (const char *)&pk, sizeof(pk));
        print("VALID");
}

参考

eosio.cdt 中使用二级索引查询的例子

合约table 定义

struct [[eosio::table("members"), eosio::contract("shadow.zion")]] member_table{
        uint64_t id;
        capi_name username;
        uint64_t group_id;

        uint64_t primary_key() const { return id; }
        uint64_t get_sub_key() const { return username; }
        uint64_t get_third_key() const { return group_id; }
    };

table 创建

typedef eosio::multi_index<"members"_n, member_table, eosio::indexed_by<"bysubkey"_n, eosio::const_mem_fun<member_table, uint64_t, &member_table::get_sub_key>>, eosio::indexed_by<"bythirdkey"_n, eosio::const_mem_fun<member_table, uint64_t, &member_table::get_third_key>>> member_tables;

table 使用

member_tables member_table( _self, _self.value);
    auto third_index = member_table.get_index<"bythirdkey"_n>();
    auto member_itr = third_index.find( group_id );
    eosio_assert( member_itr != third_index.end(), "121" );

    while(member_itr != third_index.end()){
        if(member_itr->username == account && member_itr->group_id == group_id){
            break;
        }
        member_itr++;
    }

//增,改

   if(member_itr == third_index.end()){
            member_table.emplace( _self, [&]( auto& m ) {
                m.id = member_table.available_primary_key();
                m.username = other_account;
                m.group_id = group_id;
                m.group_weight = group_weight;
            });
        }
        else{
            third_index.modify( member_itr, _self, [&]( auto& m ) {
                m.group_weight = member_itr->group_weight +  group_weight;
            });
        }

EOS transfer memo 格式化

由于一些场景比如转账后做某些操作,需要利用memo 传递多个参数信息
一般会以各种分隔符,如‘-’,‘|’,‘#’,空格等,所以需要个简单的方法格式化下

#ifndef __UTILS_HPP__
#define __UTILS_HPP__

#include <string>
#include <vector>

using namespace std;

void split(const string& s, char c,
           vector<string>& v) {
   string::size_type i = 0;
   string::size_type j = s.find(c);

   while (j != string::npos) {
      v.push_back(s.substr(i, j-i));
      i = ++j;
      j = s.find(c, j);

      if (j == string::npos)
         v.push_back(s.substr(i, s.length()));
   }
} 
#endif

比如memo 格式为“id:value”
测试代码如下

//去掉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());

vector<string> v;
split(memo, ':', v);
eosio_assert(v.size() == 2, "size need 2");
uint32_t id = std::strtoul(v[0].c_str(), NULL, 10);
uint64_t value = std::strtoull(v[1].c_str(), NULL, 10);

合约内计算EOS换算RAM

复制源代码

eosio.contracts 项目中,复制eosio.contracts/eosio.system/src/exchange_state.cppeosio.contracts/eosio.system/include/eosio.system/exchange_state.hpp两个文件到你合约项目中。
并在你的合约中include

#include "common/exchange_state.hpp"
#include "common/exchange_state.cpp"

计算代码

uint64_t bcskill_contract::exchange_ram(eosio::asset quantity){
    rammarket market(k_eosio, k_eosio.value);
    auto it = market.find(k_ramcore_sym.raw());
    eosio_assert(it != market.end(), "125");
    auto tmp = *it;

    /* Convert EOS amount to ram bytes */
    auto out_ram_bytes = tmp.convert(quantity, k_ram_sym);
    return out_ram_bytes.amount;
}

智能合约之 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