Skip to content

Add Safe{Wallet} v1.4.1 config to chain_config.json - #590

Open
duncancmt wants to merge 20 commits into
masterfrom
dcmt/safe-v1.4.1
Open

Add Safe{Wallet} v1.4.1 config to chain_config.json#590
duncancmt wants to merge 20 commits into
masterfrom
dcmt/safe-v1.4.1

Conversation

@duncancmt

Copy link
Copy Markdown
Collaborator

No description provided.

@duncancmt
duncancmt requested a review from e1Ru1o June 26, 2026 09:44
@duncancmt duncancmt self-assigned this Jun 26, 2026
@immunefi-magnus

Copy link
Copy Markdown

🛡️ Immunefi PR Reviews

We noticed that your project isn't set up for automatic code reviews. If you'd like this PR reviewed by the Immunefi team, you can request it manually using the link below:

🔗 Send this PR in for review

Once submitted, we'll take care of assigning a reviewer and follow up here.

duncancmt and others added 17 commits August 13, 2026 12:05
Co-Authored-By: OpenAI Codex <codex@openai.com>
All consumers read the `safe` contract addresses through the nested
version keys; `get_config_strict` dies on missing keys. `chain_config.json`
gains `safe["v1.3.0"].migration["v1.4.1"]` (the official SafeMigration
contract, verified by codehash on all 22 chains) and the `robinhood` entry
is converted to the nested schema.

`common_safe.sh` selects the multiCall by calling `VERSION()` on the
governance Safe. `DeploySafes` deploys the upgrade Safe as v1.3.0 (keeping
its cross-chain address) and immediately migrates it to the v1.4.1
singleton and fallback via delegatecall to `migrateL2WithFallbackHandler()`
because the SafeGuard is only compatible with v1.4.1; its setup batch runs
through the v1.4.1 MultiSendCallOnly. The EraVM path etches the v1.4.1 EVM
bytecodes and corrects the simulated storage to match the on-chain outcome.
`deploy_safe_infra.sh` also deploys the v1.4.1 contract set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dover

On EVM chains, `DeploySafes` appends two sub-calls to the upgrade Safe
setup batch: a call to the toehold that creates the v1.4.1 guard with the
zero salt, and a self-call to `setGuard`. The guard, its installation, and
the owner handover land in one Safe transaction. The sub-calls must follow
the owner handover because the Safe probes the guard with
`supportsInterface` during `setGuard`, and the guard consents only when the
Safe already runs the v1.4.1 singleton and holds its final owners and
threshold. The script checks the toehold codehash before and the guard code
and guard storage slot after.

EraVm skips the guard: its bytecode can only be published through the
factory-deps field of a native EraVm transaction, which cannot ride inside
a Safe transaction. `deploy_new_chain.sh` ends with a loud demand that the
operator deploy the guard and queue `setGuard` immediately.

`deploy_new_chain.sh` builds the guard with the pinned settings (London,
200 optimizer runs), passes the toehold and creation bytecode to the
Foundry script, source-verifies the guard after broadcast, and records the
address as `governance.timelock` in the config suggestion. On chains with
the standard upgrade Safe the guard lands at
0xecFC0A72Ec520fcEdCB1D7e663D0e728D76344F8. The forge script invocation
gains `--gas-limit 100000000`; the whole-script simulation no longer fits
in the default frame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`DeploySafes` reads the guard artifact with `vm.getCode`, like the Settler
flats, instead of taking the creation bytecode as a `run()` argument. The
artifact must be the pinned build (solc 0.8.25, London, 200 optimizer
runs): `deploy_new_chain.sh` builds it after the flat builds (whose `forge
clean` would erase it), and the `forge script` invocation skips
SafeGuard.sol so that the script build does not overwrite the artifact.
`DeploySafes` pins the creation-code keccak as a constant and refuses any
other bytes; only this build lands the guard at its canonical address
(0xecFC0A72Ec520fcEdCB1D7e663D0e728D76344F8 for the standard upgrade Safe).

The handover batch ends with `setDelay(upgradeSafeTimelockDelay)` (5 days,
from `SafeConfig`), a self-authorized call from the Safe to the guard. A
freshly-deployed guard has a delay of zero and gives no timelock protection
until this call. A post-check reads the delay back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add the missing 18th `address` to the `--sig` for `DeploySafes.run`
- Read the Guard initcode from the forge artifact with `jq`; the
  `forge inspect` invocation did not receive its `FOUNDRY_*` variables
  and clobbered the artifact with a wrong-profile build
- Enforce EIP-7825 in simulation (`--enable-tx-gas-limit`) and un-swap
  the operands of the per-transaction gas check in `DeploySafes`
- Instruct the EraVM operator to `setDelay` in the same multisend as
  `setGuard` and to record `governance.timelock`; repair the DANGER
  banner's box alignment
- Reject `register_exit_cleanup` inputs (and recovered trap bodies)
  that cannot round-trip through `trap -p` requoting
- Assert the required toolchain flavor per operation with
  `require_vanilla_foundry`/`require_zk_foundry`, deduplicate the
  toolchain version pins, and stop mutating the installed toolchain
  from within `deploy_safeguard.sh`
- Validate `VERSION()` responses against a strict pattern and look up
  the per-version `multiCall` with a jq `--arg` path lookup instead of
  interpolating on-chain data into the jq program
- Read `safe["v1.3.0"].multiCall` strictly in
  `deploy_crosschainfactory.sh`

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract the current trap body by `eval`ing the shell-quoted output of
`trap -p EXIT` into an array instead of string-stripping it. This makes
cleanup commands containing single quotes (e.g. `_escape`d paths) work
instead of being rejected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ment splicing

The EXIT trap prefix now disables nounset alongside errexit so a cleanup
that expands an unset variable cannot abort the cleanups after it.
Registration in a subshell dies instead of importing the parent's trap
(which `trap -p` reports there) and rerunning its cleanups at subshell
exit. IFS is scoped locally so the `trap -- - EXIT` detection does not
depend on the caller's IFS. Cleanups are joined with a newline instead
of `; ` so a cleanup ending in a comment or `&` cannot swallow the
cleanups registered before it.

Cleanup paths are now shell-escaped with `printf %q` instead of the
vendored realpath `_escape` helper, which backslash-escapes every
character and therefore turns a newline in a path into a line
continuation. `_escape` remains in use only for its original purpose,
glob-pattern escaping inside `_resolve_symlinks`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant