Skip to content

Commit f3310b1

Browse files
authored
improve (#1321)
Signed-off-by: Ihor Farion <ihor@umaproject.org>
1 parent 8638b84 commit f3310b1

File tree

1 file changed

+148
-11
lines changed

1 file changed

+148
-11
lines changed

scripts/verifyBytecode.sh

Lines changed: 148 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/bin/bash
2+
set -euo pipefail
23

34
# This script verifies the bytecode of a contract onchain matches the bytecode in the artifact
45
# It takes the following arguments:
@@ -8,30 +9,166 @@
89

910
# Note that this script doesn't take into account any link libraries that are used in the contract
1011

11-
args=("$@")
12+
# Example commands:
13+
# ./scripts/verifyBytecode.sh 0x2015905f5cbdb4afeba30deb6dc0f0f779ba4af5bc43edceabf0bf4343cb290b "$NODE_URL_1" SponsoredCCTPSrcPeriphery script/mintburn/cctp/DeploySponsoredCCTPSrcPeriphery.s.sol
14+
# ./scripts/verifyBytecode.sh --broadcast "$NODE_URL_1" SponsoredCCTPSrcPeriphery script/mintburn/cctp/DeploySponsoredCCTPSrcPeriphery.s.sol
1215

13-
if [ ${#args[@]} -ne 3 ]; then
14-
echo "Usage: $0 <tx_hash> <rpc_url> <contract_name>"
16+
strip_cbor_metadata() {
17+
local hex="$1"
18+
local n=${#hex}
19+
if (( n < 4 )); then
20+
echo "$hex"
21+
return
22+
fi
23+
24+
local len_hex="${hex:$((n - 4)):4}"
25+
[[ "$len_hex" =~ ^[0-9a-fA-F]{4}$ ]] || { echo "$hex"; return; }
26+
27+
local metadata_bytes=$((16#$len_hex))
28+
local remove_nibbles=$((metadata_bytes * 2 + 4))
29+
if (( remove_nibbles >= n )); then
30+
echo "$hex"
31+
return
32+
fi
33+
34+
echo "${hex:0:$((n - remove_nibbles))}"
35+
}
36+
37+
if [[ "$1" != "--broadcast" && $# -ne 4 ]] || [[ "$1" == "--broadcast" && $# -ne 4 ]]; then
38+
echo "Usage:"
39+
echo " $0 <tx_hash> <rpc_url> <contract_name> <script_path|run_json_path>"
40+
echo " $0 --broadcast <rpc_url> <contract_name> <script_path|run_json_path>"
1541
exit 1
1642
fi
1743

18-
TX=${args[0]}
19-
RPC=${args[1]}
20-
CONTRACT_NAME=${args[2]}
44+
if [[ "$1" == "--broadcast" ]]; then
45+
RPC="$2"
46+
CONTRACT_NAME="$3"
47+
BROADCAST_REF="$4"
48+
if [[ "$BROADCAST_REF" == *.json ]]; then
49+
RUN_JSON="$BROADCAST_REF"
50+
else
51+
CHAIN_ID=$(cast chain-id --rpc-url "$RPC")
52+
RUN_JSON="broadcast/$(basename "$BROADCAST_REF")/$CHAIN_ID/run-latest.json"
53+
[[ -f "$RUN_JSON" ]] || RUN_JSON="broadcast/$(basename "$BROADCAST_REF")/$CHAIN_ID/dry-run/run-latest.json"
54+
fi
55+
[[ -f "$RUN_JSON" ]] || { echo "run json not found: $RUN_JSON"; exit 1; }
56+
57+
TX_ENTRY=$(jq -c --arg name "$CONTRACT_NAME" '[.transactions[] | select(.transactionType=="CREATE" and .contractName==$name)][-1]' "$RUN_JSON")
58+
[[ "$TX_ENTRY" != "null" ]] || { echo "no CREATE tx found for $CONTRACT_NAME in $RUN_JSON"; exit 1; }
59+
TX=$(jq -r '.hash' <<< "$TX_ENTRY")
60+
else
61+
TX="$1"
62+
RPC="$2"
63+
CONTRACT_NAME="$3"
64+
BROADCAST_REF="$4"
65+
if [[ "$BROADCAST_REF" == *.json ]]; then
66+
RUN_JSON="$BROADCAST_REF"
67+
else
68+
CHAIN_ID=$(cast chain-id --rpc-url "$RPC")
69+
RUN_JSON="broadcast/$(basename "$BROADCAST_REF")/$CHAIN_ID/run-latest.json"
70+
[[ -f "$RUN_JSON" ]] || RUN_JSON="broadcast/$(basename "$BROADCAST_REF")/$CHAIN_ID/dry-run/run-latest.json"
71+
fi
72+
[[ -f "$RUN_JSON" ]] || { echo "run json not found: $RUN_JSON"; exit 1; }
73+
74+
TX_ENTRY=$(jq -c --arg tx "$TX" --arg name "$CONTRACT_NAME" '[.transactions[]? | select((((.hash // "") | tostring | ascii_downcase) == ($tx | ascii_downcase)) and .transactionType=="CREATE" and .contractName==$name)][0]' "$RUN_JSON")
75+
[[ "$TX_ENTRY" != "null" ]] || {
76+
echo "tx $TX for CREATE $CONTRACT_NAME not found in $RUN_JSON"
77+
echo "Hint: run-latest.json may have an incorrect/stale CREATE tx hash. You can try to FIX MANUALLY."
78+
echo "Hint: pass the exact broadcast run JSON that contains this tx hash."
79+
exit 1
80+
}
81+
fi
2182

22-
ONCHAIN=$(cast tx $TX --rpc-url $RPC --json | jq -r '.input' | sed 's/^0x//')
83+
ONCHAIN=$(cast tx "$TX" --rpc-url "$RPC" --json | jq -r '.input' | sed 's/^0x//')
2384

2485
ART=out/$CONTRACT_NAME.sol/$CONTRACT_NAME.json
86+
[[ -f "$ART" ]] || { echo "artifact not found: $ART"; exit 1; }
2587

2688
CREATION=$(jq -r '.bytecode.object' "$ART" | sed 's/^0x//')
2789

28-
CODE_ONCHAIN=${ONCHAIN:0:${#CREATION}}
90+
LOCAL_INIT="$CREATION"
2991

30-
cast keccak $CODE_ONCHAIN
31-
cast keccak $CREATION
92+
if [[ -n "${TX_ENTRY:-}" && "$TX_ENTRY" != "null" ]]; then
93+
CONSTRUCTOR_TYPES=$(jq -r '([.abi[]? | select(.type=="constructor")][0].inputs // []) | map(.type) | join(",")' "$ART")
94+
CONSTRUCTOR_SIG="constructor($CONSTRUCTOR_TYPES)"
95+
CONSTRUCTOR_ARGS=()
96+
while IFS= read -r arg; do
97+
CONSTRUCTOR_ARGS+=("$arg")
98+
done < <(jq -cr '.arguments[]?' <<< "$TX_ENTRY")
99+
ENCODED_ARGS=$(cast abi-encode "$CONSTRUCTOR_SIG" "${CONSTRUCTOR_ARGS[@]}" | sed 's/^0x//')
100+
LOCAL_INIT="${CREATION}${ENCODED_ARGS}"
101+
[[ -n "${RUN_JSON:-}" ]] && echo "Using constructor args from: $RUN_JSON"
102+
fi
103+
104+
echo -n "0x$ONCHAIN" | cast keccak
105+
echo -n "0x$LOCAL_INIT" | cast keccak
32106

33-
if [[ $CODE_ONCHAIN == $CREATION ]]; then
107+
if [[ "$ONCHAIN" == "$LOCAL_INIT" ]]; then
34108
echo "✅ Code match"
109+
elif [[ -n "${ENCODED_ARGS:-}" ]]; then
110+
ONCHAIN_CREATION="${ONCHAIN:0:${#CREATION}}"
111+
ONCHAIN_ARGS="${ONCHAIN:${#CREATION}:${#ENCODED_ARGS}}"
112+
STRIPPED_ONCHAIN_CREATION=$(strip_cbor_metadata "$ONCHAIN_CREATION")
113+
STRIPPED_LOCAL_CREATION=$(strip_cbor_metadata "$CREATION")
114+
if [[ "$ONCHAIN_ARGS" == "$ENCODED_ARGS" && "$STRIPPED_ONCHAIN_CREATION" == "$STRIPPED_LOCAL_CREATION" ]]; then
115+
echo "✅ Code match (metadata hash differs)"
116+
exit 0
117+
fi
118+
echo "❌ Code mismatch"
119+
echo "Onchain bytes : ${#ONCHAIN}"
120+
echo "Local bytes : ${#LOCAL_INIT}"
121+
echo "Hint: compare verified compiler settings (solc/optimizer runs/via-ir/evmVersion) against your local build."
122+
echo "Hint: metadata-only drift is common; if constructor args match, check explorer verification details."
123+
124+
# Focused diagnostics: avoid printing giant payloads.
125+
echo -n "0x$ONCHAIN_CREATION" | cast keccak
126+
echo -n "0x$CREATION" | cast keccak
127+
128+
echo -n "0x$ONCHAIN_ARGS" | cast keccak
129+
echo -n "0x$ENCODED_ARGS" | cast keccak
130+
if [[ "$ONCHAIN_ARGS" == "$ENCODED_ARGS" ]]; then
131+
echo "Constructor args: ✅ match"
132+
else
133+
echo "Constructor args: ❌ mismatch"
134+
fi
135+
136+
FIRST_DIFF=-1
137+
for ((i=0; i<${#ONCHAIN}; i++)); do
138+
if [[ "${ONCHAIN:$i:1}" != "${LOCAL_INIT:$i:1}" ]]; then
139+
FIRST_DIFF=$i
140+
break
141+
fi
142+
done
143+
if [[ $FIRST_DIFF -ge 0 ]]; then
144+
BYTE_INDEX=$((FIRST_DIFF / 2))
145+
START=$((FIRST_DIFF - 20))
146+
(( START < 0 )) && START=0
147+
echo "First differing nibble index: $FIRST_DIFF (byte ~$BYTE_INDEX)"
148+
echo "Onchain snippet: ${ONCHAIN:$START:80}"
149+
echo "Local snippet : ${LOCAL_INIT:$START:80}"
150+
fi
151+
exit 1
35152
else
36153
echo "❌ Code mismatch"
154+
echo "Onchain bytes : ${#ONCHAIN}"
155+
echo "Local bytes : ${#LOCAL_INIT}"
156+
echo "Hint: compare verified compiler settings (solc/optimizer runs/via-ir/evmVersion) against your local build."
157+
echo "Hint: metadata-only drift is common; if constructor args match, check explorer verification details."
158+
FIRST_DIFF=-1
159+
for ((i=0; i<${#ONCHAIN}; i++)); do
160+
if [[ "${ONCHAIN:$i:1}" != "${LOCAL_INIT:$i:1}" ]]; then
161+
FIRST_DIFF=$i
162+
break
163+
fi
164+
done
165+
if [[ $FIRST_DIFF -ge 0 ]]; then
166+
BYTE_INDEX=$((FIRST_DIFF / 2))
167+
START=$((FIRST_DIFF - 20))
168+
(( START < 0 )) && START=0
169+
echo "First differing nibble index: $FIRST_DIFF (byte ~$BYTE_INDEX)"
170+
echo "Onchain snippet: ${ONCHAIN:$START:80}"
171+
echo "Local snippet : ${LOCAL_INIT:$START:80}"
172+
fi
173+
exit 1
37174
fi

0 commit comments

Comments
 (0)