3MIKAN
仮想通貨直コン

CREATE2入門:salt・init codeからcontract addressを予測して安全にdeployする

CREATE2 addressをfactory・salt・init codeから計算し、constructor、library、metadata、proxy、clone、collision、counterfactual deploymentの境界を検証します。

3MIKANのブランドキャラクターが建設前に決まるcontract区画をfactory、salt、init codeの設計図と照合する記事画像

CREATE2を使うと、contractをdeployする前にaddressを計算できます。ただし、「saltが同じなら同じaddress」「runtime bytecodeだけあれば計算できる」「codeがないaddressは予約済み」という理解は正しくありません。

addressを決めるのは、CREATE2を実行するfactory address、32-byte salt、init codeのhashです。init codeにはcreation bytecodeだけでなくconstructor argumentsも入り、linked library、compiler metadata、proxy initializerの設計によっても変わります。

この記事はdeterministic deployment factory、smart account、proxy / clone factoryを実装する開発者向けです。chain → factory → effective salt → creation bytecode → constructor / initializer → init-code hash → predicted address → code・nonce・balance → deployment receiptの順に固定し、予測と安全性を分けます。

検証にはSolidity 0.8.36、Foundry / Anvil 1.8.0、OpenZeppelin Contracts 5.6.1、viem 2.56.0を使いました。chain ID 31345の隔離したlocal chainだけで実行し、public RPC、mainnet fork、browser wallet、実資産は使っていません。

CREATEとCREATE2はaddressの入力が違う

通常のCREATEは、作成者addressとnonceから次のaddressを導きます。factoryが先に別contractを作るとnonceが進むため、予定addressも変わります。

CREATE  = keccak256(rlp([creator, nonce]))[12:]

CREATE2はnonceの代わりに、次の固定長preimageを使います。

CREATE2 = keccak256(
  0xff ++ factory ++ salt ++ keccak256(init_code)
)[12:]

EIP-1014では、0xffが1 byte、factory addressが20 bytes、saltが32 bytes、init-code hashが32 bytesです。最終hashへ渡すpreimageは合計85 bytesになります。

ここでいうfactoryは、EOAのtransaction senderではありません。CREATE2 opcodeを実行するcontractのaddressです。同じEOA、salt、init codeでも、別factoryからdeployすれば別addressになります。

factory、salt、init codeの3つの部品を照合し、建設前のcontract区画へ結び付ける概念図
予定addressへ入る3要素を別々に固定します。正確な式とhashは本文を正本にします。

init codeはruntime bytecodeではない

EIP-1014のinit_codeは、実行されてruntime bytecodeを返す一時的なcodeです。Solidityでconstructor付きcontractを作る場合は、概念上次の組み合わせになります。

bytes memory initCode = abi.encodePacked(
    type(Target).creationCode,
    abi.encode(initialValue, recipient)
);
bytes32 initCodeHash = keccak256(initCode);

type(Target).runtimeCodeや、deploy後にeth_getCodeで読んだcodeを代入してはいけません。constructorはstorageを初期化し、最後に別のruntime codeを返します。

local検証では、同じfactoryとsaltでconstructorのinitialValueだけを77から78へ変えました。

constructor value init-code hash predicted address runtime-code hash
77 0x1f91…0d68 0x282A…7648 0xaa3d…fddf
78 0xb8b5…caa7 0xe1F0…3402 0xaa3d…fddf

2つのruntime-code hashは同じですが、constructor dataを含むinit-code hashは異なり、addressも異なりました。同じruntime codeになることは、同じCREATE2 addressになる条件ではありません。

Solidity手計算とviemを独立して一致させる

factory側の計算は、次のように式をそのまま実装できます。

function computeAddress(bytes32 salt, bytes32 initCodeHash)
    public
    view
    returns (address)
{
    return address(uint160(uint256(keccak256(abi.encodePacked(
        bytes1(0xff),
        address(this),
        salt,
        initCodeHash
    )))));
}

TypeScript側では、creation bytecodeとABI encoded constructor argumentsを連結してからviemへ渡します。

const initCode = concatHex([
  creationBytecode,
  encodeAbiParameters(
    parseAbiParameters('uint256, address'),
    [77n, recipient],
  ),
])

const predicted = getContractAddress({
  from: factory,
  opcode: 'CREATE2',
  salt,
  bytecode: initCode,
})

固定したローカル検証では、Solidity factoryのview関数、85-byte preimageを組み立てるTypeScript手計算、viem getContractAddressの3経路がすべて0x282AE3Ef80fB8F5F68D934dA24201EeD1bf67648で一致しました。

1つのhelperだけを予測と検証の両方に使うと、同じencoding bugを共有して一致してしまいます。可能ならSolidityとoff-chain codeを独立実装し、init codeそのものとhashも比較します。

libraryとcompiler metadataもdeployment inputに含める

Solidityのexternal library callを含むbytecodeにはlink placeholderがあり、compile時に実library addressが埋め込まれます。したがって、library addressが違えばcreation bytecode、init-code hash、CREATE2 addressも変わります。

