Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e4d195e
WIP: Add Safe{Wallet} v1.4.1 config to `chain_config.json`
duncancmt Jun 26, 2026
b2d19c3
WIP: Add Safe{Wallet} v1.4.1 config to `chain_config.json`
duncancmt Jun 26, 2026
42632b0
Finished: Add Safe{Wallet} v1.4.1 config to `chain_config.json`
duncancmt Jun 26, 2026
6ff0329
Merge branch 'master' into dcmt/safe-v1.4.1
duncancmt Aug 13, 2026
b3a34cf
Centralize shell error handling
duncancmt Jun 27, 2026
59cef16
Clean up after `cherry-pick`
duncancmt Aug 13, 2026
f98ce47
Merge branch 'master' into dcmt/safe-v1.4.1
duncancmt Aug 13, 2026
89e92dc
WIP: update scripts for new Safe v1.4.1 `chain_config.json` schema
duncancmt Aug 13, 2026
633a62a
Finished: update scripts for new Safe v1.4.1 `chain_config.json` schema
duncancmt Aug 13, 2026
5940cbc
Pedantry
duncancmt Aug 13, 2026
a61f22f
Deploy and install the SafeGuard atomically with the upgrade Safe han…
duncancmt Aug 13, 2026
6da72ef
Load the guard bytecode with `vm.getCode` and set the timelock delay
duncancmt Aug 13, 2026
1053ccc
Pedantry
duncancmt Aug 13, 2026
5847a6b
Check that the Bash and Solidity Safe Guard address derivations match
duncancmt Aug 13, 2026
23d6d82
Fix new-chain deployment and Safe tooling defects
duncancmt Aug 13, 2026
0e13d0d
Recover the EXIT trap body verbatim in register_exit_cleanup
duncancmt Aug 14, 2026
0493bd8
Harden register_exit_cleanup against nounset, subshells, IFS, and com…
duncancmt Aug 14, 2026
446b39d
Clean up slop
duncancmt Aug 14, 2026
57cf95c
Homogenize null/unset handling in Bash scripts
duncancmt Aug 14, 2026
eedf1a7
Fix Foundry verification and gas policy selection
duncancmt Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
484 changes: 374 additions & 110 deletions chain_config.json

Large diffs are not rendered by default.

