EOS智能合约开发系列(15): N与name

前面两篇文章我们分析了eosio.msig合约,中间有些内容因为篇幅没有仔细讲解,今天开始打算把一些知识点攻克一下,有些比较难的知识点,自然会详细介绍;有些呢,则看起来比较简单,然而深入进去之后,确可以加深对EOS系统的理解。今天先介绍第二个知识点:N与name。

引言

在EOS智能合约中,你应该见到过类似这样的语句:

require_auth( N(user));

结合上一篇的内容,这句话的意思是指,检查:在当前action的已授权的许可列表中,其中是否存在user账号,如果存在,则什么也不做;如果不存在,则抛出异常。

还记得我们以前写的hello合约吗?里面的hi 这个action的处理函数(handler)大致是这样的:

      /// @abi action 
      void hi( account_name user ) {
         require_auth( user );
         print( "Hi, ", name{user} );
      }

那么这个require_auth( user );与前面的require_auth( N(user) );有什么区别呢?下一行中的name{user}又是什么意思呢?
我们下面一一讲解。

N的用法

在EOS合约中,你会经常看到N的用法,它是什么东西呢?实际上,它是一个宏定义:


   /**
    * Used to generate a compile time uint64_t from the base32 encoded string interpretation of X
    *
    * @brief Used to generate a compile time uint64_t from the base32 encoded string interpretation of X
    * @param X - String representation of the name
    * @return constexpr uint64_t - 64-bit unsigned integer representation of the name
    * @ingroup types
    */

   #define N(X) ::eosio::string_to_name(#X)

