-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathJustfile
More file actions
407 lines (321 loc) · 16.3 KB
/
Copy pathJustfile
File metadata and controls
407 lines (321 loc) · 16.3 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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
set dotenv-load
# Restrict Foundry parallelism by default; override from the caller when needed.
export FOUNDRY_THREADS := env("FOUNDRY_THREADS", "4")
export FOUNDRY_COMPUTE_UNITS_PER_SECOND := env("FOUNDRY_COMPUTE_UNITS_PER_SECOND", "200")
# Make forked Anvil more tolerant to transient upstream RPC failures.
export ANVIL_FORK_RETRIES := env("ANVIL_FORK_RETRIES", "15")
export ANVIL_FORK_RETRY_BACKOFF := env("ANVIL_FORK_RETRY_BACKOFF", "1000")
export ANVIL_FORK_TIMEOUT := env("ANVIL_FORK_TIMEOUT", "90000")
# Make forked Forge tests more tolerant to transient upstream RPC failures.
export FOUNDRY_FORK_RETRIES := env("FOUNDRY_FORK_RETRIES", "15")
export FOUNDRY_FORK_RETRY_BACKOFF := env("FOUNDRY_FORK_RETRY_BACKOFF", "1000")
chain := env_var_or_default("CHAIN", "mainnet")
chain_script_suffix := if chain == "mainnet" {
"Mainnet"
} else if chain == "hoodi" {
"Hoodi"
} else if chain == "local-devnet" {
"LocalDevNet"
} else {
error("Unsupported chain " + chain + ". Supported: mainnet, hoodi, local-devnet")
}
anvil_host := env_var_or_default("ANVIL_IP_ADDR", "127.0.0.1")
anvil_port := env_var_or_default("ANVIL_PORT", "8545")
anvil_rpc_url := "http://" + anvil_host + ":" + anvil_port
disable_code_size_limit := if env("DISABLE_CODE_SIZE_LIMIT", "") != "" { "--disable-code-size-limit" } else { "" }
# Shared deployment helpers
_assert-clean-workspace:
#!/usr/bin/env bash
set -euo pipefail
if [ "${ALLOW_DIRTY_LIVE_DEPLOY:-}" = "1" ]; then
echo "Skipping clean workspace check because ALLOW_DIRTY_LIVE_DEPLOY=1." >&2
exit 0
fi
if [ -n "$(git status --porcelain=v1 --untracked-files=all)" ]; then
echo "Live deployment requires a clean git workspace." >&2
git status --short >&2
exit 1
fi
_deploy-generic deploy_script_path rpc_url *args:
FOUNDRY_PROFILE=deploy \
forge script {{deploy_script_path}} --sig="run(string)" --rpc-url {{rpc_url}} --broadcast --slow {{args}} -- `git rev-parse HEAD`
[confirm("You are about to broadcast deployment transactions to the network. Are you sure?")]
_deploy-live-generic deploy_script_path *args:
just _assert-clean-workspace
just _deploy-live-generic-no-confirm {{deploy_script_path}} --broadcast --verify {{args}}
_deploy-live-generic-no-confirm deploy_script_path *args:
FOUNDRY_PROFILE=deploy \
forge script {{deploy_script_path}} --sig="run(string)" --force --rpc-url ${RPC_URL} {{args}} -- `git rev-parse HEAD`
_deploy-live-generic-dry deploy_script_path *args:
just _deploy-live-generic-no-confirm {{deploy_script_path}} {{args}}
_verify-live-generic deploy_script_path *args:
FOUNDRY_PROFILE=deploy \
forge script {{deploy_script_path}} --sig="run(string)" --rpc-url ${RPC_URL} --verify {{args}} --unlocked -- `git rev-parse HEAD`
# Shared artifact helpers
_copy-broadcast-json script_name rpc_url dry_prefix json_name dest_path:
just _copy-file \
./broadcast/{{script_name}}.s.sol/$(cast chain-id --rpc-url "{{rpc_url}}"){{dry_prefix}}/{{json_name}} \
{{dest_path}}
_finalize-broadcast-artifacts script_name rpc_url dry_prefix json_name deploy_config_path:
just _copy-broadcast-json "{{script_name}}" "{{rpc_url}}" "{{dry_prefix}}" "{{json_name}}" "$(dirname "{{deploy_config_path}}")/transactions.json"
just _merge-external-libraries "{{deploy_config_path}}" "$(dirname "{{deploy_config_path}}")/transactions.json"
_copy-file src_path dest_path:
mkdir -p "$(dirname "{{dest_path}}")"
cp "{{src_path}}" "{{dest_path}}"
_merge-external-libraries deploy_config_path transactions_path:
node script/mergeExternalLibraries.js {{deploy_config_path}} {{transactions_path}}
# Shared local fork helpers
_local-private-key:
@jq -re '.private_keys[0]' localhost.json
# Start local anvil fork when needed.
# Prints owned PID; prints nothing when reusing an already running fork.
_fork-up:
#!/usr/bin/env bash
set -euo pipefail
if nc -z -w 1 {{anvil_host}} {{anvil_port}} > /dev/null 2>&1; then
just _warn "anvil process is already running at {{anvil_rpc_url}}; reusing existing process." >&2
exit 0
fi
rpc_url="${RPC_URL:-}"
if [ -z "${rpc_url}" ]; then
just _warn "RPC_URL is required to start anvil fork." >&2
exit 1
fi
anvil -f "${rpc_url}" --host {{anvil_host}} --port {{anvil_port}} \
--config-out localhost.json {{disable_code_size_limit}} \
--compute-units-per-second "${FOUNDRY_COMPUTE_UNITS_PER_SECOND}" \
--retries "${ANVIL_FORK_RETRIES}" \
--fork-retry-backoff "${ANVIL_FORK_RETRY_BACKOFF}" \
--timeout "${ANVIL_FORK_TIMEOUT}" \
> /dev/null 2>&1 < /dev/null &
anvil_pid=$!
# Guard against hanging forever when anvil fails to accept connections.
start_deadline_epoch=$(( $(date +%s) + 60 ))
while ! nc -z -w 1 {{anvil_host}} {{anvil_port}} > /dev/null 2>&1; do
if ! kill -0 "${anvil_pid}" 2>/dev/null; then
wait "${anvil_pid}" || true
just _warn "failed to start anvil at {{anvil_rpc_url}}." >&2
exit 1
fi
if [ "$(date +%s)" -ge "${start_deadline_epoch}" ]; then
kill "${anvil_pid}" 2>/dev/null || true
wait "${anvil_pid}" || true
just _warn "timed out waiting for anvil at {{anvil_rpc_url}}." >&2
exit 1
fi
sleep 1
done
printf "%s\n" "${anvil_pid}"
_fork-up-and-down:
#!/usr/bin/env bash
set -euo pipefail
# Emit snippet for `eval`: bind pid and install cleanup trap in the caller shell.
# If fork is reused (already running), owned_anvil_pid is empty and cleanup is a no-op.
owned_anvil_pid="$(just _fork-up)"
cat <<EOF
owned_anvil_pid="${owned_anvil_pid}"
if [ -n "\${owned_anvil_pid}" ]; then
just _info "local anvil fork started at {{anvil_rpc_url}} (pid: \${owned_anvil_pid}); it will be stopped on recipe exit."
fi
__fork_cleanup() {
if [ -z "\${owned_anvil_pid}" ]; then
return 0
fi
if ! kill -0 "\${owned_anvil_pid}" 2>/dev/null; then
return 0
fi
if ! ps -p "\${owned_anvil_pid}" -o comm= 2>/dev/null | grep -qx "anvil"; then
return 0
fi
if kill "\${owned_anvil_pid}" 2>/dev/null; then
just _info "local anvil fork stopped (pid: \${owned_anvil_pid})."
fi
}
trap __fork_cleanup EXIT
EOF
# Recipe modules
import? ".local.just"
import "fork.just"
import "csm.just"
import "csm0x02.just"
import "curated.just"
# Default and top-level workflows
default: clean deps build test-all
build *args:
forge build --skip test --skip script {{args}}
clean:
forge clean
rm -rf cache broadcast out node_modules
deps:
yarn workspaces focus --all --production
deps-dev:
yarn workspaces focus --all && npx husky install
lint-solhint:
yarn lint:solhint
lint-foundry *args:
forge lint {{args}}
lint-fix:
yarn lint:fix
lint:
just lint-foundry
yarn lint:check
test-all:
#!/usr/bin/env bash
set -euo pipefail
# Run unit tests in parallel with local fork flows, but always wait to preserve failures.
just test-unit &
unit_pid=$!
if ! just test-local; then
wait "${unit_pid}" || true
exit 1
fi
wait "${unit_pid}"
# Run all local fork deployment/integration flows across modules.
# Must be sequential because local flows share one anvil endpoint.
test-local *args:
just test-csm-local {{args}}
just test-curated-local {{args}}
just test-csm0x02-local {{args}}
# Run all unit tests
test-unit *args:
env -u FOUNDRY_THREADS forge test --skip script --no-match-path 'test/fork/**' -vvv {{args}}
# Run all deployment tests that should be executed against full scratch deployment before the module activation vote
test-deployment-full-scratch *args:
forge test --match-path 'test/fork/deployment/*' --no-match-test '.*_afterVote.*' \
-vvv --show-progress --summary --detailed {{args}}
# Run all deployment tests that should be executed against full scratch deployment after the module activation vote
test-deployment-full-afterVote *args:
forge test --match-path 'test/fork/deployment/*' --no-match-test '.*_scratch.*' \
-vvv --show-progress --summary --detailed {{args}}
# Run all integration tests
test-integration *args:
forge test --match-path 'test/fork/integration/**' \
-vvv --show-progress --summary --detailed {{args}}
# Run tests for utility contracts
test-utils *args:
forge test --match-path 'test/fork/utils/*' \
-vvv --show-progress --summary --detailed {{args}}
# Run tests applicable after the module upgrade vote. Does not include deployment tests
test-post-upgrade *args:
forge test --match-path='test/fork/**' --no-match-path 'test/fork/deployment/**' \
-vvv --show-progress --summary --detailed {{args}}
gas-report:
#!/usr/bin/env python
import subprocess
import re
command = "just test-unit --nmt 'testFuzz.+' --gas-report"
try:
output = subprocess.check_output(command, shell=True, text=True)
except subprocess.CalledProcessError as e:
print(e.output)
raise
lines = output.split('\n')
filename = 'GAS.md'
to_print = False
skip_next = False
with open(filename, 'w') as fh:
for line in lines:
if skip_next:
skip_next = False
continue
if line.startswith('|'):
to_print = True
if line.startswith('| Deployment Cost'):
to_print = False
skip_next = True
if re.match(r"Ran \d+ test suites", line):
break
if to_print:
fh.write(line + '\n')
print(f"Done. Gas report saved to {filename}")
coverage *args:
FOUNDRY_PROFILE=coverage forge coverage --no-match-coverage '(test|script)' --no-match-path 'test/fork/*' {{args}}
# Run coverage and save the report in LCOV file.
coverage-lcov *args:
FOUNDRY_PROFILE=coverage forge coverage --no-match-coverage '(test|script)' --no-match-path 'test/fork/*' --report lcov {{args}}
diffyscan-contracts *args:
yarn generate:diffyscan {{args}}
oz-upgrades:
#!/usr/bin/env bash
set -euo pipefail
CURR_DIR=$(pwd)
REFERENCE_REF="${OZ_UPGRADES_REFERENCE_REF:-v2.0}"
REFERENCE_REPO="${OZ_UPGRADES_REFERENCE_REPO:-$CURR_DIR}"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
# OpenZeppelin validates only full build-info. Do not use `just build --skip=...` here.
FOUNDRY_PROFILE=upgrades forge clean
FOUNDRY_PROFILE=upgrades forge build
git clone --branch "$REFERENCE_REF" "$REFERENCE_REPO" "$TMP_DIR"
cd "$TMP_DIR"
just deps
FOUNDRY_PROFILE=upgrades forge clean
FOUNDRY_PROFILE=upgrades forge build
cd "$CURR_DIR"
rm -rf out/v2
cp -r "$TMP_DIR/out/build-info" out/v2
# Muted some errors globally
# --unsafeAllowLinkedLibraries due to no support for linked libraries in upgrades-core
# --unsafeAllow=constructor,state-variable-immutable - all the contracts have immutables with safe usage
#
# Expected CSM v2 -> v3 findings:
# - CSModule: the old linear variables were moved to ModuleLinearStorage.BaseModuleStorage accessed at slot 0.
# OZ does not match top-level variables with struct fields accessed through assembly and reports them as deleted.
# Slots 0, 5, 6, 7, 8, and 9 preserve their meanings. Slots 1-2 replace the legacy queue with
# `totalWithdrawnValidators` and `upToDateOperatorDepositInfoCount` in `finalizeUpgradeV3`. Slots 3-4 reuse
# fields nullified in `finalizeUpgradeV2` for key balance mappings.
# - Accounting: slot 0 replaces `_feeDistributorOld`, nullified in `finalizeUpgradeV2`, with `_rewardsClaimers`.
# - FeeOracle: slots 0-1 replace deprecated values, nullified in `finalizeUpgradeV2`, with `__freeSlot1/2`.
# - ParametersRegistry: only names changed for existing penalty/fee fields; slots and types are preserved.
# - VettedGate: referral program storage was removed and is no longer used after the v3 vote migration.
# - ExitPenalties: only struct member names changed for existing penalty/fee fields; slots and types are preserved.
# - FeeDistributor and ValidatorStrikes: no storage layout changes are expected.
npx --yes @openzeppelin/upgrades-core validate --contract=CSModule --reference=v2:CSModule --referenceBuildInfoDirs=out/v2 \
--unsafeAllowLinkedLibraries --unsafeAllow=constructor,state-variable-immutable || true
npx --yes @openzeppelin/upgrades-core validate --contract=ParametersRegistry --reference=v2:CSParametersRegistry --referenceBuildInfoDirs=out/v2 \
--unsafeAllowLinkedLibraries --unsafeAllow=constructor,state-variable-immutable --unsafeAllowRenames || true
npx --yes @openzeppelin/upgrades-core validate --contract=FeeOracle --reference=v2:CSFeeOracle --referenceBuildInfoDirs=out/v2 \
--unsafeAllowLinkedLibraries --unsafeAllow=constructor,state-variable-immutable || true
npx --yes @openzeppelin/upgrades-core validate --contract=VettedGate --reference=v2:VettedGate --referenceBuildInfoDirs=out/v2 \
--unsafeAllowLinkedLibraries --unsafeAllow=constructor,state-variable-immutable || true
npx --yes @openzeppelin/upgrades-core validate --contract=Accounting --reference=v2:CSAccounting --referenceBuildInfoDirs=out/v2 \
--unsafeAllowLinkedLibraries --unsafeAllow=constructor,state-variable-immutable || true
npx --yes @openzeppelin/upgrades-core validate --contract=FeeDistributor --reference=v2:CSFeeDistributor --referenceBuildInfoDirs=out/v2 \
--unsafeAllowLinkedLibraries --unsafeAllow=constructor,state-variable-immutable || true
npx --yes @openzeppelin/upgrades-core validate --contract=ExitPenalties --reference=v2:CSExitPenalties --referenceBuildInfoDirs=out/v2 \
--unsafeAllowLinkedLibraries --unsafeAllow=constructor,state-variable-immutable --unsafeAllowRenames || true
npx --yes @openzeppelin/upgrades-core validate --contract=ValidatorStrikes --reference=v2:CSStrikes --referenceBuildInfoDirs=out/v2 \
--unsafeAllowLinkedLibraries --unsafeAllow=constructor,state-variable-immutable || true
make-fork *args:
@if nc -z -w 1 {{anvil_host}} {{anvil_port}} > /dev/null 2>&1; \
then just _warn "anvil process is already running at {{anvil_rpc_url}}. Make sure it's connected to the right network and in the right state."; \
else exec anvil -f ${RPC_URL} --host {{anvil_host}} --port {{anvil_port}} --config-out localhost.json {{disable_code_size_limit}} --timeout 90000 {{args}}; \
fi
kill-fork:
@-pkill anvil && just _warn "anvil process is killed"
deploy-utils module_name contract_name *args:
just _deploy-utils {{module_name}} {{contract_name}} {{anvil_rpc_url}} ./artifacts/latest/{{module_name}}/utils/{{contract_name}}/ "" --broadcast {{args}}
deploy-utils-dry module_name contract_name *args:
just _deploy-utils {{module_name}} {{contract_name}} $RPC_URL ./artifacts/local/{{module_name}}/utils/{{contract_name}}/ "/dry-run" {{args}}
deploy-utils-live module_name contract_name *args:
just _warn "The current `tput bold`chain={{chain}}`tput sgr0` with the following rpc url: $RPC_URL"
just _deploy-utils-live-confirmed {{module_name}} {{contract_name}} {{args}}
[confirm("You are about to broadcast utility contract deployment transactions to the network. Are you sure?")]
_deploy-utils-live-confirmed module_name contract_name *args:
just _assert-clean-workspace
just _deploy-utils {{module_name}} {{contract_name}} $RPC_URL ./artifacts/latest/{{module_name}}/utils/{{contract_name}}/ "" --broadcast --verify {{args}}
_deploy-utils module_name contract_name rpc_url artifacts_dir dry-prefix *args:
#!/usr/bin/env bash
CHAIN_LOWER="{{chain}}"
CHAIN_CAPITALIZED="${CHAIN_LOWER^}"
mkdir -p {{artifacts_dir}}
ARTIFACTS_DIR={{artifacts_dir}} \
forge script script/Deploy{{contract_name}}${CHAIN_CAPITALIZED}.s.sol:Deploy{{contract_name}}${CHAIN_CAPITALIZED} --sig="run(string)" \
--rpc-url {{rpc_url}} --slow {{args}} -- `git rev-parse HEAD`
just _copy-file \
./broadcast/Deploy{{contract_name}}${CHAIN_CAPITALIZED}.s.sol/`cast chain-id --rpc-url={{rpc_url}}`{{dry-prefix}}/run-latest.json \
{{artifacts_dir}}/transactions.json
_warn message:
@tput setaf 3 && printf "[WARNING]" && tput sgr0 && echo " {{message}}"
_info message:
@tput setaf 6 && printf "[INFO]" && tput sgr0 && echo " {{message}}"