EIPs/EIPS/eip-820.md

277 lines
17 KiB
Markdown
Raw Normal View History

---
eip: 820
title: Pseudo-introspection registry contract
author: Jordi Baylina <jordi@baylina.cat>, Jacques Dafflon <jacques.dafflon@gmail.com>
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.24;
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;
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. (0x0 if is msg.sender)
/// @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 {
address addr = _addr == 0 ? msg.sender : _addr;
require(getManager(addr) == msg.sender);
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
/// (if _addr == 0 them `msg.sender` is assumed)
/// @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) {
address addr = _addr == 0 ? msg.sender : _addr;
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
/// a `manager` defined for that address can set it.
/// ( Each address is the manager for itself until a new manager is defined)
/// @param _addr Address that you want to define the interface for
/// (if _addr == 0 them `msg.sender` is assumed)
/// @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 {
address addr = _addr == 0 ? msg.sender : _addr;
require(getManager(addr) == msg.sender);
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
```
0xf908b78085174876e800830c35008080b90864608060405234801561001057600080fd5b50610844806100206000396000f30060806040526004361061008d5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166329965a1d81146100925780633d584063146100bf578063571a1f66146100fc5780635df8122f1461012a57806365ba36c11461015157806390e47957146101bc578063aabbb8ca146101fe578063ddc23ddd14610222575b600080fd5b34801561009e57600080fd5b506100bd600160a060020a036004358116906024359060443516610250565b005b3480156100cb57600080fd5b506100e0600160a060020a0360043516610402565b60408051600160a060020a039092168252519081900360200190f35b34801561010857600080fd5b506100bd600160a060020a0360043516600160e060020a03196024351661044e565b34801561013657600080fd5b506100bd600160a060020a03600435811690602435166104d8565b34801561015d57600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526101aa9436949293602493928401919081908401838280828437509497506105a09650505050505050565b60408051918252519081900360200190f35b3480156101c857600080fd5b506101ea600160a060020a0360043516600160e060020a031960243516610604565b604080519115158252519081900360200190f35b34801561020a57600080fd5b506100e0600160a060020a036004351660243561067a565b34801561022e57600080fd5b506101ea600160a060020a0360043516600160e060020a0319602435166106f4565b6000600160a060020a038416156102675783610269565b335b90503361027582610402565b600160a060020a03161461028857600080fd5b610291836107a9565b1561029b57600080fd5b600160a060020a038216158015906102bc5750600160a060020a0382163314155b1561039157604080517f4552433832305f4143434550545f4d4147494300000000000000000000000000815281519081900360130181207ff0083250000000000000000000000000000000000000000000000000000000008252600160a060020a038481166004840152602483018790529251909285169163f00832509160448083019260209291908290030181600087803b15801561035b57600080fd5b505af115801561036f573d6000803e3d6000fd5b505050506040513d602081101561038557600080fd5b50511461039157600080fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03808216600090815260016020526040812054909116151561042c575080610449565b50600160a060020a03808216600090815260016020526040902054165b919050565b61045882826106f4565b610463576000610465565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b6000600160a060020a038316156104ef57826104f1565b335b9050336104fd82610402565b600160a060020a03161461051057600080fd5b80600160a060020a031682600160a060020a03161461052f5781610532565b60005b600160a060020a03828116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519185169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a3505050565b6000816040518082805190602001908083835b602083106105d25780518252601f1990920191602091820191016105b3565b5181516020939093036101000a6000190180199091169216919091179052604051920182900390912095945050505050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff16151561064557610645838361044e565b50600160a060020a03918216600090815260208181526040808320600160e060020a0319949094168352929052205416151590565b60008080600160a060020a038516156106935784610695565b335b91506106a0846107a9565b156106c55750826106b18282610604565b6106bc5760006106be565b815b92506106ec565b600160a060020a038083166000908152602081815260408083208884529091529020541692505b505092915050565b60008080610722857f01ffc9a7000000000000000000000000000000000000000000000000000000006107cb565b9092509050811580610732575080155b1561074057600092506106ec565b61075285600160e060020a03196107cb565b909250905081158061076357508015155b1561077157600092506106ec565b61077b85856107cb565b90925090506001821480156107905750806001145b1561079e57600192506106ec565b506000949350505050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565
```
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
```
0xe515e6d0e6b09a60e3cb50c6a8d51d3009ad18af
```
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
```
0xbe78655dff872d22b95ae366fb3477d977328ade
```
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 XXXXX should be the name of the interface camelCase.
Examples:
`sha3("ERC20Token")`
`sha3("ERC777Token")`
`sha3("ERC777TokensRecipient")`
`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/).