EOSIO合约开发库

通过简单的源码分析,可以很清楚的看到EOSIO合约开发库在目录libraries下,各个库的功能如下:
注意:由于篇幅问题,只介绍最主要的,常用的

CDT: 总目录 |----libraries: 合约开发库总目录    |----boost: 经过裁剪的boost库    |----eosiolib: 链提供的合约API接口        |----capi: 访问链的宿主函数API接口        |----contracts: 宿主函数API的部分封装            |----action.hpp: 对action分装,调用内联action,使用此类            |----contract.hpp: 合约基类            |----multi_index.hpp: 合约中最重要的表结构的实现            |----permission.hpp: 权限封装,合约中权限的判断,详细参考多签名合约             |----singleton.hpp: 单例实现,借助multi_index实现,里面只有一条记录            |----transaction.hpp: 在EOSIO中可以通过合约给链发送指定于行的交易,该类实现了合约中交易基本的打包与发送        |----core: 合约开发基本类型            |----asset.hpp: 代币类型,代币符号类型            |----check.hpp: 对于合约断言的分装            |----crypto.hpp: 加密解密            |----datastream.hpp: 结构打包程序,对结构体pack与unpack            |----name.hpp: name基本类型封装            |----print.hpp: 日志打印封装            |----time.hpp: 实现对microseconds,time_point,block_timestamp封装    |----libc: c库源码    |----libc++: c++库源码

EOSIO合约中表的结构

讨论以multi_index结构来展开,singleton表也是依赖multi_index来实现的

定义

// 以token合约账号表为例,每个账户都独立建立一张代币余额表struct [[eosio::table]] account {    asset    balance; //账户余额    uint64_t primary_key()const { return balance.symbol.code().raw(); }  // 以代币符号ID作为主键};
typedef eosio::multi_index accounts;

代码展开如下:
在此句代码中,代码依次展开如下:

// “”_n运算符重载,在name.hpp中实现template inline constexpr eosio::name operator""_n() {   constexpr auto x = eosio::name{std::string_view{eosio::detail::to_const_char_arr::value, sizeof...(Str)}};   return x;}
/**  由于multi_index是模版类,在这里先对该模版进行了特化,特化参数指明了存储的对象类型与表名,表名必须是name类型  注意在这里,特化参数必须能隐式转化成int类型才可以,所以这里实现了对""_n重载,看过name类的实现,uint64与name是可以进行互转的*/ templateclass multi_index{    ...}

使用与访问

// 获取实例/** * get_self()   :合约账号  * owner.value  :代币账户*/accounts to_acnts( get_self(), owner.value );

上句代码中,直接调用multi_index构造函数,完成multi_index对象to_acnts的实例化

multi_index( name code, uint64_t scope ):_code(code),_scope(scope),_next_primary_key(unset_next_primary_key){}

至此,整个账户表被完成的确认出来

  • 合约账号(code) :确定表属于那个合约,跨合约是不能修改表数据的,但是可以访问读取
  • 表名(table) :确定表名
  • 小分类(scope) :确定记录的小分类,在此例中scope为账户名,故token相当于为每个账户都单独建立一张余额表
  • 主键(primary_key) :确定记录主键,和scope一起合作能确定表中一条唯一的记录
// 查询,添加,修改,删除auto to = to_acnts.find( value.symbol.code().raw() );if( to == to_acnts.end() ) {    to_acnts.emplace( ram_payer, [&]( auto& a ){        a.balance = value;    });} else {    to_acnts.modify( to, same_payer, [&]( auto& a ) {        a.balance += value;    });}if (to != to_acnts.end()){    to_acnts.erase( to );}

上述代码中,通过multi_inex类依次展开有点长,主要核心代码如下

// 向表中添加数据,具体步骤如下/**1 将对象obj,pack进入buffer,统计计算出数据包大小2 计算获取主键,凑足确定一条记录的三个要素,payer.value是付费账号,eosio是资源消耗型付费3 调用宿主函数db_store_i64将数据插入表*/templateconst_iterator emplace( name payer, Lambda&& constructor ) {    ...    datastream ds( (char*)buffer, size );    ds << obj;    auto pk = obj.primary_key();    i.__primary_itr = internal_use_do_not_use::db_store_i64( _scope, static_cast(TableName), payer.value, pk, buffer, size );    ...}

同理参考emplace函数,实现对modify,erase,find函数的分析可知,三个函数最终都调用了对应的宿主函数

  • modify:调用 db_update_i64 函数
  • erase:调用 db_remove_i64 函数
  • find:调用 db_find_i64 函数
// 遍历, 类似stl中list等的遍历,在multi_index中,实现了表的迭代器,这里可以参考stl的源码进行解读for ( auto itr = to_acnts.begin(); itr != to_acnts.end(); itr++ ) {    // do somethings}

总结

通过对EOSIO合约开发库和合约实例的分析,我们解读出一些新的知识点,对EOSIO合约的开发有了更加深刻的认识

  • 整个合约的代码格式,符合C++语法规范,结构清晰
  • 可以使用C++基本的苦函数,如strcmp,strlen等,也能使用boost一些常用开发库,如vector,list等
  • 合约最终都是通过导入进来宿主函数实现对链,数据库的访问的,对于什么是宿主函数,我们后面在讨论
  • 此文中我们只分析了合约开发中最常用的操作,其他的更加的丰富的操作需要读者自己去做分析