eosio.cdt心得

写在前面

从eos v1.3.0更新开始,eosio提倡使用eosio.cdt工具来编译智能合约,以提高智能合约的执行性能等。其中相较于旧的工具,eosio.cdt更新了不少语法。

eosio

定义的合约类,数据表,对外动作接口格式有所变化。
具体可以参考eosio.cdt/exmaples/abigen_test中的例子。

CONTRACT hello : public eosio::contract { ... };

ACTION hi (name user) { ... }

TABLE testtable {
   uint64_t owner;
   uint64_t third;
   uint64_t primary_key() const { return owner; }
};

capi_name取代了以前所有uint64_t的别名,如:

  • account_name
  • permission_name
  • scope_name
  • table_name
  • action_name

asset

实现完善了不少,使得代币相关的实现更加严谨。
默认初始化的asset内的symbol为空,值为零。在作asset比较和赋值的时候,一不小新就经常遇到符号错误。在智能合约编写过程中,应该指定所设计的代码的符号与精度。初始值的asset,可以写成这样:

#define XXX_TOKEN_SYMBOL 'SYS'
#define XXX_TOKEN_PRICISION 4
eosio::asset asswecan(0, eosio::symbol(XXX_TOKEN_SYMBOL, XXX_TOKEN_PRICISION));

打印asset时,因为目前仍唯有to_string之类的相关函数,只能使用asset::print来打印。

contract

新的contract构造函数定义变化挺大的。

// action的接受者
// action的所属域code
// action的参数字节流对象,即便有参数,一般情况下也为空.
contract(
	name receiver,
	name code,
	datastream<const char*> ds
)
	: _self(receiver),_code(code),_ds(ds) 
{}

datastream

数据流,在EOS中但凡涉及到trx或action的数据存取,都会用到datastream相关的接口。
从实现上来看,datastream也不会很复杂,从某种程度上来说,他就是重载了<<和>>操作符的handle类。
其中,重载了多个<<和>>操作符,主要为不同类型的transaction、action参数的序列化与反序列化提供支持。

template<typename DataStream, typename... Args>
DataStream& operator<<( DataStream& ds, const std::tuple<Args...>& t ) {
   boost::fusion::for_each( t, [&]( const auto& i ) {
       ds << i;
   });
   return ds;
}

template<typename DataStream, typename... Args>
DataStream& operator>>( DataStream& ds, std::tuple<Args...>& t ) {
   boost::fusion::for_each( t, [&]( auto& i ) {
       ds >> i;
   });
   return ds;
}

为什么对持久化存储的multi_index和singleton的数据结构进行拓展时,一不小心就会出现read错误?这里给出了答案。
也正因如此,EOS系统合约中,有不少用于持久化存储的数据结构预备了多个reserved字段。

inline bool read( char* d, size_t s ) {
  eosio_assert( size_t(_end - _pos) >= (size_t)s, "read" );
  memcpy( d, _pos, s );
  _pos += s;
  return true;
}

由衷地叹服eosio技术大佬们的c++功力。
从代码实现上禁止用户序列化指针与隔离原语类型模板定义。

template<typename DataStream, typename T, std::enable_if_t<_datastream_detail::is_pointer<T>()>* = nullptr>
DataStream& operator >> ( DataStream& ds, T ) {
   static_assert(!_datastream_detail::is_pointer<T>(), "Pointers should not be serialized" );
   return ds;
}

template<typename DataStream, typename T, std::size_t N,
         std::enable_if_t<!_datastream_detail::is_primitive<T>() &&
                          !_datastream_detail::is_pointer<T>()>* = nullptr>
DataStream& operator << ( DataStream& ds, const T (&v)[N] ) {
   ds << unsigned_int( N );
   for( uint32_t i = 0; i < N; ++i )
      ds << v[i];
   return ds;
}

dispatcher

action分发相关的实现都在这里。
eosio.cdt不再使用宏EOSIO_ABI了,改为使用EOSIO_DISPATCH。

#define EOSIO_DISPATCH_INTERNAL( r, OP, elem ) \
   case eosio::name( BOOST_PP_STRINGIZE(elem) ).value: \
      eosio::execute_action( eosio::name(receiver), eosio::name(code), &OP::elem ); \
      break;

