アルゴリズムとかオーダーとか

仕事で勉強したことなどをまとめてます

ContractのInterface検出機能の提案ERC-165とERC-820を調べてみた

新しく提案されているERCでは他のERCを利用しているものが多くなってきました。今回はその中でも個人的によく目について気になっていたERC-165とERC-820について調べたことをまとめます。

ERC-165とERC-820は基本的にはどちらもStandard Interface Detection、標準インターフェース検出の提案です。ContractがEIPで定義されたどのインターフェースを実装しているかを定義する方法を提案しています。
ERC-165はNon-fungible Token Standardの提案であるERC-721で利用さており、またERC-820は多分一番新しい?拡張Tokenを提案しているERC-777で利用されています。
github.com
github.com

ERC-165の提案内容

Pseudo-Introspection, or standard interface detection · Issue #165 · ethereum/EIPs · GitHub より;

サマリ

スマートコントラクトがどのインタフェースを実装するかを公開し発見する標準的なメソッドを作成します。

抽象

この規格は、以下の手順の標準化からなる。

  • コントラクトが実装するインタフェースを公開する方法
  • 契約でERC-165が実装されているかどうかを判断する方法
  • 契約が任意の標準インタフェースを実装しているかどうかを判断する方法

動機

ERC-20トークインターフェイスのようないくつかの「標準インターフェイス」では、Contractがインターフェイスをサポートしているかどうかを照会することが有用である場合があります。特にERC-20では、バージョン識別子が既に提案されています。本提案は、インタフェースの概念を標準化し、インタフェース・バージョンをインタフェース識別子にマッピングすることです。

仕様

標準インタフェース識別子

標準インターフェースは、特定のbytes4の番号で識別されます。この番号はSolidityで計算されたすべてのbytes4のメソッドIDのXORになります。

たとえば、標準インタフェースのIDを計算するには

totalSupply() - >メソッドID:0x18160ddd
balanceOf(address) - >メソッドID:0x70a08231
transfer(address、uint256) - >メソッドID:0xa9059cbb
InterfaceID = 0x18160ddd ^ 0x70a08231 ^ 0xa9059cbb = 0xc1b31357
EIP165インターフェース

標準インタフェースを実装するContractでも、このメソッドを実装する必要があります。

function supportsInterface(bytes4 interfaceID) returns (bool);

この関数は以下を返す必要があります:

  • true: interfaceIDに0x01ffc9a7(EIP165インターフェースID)が指定された時
  • false: interfaceIDに0xffffffffが指定された時
  • true: 任意のinterfaceID、Contractが実装してれば
  • false: それ以外のinterfaceID

つまり、このインターフェースを実装しているのであれば、0x01ffc9a7(EIP-165のID)を渡された場合は必ずtrueを返すべきであり、0xffffffffを渡された場合はtrueを返すべきではありません。

ERC-820の提案内容

簡単な要約

この規格は、任意のアドレス(Contractまたは通常の口座)がそれがどのインタフェースを実装するかを登録することができ、どのSmart Contractがその実装を担当するかという、ユニバーサルレジストリスマート契約を定義する。

抽象

この標準は、スマートコントラクトと通常のアカウントが実装する機能を公開できるレジストリを定義しようとしています。

世界の他の国々はこのレジストリを照会して、特定のアドレスが特定のインタフェースを実装しているかどうか、またどのスマートコントラクトが実装を処理しているかを尋ねることができます。

このレジストリは、どのチェーンにも展開でき、まったく同じアドレスを共有します。

動機

Ethereumには擬似イントロスペクションを定義するさまざまなアプローチがあります。1つはEIP-165で、通常のアカウント(EOAのこと)では使用できないという問題があります。2つめの方法は、reverseENSを使用するEIP-672です。reverseENSを使用すると、2つの問題があります。第1に、それは不必要に複雑であり、第2に、ENSは依然としてマルチシーグによって制御される集中型契約である。このマルチシーグは、理論的にはシステムを修正することができます。

この規格はEIP672よりはるかに単純であり、絶対的に分散化されています。
この規格はまた、異なるChainに異なるアドレスを有するという問題を解決する。

仕様

Smart Contract
pragma solidity 0.4.19;


contract EIP820ImplementerInterface {
    /// @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 true if the contract can implement the interface represented by
    ///  `ìnterfaceHash` in behalf of `addr`
    function canImplementInterfaceForAddress(address addr, bytes32 interfaceHash) view public returns(bool);
}

