Skip to content

feat: add fund recovery contracts#121

Open
shinchann221 wants to merge 7 commits intomasterfrom
feat/fund-recovery
Open

feat: add fund recovery contracts#121
shinchann221 wants to merge 7 commits intomasterfrom
feat/fund-recovery

Conversation

@shinchann221
Copy link
Copy Markdown
Contributor

@shinchann221 shinchann221 commented Apr 22, 2026

Fund Recovery Module

Summary

Self-service path for ERC20s stuck in wrong-chain TopUp contracts. User signs on their OP cash Safe, LayerZero v2 ships the request to one of 5 dest chains, dispatcher sweeps the stuck balance into a recipient address. Replaces the bespoke CX-driven recovery process and clears the ~700-case backlog. Source = Optimism (EID 30111); destinations = Ethereum, Arbitrum, Base, BNB, HyperEVM.

Changes

  • AssetRecoveryModule.sol (Optimism, LZ v2 OAppSender): single recover(safe, token, recipient, safeSalt, destEid, lzOptions, signers, sigs) payable that:

    • verifies Safe owner-threshold sigs (digest binds nonce + safe + token + recipient + salt + destEid + keccak256(lzOptions))
    • dispatches the LZ message in the same call (no request/execute split, no timelock)
    • amount-less payload — destination sweeps the full balance at delivery
    • refunds excess msg.value over the quoted fee
    • pause/unpause via shared RoleRegistry
  • AssetRecoveryDispatcher.sol (per dest chain, LZ v2 OAppReceiver, UUPS): receives the LZ message and forwards to TopUpV2(payload.safe).executeRecovery(...).

    • Lazy-deploys TopUp via the local TopUpFactory if payload.safe.code.length == 0 — covers users who only ever sent unsupported tokens to a chain, so the regular topup batch path never triggered a deploy. Salt is asserted against factory.getDeterministicAddress(salt) == payload.safe first so a bogus salt can't litter the chain with stray TopUps.
    • srcEid + peer checks (defence-in-depth).
    • Pausable.
  • TopUpV2.sol (beacon impl on each dest chain): adds executeRecovery(token, recipient) gated to the local dispatcher. Sweeps full balanceOf(this). Reverts with OnlyUnsupportedTokens if the token has a configured bridge route (those must use the normal claim path) and with NoBalanceToRecover if there's nothing to sweep — both keep the LZ packet retryable.

  • RecoveryMessageLib.sol: encode/decode for (safe, token, recipient, salt). No amount field.

  • Deploy + verify scripts (scripts/recovery/): CREATE3 via Nick's factory so impls land at the same address on every chain. Per-chain runbook in scripts/recovery/README.md. VerifyRecoveryDeployment.s.sol asserts post-deploy invariants (predicted addresses, owner = operating safe, endpoint immutable, source EID, role registry, TopUp factory).

  • Peer wiring (ConfigureLzPeers.s.sol): emits 14 setPeer calldatas (7 OP module + 7 dispatchers) for the operating safe to 3CP-sign.

Operations

  • Pause/unpause on both AssetRecoveryModule (OP) and each AssetRecoveryDispatcher via the operating safe 0xA6cf33124cb342D1c604cAC87986B965F428AAC4.
  • Halting a single dest: setPeer(eid, bytes32(0)) on the OP module.
  • No per-tx cap, no timelock, no rolling rate limit.

Files Changed

  • src/modules/recovery/AssetRecoveryModule.sol (new)
  • src/top-up/AssetRecoveryDispatcher.sol (new)
  • src/top-up/TopUpV2.sol (new)
  • src/libraries/RecoveryMessageLib.sol (new)
  • src/interfaces/IAssetRecoveryModule.sol (new)
  • scripts/recovery/DeployAssetRecoveryModule.s.sol (new)
  • scripts/recovery/DeployAssetRecoveryDispatcher.s.sol (new)
  • scripts/recovery/UpgradeTopUpImpl.s.sol (new)
  • scripts/recovery/ConfigureLzPeers.s.sol (new)
  • scripts/recovery/VerifyRecoveryDeployment.s.sol (new)
  • scripts/recovery/RecoveryDeployConfig.sol (new)
  • scripts/recovery/lz-config.json (new)
  • scripts/recovery/README.md (new)
  • test/integration/RecoveryE2E.t.sol (new)
  • test/safe/modules/recovery/AssetRecoveryModule.t.sol (new)
  • test/top-up/AssetRecoveryDispatcher.t.sol (new)
  • test/top-up/TopUpV2.t.sol (new)
  • test/top-up/TopUpV2_StorageLayout.t.sol (new)
  • lib/LayerZero-v2, lib/devtools (submodules)
  • remappings.txt, foundry.lock, .gitmodules (LZ deps)
  • certora/confs/{CashModuleCore,DebtManagerCore,EtherFiSafe}.conf (point at SettlementDispatcherV2)

Note

High Risk
Introduces new cross-chain messaging and sweeping logic (AssetRecoveryModule/AssetRecoveryDispatcher) plus a beacon implementation upgrade path (TopUpV2), which is security- and funds-movement-critical and depends on correct peer/endpoint configuration across multiple chains.

Overview
Adds a new LayerZero v2–based fund recovery path to sweep ERC20 balances stuck in wrong-chain TopUp addresses: an Optimism AssetRecoveryModule.recover() verifies Safe-owner signatures (nonce, salt, destEid, and lzOptions hash), dispatches the LZ message, and emits RecoverySent with overpayment refund support and pause/unpause via RoleRegistry.

