1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

anvil のdeploy proxy をEVMアセンブリ解析してみた

Posted at

FoundryのテストネットツールAnvilにはデフォルトでCREATE2 Deployerというコントラクトが 0x4e59b44847b379578588920ca78fbf26c0b4956cに入っています。実際のメインネットにもありますがetherscanにもソースコードがないんですよね。 使い方がよくわからなかったので調べてみました。

先に結論

CREATE2_FACTORY.call(abi.encodePacked(salt32bytes,initCode))
先頭に32bytesのsaltをつけてあげたcreationCodeをなげるとよいです。

deply proxy EVM アセンブリ解析

これがそのgithubなのですが、コントラクトはYULで書かれています。
https://github.com/Arachnid/deterministic-deployment-proxy

YULがよくわからないのでアセンブリからみてみました。(コードが短いしEVMアセンブラの勉強もかねて。)
以下がruntimeコードです。Foundryのツールcast code <contract-address> -dでEVMアセンブラが表示できます。

root@aaa:/root# cast code 0x4e59b44847b379578588920ca78fbf26c0b4956c -d
00000000: PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0
00000021: CALLDATASIZE
00000022: ADD
00000023: PUSH1 0x0
00000025: DUP2
00000026: PUSH1 0x20
00000028: DUP3
00000029: CALLDATACOPY
0000002a: DUP1
0000002b: CALLDATALOAD
0000002c: DUP3
0000002d: DUP3
0000002e: CALLVALUE
0000002f: CREATE2
00000030: DUP1
00000031: ISZERO
00000032: ISZERO
00000033: PUSH1 0x39
00000035: JUMPI
00000036: DUP2
00000037: DUP3
00000038: REVERT
00000039: JUMPDEST
0000003a: DUP1
0000003b: DUP3
0000003c: MSTORE
0000003d: POP
0000003e: POP
0000003f: POP
00000040: PUSH1 0x14
00000042: PUSH1 0xc
00000044: RETURN

以下がEVMアセンブリとスタックの状態。EVMはレジスタを持っていないでStackで操作するスタックマシンです。

hex opcode and operand stack
0 PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 {0xfff…e0}
21 CALLDATASIZE {len(msg.data)} {0xfff…e0}
22 ADD { len(msg.data)-0x20}
23 PUSH1 0x0 {0x0} { len(msg.data) - 0x20}
25 DUP2 {len(msg.data) - 0x20} {0x0} {len(msg.data)-0x20}
26 PUSH1 0x20 {0x20}{len(msg.data)-0x20} {0x0} {len(msg.data)-0x20}
28 DUP3 {0x0} {0x20}{len{msg.data)-0x20} {0x0} {len(msg.data)-0x20}
29 CALLDATACOPY {0x0} {len(msg.data)-0x20}
2a DUP1 {0x0}{0x0} {len(msg.data)-0x20}
2b CALLDATALOAD {msg.data[0:32]} {0x0} {len(msg.data)-0x20}
2c DUP3 {len(msg.data)-0x20} {msg.data[0:32]} {0x0} {len(msg.data)-0x20}
2d DUP3 {0x0} {len(msg.data)-0x20} {msg.data[0:32]} {0x0} {len(msg.data)-0x20}
2e CALLVALUE {msg.value} {0x0} {len(msg.data)-0x20} {msg.data[0:32]} {0x0} {len(msg.data)-0x20}
2f CREATE2 {newaddress} {0x0} {len(msg.data)-0x20}
30 DUP1 {newaddress } {newaddress} {datacopy} {0x0} {len(msg.data)-0x20}
31 ISZERO
32 ISZERO { 0x1 } {newaddress} {0x0} {len(msg.data)-0x20}
33 PUSH1 0x39 {0x39} { 0x1 } {newaddress} {0x0} {len(msg.data)-0x20}
35 JUMPI {newaddress} {datacopy} {0x0} {len(msg.data)-0x20}
36 DUP2
37 DUP3
38 REVERT
39 JUMPDEST
3a DUP1 {newaddress} {newaddress} {0x0} {len(msg.data)-0x20}
3b DUP3 {0x0} {newaddress} {newaddress} {0x0} {len(msg.data)-0x20}
3c MSTORE {newaddress} {0x0} {len(msg.data)-0x20}
3d POP {0x0} {len(msg.data)-0x20}
3e POP {len(msg.data)-0x20}
3f POP
40 PUSH1 0x14 {0x14}
42 PUSH1 0xc {0xc} {0x14}
44 RETURN ( mem[0xc: 0xc+0x14] ) なのでアドレスを返す。
  • 0x29 CALLDATACOPY の引数は{0x0} {0x20} {msg.len)-0x20} これは msg.dataの0x20番目からメモリの0x00番地にコピーしています。長さは実際のcalldataから0x20引いた値です。
  • 0x2f CREATE2の引数を見ると {msg.value} {0x0} {len(msg.data)-0x20} {msg.data[0:32]}で仕様はvalue,ofset,length,saltになっておりoffset からlengthは0x29 CALLDATACOPYでコピーしたデータです。
  • またsaltはcalldataの先頭から32バイトを使用しているため calldataを[ salt 32bytes] [ creation code ]となるように投げてあげるとよさそうです。
