🤟

【ERC6551】あらゆるProxyに適応するToken Bound Accountsを開発してみた

2024/01/23に公開

はじめに

こんにちは、kai(@dikxs118)です。
今日は、ERC6551の Proxy である Token Bound Accounts(以下TBA) を ERC1167(Minimal Proxy) 以外でもデプロイできるようにしたので、それについてまとめていこうと思います!
この記事では基本的なERC6551の説明等は行いませんのでご了承ください。

現状と課題

さて、現状のERC6551ですが、TBAは Minimal Proxy でデプロイされることがMust要件になっています。また、デプロイされるバイトコードの構造もMust要件として決まっています。

The registry MUST deploy each token bound account as an ERC-1167 minimal proxy with immutable constant data appended to the bytecode.

ERC-1167 Header               (10 bytes)
<implementation (address)>    (20 bytes)
ERC-1167 Footer               (15 bytes)
<salt (bytes32)>              (32 bytes)
<chainId (uint256)>           (32 bytes)
<tokenContract (address)>     (32 bytes)
<tokenId (uint256)>           (32 bytes)

https://eips.ethereum.org/EIPS/eip-6551

しかし、TBAを Upgradeable にしたり、他の Proxy としてデプロイしたいというニーズはあるはずです。(私はありました)

ERC6551には主に ERC6551AccountERC6551Registryの2つのコントラクトで構成されており、ERC6551Accountが TBA の実装で、ERC6551Registryが Proxy のデプロイを担っています。

私はWeb2出身なのですが、ドメインのモデリングや、責務の分割、いわゆるDDDといったアーキテクチャを考えることが多かったので、アカウントの取り回し方式(Account)デプロイ方式(Registry) に依存しているのがそもそもどうなんだろうな、と思っていました。

要は、ERC6551は アカウントの取り回しに関する規格なので、

  • デプロイ方式はなんでも良いのでは
  • 各々が疎結合で独立して振る舞いを担保している方が可用性、保守性が高いのでは

という思想が今回の出発点となり、Account が Registry に依存しない ものにしよう、という方針で開発を進めていきます。

ちなみに、過去ログを見ると、ERC1967を使えるようにしたり、戻したり、1167->1967という多段のProxyで実現できるなどの話はあったようですが、いずれにしても Account が Registry に依存している状態なのと、1167-> any proxy みたいな構成は微妙かなと思います。

実装

ERC6551Account

まずはERC6551Accountの参考実装を見ていきます。

ERC6551Account
contract ERC6551Account is IERC165, IERC1271, IERC6551Account, IERC6551Executable {
    uint256 public state;

    receive() external payable {}

    function execute(address to, uint256 value, bytes calldata data, uint8 operation)
        external
        payable
        virtual
        returns (bytes memory result)
    {
        require(_isValidSigner(msg.sender), "Invalid signer");
        require(operation == 0, "Only call operations are supported");

        ++state;

        bool success;
        (success, result) = to.call{value: value}(data);

        if (!success) {
            assembly {
                revert(add(result, 32), mload(result))
            }
        }
    }

    function isValidSigner(address signer, bytes calldata) external view virtual returns (bytes4) {
        if (_isValidSigner(signer)) {
            return IERC6551Account.isValidSigner.selector;
        }

        return bytes4(0);
    }

    function isValidSignature(bytes32 hash, bytes memory signature)
        external
        view
        virtual
        returns (bytes4 magicValue)
    {
        bool isValid = SignatureChecker.isValidSignatureNow(owner(), hash, signature);

        if (isValid) {
            return IERC1271.isValidSignature.selector;
        }

        return bytes4(0);
    }

    function supportsInterface(bytes4 interfaceId) external pure virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId
            || interfaceId == type(IERC6551Account).interfaceId
            || interfaceId == type(IERC6551Executable).interfaceId;
    }

    function token() public view virtual returns (uint256, address, uint256) {
        bytes memory footer = new bytes(0x60);

        assembly {
            extcodecopy(address(), add(footer, 0x20), 0x4d, 0x60)
        }

        return abi.decode(footer, (uint256, address, uint256));
    }

    function owner() public view virtual returns (address) {
        (uint256 chainId, address tokenContract, uint256 tokenId) = token();
        if (chainId != block.chainid) return address(0);

        return IERC721(tokenContract).ownerOf(tokenId);
    }

    function _isValidSigner(address signer) internal view virtual returns (bool) {
        return signer == owner();
    }
}

https://eips.ethereum.org/EIPS/eip-6551

ふむ、非常にシンプルですね。
どうやらERC6551の本質をすごく簡単にまとめると

  • TBA(Minimal Proxy)の bytecode に埋め込まれた情報をdecodeして、chainId, tokenContract, tokenIdtoken として取得している
  • 取得した tokenContarct, tokenId から NFT の ownerOf を呼び出すことで、ownerを取得し、認証を行なっている

ということになりそうです。その上で、bytecodeに必要な情報を埋め込んでいるために、デプロイの方式が指定されているのだということが推察できます。

実際にbytecodeから、tokenを取り出しているコードを読んでいきます。

    function token() public view virtual returns (uint256, address, uint256) {
        bytes memory footer = new bytes(0x60);

        assembly {
            extcodecopy(address(), add(footer, 0x20), 0x4d, 0x60)
        }

        return abi.decode(footer, (uint256, address, uint256));
    }

EVMのbytecodeやassemblyを読む場合は、evm codeを参照すると良いです。

やっていることをまとめると、

  1. footerとして96bytes(0x60)の領域を確保する
  2. TBAのruntimeCodeにおける、77bytes(0x4d)から96bytes(0x60)をfooterの32bytes(0x20)以降の領域にコピーする
  1. footerをdecodeする

となります。

ERC-1167 Header               (10 bytes)
<implementation (address)>    (20 bytes)
ERC-1167 Footer               (15 bytes)
<salt (bytes32)>              (32 bytes)
<chainId (uint256)>           (32 bytes)
<tokenContract (address)>     (32 bytes)
<tokenId (uint256)>           (32 bytes)

検算も兼ねて ERC6551 の bytecode の構造を見ると、77bytes(saltまで)から、96bytes(chainId,tokenContract,tokenId)が取得されていることがわかりますね。

ERC6551のコードを読んだ限り、ERC1167に依存している箇所はここだけなので、ここを汎用的にいじることができれば、ERC1167に依存せず、ERC6551Accountは動きそうです。

現在の実装は 77bytes(0x4d)をオフセットとしていることがERC1167に依存しているので、ここを bytecodeの最後から96bytes という実装にしてやれば、どのようなProxyをデプロイしても、runtimeCodeの最後の96bytesに必要な情報を載せていれば、良さそうです。

