ryan

ERC721A的回溯逻辑

27 次阅读

ERC721A 中的“回溯逻辑”是它最核心、最具创新性的机制之一,正是这个逻辑让它能够:

  • 批量铸造(batch mint)多个 token,只写一次 storage;
  • 在不牺牲 ownerOf(tokenId) 准确性的前提下,极大节省 gas;

 什么是回溯逻辑?

ERC721 传统实现:


_ownerships[tokenId] = owner; // 每个 token 都写一次

ERC721A 优化后:

只记录 第一个 tokenId 的 owner,其他 token 不写入,靠回溯前一个已初始化的 tokenId 来推断 owner。例如:


_mint(alice, 3); // mint tokenId: 0, 1, 2

内部只会写入:


_packedOwnerships[0] = ... // uint256 包含 owner,timestamp,其它值


_packedOwnerships[1] == 0
_packedOwnerships[2] == 0

这时候如果你调用 ownerOf(2):

合约就会向前查找(回溯)直到找到最近一个写了所有者数据的位置(比如 tokenId 0)来推断出 tokenId 2 的 owner。

关键代码:_packedOwnershipOf(uint256 tokenId)

核心逻辑:

    
for (;;) {
          unchecked {
                    packed = _packedOwnerships[--tokenId];
                    }
                    if (packed == 0) continue;
                    if (packed & _BITMASK_BURNED == 0) return packed;
                    // Otherwise, the token is burned, and we must revert.
                    // This handles the case of batch burned tokens, where only the burned bit
                    // of the starting slot is set, and remaining slots are left uninitialized.
                    _revert(OwnerQueryForNonexistentToken.selector);
                }

回溯逻辑的完整流程:

  1. 查询 tokenId 的 _packedOwnerships[tokenId];
  2. 如果该槽位未初始化(即 packed == 0):
    • 向前一个一个递减 tokenId–;
    • 找到第一个非零的 packed 值;
    • 如果该值的 burned===false → 它就是当前 token 的所有者;
  3. 如果遇到 burned 的记录,则 revert(不能再回溯下去)

回溯逻辑的风险:burn 情况

当中间某个 token 被销毁(burned = true),它会中断回溯链。

// 初始状态

_packedOwnerships[0] = uint256

_packedOwnerships[1] = 0

_packedOwnerships[2] = 0

// 如果 tokenId=1 被 burn _packedOwnerships[1] = { burned: true }

这时候调用 ownerOf(2):

  • 会先检查 2 → 没数据 → 回溯到 1
  • 发现 1 被烧毁 → 无法再回溯 → revert!

解决方法:

在 _burn(1) 时,主动为 tokenId=2 写入所有权记录,打断回溯链。

这就是 burn 逻辑中设置:

  
_packedOwnerships[nextTokenId] = prevOwnershipPacked;

的核心原因.

在_burn的同时,要设置nextInitialized=true,告诉系统已经初始化好了下一个 token 的 slot,不需要回溯了。

总结:

ERC721A 的回溯逻辑是指:如果一个 token 没有写入显式的 owner 信息,就向前回溯到最近一个已初始化 tokenId,从中推导 owner.

发表评论