同じsourceを2つのlibrary addressでlinkした固定結果です。

linked library bytecode init-code hash predicted address
0x11…11 301 bytes 0xf670…34d1 0xE655…fca4
0x22…22 301 bytes 0x924a…c941 0x9c5F…2418

Solidity metadataも見落としやすい入力です。metadataにはcompiler version、settings、source hash、library情報などが入り、変更によってbytecodeが変わり得ます。同じsourceとSolidity 0.8.36で、metadata settingだけを変えた結果です。

metadata setting creation bytecode init-code hash predicted address
CBORなし 259 bytes 0xb601…d38b 0x1116…6ac8
IPFS metadata 313 bytes 0xe731…0b18 0x8754…21aF

sourceが同じという説明だけでは、再現可能な構築になりません。solc version、optimizer、runs、via-IR、EVM version、metadata、remapping、library addressをmanifestへ残します。bytecode再構築の詳しい手順は、runtime bytecodeを同じcompiler条件で再構築する記事で確認できます。

saltは値ではなく、導出規則まで固定する

bytes32 saltに何を入れるかはfactoryの設計です。permissionless factoryがuser入力をそのまま使い、init codeが全callerで同じなら、別callerが先に同じaddressへdeployできます。

local例では、ownerをconstructorへbindせず、deploy後のinitializeで決めるcontractを使いました。最初のcallerがraw saltでdeployとinitializeを行うと、次のcallerは同じaddressとのcollisionでdeployできません。

順番 caller result
1 0x7099…79C8 0x9384…83A4へdeployしownerになる
2 0xf39F…2266 同じaddressとのcollisionでzero address

ただし、これをすべて「salt front-runningでownershipが盗まれる」と一般化してはいけません。ownerや設定値がconstructor argumentsへ入っていれば、攻撃者が別ownerでdeployしたinit codeはhashが違うため別addressになります。攻撃者が被害者と同じinit codeを先に使える場合も、constructorが被害者ownerを固定していれば主な結果は占有によるDoSです。

対策の1つは、user saltをcaller namespaceへ入れる方法です。

bytes32 effectiveSalt = keccak256(abi.encode(msg.sender, userSalt));

固定例では、同じuser saltでも2つのcallerの予測addressは0xcb5f…75010x3437…78b8へ分かれました。ただし、namespacingだけでfactoryが安全になるわけではありません。誰がdeployできるか、init codeを誰が選べるか、ownerがどこでbindされるか、initializeが同じtransactionで完了するかも確認します。

CREATEとCREATE2の違いを調査の順番へ落とす

作成順のnonceで区画が進むCREATEと、factory、salt、設計図の組み合わせで予定区画を選ぶCREATE2を並べた概念図
CREATEは作成順、CREATE2は固定preimageを追います。どちらもdeploy成功やcode安全性までは保証しません。

予測が合わないときは、表示されたaddressを何度も見比べるより、入力を左から固定します。

確認順 残す値 典型的なずれ
1 chain ID、block、RPC 別networkを見ている
2 factory address、code hash、owner / authority 別factory、proxy先、権限変更
3 raw user salt、effective salt、導出式 padding、caller namespace、文字列hash
4 creation bytecode runtime bytecodeを使用、別artifact
5 constructor ABI data 型、順序、address、bytes encoding
6 solc / optimizer / EVM / metadata 構築環境差
7 library addresses、implementation link先やproxy先の差
8 init-code hash、expected address off-chain / on-chain計算の不一致

chain ID自体はEIP-1014の式へ直接入りません。それでもchainを最初に残すのは、factoryがそのchainに同じaddress・同じcode・同じauthorityで存在するとは限らないためです。

collision、revert、wrong factoryを別failureにする

predicted addressが正しくても、deploymentは失敗します。ローカル検証では次を別々に再現しました。

failure transaction後の観測 原因
existing code collision CREATE2 returnはzero address 同一factory / salt / init codeが既にdeploy済み
constructor revert returnはzero、code 0 bytes、nonce 0 init code実行中にrevert
wrong factory 予測0x282A…7648、実際のfactory側0x838E…972b opcode実行addressが異なる
wrong constructor args 別init-code hash、別address ABI dataが異なる
wrong library / metadata 別creation bytecode、別address compiler inputが異なる

EIP-1014は、destinationのnonceがnonzeroまたはcodeが空でない場合にcreationが失敗するcollision ruleを説明しています。同じsaltでもinit codeが違えば通常は別addressです。「saltを一度使ったらfactory全体で永久に使用禁止」という規則ではありません。

constructor revertもcollisionと同じzero addressへ見える低level factoryがあります。factoryはzero addressを必ずrejectし、可能ならrevert reason、receipt status、trace、post-stateを残します。

SELFDESTRUCT後の再deployはforkとtransaction境界が追加で必要です。EIP-6780後のcode残存とCREATE2再deployでは、Shanghai / Cancunを別local ruleとして比較しています。

