ryan

ERC721A源码解读

26 次阅读

今天介绍一下ERC721A的源码.在弄懂源码之前,首先要明白二进制的左移右移运算以及加减法操作.这是ERC721的基础.

二进制加法

需要说明的是,在计算机中做二进制数运算时,一定要明确是在多少位的整型前提下进行的,这样才能够正确处理位数溢出的问题

我们以32位,计算5+3为例,5的二进制表示为 0000… 0101,3的二进制表示为0000…0011,然后逐位二进制加法,0000…0101+0000…0011=0000…1000,然后0000…转为10进制为2^3=8.

二进制减法

减法和加法一样,只过不是把A-B变成A+(-B).-B是通过取反然后加1(补码)得到.例如计算5-3,5的二进制表示为 0000… 0101.将3取反(0变成1,1变为0),得到1111…1100,然后加1,得到1111…1101,最后逐位进行计算0000… 0101+1111…1101=10000…0010,最高位溢出的将被舍弃.所以最终果是0000…0010,转为十进制是2.

位移操作

  • 左移

所有位向左移,低位补0.左移n位,相当于乘以2的n次方(只有在不溢出的情况下成立).


x << n = x *(2^n)

  • 右移

右移 n 位,相当于除以 2 的 n 次方,向下取整(保留符号)


x >> n ≡ floor(x / 2^n)

// 以正数为例
  0000 1010   (十进制 10)
>> 1
= 0000 0101   (十进制 5)

有了上述的基本概念,我们解释一下ERC721A中合约的源码.例如:

  
uint256 private constant _BITMASK_NEXT_INITIALIZED = 1 << 225;

_BITMASK_NEXT_INITIALIZED意思是左移225位(2^225),主要用于掩码操作.

  
uint256 private constant _BITMASK_ADDRESS = (1 << 160) - 1;

_BITMASK_ADDRESS,左移160位,为1000..000(160个0),然后减1,-1表示为1111...1111,_BITMASK_ADDRESS实际可表示为111...111(161个1).

我们再来看看ERC721A是怎样把多个变量拼接到同一个uint256中.如下代码:

  
// Mapping from token ID to ownership details
    // An empty struct value does not necessarily mean the token is unowned.
    // See {_packedOwnershipOf} implementation for details.
    //
    // Bits Layout:
    // - [0..159]   `addr`
    // - [160..223] `startTimestamp`
    // - [224]      `burned`
    // - [225]      `nextInitialized`
    // - [232..255] `extraData`
    mapping(uint256 => uint256) private _packedOwnerships;

_packedOwnerships是一个mapping,key是token ID,value是对应的owner详情,包含了address、startTimestam等多个信息,从注释可以看出,每个信息都有对应的区间位置.源码中是通过_packOwnershipData方法对数据进行处理:

     
function _packOwnershipData(address owner, uint256 flags) private view returns (uint256 result) {
        assembly {
            // Mask `owner` to the lower 160 bits, in case the upper bits somehow aren't clean.
            owner := and(owner, _BITMASK_ADDRESS)
            // `owner | (block.timestamp << _BITPOS_START_TIMESTAMP) | flags`.
            result := or(owner, or(shl(_BITPOS_START_TIMESTAMP, timestamp()), flags))
        }
    }

and、or、sh1是solidity中内联汇编的语法,具体请查看https://solidity-cn.readthedocs.io/zh/develop/assembly.html#id4

owner := and(owner, _BITMASK_ADDRESS)

是一个按位与运算,它的作用是将变量 owner 和 _BITMASK_ADDRESS 进行按位与操作,**清洗掉 owner 中无关的高位,只保留低 160 位地址部分。shl(pos,x)**等价于执行 x << pos,shl(_BITPOS_START_TIMESTAMP, timestamp()的意思是将timestamp()左移160位.在看flags的实际处理方式:

  
    function _nextInitializedFlag(uint256 quantity) private pure returns (uint256 result) {
        // For branchless setting of the `nextInitialized` flag.
        assembly {
            // `(quantity == 1) << _BITPOS_NEXT_INITIALIZED`.
            result := shl(_BITPOS_NEXT_INITIALIZED, eq(quantity, 1))
        }
    }

eq是内联汇编的操作码,相当于(quantity == 1) ? 1 : 0,这个位移操作,相当于:


uint256 private constant _BITPOS_NEXT_INITIALIZED = 225;
(1 << _BITPOS_NEXT_INITIALIZED)   // 如果 quantity == 1
(0 << _BITPOS_NEXT_INITIALIZED)   // 如果 quantity != 1

224位没有设置,默认是0

所以_packedOwnerships的数据拼接结构为:

225224160-2230-159
nextInitializedburnedstartTimestampaddress

同理我们查看_packedAddressData


 // Bits Layout:
    // - [0..63]    `balance`
    // - [64..127]  `numberMinted`
    // - [128..191] `numberBurned`
    // - [192..255] `aux`
    mapping(address => uint256) private _packedAddressData;

_packedAddressData也是分区间存储多个信息.

 
 _packedAddressData[to] += quantity * ((1 << _BITPOS_NUMBER_MINTED) | 1);
// quantity * ((1 << _BITPOS_NUMBER_MINTED) | 1) 相当于 quantity << _BITPOS_NUMBER_MINTED + quantity
// 这里_packedAddressData存储了numberMinted与balance


发表评论