ERC-820: 伪自省注册表合约
Authors | Jordi Baylina <jordi@baylina.cat>, Jacques Dafflon <jacques@dafflon.tech> |
---|---|
Created | 2018-01-05 |
Requires | EIP-165, EIP-214 |
Table of Contents
:information_source: ERC-1820 已取代 ERC-820。 :information_source:
ERC-1820 修复了 Solidty 0.5 更新引入的 ERC-165 逻辑中的不兼容性。
查看 官方公告,以及关于 bug 和 修复 的评论。
除了此修复之外,ERC-1820 在功能上等同于 ERC-820。
简单总结
此标准定义了一个通用注册表智能合约,任何地址(合约或常规账户)都可以在其中注册它支持的接口以及哪个智能合约负责其实现。
此标准保持与 ERC-165 的向后兼容性。
摘要
此标准定义了一个注册表,智能合约和常规帐户可以在其中发布它们实现的功能 - 无论是直接还是通过代理合约。
任何人都可以查询此注册表,以询问特定地址是否实现了给定的接口以及哪个智能合约处理其实现。
此注册表可以部署在任何链上,并在所有链上共享相同的地址。
最后 28 个字节为零 (0
) 的接口被认为是 ERC-165 接口,此注册表应将调用转发到合约以查看它是否实现了该接口。
此合约还可以作为 ERC-165 缓存,以减少 gas 消耗。
动机
在以太坊中已经有不同的方法来定义伪自省。第一个是 ERC-165,它具有不能被常规帐户使用的限制。第二次尝试是 ERC-672,它使用反向 ENS。使用反向 ENS 存在两个问题。首先,它不必要地复杂,其次,ENS 仍然是由多重签名控制的中心化合约。从理论上讲,这个多重签名能够修改系统。
此标准比 ERC-672 简单得多,并且是完全去中心化的。
此标准还为所有链提供了一个唯一地址。从而解决了为不同链解析正确注册表地址的问题。
规范
ERC-820 注册表智能合约
这是 ERC820 registry smart contract 代码的精确副本。
/* ERC820 伪自省注册表合约
* 此标准定义了一个通用注册表智能合约,任何地址
* (合约或常规帐户) 都可以在其中注册它支持的接口以及
* 哪个智能合约负责其实现。
*
* 由 Jordi Baylina 和 Jacques Dafflon 于 2018 年编写
*
* 在法律允许的范围内,作者已将此软件的所有版权以及相关和邻近权利
* 贡献给全球的公共领域。 此软件不提供任何担保。
*
* 您应该已经收到 CC0 公共领域贡献声明的副本
* 与此软件一起。 如果没有,请参阅
* <https://creativecommons.org/publicdomain/zero/1.0/>。
*
* ███████╗██████╗ ██████╗ █████╗ ██████╗ ██████╗
* ██╔════╝██╔══██╗██╔════╝██╔══██╗╚════██╗██╔═████╗
* █████╗ ██████╔╝██║ ╚█████╔╝ █████╔╝██║██╔██║
* ██╔══╝ ██╔══██╗██║ ██╔══██╗██╔═══╝ ████╔╝██║
* ███████╗██║ ██║╚██████╗╚█████╔╝███████╗╚██████╔╝
* ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚════╝ ╚══════╝ ╚═════╝
*
* ██████╗ ███████╗ ██████╗ ██╗███████╗████████╗██████╗ ██╗ ██╗
* ██╔══██╗██╔════╝██╔════╝ ██║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝
* ██████╔╝█████╗ ██║ ███╗██║███████╗ ██║ ██████╔╝ ╚████╔╝
* ██╔══██╗██╔══╝ ██║ ██║██║╚════██║ ██║ ██╔══██╗ ╚██╔╝
* ██║ ██║███████╗╚██████╔╝██║███████║ ██║ ██║ ██║ ██║
* ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
*
*/
pragma solidity 0.4.24;
// IV 是需要拥有以 `0x820` 开头的虚地址的值。
// IV: 9513
/// @dev 如果合约是任何地址(自身除外)的某个(其他)接口的实现者,则合约必须实现的接口。
interface ERC820ImplementerInterface {
/// @notice 指示合约是否为地址“addr”实现了接口“interfaceHash”。
/// @param interfaceHash 接口名称的 keccak256 哈希
/// @param addr 合约将为其实现接口的地址
/// @return 仅当合约为地址“addr”实现“interfaceHash”时才返回 ERC820_ACCEPT_MAGIC。
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);
}
/// @title ERC820 伪自省注册表合约
/// @author Jordi Baylina 和 Jacques Dafflon
/// @notice 此合约是 ERC820 注册表的官方实现。
/// @notice 更多详细信息,请参阅 https://eips.ethereum.org/EIPS/eip-820
contract ERC820Registry {
/// @notice ERC165 无效 ID。
bytes4 constant INVALID_ID = 0xffffffff;
/// @notice ERC165 supportsInterface 方法的 Method ID(= `bytes4(keccak256('supportsInterface(bytes4)'))`)。
bytes4 constant ERC165ID = 0x01ffc9a7;
/// @notice 如果合约代表其他地址实现了接口,则返回的魔术值。
bytes32 constant ERC820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC820_ACCEPT_MAGIC"));
mapping (address => mapping(bytes32 => address)) interfaces;
mapping (address => address) managers;
mapping (address => mapping(bytes4 => bool)) erc165Cached;
/// @notice 指示合约是 `addr` 的 `interfaceHash` 的 `implementer`。
event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);
/// @notice 指示 `newManager` 是 `addr` 的新 manager 的地址。
event ManagerChanged(address indexed addr, address indexed newManager);
/// @notice 查询地址是否实现了某个 interface 以及通过哪个合约实现。
/// @param _addr 正在查询的地址,以获取 interface 的 implementer 。
/// (如果 `_addr == 0`,则假定为 “msg.sender”。)
/// @param _interfaceHash 接口名称的 keccak256 哈希值,类型为字符串。
/// 例如,[web3.utils.keccak256('ERC777Token')]。
/// @return 实现“_addr”的 interface“_interfaceHash”的合约地址
/// 如果“_addr”未为此 interface 注册 implementer,则返回 `0x0`。
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) {
address addr = _addr == 0 ? msg.sender : _addr;
if (isERC165Interface(_interfaceHash)) {
bytes4 erc165InterfaceHash = bytes4(_interfaceHash);
return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : 0;
}
return interfaces[addr][_interfaceHash];
}
/// @notice 设置合约,该合约为某个地址实现特定的 interface。
/// 只有为该地址定义的 manager 才能设置它。
/// (每个地址都是其自身的 manager,直到它设置了新 manager。)
/// @param _addr 要为其定义 interface 的地址。(如果 `_addr == 0`,则假定为 “msg.sender”。)
/// @param _interfaceHash 接口名称的 keccak256 哈希值,类型为字符串。
/// 例如,对于 `ERC777TokensRecipient` 界面,使用 `web3.utils.keccak256('ERC777TokensRecipient')`。
/// @param _implementer 实现 _addr 的 _interfaceHash 的合约地址。
function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {
address addr = _addr == 0 ? msg.sender : _addr;
require(getManager(addr) == msg.sender, "Not the manager");
require(!isERC165Interface(_interfaceHash), "Must not be a ERC165 hash");
if (_implementer != 0 && _implementer != msg.sender) {
require(
ERC820ImplementerInterface(_implementer)
.canImplementInterfaceForAddress(_interfaceHash, addr) == ERC820_ACCEPT_MAGIC,
"Does not implement the interface"
);
}
interfaces[addr][_interfaceHash] = _implementer;
emit InterfaceImplementerSet(addr, _interfaceHash, _implementer);
}
/// @notice 将 `_newManager` 设置为 `_addr` 地址的 manager。
/// 新 manager 将能够为 `_addr` 调用 `setInterfaceImplementer`。
/// @param _addr 要为其设置新 manager 的地址。
/// @param _newManager `addr` 的新 manager 的地址。
function setManager(address _addr, address _newManager) external {
require(getManager(_addr) == msg.sender, "Not the manager");
managers[_addr] = _newManager == _addr ? 0 : _newManager;
emit ManagerChanged(_addr, _newManager);
}
/// @notice 获取地址的 manager 。
/// @param _addr 要为其返回 manager 的地址。
/// @return 给定地址的 manager 的地址。
function getManager(address _addr) public view returns(address) {
// 默认情况下,地址的 manager 是相同的地址
if (managers[_addr] == 0) {
return _addr;
} else {
return managers[_addr];
}
}
/// @notice 计算给定名称的 interface 的 keccak256 哈希值。
/// @param _interfaceName interface 的名称。
/// @return interface 名称的 keccak256 哈希值。
function interfaceHash(string _interfaceName) external pure returns(bytes32) {
return keccak256(abi.encodePacked(_interfaceName));
}
/* --- ERC165 相关函数 --- */
/* --- 与 William Entriken 合作开发。--- */
/// @notice 使用合约是否实现 ERC165 interface 来更新缓存。
/// @param _contract 要为其更新缓存的合约的地址。
/// @param _interfaceId 要为其更新缓存的 ERC165 interface。
function updateERC165Cache(address _contract, bytes4 _interfaceId) external {
interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache(_contract, _interfaceId) ? _contract : 0;
erc165Cached[_contract][_interfaceId] = true;
}
/// @notice 检查合约是否实现 ERC165 interface。
/// 如果没有执行直接查找,则可以缓存结果。
/// @param _contract 要检查的合约的地址。
/// @param _interfaceId 要检查的 ERC165 interface。
/// @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 interface,不使用也不更新缓存。
/// @param _contract 要检查的合约的地址。
/// @param _interfaceId 要检查的 ERC165 interface。
/// @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 interface(以 28 个零结尾)。
/// @param _interfaceHash 要检查的哈希。
/// @return 如果哈希是 ERC165 interface(以 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
0x08, // 输入长度为 8 个字节
x, // 将输出存储在输入上 (节省空间)
0x20 // 输出长度为 32 个字节
)
result := mload(x) // 加载结果
}
}
}
部署交易
以下是必须用于在任何链上部署智能合约的原始交易。
0xf90a2a8085174876e800830c35008080b909d7608060405234801561001057600080fd5b506109b7806100206000396000f30060806040526004361061008d5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166329965a1d81146100925780633d584063146100bf5780635df8122f146100fc57806365ba36c114610123578063a41e7d5114610155578063aabbb8ca14610183578063b7056765146101a7578063f712f3e8146101e9575b600080fd5b34801561009e57600080fd5b506100bd600160a060020a036004358116906024359060443516610217565b005b3480156100cb57600080fd5b506100e0600160a060020a0360043516610512565b60408051600160a060020a039092168252519081900360200190f35b34801561010857600080fd5b506100bd600160a060020a036004351661055e565b34801561012f57600080fd5b506101436004803560248101910135610655565b60408051918252519081900360200190f35b34801561016157600080fd5b506100bd600160a060020a0360043516600160e060020a0319602435166106e3565b34801561018f57600080fd5b506100e0600160a060020a036004351660243561076d565b3480156101b357600080fd5b506101d5600160a060020a0360043516600160e060020a0319602435166107e7565b604080519115158252519081900360200190f35b3480156101f557600080fd5b506101d5600160a060020a0360043516600160e060020a03196024351661089c565b6000600160a060020a0384161561022e5783610230565b335b90503361023c82610512565b600160a060020a03161461029a576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b6102a38361091c565b156102f8576040805160e560020a62461bcd02815260206004820152601960248201527f4d757374206e6f74206265206120455243313635206861736800000000000000604482015290519081900360640190fd5b600160a060020a038216158015906103195750600160a060020a0382163314155b156104a15760405160200180807f4552433832305f4143434550545f4d414749430000000000000000000000000081525060130190506040516020818303038152906040526040518082805190602001908083835b6020831061038d5780518252601f19909201916020918201910161036e565b51815160209384036101000a6000190180199092169116179052604080519290940182900382207f249cb3fa000000000000000000000000000000000000000000000000000000008352600483018a9052600160a060020a0388811660248501529451909650938816945063249cb3fa936044808401945091929091908290030181600087803b15801561042057600080fd5b505af1158015610434573d6000803e3d6000fd5b505050506040513d602081101561044a57600080fd5b5051146104a1576040805160e560020a62461bcd02815260206004820181905260248201527f446f6573206e6f7420696d706c656d656e742074686520696e74657266616365604482015290519081900360640190fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03808216600090815260016020526040812054909116151561053c575080610559565b50600160a060020a03808216600090815260016020526040902054165b919050565b3361056883610512565b600160a060020a0316146105c6576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b81600160a060020a031681600160a060020a0316146105e557806105e8565b60005b600160a060020a03838116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519184169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a35050565b60008282604051602001808383808284378201915050925050506040516020818303038152906040526040518082805190602001908083835b602083106106ad5780518252601f19909201916020918201910161068e565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902090505b92915050565b6106ed82826107e7565b6106f85760006106fa565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b60008080600160a060020a038516156107865784610788565b335b91506107938461091c565b156107b85750826107a4828261089c565b6107af5760006107b1565b815b92506107df565b600160a060020a038083166000908152602081815260408083208884529091529020541692505b505092915050565b60008080610815857f01ffc9a70000000000000000000000000000000000000000000000000000000061093e565b9092509050811580610825575080155b1561083357600092506107df565b61084585600160e060020a031961093e565b909250905081158061085657508015155b1561086457600092506107df565b61086e858561093e565b90925090506001821480156108835750806001145b1561089157600192506107df565b506000949350505050565b600160a060020a0382166000908152600260209081526040808320600160e060```json
{
"compiler": {
"version": "0.4.24+commit.e67f0147"
},
"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 == 0`,则假定为 `msg.sender`。)",
"_interfaceHash": "接口名称的 keccak256 哈希值,以字符串形式表示。例如,`web3.utils.keccak256('ERC777Token')`。"
},
"return": "为 `_addr` 实现接口 `_interfaceHash` 的合约地址;如果 `_addr` 未为此接口注册实现者,则返回 `0x0`。"
},
"getManager(address)": {
"params": {
"_addr": "要返回管理者的地址。"
},
"return": "给定地址的管理者的地址。"
},
"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 == 0`,则假定为 `msg.sender`。)",
"_implementer": "为 `_addr` 实现 _interfaceHash 的合约地址。",
"_interfaceHash": "接口名称的 keccak256 哈希值,以字符串形式表示。例如,`web3.utils.keccak256('ERC777TokensRecipient')` 用于 `ERC777TokensRecipient` 接口。"
}
},
"setManager(address,address)": {
"params": {
"_addr": "要为其设置新管理者的地址。",
"_newManager": "`addr` 的新管理者的地址。"
}
},
"updateERC165Cache(address,bytes4)": {
"params": {
"_contract": "要为其更新缓存的合约地址。",
"_interfaceId": "要为其更新缓存的 ERC165 接口。"
}
}
},
"title": "ERC820 伪自省注册表合约"
},
"userdoc": {
"methods": {
"getInterfaceImplementer(address,bytes32)": {
"notice": "查询某个地址是否实现了某个接口,以及通过哪个合约实现。"
},
"getManager(address)": {
"notice": "获取地址的管理者。"
},
"implementsERC165Interface(address,bytes4)": {
"notice": "检查合约是否实现了 ERC165 接口。结果可能会被缓存;如果未缓存,则执行直接查找。"
},
"implementsERC165InterfaceNoCache(address,bytes4)": {
"notice": "检查合约是否实现了 ERC165 接口,而不使用或更新缓存。"
},
"interfaceHash(string)": {
"notice": "计算给定接口名称的 keccak256 哈希值。"
},
"setInterfaceImplementer(address,bytes32,address)": {
"notice": "设置合约,该合约为地址实现特定的接口。只有为该地址定义的管理者才能设置它。(每个地址都是其自身的管理者,直到它设置新的管理者。)"
},
"setManager(address,address)": {
"notice": "将 `_newManager` 设置为 `_addr` 地址的管理者。新的管理者将能够为 `_addr` 调用 `setInterfaceImplementer`。"
},
"updateERC165Cache(address,bytes4)": {
"notice": "使用合约是否实现了 ERC165 接口的信息来更新缓存。"
}
}
}
},
"settings": {
"compilationTarget": {
"./contracts/ERC820Registry.sol": "ERC820Registry"
},
"evmVersion": "byzantium",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
},
"sources": {
"./contracts/ERC820Registry.sol": {
"content": "/* ERC820 Pseudo-introspection Registry Contract\n * This standard defines a universal registry smart contract where any address\n * (contract or regular account) can register which interface it supports and\n * which smart contract is responsible for its implementation.\n *\n * Written in 2018 by Jordi Baylina and Jacques Dafflon\n *\n * To the extent possible under law, the author(s) have dedicated all copyright\n * and related and neighboring rights to this software to the public domain\n * worldwide. This software is distributed without any warranty.\n *\n * You should have received a copy of the CC0 Public Domain Dedication along\n * with this software. If not, see\n * <https://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.4.24;\n// IV is value needed to have a vanity address starting with `0x820`.\n// IV: 9513\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 ERC820ImplementerInterface {\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 ERC820_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 ERC820 Pseudo-introspection Registry Contract\n/// @author Jordi Baylina and Jacques Dafflon\n/// @notice This contract is the official implementation of the ERC820 Registry.\n/// @notice For more details, see https://eips.ethereum.org/EIPS/eip-820\ncontract ERC820Registry {\n /// @notice ERC165 Invalid ID.\n bytes4 constant INVALID_ID = 0xffffffff;\n /// @notice Method ID for the ERC165 supportsInterface method (= `bytes4(keccak256('supportsInterface(bytes4)'))`).\n bytes4 constant ERC165ID = 0x01ffc9a7;\n /// @notice Magic value which is returned if a contract implements an interface on behalf of some other address.\n bytes32 constant ERC820_ACCEPT_MAGIC = keccak256(abi.encodePacked(\"ERC820_ACCEPT_MAGIC\"));\n\n mapping (address => mapping(bytes32 => address)) interfaces;\n mapping (address => address) managers;\n mapping (address => mapping(bytes4 => bool)) 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 == 0` 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('ERC777Token')`.\n /// @return The address of the contract which implements the interface `_interfaceHash` for `_addr`\n /// or `0x0` 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 == 0 ? msg.sender : _addr;\n if (isERC165Interface(_interfaceHash)) {\n bytes4 erc165InterfaceHash = bytes4(_interfaceHash);\n return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : 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 to define the interface for. (If `_addr == 0` then `msg.sender` is assumed.)\n /// @param _interfaceHash keccak256 hash of the name of the interface as a string.\n /// For example, `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 == 0 ? msg.sender : _addr;\n require(getManager(addr) == msg.sender, \"Not the manager\");\n\n require(!isERC165Interface(_interfaceHash), \"Must not be a ERC165 hash\");\n if (_implementer != 0 && _implementer != msg.sender) {\n require(\n ERC820ImplementerInterface(_implementer)\n .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC820_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 the `_newManager` as manager for the `_addr` address.\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`.\n function setManager(address _addr, address _newManager) external {\n require(getManager(_addr) == msg.sender, \"Not the manager\");\n managers[_addr] = _newManager == _addr ? 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] == 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 _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(_contract, _interfaceId) ? _contract : 0;\n erc165Cached[_contract][_interfaceId] = true;\n }\n\n /// @notice Checks whether a contract implements an ERC165 interface or not.\n /// The result may be cached, if not a direct lookup is performed.\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 the hash is a 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 assembly {\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 0x08, // Inputs are 8 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": "0x8eecce3912a15087b3f5845d5a74af7712c93d0a8fcd6f2d40f07ed5032022ab"
}
},
"version": 1
}
</details>
Interface Name
任何接口名称都使用 keccak256
进行哈希处理,并发送到 getInterfaceImplementer()
。
如果接口是标准的一部分,最佳实践是显式声明接口名称并链接到已发布的 ERC-820,这样其他人就不必来这里查找这些规则。
为方便起见,注册表提供了一个链上计算哈希的函数:
function interfaceHash(string _interfaceName) public pure returns(bytes32)
计算给定接口名称的 keccak256 哈希值。
identifier:
65ba36c1
parameters
_interfaceName
: 接口名称。
returns: 接口名称的keccak256
哈希值。
已批准的 ERC
如果接口是已批准的 ERC 的一部分,则必须将其命名为 ERC###XXXXX
,其中 ###
是 ERC 的编号,而 XXXXX 应该是接口的 CamelCase 形式的名称。此接口的含义应在指定的 ERC 中定义。
例子:
keccak256("ERC20Token")
keccak256("ERC777Token")
keccak256("ERC777TokensSender")
keccak256("ERC777TokensRecipient")
ERC-165 兼容接口
与 ERC-165 的兼容性,包括 ERC165 Cache,已与 William Entriken 合作设计和开发。
任何最后 28 个字节为零 (0
) 的接口都应被视为 ERC-165 接口。
ERC-165 查找
任何人都可以通过调用以下两个函数之一,使用注册表显式检查合约是否实现了 ERC-165 接口:
function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool)
检查合约是否实现了 ERC-165 接口。
注意:结果会被缓存。如果缓存已过期,则必须通过调用 updateERC165Cache
来更新。(有关更多详细信息,请参见 ERC165 Cache。)
identifier:
f712f3e8
parameters
_contract
: 要检查的合约地址。
_interfaceId
: 要检查的 ERC-165 接口。
returns: 如果_contract
实现了_interfaceId
,则返回true
;否则返回 false。
function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool)
检查合约是否实现了 ERC-165 接口,而不使用或更新缓存。
identifier:
b7056765
parameters
_contract
: 要检查的合约地址。
_interfaceId
: 要检查的 ERC-165 接口。
returns: 如果_contract
实现了_interfaceId
,则返回true
;否则返回 false。
ERC-165 缓存
是否可以手动缓存合约是否实现了 ERC-165 接口,以节省 gas。
如果合约动态更改其接口并依赖于 ERC-820 注册表的 ERC-165 缓存,则必须手动更新缓存——没有自动缓存失效或缓存更新。理想情况下,合约应该在更改其接口时自动更新缓存。但是,任何人都可以代表合约更新缓存。
必须使用 updateERC165Cache
函数完成缓存更新:
function updateERC165Cache(address _contract, bytes4 _interfaceId) public
identifier:
a41e7d51
parameters
_contract
: 要为其更新缓存的合约地址。
_interfaceId
: 要为其更新缓存的 ERC-165 接口。
私有用户自定义接口
此方案是可扩展的。您可以创建自己的接口名称并提高知名度,以使其他人实现它,然后检查这些实现。玩得开心,但是请注意,您不得与上面的保留名称冲突。
为地址设置接口
对于任何要设置合约作为接口实现的地址,它必须调用 ERC-820 注册表的以下函数:
function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) public
设置合约,该合约为地址实现特定的接口。
只有为该地址定义的 manager
才能设置它。(每个地址都是其自身的管理者,有关更多详细信息,请参见 manager 部分。)
注意:如果 _addr
和 _implementer
是两个不同的地址,则:
_implementer
必须实现ERC820ImplementerInterface
(如下详述)。- 在
_implementer
上使用给定的_addr
和_interfaceHash
调用canImplementInterfaceForAddress
必须返回ERC820_ACCEPT_MAGIC
值。
注意:_interfaceHash
不得为 ERC-165 接口——不得以 28 个零 (0
) 结尾。
注意:_addr
可以为 0
,然后假定为 msg.sender
。此默认值简化了通过多重签名进行的交互,在多重签名实例的地址不变的情况下,要签名的交易数据是恒定的。
identifier:
29965a1d
parameters
_addr
: 用于定义接口的地址(如果_addr == 0
,则假定为msg.sender
)
_interfaceHash
: 接口名称的keccak256
哈希值,例如,对于 ERC777TokensRecipient 接口,web3.utils.keccak256('ERC777TokensRecipient')
_implementer
: 为_addr
实现_interfaceHash
的合约。
获取地址的接口实现
任何人都可以查询 ERC-820 注册表,以使用 getInterfaceImplementer
函数获取代表某个地址实现接口的合约地址。
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) public view returns (address)
查询地址是否实现了某个接口,以及哪个合约实现了该接口。
注意:如果 _interfaceHash
的最后 28 个字节为零 (0
),则前 4 个字节被认为是 ERC-165 接口,并且注册表应将调用转发到 _addr
处的合约,以查看它是否实现了 ERC-165 接口(_interfaceHash
的前 4 个字节)。注册表还应缓存 ERC-165 查询,以减少 gas 消耗。任何人都可以调用 erc165UpdateCache
函数来更新合约是否实现了接口。
注意:_addr
可以为 0
,然后假定为 msg.sender
。此默认值与 setInterfaceImplementer
函数的行为一致,并简化了通过多重签名进行的交互,在多重签名实例的地址不变的情况下,要签名的交易数据是恒定的。
identifier:
aabbb8ca
parameters
_addr
: 正在查询接口实现者的地址。(如果_addr == 0
,则假定为msg.sender
。)
_interfaceHash
: 接口名称的 keccak256 哈希值。例如,web3.utils.keccak256('ERC777Token')
returns: 为_addr
实现接口_interfaceHash
的合约地址;如果_addr
未为此接口注册实现者,则返回0x0
。
接口实现 (ERC820ImplementerInterface
)
interface ERC820ImplementerInterface {
/// @notice Indicates whether the contract implements the interface `interfaceHash` for the address `addr`.
/// @param addr Address for which the contract will implement the interface
/// @param interfaceHash keccak256 hash of the name of the interface
/// @return ERC820_ACCEPT_MAGIC only if the contract implements `ìnterfaceHash` for the address `addr`.
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) public view returns(bytes32);
}
任何注册为给定地址的接口实现的合约都必须实现该接口。此外,如果它代表不同的地址实现接口,则合约必须实现上面显示的 ERC820ImplementerInterface
。
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) view public returns(bytes32);
指示合约是否为给定地址 (addr
) 实现接口 (interfaceHash
)。
如果合约为给定地址 (addr
) 实现接口 (interfaceHash
),则在使用 addr
和 interfaceHash
调用它时,必须返回 ERC820_ACCEPT_MAGIC
。如果它不为给定的地址 (addr
) 实现 interfaceHash
,则不得返回 ERC820_ACCEPT_MAGIC
。
identifier:
f0083250
parameters
interfaceHash
: 已实现的接口的哈希
addr
: 实现接口的地址
returns: 仅当合约为地址addr
实现ìnterfaceHash
时返回ERC820_ACCEPT_MAGIC
。
特殊值 ERC820_ACCEPT_MAGIC
定义为字符串 "ERC820_ACCEPT_MAGIC"
的 keccka256
哈希。该注册表使用无密钥部署方法进行部署,依赖于一次性部署地址,以确保无人控制该注册表,从而确保信任。
向后兼容性
此标准向后兼容 ERC-165,因为这两种方法都可以实现而不会相互冲突。
测试用例
请查看 jbaylina/ERC820 存储库以获取完整的测试套件。
实施
该实现在仓库中可用:jbaylina/ERC820。
版权
在 CC0 下放弃版权和相关权利。
Citation
Please cite this document as:
Jordi Baylina <jordi@baylina.cat>, Jacques Dafflon <jacques@dafflon.tech>, "ERC-820: 伪自省注册表合约," Ethereum Improvement Proposals, no. 820, January 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-820.