Source mirror · commit 369512f · 2026-10-26 · MIT · canonical repo: gitlab.com/jiapunk/upgrade-airlock
git clone https://upgrade-airlock.pages.dev/source/upgrade-airlock.git
upgrade-airlock / scripts / sepolia / execute.sh
scripts/sepolia/execute.sh92 lines · 4517 bytes
1#!/usr/bin/env bash
2# Execute a proposal once it has both a clearance and a finished cooldown (AirlockAdmin.execute is open to anyone;
3# the owner key pays).
4#
5# scripts/sepolia/execute.sh <id> # live: waits for the ready block (WAIT=0 to just print the ETA)
6# FORK=19631 scripts/sepolia/execute.sh <id> # fork: mines the remaining cooldown blocks first (anvil_mine)
7#
8# Nothing is sent when execute() would revert: a vetoed proposal (ProposalVetoed), one without a clearance
9# (NotCleared; the sweeper, the listener or the approver's clear.sh records one), or a target whose code is no longer
10# the code that was judged (CodehashChanged). The script then shows the revert with an eth_call and exits 1.
11source "$(dirname "$0")/lib.sh"
12ID=${1:-}
13[[ "$ID" =~ ^[0-9]+$ ]] || die "usage: [FORK=<port>] $0 <proposal id>"
14check_rpc
15GA=$(dep airlockAdmin)
16PROXY=$(dep proxy)
17OWNER=$(addr_of owner)
18read -r KIND ST TARGET READY _ PBLOCK < <(proposal "$ID") || die "could not read proposal #$ID from $RPC (try again, or set SEPOLIA_RPC)"
19
20# show_revert: what execute(id) does right now, as an eth_call from the owner (nothing is sent).
21show_revert() {
22 local out sel e
23 out=$(cast call "$GA" 'execute(uint256)' "$ID" --from "$OWNER" --rpc-url "$RPC" 2>&1 || true)
24 sel=$(grep -oE '0x[0-9a-fA-F]{8,}' <<<"$out" | tail -n1 | cut -c1-10)
25 for e in 'ProposalVetoed(uint256)' 'NotCleared(uint256)' 'TooEarly(uint256,uint64,uint64)' 'AlreadyExecuted(uint256)' 'UnknownProposal(uint256)' 'CodehashChanged(uint256,bytes32,bytes32)'; do
26 if [ -n "$sel" ] && [ "$sel" = "$(cast sig "$e")" ]; then
27 case "$e" in
28 TooEarly*) say " execute($ID) reverts TooEarly($ID, readyBlock $READY, block $(block_now))" ;;
29 CodehashChanged*) say " execute($ID) reverts CodehashChanged: the target's code is not the code that was proposed and judged" ;;
30 *) say " execute($ID) reverts ${e%%(*}($ID)" ;;
31 esac
32 return
33 fi
34 done
35 say " execute($ID): $(tail -n1 <<<"$out" | cut -c1-200)"
36}
37
38case "$ST" in
39 0) die "proposal #$ID does not exist" ;;
40 3) say "#$ID was already executed"; exit 0 ;;
41 2)
42 EV=$(events_since "$PBLOCK") # the verdict lands after the proposal: a recent window every RPC serves
43 RH=$(first_event vetoed "$ID" "$EV" | awk '{print $3}')
44 say "#$ID (${KIND_NAMES[$KIND]} $(impl_name "$TARGET")) is Vetoed${RH:+ with $(rule_name "$RH")}; nothing sent:"
45 show_revert
46 exit 1
47 ;;
48esac
49
50CL=$(clearance "$ID")
51NOW=$(block_now)
52if [ "$CL" = "$ZERO32" ]; then
53 say "#$ID (${KIND_NAMES[$KIND]} $(impl_name "$TARGET")) has no clearance yet (ready block $READY, now $NOW); nothing sent:"
54 show_revert
55 say " a verdict comes from the listener (airlock.sh) or the sweeper (sweep.sh); the approver can clear by hand (clear.sh)"
56 exit 1
57fi
58
59if [ "$NOW" -lt "$READY" ]; then
60 LEFT=$((READY - NOW))
61 if [ "$MODE" = fork ]; then
62 cast rpc anvil_mine "$(printf '0x%x' "$LEFT")" --rpc-url "$RPC" >/dev/null
63 say "fork: mined $LEFT blocks to reach the ready block $READY"
64 elif [ "${WAIT:-1}" = 1 ]; then
65 say "#$ID is cleared and ready at block $READY, now $NOW: waiting ~$(fmt_secs $((LEFT * SEPOLIA_BLOCK_SECS)))"
66 while [ "$(block_now)" -lt "$READY" ]; do sleep 6; done
67 else
68 say "#$ID is cleared and ready at block $READY, now $NOW (~$(fmt_secs $((LEFT * SEPOLIA_BLOCK_SECS))) at 12 s blocks)"
69 exit 1
70 fi
71fi
72
73# a last dry run: a guardian's veto in the meantime, or a target whose code changed (CodehashChanged), sends nothing
74if ! cast call "$GA" 'execute(uint256)' "$ID" --from "$OWNER" --rpc-url "$RPC" >/dev/null 2>&1; then
75 say "#$ID (${KIND_NAMES[$KIND]} $(impl_name "$TARGET")) would revert now; nothing sent:"
76 show_revert
77 exit 1
78fi
79BEFORE=$(cast parse-bytes32-address "$(cast storage "$PROXY" "$IMPL_SLOT" --rpc-url "$RPC")")
80RCPT=$(send_as owner "$GA" 'execute(uint256)' "$ID")
81TX=$(jq -r .transactionHash <<<"$RCPT")
82[ "$(jq -r .status <<<"$RCPT")" = 0x1 ] || die "execute failed: $(tx_ref "$TX")"
83say "EXECUTED #$ID (${KIND_NAMES[$KIND]} $(impl_name "$TARGET")) in block $(cast to-dec "$(jq -r .blockNumber <<<"$RCPT")")"
84say " clearance evidence $CL"
85say " tx $(tx_ref "$TX")"
86if [ "$KIND" = 0 ]; then
87 AFTER=$(cast parse-bytes32-address "$(cast storage "$PROXY" "$IMPL_SLOT" --rpc-url "$RPC")")
88 say " proxy implementation $(impl_name "$BEFORE") -> $(impl_name "$AFTER")"
89 V=$(cast call "$PROXY" 'version()(string)' --rpc-url "$RPC" 2>/dev/null || true)
90 [ -n "$V" ] && say " token version() = $V"
91fi
92exit 0