ERC-1820 Pseudo-introspection registry contract
Final · Other · created 2019-03-04
Defines a shared registry where any address, whether a contract or a regular account, can register and look up which interfaces it supports and which contract implements them. The same address is used on every chain.
Why it is needed
For smart contracts to interact safely, each side must first know which functions (interfaces) the other supports. The earlier approach, ERC-165, has the contract answer for itself, so regular accounts cannot use it. ERC-672, which came later, relied on reverse ENS, but the original text names two problems with it: it is needlessly complex, and ENS itself is a centralized contract managed by a multisig wallet. That multisig could, in theory, change the system. Also, if the registry address differs on each chain, you have to verify which address is the real one every time. ERC-1820 solves these problems together. A regular account can also register an interface by designating a proxy contract, the design is simple, no one controls it, and it has the same address on every chain. This standard fixes an ERC-165 compatibility bug caused by the Solidity 0.5 update. Its functionality is the same as ERC-820, but it must be used instead of ERC-820.
What to implement
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address): Looks up whether an address supports an interface and which contract implements it. Returns 0 if nothing is registered.function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external: Registers the contract that implements a given interface for an address. Only that address's manager can call it.function setManager(address _addr, address _newManager) external: Changes the manager of an address. Passing 0x0 resets it to the address itself.function getManager(address _addr) public view returns(address): Returns the manager of an address. If none was set, it is the address itself.function interfaceHash(string _interfaceName) public pure returns(bytes32): Computes the keccak256 hash of an interface name on-chain.function updateERC165Cache(address _contract, bytes4 _interfaceId) external: Re-queries whether a contract supports an ERC-165 interface and updates the cache. Anyone can call it.function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool): Checks whether a contract supports an ERC-165 interface. It uses the cache if one exists, and otherwise queries the contract directly.function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool): Checks ERC-165 support by querying the contract directly, without reading or updating the cache.function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32): A function that a proxy implementer contract must implement. It returns ERC1820_ACCEPT_MAGIC only when it handles the given address and interface.event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);: Emitted when the interface implementer of an address is set.event ManagerChanged(address indexed addr, address indexed newManager);: Emitted when the manager of an address is changed.
In depth
Registry storage layout
The registry has three mappings. interfaces maps an address and an interface hash (bytes32) to an implementer address. managers maps an address to a manager address, and a value of 0 means the address is its own manager. erc165Cached is a flag that records, for each address and ERC-165 interface ID (bytes4), whether the result is cached. The constants are INVALID_ID (0xffffffff), the ERC-165 invalid ID; ERC165ID (0x01ffc9a7), the method ID of supportsInterface(bytes4); and ERC1820_ACCEPT_MAGIC, used for proxy implementer approval. There are two events: InterfaceImplementerSet, emitted when an implementer changes, and ManagerChanged, emitted when a manager changes. The contract is written in Solidity 0.5.3, and a comment records the IV value 53759 used to obtain an address starting with 0x1820.
Interface names and hash rules
An interface is identified by a bytes32 value, the keccak256 hash of its name string. The registry's interfaceHash function computes this hash on-chain. Interfaces of approved ERCs must be named in the format ERC###XXXXX. ### is the ERC number and XXXXX is the interface name in camel case, and its meaning must be defined in that ERC. The original text gives these examples: keccak256("ERC20Token"), keccak256("ERC777Token"), keccak256("ERC777TokensSender"), keccak256("ERC777TokensRecipient"). If the last 28 bytes of the hash are all 0, it is treated as an ERC-165 interface, and the first 4 bytes become the ERC-165 interface ID. Users may also use their own private interface names, but these must not conflict with the reserved naming convention above.
Validation order in setInterfaceImplementer
An implementer is registered with setInterfaceImplementer(_addr, _interfaceHash, _implementer). If _addr is 0, it is treated as msg.sender. Thanks to this default, a multisig wallet can sign the same transaction data regardless of the instance address. The function first checks that getManager(addr) equals msg.sender, and otherwise reverts with "Not the manager". Next, if _interfaceHash is in ERC-165 format (last 28 bytes are 0), it reverts with "Must not be an ERC165 hash". If _implementer is neither 0 nor msg.sender, it calls canImplementInterfaceForAddress(_interfaceHash, addr) on _implementer to check that ERC1820_ACCEPT_MAGIC is returned, and otherwise reverts with "Does not implement the interface". If all checks pass, it stores the value in interfaces[addr][_interfaceHash] and emits the InterfaceImplementerSet event. If _implementer is 0, 0 is stored without the proxy check.
getInterfaceImplementer and ERC-165 forwarding
A lookup is done with getInterfaceImplementer(_addr, _interfaceHash), and anyone can call it. If _addr is 0, it is likewise treated as msg.sender. If the last 28 bytes of _interfaceHash are 0, the registry does not consult the mapping. It takes the first 4 bytes as an ERC-165 interface ID and queries the _addr contract directly. This goes through implementsERC165Interface, so the cache is used if one exists. It returns _addr itself if the interface is supported, and 0 otherwise. If the hash is not in ERC-165 format, it returns the value of interfaces[addr][_interfaceHash] as is, which is 0 if nothing is registered. Because a single function can query both ERC-165 and ERC-1820 this way, the original text states that implementing both standards together causes no conflict.
Proxy implementers and ERC1820_ACCEPT_MAGIC
A contract that implements an interface on behalf of an address must implement that interface itself and must additionally implement ERC1820ImplementerInterface. The only function of this interface, canImplementInterfaceForAddress(interfaceHash, addr), must return ERC1820_ACCEPT_MAGIC only when it handles the given address and hash combination, and must never return this value if it does not. ERC1820_ACCEPT_MAGIC is the keccak256 hash of the string "ERC1820_ACCEPT_MAGIC". The reason for using a magic value instead of a boolean is explained in the original text. A contract that does not implement this function but has a fallback function that does not revert may have the call routed to the fallback, which can return 1, and that would look as if it returned true. Requiring a specific hash value prevents this kind of misjudgment. The function selector is f0083250.
Manager permissions
Each address has a manager, who is the only party that can register interface implementers for that address. The default is the address itself. getManager(_addr) returns _addr if managers[_addr] is 0, and otherwise returns the stored manager. A manager can hand the role over with setManager(_addr, _newManager), which only the current manager can call. Passing 0x0 as _newManager resets the manager to _addr itself. In the implementation, when _newManager equals _addr it also stores 0, returning to the default state. Every change emits a ManagerChanged(addr, newManager) event. The original text explains that for externally owned accounts, this kind of delegation to a proxy contract is essential, and that it lets existing contracts such as multisigs or DAOs avoid being redeployed.
ERC-165 lookups and the manual cache
The registry also acts as an ERC-165 cache. implementsERC165Interface(_contract, _interfaceId) queries the contract directly if the erc165Cached flag is not set. If it is set, it answers by checking whether interfaces[_contract][_interfaceId] equals _contract. The direct lookup, implementsERC165InterfaceNoCache, sends three staticcalls through noThrowCall, each with 30000 gas. First, the supportsInterface(0x01ffc9a7) call must succeed and return a nonzero value. Second, the call for the invalid ID 0xffffffff must succeed and return 0. Third, the call for the target _interfaceId must succeed and return exactly 1 for the result to be true. The cache is neither updated nor invalidated automatically. A contract that changes its interfaces dynamically must update the cache manually with updateERC165Cache(_contract, _interfaceId). Ideally the contract updates it itself, but anyone can call it on its behalf. This function writes either _contract or 0 into interfaces depending on the lookup result, and sets erc165Cached to true. The original text states that this part was designed together with William Entriken.
Keyless deployment, fixed address, and replacing ERC-820
The registry is deployed with a keyless deployment method known as Nick's method. The deployment transaction does not use EIP-155 so that it is valid on any chain, and the gas price is set high at 100 Gwei. The signature has v of 27, and both r and s are the value 1820 repeated 16 times. Recovering the signer address from this human-chosen pattern gives 0xa990077c3205cbDf861e17Fa532eeB069cE9fF96, and nobody knows the private key of this account. To deploy, first send exactly 0.08 ether to this account, then broadcast the raw transaction given in the original text as is. As a result, the registry address is the same on every chain, 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24, and no one can deploy a different contract to that address. This standard replaces ERC-820. It fixes an incompatibility in the ERC-165 logic caused by the Solidity 0.5 update, and its other features are the same as ERC-820. The original text explicitly states that ERC-1820 must be used instead of ERC-820. Tests and the implementation are in the 0xjac/ERC1820 repository, and its status is Final.