forge scriptでのデプロイコード
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Script} from "forge-std/Script.sol";
import {NFT} from "src/NFT.sol";
import {Factory} from "src/Factory.sol";
import "forge-std/console.sol";

contract DeterministicDeploy is Script {
    // CREATE2_FACTORY

    function run() public {
        bool success;
        bytes memory data;
        bytes32 salt=0x0102030400000000000000000000000000000000000000000000000000000000;
        vm.broadcast();
        bytes memory creationBytecode = type(NFT).creationCode;
        bytes memory initCode = abi.encodePacked(creationBytecode, abi.encode("NFT contract","NFT","http://mynft.org"));
        (success, data) = CREATE2_FACTORY.call(abi.encodePacked(salt,initCode));
        address newaddr = address(uint160(bytes20(data)));
        console.logAddress(newaddr);
        bytes memory bytecode = abi.encodePacked(bytes1(0xff),address(CREATE2_FACTORY),salt,keccak256(initCode)); 
        address newaddr2 = address(uint160(uint(keccak256(bytecode))));
        console.logAddress(newaddr2);
    }
}

結論は先頭に32bytesのsaltをつけてあげたcreationCodeをなげるとよいです。ちゃんとgithubにもThe data should be the 32 byte 'salt' followed by your init code.ってかいてありました。

改めてYUL を読む

EVMアセンブリ解析のあとにYULを読むと意味がわかってきました。

object "Proxy" {
	// deployment code
	code {
		let size := datasize("runtime")
		data
  copy(0, dataoffset("runtime"), size)
		return(0, size)
	}
	object "runtime" {
		// deployed code
		code {
			calldatacopy(0, 32, sub(calldatasize(), 32))
			let result := create2(callvalue(), 0, sub(calldatasize(), 32), calldataload(0))
			if iszero(result) { revert(0, 0) }
			mstore(0, result)
			return(12, 20)
		}
	}
}

まずこれはCreationCodeというものでインストールパッケージみたいなものです。
https://github.com/Arachnid/deterministic-deployment-proxy

  • はじめの object "Proxy"配下のcode { } は実行コードをメモリにコピーします。こちらの"runtime"となっている部分がデプロイされる実行コードです。
  • コンストラクタがあればこの code{}配下にはいります。今回はありません。
  • デプロイされるとobject "runtime"以下がEVMのオンチェーンに保存されます。deployedBytecodeとも言われます。
  • cast code <address> -dで表示されるコードは"rumtime"以下のdeployedBytecodeになります。

rumtime以下の実行コードは

  • calldatacopy(0, 32, sub(calldatasize(), 32)) で msg.dataの32バイトをスキップして最後までメモリに記入
  • create2の引数はcallvalue, コピーされたCreationCodeのメモリの位置、creationCodeの長さ、salt
  • saltはcalldataload(0)で最初の32バイトを持ってきている
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?