这是直接从eos源码中的types.h文件里,摘取出来的。可以看到N(x)等同于::eosio::string_to_name(#X)。其中#X代表把X转化它的字面量的字符串形式,如果X是user,那么#X就等于"user"。这是C语言中宏定义的一种语法。

eosio::string_to_name是一个函数,它可以把一个字符串转化为一个数字。我们来看下它的定义:


   /**
    *  Converts a base32 string to a uint64_t. This is a constexpr so that
    *  this method can be used in template arguments as well.
    *
    *  @brief Converts a base32 string to a uint64_t.
    *  @param str - String representation of the name
    *  @return constexpr uint64_t - 64-bit unsigned integer representation of the name
    *  @ingroup types
    */
   static constexpr uint64_t string_to_name( const char* str ) {

      uint32_t len = 0;
      while( str[len] ) ++len;

      uint64_t value = 0;

      for( uint32_t i = 0; i <= 12; ++i ) {
         uint64_t c = 0;
         if( i < len && i <= 12 ) c = uint64_t(char_to_symbol( str[i] ));

         if( i < 12 ) {
            c &= 0x1f;
            c <<= 64-5*(i+1);
         }
         else {
            c &= 0x0f;
         }

         value |= c;
      }

      return value;
   }

具体在for循环里的运算细节可以不必深究,它实际上是把传进来的字符串str当作一个base32编码的字符串,然后把它转化为对应的整数形式(uint64_t类型的)。

在EOS系统中,几乎所有的标识性的name,都是以和base32编码可以互相转换的整数类型(具体是uint64_t)存在的。比如,账户名, 许可名,table的名字,多重签名提案的名字等等。

为了保证这些名字能够以base32位编码,并且能被一个对应的unint64_t整数唯一标识,规定这些名字要满足如下要求:长度不超过12个字符,并且每个字符必须是下列之一:.12345abcdefghijklmnopqrstuvwxyz

现在你应该明白,为什么账户名要有这些限制了吧?其实不止账户名,也不止上面的这些名字,哪怕是你将来自己的智能合约定义了一个可以标识某种持久化数据的名字,最好也遵守这个约定,这样可以确保它能转化位uint64_t整型。
为什么非要能转化为uint64_t呢?因为EOS系统的设计如此。能转化uint64_t是一个在效率和易用性上面的平衡,首先这12位字符能够表示3212次方个名字,也就是260 次方,这是个天文数字,足够使用;同时不超过64位bit;其次,uint64_t数值,在当代的64位CPU上,一个时钟周期就可以运算一次。如果是uint128_t,则需要拆分成一个个64位的部分去运算,就慢了很多;如果uint32_t呢,不足64位,也同样需要一个时钟周期。所以64位刚刚好。

我们回到前面我们抛出的哪个问题:

require_auth( user );require_auth( N(user) );有什么区别?

require_auth(user)中,user是一个变量,它的值可能是bob也可以是carl,也可能是一个名为user"user"账户,以user变量的值而定。

我们看下hi这个action 处理器的函数声明:

void hi( account_name user );

eos在调用action handler的时候,传进来的account_name类型,实际上就是uint64_t类型,如果user变量所代表的账户的是bob,那么user本身并不是"bob"字符串,而是它对应的uint64_t类型的值。

如果这里的user是bob账户,那么require_auth( user );就是在检验:

bob账户是否存在于当前action的已授权许可列表中。

require_auth( N(user) );是什么意思呢?N(user)user转化成了"user"字符串代表的整数形式,那么require_auth( N(user) );的意思就是在校验:

名为user的账户是否存在于当前action的已授权许可列表中。

name

name是一种类型,它的源码是这样的:

/**
    *  Wraps a uint64_t to ensure it is only passed to methods that expect a Name and
    *  that no mathematical operations occur.  It also enables specialization of print
    *  so that it is printed as a base32 string.
    *
    *  @brief wraps a uint64_t to ensure it is only passed to methods that expect a Name
    *  @ingroup types
    */
   struct name {
      /**
       * Conversion Operator to convert name to uint64_t
       *
       * @brief Conversion Operator
       * @return uint64_t - Converted result
       */
      operator uint64_t()const { return value; }

      // keep in sync with name::operator string() in eosio source code definition for name
      std::string to_string() const {
         static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz";

         std::string str(13,'.');

         uint64_t tmp = value;
         for( uint32_t i = 0; i <= 12; ++i ) {
            char c = charmap[tmp & (i == 0 ? 0x0f : 0x1f)];
            str[12-i] = c;
            tmp >>= (i == 0 ? 4 : 5);
         }

         trim_right_dots( str );
         return str;
      }

      /**
       * Equality Operator for name
       *
       * @brief Equality Operator for name
       * @param a - First data to be compared
       * @param b - Second data to be compared
       * @return true - if equal
       * @return false - if unequal
       */
      friend bool operator==( const name& a, const name& b ) { return a.value == b.value; }

      /**
       * Internal Representation of the account name
       *
       * @brief Internal Representation of the account name
       */
      account_name value = 0;

   private:
      static void trim_right_dots(std::string& str ) {
         const auto last = str.find_last_not_of('.');
         if (last != std::string::npos)
            str = str.substr(0, last + 1);
      }
   };

} // namespace eosio

就像name类的注释中说的那样,它的作用是把一个uint64_t的值转化位base32编码形式,作用刚好和N相反。

再看下上面我们看过的hi 函数:

      /// @abi action 
      void hi( account_name user ) {
         require_auth( user );
         print( "Hi, ", name{user} );
      }

name{user},这里user是个某个账户的整数形式,它原来的字符串形式可能是"bob",也可能是其他的名字。我们假设是"bob"吧,那么name{user}就是"bob"了。
这里在print函数里面,我们就能打印出bob了,如果我们直接用print( "Hi, ",user );,那么打印出来的user部分,就是一个让人摸不着头脑的数字了。

所以,name这个类对于我们打印一个name很有作用。关于name{user}这句话的语法含义,实际上是调用了name类的默认构造函数,生成了一个name对象,print函数在打印的时候,会自动调用name对象的to_string方法,得到字符串并打印出来。因为本文着重于EOS,所以关于C++语法含义,本文因为篇幅问题,只能点到为止,更多C++的信息,建议购买相关书籍或者网上搜索相关资料学习。


简介:不羁,一名程序员;专研EOS技术,玩转EOS智能合约开发。
微信公众号:know_it_well
知识星球地址:https://t.zsxq.com/QvbuzFM

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容