// Helper macro for EOSIO_DISPATCH
#define EOSIO_DISPATCH_HELPER( TYPE,  MEMBERS ) \
   BOOST_PP_SEQ_FOR_EACH( EOSIO_DISPATCH_INTERNAL, TYPE, MEMBERS )

#define EOSIO_DISPATCH( TYPE, MEMBERS ) \
extern "C" { \
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
      // 只接受自己的action,其他人发来的receipt不会再接收
      if( code == receiver ) { \
         switch( action ) { \
            EOSIO_DISPATCH_HELPER( TYPE, MEMBERS ) \
         } \
         /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
      } \
   } \
} \

wabt虚拟机是如何分发action的呢?这里可以略窥其一二。

template<typename Contract, typename FirstAction>
bool dispatch( uint64_t code, uint64_t act ) {
   if( code == FirstAction::get_account() && FirstAction::get_name() == act ) {
      Contract().on( unpack_action_data<FirstAction>() );
      return true;
   }
   return false;
}

template<typename Contract, typename FirstAction, typename SecondAction, typename... Actions>
bool dispatch( uint64_t code, uint64_t act ) {
   if( code == FirstAction::get_account() && FirstAction::get_name() == act ) {
      Contract().on( unpack_action_data<FirstAction>() );
      return true;
   }
   return eosio::dispatch<Contract,SecondAction,Actions...>( code, act );
}

template<typename T, typename... Args>
bool execute_action( name self, name code, void (T::*func)(Args...)  ) {
   size_t size = action_data_size();

   //using malloc/free here potentially is not exception-safe, although WASM doesn't support exceptions
   constexpr size_t max_stack_buffer_size = 512;
   void* buffer = nullptr;
   if( size > 0 ) {
      buffer = max_stack_buffer_size < size ? malloc(size) : alloca(size);
      read_action_data( buffer, size );
   }
   
   std::tuple<std::decay_t<Args>...> args;
   datastream<const char*> ds((char*)buffer, size);
   ds >> args;
   
   T inst(self, code, ds);
   auto f2 = [&]( auto... a ){
      ((&inst)->*func)( a... );
   };

   // 此处会调用f2(args...)
   boost::mp11::tuple_apply( f2, args );
   if ( max_stack_buffer_size < size ) {
      free(buffer);
   }
   return true;
}

multi_index

template<name::raw TableName, typename T, typename... Indices>
class multi_index;

因为在定义multi_index时候,TableName需要使用常量表达式,所以现在一般的写法是

// N("testtab") 变为 "testtab"_n
typedef eosio::multi_index< "testtab"_n,  abitest::testtable > testtable_t;

singleton

也是实际上也是一个multi_index,是一个不对外声明的multi_index。在想要用到map, set, tuple等数据结构存储持久化数据,而又不必要对外公开该数据记录,笔者认为采用singleton未尝不是一个好的选择。

template<name::raw SingletonName, typename T>
class singleton
{
   constexpr static uint64_t pk_value = static_cast<uint64_t>(SingletonName);
   
   struct row {
      T value;
      
      uint64_t primary_key() const { return pk_value; }

      EOSLIB_SERIALIZE( row, (value) )
   };

   typedef eosio::multi_index<SingletonName, row> table;

   public:
   
      singleton( name code, uint64_t scope ) : _t( code, scope ) {}
	
	  // ... 
	
	  private:
	  	table _t;
}

transaction

接口相较于旧版本并无太大改动,只是其中的发送延迟交易的接口添加了一个字段。
sender_id 字面上意思是发送者的ID,但结合其他源码的分析,这实际上是需要指定一个唯一的交易ID,只是通常的做法是把这个交易ID指定为发送者的用户ID。
用户ID是uint64_t,sender_id为uint128_t,static_cast类型转换一下就可以了。

void send_deferred(const uint128_t& sender_id, capi_name payer, const char *serialized_transaction, size_t size, uint32_t replace_existing = 0);

class transaction : public transaction_header {
public:

   // ...

   void send(const uint128_t& sender_id, name payer, bool replace_existing = false) const {
      auto serialize = pack(*this);
      send_deferred(sender_id, payer.value, serialize.data(), serialize.size(), replace_existing);
   }

   // ...
};

你可能感兴趣的:(blockchain)