From 5220ea66904ea25ba00d19fc12054fa6305efd79 Mon Sep 17 00:00:00 2001 From: Lazaro Raul Date: Fri, 10 Jul 2026 18:27:10 +0200 Subject: [PATCH 01/11] Add SafeGuard deployment script Co-Authored-By: Claude Opus 4.8 (1M context) --- sh/common.sh | 1 + sh/deploy_safeguard.sh | 324 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 325 insertions(+) create mode 100755 sh/deploy_safeguard.sh diff --git a/sh/common.sh b/sh/common.sh index 69e3a800d..6a08ffe7b 100644 --- a/sh/common.sh +++ b/sh/common.sh @@ -5,6 +5,7 @@ if ! hash forge &>/dev/null ; then exit 1 fi +foundryup -u v1.5.1 &>/dev/null || true if [[ $(forge --version) != *b0a9dd9ceda36f63e2326ce530c10e6916f4b8a2* ]] ; then echo 'Wrong foundry version installed' >&2 echo 'Run `foundryup -i v1.5.1`' >&2 diff --git a/sh/deploy_safeguard.sh b/sh/deploy_safeguard.sh new file mode 100755 index 000000000..b76725af8 --- /dev/null +++ b/sh/deploy_safeguard.sh @@ -0,0 +1,324 @@ +#!/usr/bin/env bash + +## POSIX Bash implementation of realpath +## Copied and modified from https://github.com/mkropat/sh-realpath and https://github.com/AsymLabs/realpath-lib/ +## Copyright (c) 2014 Michael Kropat - MIT License +## Copyright (c) 2013 Asymmetry Laboratories - MIT License + +function realpath { + _resolve_symlinks "$(_canonicalize "$1")" +} + +function _directory { + local out slsh + slsh=/ + out="$1" + out="${out//$slsh$slsh/$slsh}" + if [ "$out" = / ]; then + echo / + return + fi + out="${out%/}" + case "$out" in + */*) + out="${out%/*}" + ;; + *) + out=. + ;; + esac + if [ "$out" ]; then + printf '%s\n' "$out" + else + echo / + fi +} + +function _file { + local out slsh + slsh=/ + out="$1" + out="${out//$slsh$slsh/$slsh}" + if [ "$out" = / ]; then + echo / + return + fi + out="${out%/}" + out="${out##*/}" + printf '%s\n' "$out" +} + +function _resolve_symlinks { + local path pattern context + while [ -L "$1" ]; do + context="$(_directory "$1")" + path="$(POSIXLY_CORRECT=y ls -ld -- "$1" 2>/dev/null)" + pattern='*'"$(_escape "$1")"' -> ' + path="${path#$pattern}" + set -- "$(_canonicalize "$(_prepend_context "$context" "$path")")" "$@" + _assert_no_path_cycles "$@" || return 1 + done + printf '%s\n' "$1" +} + +function _escape { + local out + out='' + local -i i + for ((i=0; i < ${#1}; i+=1)); do + out+='\'"${1:$i:1}" + done + printf '%s\n' "$out" +} + +function _prepend_context { + if [ "$1" = . ]; then + printf '%s\n' "$2" + else + case "$2" in + /* ) printf '%s\n' "$2" ;; + * ) printf '%s\n' "$1/$2" ;; + esac + fi +} + +function _assert_no_path_cycles { + local target path + + if [ $# -gt 16 ]; then + return 1 + fi + + target="$1" + shift + + for path in "$@"; do + if [ "$path" = "$target" ]; then + return 1 + fi + done +} + +function _canonicalize { + local d f + if [ -d "$1" ]; then + (CDPATH= cd -P "$1" 2>/dev/null && pwd -P) + else + d="$(_directory "$1")" + f="$(_file "$1")" + (CDPATH= cd -P "$d" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$f") + fi +} + +## end POSIX Bash implementation of realpath + +set -Eeufo pipefail -o posix + +declare project_root +project_root="$(_directory "$(_directory "$(realpath "${BASH_SOURCE[0]}")")")" +declare -r project_root +cd "$project_root" + +. "$project_root"/sh/common.sh + +declare safe +safe="$(get_config governance.upgradeSafe)" +declare -r safe + +if [[ ${safe:-null} == [nN][uU][lL][lL] ]] || [[ -z ${safe:-} ]] ; then + echo 'governance.upgradeSafe is missing from chain_config.json for chain "'"$chain_name"'"' >&2 + exit 1 +fi + +declare onchain_singleton +onchain_singleton="$(cast call --rpc-url "$rpc_url" "$safe" 'masterCopy()(address)')" +onchain_singleton="$(cast to-check-sum-address "$onchain_singleton")" +declare -r onchain_singleton + +declare -a candidate_factories candidate_variants +declare -A singleton_inithash=( + [ZeroExSettlerDeployerSafeGuardOnePointThree]=0x49f30800a6ac5996a48b80c47ff20f19f8728812498a2a7fe75a14864fab6438 + [ZeroExSettlerDeployerSafeGuardOnePointFourPointOne]=0x3555bd3ee95b1c6605c602740d71efaf200068e0395ccd701ac82ab8e42307bd + [ZeroExSettlerDeployerSafeGuardOnePointThreeEraVm]=0x0100080f935a1a562e892e1e71d9a0ca8cd349d19a413e0b7e7172c5e8c83ed1 + [ZeroExSettlerDeployerSafeGuardOnePointFourPointOneEraVm]=0x010006c19437ff25b448f038f7ea0a4c910e0ae9cd8e55f2d199b7916b72eb1e +) +if [[ $era_vm != [Ff]alse ]] ; then + candidate_factories=(0xaECDbB0a3B1C6D1Fe1755866e330D82eC81fD4FD) + candidate_variants=( + ZeroExSettlerDeployerSafeGuardOnePointThreeEraVm + ZeroExSettlerDeployerSafeGuardOnePointFourPointOneEraVm + ) +else + candidate_factories=( + 0x4e59b44847b379578588920cA78FbF26c0B4956C # Arachnid ("Nick's method") + 0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7 # Safe Singleton Factory + 0xC0DEb853af168215879d284cc8B4d0A645fA9b0E # ERC-7955 + 0x0000000000000000000000000000000000000012 # EIP-7997 + ) + candidate_variants=( + ZeroExSettlerDeployerSafeGuardOnePointThree + ZeroExSettlerDeployerSafeGuardOnePointFourPointOne + ) +fi + +function predict_create2 { + declare _predict_out + _predict_out="$(cast keccak "$(cast concat-hex 0xff "$1" "$(cast hash-zero)" "$2")")" + cast to-check-sum-address "0x${_predict_out:26:40}" +} + +function predict_create2_era_vm { + declare _predict_out + _predict_out="$(cast keccak "$(cast concat-hex "$(cast keccak zksyncCreate2)" "$(cast to-uint256 "$1")" "$(cast hash-zero)" "$2" "$3")")" + cast to-check-sum-address "0x${_predict_out:26:40}" +} + +function predict_singleton { + if [[ $era_vm = [Ff]alse ]] ; then + predict_create2 "$1" "$2" + else + predict_create2_era_vm "$1" "$2" "$(cast keccak '')" + fi +} + +declare guard_contract='' factory='' +declare _f _v +for _f in "${candidate_factories[@]}" ; do + for _v in "${candidate_variants[@]}" ; do + if [[ "$(predict_singleton "$_f" "${singleton_inithash[$_v]}")" == "$onchain_singleton" ]] ; then + guard_contract="$_v" + factory="$_f" + break 2 + fi + done +done +if [[ -z $guard_contract ]] ; then + echo 'No supported (factory, Safe version) pair produces the upgrade Safe'"'"'s singleton ('"$onchain_singleton"') on chain "'"$chain_name"'".' >&2 + echo 'Either the Safe uses an unsupported version, or its singleton was deployed by a factory the Guard does not support.' >&2 + exit 1 +fi +declare -r guard_contract factory + +if [[ $guard_contract == *OnePointThree* ]] ; then + echo 'Chain "'"$chain_name"'" runs Safe 1.3.0, whose Guard ('"$guard_contract"') cannot be deployed by this script.' >&2 + echo 'The 1.3.0 Guard disables itself at construction unless the Safe already designates it as its guard' >&2 + echo 'It must be created and enabled in one atomic transaction executed by the upgrade Safe' >&2 + exit 1 +fi + +if [[ "$(cast code --rpc-url "$rpc_url" "$factory")" == '0x' ]] ; then + echo 'The CREATE2 factory ('"$factory"') is not deployed on chain "'"$chain_name"'"' >&2 + exit 1 +fi + +declare signer +IFS='' read -p 'What address will you submit with?: ' -e -r -i 0xEf37aD2BACD70119F141140f7B5E46Cd53a65fc4 signer +declare -r signer + +. "$project_root"/sh/common_wallet_type.sh +. "$project_root"/sh/common_gas.sh + +# The Guard MUST compile with these exact settings: +export FOUNDRY_EVM_VERSION=london +export FOUNDRY_OPTIMIZER_RUNS=200 + +declare constructor_args +constructor_args="$(cast abi-encode 'constructor(address)' "$safe")" +declare -r constructor_args + +declare predicted +if [[ $era_vm = [Ff]alse ]] ; then + forge clean + forge build src/deployer/SafeGuard.sol + declare guard_bytecode initcode + guard_bytecode="$(forge inspect src/deployer/SafeGuard.sol:"$guard_contract" bytecode)" + initcode="$(cast concat-hex "$guard_bytecode" "$constructor_args")" + declare -r guard_bytecode initcode + predicted="$(predict_create2 "$factory" "$(cast keccak "$initcode")")" +else + # EraVM needs zkSync artifacts so we switch to the zksync aware foundry version + foundryup-zksync -u foundry-zksync-v0.1.9 || true + if [[ $(forge --version) != *14afc70e251c89b7e2af6e6ac02e9ac6f095b5cc* ]] ; then + echo 'Wrong foundry version installed' >&2 + echo 'Run `foundryup-zksync -i foundry-zksync-v0.1.9`' >&2 + exit 1 + fi + # foundry-zksync errors on an empty profile, so, we set default if empty + export FOUNDRY_PROFILE="${FOUNDRY_PROFILE:-default}" + + forge clean + forge build --zksync --zk-compile src/deployer/SafeGuard.sol + declare art="$project_root/zkout/SafeGuard.sol/$guard_contract.json" + declare bytecode_hash guard_bytecode + bytecode_hash="0x$(jq -Mr '.hash' "$art")" + guard_bytecode="0x$(jq -Mr '.bytecode.object' "$art")" + declare -r bytecode_hash guard_bytecode + predicted="$(predict_create2_era_vm "$factory" "$bytecode_hash" "$(cast keccak "$constructor_args")")" +fi +declare -r predicted + +echo 'SafeGuard variant : '"$guard_contract" >&2 +echo 'Protected Safe : '"$safe" >&2 +echo 'Predicted address : '"$predicted" >&2 + +if [[ "$(cast code --rpc-url "$rpc_url" "$predicted")" != '0x' ]] ; then + echo 'SafeGuard already deployed at '"$predicted"' on chain "'"$chain_name"'". Nothing to do.' >&2 + exit 0 +fi + +declare -a deploy_args zk_tx_flags=() +if [[ $era_vm = [Ff]alse ]] ; then + deploy_args=("$factory" "$(cast concat-hex "$(cast hash-zero)" "$initcode")") +else + declare _create2_calldata + _create2_calldata="$(cast calldata 'create2(bytes32,bytes32,bytes)' "$(cast hash-zero)" "$bytecode_hash" "$constructor_args")" + deploy_args=("$factory" "$(cast concat-hex "$(cast hash-zero)" "$_create2_calldata")") + zk_tx_flags=(--zksync --zk-factory-deps "$guard_bytecode") +fi +declare -r -a deploy_args zk_tx_flags + +declare -i gas_limit +if [[ ${BROADCAST-no} = [Yy]es ]] ; then + declare -i gas_estimate + gas_estimate="$(cast estimate --from "$signer" --rpc-url "$rpc_url" --gas-price $gas_price --chain $chainid "${extra_flags[@]}" "${zk_tx_flags[@]}" "${deploy_args[@]}")" + declare -r -i gas_estimate + gas_limit="$(apply_gas_multiplier $gas_estimate)" +else + gas_limit=$eip7825_gas_limit +fi +declare -r -i gas_limit + +declare -a maybe_broadcast=() +declare submit_rpc +if [[ ${BROADCAST-no} = [Yy]es ]] ; then + maybe_broadcast+=(send --chain $chainid) + if [[ $wallet_type = 'frame' ]] ; then + submit_rpc='http://127.0.0.1:1248' + maybe_broadcast+=(--unlocked) + else + submit_rpc="$rpc_url" + maybe_broadcast+=("${wallet_args[@]}") + fi +else + maybe_broadcast+=(call --trace -vvvv) + submit_rpc="$rpc_url" +fi +declare -r -a maybe_broadcast +declare -r submit_rpc + +cast "${maybe_broadcast[@]}" --from "$signer" --rpc-url "$submit_rpc" --gas-price $gas_price --gas-limit $gas_limit "${extra_flags[@]}" "${zk_tx_flags[@]}" "${deploy_args[@]}" + +if [[ ${BROADCAST-no} = [Yy]es ]] ; then + sleep 60 + + if [[ "$(cast code --rpc-url "$rpc_url" "$predicted")" == '0x' ]] ; then + echo 'Deployment did not produce code at the predicted address '"$predicted" >&2 + exit 1 + fi + + verify_contract "$constructor_args" "$predicted" src/deployer/SafeGuard.sol:"$guard_contract" 0.8.25 + + echo 'SafeGuard deployed to '"$predicted"' on chain "'"$chain_name"'"' >&2 +else + echo 'Did not broadcast; skipping verification' >&2 +fi From c9b1968de03a1b527205dee2e3f2baa686791bbb Mon Sep 17 00:00:00 2001 From: Lazaro Raul Date: Mon, 13 Jul 2026 20:29:18 +0200 Subject: [PATCH 02/11] review comments Co-Authored-By: Claude Opus 4.8 (1M context) --- sh/deploy_safeguard.sh | 78 +++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/sh/deploy_safeguard.sh b/sh/deploy_safeguard.sh index b76725af8..54c2c3932 100755 --- a/sh/deploy_safeguard.sh +++ b/sh/deploy_safeguard.sh @@ -130,23 +130,37 @@ if [[ ${safe:-null} == [nN][uU][lL][lL] ]] || [[ -z ${safe:-} ]] ; then exit 1 fi +declare safe_codehash +if [[ $era_vm = [Tt]rue ]] ; then + safe_codehash="$(cast call --rpc-url "$rpc_url" 0x0000000000000000000000000000000000008002 'getCodeHash(uint256)(bytes32)' "$safe")" +else + safe_codehash="$(cast keccak "$(cast code --rpc-url "$rpc_url" "$safe")")" +fi +declare -r safe_codehash +case "$safe_codehash" in + 0xaea7d4252f6245f301e540cfbee27d3a88de543af8e49c5c62405d5499fab7e5|\ + 0xb89c1b3bdf2cf8827818646bce9a8f6e372885f8c55e5c07acbd307cb133b000|\ + 0xd7d408ebcd99b2b70be43e20253d6d92a8ea8fab29bd3be7f55b10032331fb4c|\ + 0x0100004124426fb9ebb25e27d670c068e52f9ba631bd383279a188be47e3f86d|\ + 0x0100003b6cfa15bd7d1cae1c9c022074524d7785d34859ad0576d8fab4305d4f) ;; + *) + echo 'Upgrade Safe ('"$safe"') is not a recognized Safe proxy (codehash '"$safe_codehash"')' >&2 + exit 1 + ;; +esac + declare onchain_singleton onchain_singleton="$(cast call --rpc-url "$rpc_url" "$safe" 'masterCopy()(address)')" onchain_singleton="$(cast to-check-sum-address "$onchain_singleton")" declare -r onchain_singleton -declare -a candidate_factories candidate_variants -declare -A singleton_inithash=( - [ZeroExSettlerDeployerSafeGuardOnePointThree]=0x49f30800a6ac5996a48b80c47ff20f19f8728812498a2a7fe75a14864fab6438 - [ZeroExSettlerDeployerSafeGuardOnePointFourPointOne]=0x3555bd3ee95b1c6605c602740d71efaf200068e0395ccd701ac82ab8e42307bd - [ZeroExSettlerDeployerSafeGuardOnePointThreeEraVm]=0x0100080f935a1a562e892e1e71d9a0ca8cd349d19a413e0b7e7172c5e8c83ed1 - [ZeroExSettlerDeployerSafeGuardOnePointFourPointOneEraVm]=0x010006c19437ff25b448f038f7ea0a4c910e0ae9cd8e55f2d199b7916b72eb1e -) -if [[ $era_vm != [Ff]alse ]] ; then +declare -a candidate_factories +declare -A singleton_inithash +if [[ $era_vm = [Tt]rue ]] ; then candidate_factories=(0xaECDbB0a3B1C6D1Fe1755866e330D82eC81fD4FD) - candidate_variants=( - ZeroExSettlerDeployerSafeGuardOnePointThreeEraVm - ZeroExSettlerDeployerSafeGuardOnePointFourPointOneEraVm + singleton_inithash=( + [ZeroExSettlerDeployerSafeGuardOnePointThreeEraVm]=0x0100080f935a1a562e892e1e71d9a0ca8cd349d19a413e0b7e7172c5e8c83ed1 + [ZeroExSettlerDeployerSafeGuardOnePointFourPointOneEraVm]=0x010006c19437ff25b448f038f7ea0a4c910e0ae9cd8e55f2d199b7916b72eb1e ) else candidate_factories=( @@ -155,16 +169,16 @@ else 0xC0DEb853af168215879d284cc8B4d0A645fA9b0E # ERC-7955 0x0000000000000000000000000000000000000012 # EIP-7997 ) - candidate_variants=( - ZeroExSettlerDeployerSafeGuardOnePointThree - ZeroExSettlerDeployerSafeGuardOnePointFourPointOne + singleton_inithash=( + [ZeroExSettlerDeployerSafeGuardOnePointThree]=0x49f30800a6ac5996a48b80c47ff20f19f8728812498a2a7fe75a14864fab6438 + [ZeroExSettlerDeployerSafeGuardOnePointFourPointOne]=0x3555bd3ee95b1c6605c602740d71efaf200068e0395ccd701ac82ab8e42307bd ) fi function predict_create2 { declare _predict_out - _predict_out="$(cast keccak "$(cast concat-hex 0xff "$1" "$(cast hash-zero)" "$2")")" - cast to-check-sum-address "0x${_predict_out:26:40}" + _predict_out="$(cast compute-address "$1" --salt "$(cast hash-zero)" --init-code-hash "$2")" + echo "${_predict_out##* }" } function predict_create2_era_vm { @@ -174,17 +188,17 @@ function predict_create2_era_vm { } function predict_singleton { - if [[ $era_vm = [Ff]alse ]] ; then - predict_create2 "$1" "$2" - else + if [[ $era_vm = [Tt]rue ]] ; then predict_create2_era_vm "$1" "$2" "$(cast keccak '')" + else + predict_create2 "$1" "$2" fi } declare guard_contract='' factory='' declare _f _v for _f in "${candidate_factories[@]}" ; do - for _v in "${candidate_variants[@]}" ; do + for _v in "${!singleton_inithash[@]}" ; do if [[ "$(predict_singleton "$_f" "${singleton_inithash[$_v]}")" == "$onchain_singleton" ]] ; then guard_contract="$_v" factory="$_f" @@ -227,15 +241,7 @@ constructor_args="$(cast abi-encode 'constructor(address)' "$safe")" declare -r constructor_args declare predicted -if [[ $era_vm = [Ff]alse ]] ; then - forge clean - forge build src/deployer/SafeGuard.sol - declare guard_bytecode initcode - guard_bytecode="$(forge inspect src/deployer/SafeGuard.sol:"$guard_contract" bytecode)" - initcode="$(cast concat-hex "$guard_bytecode" "$constructor_args")" - declare -r guard_bytecode initcode - predicted="$(predict_create2 "$factory" "$(cast keccak "$initcode")")" -else +if [[ $era_vm = [Tt]rue ]] ; then # EraVM needs zkSync artifacts so we switch to the zksync aware foundry version foundryup-zksync -u foundry-zksync-v0.1.9 || true if [[ $(forge --version) != *14afc70e251c89b7e2af6e6ac02e9ac6f095b5cc* ]] ; then @@ -254,6 +260,14 @@ else guard_bytecode="0x$(jq -Mr '.bytecode.object' "$art")" declare -r bytecode_hash guard_bytecode predicted="$(predict_create2_era_vm "$factory" "$bytecode_hash" "$(cast keccak "$constructor_args")")" +else + forge clean + forge build src/deployer/SafeGuard.sol + declare guard_bytecode initcode + guard_bytecode="$(forge inspect src/deployer/SafeGuard.sol:"$guard_contract" bytecode)" + initcode="$(cast concat-hex "$guard_bytecode" "$constructor_args")" + declare -r guard_bytecode initcode + predicted="$(predict_create2 "$factory" "$(cast keccak "$initcode")")" fi declare -r predicted @@ -267,13 +281,13 @@ if [[ "$(cast code --rpc-url "$rpc_url" "$predicted")" != '0x' ]] ; then fi declare -a deploy_args zk_tx_flags=() -if [[ $era_vm = [Ff]alse ]] ; then - deploy_args=("$factory" "$(cast concat-hex "$(cast hash-zero)" "$initcode")") -else +if [[ $era_vm = [Tt]rue ]] ; then declare _create2_calldata _create2_calldata="$(cast calldata 'create2(bytes32,bytes32,bytes)' "$(cast hash-zero)" "$bytecode_hash" "$constructor_args")" deploy_args=("$factory" "$(cast concat-hex "$(cast hash-zero)" "$_create2_calldata")") zk_tx_flags=(--zksync --zk-factory-deps "$guard_bytecode") +else + deploy_args=("$factory" "$(cast concat-hex "$(cast hash-zero)" "$initcode")") fi declare -r -a deploy_args zk_tx_flags From fa33df087bbb00e82384b2da62cfea68bbdd5798 Mon Sep 17 00:00:00 2001 From: Lazaro Raul Date: Mon, 13 Jul 2026 20:29:27 +0200 Subject: [PATCH 03/11] fix zksync verification Co-Authored-By: Claude Opus 4.8 (1M context) --- sh/common.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sh/common.sh b/sh/common.sh index 6a08ffe7b..d343763dc 100644 --- a/sh/common.sh +++ b/sh/common.sh @@ -129,6 +129,11 @@ function verify_contract { _verify_extra_flags+=(--compiler-version "$1") shift fi + # EraVm artifacts must be verified through the zkSync flow; the flag threads into every verifier + # invocation below alongside --compiler-version. + if [[ $era_vm = [Tt]rue ]] ; then + _verify_extra_flags+=(--zksync) + fi declare -r -a _verify_extra_flags declare _verify_etherscanApi From a4ef3a95c139e2e6acf87c6d4f83675aed758ff5 Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Sat, 27 Jun 2026 12:04:11 +0200 Subject: [PATCH 04/11] Add `AGENTS.md` admonishment against code archaeology --- AGENTS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 6b9563a67..c97391b19 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -411,9 +411,15 @@ IERC20 internal constant ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeee ## Critical Reminders +Comments, notes, commit messages, PR descriptions, and docs must describe only +the current implementation unless historical context is required for present +correctness. Archaeology is forbidden. + ### DO NOT - Create documentation files unless explicitly requested +- Write notes, comments, docs, commit messages, or PR descriptions that describe historical evolution instead of the current system unless the history is required for current correctness +- Use comments to explain what used to be true, what changed, why something was once necessary, or that a workaround/kludge existed previously - Make up performance numbers or generic justifications for changes - Add features beyond what was asked (no over-engineering) - Modify the `_dispatch` copy/paste pattern without updating all locations @@ -422,6 +428,7 @@ IERC20 internal constant ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeee ### ALWAYS - Read relevant existing code before making changes +- Write comments as current-state documentation only - Check gas impact with `npm run diff:main` - Follow existing patterns in chain-specific code - Mark assembly blocks `memory-safe` when appropriate From 1dcb2f3b4e5ef7f4755093ead4dda465f90ae658 Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Fri, 8 May 2026 17:44:28 +0200 Subject: [PATCH 05/11] Require that AI agents add a `Co-Authored-By:` to each commit message --- AGENTS.md | 4 +++- CONTRIBUTING.md | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index c97391b19..eb3a3aaed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -359,9 +359,11 @@ Key settings in `foundry.toml`: # ✅ Stage specific files and review git add src/core/MyFeature.sol test/unit/MyFeatureTest.t.sol git diff --staged -git commit -m "Fix bug in MyFeature" +git commit -m "Fix bug in MyFeature" -m 'Co-Authored-By: AI Agent ' ``` +You MUST add a `Co-Authored-By:` line at the end of each commit message explicitly referencing yourself as the coauthor. Refer to yourself using the name that best describes your identity as a model, harness, or agentic system. If this is ambiguous, add multiple `Co-Authored-By:` lines. + ### Before Committing ```bash diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b85650b3c..01238c283 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,10 @@ that appears to be majority AI-authored without human review. Any contributor who opens majority AI-authored pull requests may be banned from opening pull requests against this repository. +All AI-assisted commits must include one or more `Co-Authored-By:` lines +attributing which AI models, harnesses, and/or agents were used in the +production of those commits. + # Documentation We do not accept pull requests that only modify documentation, correct spelling, From d5d39716190ac91f42ecb6ae7d22fe316aaa091d Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Fri, 8 May 2026 17:59:58 +0200 Subject: [PATCH 06/11] More AGENTS.md instructions for coauthorship --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index eb3a3aaed..7c52cc48b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -362,7 +362,7 @@ git diff --staged git commit -m "Fix bug in MyFeature" -m 'Co-Authored-By: AI Agent ' ``` -You MUST add a `Co-Authored-By:` line at the end of each commit message explicitly referencing yourself as the coauthor. Refer to yourself using the name that best describes your identity as a model, harness, or agentic system. If this is ambiguous, add multiple `Co-Authored-By:` lines. +You MUST add a `Co-Authored-By:` line at the end of each commit message explicitly referencing yourself as the coauthor. Refer to yourself using the name that best describes your identity as a model, harness, or agentic system, and use an email address that is reflective of your creator(s). If this is ambiguous, add multiple `Co-Authored-By:` lines. ### Before Committing From e2e6931c5710109d57fe4ef9f602c31a7bc30269 Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Sat, 27 Jun 2026 12:06:20 +0200 Subject: [PATCH 07/11] Add repository guidance forbidding opaque outside identifiers in work product. Co-Authored-By: Codex --- AGENTS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 7c52cc48b..af4dfe98d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -417,10 +417,18 @@ Comments, notes, commit messages, PR descriptions, and docs must describe only the current implementation unless historical context is required for present correctness. Archaeology is forbidden. +Work product must not reference opaque external planning material. Code, +comments, docs, commit messages, PR descriptions, and other in-repo literature +must not include outside task identifiers, plan-document labels, milestone +names, tracking IDs, TODO placeholders, or similar references unless the +referenced artifact is committed in this repository and the reference is +required for current correctness. + ### DO NOT - Create documentation files unless explicitly requested - Write notes, comments, docs, commit messages, or PR descriptions that describe historical evolution instead of the current system unless the history is required for current correctness +- Write comments, code, docs, commit messages, PR descriptions, or other in-repo literature that cite opaque outside task identifiers, plan labels, milestones, tracking IDs, TODO placeholders, or issue labels - Use comments to explain what used to be true, what changed, why something was once necessary, or that a workaround/kludge existed previously - Make up performance numbers or generic justifications for changes - Add features beyond what was asked (no over-engineering) From 64a5195571f3c28d476a7ca6ad4ba2596398134b Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Fri, 26 Jun 2026 18:17:40 +0200 Subject: [PATCH 08/11] Add commenting-discipline dicta to AGENTS.md State, under Critical Reminders, that comments belong only where the code cannot speak for itself; that a comment explains only its associated code, never the chat/task/plan/diff; and that comments address behavior and intent rather than identifiers, justifying only the obtuse or non-idiomatic. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index af4dfe98d..aadecf33a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -424,6 +424,20 @@ names, tracking IDs, TODO placeholders, or similar references unless the referenced artifact is committed in this repository and the reference is required for current correctness. +### Commenting Discipline + +Comments must be added only where the code cannot speak for itself, and code +must be written so that this is rare. Delete any comment that restates what the +code does. + +Comments must explain only their associated code. A comment must never explain +the chat, the task, the plan, or the changes from a previous revision. + +Comments must refer to behavior and intent, never to function or variable names. +A comment must explain _what_ a function does only when, by external constraints +or the desire to optimize, that function is forced into an obtuse, arcane, or +non-idiomatic structure in order to achieve its goal. + ### DO NOT - Create documentation files unless explicitly requested From f19bd093e1f08aa96722675bb8f487e873f5ce7f Mon Sep 17 00:00:00 2001 From: Duncan Townsend Date: Fri, 26 Jun 2026 19:53:21 +0200 Subject: [PATCH 09/11] Add admonishment against `-f` and `--force` CLI flags --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index aadecf33a..cc854afb6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -448,6 +448,7 @@ non-idiomatic structure in order to achieve its goal. - Add features beyond what was asked (no over-engineering) - Modify the `_dispatch` copy/paste pattern without updating all locations - Create standalone test files; use the project's test infrastructure +- Use the `-f` or the `--force` flag to _**ANY**_ tool or utility, _EVER_. ### ALWAYS From b4b48312cd589d7e1f0bdefaa2b4b7d5d0779a21 Mon Sep 17 00:00:00 2001 From: Lazaro Raul Date: Fri, 17 Jul 2026 18:17:41 +0200 Subject: [PATCH 10/11] review comments Co-Authored-By: Claude Fable 5 --- .gitignore | 1 + foundry.toml | 1 + sh/deploy_safeguard.sh | 64 ++++++++++++++++++++---------------------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index 11c7e59b7..d30368b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /cache/ /out/ /src/flat/ +/zkout/ # Ignores development broadcast logs !/broadcast diff --git a/foundry.toml b/foundry.toml index 876b70947..95176be1b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,3 +1,4 @@ +[profile.""] # added for foundry-zksync to detect profile.default [profile.default] unchecked_cheatcode_artifacts = true ignored_error_codes = [2394, 2424, 4591, 5159, 9170] diff --git a/sh/deploy_safeguard.sh b/sh/deploy_safeguard.sh index 54c2c3932..4d73c1a7e 100755 --- a/sh/deploy_safeguard.sh +++ b/sh/deploy_safeguard.sh @@ -125,7 +125,7 @@ declare safe safe="$(get_config governance.upgradeSafe)" declare -r safe -if [[ ${safe:-null} == [nN][uU][lL][lL] ]] || [[ -z ${safe:-} ]] ; then +if [[ ${safe:-null} == [nN][uU][lL][lL] ]] ; then echo 'governance.upgradeSafe is missing from chain_config.json for chain "'"$chain_name"'"' >&2 exit 1 fi @@ -133,25 +133,30 @@ fi declare safe_codehash if [[ $era_vm = [Tt]rue ]] ; then safe_codehash="$(cast call --rpc-url "$rpc_url" 0x0000000000000000000000000000000000008002 'getCodeHash(uint256)(bytes32)' "$safe")" + case "$safe_codehash" in + 0x0100004124426fb9ebb25e27d670c068e52f9ba631bd383279a188be47e3f86d|\ + 0x0100003b6cfa15bd7d1cae1c9c022074524d7785d34859ad0576d8fab4305d4f) ;; + *) + echo 'Upgrade Safe ('"$safe"') is not a recognized EraVM Safe proxy (codehash '"$safe_codehash"')' >&2 + exit 1 + ;; + esac else safe_codehash="$(cast keccak "$(cast code --rpc-url "$rpc_url" "$safe")")" + case "$safe_codehash" in + 0xaea7d4252f6245f301e540cfbee27d3a88de543af8e49c5c62405d5499fab7e5|\ + 0xb89c1b3bdf2cf8827818646bce9a8f6e372885f8c55e5c07acbd307cb133b000|\ + 0xd7d408ebcd99b2b70be43e20253d6d92a8ea8fab29bd3be7f55b10032331fb4c) ;; + *) + echo 'Upgrade Safe ('"$safe"') is not a recognized Safe proxy (codehash '"$safe_codehash"')' >&2 + exit 1 + ;; + esac fi declare -r safe_codehash -case "$safe_codehash" in - 0xaea7d4252f6245f301e540cfbee27d3a88de543af8e49c5c62405d5499fab7e5|\ - 0xb89c1b3bdf2cf8827818646bce9a8f6e372885f8c55e5c07acbd307cb133b000|\ - 0xd7d408ebcd99b2b70be43e20253d6d92a8ea8fab29bd3be7f55b10032331fb4c|\ - 0x0100004124426fb9ebb25e27d670c068e52f9ba631bd383279a188be47e3f86d|\ - 0x0100003b6cfa15bd7d1cae1c9c022074524d7785d34859ad0576d8fab4305d4f) ;; - *) - echo 'Upgrade Safe ('"$safe"') is not a recognized Safe proxy (codehash '"$safe_codehash"')' >&2 - exit 1 - ;; -esac declare onchain_singleton onchain_singleton="$(cast call --rpc-url "$rpc_url" "$safe" 'masterCopy()(address)')" -onchain_singleton="$(cast to-check-sum-address "$onchain_singleton")" declare -r onchain_singleton declare -a candidate_factories @@ -182,24 +187,25 @@ function predict_create2 { } function predict_create2_era_vm { + if (( ${#1} != 42 || ${#2} != 66 || ${#3} != 66 )) ; then + echo 'predict_create2_era_vm: argument has the wrong width' >&2 + return 1 + fi declare _predict_out _predict_out="$(cast keccak "$(cast concat-hex "$(cast keccak zksyncCreate2)" "$(cast to-uint256 "$1")" "$(cast hash-zero)" "$2" "$3")")" cast to-check-sum-address "0x${_predict_out:26:40}" } -function predict_singleton { - if [[ $era_vm = [Tt]rue ]] ; then - predict_create2_era_vm "$1" "$2" "$(cast keccak '')" - else - predict_create2 "$1" "$2" - fi -} - declare guard_contract='' factory='' -declare _f _v +declare _f _v _predicted_singleton for _f in "${candidate_factories[@]}" ; do for _v in "${!singleton_inithash[@]}" ; do - if [[ "$(predict_singleton "$_f" "${singleton_inithash[$_v]}")" == "$onchain_singleton" ]] ; then + if [[ $era_vm = [Tt]rue ]] ; then + _predicted_singleton="$(predict_create2_era_vm "$_f" "${singleton_inithash[$_v]}" "$(cast keccak 0x)")" + else + _predicted_singleton="$(predict_create2 "$_f" "${singleton_inithash[$_v]}")" + fi + if [[ $_predicted_singleton == "$onchain_singleton" ]] ; then guard_contract="$_v" factory="$_f" break 2 @@ -220,7 +226,7 @@ if [[ $guard_contract == *OnePointThree* ]] ; then exit 1 fi -if [[ "$(cast code --rpc-url "$rpc_url" "$factory")" == '0x' ]] ; then +if [[ $(cast code --rpc-url "$rpc_url" "$factory") == '0x' ]] ; then echo 'The CREATE2 factory ('"$factory"') is not deployed on chain "'"$chain_name"'"' >&2 exit 1 fi @@ -249,9 +255,6 @@ if [[ $era_vm = [Tt]rue ]] ; then echo 'Run `foundryup-zksync -i foundry-zksync-v0.1.9`' >&2 exit 1 fi - # foundry-zksync errors on an empty profile, so, we set default if empty - export FOUNDRY_PROFILE="${FOUNDRY_PROFILE:-default}" - forge clean forge build --zksync --zk-compile src/deployer/SafeGuard.sol declare art="$project_root/zkout/SafeGuard.sol/$guard_contract.json" @@ -275,7 +278,7 @@ echo 'SafeGuard variant : '"$guard_contract" >&2 echo 'Protected Safe : '"$safe" >&2 echo 'Predicted address : '"$predicted" >&2 -if [[ "$(cast code --rpc-url "$rpc_url" "$predicted")" != '0x' ]] ; then +if [[ $(cast code --rpc-url "$rpc_url" "$predicted") != '0x' ]] ; then echo 'SafeGuard already deployed at '"$predicted"' on chain "'"$chain_name"'". Nothing to do.' >&2 exit 0 fi @@ -325,11 +328,6 @@ cast "${maybe_broadcast[@]}" --from "$signer" --rpc-url "$submit_rpc" --gas-pric if [[ ${BROADCAST-no} = [Yy]es ]] ; then sleep 60 - if [[ "$(cast code --rpc-url "$rpc_url" "$predicted")" == '0x' ]] ; then - echo 'Deployment did not produce code at the predicted address '"$predicted" >&2 - exit 1 - fi - verify_contract "$constructor_args" "$predicted" src/deployer/SafeGuard.sol:"$guard_contract" 0.8.25 echo 'SafeGuard deployed to '"$predicted"' on chain "'"$chain_name"'"' >&2 From c4ac321a1d99d7f077f07d836cadfaff730bf5a0 Mon Sep 17 00:00:00 2001 From: Lazaro Raul Date: Thu, 23 Jul 2026 15:20:58 +0200 Subject: [PATCH 11/11] chore: review comments --- sh/deploy_safeguard.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sh/deploy_safeguard.sh b/sh/deploy_safeguard.sh index 4d73c1a7e..2504af3a6 100755 --- a/sh/deploy_safeguard.sh +++ b/sh/deploy_safeguard.sh @@ -132,6 +132,9 @@ fi declare safe_codehash if [[ $era_vm = [Tt]rue ]] ; then + # On EraVM an account's codehash is the versioned bytecode hash recorded by the AccountCodeStorage + # system contract, not the keccak256 of its code. + # https://docs.zksync.io/zksync-protocol/contracts/system-contracts#accountcodestorage safe_codehash="$(cast call --rpc-url "$rpc_url" 0x0000000000000000000000000000000000008002 'getCodeHash(uint256)(bytes32)' "$safe")" case "$safe_codehash" in 0x0100004124426fb9ebb25e27d670c068e52f9ba631bd383279a188be47e3f86d|\ @@ -181,9 +184,7 @@ else fi function predict_create2 { - declare _predict_out - _predict_out="$(cast compute-address "$1" --salt "$(cast hash-zero)" --init-code-hash "$2")" - echo "${_predict_out##* }" + cast create2 --deployer "$1" --salt "$(cast hash-zero)" --init-code-hash "$2" } function predict_create2_era_vm {