proxy initializerはどこで実行するかでaddressへの影響が変わる

ERC1967Proxyは、constructorへimplementation addressとinitializer dataを渡し、そのdataをimplementationへdelegatecallできます。この形ではinitializer dataがproxyのconstructor argumentsに含まれるため、init codeの一部です。

固定例では、同じimplementation、factory、saltでも、initializerのvalueを77から78へ変えると予測addressが0x42A5…772cから0x6cDC…8C61へ変わりました。

一方、空のinitializerでproxyをdeployし、後transactionでinitializeする設計なら、後から渡すownerやvalueはCREATE2 preimageへ入りません。しかし未初期化の時間を作ると、第三者にinitializeされる危険があります。initializerをaddressへ含めるかどうかだけでなく、deployとinitializeをatomicに完了するかをreviewします。

proxyのimplementation、admin / upgrade authority、storage slotの読み分けは、ERC-1967 proxyの確認手順も参照してください。

minimal cloneはimplementationがinit codeへ入り、initializerは後段になる

OpenZeppelin Clones.cloneDeterministicは、ERC-1167 minimal proxyのinit codeへimplementation addressを埋め込み、factoryとsaltからaddressを予測します。固定例では次を一致させました。

field fixed value
implementation 0x9fE4…a6e0
effective salt 0xae1a…8d01
clone init-code hash 0x4798…e0ba
predicted / deployed 0xd563…33Db
atomic initialized owner / value 0xf39F…2266 / 99

通常のminimal cloneではinitializer calldataはclone init codeの外にあります。つまりinitializer valueを変えてもclone address計算へ直接入らない設計があります。そのためfactoryがcloneを作った直後、同じtransactionでinitializeすることが重要です。

OpenZeppelinは、implementationにcodeがないcloneではinitialize callが成功したように見えても効果がなく、未初期化のまま残り得ると警告しています。予測前とdeploy前にimplementation code hashを確認します。

counterfactual addressは「存在前に使える」が「予約済み」ではない

EIP-1014の目的には、まだon-chainに存在しないaddressとのcounterfactual interactionがあります。smart accountではfactory、owner、saltから予定addressを計算し、署名やUserOperationを先に組み立てることがあります。

local chainで予定accountを3段階に分けました。

phase code nonce balance
予測直後 0 bytes 0 0
local test ETHを事前送付 0 bytes 0 0.25 ETH
CREATE2 deploy後 108 bytes 1 0.25 ETH

codeもnonceも0のaddressへnative balanceを置けること、後で同じaddressへdeployするとbalanceが残ることを確認しました。しかし、これは実資産の事前送付を勧める結果ではありません。

予測一致だけでは、factoryが将来もdeploy可能か、ownerが正しいか、init codeが監査済みか、constructorがrevertしないか、資産を取り出せるcodeになるかは分かりません。counterfactual signatureのfactory calldataとrollback検証は、ERC-6492の未deploy account署名検証で扱っています。

deployment manifestに固定する項目

addressだけを共有するのではなく、再計算できる入力と実行結果を1件にまとめます。

group fields
network chain ID、genesis / fork、RPCの用途、block
factory identity address、runtime code hash、implementation、owner / deploy authority
salt raw value、型、encoding、namespace、effective bytes32
compiler solc version、optimizer、runs、via-IR、EVM version、metadata setting
link fully qualified library name、library address、remapping
init code creation bytecode hash、constructor types / values、initializer data hash
prediction expected address、Solidity result、off-chain result
pre-state eth_getCode、nonce、balance
deployment transaction hash、receipt status、created address、event
post-state code hash、nonce、balance、owner / initialized state

manifestにsecretを入れてはいけません。private key、seed、API secret、個人情報は対象外です。予測addressへ権限や資産を結び付ける前に、別担当者が同じmanifestから再計算できる状態にします。

review checklist

  1. CREATE2を実行するfactory addressとruntime code hashを確認したか。
  2. user saltとeffective saltを分け、encodingとcaller namespaceを固定したか。
  3. runtime bytecodeではなく、constructor data付きcreation bytecodeをhashしたか。
  4. compiler、optimizer、EVM、metadata、library addressを固定したか。
  5. proxy initializerがconstructor内かdeploy後かを確認したか。
  6. clone / accountのinitializeを同じtransactionで完了するか。
  7. predicted addressのcode、nonce、balanceをdeploy直前に読み直したか。
  8. collision、constructor revert、wrong factoryを別errorとして扱うか。
  9. factoryのdeploy権限、upgrade権限、pause条件を確認したか。
  10. address予測をcontract安全性、deploy成功、資産回収の保証として表示していないか。

まとめ

CREATE2 addressは、saltだけでは決まりません。factory address、effective salt、constructorやinitializerを含むinit code、そのcompiler / library条件を固定して初めて再計算できます。

予測が一致したら、次はcode・nonce・balance、factory権限、collision、constructor result、initializeのatomicityを確認します。deterministicなのはaddress導出であり、deploymentの成功やcontractの安全性ではありません。

確認した一次情報