前面两篇文章我们分析了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