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