Michael.W基于Foundry精读Openzeppelin第25期——IERC1820Registry.sol
- 0. 版本
- 1. IERC1820Registry.sol
- 2. 官方实现合约代码解读
- 2.1 ERC1820Registry
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神。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人