Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ReverseENS Pseudo-Introspection, or standard interface detection #672

Closed
jbaylina opened this issue Jul 17, 2017 · 6 comments
Closed

ReverseENS Pseudo-Introspection, or standard interface detection #672

jbaylina opened this issue Jul 17, 2017 · 6 comments

Comments

@jbaylina
Copy link
Contributor

jbaylina commented Jul 17, 2017

Preamble

EIP: <to be assigned>
Title: ReverseENS Pseudo-Introspection, or standard interface detection
Author: Jordi Baylina @jbaylina, Jorge Izquierdo @izqui
Type: Standard Track
Category: ERC
Status: Draft
Created: 2017-07-14

Simple Summary

Creates a standard method to publish and discover which interfaces a smart contract or a regular address implements.

Abstract

This ERC standardizes a way to publish which interfaces does a specific address support or which is the delegated counterparty address that can handle the interface on its behalf.

The proposed mechanism allows backwards compatibility for all contracts that can execute arbitrary external transactions (multisigs) and provides an upgrade path for contracts that can designate other contracts as the implementation of a certain interface.

Motivation

For some "standard interfaces" like the ERC 20 token interface ( #20 ), it is sometimes useful to query whether a contract supports the interface and if yes, which version of the interface, in order to adapt the way in which the contract is to be utilized.

Specification

The interfaces that a specific address implements will be registered using ReverseENS records. Any address can automatically claim its own .addr.reverse for just gas and point it to a resolver. Then, for every new interface the contract supports, it will create a new record (setSubnodeOwner) to set each desired address that implements the interface. The public resolver can be used for this purpose, as it implements addr() and setAddr(address).

If the resolver or the addr() for [InterfaceName].[ADDRESS].addr.reverse is 0, then we consider the address to not implement the interface.

Otherwise, the returned addr() points to the the contract that implements that interface for the specific address.

This address can be the same address if the contract implements the interface, or the address of a different contract (proxy contract) that handles this interface.

Example

ITokenFallback.1234567890123456789012345678901234567890.addr.reverse -> 0x1234567890123456789012345678901234567890

Rationale

We tried to keep this specification as simple as possible. This implementation is also compatible with the current solidity version and the current ENS implementation.

This standard has many advantages in respect to the current EIP165 proposal:

  • It allows the definition of different proxy contracts for each interface.
  • It allows to extend the functionality with future interfaces by just adding an entry to the ReverseENS which points to a new proxy contract that handles this interface.
  • It allows for interface updates
  • It allows the definition of functionality for account addresses.
  • It allows the addition of interfaces to already deployed multisigs and DAOs.

As an example, with this interface it’s trivial to define a proxy contract for a regular address that implements ITokenFallback that defines who can send a token to this address and what to do with this token.

Backwards Compatibility

This interface allows regular accounts and generic contracts to make any call like multisigs or DAOs, and to define new contracts that implement this interface.

Example implementation

pragma solidity ^0.4.18;

interface IENS {
    function owner(bytes32 _node) public constant returns(address);
    function resolver(bytes32 _node) public constant returns(address);
    function ttl(bytes32 _node) public constant returns(uint64);
    function setOwner(bytes32 _node, address _owner) public;
    function setSubnodeOwner(bytes32 _node, bytes32 _label, address _owner) public;
    function setResolver(bytes32 _node, address _resolver) public;
    function setTTL(bytes32 _node, uint64 _ttl) public;
}

interface IReverseRegistrar {
    function claimWithResolver(address _owner, address _resolver) public returns (bytes32 node);
}

interface IPublicResolver {
    function addr(bytes32 _node) public constant returns (address ret);
    function setAddr(bytes32 _node, address _addr) public;
}

// [functionSig or interfaceId].[address].addr.reverse

// Base contract for any contract that uses EnsPseudoIntrospection
contract EIP672 {
    address constant ENS_MAIN = 0x314159265dD8dbb310642f98f50C066173C1259b;
    address constant ENS_ROPSTEM = 0x112234455C3a32FD11230C42E7Bccd4A84e02010;
    address constant ENS_RINKEBY = 0xe7410170f87102DF0055eB195163A03B7F2Bff4A;
    address constant ENS_SIMULATOR = 0x8cDE56336E289c028C8f7CF5c20283fF02272182;
    bytes32 constant public REVERSE_ROOT_NODE = keccak256(keccak256(bytes32(0), keccak256('reverse')), keccak256('addr'));
    bytes32 constant public PUBLICRESOLVE_ROOT_NODE = keccak256(keccak256(bytes32(0), keccak256('eth')), keccak256('resolver'));
    IENS public ens;

    function EIP672() public {
      if (isContract(ENS_MAIN)) {
        ens = IENS(ENS_MAIN);
      } else if (isContract(ENS_ROPSTEM)) {
        ens = IENS(ENS_ROPSTEM);
      } else if (isContract(ENS_RINKEBY)) {
        ens = IENS(ENS_RINKEBY);
      } else if (isContract(ENS_SIMULATOR)) {
        ens = IENS(ENS_SIMULATOR);
      } else {
        assert(false);
      }

      IReverseRegistrar reverseRegistrar = IReverseRegistrar(ens.owner(REVERSE_ROOT_NODE));

      IPublicResolver resolver = IPublicResolver(ens.resolver(PUBLICRESOLVE_ROOT_NODE));
      IPublicResolver publicResolver = IPublicResolver(resolver.addr(PUBLICRESOLVE_ROOT_NODE));

      reverseRegistrar.claimWithResolver(
        address(this),
        address(publicResolver));
    }

    function setInterfaceImplementation(string ifaceLabel, address impl) internal {

        bytes32 node = rootNodeForAddress(address(this));
        bytes32 ifaceLabelHash = keccak256(ifaceLabel);
        bytes32 ifaceNode = keccak256(node, ifaceLabelHash);

        ens.setSubnodeOwner(node, ifaceLabelHash, address(this));

        IPublicResolver resolver = IPublicResolver(ens.resolver(PUBLICRESOLVE_ROOT_NODE));
        IPublicResolver publicResolver = IPublicResolver(resolver.addr(PUBLICRESOLVE_ROOT_NODE));

        ens.setResolver(ifaceNode, publicResolver);
        publicResolver.setAddr(ifaceNode, impl);
    }

    function interfaceAddr(address addr, string ifaceLabel) internal constant returns(address) {
        bytes32 node = rootNodeForAddress(address(addr));
        bytes32 ifaceNode = keccak256(node, keccak256(ifaceLabel));
        IPublicResolver resolver = IPublicResolver(ens.resolver(ifaceNode));
        if (address(resolver) == 0) return 0;
        return resolver.addr(ifaceNode);
    }

    function rootNodeForAddress(address addr) internal constant returns (bytes32) {
        return keccak256(REVERSE_ROOT_NODE, keccak256HexAddress(addr));
    }

    function keccak256HexAddress(address addr) private constant returns (bytes32 ret) {
        addr; ret; // Stop warning us about unused variables
        assembly {
            let lookup := 0x3031323334353637383961626364656600000000000000000000000000000000
            let i := 40
        loop:
            i := sub(i, 1)
            mstore8(i, byte(and(addr, 0xf), lookup))
            addr := div(addr, 0x10)
            i := sub(i, 1)
            mstore8(i, byte(and(addr, 0xf), lookup))
            addr := div(addr, 0x10)
            jumpi(loop, i)
            ret := keccak256(0, 40)
        }
    }

    /// @dev Internal function to determine if an address is a contract
    /// @param _addr The address being queried
    /// @return True if `_addr` is a contract
    function isContract(address _addr) constant internal returns(bool) {
        uint size;
        if (_addr == 0) return false;
        assembly {
            size := extcodesize(_addr)
        }
        return size>0;
    }
}

Test Cases

You can check the implementation and a test here: https://github.com/jbaylina/eip672

Copyright

Copyright and related rights waived via CC0.

@LefterisJP
Copy link
Contributor

It's an interesting idea and just as the original EIP165 idea it can be really useful for many contract developers.

I have some questions.

  1. How would you define InterfaceName here? Would the community need to agree on what the name for each interface be? Say ERC20 for the standard token contract?

  2. How would different versions of an interface be handled? Append something like v1, v2 or just new names all together?

  3. Since the deployer of the contract is the one who would have to invoke the reverse ENS registrar to register the interface manually if they:

    1. Either lie about it and register interfaces they don't implement
    2. Forget to register an interface they do implement

Then there is nothing that can be done to enforce it, right? All this should just be seen as only a guideline by the contract author?

@jbaylina
Copy link
Contributor Author

@LefterisJP

  1. Yes, the idea is to NOT define which names should be used in this standard and let it open. I think that the best approach is that each standard should define each own interface names. The only precaution to be taken is that it should not be repeated the same name for different interfaces in different standards.

  2. Again, this is not defined in this standard and should be defined in the specific ERC for each interface. A good way to define names (may be it could be a recommendation on this standard) , is to use the same rules that uses Microsoft for their COM interfaces: Prepend an I in front of the interface name and add a version number at the end of the name starting on version 2. As an example:
    IMinimeController, IMinimeController2

  3. As far as I understand the reverseENS, only the contract itself can claim the reverse name for its address, not the deployer. In general, the claim of the reverse name will be done in constructors for real contracts. I can also be done in specific calls for regular addresses, wallets, daos, uport proxy, etc. And may be some future contracts may implement some mechanisms with some rules for implementing future interfaces.

The called party can lie about the interface implementation, they can also implement it in the wrong way. The caller should be aware of that and should not assume and check any thing that is executed in an untrusted contract.

@stevenh512
Copy link

I see one problem with this, it breaks reverse resolution. I don't see any reason why the root node can't use the reverse resolver, as long as the subnodes for each interface use the public resolver. If you set the root node to use the public resolver and provide either 1) a way to transfer ownership of the root node to another address (for example the contract's owner/deployer) or 2) a way to set the contract's "name" in the reverse resolver, this should work without breaking reverse resolution.

@jbaylina
Copy link
Contributor Author

@stevenh512 This standard does not break the rerverse resolution. This is done in the name field of resolver for [address].addr.reverse This standard just add subnodes of the form [InterfaceName].[addres].addr.reverse whos addr method of their resolver points to the contract that implements that interface for that address.

@stevenh512
Copy link

stevenh512 commented Nov 23, 2017

@jbaylina I understand I may have miscommunicated what I meant in my previous comment and also (after watching your talk about this and Yoga Token) that English is probably not your first language, so perhaps we're misunderstanding each other a little bit here. My fault for not explaining more clearly.

It's not the adding of subnodes (or the requirement that those subnodes use the public resolver or some other resolver that provides addr and setAddr methods) that breaks reverse resolution, it's the fact that the root [address].addr.reverse node is being unnecessarily set to use the public resolver combined with the fact that the contract's owner/creator has no way to take owership of that root node and change it. For reverse resolution to work, the root node needs a resolver that provides name and setName methods, which the reverse resolver does.

The way ENS works, if the root node is using the reverse resolver (so that reverse resolution works as expected), there is no reason why the [interface].[address].addr.reverse subnodes can't use the public resolver or any other resolver you choose. Other than the fact that the root node's owner can always take ownership of them, they're treated as completely separate entities.

I'm having network issues at the moment, but if you'd like I can try sending a PR on your eip672 repository in a few days.

(edited for clarity and to fix typos)

@jbaylina
Copy link
Contributor Author

@stevenh512

Ah, yea I see your point!

I merged the PR. Also note that claimWithResolver could be changed to just a claim. It's not necessary to set a resolver for the address node. Unless, of course, if you want to set a name for the address.

BTW, good PR! This repository may became a ReverseENS manager for contracts, instead of just an EIP672 helper!. new name proposals for the repository are accepted..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants
@stevenh512 @LefterisJP @jbaylina and others