下記のように実装しました。

function token() public view virtual returns (uint256, address, uint256) {
        bytes memory footer = new bytes(0x60);

        assembly {
            extcodecopy(address(), add(footer, 0x20), sub(extcodesize(address()), 0x60), 0x60)
        }

        return abi.decode(footer, (uint256, address, uint256));
    }

ERC6551Registry

次は、ERC6551Registryの内容を改変して、どんなProxyでもできるようにしていく方針を考えます。
私はそもそもRegistryはERCの規格に含める必要はなくて、参考実装を提供するくらいで良いのかなと思っています。

今回は、ERC7546: Upgradeable Cloneを上記の実装方針に沿って、TBAのProxyとしてデプロイします。

ERC7546ついては、過去に私が記事を書いているのでそちらを参照してみてください。
https://zenn.dev/adachi_standard/articles/883ff4e4d81608

Registryの参考実装はassemblyで書かれているので、interfaceを確認して何をしているのかみていきます。

IERC6551Registry
  /**
     * @dev Creates a token bound account for a non-fungible token.
     *
     * If account has already been created, returns the account address without calling create2.
     *
     * Emits ERC6551AccountCreated event.
     *
     * @return account The address of the token bound account
     */
    function createAccount(
        address implementation,
        bytes32 salt,
        uint256 chainId,
        address tokenContract,
        uint256 tokenId
    ) external returns (address account);

    /**
     * @dev Returns the computed token bound account address for a non-fungible token.
     *
     * @return account The address of the token bound account
     */
    function account(
        address implementation,
        bytes32 salt,
        uint256 chainId,
        address tokenContract,
        uint256 tokenId
    ) external view returns (address account);

If account has already been created, returns the account address without calling create2.

どうやら、create2を使ってこのロジックでデプロイしているだけのようです。

つまり最終的なゴールは、runtimeCodeの最後にsalt(uint256)とtokenであるchainId(uint256),tokenContract(address),tokenId(uint256)の128bytesを追加した状態で create2 にてデプロイする、ということになります。

以降ステップを追って解説していきます。

EVMのバイトコード

EVMのバイトコードについて理解がないとこの先に進めないので、まずはEVMのバイトコードにおける構造について軽くおさらいします。

この記事が分かりやすいかと思います。
https://www.rareskills.io/post/ethereum-contract-creation-code

EVMのcreation codeとは一般的に、 <init code> <runtime code> <constructor parameters>から構成されます。抑える点は下記です。

init code -> デプロイの際に実行されるコード。コンストラクタを解決(初期化)し、runtime codeをアドレスに配置する
runtime code -> デプロイ後に実行されるコード。スマートコントラクトの本体。
constructor parameters -> init codeがコンストラクタを解決するときに渡される引数

バイトコードでERC7546Proxyをデプロイする

まずは、最初のステップとして、 create2を使って、128bytesをくっつけない状態で生の ERC7546Proxy をデプロイしてみましょう。

コントラクトの creation codeが欲しいので、solidityのdocsをみてみると、 type(C).creationCode で取得できるようです。
type(C).runtimeCode で runtime code も取得できるので同時に見てみましょう。

type(ERC7546Proxy).creationCode
60806040526040516105ac3803806105ac83398101604081905261002291610310565b61002c8282610033565b505061043e565b61003c8261010b565b6040516001600160a01b038316907fa657f2ad315cf3bb35cf1964158da75c3f334481df05a4a1644b2376b17a59b290600090a28051156100ff576100fa6001600160a01b03831663dc9cc645610092846103d0565b6040516001600160e01b031960e084901b81168252919091166004820152602401602060405180830381865afa1580156100d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f49190610407565b8261019a565b505050565b610107610211565b5050565b806001600160a01b03163b6000036101595760405162461bcd60e51b815260206004820152600c60248201526b1393d397d0d3d395149050d560a21b60448201526064015b60405180910390fd5b7f267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f480546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080846001600160a01b0316846040516101b79190610422565b600060405180830381855af49150503d80600081146101f2576040519150601f19603f3d011682016040523d82523d6000602084013e6101f7565b606091505b509092509050610208858383610232565b95945050505050565b34156102305760405163b398979f60e01b815260040160405180910390fd5b565b6060826102475761024282610291565b61028a565b815115801561025e57506001600160a01b0384163b155b1561028757604051639996b31560e01b81526001600160a01b0385166004820152602401610150565b50805b9392505050565b8051156102a15780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80516001600160a01b03811681146102d157600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b838110156103075781810151838201526020016102ef565b50506000910152565b6000806040838503121561032357600080fd5b61032c836102ba565b60208401519092506001600160401b038082111561034957600080fd5b818501915085601f83011261035d57600080fd5b81518181111561036f5761036f6102d6565b604051601f8201601f19908116603f01168101908382118183101715610397576103976102d6565b816040528281528860208487010111156103b057600080fd5b6103c18360208301602088016102ec565b80955050505050509250929050565b805160208201516001600160e01b031980821692919060048310156103ff5780818460040360031b1b83161693505b505050919050565b60006020828403121561041957600080fd5b61028a826102ba565b600082516104348184602087016102ec565b9190910192915050565b61015f8061044d6000396000f3fe60806040523661000b57005b610013610015565b005b610025610020610027565b6100d5565b565b600061005a7f267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f4546001600160a01b031690565b60405163dc9cc64560e01b81526001600160e01b03196000351660048201526001600160a01b03919091169063dc9cc64590602401602060405180830381865afa1580156100ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100d091906100f9565b905090565b3660008037600080366000845af43d6000803e8080156100f4573d6000f35b3d6000fd5b60006020828403121561010b57600080fd5b81516001600160a01b038116811461012257600080fd5b939250505056fea26469706673582212201fd53eab52ad2cec1c156e10b2ae6d953858615016e763909ed5a98477337a8964736f6c63430008170033
type(ERC7546Proxy).runtimeCode
60806040523661000b57005b610013610015565b005b610025610020610027565b6100d5565b565b600061005a7f267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f4546001600160a01b031690565b60405163dc9cc64560e01b81526001600160e01b03196000351660048201526001600160a01b03919091169063dc9cc64590602401602060405180830381865afa1580156100ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100d091906100f9565b905090565b3660008037600080366000845af43d6000803e8080156100f4573d6000f35b3d6000fd5b60006020828403121561010b57600080fd5b81516001600160a01b038116811461012257600080fd5b939250505056fea26469706673582212201fd53eab52ad2cec1c156e10b2ae6d953858615016e763909ed5a98477337a8964736f6c63430008170033

差分を見ると、creationCodeの最後が runtimeCode で終わっています。
まだコンストラクトの引数は与えられていないので、creationCodeは init code と runtime code を返しています。
これにコンストラクタの引数を付与してあげれば、creation codeとしてデプロイできそうなのでテストを書いて実証します。

