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

  • Michael.W
  • 更新于 2023-08-12 06:41
  • 阅读 1434

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

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神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!

1.jpeg

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

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

0 条评论

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