mirror of https://github.com/status-im/EIPs.git
275 lines
16 KiB
Markdown
275 lines
16 KiB
Markdown
|
---
|
||
|
eip: 820
|
||
|
title: Pseudo-introspection registry contract
|
||
|
author: Jordi Baylina <jordi@baylina.cat>
|
||
|
discussions-to: https://github.com/ethereum/EIPs/issues/820
|
||
|
status: Draft
|
||
|
type: Standards Track
|
||
|
category: ERC
|
||
|
created: 2018-01-05
|
||
|
---
|
||
|
|
||
|
## Simple Summary
|
||
|
|
||
|
This standard defines a universal registry smart contract where any address (contract or regular account) can register which interface it implements and which smart contract is responsible for its implementation.
|
||
|
|
||
|
This standard keeps backwards compatibility with [EIP-165](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md)
|
||
|
|
||
|
## Abstract
|
||
|
|
||
|
This standard attempts to define a registry where smart contracts and regular accounts can publish which functionalities they implement.
|
||
|
|
||
|
The rest of the world can query this registry to ask if a specific address implements a given interface and which smart contract handles its implementation.
|
||
|
|
||
|
This registry can be deployed on any chain and will share the exact same address.
|
||
|
|
||
|
Interfaces where the last 28 bytes are `0` are considered [EIP-165](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md) interfaces, and this registry
|
||
|
will forward the call to the contract to see if they implement that interface.
|
||
|
|
||
|
This contract will act also as an [EIP-165](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md) cache in order to safe gas.
|
||
|
|
||
|
## Motivation
|
||
|
|
||
|
There has been different approaches to define pseudo-introspection in the Ethereum. The first is [EIP-165](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md) which has the problem that it is not available for regular accounts to use. The second approach is EIP-672 which uses reverseENS. Using reverseENS, has two issues. First, it is unnecessarily complex, and second, ENS is still a centralized contract controlled by a multisig. This multisig, theoretically would be able to modify the system.
|
||
|
|
||
|
This standard is much simpler than [EIP-672](https://github.com/ethereum/EIPs/issues/672) and it is absolutely decentralized.
|
||
|
|
||
|
This standard also solves the problem of having different addresses for different chains.
|
||
|
|
||
|
## Specification
|
||
|
|
||
|
### The smart contract
|
||
|
|
||
|
```solidity
|
||
|
pragma solidity 0.4.20;
|
||
|
|
||
|
interface ERC820ImplementerInterface {
|
||
|
/// @notice Contracts that implement an interferce in behalf of another contract must return true
|
||
|
/// @param addr Address that the contract woll implement the interface in behalf of
|
||
|
/// @param interfaceHash keccak256 of the name of the interface
|
||
|
/// @return ERC820_ACCEPT_MAGIC if the contract can implement the interface represented by
|
||
|
/// `ìnterfaceHash` in behalf of `addr`
|
||
|
function canImplementInterfaceForAddress(address addr, bytes32 interfaceHash) view public returns(bytes32);
|
||
|
}
|
||
|
|
||
|
contract ERC820Registry {
|
||
|
bytes4 constant InvalidID = 0xffffffff;
|
||
|
bytes4 constant ERC165ID = 0x01ffc9a7;
|
||
|
bytes32 constant ERC820_ACCEPT_MAGIC = keccak256("ERC820_ACCEPT_MAGIC");
|
||
|
|
||
|
|
||
|
mapping (address => mapping(bytes32 => address)) interfaces;
|
||
|
mapping (address => address) managers;
|
||
|
mapping (address => mapping(bytes4 => bool)) erc165Cache;
|
||
|
|
||
|
modifier canManage(address addr) {
|
||
|
require(getManager(addr) == msg.sender);
|
||
|
_;
|
||
|
}
|
||
|
|
||
|
|
||
|
event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);
|
||
|
event ManagerChanged(address indexed addr, address indexed newManager);
|
||
|
|
||
|
/// @notice Query the hash of an interface given a name
|
||
|
/// @param interfaceName Name of the interfce
|
||
|
function interfaceHash(string interfaceName) public pure returns(bytes32) {
|
||
|
return keccak256(interfaceName);
|
||
|
}
|
||
|
|
||
|
/// @notice GetManager
|
||
|
function getManager(address addr) public view returns(address) {
|
||
|
// By default the manager of an address is the same address
|
||
|
if (managers[addr] == 0) {
|
||
|
return addr;
|
||
|
} else {
|
||
|
return managers[addr];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// @notice Sets an external `manager` that will be able to call `setInterfaceImplementer()`
|
||
|
/// on behalf of the address.
|
||
|
/// @param addr Address that you are defining the manager for.
|
||
|
/// @param newManager The address of the manager for the `addr` that will replace
|
||
|
/// the old one. Set to 0x0 if you want to remove the manager.
|
||
|
function setManager(address addr, address newManager) public canManage(addr) {
|
||
|
managers[addr] = newManager == addr ? 0 : newManager;
|
||
|
ManagerChanged(addr, newManager);
|
||
|
}
|
||
|
|
||
|
/// @notice Query if an address implements an interface and thru which contract
|
||
|
/// @param addr Address that is being queried for the implementation of an interface
|
||
|
/// @param iHash SHA3 of the name of the interface as a string
|
||
|
/// Example `web3.utils.sha3('ERC777Token`')`
|
||
|
/// @return The address of the contract that implements a specific interface
|
||
|
/// or 0x0 if `addr` does not implement this interface
|
||
|
function getInterfaceImplementer(address addr, bytes32 iHash) constant public returns (address) {
|
||
|
if (isERC165Interface(iHash)) {
|
||
|
bytes4 i165Hash = bytes4(iHash);
|
||
|
return erc165InterfaceSupported(addr, i165Hash) ? addr : 0;
|
||
|
}
|
||
|
return interfaces[addr][iHash];
|
||
|
}
|
||
|
|
||
|
/// @notice Sets the contract that will handle a specific interface; only
|
||
|
/// the address itself or a `manager` defined for that address can set it
|
||
|
/// @param addr Address that you want to define the interface for
|
||
|
/// @param iHash SHA3 of the name of the interface as a string
|
||
|
/// For example `web3.utils.sha3('Ierc777')` for the Ierc777
|
||
|
function setInterfaceImplementer(address addr, bytes32 iHash, address implementer) public canManage(addr) {
|
||
|
require(!isERC165Interface(iHash));
|
||
|
if ((implementer != 0) && (implementer!=msg.sender)) {
|
||
|
require(ERC820ImplementerInterface(implementer).canImplementInterfaceForAddress(addr, iHash)
|
||
|
== ERC820_ACCEPT_MAGIC);
|
||
|
}
|
||
|
interfaces[addr][iHash] = implementer;
|
||
|
InterfaceImplementerSet(addr, iHash, implementer);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// ERC165 Specific
|
||
|
|
||
|
function isERC165Interface(bytes32 iHash) internal pure returns (bool) {
|
||
|
return iHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0;
|
||
|
}
|
||
|
|
||
|
function erc165InterfaceSupported(address _contract, bytes4 _interfaceId) constant public returns (bool) {
|
||
|
if (!erc165Cache[_contract][_interfaceId]) {
|
||
|
erc165UpdateCache(_contract, _interfaceId);
|
||
|
}
|
||
|
return interfaces[_contract][_interfaceId] != 0;
|
||
|
}
|
||
|
|
||
|
function erc165UpdateCache(address _contract, bytes4 _interfaceId) public {
|
||
|
interfaces[_contract][_interfaceId] =
|
||
|
erc165InterfaceSupported_NoCache(_contract, _interfaceId) ? _contract : 0;
|
||
|
erc165Cache[_contract][_interfaceId] = true;
|
||
|
}
|
||
|
|
||
|
function erc165InterfaceSupported_NoCache(address _contract, bytes4 _interfaceId) public constant returns (bool) {
|
||
|
uint256 success;
|
||
|
uint256 result;
|
||
|
|
||
|
(success, result) = noThrowCall(_contract, ERC165ID);
|
||
|
if ((success==0)||(result==0)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
(success, result) = noThrowCall(_contract, InvalidID);
|
||
|
if ((success==0)||(result!=0)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
(success, result) = noThrowCall(_contract, _interfaceId);
|
||
|
if ((success==1)&&(result==1)) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function noThrowCall(address _contract, bytes4 _interfaceId) constant internal 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 begining 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
|
||
|
0x08, // Inputs are 8 bytes long
|
||
|
x, // Store output over input (saves space)
|
||
|
0x20) // Outputs are 32 bytes long
|
||
|
|
||
|
result := mload(x) // Load the result
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Raw transaction for deploying the smart contract on any chain
|
||
|
|
||
|
```
|
||
|
0xf908778085174876e800830c35008080b908246060604052341561000f57600080fd5b6108068061001e6000396000f30060606040526004361061008d5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166329965a1d81146100925780633d584063146100bd578063571a1f66146100f85780635df8122f1461012457806365ba36c11461014957806390e47957146101ac578063aabbb8ca146101ec578063ddc23ddd1461020e575b600080fd5b341561009d57600080fd5b6100bb600160a060020a03600435811690602435906044351661023a565b005b34156100c857600080fd5b6100dc600160a060020a03600435166103ec565b604051600160a060020a03909116815260200160405180910390f35b341561010357600080fd5b6100bb600160a060020a0360043516600160e060020a031960243516610438565b341561012f57600080fd5b6100bb600160a060020a03600435811690602435166104c2565b341561015457600080fd5b61019a60046024813581810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965061057d95505050505050565b60405190815260200160405180910390f35b34156101b757600080fd5b6101d8600160a060020a0360043516600160e060020a0319602435166105e2565b604051901515815260200160405180910390f35b34156101f757600080fd5b6100dc600160a060020a0360043516602435610658565b341561021957600080fd5b6101d8600160a060020a0360043516600160e060020a0319602435166106b7565b8233600160a060020a031661024e826103ec565b600160a060020a03161461026157600080fd5b61026a8361076e565b1561027457600080fd5b600160a060020a0382161580159061029e575033600160a060020a031682600160a060020a031614155b15610373576040517f4552433832305f4143434550545f4d41474943000000000000000000000000008152601301604051908190039020600160a060020a03831663f008325086866000604051602001526040517c010000000000000000000000000000000000000000000000000000000063ffffffff8516028152600160a060020a0390921660048301526024820152604401602060405180830381600087803b151561034b57600080fd5b6102c65a03f1151561035c57600080fd5b505050604051805191909114905061037357600080fd5b600160a060020a0384811660008181526020818152604080832088845290915290819020805473ffffffffffffffffffffffffffffffffffffffff191693861693841790558591907f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db153905160405180910390a450505050565b600160a060020a038082166000908152600160205260408120549091161515610416575080610433565b50600160a060020a03808216600090815260016020526040902054165b919050565b61044282826106b7565b61044d57600061044f565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b8133600160a060020a03166104d6826103ec565b600160a060020a0316146104e957600080fd5b82600160a060020a031682600160a060020a031614610508578161050b565b60005b600160a060020a0384811660008181526001602052604090819020805473ffffffffffffffffffffffffffffffffffffffff191694841694909417909355908416917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a4350905160405180910390a3505050565b6000816040518082805190602001908083835b602083106105af5780518252601f199092019160209182019101610590565b6001836020036101000a038019825116818451161790925250505091909101925060409150505180910390209050919050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff161515610623576106238383610438565b50600160a060020a03918216600090815260208181526040808320600160e060020a0319949094168352929052205416151590565b6000806106648361076e565b1561068957508161067584826105e2565b610680576000610682565b835b91506106b0565b600160a060020a038085166000908152602081815260408083208784529091529020541691505b5092915050565b600080806106e5857f01ffc9a700000000000000000000000000000000000000000000000000000000610790565b90925090508115806106f5575080155b156107035760009250610766565b61071585600160e060020a0319610790565b909250905081158061072657508015155b156107345760009250610766565b61073e8585610790565b90925090506001821480156107535750806001145b156107615760019250610766565b600092505b505092915050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565b6000807f01ffc9a70000000000000000000000000000000000000000000000000000000060405181815284600482015260208160088389617530fa935
|
||
|
```
|
||
|
|
||
|
You can see the string of `a`s at the end of the transaction. This is the `s` of the signature, meaning that its a deterministic by hand forced signature.
|
||
|
|
||
|
### Deployment method
|
||
|
|
||
|
This contract is going to be deployed using the Nick's Method.
|
||
|
|
||
|
This method works as follows:
|
||
|
|
||
|
1. Generate a transaction that deploys the contract from a new random account. This transaction must not use [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md) so it can work on any chain. This transaction needs to also have a relatively high gas price in order to be deployed in any chain. In this case, it's going to be 100Gwei.
|
||
|
2. Set the `v`, `r`, `s` of the transaction signature to the following values:
|
||
|
`
|
||
|
v: 27`
|
||
|
`
|
||
|
r: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798`
|
||
|
`
|
||
|
s: 0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`
|
||
|
This nice `s` value is a random number generated deterministically by a human.
|
||
|
3. We recover the sender of this transaction. We will have an account that can broadcast that transaction, but we also have the waranty that nobody knows the private key of that account.
|
||
|
4. Send Ether to this deployment account.
|
||
|
5. Broadcast the transaction.
|
||
|
|
||
|
This operation can be done in any chain, guaranteed that the contract address is going to always be the same and nobody will be able to mess up that address with a different contract.
|
||
|
|
||
|
|
||
|
### Special registry deployment account
|
||
|
|
||
|
```
|
||
|
0x91c2b265ece9442ed28e3c4283652b1894dcdabb
|
||
|
```
|
||
|
|
||
|
This account is generated by reverse engineering it from it's signature for the transaction, in this way no one knows the private key, but it is known that it's the valid signer of the deployment transaction.
|
||
|
|
||
|
### Deployed contract
|
||
|
|
||
|
```
|
||
|
0x991a1bcb077599290d7305493c9a630c20f8b798
|
||
|
```
|
||
|
|
||
|
The contract will have this address for every chain it is deployed to.
|
||
|
|
||
|
### Interface name
|
||
|
|
||
|
Your interface name is hashed and sent to `getInterfaceImplementer()`. If you are writing a standard, it is best practice to explicitly state the interface name and link to this published EIP-820 so that other people don't have to come here to look up these rules.
|
||
|
|
||
|
#### If it's an approved EIP
|
||
|
|
||
|
The interface is named like `ERC###XXXXX`. The meaning of this interface is defined in the EIP specified. And XXX should be the name of the interface camelCase.
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
`sha3("ERC20Token")`
|
||
|
`sha3("ERC777Token")`
|
||
|
`sha3("ERC777TokensReceiver")`
|
||
|
`sha3("ERC777TokensSender")`
|
||
|
|
||
|
#### [EIP-165](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md) compatible interfaces
|
||
|
|
||
|
Interfaces where the last 28bytes are 0, then this will be considered an [EIP-165](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md) interface.
|
||
|
|
||
|
#### Private user defined interface
|
||
|
|
||
|
This scheme is extensible. If you want to make up your own interface name and raise awareness to get other people to implement it and then check for those implementations, great! Have fun, but please do not conflict with the reserved designations above.
|
||
|
|
||
|
## Backwards Compatibility
|
||
|
|
||
|
This standard is backwards compatible with [EIP-165](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md), as both methods can be implemented without conflicting one each other.
|
||
|
|
||
|
## Test Cases
|
||
|
|
||
|
Please, check the repository https://github.com/jbaylina/eip820 for the full test suit.
|
||
|
|
||
|
## Implementation
|
||
|
|
||
|
The implementation can be found in this repo: https://github.com/jbaylina/eip820
|
||
|
|
||
|
## Copyright
|
||
|
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
|