Repository navigation
Expand file tree
/
Copy pathdeploy-core
More file actions
executable file
·288 lines (264 loc) · 15 KB
/
Copy pathdeploy-core
File metadata and controls
executable file
·288 lines (264 loc) · 15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#!/bin/bash
# ==============================================================================
# Phase 1: Core Infrastructure Deployment
# ==============================================================================
set -e # Fail fast on any command failure
ENV=${1:-"anvil"}
ENV_FILE=".env.$ENV"
if [ -f "$ENV_FILE" ]; then
set -a; source "$ENV_FILE"; set +a
fi
# Determine RPC
if [ "$ENV" == "anvil" ]; then
# ANVIL_RPC_URL lets a second local node (another port) be targeted without editing this file.
RPC_URL="${ANVIL_RPC_URL:-http://127.0.0.1:8545}"
else
ENV_UPPER=$(echo $ENV | tr '[:lower:]' '[:upper:]')
ENV_CLEAN=${ENV_UPPER//-/_}
VAR_NAME="${ENV_CLEAN}_RPC_URL"
RPC_URL=${!VAR_NAME:-$RPC_URL}
fi
if [ -z "$RPC_URL" ]; then
echo -e "\033[0;31m❌ Error: RPC_URL for environment '$ENV' is not defined.\033[0m"
echo "Check if .env.$ENV exists and contains ${ENV_CLEAN}_RPC_URL"
exit 1
fi
# Smart Hash Check
# Cover Solidity sources AND the build/deploy toolchain files so that changes to
# foundry.toml (optimizer, profile, remappings), remappings.txt, or deploy-core
# itself will also trigger a fresh deploy rather than being silently skipped.
# T-4 (Codex stop-review HIGH-3): also the deploy/verify SCRIPTS (contracts/script — incl. the
# default-artifact verifier), the anvil infra helper, prepare-test and audit-core. A change to
# the code that deploys or verifies must re-run deploy + audit, never be skipped as "unchanged".
CURRENT_HASH=$(
{ find contracts/src -name "*.sol" -not -path "*/mocks/*" -type f -exec shasum -a 256 {} +; \
find contracts/script -name "*.sol" -type f -exec shasum -a 256 {} +; \
shasum -a 256 foundry.toml remappings.txt deploy-core prepare-test audit-core \
contracts/test/helpers/AnvilMockPriceFeed.sol 2>/dev/null; } \
| sort | shasum -a 256 | awk '{print $1}')
CONFIG_FILE_PATH="deployments/config.$ENV.json"
FORCE_DEPLOY=false
FRESH_DEPLOY=false
if [[ "$*" == *"--force"* ]]; then FORCE_DEPLOY=true; fi
if [[ "$*" == *"--fresh-deploy"* ]]; then FRESH_DEPLOY=true; fi
# Warn loudly if --fresh-deploy is used on a live network (destructive — creates new proxies)
if [ "$FRESH_DEPLOY" = true ] && [ "$ENV" != "anvil" ]; then
echo -e "\033[0;31m⚠️ WARNING: --fresh-deploy will deploy NEW UUPS proxies and LOSE all on-chain state\033[0m"
echo -e "\033[0;31m (registered communities, staked GToken, SBT records, etc.)\033[0m"
echo -e "\033[0;31m Only use this for brand-new network deployments!\033[0m"
read -p " Type 'yes' to confirm fresh deployment on $ENV: " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
echo "Aborted."
exit 1
fi
fi
SHOULD_RUN_DEPLOY=false
if [ "$ENV" != "anvil" ] && [ -f "$CONFIG_FILE_PATH" ] && [ "$FORCE_DEPLOY" = false ] && [ "$FRESH_DEPLOY" = false ]; then
STORED_HASH=$(jq -r '.srcHash // ""' "$CONFIG_FILE_PATH")
if [ "$CURRENT_HASH" == "$STORED_HASH" ]; then
echo "✅ Code unchanged ($CURRENT_HASH). Skipping Core Deployment."
SHOULD_RUN_DEPLOY=false
else
echo "🔄 Code changed. Old: $STORED_HASH -> New: $CURRENT_HASH"
SHOULD_RUN_DEPLOY=true
fi
else
# Anvil, no config file, --force, or --fresh-deploy
SHOULD_RUN_DEPLOY=true
fi
export CONFIG_FILE="config.$ENV.json"
# SRC_HASH is intentionally NOT exported here. The forge deploy scripts write
# srcHash via vm.envOr("SRC_HASH", ""), so the config will contain an empty hash
# after deployment. deploy-core writes the real hash into the config ONLY after
# audit-core passes (see "Commit srcHash" block below). This prevents a failed
# audit from leaving a valid-looking srcHash in config that would cause the next
# run to skip both the deploy and the audit.
export DEPLOY_TIME=$(date "+%Y-%m-%d %H:%M:%S")
export ENV="$ENV"
# CC-48 round-7 LOW-4 / round-8 LOW-1: chainid 31337 no longer skips GovernanceOwnerGate
# silently, AND this entry point no longer satisfies the acknowledgement unconditionally.
#
# Round 7 turned the skip into a typed ack, then set that ack here for every
# `deploy-core anvil` run on the grounds that it is "a genuinely local node started by this
# repo's own tooling". That reason was FALSE and is retracted: deploy-core does not start
# anvil (run_full_regression.sh only tells the operator to start one themselves, and
# deprecated/scripts/test-anvil.sh starts `anvil --fork-url $SEPOLIA_RPC_URL`). So on the
# fork-rehearsal path the ack was set automatically and nobody ever acknowledged anything —
# exactly the hole round 7 claimed to close.
#
# The ack is now PROBED FOR, not assumed. It is pre-set only when the node answers as a
# fresh local chain: chain id 31337 AND a head block below the ceiling the gate itself
# enforces. Anything else — a fork, an unreachable node, a missing `cast` — leaves it unset
# and the operator is told what to type. Fail-closed in the direction of asking a human.
# `deploy-core sepolia` and up are untouched: they must satisfy the real gate.
LOCAL_FRESH_BLOCK_CEILING=1000000
if [ "$ENV" == "anvil" ] && [ -z "${LOCAL_DEV_GOVERNANCE_ACK:-}" ]; then
ANVIL_RPC="${RPC_URL:-http://127.0.0.1:8545}"
PROBED_CHAIN_ID=$(cast chain-id --rpc-url "$ANVIL_RPC" 2>/dev/null || echo "")
PROBED_BLOCK=$(cast block-number --rpc-url "$ANVIL_RPC" 2>/dev/null || echo "")
if [ "$PROBED_CHAIN_ID" == "31337" ] && [ -n "$PROBED_BLOCK" ] \
&& [ "$PROBED_BLOCK" -lt "$LOCAL_FRESH_BLOCK_CEILING" ] 2>/dev/null; then
echo " [gov-gate] probed $ANVIL_RPC: chain id 31337, head block $PROBED_BLOCK"
echo " [gov-gate] -> fresh local node, pre-setting LOCAL_DEV_GOVERNANCE_ACK=true."
echo " [gov-gate] This is a HEURISTIC, not a proof that the node is not a fork."
export LOCAL_DEV_GOVERNANCE_ACK=true
else
echo " [gov-gate] probed $ANVIL_RPC: chain id '${PROBED_CHAIN_ID:-<unreachable>}',"
echo " [gov-gate] head block '${PROBED_BLOCK:-<unreachable>}' -- this does NOT look like a"
echo " [gov-gate] fresh local anvil, so LOCAL_DEV_GOVERNANCE_ACK is NOT being set for you."
echo " [gov-gate] If you really mean to rehearse against this node with no governance gate,"
echo " [gov-gate] set LOCAL_DEV_GOVERNANCE_ACK=true yourself. Note the gate also refuses a"
echo " [gov-gate] 31337 node whose head block is >= $LOCAL_FRESH_BLOCK_CEILING regardless."
fi
fi
# ─── Routing: Anvil → DeployAnvil, live fresh → DeployLive, live upgrade → UpgradeLive ───
# From v5.3.3-beta onwards, ALL SuperPaymaster/Registry changes on live networks
# MUST go through UpgradeLive (UUPS) to preserve proxy addresses and on-chain state.
# Use --fresh-deploy only for brand-new network deployments.
if [ "$ENV" == "anvil" ]; then
SCRIPT_NAME="DeployAnvil"
elif [ "$FRESH_DEPLOY" = true ] || ! jq -e '.registry != null and .registry != "0x0000000000000000000000000000000000000000"' "$CONFIG_FILE_PATH" > /dev/null 2>&1; then
# Fresh deploy: no config yet, or --fresh-deploy flag
SCRIPT_NAME="DeployLive"
echo "🆕 Mode: Fresh Deploy (DeployLive)"
else
# Existing proxies found → UUPS upgrade (preserves state)
SCRIPT_NAME="UpgradeLive"
echo "🔄 Mode: UUPS Upgrade (UpgradeLive) — proxy addresses preserved"
fi
# Prepare Forge flags
FORGE_FLAGS="--rpc-url $RPC_URL --broadcast --slow -vv"
if [ "$ENV" == "anvil" ]; then
# Bypass EIP-170 size-limit interactive prompt on Anvil (size limits are advisory,
# not enforced by Anvil's EVM). Without this flag, forge 1.4+ will request terminal
# confirmation when a contract exceeds 24576 bytes, causing CI/non-TTY failures.
FORGE_FLAGS="$FORGE_FLAGS --disable-code-size-limit"
else
# --non-interactive: forge 1.4+ prompts for broadcast confirmation via /dev/tty even
# with --broadcast, which blocks non-TTY (CI/background) runs indefinitely.
FORGE_FLAGS="$FORGE_FLAGS --timeout 300 --non-interactive"
fi
# Determine Signer
if [ -n "$DEPLOYER_ACCOUNT" ]; then
echo "🔐 Using Foundry Keystore Account: $DEPLOYER_ACCOUNT"
# Check if DEPLOYER_ADDRESS is already configured to skip password prompt
if [ -n "$DEPLOYER_ADDRESS" ]; then
echo " ✅ Using configured address: $DEPLOYER_ADDRESS"
else
echo " 🔍 Resolving address from keystore (Password Required)..."
DEPLOYER_ADDR=$(cast wallet address --account $DEPLOYER_ACCOUNT)
echo " 📍 Resolved Address: $DEPLOYER_ADDR"
export DEPLOYER_ADDRESS=$DEPLOYER_ADDR
fi
# When using --account, we don't need --sender if the account is the sender
FORGE_FLAGS="$FORGE_FLAGS --account $DEPLOYER_ACCOUNT"
elif [ -n "$PRIVATE_KEY" ]; then
echo "⚠️ Using Plaintext Private Key (Not Recommended)"
FORGE_FLAGS="$FORGE_FLAGS --private-key $PRIVATE_KEY"
else
# For Anvil, we can use the default test account if needed, or fail for live nets
if [ "$ENV" != "anvil" ]; then
echo "❌ Error: No DEPLOYER_ACCOUNT or PRIVATE_KEY defined in .env.$ENV"
exit 1
else
# Anvil default (0xf39... id 0)
echo "🔧 Using Anvil Default Account"
FORGE_FLAGS="$FORGE_FLAGS --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
# Export the corresponding address so VerifyV3_1_1 can resolve DEPLOYER_ADDRESS
export DEPLOYER_ADDRESS="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
fi
fi
if [ "$SHOULD_RUN_DEPLOY" = true ]; then
echo "🚀 Starting Phase 1: Core Infrastructure Deployment ($ENV)"
# AUD-4: the 5.5.0 contracts are deployed BY ARTIFACT PATH from out/<C>.sol/<C>.json
# (profile.default). `forge script` only compiles the script's own closure (under the
# registry-size profile, because the scripts import Registry.sol), so build the default
# artifacts first — otherwise a stale or missing out/ would be what gets deployed.
forge build
forge script "contracts/script/v3/${SCRIPT_NAME}.s.sol:$SCRIPT_NAME" $FORGE_FLAGS
# Sync ABI JSONs from compiled artifacts (keeps abis/ in sync with deployed bytecode)
echo " 📄 Syncing ABI JSONs from out/..."
for contract in SuperPaymaster Registry xPNTsToken xPNTsFactory GToken GTokenStaking MySBT MicroPaymentChannel X402Facilitator PolicyRegistry TimelockController; do
artifact=$(find out -name "${contract}.json" -not -path "*test*" 2>/dev/null | head -1)
if [ -n "$artifact" ]; then
jq '.abi' "$artifact" > "abis/${contract}.json" 2>/dev/null && echo " ✓ $contract" || true
fi
done
fi
# --- PHASE 2: Core Logic Audit (Self-Verification) ---
# Delegate to audit-core (per-check idempotency + retry + ABI/interface checks).
#
# Deliberately WITHOUT --force. This line used to claim "--force because we just
# deployed: stamps from a previous run are now stale", which described a flag the call
# below never passed and does not need: the stamp is `registryImpl|spImpl|updateTime`,
# so a real deployment invalidates every stamp by itself. On the skip-if-unchanged path
# nothing was redeployed, and re-running the deployment checks would tell us only what
# the unchanged stamp already says.
#
# What that idempotency must NOT be allowed to hide is a check over LIVE state, which
# moves without the stamp moving. Those are listed in audit-core's NEVER_SKIP and run
# every time regardless.
echo ""
if [ -x "./audit-core" ]; then
./audit-core "$ENV"
else
echo -e "\033[0;31m❌ audit-core not found or not executable. Run: chmod +x audit-core\033[0m"
exit 1
fi
echo -e "\n\033[0;32m✅ Core Infrastructure is Deployed & Audited!\033[0m"
# --- Commit srcHash (only after all audit checks passed) ---
# The forge deploy scripts wrote srcHash="" to config (SRC_HASH was not exported).
# Now that audit-core has returned 0 we can safely record the real hash so that
# the next deploy-core invocation can skip the deploy when sources are unchanged.
# If audit-core had failed (exit 1), set -e would have aborted before this point,
# leaving srcHash="" in config and forcing a full redeploy+reaudit on the next run.
if [ -f "$CONFIG_FILE_PATH" ]; then
echo " 📝 Committing srcHash to config (${CURRENT_HASH})..."
jq --arg h "$CURRENT_HASH" '.srcHash = $h' "$CONFIG_FILE_PATH" \
> "${CONFIG_FILE_PATH}.tmp" && mv "${CONFIG_FILE_PATH}.tmp" "$CONFIG_FILE_PATH"
echo " ✅ srcHash committed."
fi
# --- PHASE 2.5: Prepare Test Accounts (testnet only, not mainnet/optimism) ---
# After a successful deploy, idempotently set up the Mycelium/Anni demo operator.
# Skipped on mainnet and optimism (real money; manual operator onboarding required).
# prepare-test is itself idempotent — each step checks before acting.
if [ "$SHOULD_RUN_DEPLOY" = true ]; then
case "$ENV" in
sepolia|op-sepolia|anvil)
echo ""
echo "━━━ Phase 2.5) Prepare Test Accounts ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# T-4 (Codex stop-review MEDIUM-8): prepare-test ends with verifiers (Check09 and,
# on anvil, CheckDefaultArtifacts). A verifier failure must FAIL the deployment, not
# be downgraded to a warning — and must not leave a valid srcHash behind, or the next
# run would skip deploy + audit as "unchanged".
if [ ! -x "./prepare-test" ] || ! ./prepare-test "$ENV"; then
echo -e "\033[0;31m❌ prepare-test (incl. its verifiers) failed — deployment NOT accepted.\033[0m"
if [ -f "$CONFIG_FILE_PATH" ]; then
jq '.srcHash = ""' "$CONFIG_FILE_PATH" > "${CONFIG_FILE_PATH}.tmp" && mv "${CONFIG_FILE_PATH}.tmp" "$CONFIG_FILE_PATH"
echo " srcHash cleared so the next run redeploys and re-audits."
fi
exit 1
fi
;;
mainnet|optimism|op-mainnet)
echo -e "\n\033[0;33mℹ️ Skipping prepare-test on $ENV (mainnet — manual operator onboarding required)\033[0m"
echo -e "\033[0;33m Run: forge script contracts/script/v3/InitializeAAStar.s.sol --account \$DEPLOYER_ACCOUNT --broadcast\033[0m"
echo -e "\033[0;33m forge script contracts/script/v3/InitializeMycelium.s.sol --account \$ANNI_ACCOUNT --broadcast\033[0m"
;;
esac
fi
# --- PHASE 3: Etherscan Verification (live networks only) ---
# Skip for anvil; for sepolia / op-* / mainnet, submit every freshly-deployed
# contract to Etherscan so external tools (SDK, audit, block explorer users)
# can see the ABI immediately. Idempotent: already-verified contracts return
# success fast. Failure is non-fatal — operator can re-run `./verify-all.sh
# --env $ENV` later. See memory: 部署后必须 verify.
if [ "$ENV" != "anvil" ] && [ "$SHOULD_RUN_DEPLOY" = true ]; then
echo -e "\n🔍 Starting Etherscan verification (this can take a few minutes)..."
if [ -x "./verify-all.sh" ]; then
./verify-all.sh --env "$ENV" || echo -e "\033[1;33m⚠️ Some contracts failed to verify. Re-run './verify-all.sh --env $ENV' manually.\033[0m"
else
echo -e "\033[1;33m⚠️ verify-all.sh not executable; skipping Etherscan verification.\033[0m"
fi
fi
echo -e "\n\033[0;32m✅ deploy-core finished for env=$ENV\033[0m"