Skip to content

feat(EXSC-413): S5 — pause (instance + global-read; withdrawals-always-open)#1982

Draft
gvladika wants to merge 3 commits into
feature/exsc-409-s1-core-erc-4626-immutable-args-cwiafrom
feature/exsc-413-s5-pause-instance-global-read-withdrawals-always-open
Draft

feat(EXSC-413): S5 — pause (instance + global-read; withdrawals-always-open)#1982
gvladika wants to merge 3 commits into
feature/exsc-409-s1-core-erc-4626-immutable-args-cwiafrom
feature/exsc-413-s5-pause-instance-global-read-withdrawals-always-open

Conversation

@gvladika

Copy link
Copy Markdown
Contributor

Which Linear task belongs to this PR?

EXSC-413 — S5: Pause (instance + global-read; withdrawals-always-open)

Stacked PR. Base is feature/exsc-408-s11-access-interfaces-reference-adapter (#1981, S11). Review the incremental diff against the base; do not merge before its parents (S1 → S11) land. Stack: S1 → S11 → S5 → S3.

Why did I implement it this way?

S5 is the clone-side pause: enforcement + the live reads. The factory-side global flag and emergency-role plumbing already exist from S8 (merged into S1); this PR makes instances honour them and adds the two instance-level pauses.

  • Abstract VaultWrapperPausable mixin (the ticket's "pause module"). Two independent instance flags so neither authority can lift the other's pause: emergencyPaused (toggled by the LI.FI emergency multisig) and integratorPaused (toggled by the integrator). Plus a live factory.globalPaused() read. depositsPaused() = emergencyPaused || integratorPaused || globalPaused().
  • Authorities are read live, not snapshotted. _emergencyPauseAuthority() returns factory.emergencyPauser() so rotating the emergency multisig at the factory propagates to every instance with no clone change; _integratorPauseAuthority() is the per-vault vaultWrapperAdmin. The wrapper supplies these via three virtual hooks the mixin declares, keeping the mixin storage-agnostic.
  • Deposits-only, withdrawals always open — the load-bearing decision. _requireDepositsNotPaused() is wired into deposit/mint only. Critically, the prior no-op _requireNotPaused() lived inside the shared _beforeOperation() that also runs on withdraw/redeem — so implementing it there would have frozen user exits during an emergency, the opposite of a circuit breaker. It was removed from _beforeOperation() (now access + accrual only) and replaced by the inflow-only guard. Self-withdraw therefore works under every pause combination; the fee sweep bypass lands with S3.
  • Factory interface gained globalPaused()/emergencyPauser() view declarations — already satisfied by the factory's existing public-getter state vars, so no factory code change and no version bump (subsystem is unreleased).

Tests

VaultWrapperPause.t.sol (16) uses a MockPauseFactory that deploys the proxy (so it is the factory the instance reads back) and toggles the global flag: deposit/mint revert under each of the three pause sources; withdraw/redeem succeed under emergency, integrator, and all-combined pauses; integrator vs LI.FI authority separation (each cannot drive or lift the other's pause; strangers rejected); unpause resumes; the depositsPaused() view and pause events. The existing LiFiVaultWrapper.t.sol (whose factory is the test contract) gained globalPaused()/emergencyPauser() shims so its deposits still resolve.

Checklist before requesting a review

  • I have performed a self-review of my code
  • This pull request is as small as possible and only tackles one problem
  • I have run /pr-ready (local CodeRabbit) on this branch and resolved (or explicitly documented) all findings — see .agents/commands/pr-ready.md
  • I have added tests that cover the functionality / test the bug
  • For new facets: I have checked all points from this list: https://www.notion.so/lifi/New-Facet-Contract-Checklist-157f0ff14ac78095a2b8f999d655622e
  • I have updated any required documentation

Checklist for reviewer (DO NOT DEPLOY and contracts BEFORE CHECKING THIS!!!)

  • I have checked that any arbitrary calls to external contracts are validated and or restricted
  • I have checked that any privileged calls (i.e. storage modifications) are validated and or restricted
  • I have ensured that any new contracts have had AT A MINIMUM 1 preliminary audit conducted on by <company/auditor>

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ed6b1a64-3072-4124-9990-ec5010067d7e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/exsc-413-s5-pause-instance-global-read-withdrawals-always-open

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

gvladika and others added 3 commits June 26, 2026 16:26
S5. Add VaultWrapperPausable abstract mixin with two independent instance
flags (emergencyPaused via the LI.FI emergency multisig read live from the
factory, integratorPaused via the vault admin) plus a live
factory.globalPaused() read. The pause guard is wired into deposit/mint
only; withdraw/redeem stay structurally open under every pause
combination. _requireNotPaused was removed from the shared _beforeOperation
(which previously gated exits too) and replaced by _requireDepositsNotPaused
on inflows — fixing the latent freeze-on-exit defect.

ILiFiVaultWrapperFactory gains globalPaused()/emergencyPauser() view
declarations (already satisfied by the factory's public getters).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial review gaps: add a real-factory end-to-end suite proving
factory.globalPause() freezes a live instance's deposits (and unpause
resumes), plus the documented carve-out that a deploy while globally
paused yields a frozen-from-birth instance. Cover mint() under integrator
and global pause, and withdraw-open under global-pause-alone. Blank line
after pragma in LiFiVaultWrapper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Append-only upgrade gap so the pause mixin can gain state without shifting
the storage of the contracts that inherit it, consistent with the wrapper's
own gap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gvladika gvladika force-pushed the feature/exsc-413-s5-pause-instance-global-read-withdrawals-always-open branch from b0e15d6 to f83fbb7 Compare June 26, 2026 14:31
@gvladika gvladika changed the base branch from feature/exsc-408-s11-access-interfaces-reference-adapter to feature/exsc-409-s1-core-erc-4626-immutable-args-cwia June 26, 2026 14:31
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