Alert Source Discuss
Standards Track: ERC

ERC-1820: 伪自省注册表合约

Authors Jordi Baylina <jordi@baylina.cat>, Jacques Dafflon <mail@0xjac.com>
Created 2019-03-04
Requires EIP-165, EIP-214

:information_source: ERC-1820 已经取代了 ERC-820 :information_source: ERC-1820 修复了 Solidity 0.5 更新引入的 ERC-165 逻辑中的不兼容性。 请查看官方公告,以及关于错误修复的评论。 除了这个修复,ERC-1820 在功能上等同于 ERC-820

:warning: 必须使用 ERC-1820 代替 ERC-820。 :warning:

概要

本标准定义了一个通用的注册表智能合约,任何地址(合约或常规账户)都可以在其中注册它支持哪个接口,以及哪个智能合约负责它的实现。

本标准保持了与 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

如果 _newManager0x0,则 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:要检查的合约的地址。
_interfaceIdERC-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),则在使用 addrinterfaceHash 调用时,它必须返回 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

如果 _newManager0x0,则 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);

当地址 addrinterfaceHash 接口设置为 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 相比,此标准使用 stringkeccak256 哈希值而不是 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.