Skip to content

[474, multichain] ETHFI OFT security hardening (4-DVN + library pinning)#11

Open
jtfirek wants to merge 3 commits into
masterfrom
474/ethfi-security-upgrade
Open

[474, multichain] ETHFI OFT security hardening (4-DVN + library pinning)#11
jtfirek wants to merge 3 commits into
masterfrom
474/ethfi-security-upgrade

Conversation

@jtfirek

@jtfirek jtfirek commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the Forge script and LZ-config that generates the 5 Gnosis Safe batches used by 3CP-474 to harden the ETHFI OFT (0xe0080d2F853ecDdbd81A643dC10DA075Df26fD3f) on Ethereum, Optimism, Arbitrum, Base and Scroll.

For every peer EID on every chain, each batch:

  • pins SendUln302 + ReceiveUln302 via setSendLibrary / setReceiveLibrary, and
  • sets a 4-of-4 required-DVN ULN config (LayerZero Labs / Nethermind / Horizen / Canary) at 45 confirmations via setConfig (send + receive).

This mirrors the LayerZero-config portions of weETH PR #74 (3CP-455 / 3CP-461) but without the unpauseBridge / rate-limiter calls, since ETHFI's OFTUpgradeable / EtherfiOFTAdapterUpgradeable does not expose those entry-points.

Files

  • script/SecurityUpgrade.s.sol (new) — emits ethfi-{ethereum,op,arbitrum,base,scroll}-SecurityUpgrade.json to ./output/
  • addresses/lz-oft-config.json (new) — full LZ-Endpoint / library / 4-DVN / controller-Safe config for the 5 ETHFI chains. All addresses cross-checked against the LayerZero metadata API (https://metadata.layerzero-api.com/v1/metadata/dvns?chainName=<chain>).
  • .gitignore — ignores ./output/ (batches are committed in 3CP-secure).

Per-chain summary

Chain OFT Peers Calls Controller Safe
ethereum (1) EtherfiOFTAdapterUpgradeable optimism, arbitrum, base, scroll 10 0x2aCA71020De61bb532008049e1Bd41E451aE8AdC
optimism (10) ETHFI OFT ethereum, arbitrum, base, scroll 10 0x764682c769CcB119349d92f1B63ee1c03d6AECFf
arbitrum (42161) ETHFI OFT ethereum, optimism, base, scroll 10 0x0c6ca434756EeDF928a55EBeAf0019364B279732
base (8453) ETHFI OFT ethereum, optimism, arbitrum, scroll 10 0x7a00657a45420044bc526B90Ad667aFfaee0A868
scroll (534352) ETHFI OFT ethereum, optimism, arbitrum, base 10 0x3cD08f51D0EA86ac93368DE31822117cd70CECA3

Each Safe is the OApp delegate on the LZ Endpoint (verified via endpoint.delegates(oft) on each RPC).

Reproduction

mkdir -p output
forge script script/SecurityUpgrade.s.sol:EthfiOftSecurityUpgrade --sig run

Hashes for the produced JSONs are then re-derived in 3CP-secure via build_multisend.sh + safe_hashes.sh --offline (see the companion 3CP-secure PR for the full hash table).

Companion PRs

  • 3CP-secure: queued/474/ (signed batches + 474.md hash table)
  • oft-factory: EURC counterpart of this script (3 chains)

Made with Cursor

jtfirek added 2 commits April 28, 2026 15:34
Adds Forge script and config to generate Gnosis Safe batches that pin LZ
SendUln302 / ReceiveUln302 libraries and upgrade ETHFI OFT
(0xe008...fD3f) to a 4-of-4 required-DVN ULN config (LZ Labs / Nethermind
/ Horizen / Canary, 45-block confirmations) on Ethereum, Optimism,
Arbitrum, Base, and Scroll. Mirrors the LayerZero-config portions of
weETH 3CP-455/461.

- script/SecurityUpgrade.s.sol: produces 5 ETHFI JSONs in ./output/
- addresses/lz-oft-config.json: full LZ-Endpoint / library / 4-DVN /
  controller-Safe config for the 5 ETHFI chains, cross-checked against
  the LayerZero metadata API
- .gitignore: ignores ./output/ (batches are committed in 3CP-secure)

Made-with: Cursor
Forking the live endpoint at script-run time and querying
isDefaultSendLibrary / getReceiveLibrary lets us elide setSendLibrary
and setReceiveLibrary calls that would otherwise revert with
LZ_SameValue. After the audit, the only ETHFI peer still on the
default library is the OP peer (eid 30111) on ethereum / arbitrum /
base / scroll — every other peer is already custom-pinned, so those
calls are dropped from the batch. Optimism's ETHFI is already fully
pinned, so its batch collapses to just the two setConfig calls.

Adds test/SecurityUpgradeSendSim.t.sol which forks every chain in
scope, replays the per-chain Gnosis batch as the controller Safe, and
asserts oft.send still succeeds afterwards (5 cases, all passing).
The Adapter chains (ethereum, arbitrum, base, scroll) deal the
underlying ERC20 to the sender and approve the OFT before sending.

Made-with: Cursor
@jtfirek

jtfirek commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator Author

Added the post-upgrade send simulation requested in the parent PR. Forks ethereum / optimism / arbitrum / base / scroll, replays the ETHFI SecurityUpgrade Gnosis batch as the controller Safe, then calls ETHFI.send with a freshly-funded sender. All 5 cases pass; on the Adapter chains (ethereum / arbitrum / base / scroll) the test deals the underlying ERC20 to the sender and approves the OFT.

The simulation also flagged a real bug — most ETHFI peers are already custom-pinned to the target SendUln302 / ReceiveUln302 today, so the original setSendLibrary / setReceiveLibrary calls would have reverted with LZ_SameValue(). The script now forks each chain at run-time and only emits pin calls where the OFT is on the default library. The only peer still using the default lib is the OP peer (eid 30111) on ethereum / arbitrum / base / scroll — those 4 batches keep one setSendLibrary + one setReceiveLibrary call. Optimism's ETHFI is fully pinned, so its batch collapses to just the 2 setConfig calls.

End-to-end test that, on a fork of every chain in scope:
1. Applies the COMBINED 3CP-secure Gnosis batch (the exact bytes
   signers will execute) via ../3CP-secure/queued/474/<batch>.json
2. Calls oft.send for every asset deployed on that chain (EURC + ETHFI
   on shared chains, ETHFI-only on arb / base)
3. Iterates every (oft, peer) route and asserts both
   isDefaultSendLibrary == false / getSendLibrary == target SendUln302
   and getReceiveLibrary == (target ReceiveUln302, isDefault=false)

Coverage: 8 send calls + 26 routes verified pinned across 5 chains.
All passing.

Adds ../3CP-secure read access to fs_permissions so the test can read
the parent repo's combined batches directly.

Made-with: Cursor
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