On destination chains, introduces an upgradeable singleton AssetRecoveryDispatcher that validates source EID/peer, optionally lazy-deploys the user’s TopUp via TopUpFactory using the payload salt, and forwards to a new beacon implementation TopUpV2.executeRecovery() which is dispatcher-gated and sweeps the full balance (reverting when empty or for supported tokens).

Includes CREATE3-based deploy/verify/runbook tooling under scripts/recovery/, adds LayerZero dependencies/remappings (new submodules + foundry.lock), updates dev deployment JSONs, pins the HyperEVM fork test to a fixed block, and adds unit + E2E tests plus mocks to cover the new recovery flow and storage-layout safety for the beacon upgrade.

Reviewed by Cursor Bugbot for commit 7b5c276. Bugbot is set up for automated code reviews on this repo. Configure here.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e94774149e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread scripts/recovery/VerifyRecoveryDeployment.s.sol Outdated
Comment thread scripts/recovery/VerifyRecoveryDeployment.s.sol
Comment thread src/modules/recovery/RecoveryModule.sol Outdated
Comment thread scripts/recovery/VerifyRecoveryDeployment.s.sol Outdated
Comment thread src/modules/recovery/RecoveryModule.sol Outdated
Comment thread src/top-up/RecoveryDispatcher.sol Outdated
Comment thread src/modules/recovery/RecoveryModule.sol Outdated
@etherfi-protocol etherfi-protocol deleted a comment from github-actions Bot Apr 27, 2026
@etherfi-protocol etherfi-protocol deleted a comment from github-actions Bot Apr 27, 2026
@etherfi-protocol etherfi-protocol deleted a comment from github-actions Bot Apr 27, 2026
@etherfi-protocol etherfi-protocol deleted a comment from github-actions Bot Apr 27, 2026
@etherfi-protocol etherfi-protocol deleted a comment from github-actions Bot Apr 27, 2026
@etherfi-protocol etherfi-protocol deleted a comment from github-actions Bot Apr 27, 2026
@github-actions
Copy link
Copy Markdown

Certora Run Started (Certora verification)

  • Group ID: 477c3b62-2f21-4164-9e10-10d3187ef9f2
Config Status Link Log File
CashModuleCore.conf --rule "p03" Failed (1) - certora/confs/CashModuleCore.conf-5de66a266628.log
EtherFiSafe.conf Failed (1) - certora/confs/EtherFiSafe.conf-11e1ad1397cc.log

Certora Run Summary

  • Started 0 jobs
  • 2 jobs failed

Download Logs

…ck TopUp funds

Adds RecoveryModule on Optimism (OApp sender, non-upgradable) + RecoveryDispatcher
(OApp receiver, UUPS upgradable) on each destination chain, plus TopUpV2 with a
dispatcher-gated executeRecovery. Owners sign a digest binding chainid, module,
nonce, safe, token, amount, recipient, destEid and keccak256(lzOptions); the
single recover() call verifies sigs and ships the LZ v2 message in the same tx.

Includes Optimism deploy script + per-dest dispatcher and TopUp upgrade scripts
(CREATE3, deterministic), an LZ peer-configuration script, and a verifier that
asserts module/dispatcher immutables (endpoint, dataProvider, role registry)
plus EIP-1967 impl slot, owner, SOURCE_EID, and optional beacon impl pointer.

RecoveryDispatched event arg order matches RecoverySent.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c88f288. Configure here.

Comment thread test/top-up/AssetRecoveryDispatcher.t.sol
Copy link
Copy Markdown

@certora-run certora-run Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verification Results

  • Group ID: 022348ce-dec8-4cc3-ba13-dabced7e28df
  • Commit: c88f288
Job Result VERIFIED Link
EtherFiSafe.conf 0 Link
CashModuleCore.conf --rule "p03" 2 Link

Copy link
Copy Markdown

@certora-run certora-run Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verification Results

  • Group ID: 64272e88-ef32-4b26-b9d8-4e7a6da95b51
  • Commit: 249d503
Job Result VERIFIED Link
EtherFiSafe.conf 14 Link
CashModuleCore.conf --rule "p03" 2 Link

Comment thread src/modules/recovery/RecoveryModule.sol Outdated
Comment thread src/top-up/RecoveryDispatcher.sol Outdated
Copy link
Copy Markdown

@certora-run certora-run Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verification Results

  • Group ID: 0c098c99-23cc-4c50-9bd1-015b4d99e742
  • Commit: 6fed977
Job Result VERIFIED Link
EtherFiSafe.conf 14 Link
CashModuleCore.conf --rule "p03" 2 Link

Copy link
Copy Markdown

@certora-run certora-run Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verification Results

  • Group ID: be4f1b68-bcf8-4145-a526-e164cdbacf23
  • Commit: 712f74e
Job Result VERIFIED Link
EtherFiSafe.conf 14 Link
CashModuleCore.conf --rule "p03" 2 Link

Copy link
Copy Markdown

@certora-run certora-run Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verification Results

  • Group ID: 687049b7-1fa9-42e5-a55d-1bbb90b22c6f
  • Commit: c037637
Job Result VERIFIED Link
EtherFiSafe.conf 14 Link
CashModuleCore.conf --rule "p03" 2 Link

Copy link
Copy Markdown

@certora-run certora-run Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verification Results

  • Group ID: 3d52754f-b81d-45da-8841-898f8c180b64
  • Commit: 7b5c276
Job Result VERIFIED Link
EtherFiSafe.conf 14 Link
CashModuleCore.conf --rule "p03" 2 Link

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.

2 participants