contract EIP820Registry {

    mapping (address => mapping(bytes32 => address)) interfaces;
    mapping (address => address) managers;

    modifier canManage(address addr) {
        require(getManager(addr) == msg.sender);
        _;
    }

    /// @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('Ierc777`')`
    /// @return The address of the contract that implements a speficic interface
    ///  or 0x0 if `addr` does not implement this interface
    function getInterfaceImplementer(address addr, bytes32 iHash) public constant returns (address) {
        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)  {
        if ((implementer != 0) && (implementer!=msg.sender)) {
            require(EIP820ImplementerInterface(implementer).canImplementInterfaceForAddress(addr, iHash));
        }
        interfaces[addr][iHash] = implementer;
        InterfaceImplementerSet(addr, iHash, implementer);
    }

    event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);
    event ManagerChanged(address indexed addr, address indexed newManager);
}
任意のチェーンにスマート契約を展開するためのRaw Transaction
0xf9051b8085174876e800830c35008080b904c86060604052341561000f57600080fd5b6104aa8061001e6000396000f30060606040526004361061006c5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166329965a1d81146100715780633d5840631461009c5780635df8122f146100d757806365ba36c1146100fc578063aabbb8ca1461015f575b600080fd5b341561007c57600080fd5b61009a600160a060020a036004358116906024359060443516610181565b005b34156100a757600080fd5b6100bb600160a060020a03600435166102ec565b604051600160a060020a03909116815260200160405180910390f35b34156100e257600080fd5b61009a600160a060020a0360043581169060243516610338565b341561010757600080fd5b61014d60046024813581810190830135806020601f820181900481020160405190810160405281815292919060208401838380828437509496506103f395505050505050565b60405190815260200160405180910390f35b341561016a57600080fd5b6100bb600160a060020a0360043516602435610458565b8233600160a060020a0316610195826102ec565b600160a060020a0316146101a857600080fd5b600160a060020a038216158015906101d2575033600160a060020a031682600160a060020a031614155b156102735781600160a060020a031663f008325085856000604051602001526040517c010000000000000000000000000000000000000000000000000000000063ffffffff8516028152600160a060020a0390921660048301526024820152604401602060405180830381600087803b151561024d57600080fd5b6102c65a03f1151561025e57600080fd5b50505060405180519050151561027357600080fd5b600160a060020a0384811660008181526020818152604080832088845290915290819020805473ffffffffffffffffffffffffffffffffffffffff191693861693841790558591907f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db153905160405180910390a450505050565b600160a060020a038082166000908152600160205260408120549091161515610316575080610333565b50600160a060020a03808216600090815260016020526040902054165b919050565b8133600160a060020a031661034c826102ec565b600160a060020a03161461035f57600080fd5b82600160a060020a031682600160a060020a03161461037e5781610381565b60005b600160a060020a0384811660008181526001602052604090819020805473ffffffffffffffffffffffffffffffffffffffff191694841694909417909355908416917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a4350905160405180910390a3505050565b6000816040518082805190602001908083835b602083106104255780518252601f199092019160209182019101610406565b6001836020036101000a038019825116818451161790925250505091909101925060409150505180910390209050919050565b600160a060020a03918216600090815260208181526040808320938352929052205416905600a165627a7a72305820b34bad64d26ce55bab1c48c59eb70737ab782b820d0f1ed6e2f6d6780d62dec300291ba079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798a00aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

文末に文字列aがあります。これはSignature: sであり、つまり、手動で付与した署名ということです。

特別なレジストリ配備アカウント
0xc253917a2b4a2b7f43286ae500132dae7dc22459

このアカウントは、トランザクションシグネチャからリバースエンジニアリングすることによって生成されます。この方法では、誰もプライベートキーを認識できませんが、デプロイメントトランザクションの有効な署名者であることがわかります。

展開方法

この契約を展開する方法は、ETHをレジストリ展開アカウントに送信し、トランザクションをブロードキャストすることです。

まとめ

EIP-165はinterfaceで定義された実装すべきmethodのIDからinterfaceIDを生成しているのに対して、ERC-820では実装している標準インターフェース名(例えば、EIP20-Tokenとか)のhash値からinterfaceIDを生成している違いがあります。
個人的にはERC-165のほうが好きです。ERC-820も十分に有用ではありますが、現状ではそこまでする必要がないかなぁと思ってます。
ERC-165はメソッドの定義順序の問題や、そもそもハードコーディングなのでいくらでも嘘をつける(これはERC-820も同様ですが)問題もありますが、これらの両方ともSolidityのコンパイラーレベルで対応可能な問題でもあるので、SolidityがERC-165を対応してくれたりするとすごく便利になるなーって思いました。

ERC-820で興味深かったのは、ERC-820ではEOAに対しても何かしらの処理を(擬似的に)持たせることができるという点です。これによって、あるEOAアドレスに送金するとそれをトリガーにしてContractを動かしたりできるっていうのはすごい可能性があるかも?って思いました。
あと、レジストリーのaddressをどのチェーン(Ropsten、private、mainnet)でも同じ値に固定することもERC-820の提案に含まれているのも興味深いなぁって思いました。
Contractがどうして同じaddressになるのかは以下を参照ください。
addresses - How is the address of an Ethereum contract computed? - Ethereum Stack Exchange

これらの提案を元にインターフェースの検出機構が出来上がれば、Contract同士が連携して何かしらのサービスを構築することが、より安全で効率的に行える様になると思うので今後が非常に楽しみです。