function test_Success_Deploy_Create2() public {
        address deployer = vm.addr(100);
        address dictionary = address(new Dictionary(deployer));
        bytes memory initData = "";
        bytes memory code = abi.encodePacked(
            type(ERC7546Proxy).creationCode,
            abi.encode(dictionary, initData)
        );

        uint256 salt = 1;
        address account = Create2.deploy(0, bytes32(salt), code);
        assertEq(account.code, type(ERC7546Proxy).runtimeCode);
    }

きちんと、デプロイできています!!

128bytes加えた状態のERC7546Proxyをデプロイする

それでは、元々やりたかった ERC7546Proxy の runtimeCode に 128bytes(salt, chainId, tokenContract, tokenId) を加えてデプロイしていきましょう。

しかし、下記のように、bytesを生成しても、runtime codeに追加することは出来なさそうです。

bytes memory code = abi.encodePacked(
            type(ERC7546Proxy).creationCode,
            abi.encode(dictionary, initData),
            abi.encode(salt, chainId, tokenContract, tokenId)
        );

type(ERC7546Proxy).creationCode -> init code + runtime code
abi.encode(dictionary, initData) -> constructor parameters
abi.encode(salt, chainId, tokenContract, tokenId -> (参照されない)constructor parameters

現状を整理して、考察すると、

  • init codeの中で runtime code のサイズを決めていそう
  • init codeの中で constructor parameters を参照する順番を決めていそう

ということが推察できます。

なので、方針として、 init codeの中で runtime code のサイズを決めている部分に128bytes足して領域を確保してあげれば良さそう です。

init code -> runtime codeの領域を128bytes増やす
runtime code -> 128bytes(salt, chainId, tokenContract, tokenId)を追加する
constructor parameters -> 既存通りパラメータを渡す

init code = type(ERC7546Proxy).creationCode - type(ERC7546Proxy).runtimeCode によって導出した init codeは下記です。

init code(type(ERC7546Proxy).creationCode - type(ERC7546Proxy).runtimeCode)
60806040526040516105ac3803806105ac83398101604081905261002291610310565b61002c8282610033565b505061043e565b61003c8261010b565b6040516001600160a01b038316907fa657f2ad315cf3bb35cf1964158da75c3f334481df05a4a1644b2376b17a59b290600090a28051156100ff576100fa6001600160a01b03831663dc9cc645610092846103d0565b6040516001600160e01b031960e084901b81168252919091166004820152602401602060405180830381865afa1580156100d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f49190610407565b8261019a565b505050565b610107610211565b5050565b806001600160a01b03163b6000036101595760405162461bcd60e51b815260206004820152600c60248201526b1393d397d0d3d395149050d560a21b60448201526064015b60405180910390fd5b7f267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f480546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080846001600160a01b0316846040516101b79190610422565b600060405180830381855af49150503d80600081146101f2576040519150601f19603f3d011682016040523d82523d6000602084013e6101f7565b606091505b509092509050610208858383610232565b95945050505050565b34156102305760405163b398979f60e01b815260040160405180910390fd5b565b6060826102475761024282610291565b61028a565b815115801561025e57506001600160a01b0384163b155b1561028757604051639996b31560e01b81526001600160a01b0385166004820152602401610150565b50805b9392505050565b8051156102a15780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80516001600160a01b03811681146102d157600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b838110156103075781810151838201526020016102ef565b50506000910152565b6000806040838503121561032357600080fd5b61032c836102ba565b60208401519092506001600160401b038082111561034957600080fd5b818501915085601f83011261035d57600080fd5b81518181111561036f5761036f6102d6565b604051601f8201601f19908116603f01168101908382118183101715610397576103976102d6565b816040528281528860208487010111156103b057600080fd5b6103c18360208301602088016102ec565b80955050505050509250929050565b805160208201516001600160e01b031980821692919060048310156103ff5780818460040360031b1b83161693505b505050919050565b60006020828403121561041957600080fd5b61028a826102ba565b600082516104348184602087016102ec565b9190910192915050565b61015f8061044d6000396000f3fe

この記事を読んでいる皆さんは、EVMのbytecodeくらい脳内で解析できると思いますが、私はまだそこまで至れていないので、EVMCODEを使ってアセンブリにしてコードを読んでいきます。

EVM: assembly
assembly
[00]	PUSH1	80
[02]	PUSH1	40
[04]	MSTORE	
[05]	PUSH1	40
[07]	MLOAD	
[08]	PUSH2	05ac
[0b]	CODESIZE	
[0c]	SUB	
[0d]	DUP1	
[0e]	PUSH2	05ac
[11]	DUP4	
[12]	CODECOPY	
[13]	DUP2	
[14]	ADD	
[15]	PUSH1	40
[17]	DUP2	
[18]	SWAP1	
[19]	MSTORE	
[1a]	PUSH2	0022
[1d]	SWAP2	
[1e]	PUSH2	0310
[21]	JUMP	
[22]	JUMPDEST	
[23]	PUSH2	002c
[26]	DUP3	
[27]	DUP3	
[28]	PUSH2	0033
[2b]	JUMP	
[2c]	JUMPDEST	
[2d]	POP	
[2e]	POP	
[2f]	PUSH2	043e
[32]	JUMP	
[33]	JUMPDEST	
[34]	PUSH2	003c
[37]	DUP3	
[38]	PUSH2	010b
[3b]	JUMP	
[3c]	JUMPDEST	
[3d]	PUSH1	40
[3f]	MLOAD	
[40]	PUSH1	01
[42]	PUSH1	01
[44]	PUSH1	a0
[46]	SHL	
[47]	SUB	
[48]	DUP4	
[49]	AND	
[4a]	SWAP1	
[4b]	PUSH32	a657f2ad315cf3bb35cf1964158da75c3f334481df05a4a1644b2376b17a59b2
[6c]	SWAP1	
[6d]	PUSH1	00
[6f]	SWAP1	
[70]	LOG2	
[71]	DUP1	
[72]	MLOAD	
[73]	ISZERO	
[74]	PUSH2	00ff
[77]	JUMPI	
[78]	PUSH2	00fa
[7b]	PUSH1	01
[7d]	PUSH1	01
[7f]	PUSH1	a0
[81]	SHL	
[82]	SUB	
[83]	DUP4	
[84]	AND	
[85]	PUSH4	dc9cc645
[8a]	PUSH2	0092
[8d]	DUP5	
[8e]	PUSH2	03d0
[91]	JUMP	
[92]	JUMPDEST	
[93]	PUSH1	40
[95]	MLOAD	
[96]	PUSH1	01
[98]	PUSH1	01
[9a]	PUSH1	e0
[9c]	SHL	
[9d]	SUB	
[9e]	NOT	
[9f]	PUSH1	e0
[a1]	DUP5	
[a2]	SWAP1	
[a3]	SHL	
[a4]	DUP2	
[a5]	AND	
[a6]	DUP3	
[a7]	MSTORE	
[a8]	SWAP2	
[a9]	SWAP1	
[aa]	SWAP2	
[ab]	AND	
[ac]	PUSH1	04
[ae]	DUP3	
[af]	ADD	
[b0]	MSTORE	
[b1]	PUSH1	24
[b3]	ADD	
[b4]	PUSH1	20
[b6]	PUSH1	40
[b8]	MLOAD	
[b9]	DUP1	
[ba]	DUP4	
[bb]	SUB	
[bc]	DUP2	
[bd]	DUP7	
[be]	GAS	
[bf]	STATICCALL	
[c0]	ISZERO	
[c1]	DUP1	
[c2]	ISZERO	
[c3]	PUSH2	00d0
[c6]	JUMPI	
[c7]	RETURNDATASIZE	
[c8]	PUSH1	00
[ca]	DUP1	
[cb]	RETURNDATACOPY	
[cc]	RETURNDATASIZE	
[cd]	PUSH1	00
[cf]	REVERT	
[d0]	JUMPDEST	
[d1]	POP	
[d2]	POP	
[d3]	POP	
[d4]	POP	
[d5]	PUSH1	40
[d7]	MLOAD	
[d8]	RETURNDATASIZE	
[d9]	PUSH1	1f
[db]	NOT	
[dc]	PUSH1	1f
[de]	DUP3	
[df]	ADD	
[e0]	AND	
[e1]	DUP3	
[e2]	ADD	
[e3]	DUP1	
[e4]	PUSH1	40
[e6]	MSTORE	
[e7]	POP	
[e8]	DUP2	
[e9]	ADD	
[ea]	SWAP1	
[eb]	PUSH2	00f4
[ee]	SWAP2	
[ef]	SWAP1	
[f0]	PUSH2	0407
[f3]	JUMP	
[f4]	JUMPDEST	
[f5]	DUP3	
[f6]	PUSH2	019a
[f9]	JUMP	
[fa]	JUMPDEST	
[fb]	POP	
[fc]	POP	
[fd]	POP	
[fe]	JUMP	
[ff]	JUMPDEST	
[100]	PUSH2	0107
[103]	PUSH2	0211
[106]	JUMP	
[107]	JUMPDEST	
[108]	POP	
[109]	POP	
[10a]	JUMP	
[10b]	JUMPDEST	
[10c]	DUP1	
[10d]	PUSH1	01
[10f]	PUSH1	01
[111]	PUSH1	a0
[113]	SHL	
[114]	SUB	
[115]	AND	
[116]	EXTCODESIZE	
[117]	PUSH1	00
[119]	SUB	
[11a]	PUSH2	0159
[11d]	JUMPI	
[11e]	PUSH1	40
[120]	MLOAD	
[121]	PUSH3	461bcd
[125]	PUSH1	e5
[127]	SHL	
[128]	DUP2	
[129]	MSTORE	
[12a]	PUSH1	20
[12c]	PUSH1	04
[12e]	DUP3	
[12f]	ADD	
[130]	MSTORE	
[131]	PUSH1	0c
[133]	PUSH1	24
[135]	DUP3	
[136]	ADD	
[137]	MSTORE	
[138]	PUSH12	1393d397d0d3d395149050d5
[145]	PUSH1	a2
[147]	SHL	
[148]	PUSH1	44
[14a]	DUP3	
[14b]	ADD	
[14c]	MSTORE	
[14d]	PUSH1	64
[14f]	ADD	
[150]	JUMPDEST	
[151]	PUSH1	40
[153]	MLOAD	
[154]	DUP1	
[155]	SWAP2	
[156]	SUB	
[157]	SWAP1	
[158]	REVERT	
[159]	JUMPDEST	
[15a]	PUSH32	267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f4
[17b]	DUP1	
[17c]	SLOAD	
[17d]	PUSH1	01
[17f]	PUSH1	01
[181]	PUSH1	a0
[183]	SHL	
[184]	SUB	
[185]	NOT	
[186]	AND	
[187]	PUSH1	01
[189]	PUSH1	01
[18b]	PUSH1	a0
[18d]	SHL	
[18e]	SUB	
[18f]	SWAP3	
[190]	SWAP1	
[191]	SWAP3	
[192]	AND	
[193]	SWAP2	
[194]	SWAP1	
[195]	SWAP2	
[196]	OR	
[197]	SWAP1	
[198]	SSTORE	
[199]	JUMP	
[19a]	JUMPDEST	
[19b]	PUSH1	60
[19d]	PUSH1	00
[19f]	DUP1	
[1a0]	DUP5	
[1a1]	PUSH1	01
[1a3]	PUSH1	01
[1a5]	PUSH1	a0
[1a7]	SHL	
[1a8]	SUB	
[1a9]	AND	
[1aa]	DUP5	
[1ab]	PUSH1	40
[1ad]	MLOAD	
[1ae]	PUSH2	01b7
[1b1]	SWAP2	
[1b2]	SWAP1	
[1b3]	PUSH2	0422
[1b6]	JUMP	
[1b7]	JUMPDEST	
[1b8]	PUSH1	00
[1ba]	PUSH1	40
[1bc]	MLOAD	
[1bd]	DUP1	
[1be]	DUP4	
[1bf]	SUB	
[1c0]	DUP2	
[1c1]	DUP6	
[1c2]	GAS	
[1c3]	DELEGATECALL	
[1c4]	SWAP2	
[1c5]	POP	
[1c6]	POP	
[1c7]	RETURNDATASIZE	
[1c8]	DUP1	
[1c9]	PUSH1	00
[1cb]	DUP2	
[1cc]	EQ	
[1cd]	PUSH2	01f2
[1d0]	JUMPI	
[1d1]	PUSH1	40
[1d3]	MLOAD	
[1d4]	SWAP2	
[1d5]	POP	
[1d6]	PUSH1	1f
[1d8]	NOT	
[1d9]	PUSH1	3f
[1db]	RETURNDATASIZE	
[1dc]	ADD	
[1dd]	AND	
[1de]	DUP3	
[1df]	ADD	
[1e0]	PUSH1	40
[1e2]	MSTORE	
[1e3]	RETURNDATASIZE	
[1e4]	DUP3	
[1e5]	MSTORE	
[1e6]	RETURNDATASIZE	
[1e7]	PUSH1	00
[1e9]	PUSH1	20
[1eb]	DUP5	
[1ec]	ADD	
[1ed]	RETURNDATACOPY	
[1ee]	PUSH2	01f7
[1f1]	JUMP	
[1f2]	JUMPDEST	
[1f3]	PUSH1	60
[1f5]	SWAP2	
[1f6]	POP	
[1f7]	JUMPDEST	
[1f8]	POP	
[1f9]	SWAP1	
[1fa]	SWAP3	
[1fb]	POP	
[1fc]	SWAP1	
[1fd]	POP	
[1fe]	PUSH2	0208
[201]	DUP6	
[202]	DUP4	
[203]	DUP4	
[204]	PUSH2	0232
[207]	JUMP	
[208]	JUMPDEST	
[209]	SWAP6	
[20a]	SWAP5	
[20b]	POP	
[20c]	POP	
[20d]	POP	
[20e]	POP	
[20f]	POP	
[210]	JUMP	
[211]	JUMPDEST	
[212]	CALLVALUE	
[213]	ISZERO	
[214]	PUSH2	0230
[217]	JUMPI	
[218]	PUSH1	40
[21a]	MLOAD	
[21b]	PUSH4	b398979f
[220]	PUSH1	e0
[222]	SHL	
[223]	DUP2	
[224]	MSTORE	
[225]	PUSH1	04
[227]	ADD	
[228]	PUSH1	40
[22a]	MLOAD	
[22b]	DUP1	
[22c]	SWAP2	
[22d]	SUB	
[22e]	SWAP1	
[22f]	REVERT	
[230]	JUMPDEST	
[231]	JUMP	
[232]	JUMPDEST	
[233]	PUSH1	60
[235]	DUP3	
[236]	PUSH2	0247
[239]	JUMPI	
[23a]	PUSH2	0242
[23d]	DUP3	
[23e]	PUSH2	0291
[241]	JUMP	
[242]	JUMPDEST	
[243]	PUSH2	028a
[246]	JUMP	
[247]	JUMPDEST	
[248]	DUP2	
[249]	MLOAD	
[24a]	ISZERO	
[24b]	DUP1	
[24c]	ISZERO	
[24d]	PUSH2	025e
[250]	JUMPI	
[251]	POP	
[252]	PUSH1	01
[254]	PUSH1	01
[256]	PUSH1	a0
[258]	SHL	
[259]	SUB	
[25a]	DUP5	
[25b]	AND	
[25c]	EXTCODESIZE	
[25d]	ISZERO	
[25e]	JUMPDEST	
[25f]	ISZERO	
[260]	PUSH2	0287
[263]	JUMPI	
[264]	PUSH1	40
[266]	MLOAD	
[267]	PUSH4	9996b315
[26c]	PUSH1	e0
[26e]	SHL	
[26f]	DUP2	
[270]	MSTORE	
[271]	PUSH1	01
[273]	PUSH1	01
[275]	PUSH1	a0
[277]	SHL	
[278]	SUB	
[279]	DUP6	
[27a]	AND	
[27b]	PUSH1	04
[27d]	DUP3	
[27e]	ADD	
[27f]	MSTORE	
[280]	PUSH1	24
[282]	ADD	
[283]	PUSH2	0150
[286]	JUMP	
[287]	JUMPDEST	
[288]	POP	
[289]	DUP1	
[28a]	JUMPDEST	
[28b]	SWAP4	
[28c]	SWAP3	
[28d]	POP	
[28e]	POP	
[28f]	POP	
[290]	JUMP	
[291]	JUMPDEST	
[292]	DUP1	
[293]	MLOAD	
[294]	ISZERO	
[295]	PUSH2	02a1
[298]	JUMPI	
[299]	DUP1	
[29a]	MLOAD	
[29b]	DUP1	
[29c]	DUP3	
[29d]	PUSH1	20
[29f]	ADD	
[2a0]	REVERT	
[2a1]	JUMPDEST	
[2a2]	PUSH1	40
[2a4]	MLOAD	
[2a5]	PUSH4	0a12f521
[2aa]	PUSH1	e1
[2ac]	SHL	
[2ad]	DUP2	
[2ae]	MSTORE	
[2af]	PUSH1	04
[2b1]	ADD	
[2b2]	PUSH1	40
[2b4]	MLOAD	
[2b5]	DUP1	
[2b6]	SWAP2	
[2b7]	SUB	
[2b8]	SWAP1	
[2b9]	REVERT	
[2ba]	JUMPDEST	
[2bb]	DUP1	
[2bc]	MLOAD	
[2bd]	PUSH1	01
[2bf]	PUSH1	01
[2c1]	PUSH1	a0
[2c3]	SHL	
[2c4]	SUB	
[2c5]	DUP2	
[2c6]	AND	
[2c7]	DUP2	
[2c8]	EQ	
[2c9]	PUSH2	02d1
[2cc]	JUMPI	
[2cd]	PUSH1	00
[2cf]	DUP1	
[2d0]	REVERT	
[2d1]	JUMPDEST	
[2d2]	SWAP2	
[2d3]	SWAP1	
[2d4]	POP	
[2d5]	JUMP	
[2d6]	JUMPDEST	
[2d7]	PUSH4	4e487b71
[2dc]	PUSH1	e0
[2de]	SHL	
[2df]	PUSH1	00
[2e1]	MSTORE	
[2e2]	PUSH1	41
[2e4]	PUSH1	04
[2e6]	MSTORE	
[2e7]	PUSH1	24
[2e9]	PUSH1	00
[2eb]	REVERT	
[2ec]	JUMPDEST	
[2ed]	PUSH1	00
[2ef]	JUMPDEST	
[2f0]	DUP4	
[2f1]	DUP2	
[2f2]	LT	
[2f3]	ISZERO	
[2f4]	PUSH2	0307
[2f7]	JUMPI	
[2f8]	DUP2	
[2f9]	DUP2	
[2fa]	ADD	
[2fb]	MLOAD	
[2fc]	DUP4	
[2fd]	DUP3	
[2fe]	ADD	
[2ff]	MSTORE	
[300]	PUSH1	20
[302]	ADD	
[303]	PUSH2	02ef
[306]	JUMP	
[307]	JUMPDEST	
[308]	POP	
[309]	POP	
[30a]	PUSH1	00
[30c]	SWAP2	
[30d]	ADD	
[30e]	MSTORE	
[30f]	JUMP	
[310]	JUMPDEST	
[311]	PUSH1	00
[313]	DUP1	
[314]	PUSH1	40
[316]	DUP4	
[317]	DUP6	
[318]	SUB	
[319]	SLT	
[31a]	ISZERO	
[31b]	PUSH2	0323
[31e]	JUMPI	
[31f]	PUSH1	00
[321]	DUP1	
[322]	REVERT	
[323]	JUMPDEST	
[324]	PUSH2	032c
[327]	DUP4	
[328]	PUSH2	02ba
[32b]	JUMP	
[32c]	JUMPDEST	
[32d]	PUSH1	20
[32f]	DUP5	
[330]	ADD	
[331]	MLOAD	
[332]	SWAP1	
[333]	SWAP3	
[334]	POP	
[335]	PUSH1	01
[337]	PUSH1	01
[339]	PUSH1	40
[33b]	SHL	
[33c]	SUB	
[33d]	DUP1	
[33e]	DUP3	
[33f]	GT	
[340]	ISZERO	
[341]	PUSH2	0349
[344]	JUMPI	
[345]	PUSH1	00
[347]	DUP1	
[348]	REVERT	
[349]	JUMPDEST	
[34a]	DUP2	
[34b]	DUP6	
[34c]	ADD	
[34d]	SWAP2	
[34e]	POP	
[34f]	DUP6	
[350]	PUSH1	1f
[352]	DUP4	
[353]	ADD	
[354]	SLT	
[355]	PUSH2	035d
[358]	JUMPI	
[359]	PUSH1	00
[35b]	DUP1	
[35c]	REVERT	
[35d]	JUMPDEST	
[35e]	DUP2	
[35f]	MLOAD	
[360]	DUP2	
[361]	DUP2	
[362]	GT	
[363]	ISZERO	
[364]	PUSH2	036f
[367]	JUMPI	
[368]	PUSH2	036f
[36b]	PUSH2	02d6
[36e]	JUMP	
[36f]	JUMPDEST	
[370]	PUSH1	40
[372]	MLOAD	
[373]	PUSH1	1f
[375]	DUP3	
[376]	ADD	
[377]	PUSH1	1f
[379]	NOT	
[37a]	SWAP1	
[37b]	DUP2	
[37c]	AND	
[37d]	PUSH1	3f
[37f]	ADD	
[380]	AND	
[381]	DUP2	
[382]	ADD	
[383]	SWAP1	
[384]	DUP4	
[385]	DUP3	
[386]	GT	
[387]	DUP2	
[388]	DUP4	
[389]	LT	
[38a]	OR	
[38b]	ISZERO	
[38c]	PUSH2	0397
[38f]	JUMPI	
[390]	PUSH2	0397
[393]	PUSH2	02d6
[396]	JUMP	
[397]	JUMPDEST	
[398]	DUP2	
[399]	PUSH1	40
[39b]	MSTORE	
[39c]	DUP3	
[39d]	DUP2	
[39e]	MSTORE	
[39f]	DUP9	
[3a0]	PUSH1	20
[3a2]	DUP5	
[3a3]	DUP8	
[3a4]	ADD	
[3a5]	ADD	
[3a6]	GT	
[3a7]	ISZERO	
[3a8]	PUSH2	03b0
[3ab]	JUMPI	
[3ac]	PUSH1	00
[3ae]	DUP1	
[3af]	REVERT	
[3b0]	JUMPDEST	
[3b1]	PUSH2	03c1
[3b4]	DUP4	
[3b5]	PUSH1	20
[3b7]	DUP4	
[3b8]	ADD	
[3b9]	PUSH1	20
[3bb]	DUP9	
[3bc]	ADD	
[3bd]	PUSH2	02ec
[3c0]	JUMP	
[3c1]	JUMPDEST	
[3c2]	DUP1	
[3c3]	SWAP6	
[3c4]	POP	
[3c5]	POP	
[3c6]	POP	
[3c7]	POP	
[3c8]	POP	
[3c9]	POP	
[3ca]	SWAP3	
[3cb]	POP	
[3cc]	SWAP3	
[3cd]	SWAP1	
[3ce]	POP	
[3cf]	JUMP	
[3d0]	JUMPDEST	
[3d1]	DUP1	
[3d2]	MLOAD	
[3d3]	PUSH1	20
[3d5]	DUP3	
[3d6]	ADD	
[3d7]	MLOAD	
[3d8]	PUSH1	01
[3da]	PUSH1	01
[3dc]	PUSH1	e0
[3de]	SHL	
[3df]	SUB	
[3e0]	NOT	
[3e1]	DUP1	
[3e2]	DUP3	
[3e3]	AND	
[3e4]	SWAP3	
[3e5]	SWAP2	
[3e6]	SWAP1	
[3e7]	PUSH1	04
[3e9]	DUP4	
[3ea]	LT	
[3eb]	ISZERO	
[3ec]	PUSH2	03ff
[3ef]	JUMPI	
[3f0]	DUP1	
[3f1]	DUP2	
[3f2]	DUP5	
[3f3]	PUSH1	04
[3f5]	SUB	
[3f6]	PUSH1	03
[3f8]	SHL	
[3f9]	SHL	
[3fa]	DUP4	
[3fb]	AND	
[3fc]	AND	
[3fd]	SWAP4	
[3fe]	POP	
[3ff]	JUMPDEST	
[400]	POP	
[401]	POP	
[402]	POP	
[403]	SWAP2	
[404]	SWAP1	
[405]	POP	
[406]	JUMP	
[407]	JUMPDEST	
[408]	PUSH1	00
[40a]	PUSH1	20
[40c]	DUP3	
[40d]	DUP5	
[40e]	SUB	
[40f]	SLT	
[410]	ISZERO	
[411]	PUSH2	0419
[414]	JUMPI	
[415]	PUSH1	00
[417]	DUP1	
[418]	REVERT	
[419]	JUMPDEST	
[41a]	PUSH2	028a
[41d]	DUP3	
[41e]	PUSH2	02ba
[421]	JUMP	
[422]	JUMPDEST	
[423]	PUSH1	00
[425]	DUP3	
[426]	MLOAD	
[427]	PUSH2	0434
[42a]	DUP2	
[42b]	DUP5	
[42c]	PUSH1	20
[42e]	DUP8	
[42f]	ADD	
[430]	PUSH2	02ec
[433]	JUMP	
[434]	JUMPDEST	
[435]	SWAP2	
[436]	SWAP1	
[437]	SWAP2	
[438]	ADD	
[439]	SWAP3	
[43a]	SWAP2	
[43b]	POP	
[43c]	POP	
[43d]	JUMP	
[43e]	JUMPDEST	
[43f]	PUSH2	015f
[442]	DUP1	
[443]	PUSH2	044d
[446]	PUSH1	00
[448]	CODECOPY	
[449]	PUSH1	00
[44b]	RETURN	

アセンブリの詳細は省きますが、
下記3つの技を使えば、大抵 runtime code の領域を確保している箇所を特定できます。

  • type(ERC7546Proxy).runtimeCode のサイズを16進にして探す
    • 今回は351bytes(0x015F)なので[43f]
  • CODECOPYしている前で領域を確保している場所を探す
  • アセンブリを全部読む

ERC7546Proxyは、コンストラクタがそれなりにコードを持っているので結構大変ですが、コンストラクタを持っていないProxyや、軽量なコンストラクタの場合は瞬殺で探し出せると思います。

今回は結論として、下記二つに128bytesを足す必要がありました。

  • [0e] 05ac -> コンストラクタの開始位置(これをずらさないと引数を取りに行くbytesもズレる)
  • [43f] 015F -> 実際の runtime 領域のサイズ

これらに128bytes(0x80)を足した init code に runtime code を加えると下記のbytesになります。

runtime codeを128bytes拡張したERC7546Proxy
60806040526040516105ac38038061062c83398101604081905261002291610310565b61002c8282610033565b505061043e565b61003c8261010b565b6040516001600160a01b038316907fa657f2ad315cf3bb35cf1964158da75c3f334481df05a4a1644b2376b17a59b290600090a28051156100ff576100fa6001600160a01b03831663dc9cc645610092846103d0565b6040516001600160e01b031960e084901b81168252919091166004820152602401602060405180830381865afa1580156100d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f49190610407565b8261019a565b505050565b610107610211565b5050565b806001600160a01b03163b6000036101595760405162461bcd60e51b815260206004820152600c60248201526b1393d397d0d3d395149050d560a21b60448201526064015b60405180910390fd5b7f267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f480546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080846001600160a01b0316846040516101b79190610422565b600060405180830381855af49150503d80600081146101f2576040519150601f19603f3d011682016040523d82523d6000602084013e6101f7565b606091505b509092509050610208858383610232565b95945050505050565b34156102305760405163b398979f60e01b815260040160405180910390fd5b565b6060826102475761024282610291565b61028a565b815115801561025e57506001600160a01b0384163b155b1561028757604051639996b31560e01b81526001600160a01b0385166004820152602401610150565b50805b9392505050565b8051156102a15780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80516001600160a01b03811681146102d157600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b838110156103075781810151838201526020016102ef565b50506000910152565b6000806040838503121561032357600080fd5b61032c836102ba565b60208401519092506001600160401b038082111561034957600080fd5b818501915085601f83011261035d57600080fd5b81518181111561036f5761036f6102d6565b604051601f8201601f19908116603f01168101908382118183101715610397576103976102d6565b816040528281528860208487010111156103b057600080fd5b6103c18360208301602088016102ec565b80955050505050509250929050565b805160208201516001600160e01b031980821692919060048310156103ff5780818460040360031b1b83161693505b505050919050565b60006020828403121561041957600080fd5b61028a826102ba565b600082516104348184602087016102ec565b9190910192915050565b6101df8061044d6000396000f3fe60806040523661000b57005b610013610015565b005b610025610020610027565b6100d5565b565b600061005a7f267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f4546001600160a01b031690565b60405163dc9cc64560e01b81526001600160e01b03196000351660048201526001600160a01b03919091169063dc9cc64590602401602060405180830381865afa1580156100ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100d091906100f9565b905090565b3660008037600080366000845af43d6000803e8080156100f4573d6000f35b3d6000fd5b60006020828403121561010b57600080fd5b81516001600160a01b038116811461012257600080fd5b939250505056fea26469706673582212201fd53eab52ad2cec1c156e10b2ae6d953858615016e763909ed5a98477337a8964736f6c63430008170033

コードを書いてデプロイ後の runtime code を確認してみましょう。

function test_Success_Deploy_ERC7546Proxy_For_ERC6551Account() public {
        address deployer = vm.addr(100);
        address dictionary = address(new Dictionary(deployer));
        bytes memory initData = "";


        uint256 salt = 1;
        uint256 chainId = 2;
        address tokenContract = vm.addr(101);
        uint256 tokenId = 3;
        bytes memory code = abi.encodePacked(
        /// @dev parse type(ERC7546Proxy).creationCode to increase the runtimeCode area by 128 bytes.
            hex"60806040526040516105ac38038061062c83398101604081905261002291610310565b61002c8282610033565b505061043e565b61003c8261010b565b6040516001600160a01b038316907fa657f2ad315cf3bb35cf1964158da75c3f334481df05a4a1644b2376b17a59b290600090a28051156100ff576100fa6001600160a01b03831663dc9cc645610092846103d0565b6040516001600160e01b031960e084901b81168252919091166004820152602401602060405180830381865afa1580156100d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f49190610407565b8261019a565b505050565b610107610211565b5050565b806001600160a01b03163b6000036101595760405162461bcd60e51b815260206004820152600c60248201526b1393d397d0d3d395149050d560a21b60448201526064015b60405180910390fd5b7f267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f480546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080846001600160a01b0316846040516101b79190610422565b600060405180830381855af49150503d80600081146101f2576040519150601f19603f3d011682016040523d82523d6000602084013e6101f7565b606091505b509092509050610208858383610232565b95945050505050565b34156102305760405163b398979f60e01b815260040160405180910390fd5b565b6060826102475761024282610291565b61028a565b815115801561025e57506001600160a01b0384163b155b1561028757604051639996b31560e01b81526001600160a01b0385166004820152602401610150565b50805b9392505050565b8051156102a15780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80516001600160a01b03811681146102d157600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b838110156103075781810151838201526020016102ef565b50506000910152565b6000806040838503121561032357600080fd5b61032c836102ba565b60208401519092506001600160401b038082111561034957600080fd5b818501915085601f83011261035d57600080fd5b81518181111561036f5761036f6102d6565b604051601f8201601f19908116603f01168101908382118183101715610397576103976102d6565b816040528281528860208487010111156103b057600080fd5b6103c18360208301602088016102ec565b80955050505050509250929050565b805160208201516001600160e01b031980821692919060048310156103ff5780818460040360031b1b83161693505b505050919050565b60006020828403121561041957600080fd5b61028a826102ba565b600082516104348184602087016102ec565b9190910192915050565b6101df8061044d6000396000f3fe60806040523661000b57005b610013610015565b005b610025610020610027565b6100d5565b565b600061005a7f267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f4546001600160a01b031690565b60405163dc9cc64560e01b81526001600160e01b03196000351660048201526001600160a01b03919091169063dc9cc64590602401602060405180830381865afa1580156100ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100d091906100f9565b905090565b3660008037600080366000845af43d6000803e8080156100f4573d6000f35b3d6000fd5b60006020828403121561010b57600080fd5b81516001600160a01b038116811461012257600080fd5b939250505056fea26469706673582212201fd53eab52ad2cec1c156e10b2ae6d953858615016e763909ed5a98477337a8964736f6c63430008170033",
            abi.encode(salt, chainId, tokenContract, tokenId),
            abi.encode(dictionary, initData)
        );


        address account = Create2.deploy(0, bytes32(salt), code);
        console2.logBytes(account.code);
    }

0x60806040523661000b57005b610013610015565b005b610025610020610027565b6100d5565b565b600061005a7f267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f4546001600160a01b031690565b60405163dc9cc64560e01b81526001600160e01b03196000351660048201526001600160a01b03919091169063dc9cc64590602401602060405180830381865afa1580156100ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100d091906100f9565b905090565b3660008037600080366000845af43d6000803e8080156100f4573d6000f35b3d6000fd5b60006020828403121561010b57600080fd5b81516001600160a01b038116811461012257600080fd5b939250505056fea26469706673582212201fd53eab52ad2cec1c156e10b2ae6d953858615016e763909ed5a98477337a8964736f6c6343000817003300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000e6b3367318c5e11a6eed3cd0d850ec06a02e9b900000000000000000000000000000000000000000000000000000000000000003

runtime code の最後128bytesが期待した値になっていますね!!!
実際にその後、ERC6551Accountとの連携を確認しましたが、全て期待通りの動作をしました👍

最終的なコードはこうなりました。

ERC7546Registry
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/utils/Create2.sol";
import {IERC7546Registry} from "./interfaces/IERC7546Registry.sol";
import "@ucs/proxy/ERC7546Proxy.sol";

contract ERC7546Registry is IERC7546Registry {

    function createAccount(
        uint256 salt,
        uint256 chainId,
        address tokenContract,
        uint256 tokenId,
        address dictionary,
        bytes calldata initData
    ) external override returns (address payable) {
        bytes memory code = _creationCode(salt, chainId, tokenContract, tokenId, dictionary, initData);

        address _account = Create2.computeAddress(
            bytes32(salt),
            keccak256(code)
        );

        if (_account.code.length != 0) return payable(_account);

        _account = Create2.deploy(0, bytes32(salt), code);

        emit ERC7546AccountCreated(
            _account,
            salt,
            chainId,
            tokenContract,
            tokenId,
            dictionary
        );

        return payable(_account);
    }

    function account(
        uint256 salt,
        uint256 chainId,
        address tokenContract,
        uint256 tokenId,
        address dictionary,
        bytes calldata initData
    ) external view returns (address payable) {
        bytes memory code = _creationCode(salt,chainId,tokenContract,tokenId, dictionary, initData);

        address _account = Create2.computeAddress(
            bytes32(salt),
            keccak256(code)
        );
        return payable(_account);
    }

    function _creationCode(
        uint256 salt,
        uint256 chainId,
        address tokenContract,
        uint256 tokenId,
        address dictionary,
        bytes calldata initData
    ) internal pure returns (bytes memory) {
        return
            abi.encodePacked(
        /// @dev parse type(ERC7546Proxy).creationCode to increase the runtimeCode area by 128 bytes.
            hex"60806040526040516105ac38038061062c83398101604081905261002291610310565b61002c8282610033565b505061043e565b61003c8261010b565b6040516001600160a01b038316907fa657f2ad315cf3bb35cf1964158da75c3f334481df05a4a1644b2376b17a59b290600090a28051156100ff576100fa6001600160a01b03831663dc9cc645610092846103d0565b6040516001600160e01b031960e084901b81168252919091166004820152602401602060405180830381865afa1580156100d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f49190610407565b8261019a565b505050565b610107610211565b5050565b806001600160a01b03163b6000036101595760405162461bcd60e51b815260206004820152600c60248201526b1393d397d0d3d395149050d560a21b60448201526064015b60405180910390fd5b7f267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f480546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080846001600160a01b0316846040516101b79190610422565b600060405180830381855af49150503d80600081146101f2576040519150601f19603f3d011682016040523d82523d6000602084013e6101f7565b606091505b509092509050610208858383610232565b95945050505050565b34156102305760405163b398979f60e01b815260040160405180910390fd5b565b6060826102475761024282610291565b61028a565b815115801561025e57506001600160a01b0384163b155b1561028757604051639996b31560e01b81526001600160a01b0385166004820152602401610150565b50805b9392505050565b8051156102a15780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80516001600160a01b03811681146102d157600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b838110156103075781810151838201526020016102ef565b50506000910152565b6000806040838503121561032357600080fd5b61032c836102ba565b60208401519092506001600160401b038082111561034957600080fd5b818501915085601f83011261035d57600080fd5b81518181111561036f5761036f6102d6565b604051601f8201601f19908116603f01168101908382118183101715610397576103976102d6565b816040528281528860208487010111156103b057600080fd5b6103c18360208301602088016102ec565b80955050505050509250929050565b805160208201516001600160e01b031980821692919060048310156103ff5780818460040360031b1b83161693505b505050919050565b60006020828403121561041957600080fd5b61028a826102ba565b600082516104348184602087016102ec565b9190910192915050565b6101df8061044d6000396000f3fe60806040523661000b57005b610013610015565b005b610025610020610027565b6100d5565b565b600061005a7f267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f4546001600160a01b031690565b60405163dc9cc64560e01b81526001600160e01b03196000351660048201526001600160a01b03919091169063dc9cc64590602401602060405180830381865afa1580156100ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100d091906100f9565b905090565b3660008037600080366000845af43d6000803e8080156100f4573d6000f35b3d6000fd5b60006020828403121561010b57600080fd5b81516001600160a01b038116811461012257600080fd5b939250505056fea26469706673582212201fd53eab52ad2cec1c156e10b2ae6d953858615016e763909ed5a98477337a8964736f6c63430008170033",
            abi.encode(salt, chainId, tokenContract, tokenId),
            abi.encode(dictionary, initData)
        );
    }
}

まとめ

最後まで読んでいただきありがとうございました!まとめます。

今回の検証で、ERC6551Account と Registry に依存関係がなくなることを確認しました。
この実装では、

  • ERC6551Account は デプロイ方式に依存しない(runtime codeの後ろ128bytesに必要な情報をつけることはすでに仕様となっている)
  • RegistryはデプロイしたいProxyに合わせて実装する

という方針をとることで、より汎用性の高いERC6551の実現が実証できたのではないかと思います。ガスコストの最適化といった文面では当然1167に劣りますが、1167を使いたい人はそれを使えば良いし、それ以外も自由に使えるようになったことで、私個人的には満足しています。

指摘や、マサカリは大歓迎ですので、忌憚なくコメントいただければ幸いです!

最後に、bytecodeを読めないようでは、まだまだ solidity が書けるとは言えないと教えていただいた @ecdysis_xyz@kai_hiroi, @0xHaku に感謝です🙏

Discussion