269 changes: 191 additions & 78 deletions script/DeploySafes.s.sol

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions script/SafeCode.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ struct SafeBytecodes {
bytes singletonCode;
bytes fallbackCode;
bytes multicallCode;
bytes singletonV141Code;
bytes fallbackV141Code;
bytes multicallV141Code;
bytes migrationCode;
bytes proxyCode;
bytes proxyCodeEraVm;
}
Expand All @@ -21,6 +25,14 @@ function load(SafeBytecodes memory self, VmSafe vm) view {
assert(keccak256(self.fallbackCode) == 0x03e69f7ce809e81687c69b19a7d7cca45b6d551ffdec73d9bb87178476de1abf);
self.multicallCode = vm.readFileBinary("script/multicall.bin");
assert(keccak256(self.multicallCode) == 0xa9865ac2d9c7a1591619b188c4d88167b50df6cc0c5327fcbd1c8c75f7c066ad);
self.singletonV141Code = vm.readFileBinary("script/singleton_v141.bin");
assert(keccak256(self.singletonV141Code) == 0xb1f926978a0f44a2c0ec8fe822418ae969bd8c3f18d61e5103100339894f81ff);
self.fallbackV141Code = vm.readFileBinary("script/fallback_v141.bin");
assert(keccak256(self.fallbackV141Code) == 0x7c6007a5d711cea8dfd5d91f5940ec29c7f200fe511eb1fc1397b367af3c42f9);
self.multicallV141Code = vm.readFileBinary("script/multicall_v141.bin");
assert(keccak256(self.multicallV141Code) == 0xecd5bd14a08c5d2122379900b2f272bdf107a7e92423c10dd5fe3254386c9939);
self.migrationCode = vm.readFileBinary("script/migration.bin");
assert(keccak256(self.migrationCode) == 0xc00d7921460cd5a05393e7772e634bd7d212f356356aa3a77f0120a9b8e25e99);
self.proxyCode = vm.readFileBinary("script/proxy.bin");
assert(keccak256(self.proxyCode) == 0xb89c1b3bdf2cf8827818646bce9a8f6e372885f8c55e5c07acbd307cb133b000);
self.proxyCodeEraVm = vm.readFileBinary("script/proxy_eravm.bin");
Expand Down
1 change: 1 addition & 0 deletions script/SafeConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ library SafeConfig {
}

uint256 internal constant upgradeSafeThreshold = 2;
uint24 internal constant upgradeSafeTimelockDelay = 5 days;

function getUpgradeSafeSigners() internal view returns (address[] memory) {
address[] memory result = new address[](4);
Expand Down
Binary file added script/fallback_v141.bin
Binary file not shown.
Binary file added script/migration.bin
Binary file not shown.
Binary file added script/multicall_v141.bin
Binary file not shown.
Binary file added script/singleton_v141.bin
Binary file not shown.
184 changes: 145 additions & 39 deletions sh/common.sh
Original file line number Diff line number Diff line change
@@ -1,54 +1,114 @@
function die {
if (( $# != 0 )) ; then
printf '%s\n' "$@" >&2
fi
exit 1
}

. "$project_root"/sh/common_bash_version_check.sh

function register_exit_cleanup {
# In a subshell, `trap -p` reports the parent's traps, so composing here would rerun the
# parent's cleanups at subshell exit.
if (( BASH_SUBSHELL != 0 )) ; then
die '`register_exit_cleanup` must not be called in a subshell'
fi

declare -r _register_exit_cleanup="$1"
shift

# `trap -p` emits the trap body as a single shell-quoted word, so `eval`ing its output inside an
# array assignment recovers the body verbatim (embedded quotes included) without executing any
# of it.
declare -r IFS=' '
declare -a _register_exit_trap
eval "_register_exit_trap=( $(trap -p EXIT) )"
declare -r -a _register_exit_trap

if (( ${#_register_exit_trap[@]} == 0 )) || [[ ${_register_exit_trap[*]} = 'trap -- - EXIT' ]] ; then
trap 'trap - EXIT; set +eu; '"$_register_exit_cleanup" EXIT
return
fi

if (( ${#_register_exit_trap[@]} != 4 )) || [[ ${_register_exit_trap[2]} != 'trap - EXIT; set +eu; '* ]] ; then
die '`trap EXIT` cleanup malformed; cannot add a new cleanup'
fi

# Cleanups run in the reverse of registration order and are newline-joined so that a cleanup
# ending in a comment or `&` cannot swallow the cleanups registered before it.
trap 'trap - EXIT; set +eu; '"$_register_exit_cleanup"$'\n'"${_register_exit_trap[2]#trap - EXIT; set +eu; }" EXIT
}

if ! hash forge &>/dev/null ; then
echo 'foundry is not installed' >&2
exit 1
die 'foundry is not installed'
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
echo 'This doesn'"'"'t work on old versions of `foundryup`' >&2
echo 'You have to `curl -L https://foundry.paradigm.xyz | bash` to update `foundryup`' >&2
exit 1
if ! hash cast &>/dev/null ; then
die 'cast is not installed'
fi

declare foundry_version
foundry_version="$(forge --version)"
declare -r foundry_version

declare cast_version
cast_version="$(cast --version)"
declare -r cast_version

declare foundry_flavor
if [[ $foundry_version == *foundry-zksync* ]] ; then
foundry_flavor=zkfoundry
else
foundry_flavor=vanilla
fi
declare -r foundry_flavor

declare cast_flavor
if [[ $cast_version == *foundry-zksync* ]] ; then
cast_flavor=zkfoundry
else
cast_flavor=vanilla
fi
declare -r cast_flavor

if [[ $cast_flavor != "$foundry_flavor" ]] ; then
die '`forge` and `cast` are from different Foundry toolchains' \
'forge: '"$foundry_version" \
'cast: '"$cast_version"
fi

declare -r vanilla_foundry_version=b0a9dd9ceda36f63e2326ce530c10e6916f4b8a2
declare -r zk_foundry_version=foundry-zksync-v0.1.9

if ! hash curl &>/dev/null ; then
echo 'curl is not installed' >&2
exit 1
die 'curl is not installed'
fi

if ! hash jq &>/dev/null ; then
echo 'jq is not installed' >&2
exit 1
die 'jq is not installed'
fi

if [ ! -f "$project_root"/api_secrets.json ] ; then
echo 'api_secrets.json is missing' >&2
exit 1
die 'api_secrets.json is missing'
fi

declare api_secrets_permissions
api_secrets_permissions="$(ls -l "$project_root"/api_secrets.json)"
api_secrets_permissions="${api_secrets_permissions::10}"
declare -r api_secrets_permissions
if [[ $api_secrets_permissions != '-rw-------' ]] ; then
echo 'api_secrets.json permissions too lax' >&2
echo 'run: chmod 600 api_secrets.json' >&2
exit 1
die 'api_secrets.json permissions too lax' \
'run: chmod 600 api_secrets.json'
fi

if (( $# == 0 )) ; then
echo 'chain_name argument is missing' >&2
exit 1
die 'chain_name argument is missing'
fi
declare -r chain_name="$1"
shift

if [[ $(jq -Mr .'"'"$chain_name"'"' < "$project_root"/api_secrets.json) == 'null' ]] ; then
echo "$chain_name"' is missing from api_secrets.json' >&2
exit 1
if [[ $(jq -Mr .'"'"$chain_name"'"' < "$project_root"/api_secrets.json) = [nN][uU][lL][lL] ]] ; then
die "$chain_name"' is missing from api_secrets.json'
fi

function get_api_secret {
Expand All @@ -59,31 +119,81 @@ function get_config {
jq -Mr .'"'"$chain_name"'"'."$1" < "$project_root"/chain_config.json
}

function get_config_strict {
declare _get_config_strict_result
_get_config_strict_result="$(get_config "$1")"
declare -r _get_config_strict_result
if [[ ${_get_config_strict_result:-null} = [nN][uU][lL][lL] ]] ; then
die 'Config key '"$1"' is missing for chain '"$chain_name"
fi
echo "$_get_config_strict_result"
}

if [[ ${IGNORE_HARDFORK-no} != [Yy]es ]] ; then
if [[ $(get_config hardfork.shanghai) != [Tt]rue ]] ; then
echo 'You are on the wrong branch (switch to `fork/london`)' >&2
exit 1
die 'You are on the wrong branch (switch to `fork/london`)'
fi

if [[ $(get_config hardfork.cancun) != [Tt]rue ]] ; then
echo 'You are on the wrong branch (switch to `fork/shanghai`)' >&2
exit 1
die 'You are on the wrong branch (switch to `fork/shanghai`)'
fi

if [[ $(get_config hardfork.osaka) != [Tt]rue ]] ; then
echo 'You are on the wrong branch (switch to `fork/cancun`)' >&2
exit 1
die 'You are on the wrong branch (switch to `fork/cancun`)'
fi
fi

declare era_vm
era_vm="$(get_config hardfork.eraVm)"
declare -r era_vm

if [[ $foundry_flavor = zkfoundry ]] && [[ $era_vm = [Ff]alse ]] ; then
die 'zkFoundry must not be used on non-EraVM chains' \
'Run this script with vanilla Foundry v1.5.1 first in PATH'
fi

if [[ $foundry_flavor = vanilla ]] ; then
if [[ $foundry_version != *"$vanilla_foundry_version"* ]] ; then
die 'Wrong vanilla Foundry version installed' \
'Run `foundryup -i v1.5.1`' \
'This doesn'"'"'t work on old versions of `foundryup`' \
'You have to `curl -L https://foundry.paradigm.xyz | bash` to update `foundryup`'
fi
if [[ $cast_version != *"$vanilla_foundry_version"* ]] ; then
die 'Wrong vanilla cast version installed' \
'Run `foundryup -i v1.5.1`'
fi
else
if [[ $foundry_version != *"$zk_foundry_version"* ]] ; then
die 'Wrong zkFoundry version installed' \
'Run `foundryup-zksync -i '"$zk_foundry_version"'`'
fi
if [[ $cast_version != *"$zk_foundry_version"* ]] ; then
die 'Wrong zkFoundry cast version installed' \
'Run `foundryup-zksync -i '"$zk_foundry_version"'`'
fi
fi

function require_vanilla_foundry {
if [[ $foundry_flavor != vanilla ]] ; then
die 'This operation requires vanilla Foundry, but `forge` is the zkFoundry fork' \
'Run `foundryup -i v1.5.1` and make sure it is first in PATH'
fi
}

function require_zk_foundry {
if [[ $era_vm = [Ff]alse ]] ; then
die 'This operation requested zkFoundry on a non-EraVM chain'
fi
if [[ $foundry_flavor != zkfoundry ]] ; then
die 'This operation requires the zkFoundry fork for EraVM bytecode' \
'Install it with `foundryup-zksync -i '"$zk_foundry_version"'` and make sure it is first in PATH'
fi
}

if [[ $era_vm != [Ff]alse ]] ; then
if (( $(get_config gasMultiplierPercent) < 500 )) ; then
echo 'EraVm chains must set a gas multiplier of 5x or more' >&2
exit 1
die 'EraVm chains must set a gas multiplier of 5x or more'
fi
fi

Expand All @@ -99,18 +209,16 @@ declare rpc_url
rpc_url="$(get_api_secret rpcUrl)"
declare -r rpc_url

if [[ ${rpc_url:-unset} = 'unset' ]] || [[ $rpc_url = 'null' ]] ; then
echo '`rpcUrl` is unset in `api_secrets.json` for chain "'"$chain_name"'"' >&2
exit 1
if [[ ${rpc_url:-null} = [nN][uU][lL][lL] ]] ; then
die '`rpcUrl` is unset in `api_secrets.json` for chain "'"$chain_name"'"'
fi

declare -i rpc_chainid
rpc_chainid="$(cast chain-id --rpc-url "$rpc_url")"
declare -r -i rpc_chainid

if (( rpc_chainid != chainid )) ; then
echo 'Your RPC thinks you are on chain '$rpc_chainid'. You probably have the wrong RPC.' >&2
exit 1
die 'Your RPC thinks you are on chain '$rpc_chainid'. You probably have the wrong RPC.'
fi

declare -a extra_flags
Expand All @@ -129,9 +237,7 @@ 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
if [[ $foundry_flavor == zkfoundry ]] ; then
_verify_extra_flags+=(--zksync)
fi
declare -r -a _verify_extra_flags
Expand Down
21 changes: 4 additions & 17 deletions sh/common_deploy_bridge_settler.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,9 @@ flat_bridge_settler_source="$project_root"/src/flat/"$chain_display_name"BridgeS
declare -r flat_bridge_settler_source

if [[ "${bridge_settler_skip_clean-no}" == [Yy]es ]] ; then
declare swap_settler_trap
swap_settler_trap="$(trap -p EXIT)"

if [[ $swap_settler_trap != "trap -- 'trap - EXIT; set +e; "* ]] || [[ $swap_settler_trap != *"' EXIT" ]] ; then
echo '`trap EXIT` cleanup malformed; cannot add a new cleanup' >&2
exit 1
fi
swap_settler_trap="${swap_settler_trap%\' EXIT}"
swap_settler_trap="${swap_settler_trap#trap -- \'trap - EXIT; set +e; }"
trap 'trap - EXIT; set +e; '"$swap_settler_trap"'; rm -f '"$(_escape "$flat_bridge_settler_source")" EXIT

unset -v swap_settler_trap
register_exit_cleanup 'rm -f '"$(printf '%q' "$flat_bridge_settler_source")"
else
trap 'trap - EXIT; set +e; rm -f '"$(_escape "$flat_bridge_settler_source")" EXIT
trap 'trap - EXIT; set +eu; rm -f '"$(printf '%q' "$flat_bridge_settler_source")" EXIT
fi

forge flatten -o "$flat_bridge_settler_source" src/chains/"$chain_display_name"/BridgeSettler.sol >/dev/null
Expand All @@ -31,17 +20,15 @@ bridge_settler_artifact="$project_root"/out/"$chain_display_name"BridgeSettlerFl
declare -r bridge_settler_artifact

if [ ! -f "$bridge_settler_artifact" ] ; then
echo 'Cannot find '"$chain_display_name"'BridgeSettler.json' >&2
exit 1
die 'Cannot find '"$chain_display_name"'BridgeSettler.json'
fi

if [[ -z "${constructor_args-}" ]] ; then
declare constructor_args
constructor_args="$(cast abi-encode 'constructor(bytes20)' 0x"$(git rev-parse HEAD)")"
declare -r constructor_args
elif [[ "$constructor_args" != "$(cast abi-encode 'constructor(bytes20)' 0x"$(git rev-parse HEAD)")" ]] ; then
echo 'Malformed constructor arguments' >&2
exit 1
die 'Malformed constructor arguments'
fi

declare bridge_settler_initcode
Expand Down
12 changes: 5 additions & 7 deletions sh/common_deploy_settler.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ forge clean
declare flat_taker_source
flat_taker_source="$project_root"/src/flat/"$chain_display_name"TakerSubmittedFlat.sol
declare -r flat_taker_source
trap 'trap - EXIT; set +e; rm -f '"$(_escape "$flat_taker_source")" EXIT
register_exit_cleanup 'rm -f '"$(printf '%q' "$flat_taker_source")"
forge flatten -o "$flat_taker_source" src/chains/"$chain_display_name"/TakerSubmitted.sol >/dev/null
FOUNDRY_SOLC_VERSION=0.8.34 forge build "$flat_taker_source"

declare flat_metatx_source
flat_metatx_source="$project_root"/src/flat/"$chain_display_name"MetaTxnFlat.sol
declare -r flat_metatx_source
trap 'trap - EXIT; set +e; rm -f '"$(_escape "$flat_taker_source")"' '"$(_escape "$flat_metatx_source")" EXIT
register_exit_cleanup 'rm -f '"$(printf '%q' "$flat_metatx_source")"
forge flatten -o "$flat_metatx_source" src/chains/"$chain_display_name"/MetaTxn.sol >/dev/null
FOUNDRY_SOLC_VERSION=0.8.34 forge build "$flat_metatx_source"

declare flat_intent_source
flat_intent_source="$project_root"/src/flat/"$chain_display_name"IntentFlat.sol
declare -r flat_intent_source
trap 'trap - EXIT; set +e; rm -f '"$(_escape "$flat_taker_source")"' '"$(_escape "$flat_metatx_source")"' '"$(_escape "$flat_intent_source")" EXIT
register_exit_cleanup 'rm -f '"$(printf '%q' "$flat_intent_source")"
forge flatten -o "$flat_intent_source" src/chains/"$chain_display_name"/Intent.sol >/dev/null
FOUNDRY_SOLC_VERSION=0.8.34 forge build "$flat_intent_source"

Expand All @@ -33,8 +33,7 @@ intent_artifact="$project_root"/out/"$chain_display_name"IntentFlat.sol/"$chain_
declare -r intent_artifact

if [ ! -f "$taker_artifact" ] || [ ! -f "$metatx_artifact" ] || [ ! -f "$intent_artifact" ] ; then
echo 'Cannot find '"$chain_display_name"'Settler.json' >&2
exit 1
die 'Cannot find '"$chain_display_name"'Settler.json'
fi

declare constructor_args
Expand Down Expand Up @@ -70,8 +69,7 @@ declare -r deploy_intent_calldata
declare next_intent_settler_address
if [[ -z "${deployer_address-}" ]] ; then
if [[ $(get_config hardfork.shanghai) != [Tt]rue ]] ; then
echo 'NO NEW LONDON CHAINS!!!' >&2
exit 1
die 'NO NEW LONDON CHAINS!!!'
fi
next_intent_settler_address="$(cast keccak "$(cast concat-hex 0xff 0x00000000000004533Fe15556B1E086BB1A72cEae "$(cast to-uint256 "$(bc <<<'obase=16;4*2^128+'"$chainid"'*2^64+1')")" 0x3bf3f97f0be1e2c00023033eefeb4fc062ac552ff36778b17060d90b6764902f)")"
next_intent_settler_address="${next_intent_settler_address:26:40}"
Expand Down
Loading