diff --git a/playbooks/gloas-dev/deploy-eip8282-contracts.yaml b/playbooks/gloas-dev/deploy-eip8282-contracts.yaml new file mode 100644 index 00000000..fdf4e877 --- /dev/null +++ b/playbooks/gloas-dev/deploy-eip8282-contracts.yaml @@ -0,0 +1,178 @@ +id: deploy-eip8282-contracts +name: "GLOAS: Deploy EIP-8282 builder request contracts (pre-fork)" +description: | + Deploys the EIP-8282 builder deposit (request type 0x03) and builder exit + (request type 0x04) system contracts onto a running devnet BEFORE the GLOAS + fork activates. + + glamsterdam-devnet-7 deliberately omits these predeploys from genesis + (DEPLOY_EIP8282_CONTRACTS=false in the genesis generator) to exercise the + mainnet-like deployment workflow: from GLOAS activation onward every block + is INVALID unless both contracts exist, so they must be deployed as regular + transactions pre-fork. Intended to be registered as a startup test so the + contracts land right after the devnet comes up. + + Deployment goes through the deterministic CREATE2 factory (predeployed in + devnet genesis as a well-known contract) using the vanity-mined salts from + ethereum/execution-specs#3091 — CREATE2(factory, salt, initcode) yields the + canonical ...8282 addresses the clients expect. The factory REVERTS when + CREATE2 fails (including the already-deployed collision case), so a + successful receipt is proof of deployment. + + Idempotent: an eth_call precheck against the builder deposit contract's fee + getter detects existing code and skips deployment, so restarts of a + startup-scheduled run stay green. The exit contract cannot be prechecked the + same way — its constructor arms the slot-0 excess inhibitor, which makes its + fee getter revert by design until the first post-GLOAS system call — so its + deployment is asserted via the deploy receipt (both contracts only ever + deploy as a pair). +version: 1.0.0 +tags: [gloas, builder, eip-8282, deployment, prefork] +timeout: 30m +schedule: + startup: true +config: + walletPrivkey: '' + # Deterministic CREATE2 deployment factory (predeployed in devnet genesis). + create2Factory: '0x4e59b44847b379578588920cA78FbF26c0B4956C' + # EIP-8282 predeploy addresses (canonical, from the EIP / execution-specs#3091). + builderDepositContract: '0x0000bFF46984e3725691FA540a8C7589300D8282' + builderExitContract: '0x000064D678505ad48F8cCb093BC65613800E8282' + # Factory calldata: 32-byte vanity-mined salt ++ initcode, verbatim from + # ethereum/execution-specs#3091 builder_deposit_factory_deploy.json. + # The initcode's runtime is byte-identical to the egg v6.1.3 genesis + # predeploy (source: ethereum/sys-asm src/builder_deposits). + depositDeployData: '0x1f4f2c41c28e816e259b621c58b94b37309c8dec42c8f6e400001a46c9d96bf761027480600a5f395ff33373fffffffffffffffffffffffffffffffffffffffe1461011c575f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146102705760015460088111605257506058565b60089003015b601190600182026001905f5b5f821115607f57810190830284830290049160010191906064565b90939004925050503660b814609f57366102705734610270575f5260205ff35b8034106102705760383567ffffffffffffffff1680633b9aca001161027057633b9aca00029034031061027057600154600101600155600354806006026004015f358155600101602035815560010160403581556001016060358155600101608035815560010160a035905560b85f5f3760b85fa0600101600355005b60035460025480820380604011610131575060405b5f5b8181146101d7578281016006026004018160b8028154815260200181600101548152602001816002015480825260401c67ffffffffffffffff16816010018160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c816001015353602001816003015481526020018160040154815260200190600501549052600101610133565b91018092146101e957906002556101f4565b90505f6002555f6003555b36610242575f54600154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461023057600882820111610238575b50505f610264565b0160089003610264565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5f555f60015560b8025ff35b5f5ffd' + # Same, from builder_exit_factory_deploy.json (source: ethereum/sys-asm + # src/builder_exits). The constructor prefix (PUSH32 2^256-1, PUSH0, SSTORE) + # arms the slot-0 excess inhibitor at deployment. + exitDeployData: '0x89abb1878437213f971f849327423ed1d9c5cdb03970cd8f0000318b3ff101197fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5f556101ca80602d5f395ff33373fffffffffffffffffffffffffffffffffffffffe1460e1575f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101c65760015460028111605157506057565b60029003015b601190600182026001905f5b5f821115607e57810190830284830290049160010191906063565b909390049250505036603014609e57366101c657346101c6575f5260205ff35b34106101c657600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160f5575060105b5f5b81811461012d5782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160f7565b910180921461013f579060025561014a565b90505f6002555f6003555b36610198575f54600154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101865760028282011161018e575b50505f6101ba565b01600290036101ba565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5f555f6001556044025ff35b5f5ffd' + +tasks: +- name: check_clients_are_healthy + title: "Check if at least one client is ready" + timeout: 5m + config: + minClientCount: 1 + +# The deposit contract has no inhibitor: with code present, an empty-calldata +# eth_call hits the fee getter and returns a 32-byte fee; without code it +# returns 0x. (Never reverts, unlike the exit contract's armed fee getter.) +- name: check_eth_call + id: predeploy_check + title: "Probe builder deposit contract for existing code" + timeout: 2m + configVars: + callAddress: builderDepositContract + +- name: get_consensus_specs + id: get_specs + title: "Get consensus chain specs" + +- name: check_consensus_slot_range + id: current_slot + title: "Get current slot" + +# Deploying after GLOAS activation is futile: without the contracts every +# block since activation is invalid, so the deploy transactions can never be +# included. Fail fast with a clear message instead of a receipt timeout. +- name: run_shell + id: fork_guard + title: "Assert GLOAS has not activated yet" + if: "tasks.predeploy_check.outputs.callResult == \"0x\"" + config: + envVars: + GLOAS_FORK_EPOCH: "tasks.get_specs.outputs.specs.GLOAS_FORK_EPOCH" + SLOTS_PER_EPOCH: "tasks.get_specs.outputs.specs.SLOTS_PER_EPOCH" + CURRENT_SLOT: "tasks.current_slot.outputs.currentSlot" + command: | + set -e + GLOAS_FORK_EPOCH=$(echo "$GLOAS_FORK_EPOCH" | jq -r .) + SLOTS_PER_EPOCH=$(echo "$SLOTS_PER_EPOCH" | jq -r .) + CURRENT_SLOT=$(echo "$CURRENT_SLOT" | jq -r .) + + ACTIVATION_SLOT=$((GLOAS_FORK_EPOCH * SLOTS_PER_EPOCH)) + if [ "$CURRENT_SLOT" -ge "$ACTIVATION_SLOT" ]; then + echo "ERROR: GLOAS is active (slot $CURRENT_SLOT >= $ACTIVATION_SLOT)" + echo "and the EIP-8282 contracts are NOT deployed. Every block since" + echo "activation is invalid without them; the deploy transactions can" + echo "no longer be included. The network must be relaunched (or the" + echo "contracts baked into genesis via DEPLOY_EIP8282_CONTRACTS=true)." + exit 1 + fi + echo "GLOAS activates at slot $ACTIVATION_SLOT (epoch $GLOAS_FORK_EPOCH), current slot $CURRENT_SLOT — proceeding." + +- name: generate_child_wallet + id: deployer_wallet + title: "Generate funded wallet for the deployment transactions" + if: "tasks.predeploy_check.outputs.callResult == \"0x\"" + config: + walletSeed: eip8282-deployer + prefundMinBalance: 1000000000000000000 # 1 ETH (two ~0.4M gas txs) + configVars: + privateKey: walletPrivkey + +- name: generate_transaction + id: deploy_deposit_contract + title: "Deploy builder deposit contract (request type 0x03)" + if: "tasks.predeploy_check.outputs.callResult == \"0x\"" + config: + gasLimit: 2000000 + awaitReceipt: true + failOnReject: true + configVars: + privateKey: tasks.deployer_wallet.outputs.childWallet.privkey + targetAddress: create2Factory + callData: depositDeployData + +- name: generate_transaction + id: deploy_exit_contract + title: "Deploy builder exit contract (request type 0x04)" + if: "tasks.predeploy_check.outputs.callResult == \"0x\"" + config: + gasLimit: 2000000 + awaitReceipt: true + failOnReject: true + configVars: + privateKey: tasks.deployer_wallet.outputs.childWallet.privkey + targetAddress: create2Factory + callData: exitDeployData + +# Re-probe the deposit contract: it must now answer the fee getter. The exit +# contract intentionally reverts on eth_call while the inhibitor is armed, so +# its deployment is proven by the deploy_exit_contract receipt above. +- name: check_eth_call + id: postdeploy_check + title: "Verify builder deposit contract answers its fee getter" + timeout: 2m + configVars: + callAddress: builderDepositContract + +- name: run_shell + id: verify_summary + title: "Assert deployment state and summarize" + config: + envVars: + PRE: "tasks.predeploy_check.outputs.callResult" + POST: "tasks.postdeploy_check.outputs.callResult" + DEPOSIT_ADDR: "builderDepositContract" + EXIT_ADDR: "builderExitContract" + command: | + set -e + PRE=$(echo "$PRE" | jq -r .) + POST=$(echo "$POST" | jq -r .) + DEPOSIT_ADDR=$(echo "$DEPOSIT_ADDR" | jq -r .) + EXIT_ADDR=$(echo "$EXIT_ADDR" | jq -r .) + + if [ "$POST" = "0x" ]; then + echo "ERROR: builder deposit contract at $DEPOSIT_ADDR still has no code." + exit 1 + fi + + if [ "$PRE" = "0x" ]; then + echo "Deployed EIP-8282 contracts via CREATE2 factory:" + else + echo "EIP-8282 contracts were already deployed:" + fi + echo " builder deposit: $DEPOSIT_ADDR (fee getter returned $POST)" + echo " builder exit: $EXIT_ADDR (fee getter reverts until the first post-GLOAS system call clears the inhibitor — deployment proven by receipt)"