Michael.W基于Foundry精读Openzeppelin第24期——ERC165Storage.sol

0. 版本

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

0.1 ERC165Storage.sol

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

ERC165Storage合约是ERC165的一种拓展。IERC165的supportsInterface(bytes4 interfaceId)函数的标准实现方式是静态地将已实现接口的interface id硬编码到bytecode中,而ERC165Storage合约则可在合约部署完成后动态地添加已支持的interface id。

1. 目标合约

继承ERC165Storage成为一个可调用合约:

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

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "openzeppelin-contracts/contracts/utils/introspection/ERC165Storage.sol";
import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";

interface ICustomized {
    function helloMichael(string memory str) external;
}

contract MockERC165Storage is ERC165Storage, ERC20("", ""), ICustomized {
    string _str;

    // implementation of interface ICustomized
    function helloMichael(string memory str) external {
        _str = str;
    }

    function registerInterface(bytes4 interfaceId) external {
        _registerInterface(interfaceId);
    }
}

同时该合约实现了一个自定义interface——ICustomized。

全部foundry测试合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/utils/introspection/ERC165Storage.t.sol

2. 代码精读

2.1 _registerInterface(bytes4 interfaceId)

动态添加已支持的interface id。

注:IERC165的interface id已经在父合约ERC165中默认支持了,所以不需要使用该方法来动态添加对IERC165的支持。

    // 用于存放interface id与本合约是否支持该interface id之间映射关系的mapping
    mapping(bytes4 => bool) private _supportedInterfaces;
    
    function _registerInterface(bytes4 interfaceId) internal virtual {
    	// 按照EIP-165规范,任何接口的interface id都不应该是0xffffffff。所以当输入的interface id为0xffffffff时,直接revert
       	require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
       	// 在mapping _supportedInterfaces中将输入interface id为key的value置为true,表示本合约已经支持了该interface
       	_supportedInterfaces[interfaceId] = true;
    }

2.2 supportsInterface(bytes4 interfaceId)

对外提供本合约是否实现了传入interfaceId标识的interface的查询功能。

注:ERC165Storage由于继承了ERC165合约,此处是对ERC165中同名方法的重写。

    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
    	// 如果ERC165.supportsInterface()对传入interface id的查询结果为true或_supportedInterfaces中记录了已支持该传入interface id时,该方法返回true。简而言之,当传入的interface id为IERC165的interface id或_supportedInterfaces已记录的value为true的interface id,该方法返回true,否则返回false
        return super.supportsInterface(interfaceId) || _supportedInterfaces[interfaceId];
    }

2.3 foundry代码验证

contract ERC165StorageTest is Test {
    MockERC165Storage me = new MockERC165Storage();

    function test_ERC165Storage() external {
        // only support IERC165 in initial status
        assertTrue(me.supportsInterface(type(IERC165).interfaceId));
        assertFalse(me.supportsInterface(type(IERC20).interfaceId));
        assertFalse(me.supportsInterface(type(IERC20Metadata).interfaceId));
        assertFalse(me.supportsInterface(type(ICustomized).interfaceId));

        // register interfaces
        me.registerInterface(type(IERC20).interfaceId);
        me.registerInterface(type(IERC20Metadata).interfaceId);
        me.registerInterface(type(ICustomized).interfaceId);
        // revert if try to register invalid interface id (0xffffffff) in IERC165
        vm.expectRevert("ERC165: invalid interface id");
        me.registerInterface(0xffffffff);

        // check
        assertTrue(me.supportsInterface(type(IERC20).interfaceId));
        assertTrue(me.supportsInterface(type(IERC20Metadata).interfaceId));
        assertTrue(me.supportsInterface(type(ICustomized).interfaceId));
    }
}

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

在这里插入图片描述

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

你可能感兴趣的:(Openzeppelin,ERC165Storage,IERC165,foundry,solidity)