Michael.W基于Foundry精读Openzeppelin第25期——IERC1820Registry.sol

  • Michael.W
  • 更新于 2023-08-13 22:35
  • 阅读 726

IERC1820Registry.sol是global ERC1820 Registry的接口文件。ERC1820 Registry旨在创建一个全网唯一的interface与对应implementer的查询中心。所有地址都可以在其中注册interface与对应implementer的关联关系。

0. 版本

[openzeppelin]:v4.8.3,[forge-std]:v1.5.6

1. IERC1820Registry.sol

Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/utils/introspection/IERC1820Registry.sol

IERC1820Registry.sol是global ERC1820 Registry的接口文件。ERC1820 Registry旨在创建一个全网唯一的interface与对应implementer的查询中心。所有accounts都可以在ERC1820 Registry中注册任何interface与该interface的implementer的关联关系。同时,ERC1820 Registry对外也提供了对应的查询方法并且兼容IERC165中利用interface id的那套查询规则。

需要注意的是,一个implementer地址可以被记录在多个accounts名下且一个implementer地址可以实现多个interfaces。对协议的深度解释可参见EIP-1820详情:https://eips.ethereum.org/EIPS/eip-1820

pragma solidity ^0.8.0;

interface IERC1820Registry {
    event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer);

    event ManagerChanged(address indexed account, address indexed newManager);

    // 为一个account地址设置管理员地址。一个account地址的管理员可以为该account地址设置interface implementers。一个account地址默认是它自己的管理员。向newManager传入address(0)表示恢复account地址的管理员到初始状态
    // 注:只有account的当前管理员才可以调用该函数来设置新的管理员地址
    function setManager(address account, address newManager) external;

    // 查询目标地址account当前的管理员地址
    function getManager(address account) external view returns (address);

    // 在account名下,设置实现_interfaceHash对应interface的implementer合约地址_implementer
    // 当account参数传入address(0)表示该参数为msg.sender。当implementer为address(0)表示删除之前的对应interfaceHash的implementer地址。
    // 注:
    // 1. 只有account当前的manager才可以调用该方法;
    // 2. interfaceHash跟之前IERC165规范中定义的interface id不同且interfaceHash不可以以28个hex字符0结尾;
    // 3. implementer合约地址必须实现IERC1820Implementer interface
    function setInterfaceImplementer(
        address account,
        bytes32 _interfaceHash,
        address implementer
    ) external;

    // 查询account名下实现interface hash对应interface的implementer地址。如果account名下对应interface hash尚未注册implementer,该方法返回address(0)。当account参数传入address(0)表示该参数为msg.sender。
    // 注:如果_interfaceHash参数是由IERC165规范中定义的interface id+28个hex字符0构成,该方法会默认检查account地址支持是否支持interface id的逻辑
    function getInterfaceImplementer(address account, bytes32 _interfaceHash) external view returns (address);

    // 计算一个interface name(字符串)的keccak256 hash值。
    // interface name的字符串格式说明:https://eips.ethereum.org/EIPS/eip-1820#interface-name
    // 如果该interface是ERC标准的,那么它的interface name必须为 ERC###XXXXX,其中###为ERC提案号、XXXXX为该interface name的驼峰拼写形式。例如:ERC20Token/ERC777Token/ERC777TokensSender/ERC777TokensRecipient
    function interfaceHash(string calldata interfaceName) external pure returns (bytes32);

    // 在cache中更新目标地址account对IERC165定义的某interface id的支持
    function updateERC165Cache(address account, bytes4 interfaceId) external;

    // 查询目标地址account是否支持IERC165中定义的某interface id。如果支持返回true,否则返回false。
    // 注:
    // - 如果account地址与interface id的支持关系没有被显式更新到cache中,该方法将直接query account地址进行对应interface id的支持查询;
    // - 如果account地址与interface id的支持关系被显式更新到cache中(通过updateERC165Cache()方法)但是该cache中的内容已经不是最新的,需要再次调用updateERC165Cache()来手动更新本合约中的cache
    function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool);

    // 不通过本合约的cache来查询目标地址account是否支持IERC165中定义的某interface id。如果支持返回true,否则返回false。
    function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);
}

