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. |
| 11 | source "$(dirname "$0")/lib.sh" |
| 12 | ID=${1:-} |
| 13 | [[ "$ID" =~ ^[0-9]+$ ]] || die "usage: [FORK=<port>] $0 <proposal id>" |
| 14 | check_rpc |
| 15 | GA=$(dep airlockAdmin) |
| 16 | PROXY=$(dep proxy) |
| 17 | OWNER=$(addr_of owner) |
| 18 | read -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). |
| 21 | show_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 | |
| 38 | case "$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 | ;; |
| 48 | esac |
| 49 | |
| 50 | CL=$(clearance "$ID") |
| 51 | NOW=$(block_now) |
| 52 | if [ "$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 |
| 57 | fi |
| 58 | |
| 59 | if [ "$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 |
| 71 | fi |
| 72 | |
| 73 | # a last dry run: a guardian's veto in the meantime, or a target whose code changed (CodehashChanged), sends nothing |
| 74 | if ! 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 |
| 78 | fi |
| 79 | BEFORE=$(cast parse-bytes32-address "$(cast storage "$PROXY" "$IMPL_SLOT" --rpc-url "$RPC")") |
| 80 | RCPT=$(send_as owner "$GA" 'execute(uint256)' "$ID") |
| 81 | TX=$(jq -r .transactionHash <<<"$RCPT") |
| 82 | [ "$(jq -r .status <<<"$RCPT")" = 0x1 ] || die "execute failed: $(tx_ref "$TX")" |
| 83 | say "EXECUTED #$ID (${KIND_NAMES[$KIND]} $(impl_name "$TARGET")) in block $(cast to-dec "$(jq -r .blockNumber <<<"$RCPT")")" |
| 84 | say " clearance evidence $CL" |
| 85 | say " tx $(tx_ref "$TX")" |
| 86 | if [ "$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" |
| 91 | fi |
| 92 | exit 0 |