EIP: 820
Title: Pseudo-introspection using a registry contract.
Author: Jordi Baylina <jordi@baylina.cat>
Type: StandardTrack
Category: ERC
Status: Draft
Created: 2018-01-05
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
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 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 cache in order to safe gas.
There has been different approaches to define pseudo-introspection in the Ethereum. The first is EIP-165 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 and it is absolutely decentralized.
This standard also solves the problem of having different addresses for different chains.
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
}
}
}
0xf908778085174876e800830c35008080b908246060604052341561000f57600080fd5b6108068061001e6000396000f30060606040526004361061008d5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166329965a1d81146100925780633d584063146100bd578063571a1f66146100f85780635df8122f1461012457806365ba36c11461014957806390e47957146101ac578063aabbb8ca146101ec578063ddc23ddd1461020e575b600080fd5b341561009d57600080fd5b6100bb600160a060020a03600435811690602435906044351661023a565b005b34156100c857600080fd5b6100dc600160a060020a03600435166103ec565b604051600160a060020a03909116815260200160405180910390f35b341561010357600080fd5b6100bb600160a060020a0360043516600160e060020a031960243516610438565b341561012f57600080fd5b6100bb600160a060020a03600435811690602435166104c2565b341561015457600080fd5b61019a60046024813581810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965061057d95505050505050565b60405190815260200160405180910390f35b34156101b757600080fd5b6101d8600160a060020a0360043516600160e060020a0319602435166105e2565b604051901515815260200160405180910390f35b34156101f757600080fd5b6100dc600160a060020a0360043516602435610658565b341561021957600080fd5b6101d8600160a060020a0360043516600160e060020a0319602435166106b7565b8233600160a060020a031661024e826103ec565b600160a060020a03161461026157600080fd5b61026a8361076e565b1561027457600080fd5b600160a060020a0382161580159061029e575033600160a060020a031682600160a060020a031614155b15610373576040517f4552433832305f4143434550545f4d41474943000000000000000000000000008152601301604051908190039020600160a060020a03831663f008325086866000604051602001526040517c010000000000000000000000000000000000000000000000000000000063ffffffff8516028152600160a060020a0390921660048301526024820152604401602060405180830381600087803b151561034b57600080fd5b6102c65a03f1151561035c57600080fd5b505050604051805191909114905061037357600080fd5b600160a060020a0384811660008181526020818152604080832088845290915290819020805473ffffffffffffffffffffffffffffffffffffffff191693861693841790558591907f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db153905160405180910390a450505050565b600160a060020a038082166000908152600160205260408120549091161515610416575080610433565b50600160a060020a03808216600090815260016020526040902054165b919050565b61044282826106b7565b61044d57600061044f565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b8133600160a060020a03166104d6826103ec565b600160a060020a0316146104e957600080fd5b82600160a060020a031682600160a060020a031614610508578161050b565b60005b600160a060020a0384811660008181526001602052604090819020805473ffffffffffffffffffffffffffffffffffffffff191694841694909417909355908416917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a4350905160405180910390a3505050565b6000816040518082805190602001908083835b602083106105af5780518252601f199092019160209182019101610590565b6001836020036101000a038019825116818451161790925250505091909101925060409150505180910390209050919050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff161515610623576106238383610438565b50600160a060020a03918216600090815260208181526040808320600160e060020a0319949094168352929052205416151590565b6000806106648361076e565b1561068957508161067584826105e2565b610680576000610682565b835b91506106b0565b600160a060020a038085166000908152602081815260408083208784529091529020541691505b5092915050565b600080806106e5857f01ffc9a700000000000000000000000000000000000000000000000000000000610790565b90925090508115806106f5575080155b156107035760009250610766565b61071585600160e060020a0319610790565b909250905081158061072657508015155b156107345760009250610766565b61073e8585610790565b90925090506001821480156107535750806001145b156107615760019250610766565b600092505b505092915050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565b6000807f01ffc9a70000000000000000000000000000000000000000000000000000000060405181815284600482015260208160088389617530fa935080519250505092509290505600a165627a7a72305820b424185958879a1eef1cb7235bfd8ed607a7402b46853860e5343340925f028e00291ba079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798a00aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
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.
This contract is going to be deployed using the Nick's Method.
This method works as follows:
- Generate a transaction that deploys the contract from a new random account. This transaction must not use EIP-155 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.
- Set the
v
,r
,s
of the transaction signature to the following values:v: 27
r: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
s: 0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
This nices
value is a random number generated deterministically by a human. - 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.
- Send Ether to this deployment account.
- 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.
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.
0x991a1bcb077599290d7305493c9a630c20f8b798
The contract will have this address for every chain it is deployed to.
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.
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 compatible interfaces
Interfaces where the last 28bytes are 0, then this will be considered an EIP-165 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.
This standard is backwards compatible with EIP-165, as both methods can be implemented without conflicting one each other.
Please, check the repository https://github.com/jbaylina/eip820 for the full test suit.
The implementation can be found in this repo: https://github.com/jbaylina/eip820
Copyright and related rights waived via CC0.