# EIP 1820: 伪自省注册表合约
作者 | 状态 | 类型 | 分类 | 依赖 | 创建时间 | 取代 |
---|---|---|---|---|---|---|
Jordi Baylina, Jacques Dafflon | Final | Standards Track | ERC | 165, 214 | 2019-03-04 | 820 |
ERC1820 取代了 ERC820, ERC1820 修复了 Solidty 0.5 更新带来的与ERC165不兼容的问题。这里是官方声明erc1820-annoucement,Bug描述erc820-bug,以及Bug 修复erc820-fix,除了这个bug之外,ERC1820 功能上等价于 ERC820。
# 简要说明
ERC1820标准定义了一个通用注册表合约,任何地址(合约或普通用户帐户)都可以注册它支持的接口以及哪个智能合约负责接口实现。
ERC1820标准向后兼容 ERC165 。
# 摘要
ERC1820标准定义智能合约和普通用户帐户可以向注册表发布其实现了哪些功能(普通用户帐户通过代理合约实现)
任何人都可以查询此注册表,询问哪个地址是否实现了给定的接口以及哪个智能合约处理实现逻辑。
ERC1820注册表合约可以部署在任何链上,并在所有链上的地址是相同的。
接口的后28个字节都为0的话,会认为是 ERC165 接口,并且注册表将转发到合约以查看是否实现了接口。
此合约还充当 ERC165 缓存,以减少 gas 消耗。
# 动机
在以太坊上有很多方法定义伪自省,ERC165不能由普通用户帐户使用。 ERC672 则使用了反向 ENS,反向 ENS 有两个问题:增加了不必要的复杂度,其次,ENS 是由多签控制的中心化合约。 从理论上讲,这种多签能够修改系统。
ERC1820标准比 ERC672 简单得多,并且完全去中心化。
此标准还为所有链提供一个唯一(相同的)地址。从而解决了解决不同链的查找注册表地址的问题。
# 规范
# ERC1820注册表合约
以下是复制的 ERC1820 注册表合约代码
pragma solidity 0.5.3;
// IV is value needed to have a vanity address starting with '0x1820'.
// 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 伪自省注册表合约
/// @notice 该合约是ERC1820注册表的官方实现。
contract ERC1820Registry {
/// @notice ERC165 无效 ID.
bytes4 constant internal INVALID_ID = 0xffffffff;
/// @notice ERC165 的 supportsInterface 接口ID (= `bytes4(keccak256('supportsInterface(bytes4)'))`).
bytes4 constant internal ERC165ID = 0x01ffc9a7;
/// @notice 如果合约代表某个其他地址实现接口,则返回Magic值。
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接口的flag,指示是否被缓存。
mapping(address => mapping(bytes4 => bool)) internal erc165Cached;
/// @notice 表示合约是'addr'的'interfaceHash'的'实现者'。
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 返回实现者的地址,没有实现返回 ‘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");
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 为地址_addr 设置新的管理员地址_newManager, 新的管理员能给'_addr' 调用 'setInterfaceImplementer' 设置是实现者。
/// (传 '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 获取地址 _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)) {
return _addr;
} else {
return managers[_addr];
}
}
/// @notice 计算给定名称的接口的keccak256哈希值。
function interfaceHash(string calldata _interfaceName) external pure returns(bytes32) {
return keccak256(abi.encodePacked(_interfaceName));
}
/* --- ERC165 相关方法 --- */
/// @notice 更新合约是否实现了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 True 如果合约实现了接口返回 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 True 如果合约实现了接口返回 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 检查_interfaceHash 是否是ERC165接口(以28个零结尾)。
/// @param _interfaceHash 要检查接口 hash。
/// @return 如果 '_interfaceHash'是ERC165接口返回 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) // Find empty storage location using "free memory pointer"
mstore(x, erc165ID) // Place signature at beginning of empty storage
mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature
success := staticcall(
30000, // 30k gas
_contract, // To addr
x, // Inputs are stored at location x
0x24, // Inputs are 36 (4 + 32) bytes long
x, // Store output over input (saves space)
0x20 // Outputs are 32 bytes long
)
result := mload(x) // Load the result
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# 部署合约交易
以下是在任何链上部署注册表合约的原始交易,必须使用这个交易进行部署。
0xf90a388085174876e800830c35008080b909e5608060405234801561001057600080fd5b506109c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063a41e7d5111610078578063a41e7d51146101d4578063aabbb8ca1461020a578063b705676514610236578063f712f3e814610280576100a5565b806329965a1d146100aa5780633d584063146100e25780635df8122f1461012457806365ba36c114610152575b600080fd5b6100e0600480360360608110156100c057600080fd5b50600160a060020a038135811691602081013591604090910135166102b6565b005b610108600480360360208110156100f857600080fd5b5035600160a060020a0316610570565b60408051600160a060020a039092168252519081900360200190f35b6100e06004803603604081101561013a57600080fd5b50600160a060020a03813581169160200135166105bc565b6101c26004803603602081101561016857600080fd5b81019060208101813564010000000081111561018357600080fd5b82018360208201111561019557600080fd5b803590602001918460018302840111640100000000831117156101b757600080fd5b5090925090506106b3565b60408051918252519081900360200190f35b6100e0600480360360408110156101ea57600080fd5b508035600160a060020a03169060200135600160e060020a0319166106ee565b6101086004803603604081101561022057600080fd5b50600160a060020a038135169060200135610778565b61026c6004803603604081101561024c57600080fd5b508035600160a060020a03169060200135600160e060020a0319166107ef565b604080519115158252519081900360200190f35b61026c6004803603604081101561029657600080fd5b508035600160a060020a03169060200135600160e060020a0319166108aa565b6000600160a060020a038416156102cd57836102cf565b335b9050336102db82610570565b600160a060020a031614610339576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b6103428361092a565b15610397576040805160e560020a62461bcd02815260206004820152601a60248201527f4d757374206e6f7420626520616e204552433136352068617368000000000000604482015290519081900360640190fd5b600160a060020a038216158015906103b85750600160a060020a0382163314155b156104ff5760405160200180807f455243313832305f4143434550545f4d4147494300000000000000000000000081525060140190506040516020818303038152906040528051906020012082600160a060020a031663249cb3fa85846040518363ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018083815260200182600160a060020a0316600160a060020a031681526020019250505060206040518083038186803b15801561047e57600080fd5b505afa158015610492573d6000803e3d6000fd5b505050506040513d60208110156104a857600080fd5b5051146104ff576040805160e560020a62461bcd02815260206004820181905260248201527f446f6573206e6f7420696d706c656d656e742074686520696e74657266616365604482015290519081900360640190fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03818116600090815260016020526040812054909116151561059a5750806105b7565b50600160a060020a03808216600090815260016020526040902054165b919050565b336105c683610570565b600160a060020a031614610624576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b81600160a060020a031681600160a060020a0316146106435780610646565b60005b600160a060020a03838116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519184169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a35050565b600082826040516020018083838082843780830192505050925050506040516020818303038152906040528051906020012090505b92915050565b6106f882826107ef565b610703576000610705565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b600080600160a060020a038416156107905783610792565b335b905061079d8361092a565b156107c357826107ad82826108aa565b6107b85760006107ba565b815b925050506106e8565b600160a060020a03
90811660009081526020818152604080832086845290915290205416905092915050565b6000808061081d857f01ffc9a70000000000000000000000000000000000000000000000000000000061094c565b909250905081158061082d575080155b1561083d576000925050506106e8565b61084f85600160e060020a031961094c565b909250905081158061086057508015155b15610870576000925050506106e8565b61087a858561094c565b909250905060018214801561088f5750806001145b1561089f576001925050506106e8565b506000949350505050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff1615156108f2576108eb83836107ef565b90506106e8565b50600160a060020a03808316600081815260208181526040808320600160e060020a0319871684529091529020549091161492915050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565b6040517f01ffc9a7000000000000000000000000000000000000000000000000000000008082526004820183905260009182919060208160248189617530fa90519096909550935050505056fea165627a7a72305820377f4a2d4301ede9949f163f319021a6e9c687c292a5e2b2c4734c126b524e6c00291ba01820182018201820182018201820182018201820182018201820182018201820a01820182018201820182018201820182018201820182018201820182018201820
2
3
4
交易结尾的“1820”的字符串是签名的 r
和 s
。这是人为产生的确定性模式,任何人都可以推断出部署账号而又没有人知道私钥。
# 部署方法
该合约将使用无密钥部署方法(也称为Nick的方法)进行部署,该方法依赖于一次性地址。 (详细信息,请参阅Nick的文章)。 此方法的工作原理如下:
- 从新的随机帐户生成部署合约的交易。
- 此交易不得使用EIP155,以便支持任何链。
- 该交易必须具有相对较高的GAS价格以便部署在任何一条链上。 在这种情况下,它将是 100 Gwei。
用以下的值设置交易签名的
v
,r
,s
:v: 27, r: 0x1820182018201820182018201820182018201820182018201820182018201820' s: 0x1820182018201820182018201820182018201820182018201820182018201820'
1
2
3
由'1820重复模式构成的
r和
s`值 是人为确定性生成的可预测的“随机数”。
从交易签名还原出签名账号,即一次性部署帐户。
因此,我们获得了一个可以广播该交易的帐户,也可以保证没有人知道该帐户的私钥。
发送 0.08 ether 到这个一次性部署帐户。
广播部署交易。
此操作可以在任何链上完成,保证合约地址始终相同,并且任何人无法用一个其他的合约获得这个地址。
# 一次性使用的注册表部署帐户
0xa990077c3205cbDf861e17Fa532eeB069cE9fF96
此帐户是通过对其交易签名进行逆向工程生成的。因此没有人知道私钥,但是它是部署合约的有效签名者。
部署注册表,必须先将0.08以太发送到此帐户
# 注册表合约地址
0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
注册表合约地址在每个链的地址都一样,就是上面这个地址。
合约元数据 ./contracts/ERC1820Registry.sol
{
"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": "Address being queried for the implementer of an interface. (If '_addr' is the zero address then 'msg.sender' is assumed.)",
"_interfaceHash": "Keccak256 hash of the name of the interface as a string. E.g., 'web3.utils.keccak256(\"ERC777TokensRecipient\")' for the 'ERC777TokensRecipient' interface."
},
"return": "The address of the contract which implements the interface '_interfaceHash' for '_addr' or '0' if '_addr' did not register an implementer for this interface."
},
"getManager(address)": {
"params": {
"_addr": "Address for which to return the manager."
},
"return": "Address of the manager for a given address."
},
"implementsERC165Interface(address,bytes4)": {
"params": {
"_contract": "Address of the contract to check.",
"_interfaceId": "ERC165 interface to check."
},
"return": "True if '_contract' implements '_interfaceId', false otherwise."
},
"implementsERC165InterfaceNoCache(address,bytes4)": {
"params": {
"_contract": "Address of the contract to check.",
"_interfaceId": "ERC165 interface to check."
},
"return": "True if '_contract' implements '_interfaceId', false otherwise."
},
"interfaceHash(string)": {
"params": {
"_interfaceName": "Name of the interface."
},
"return": "The keccak256 hash of an interface name."
},
"setInterfaceImplementer(address,bytes32,address)": {
"params": {
"_addr": "Address for which to set the interface. (If '_addr' is the zero address then 'msg.sender' is assumed.)",
"_implementer": "Contract address implementing '_interfaceHash' for '_addr'.",
"_interfaceHash": "Keccak256 hash of the name of the interface as a string. E.g., 'web3.utils.keccak256(\"ERC777TokensRecipient\")' for the 'ERC777TokensRecipient' interface."
}
},
"setManager(address,address)": {
"params": {
"_addr": "Address for which to set the new manager.",
"_newManager": "Address of the new manager for 'addr'. (Pass '0x0' to reset the manager to '_addr'.)"
}
},
"updateERC165Cache(address,bytes4)": {
"params": {
"_contract": "Address of the contract for which to update the cache.",
"_interfaceId": "ERC165 interface for which to update the cache."
}
}
},
"title": "ERC1820 Pseudo-introspection Registry Contract"
},
"userdoc": {
"methods": {
"getInterfaceImplementer(address,bytes32)": {
"notice": "Query if an address implements an interface and through which contract."
},
"getManager(address)": {
"notice": "Get the manager of an address."
},
"implementsERC165InterfaceNoCache(address,bytes4)": {
"notice": "Checks whether a contract implements an ERC165 interface or not without using nor updating the cache."
},
"interfaceHash(string)": {
"notice": "Compute the keccak256 hash of an interface given its name."
},
"setInterfaceImplementer(address,bytes32,address)": {
"notice": "Sets the contract which implements a specific interface for an address. Only the manager defined for that address can set it. (Each address is the manager for itself until it sets a new manager.)"
},
"setManager(address,address)": {
"notice": "Sets '_newManager' as manager for '_addr'. The new manager will be able to call 'setInterfaceImplementer' for '_addr'."
},
"updateERC165Cache(address,bytes4)": {
"notice": "Updates the cache with whether the contract implements an ERC165 interface or not."
}
},
"notice": "This contract is the official implementation of the ERC1820 Registry.For more details, see https://learnblockchain.cn/docs/eips/eip-1820.html"
}
},
"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, _interfaceHash, _implementer);\n }\n\n /// @notice Sets '_newManager' as manager for '_addr'.\n /// The new manager will be able to call 'setInterfaceImplementer' for '_addr'.\n /// @param _addr Address for which to set the new manager.\n /// @param _newManager Address of the new manager for 'addr'. (Pass '0x0' to reset the manager to '_addr'.)\n function setManager(address _addr, address _newManager) external {\n require(getManager(_addr) == msg.sender, \"Not the manager\");\n managers[_addr] = _newManager == _addr ? address(0) : _newManager;\n
emit ManagerChanged(_addr, _newManager);\n }\n\n /// @notice Get the manager of an address.\n /// @param _addr Address for which to return the manager.\n /// @return Address of the manager for a given address.\n function getManager(address _addr) public view returns(address) {\n // By default the manager of an address is the same address\n if (managers[_addr] == address(0)) {\n return _addr;\n } else {\n return managers[_addr];\n }\n }\n\n /// @notice Compute the keccak256 hash of an interface given its name.\n /// @param _interfaceName Name of the interface.\n /// @return The keccak256 hash of an interface name.\n function interfaceHash(string calldata _interfaceName) external pure returns(bytes32) {\n return keccak256(abi.encodePacked(_interfaceName));\n }\n\n /* --- ERC165 Related Functions --- */\n /* --- Developed in collaboration with William Entriken. --- */\n\n /// @notice Updates the cache with whether the contract implements an ERC165 interface or not.\n /// @param _contract Address of the contract for which to update the cache.\n /// @param _interfaceId ERC165 interface for which to update the cache.\n function updateERC165Cache(address _contract, bytes4 _interfaceId) external {\n interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache(\n _contract, _interfaceId) ? _contract : address(0);\n erc165Cached[_contract][_interfaceId] = true;\n }\n\n /// @notice Checks whether a contract implements an ERC165 interface or not.\n // If the result is not cached a direct lookup on the contract address is performed.\n // If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling\n // 'updateERC165Cache' with the contract address.\n /// @param _contract Address of the contract to check.\n /// @param _interfaceId ERC165 interface to check.\n /// @return True if '_contract' implements '_interfaceId', false otherwise.\n function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) {\n if (!erc165Cached[_contract][_interfaceId]) {\n return implementsERC165InterfaceNoCache(_contract, _interfaceId);\n }\n return interfaces[_contract][_interfaceId] == _contract;\n }\n\n /// @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache.\n /// @param _contract Address of the contract to check.\n /// @param _interfaceId ERC165 interface to check.\n /// @return True if '_contract' implements '_interfaceId', false otherwise.\n function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) {\n uint256 success;\n uint256 result;\n\n (success, result) = noThrowCall(_contract, ERC165ID);\n if (success == 0 || result == 0) {\n return false;\n }\n\n (success, result) = noThrowCall(_contract, INVALID_ID);\n if (success == 0 || result != 0) {\n return false;\n }\n\n (success, result) = noThrowCall(_contract, _interfaceId);\n if (success == 1 && result == 1) {\n return true;\n }\n return false;\n }\n\n /// @notice Checks whether the hash is a ERC165 interface (ending with 28 zeroes) or not.\n /// @param _interfaceHash The hash to check.\n /// @return True if '_interfaceHash' is an ERC165 interface (ending with 28 zeroes), false otherwise.\n function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) {\n return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0;\n }\n\n /// @dev Make a call on a contract without throwing if the function does not exist.\n function noThrowCall(address _contract, bytes4 _interfaceId)\n internal view returns (uint256 success, uint256 result)\n {\n bytes4 erc165ID = ERC165ID;\n\n assem
bly {\n let x := mload(0x40) // Find empty storage location using \"free memory pointer\"\n mstore(x, erc165ID) // Place signature at beginning of empty storage\n mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature\n\n success := staticcall(\n 30000, // 30k gas\n _contract, // To addr\n x, // Inputs are stored at location x\n 0x24, // Inputs are 36 (4 + 32) bytes long\n x, // Store output over input (saves space)\n 0x20 // Outputs are 32 bytes long\n )\n\n result := mload(x) // Load the result\n }\n }\n}\n",
"keccak256": "0x64025ecebddb6e126a5075c1fd6c01de2840492668e2909cef7157040a9d1945"
}
},
"version": 1
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# 接口名称
任何接口名称使用 keccak256
进行hash ,并发送给 getInterfaceImplementer()
。
如果接口是标准的一部分,最佳做法是明确说明接口名称并链接到发布的ERC1820,以便其他人不必来这里查找这些规则。
为方便起见,注册表提供了计算哈希链上的函数:
function interfaceHash(string _interfaceName) public pure returns(bytes32)
计算给定名称的接口的keccak256哈希值。
- 接口id:
65ba36c1
- 参数 : _interfaceName 接口名称
- 接口名称的
keccak256
哈希
# 已接受的ERCs
如果接口是批准的ERC的一部分,它必须命名为 ERC###XXXXX
,其中 ###
是ERC的编号,XXXXX 应该是CamelCase中接口的名称。
该接口的含义应该在指定的ERC中定义。
例如:
keccak256("ERC20Token")
keccak256("ERC777Token")
keccak256("ERC777TokensSender")
keccak256("ERC777TokensRecipient")
# ERC165 兼容接口
与 ERC165 兼容,包括 ERC165 Cache,由 William Entriken 设计和开发。
最后28个字节为零(“0”)的任何接口都应被视为ERC165接口。
# ERC165 接口查找**
任何人都可以通过调用以下两个函数中的任意一个来检查合约是否实现ERC165接口:
implementsERC165Interface函数
function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool)
检查合约是否实现ERC165接口。
如果结果未缓存,则对合约地址上直接查找函数。
注意: 如果结果未缓存或缓存已过期,则必须通过手动调用updateERC165Cache
来更新缓存。
(详细信息可参阅ERC165缓存。)
- 接口id:
f712f3e8
- 参数:
- _contract :检查的合约地址
- _interfaceId :ERC165 接口id
- 返回值:实现了返回 true, 否则false
implementsERC165InterfaceNoCache函数
function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool)
在不使用或更新缓存的情况下检查合约是否实现ERC165接口
- 接口id:
b7056765
- 参数:
- _contract :检查的合约地址
- _interfaceId :ERC165 接口id
- 返回值:实现了返回 true, 否则false
# ERC165 缓存
可以手动缓存合约是否实现ERC165接口以节省gas。
如果合约动态地更改其接口并依赖于ERC1820注册表的ERC165缓存,则必须手动更新缓存(没有自动缓存失效或缓存更新的机制)。
理想情况下,合约应该在更改其接口时自动更新缓存。不过任何人都可以代表合约来更新缓存。
缓存更新必须使用 updateERC165Cache
函数完成:
function updateERC165Cache(address _contract, bytes4 _interfaceId) external
- 接口id:
a41e7d51
- 参数:
- _contract :要更新缓存的合约地址
- _interfaceId :ERC165 接口id
# 私有用户定义接口
该方案是可扩展的。大家可以编写自己的接口名称并让其他人实现它,然后检查这些实现。但请不要与上面保留的名称冲突。
# 为接口设置实现地址
可以为任何地址设置接口实现的的合约,必须调用ERC1820注册表的以下函数:
function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external
设置实现某个地址上某个接口的合约地址。需要由管理员来设置。每个地址是他自己的管理员,查看下面管理员部分了解详情。
注意: 如果 _addr
和 _implementer
是两个不同的地址,则:
_implementer
必须实现ERC1820ImplementerInterface
接口(下面详细介绍)。- 在
_implementer
上用函数_addr
_interfaceHash
调用canImplementInterfaceForAddress
时,必须返回ERC1820_ACCEPT_MAGIC
。
注意: _interfaceHash
不能是 ERC165 接口,即不能以28个字节0结尾。
注意: _addr
可以为 0
, 则假定为 msg.sender
。
此默认值通过multisig简化了交互,其中要签名的交易数据是常量,而不用管multisig实例的地址。
- 接口id:
29965a1d
- 参数:
- _addr: 要关联的地址(如果为
0
, 则假定为msg.sender
) - _interfaceHash 实现接口的Keccak256 hash, 如
web3.utils.keccak256('ERC777TokensRecipient')
用于ERC777TokensRecipient接口 - _implementer : 实现合约地址
- _addr: 要关联的地址(如果为
# 获取地址接口的实现合约
任何人都可以使用 getInterfaceImplementer
函数查询ERC1820注册表以获取代表某个地址实现接口的合约的地址。
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address)
查询地址是否实现了接口以及通过哪个合约实现。
注意: 如果_interfaceHash
的最后28个字节是零(0
),那么前4个字节被认为是ERC165接口,注册表合约应该将调用转发到 _addr
合约,检查它是否实现了 ERC165接口(_interfaceHash
的前4个字节)。
注册表合约还应缓存ERC165查询以减少gas消耗。 任何人都可以调用 erc165UpdateCache
函数来更新合约是否实现了一个接口。
注意: _addr
可以为 0
, 则假定为 msg.sender
。此缺省值与setInterfaceImplementer
函数的行为一致,并通过multisigs简化了交互,其中要签名的交易数据是常量,而不管multisig实例的地址如何。
- 接口id:
aabbb8ca
- 参数:
- _addr: 要查询的地址(如果为
0
, 则假定为msg.sender
) - _interfaceHash 接口名的Keccak256 hash, 如
web3.utils.keccak256('ERC777Token')
- _addr: 要查询的地址(如果为
- 返回: 实现合约地址, 如果没有返回 0 地址。
# 接口实现 (ERC1820ImplementerInterface
)
interface ERC1820ImplementerInterface {
/// @notice 指示合约是否为地址 “addr” 实现接口 “interfaceHash”。
/// @param interfaceHash interfaceHash 接口名称的 keccak256 哈希值
/// @param addr 为哪一个地址实现接口
/// @return ERC1820_ACCEPT_MAGIC 只有当合约为地址'addr'实现'interfaceHash'时返回 ERC1820_ACCEPT_MAGIC
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);
}
2
3
4
5
6
7
任何合约要被注册为某地址的接口实现必须实现上面的接口。
此外,如果它代表不同的地址实现接口,合约也必须实现上面的ERC1820ImplementerInterface
。
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32)
示合约是否为地址 “addr” 实现接口 “interfaceHash”。
当合约为地址'addr'实现'interfaceHash'时必须返回 ERC1820_ACCEPT_MAGIC
,如果没有实现一定不要返回ERC1820_ACCEPT_MAGIC
。
- 接口id:
f0083250
- 参数:
- _interfaceHash 接口hash
- addr: 要实现接口的地址
- 返回: 仅当实现了返回 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
。
# 管理员
地址管理员(常规帐户或合约)是唯一允许注册地址接口实现的实体。 默认情况下,任何地址都是自己的管理员。
管理员可以通过在注册表合约上调用“setManager”将其角色转移到另一个地址。
setManager
函数
function setManager(address _addr, address _newManager) external
设置 _newManager
作为 _addr
的管理员。
新的管理员可以为 _addr
调用 setInterfaceImplementer
。
如果 _newManager
是 0x0
, 则管理员重置为自身。
- 接口id:
5df8122f
- 参数:
- _addr: 要设置的地址
- _newManager: 新的管理员
getManager
函数
function getManager(address _addr) public view returns(address)
获取地址的管理员
- 接口id:
3d584063
- 参数:
- _addr: 要查询的地址
- 返回地址的管理员
# 原理阐述
该标准为任何类型的地址(外部用户地址和合约)提供了一种实现接口的方法,并可能将接口的实现委托给代理合约。 委托给代理合约对于用户帐户地址是必要的,并且有助于避免重新部署如多签和DAO之类的现有合约。
注册表合约还可以充当ERC165缓存,以便在查找合约实现特定ERC165接口时节省Gas。 此实现特意保持缓存简单,因此没有自动缓存更新或失效机制。
任何人都可以通过调用updateERC165Cache
函数轻松安全地更新任何接口和任何合约的缓存。
使用依赖于一次性部署地址的无密钥部署方法部署注册表,以确保没有人控制注册表,从而确保信任。
# 向后兼容性
该标准与ERC165向后兼容,两者的方法都实现了,不会相互冲突。
# 测试用例
查看 0xjac/ERC1820 获取测试用例。
# 实现
代码实现在: 0xjac/ERC1820.