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バイトを持ってきている