2. 官方实现合约代码解读

ETH上部署的官方ERC1820 Registry合约地址为:0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24

该合约为全局的registry合约,无论是合约地址还是EOA地址都可以在该Registry中注册它实现了哪些interface或者哪些合约是哪些interface的implemetation。

2.1 ERC1820Registry

注:该合约已经在etherscan上开源代码,详情参见:https://etherscan.io/address/0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24#code

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/utils/introspection/ERC1820Registry.sol

pragma solidity 0.5.3;

contract ERC1820Registry {
    // IERC165中定义的invalid interface id
    bytes4 constant internal INVALID_ID = 0xffffffff;
    // IERC165的interface id,也是supportsInterface(bytes4)的方法selector
    bytes4 constant internal ERC165ID = 0x01ffc9a7;

    // magic值
    // 注:协议规定,当查询一个合约(该合约实现了IERC1820Implementer interface)确实为某addr名下interface的implementer时,会返回该值
    bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));

    // 用于记录目标合约名下不同interface hash与它们的implementer地址的mapping
    mapping(address => mapping(bytes32 => address)) internal interfaces;
    // 用于存放目标地址与其管理员的mapping
    mapping(address => address) internal managers;
    // 用于记录目标地址名下对不同IERC165 interface id的支持情况的cache
    mapping(address => mapping(bytes4 => bool)) internal erc165Cached;

    // 事件表明:addr名下,implementer合约地址实现了interfaceHash对应的interface
    event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);
    // 事件表明:newManager地址已经成为addr地址的管理员
    event ManagerChanged(address indexed addr, address indexed newManager);

    // 查询_addr名下实现输入_interfaceHash的implementer合约地址。这又根据_interfaceHash的不同分成以下两种情况:
    // 1. _interfaceHash由IERC165定义的interface id构成,那么等于查询_addr是否支持该interface id。如果支持,返回_addr地址,否则返回address(0)
    // 2. _interfaceHash直接是从interface name取哈希值得来,那么等于查询_addr名下interface hash为_interfaceHash的implementer地址。如果尚未注册过,返回address(0)
    // 注:当_addr参数传入address(0)表示该参数为msg.sender
    function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) {
        // 如果输入的_addr是address(0),变量addr就为msg.sender。否则变量addr为输入的_addr
        address addr = _addr == address(0) ? msg.sender : _addr;
        if (isERC165Interface(_interfaceHash)) {
            // 如果输入的_interfaceHash表示一个IERC165中定义的interface id,即_interfaceHash为28个hex字符0结尾,将其前8个hex字符(前4个字节,即真正的IERC165中定义的interface id)通过类型转换的方式存入变量erc165InterfaceHash
            bytes4 erc165InterfaceHash = bytes4(_interfaceHash);
            // 调用implementsERC165Interface()方法查询addr地址是否支持IERC165中定义的interface id —— erc165InterfaceHash。如果支持返回addr地址,否则返回address(0)
            return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : address(0);
        }

        // 如果输入的_interfaceHash不表示一个IERC165中定义的interface id,直接返回interfaces[addr][_interfaceHash]
        return interfaces[addr][_interfaceHash];
    }

    // 在_addr名下,设置实现_interfaceHash对应interface的implementer合约地址_implementer
    // 注:
    // 1. 只有_addr当前的manager才可以调用该方法;
    // 2. 当_addr参数传入address(0)表示该参数为msg.sender。当implementer为address(0)表示删除之前的对应interfaceHash的implementer地址;
    // 3. interfaceHash跟之前IERC165规范中定义的interface id不同,且interfaceHash不可以以28个hex字符0结尾;
    // 4. _implementer合约地址必须实现interface:IERC1820Implementer
    function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {
        // 如果输入的_addr是address(0),变量addr就为msg.sender。否则变量addr为输入的_addr
        address addr = _addr == address(0) ? msg.sender : _addr;
        // 要求本次调用者为addr的manager
        require(getManager(addr) == msg.sender, "Not the manager");
    // 要求输入的_interfaceHash不可以表示的是一个IERC165中定义的interface id。即_interfaceHash不可以是"IERC165中定义的interface id"+28个hex字符0
        require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash");
        if (_implementer != address(0) && _implementer != msg.sender) {
            // 如果输入的_implementer既不是address(0)也不是msg.sender,调用_implementer合约的canImplementInterfaceForAddress()方法查询其是否在addr名下实现了输入的_interfaceHash。查询返回结果为ERC1820_ACCEPT_MAGIC表示通过了_implementer合约的二次确认,否则表示_implementer合约不确认上述关系,直接revert
            require(
                ERC1820ImplementerInterface(_implementer)
                    .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC,
                "Does not implement the interface"
            );
        }
        // 在mapping interfaces中的addr名下,增添输入的_interfaceHash和_implementer的映射关系
        // 注:如果_implementer为address(0),这里就表示清除输入的_interfaceHash和_implementer的映射关系
        interfaces[addr][_interfaceHash] = _implementer;
        // 抛出事件InterfaceImplementerSet
        emit InterfaceImplementerSet(addr, _interfaceHash, _implementer);
    }

    // 为一个_addr地址设置管理员地址
    // 注:
    // - 只有_addr地址的当前管理员才可以调用该函数来设置新的管理员地址;
    // - 当_newManager为_addr或者address(0),都表示将管理员还给_addr地址
    function setManager(address _addr, address _newManager) external {
        // 要求当前caller是当下_addr地址的管理员,否则revert
        require(getManager(_addr) == msg.sender, "Not the manager");
        // 如果输入的_newManager为address(0)或_addr,都会将管理员还给_addr。否则,_addr的管理员就变成输入的_newManager
        managers[_addr] = _newManager == _addr ? address(0) : _newManager;
        // 抛出ManagerChanged事件
        emit ManagerChanged(_addr, _newManager);
    }

    // 查询目标地址_addr当前的管理员地址
    function getManager(address _addr) public view returns(address) {
        // By default the manager of an address is the same address
        if (managers[_addr] == address(0)) {
            // 如果managers[_addr]的value为0,则默认_addr自己就是当前管理员
            return _addr;
        } else {
            // 如果managers[_addr]的value不为0,那么该value值就是管理员
            return managers[_addr];
        }
    }

    // 通过interface name计算interfaceHash
    function interfaceHash(string calldata _interfaceName) external pure returns(bytes32) {
        // 将_interfaceName的keccak256 hash值当做interface hash
        return keccak256(abi.encodePacked(_interfaceName));
    }

    // 在cache中更新目标地址_contract对IERC165定义的某interface id的支持
    function updateERC165Cache(address _contract, bytes4 _interfaceId) external {
        // 调用implementsERC165InterfaceNoCache(),来query目标合约_contract,查询其是否支持IERC165定义的interface id —— _interfaceId。如果_contract已经支持_interfaceId,那么interfaces[_contract][_interfaceId]的value改写为_contract,否则改写为address(0)
        interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache(
            _contract, _interfaceId) ? _contract : address(0);
        // 更新mapping erc165Cached,目标合约_contract下的对应key为_interfaceId的value置为true
        erc165Cached[_contract][_interfaceId] = true;
    }

    // 查询目标地址_contract是否支持IERC165中定义的某interface id。如果支持返回true,否则返回false
    // 注:优先通过本合约中的cache来查询。如果cache中未查询到,再直接与_contract合约交互来查询
    function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) {

        if (!erc165Cached[_contract][_interfaceId]) {
            // 如果本合约的cache中未记录_contract支持_interfaceId,就调用implementsERC165InterfaceNoCache()方法通过staticcall的方式去_contract合约查询
            return implementsERC165InterfaceNoCache(_contract, _interfaceId);
        }
        // 如果本合约的cache中有记录_contract支持_interfaceId
        return interfaces[_contract][_interfaceId] == _contract;
    }

    // 不通过本合约的cache来查询目标地址_contract是否支持IERC165中定义的某interface id。如果支持返回true,否则返回false。
    function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) {
        uint256 success;
        uint256 result;

    // 通过内联汇编staticcall的方式,查询目标合约_contract是否支持IERC165的interface id
        (success, result) = noThrowCall(_contract, ERC165ID);
        if (success == 0 || result == 0) {
            // 如果调用失败或者返回值为0(即false),说明目标合约_contract连IERC165都不支持(目标合约支持某interface id的前提条件是目标合约必须支持IERC165)。返回false
            return false;
        }

    // 通过内联汇编staticcall的方式,查询目标合约_contract是否支持invalid interface id
        (success, result) = noThrowCall(_contract, INVALID_ID);
        if (success == 0 || result != 0) {
            // 如果调用失败或者返回值不为0(即true),说明目标合约_contract连IERC165都不支持。返回false
            // 注:一个合约对IERC165的支持表现为支持IERC165 interface id且不支持invalid interface id。详情参见:https://learnblockchain.cn/article/6321 —— 2.2 supportsERC165(address account) 
            return false;
        }

        // 通过内联汇编staticcall的方式,查询目标合约_contract是否支持输入的_interfaceId
        (success, result) = noThrowCall(_contract, _interfaceId);
        if (success == 1 && result == 1) {
            // 如果调用成功且返回值为1(即true),说明目标合约_contract支持IERC165和输入的_interfaceId,返回true
            return true;
        }
        // 说明目标合约_contract支持IERC165但是不支持输入的_interfaceId,返回false
        return false;
    }

    // 检查目标_interfaceHash是否表示的是一个IERC165中定义的interface id
    function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) {
        // 如果_interfaceHash以28个hex字符0结尾,表示其前32-28=8个hex字符为IERC165中定义的interface id,返回true。否则表示_interfaceHash并不是interface id,返回false
        return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0;
    }

    // 调用_contract合约地址的supportsInterface(bytes4 _interfaceId)方法。如果_contract合约中未定义该方法也不会revert。返回值为:
    // - success: 调用是否成功
    // - result: 调用supportsInterface(bytes4 _interfaceId)方法返回的结果
    function noThrowCall(address _contract, bytes4 _interfaceId)
        internal view returns (uint256 success, uint256 result)
    {
        // 将supportsInterface(bytes4 _interfaceId)的方法selector从storage复制到内存中
        bytes4 erc165ID = ERC165ID;

        assembly {
            // 获取free memory pointer
            let x := mload(0x40)
            // 在x指针开始指向的内存中拼凑supportsInterface(bytes4 _interfaceId)的calldata
            mstore(x, erc165ID)                
            mstore(add(x, 0x04), _interfaceId) 

            // 使用staticcall来query _contract合约中的supportsInterface(bytes4 _interfaceId)方法
            // 注: 内联汇编staticcall详解参见博文:https://learnblockchain.cn/article/6309
            success := staticcall(
                30000,                         // 本次调用的gas上限为30000 gas
                _contract,                     // staticcall合约地址
                x,                             // calldata从x指针指向的内存地址开始
                0x24,                          // calldata在内存中的长度为36字节(selector占4字节+参数_interfaceId占32字节)
                x,                             // 从x指针指向的内存开始存放staticcall的返回数据
                0x20                           // memory中存放返回数据的长度为32个字节
            )

            // 从memory中将staticcall的返回值变成uint256类型返回
            result := mload(x)                 
        }
    }
}

ps: 本人热爱图灵,热爱中本聪,热爱V神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!

1.jpeg

公众号名称:后现代泼痞浪漫主义奠基人

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Michael.W
Michael.W
0x93E7...0000
狂热的区块链爱好者