ERC-1820: 伪自省注册表合约
Authors | Jordi Baylina <jordi@baylina.cat>, Jacques Dafflon <mail@0xjac.com> |
---|---|
Created | 2019-03-04 |
Requires | EIP-165, EIP-214 |
Table of Contents
:information_source: ERC-1820 已经取代了 ERC-820。 :information_source: ERC-1820 修复了 Solidity 0.5 更新引入的 ERC-165 逻辑中的不兼容性。 请查看官方公告,以及关于错误和修复的评论。 除了这个修复,ERC-1820 在功能上等同于 ERC-820。
概要
本标准定义了一个通用的注册表智能合约,任何地址(合约或常规账户)都可以在其中注册它支持哪个接口,以及哪个智能合约负责它的实现。
本标准保持了与 ERC-165 的向后兼容性。
摘要
本标准定义了一个注册表,智能合约和常规账户可以在其中发布它们实现的哪些功能——无论是直接实现还是通过代理合约实现。
任何人都可以查询此注册表,以询问特定地址是否实现了给定的接口,以及哪个智能合约处理它的实现。
这个注册表可以部署在任何链上,并在所有链上共享相同的地址。
最后 28 个字节为零 (0
) 的接口被认为是 ERC-165 接口,
并且此注册表应将调用转发到合约,以查看它是否实现了该接口。
此合约还充当 ERC-165 缓存,以减少 gas 消耗。
动机
在 Ethereum 中已经有不同的方法来定义伪自省。 第一种是 ERC-165,它的局限性在于它不能被常规账户使用。 第二次尝试是 ERC-672,它使用反向 ENS。 使用反向 ENS 有两个问题。 首先,它不必要地复杂,其次,ENS 仍然是由多重签名控制的中心化合约。 理论上,这个多重签名能够修改系统。
这个标准比 ERC-672 简单得多,并且是完全去中心化的。
这个标准还为所有链提供了一个唯一的地址。 从而解决了为不同的链解析正确的注册表地址的问题。
规范
ERC-1820 注册表智能合约
这是 [ERC1820 注册表智能合约] 代码的精确副本。
/* ERC1820 伪自省注册表合约
* 本标准定义了一个通用的注册表智能合约,任何地址(合约或常规帐户)都可以在其中
* 注册它支持的接口以及哪个智能合约负责它的实现。
*
* 由 Jordi Baylina 和 Jacques Dafflon 于 2019 年编写
*
* 在法律允许的范围内,作者已将本软件的所有版权和相关及邻近权利
* 贡献给全世界的公共领域。 本软件不提供任何保证地分发。
*
* 您应该已收到随本软件一起提供的 CC0 公共领域贡献副本。 如果没有,请参见
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*
* ███████╗██████╗ ██████╗ ██╗ █████╗ ██████╗ ██████╗
* ██╔════╝██╔══██╗██╔════╝███║██╔══██╗╚════██╗██╔═████╗
* █████╗ ██████╔╝██║ ╚██║╚█████╔╝ █████╔╝██║██╔██║
* ██╔══╝ ██╔══██╗██║ ██║██╔══██╗██╔═══╝ ████╔╝██║
* ███████╗██║ ██║╚██████╗ ██║╚█████╔╝███████╗╚██████╔╝
* ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚══════╝ ╚═════╝
*
* ██████╗ ███████╗ ██████╗ ██╗███████╗████████╗██████╗ ██╗ ██╗
* ██╔══██╗██╔════╝██╔════╝ ██║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝
* ██████╔╝█████╗ ██║ ███╗██║███████╗ ██║ ██████╔╝ ╚████╔╝
* ██╔══██╗██╔══╝ ██║ ██║██║╚════██║ ██║ ██╔══██╗ ╚██╔╝
* ██║ ██║███████╗╚██████╔╝██║███████║ ██║ ██║ ██║ ██║
* ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
*
*/
pragma solidity 0.5.3;
// IV 是需要具有以 '0x1820' 开头的 vanity 地址的值。
// IV: 53759
/// @dev 如果合约是任何地址(除了自身)的某个(其他)接口的实现者,则合约必须实现的接口。
interface ERC1820ImplementerInterface {
/// @notice 指示合约是否为地址“addr”实现了接口“interfaceHash”。
/// @param interfaceHash 接口名称的 keccak256 哈希值
/// @param addr 合约将为其实现接口的地址
/// @return 仅当合约为地址“addr”实现“interfaceHash”时才返回 ERC1820_ACCEPT_MAGIC。
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);
}
/// @title ERC1820 伪自省注册表合约
/// @author Jordi Baylina 和 Jacques Dafflon
/// @notice 此合约是 ERC1820 注册表的官方实现。
/// @notice 更多详情,请参见 https://eips.ethereum.org/EIPS/eip-1820
contract ERC1820Registry {
/// @notice ERC165 无效 ID。
bytes4 constant internal INVALID_ID = 0xffffffff;
/// @notice ERC165 supportsInterface 方法的 Method ID (= `bytes4(keccak256('supportsInterface(bytes4)'))`)。
bytes4 constant internal ERC165ID = 0x01ffc9a7;
/// @notice 如果合约代表其他地址实现接口,则返回的 Magic value。
bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));
/// @notice 从地址和接口哈希到它们的实现者的映射。
mapping(address => mapping(bytes32 => address)) internal interfaces;
/// @notice 从地址到其管理器的映射。
mapping(address => address) internal managers;
/// @notice 用于指示是否对每个地址和 erc165 接口进行缓存的标志。
mapping(address => mapping(bytes4 => bool)) internal erc165Cached;
/// @notice 指示合约是“addr”的“interfaceHash”的“implementer”。
event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);
/// @notice 指示“newManager”是“addr”的新管理器的地址。
event ManagerChanged(address indexed addr, address indexed newManager);
/// @notice 查询地址是否实现了接口以及通过哪个合约实现。
/// @param _addr 查询接口实现者的地址。
/// (如果“_addr”为零地址,则假定为“msg.sender”。)
/// @param _interfaceHash 接口名称的 Keccak256 哈希值,以字符串形式表示。
/// 例如,“web3.utils.keccak256("ERC777TokensRecipient")”表示“ERC777TokensRecipient”接口。
/// @return 实现“_addr”的接口“_interfaceHash”的合约地址
/// 或者,如果“_addr”未为此接口注册实现者,则返回“0”。
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) {
address addr = _addr == address(0) ? msg.sender : _addr;
if (isERC165Interface(_interfaceHash)) {
bytes4 erc165InterfaceHash = bytes4(_interfaceHash);
return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : address(0);
}
return interfaces[addr][_interfaceHash];
}
/// @notice 设置为地址实现特定接口的合约。
/// 只有为该地址定义的管理器才能设置它。
/// (每个地址都是其自身的管理器,直到它设置新的管理器。)
/// @param _addr 要为其设置接口的地址。
/// (如果“_addr”为零地址,则假定为“msg.sender”。)
/// @param _interfaceHash 接口名称的 Keccak256 哈希值,以字符串形式表示。
/// 例如,“web3.utils.keccak256("ERC777TokensRecipient")”表示“ERC777TokensRecipient”接口。
/// @param _implementer 实现“_addr”的“_interfaceHash”的合约地址。
function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {
address addr = _addr == address(0) ? msg.sender : _addr;
require(getManager(addr) == msg.sender, "Not the manager"); // 不是管理器
require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash"); // 必须不是 ERC165 哈希
if (_implementer != address(0) && _implementer != msg.sender) {
require(
ERC1820ImplementerInterface(_implementer)
.canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC,
"Does not implement the interface" // 没有实现接口
);
}
interfaces[addr][_interfaceHash] = _implementer;
emit InterfaceImplementerSet(addr, _interfaceHash, _implementer);
}
/// @notice 设置“_newManager”作为“_addr”的管理器。
/// 新的管理器将能够为“_addr”调用“setInterfaceImplementer”。
/// @param _addr 要为其设置新管理器的地址。
/// @param _newManager 'addr'的新管理器的地址。(传递“0x0”以将管理器重置为“_addr”。)
function setManager(address _addr, address _newManager) external {
require(getManager(_addr) == msg.sender, "Not the manager"); // 不是管理器
managers[_addr] = _newManager == _addr ? address(0) : _newManager;
emit ManagerChanged(_addr, _newManager);
}
/// @notice 获取地址的管理器。
/// @param _addr 要为其返回管理器的地址。
/// @return 给定地址的管理器的地址。
function getManager(address _addr) public view returns(address) {
// 默认情况下,地址的管理器是同一地址
if (managers[_addr] == address(0)) {
return _addr;
} else {
return managers[_addr];
}
}
/// @notice 计算给定接口名称的 keccak256 哈希。
/// @param _interfaceName 接口的名称。
/// @return 接口名称的 keccak256 哈希。
function interfaceHash(string calldata _interfaceName) external pure returns(bytes32) {
return keccak256(abi.encodePacked(_interfaceName));
}
/* --- ERC165 相关函数 --- */
/* --- 与 William Entriken 合作开发。 --- */
/// @notice 使用合约是否实现 ERC165 接口来更新缓存。
/// @param _contract 要为其更新缓存的合约地址。
/// @param _interfaceId 要为其更新缓存的 ERC165 接口。
function updateERC165Cache(address _contract, bytes4 _interfaceId) external {
interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache(
_contract, _interfaceId) ? _contract : address(0);
erc165Cached[_contract][_interfaceId] = true;
}
/// @notice 检查合约是否实现 ERC165 接口。
// 如果结果未缓存,则直接在合约地址上执行查找。
// 如果结果未缓存或缓存值已过期,则必须通过使用合约地址调用
// “updateERC165Cache”来手动更新缓存。
/// @param _contract 要检查的合约地址。
/// @param _interfaceId 要检查的 ERC165 接口。
/// @return 如果“_contract”实现了“_interfaceId”,则返回 True,否则返回 false。
function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) {
if (!erc165Cached[_contract][_interfaceId]) {
return implementsERC165InterfaceNoCache(_contract, _interfaceId);
}
return interfaces[_contract][_interfaceId] == _contract;
}
/// @notice 检查合约是否实现 ERC165 接口,无需使用或更新缓存。
/// @param _contract 要检查的合约地址。
/// @param _interfaceId 要检查的 ERC165 接口。
/// @return 如果“_contract”实现了“_interfaceId”,则返回 True,否则返回 false。
function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) {
uint256 success;
uint256 result;
(success, result) = noThrowCall(_contract, ERC165ID);
if (success == 0 || result == 0) {
return false;
}
(success, result) = noThrowCall(_contract, INVALID_ID);
if (success == 0 || result != 0) {
return false;
}
(success, result) = noThrowCall(_contract, _interfaceId);
if (success == 1 && result == 1) {
return true;
}
return false;
}
/// @notice 检查哈希是否为 ERC165 接口(以 28 个零结尾)。
/// @param _interfaceHash 要检查的哈希。
/// @return 如果“_interfaceHash”是 ERC165 接口(以 28 个零结尾),则返回 True,否则返回 false。
function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) {
return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0;
}
/// @dev 对合约进行调用,如果该函数不存在则不抛出异常。
function noThrowCall(address _contract, bytes4 _interfaceId)
internal view returns (uint256 success, uint256 result)
{
bytes4 erc165ID = ERC165ID;
assembly {
let x := mload(0x40) // 使用“空闲内存指针”查找空存储位置
mstore(x, erc165ID) // 将签名放在空存储的开头
mstore(add(x, 0x04), _interfaceId) // 将第一个参数直接放在签名旁边
success := staticcall(
30000, // 30k gas
_contract, // To addr
x, // 输入存储在位置 x
0x24, // 输入长度为 36 (4 + 32) 字节
x, // 将输出存储在输入上(节省空间)
0x20 // 输出长度为 32 字节
)
result := mload(x) // 加载结果
}
}
}
部署交易
以下是必须用于在任何链上部署智能合约的原始交易。
0xf90a388085174876e800830c35008080b909e5608060405234801561001057600080fd5b506109c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063a41e7d5111610078578063a41e7d51146101d4578063aabbb8ca1461020a578063b705676514610236578063f712f3e814610280576100a5565b806329965a1d146100aa5780633d584063146100e25780635df8122f1461012457806365ba36c114610152575b600080fd5b6100e0600480360360608110156100c057600080fd5b50600160a060020a038135811691602081013591604090910135166102b6565b005b610108600480360360208110156100f857600080fd5b5035600160a060020a0316610570565b60408051600160a060020a039092168252519081900360200190f35b6100e06004803603604081101561013a57600080fd5b50600160a060020a03813581169160200135166105bc565b6101c26004803603602081101561016857600080fd5b81019060208101813564010000000081111561018357600080fd5b82018360208201111561019557600080fd5b803590602001918460018302840111640100000000831117156101b757600080fd5b5090925090506106b3565b60408051918252519081900360200190f35b6100e0600480360360408110156101ea57600080fd5b508035600160a060020a03169060200135600160e060020a0319166106ee565b6101086004803603604081101561022057600080fd5b50600160a060020a038135169060200135610778565b61026c6004803603604081101561024c57600080fd5b508035600160a060020a03169060200135600160e060020a0319166107ef565b604080519115158252519081900360200190f35b61026c6004803603604081101561029657600080fd5b508035600160a060020a03169060200135600160e060020a0319166108aa565b6000600160a060020a038416156102cd57836102cf565b335b9050336102db82610570565b600160a060020a031614610339576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b6103428361092a565b15610397576040805160e560020a62461bcd02815260206004820152601a60248201527f4d757374206e6f7420626520616e204552433136352068617368000000000000604482015290519081900360640190fd5b600160a060020a038216158015906103b85750600160a060020a0382163314155b156104ff5760405160200180807f455243313832305f4143434550545f4d4147494300000000000000000000000081525060140190506040516020818303038152906040528051906020012082600160a060020a031663249cb3fa85846040518363ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018083815260200182600160a060020a0316600160a060020a031681526020019250505060206040518083038186803b15801561047e57600080fd5b505afa158015610492573d6000803e3d6000fd5b505050506040513d60208110156104a857600080fd5b5051146104ff576040805160e560020a62461bcd02815260206004820181905260248201527f446f6573206e6f7420696d706c656d656e742074686520696e74657266616365604482015290519081900360640190fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03818116600090815260016020526040812054909116151561059a5750806105b7565b50600160a060020a03808216600090815260016020526040902054165b919050565b336105c683610570565b600160a060020a031614610624576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b81600160a060020a031681600160a060020a0316146106435780610646565b60005b600160a060020a03838116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519184169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a35050565b600082826040516020018083838082843780830192505050925050506040516020818303038152906040528051906020012090505b92915050565b6106f882826107ef565b610703576000610705565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b600080600160a060020a038416156107905783610792565b335b905061079d8361092a565b156107c357826107ad82826108aa565b6107b85760006107ba565b815b925050506106e8565b600160a060020a0390811660009081526020818152604080832086845290915290205416905092915050565b6000808061081d857f01ffc9a70000000000000000000000000000000000000000000000000000000061094c565b909250905081158061082d575080155b1561083d576000925050506106e8565b61084f85600160e060020a031961094c565b909250905081158061```md
---
title: ERC-1820:通用伪自检注册表合约
description: 定义了一个通用的注册表智能合约,任何地址(合约或常规账户)都可以在其中注册它支持的接口以及哪个智能合约负责其实现。
---
## 目录
1. [简介](#introduction)
2. [动机](#motivation)
3. [规范](#specification)
4. [原理](#rationale)
5. [实施](#implementation)
6. [安全注意事项](#security-considerations)
7. [版权](#copyright)
## 摘要
该标准定义了一个通用的注册表智能合约,任何地址(合约或常规账户)都可以在其中注册它支持的接口以及哪个智能合约负责其实现。
## 动机
目前,没有标准化的方法来检查给定的智能合约是否实现了给定的接口。
接口检测传统上是通过 [ERC-165] 完成的,但这需要合约明确声明它实现的接口。
这在许多情况下存在问题,最常见的是:
1. 无法以向后兼容的方式为现有合约添加接口。
2. 如果合约要与其他合约交互,则需要知道合约是否实现了给定的接口。
## 规范
### 智能合约
``` solidity
pragma solidity ^0.5.0;
contract ERC1820Registry {
function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external;
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address);
function setManager(address _addr, address _newManager) external;
function getManager(address _addr) external view returns (address);
function interfaceHash(string _interfaceName) external pure returns(bytes32);
function implementsERC165Interface(address _contract, bytes4 _interfaceId) external view returns (bool);
function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) external view returns (bool);
function updateERC165Cache(address _contract, bytes4 _interfaceId) external;
}
该注册表合约必须部署在一个众所周知且不可变的地址上。
该地址是 0x1820a4B7618BdE71Dce8cdc73aAB6c95905faD24
,并且合约初始化后,keccak256(bytes("ERC1820_REGISTRY_NAME"))
必须返回该注册表合约地址。
keccak256(bytes("ERC1820_REGISTRY_NAME")) == keccak256(ERC1820Registry.address)
必须总是返回 true
。
函数
setInterfaceImplementer
function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external
设置实现地址特定接口的合约。
只有为该地址定义的 manager
才能设置它。(在设置新 manager
之前,每个地址都是自己的 manager,有关更多详细信息,请参见 manager 部分。)
注意:如果 _addr
和 _implementer
是两个不同的地址,则:
_implementer
必须实现ERC1820ImplementerInterface
(详见下文)。- 使用给定的
_addr
和_interfaceHash
在_implementer
上调用canImplementInterfaceForAddress
必须返回ERC1820_ACCEPT_MAGIC
值。
注意:_interfaceHash
不能是 ERC-165 接口,它不能以 28 个零(0
)结尾。
注意:_addr
可以为 0
,那么将假定为 msg.sender
。此默认值简化了通过多重签名的交互,在这种交互中,要签名的交易数据是恒定的,而与多重签名实例的地址无关。
标识符:
29965a1d
参数
_addr
:要设置接口的地址。(如果_addr
为零地址,则假定为msg.sender
。)
_interfaceHash
:接口名称的 Keccak256 哈希值,例如web3.utils.keccak256('ERC777TokensRecipient')
(对于 ERC777TokensRecipient 接口)。
_implementer
:实现_interfaceHash
的合约。
getInterfaceImplementer
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address)
查询地址是否实现接口以及通过哪个合约实现。
注意:如果 _interfaceHash
的最后 28 个字节为零(0
),则前 4 个字节将被视为 ERC-165 接口,并且注册表应将调用转发到 _addr
处的合约,以查看它是否实现了 ERC-165 接口(_interfaceHash
的前 4 个字节)。该注册表还应缓存 ERC-165 查询以减少 gas 消耗。任何人都可以调用 erc165UpdateCache
函数来更新合约是否实现接口。
注意:_addr
可以为 0
,那么将假定为 msg.sender
。此默认值与 setInterfaceImplementer
函数的行为一致,并简化了通过多重签名的交互,在这种交互中,要签名的交易数据是恒定的,而与多重签名实例的地址无关。
标识符:
aabbb8ca
参数
_addr
:正在查询接口实现的地址。(如果_addr
为零地址,则假定为msg.sender
。)
_interfaceHash
:接口名称的 keccak256 哈希值。例如web3.utils.keccak256('ERC777Token')
返回值: 实现_interfaceHash
接口的合约的地址,如果_addr
没有为此接口注册实现者,则返回0
。
setManager
function setManager(address _addr, address _newManager) external
将 _newManager
设置为 _addr
的 manager。
新的 manager 将能够为 _addr
调用 setInterfaceImplementer
。
如果 _newManager
是 0x0
,则 manager 将重置为 _addr
本身作为 manager。
标识符:
5df8122f
参数
_addr
:要设置新 manager 的地址。
_newManager
:_addr
的新 manager 的地址。(传递0x0
以将 manager 重置为_addr
。)
getManager
function getManager(address _addr) external view returns (address)
获取地址的 manager。
标识符:
ef277308
参数
_addr
:要返回其 manager 的地址。
返回值: 给定地址的 manager 的地址。
interfaceHash
function interfaceHash(string _interfaceName) external pure returns(bytes32)
计算给定接口名称的 keccak256 哈希值。
标识符:
65ba36c1
参数
_interfaceName
:接口的名称。
返回值: 接口名称的keccak256
哈希值。
implementsERC165Interface
function implementsERC165Interface(address _contract, bytes4 _interfaceId) external view returns (bool)
检查合约是否实现 ERC-165 接口。
如果未缓存结果,则对合约地址执行直接查找。
注意:如果未缓存结果或缓存值已过期,则必须通过使用合约地址手动调用 updateERC165Cache
来手动更新缓存。
(有关更多详细信息,请参见 [ERC165 缓存]。)
标识符:
f712f3e8
参数
_contract
:要检查的合约的地址。
_interfaceId
:要检查的 ERC-165 接口。
返回值: 如果_contract
实现了_interfaceId
,则返回true
;否则返回false
。
implementsERC165InterfaceNoCache
function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) external view returns (bool)
检查合约是否实现了 ERC-165 接口,而不使用或更新缓存。
标识符:
b7056765
参数
_contract
:要检查的合约的地址。
_interfaceId
:ERC-165 接口来检查。
返回值: 如果_contract
实现了_interfaceId
,则返回true
;否则返回false
。
updateERC165Cache
function updateERC165Cache(address _contract, bytes4 _interfaceId) external
更新缓存,指示合约是否实现 ERC-165 接口。
标识符:
a41e7d51
参数
_contract
:要更新缓存的合约的地址。
_interfaceId
:要更新缓存的 ERC-165 接口。
实施者接口 (ERC1820ImplementerInterface
)
interface ERC1820ImplementerInterface {
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);
}
任何被注册为给定地址的接口实现的合约都必须实现该接口。
此外,如果它代表不同的地址实现接口,则合约必须实现上面显示的 ERC1820ImplementerInterface
。
canImplementInterfaceForAddress
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32)
指示合约是否使用给定的地址 (addr
) 实现接口 (interfaceHash
)。
如果合约为给定地址 (addr
) 实现了接口 (interfaceHash
),则在使用 addr
和 interfaceHash
调用时,它必须返回 ERC1820_ACCEPT_MAGIC
。
如果没有为给定地址 (addr
) 实现 interfaceHash
,则不得返回 ERC1820_ACCEPT_MAGIC
。
标识符:
f0083250
参数
interfaceHash
:已实现的接口的哈希值
addr
:实现接口的地址
返回值: 仅当合约为地址addr
实现ìnterfaceHash
时,才返回ERC1820_ACCEPT_MAGIC
。
特殊值 ERC1820_ACCEPT_MAGIC
定义为字符串 "ERC1820_ACCEPT_MAGIC"
的 keccka256
哈希值。
bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));
返回
ERC1820_ACCEPT_MAGIC
而不是布尔值的原因是防止出现合约无法实现canImplementInterfaceForAddress
但实现了不抛出的回退函数的情况。在这种情况下,由于canImplementInterfaceForAddress
不存在,因此改为调用回退函数,执行时不抛出并返回1
。因此,使其看起来好像canImplementInterfaceForAddress
返回了true
。
Manager
地址(常规帐户或合约)的 manager 是唯一允许为该地址注册接口实现的实体。 默认情况下,任何地址都是自己的 manager。
manager 可以通过在注册表合约上调用 setManager
并提供要转移 manager 的地址和新 manager 的地址,将其角色转移到另一个地址。
setManager
函数
function setManager(address _addr, address _newManager) external
将 _newManager
设置为 _addr
的 manager。
新的 manager 将能够为 _addr
调用 setInterfaceImplementer
。
如果 _newManager
为 0x0
,则 manager 将重置为 _addr
本身作为 manager。
标识符:
5df8122f
参数
_addr
:要设置新 manager 的地址。
_newManager
:_addr
的新 manager 的地址。(传递0x0
以将 manager 重置为_addr
。)
getManager
函数
function getManager(address _addr) external view returns(address)
获取地址的 manager。
标识符:
ef277308
参数
_addr
:要返回其 manager 的地址。
返回值: 给定地址的 manager 的地址。
事件
InterfaceImplementerSet
event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);
当地址 addr
的 interfaceHash
接口设置为 implementer
时发出。
ManagerChanged
event ManagerChanged(address indexed addr, address indexed newManager);
当地址 addr
的 manager 更改为 newManager
时发出。
术语约定
- 应当使用关键字 MUST、SHALL、REQUIRED、SHOULD、RECOMMENDED、MAY 和 OPTIONAL 来描述本规范,如 [RFC 2119] 中所述。
- ERC-165:指向 ERC-165 标准的链接
- [管理器]:指向“管理器”部分的链接
- [ERC165 缓存]:指向“[ERC-165] 缓存”部分的链接
web3.utils.keccak256('ERC777TokensRecipient')
:用于计算ERC777TokensRecipient
接口的bytes32
哈希值的 JavaScript 表示形式。其他语言可以使用各自的keccak256
实现来获得相同的结果,但值必须是bytes32
。
实施
- ERC1820Registry.sol:ERC1820 注册智能合约的实现。
- ERC1820Client.sol:一个抽象智能合约,可以继承它来简化与注册表合约的交互。
原理
与 ERC-165 相比,此标准使用 string
的 keccak256
哈希值而不是 bytes4
作为接口 ID 的原因是,使用 bytes4
可能会发生冲突。
即使发生冲突的可能性很小,也最好使用不会发生冲突的东西。
实现
ERC-1820 注册表的实现地址为 0x1820a4B7618BdE71Dce8cdc73aAB6c95905faD24
。
此合约的源代码以及测试可以在存储库中找到。
<details>
<summary>
<b>
点击查看合约 ABI
</b>
</summary>
```json
{
“compiler”: {
“version”: “0.5.3+commit.10d17f24”
},
“language”: “Solidity”,
“output”: {
“abi”: [
{
“constant”: false,
“inputs”: [
{
“name”: “_addr”,
“type”: “address”
},
{
“name”: “_interfaceHash”,
“type”: “bytes32”
},
{
“name”: “_implementer”,
“type”: “address”
}
],
“name”: “setInterfaceImplementer”,
“outputs”: [],
“payable”: false,
“stateMutability”: “nonpayable”,
“type”: “function”
},
{
“constant”: true,
“inputs”: [
{
“name”: “_addr”,
“type”: “address”
}
],
“name”: “getManager”,
“outputs”: [
{
“name”: “”,
“type”: “address”
}
],
“payable”: false,
“stateMutability”: “view”,
“type”: “function”
},
{
“constant”: false,
“inputs”: [
{
“name”: “_addr”,
“type”: “address”
},
{
“name”: “_newManager”,
“type”: “address”
}
],
“name”: “setManager”,
“outputs”: [],
“payable”: false,
“stateMutability”: “nonpayable”,
“type”: “function”
},
{
“constant”: true,
“inputs”: [
{
“name”: “_interfaceName”,
“type”: “string”
}
],
“name”: “interfaceHash”,
“outputs”: [
{
“name”: “”,
“type”: “bytes32”
}
],
“payable”: false,
“stateMutability”: “pure”,
“type”: “function”
},
{
“constant”: false,
“inputs”: [
{
“name”: “_contract”,
“type”: “address”
},
{
“name”: “_interfaceId”,
“type”: “bytes4”
}
],
“name”: “updateERC165Cache”,
“outputs”: [],
“payable”: false,
“stateMutability”: “nonpayable”,
“type”: “function”
},
{
“constant”: true,
“inputs”: [
{
“name”: “_addr”,
“type”: “address”
},
{
“name”: “_interfaceHash”,
“type”: “bytes32”
}
],
“name”: “getInterfaceImplementer”,
“outputs”: [
{
“name”: “”,
“type”: “address”
}
],
“payable”: false,
“stateMutability”: “view”,
“type”: “function”
},
{
“constant”: true,
“inputs”: [
{
“name”: “_contract”,
“type”: “address”
},
{
“name”: “_interfaceId”,
“type”: “bytes4”
}
],
“name”: “implementsERC165InterfaceNoCache”,
“outputs”: [
{
“name”: “”,
“type”: “bool”
}
],
“payable”: false,
“stateMutability”: “view”,
“type”: “function”
},
{
“constant”: true,
“inputs”: [
{
“name”: “_contract”,
“type”: “address”
},
{
“name”: “_interfaceId”,
“type”: “bytes4”
}
],
“name”: “implementsERC165Interface”,
“outputs”: [
{
“name”: “”,
“type”: “bool”
}
],
“payable”: false,
“stateMutability”: “view”,
“type”: “function”
},
{
“anonymous”: false,
“inputs”: [
{
“indexed”: true,
“name”: “addr”,
“type”: “address”
},
{
“indexed”: true,
“name”: “interfaceHash”,
“type”: “bytes32”
},
{
“indexed”: true,
“name”: “implementer”,
“type”: “address”
}
],
“name”: “InterfaceImplementerSet”,
“type”: “event”
},
{
“anonymous”: false,
“inputs”: [
{
“indexed”: true,
“name”: “addr”,
“type”: “address”
},
{
“indexed”: true,
“name”: “newManager”,
“type”: “address”
}
],
“name”: “ManagerChanged”,
“type”: “event”
}
],
“devdoc”: {
“author”: “Jordi Baylina and Jacques Dafflon”,
“methods”: {
“getInterfaceImplementer(address,bytes32)”: {
“params”: {
“_addr”: “正在查询接口实现者的地址。(如果’_addr’为零地址,则假定为’msg.sender’。)”,
“_interfaceHash”: “接口名称的 Keccak256 哈希值,作为字符串。例如,’web3.utils.keccak256("ERC777TokensRecipient")’ 对于 ‘ERC777TokensRecipient’ 接口。”
},
“return”: “为’_addr’实现接口’_interfaceHash’的合约地址,如果’_addr’未为此接口注册实现者,则返回’0’。”
},
“getManager(address)”: {
“params”: {
“_addr”: “要为其返回 manager 的地址。”
},
“return”: “给定地址的 manager 的地址。”
},
“implementsERC165Interface(address,bytes4)”: {
“params”: {
“_contract”: “要检查的合约的地址。”,
“_interfaceId”: “要检查的 ERC165 接口。”
},
“return”: “如果’_contract’实现了’_interfaceId’,则返回 True,否则返回 false。”
},
“implementsERC165InterfaceNoCache(address,bytes4)”: {
“params”: {
“_contract”: “要检查的合约的地址。”,
“_interfaceId”: “要检查的 ERC165 接口。”
},
“return”: “如果’_contract’实现了’_interfaceId’,则返回 True,否则返回 false。”
},
“interfaceHash(string)”: {
“params”: {
“_interfaceName”: “接口的名称。”
},
“return”: “接口名称的 keccak256 哈希值。”
},
“setInterfaceImplementer(address,bytes32,address)”: {
“params”: {
“_addr”: “要设置接口的地址。(如果’_addr’为零地址,则假定为’msg.sender’。)”,
“_implementer”: “为’_addr’实现’_interfaceHash’的合约地址。”,
“_interfaceHash”: “接口名称的 Keccak256 哈希值,作为字符串。例如,’web3.utils.keccak256("ERC777TokensRecipient")’ 对于 ‘ERC777TokensRecipient’ 接口。”
}
},
“setManager(address,address)”: {
“params”: {
“_addr”: “要为其设置新 manager 的地址。”,
“_newManager”: “‘addr’的新 manager 的地址。(传递’0x0’以将 manager 重置为’_addr’。)”
}
},
“updateERC165Cache(address,bytes4)”: {
“params”: {
“_contract”: “要为其更新缓存的合约的地址。”,
“_interfaceId”: “要为其更新缓存的 ERC165 接口。”
}
}
},
“title”: “ERC1820 伪自检注册表合约”
},
“userdoc”: {
“methods”: {
“getInterfaceImplementer(address,bytes32)”: {
“notice”: “查询地址是否实现接口以及通过哪个合约实现。”
},
“getManager(address)”: {
“notice”: “获取地址的 manager。”
},
“implementsERC165InterfaceNoCache(address,bytes4)”: {
“notice”: “检查合约是否实现了 ERC165 接口,而不使用或更新缓存。”
},
“interfaceHash(string)”: {
“notice”: “计算给定接口名称的 keccak256 哈希值。”
},
“setInterfaceImplementer(address,bytes32,address)”: {
“notice”: “设置合约,该合约为特定地址实现特定接口。只有为该地址定义的 manager 才能设置它。(在设置新 manager 之前,每个地址都是自己的 manager。)”
},
“setManager(address,address)”: {
“notice”: “将’_newManager’设置为’_addr’的 manager。新的 manager 将能够为’_addr’调用’setInterfaceImplementer’。”
},
“updateERC165Cache(address,bytes4)”: {
“notice”: “使用合约是否实现了 ERC165 接口来更新缓存。”
}
},
“notice”: “此合约是 ERC1820 注册表的官方实现。有关更多详细信息,请参见 https://eips.ethereum.org/EIPS/eip-1820”
}
},
“settings”: {
“compilationTarget”: {
“./contracts/ERC1820Registry.sol”: “ERC1820Registry”
},
“evmVersion”: “byzantium”,
“libraries”: {},
“optimizer”: {
“enabled”: true,
“runs”: 200
},
“remappings”: []
},
“sources”: {
“./contracts/ERC1820Registry.sol”: {
“content”: “/* ERC1820 Pseudo-introspection Registry Contract\n * This standard defines a universal registry smart contract where any address (contract or regular account) can\n * register which interface it supports and which smart contract is responsible for its implementation.\n *\n * Written in 2019 by Jordi Baylina and Jacques Dafflon\n *\n * To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to\n * this software to the public domain worldwide. This software is distributed without any warranty.\n *\n * You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see\n * http://creativecommons.org/publicdomain/zero/1.0/.\n *\n * ███████╗██████╗ ██████╗ ██╗ █████╗ ██████╗ ██████╗\n * ██╔════╝██╔══██╗██╔════╝███║██╔══██╗╚════██╗██╔═████╗\n * █████╗ ██████╔╝██║ ╚██║╚█████╔╝ █████╔╝██║██╔██║\n * ██╔══╝ ██╔══██╗██║ ██║██╔══██╗██╔═══╝ ████╔╝██║\n * ███████╗██║ ██║╚██████╗ ██║╚█████╔╝███████╗╚██████╔╝\n * ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚══════╝ ╚═════╝\n *\n * ██████╗ ███████╗ ██████╗ ██╗███████╗████████╗██████╗ ██╗ ██╗\n * ██╔══██╗██╔════╝██╔════╝ ██║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝\n * ██████╔╝█████╗ ██║ ███╗██║███████╗ ██║ ██████╔╝ ╚████╔╝\n * ██╔══██╗██╔══╝ ██║ ██║██║╚════██║ ██║ ██╔══██╗ ╚██╔╝\n * ██║ ██║███████╗╚██████╔╝██║███████║ ██║ ██║ ██║ ██║\n * ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝\n *\n */\npragma solidity 0.5.3;\n// IV is value needed to have a vanity address starting with ‘0x1820’.\n// IV: 53759\n\n/// @dev The interface a contract MUST implement if it is the implementer of\n/// some (other) interface for any address other than itself.\ninterface ERC1820ImplementerInterface {\n /// @notice Indicates whether the contract implements the interface ‘interfaceHash’ for the address ‘addr’ or not.\n /// @param interfaceHash keccak256 hash of the name of the interface\n /// @param addr Address for which the contract will implement the interface\n /// @return ERC1820_ACCEPT_MAGIC only if the contract implements ‘interfaceHash’ for the address ‘addr’.\n function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);\n}\n\n\n/// @title ERC1820 Pseudo-introspection Registry Contract\n/// @author Jordi Baylina and Jacques Dafflon\n/// @notice This contract is the official implementation of the ERC1820 Registry.\n/// @notice For more details, see https://eips.ethereum.org/EIPS/eip-1820\ncontract ERC1820Registry {\n /// @notice ERC165 Invalid ID.\n bytes4 constant internal INVALID_ID = 0xffffffff;\n /// @notice Method ID for the ERC165 supportsInterface method (= bytes4(keccak256('supportsInterface(bytes4)'))
).\n bytes4 constant internal ERC165ID = 0x01ffc9a7;\n /// @notice Magic value which is returned if a contract implements an interface on behalf of some other address.\n bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));\n\n /// @notice mapping from addresses and interface hashes to their implementers.\n mapping(address => mapping(bytes32 => address)) internal interfaces;\n /// @notice mapping from addresses to their manager.\n mapping(address => address) internal managers;\n /// @notice flag for each address and erc165 interface to indicate if it is cached.\n mapping(address => mapping(bytes4 => bool)) internal erc165Cached;\n\n /// @notice Indicates a contract is the ‘implementer’ of ‘interfaceHash’ for ‘addr’.\n event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);\n /// @notice Indicates ‘newManager’ is the address of the new manager for ‘addr’.\n event ManagerChanged(address indexed addr, address indexed newManager);\n\n /// @notice Query if an address implements an interface and through which contract.\n /// @param _addr Address being queried for the implementer of an interface.\n /// (If ‘_addr’ is the zero address then ‘msg.sender’ is assumed.)\n /// @param _interfaceHash Keccak256 hash of the name of the interface as a string.\n /// E.g., ‘web3.utils.keccak256("ERC777TokensRecipient")’ for the ‘ERC777TokensRecipient’ interface.\n /// @return The address of the contract which implements the interface ‘_interfaceHash’ for ‘_addr’\n /// or ‘0’ if ‘_addr’ did not register an implementer for this interface.\n function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) {\n address addr = _addr == address(0) ? msg.sender : _addr;\n if (isERC165Interface(_interfaceHash)) {\n bytes4 erc165InterfaceHash = bytes4(_interfaceHash);\n return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : address(0);\n }\n return interfaces[addr][_interfaceHash];\n }\n\n /// @notice Sets the contract which implements a specific interface for an address.\n /// Only the manager defined for that address can set it.\n /// (Each address is the manager for itself until it sets a new manager.)\n /// @param _addr Address for which to set the interface.\n /// (If ‘_addr’ is the zero address then ‘msg.sender’ is assumed.)\n /// @param _interfaceHash Keccak256 hash of the name of the interface as a string.\n /// E.g., ‘web3.utils.keccak256("ERC777TokensRecipient")’ for the ‘ERC777TokensRecipient’ interface.\n /// @param _implementer Contract address implementing ‘_interfaceHash’ for ‘_addr’.\n function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {\n address addr = _addr == address(0) ? msg.sender : _addr;\n require(getManager(addr) == msg.sender, "Not the manager");\n\n require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash");\n if (_implementer != address(0) && _implementer != msg.sender) {\n require(\n ERC1820ImplementerInterface(_implementer)\n .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC,\n "Does not implement the interface"\n );\n }\n interfaces[addr][_interfaceHash] = _implementer;\n emit InterfaceImplementerSet(addr, _interface> identifier: 3d584063
parameters
_addr
: 要为其返回管理器的地址。 returns: 给定地址的管理器地址。
理由
该标准为任何类型的地址(外部拥有的和合约)提供了一种实现接口的方式,并且可能将接口的实现委托给代理合约。 这种委托给代理合约对于外部拥有的账户是必要的,并且有助于避免重新部署现有的合约,例如多重签名和 DAO。
注册表还可以充当 ERC-165 缓存,以便在查找合约是否实现了特定的 ERC-165 接口时节省 gas。
此缓存被有意地保持简单,没有自动缓存更新或失效。
任何人都可以通过调用 updateERC165Cache
函数轻松且安全地更新任何接口和任何合约的缓存。
注册表使用一种无密钥部署方法进行部署,该方法依赖于一次性部署地址,以确保没有人控制注册表,从而确保信任。
向后兼容性
此标准与 ERC-165 向后兼容,因为两种方法可以实现而不会相互冲突。
测试用例
请查看 0xjac/ERC1820 存储库以获取完整的测试套件。
实现
该实现在 repo 中可用:0xjac/ERC1820。
版权
通过 CC0 放弃版权和相关权利。
Citation
Please cite this document as:
Jordi Baylina <jordi@baylina.cat>, Jacques Dafflon <mail@0xjac.com>, "ERC-1820: 伪自省注册表合约," Ethereum Improvement Proposals, no. 1820, March 2019. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1820.