Skip to content

Bailsec Parallel Savings Fix — audit remediation (Issues 01-05, 07) - #31

Merged
FabienCoutant merged 9 commits into
feat/savings-donation-and-pause-refactorfrom
audit/bailsec-june-2026-savings-donation
Jul 13, 2026
Merged

FabienCoutant merged 9 commits into
feat/savings-donation-and-pause-refactorfrom
audit/bailsec-june-2026-savings-donation

Conversation

@FabienCoutant

@FabienCoutant FabienCoutant commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Remediation of the Bailsec "Parallel Fix" report (June 2026), stacked on top of the audited base (61942aa, post-Cyfrin). One commit per finding.

Final report: all findings Resolved / Acknowledged. Both the 1st and final reports are in docs/audits/savings-fix/.

Code findings

  • [Issue_01 / Low] The role script never granted the initializeStoredAssets selector, so the prescribed atomic upgradeToAndCall reverts in production whenever the upgrading governor is not also the AccessManager admin. Granted the selector to GOVERNOR_ROLE in SetSavingRoles.s.sol and in the test access config.
  • [Issue_02 / Low] initializeStoredAssets seeded storedAssets but did not re-anchor lastUpdate, so the first accrual minted the pre-upgrade idle window. It now sets lastUpdate = block.timestamp after seeding.
  • [Issue_03 / Low] totalAssets() projected yield while paused, overvaluing shares for external integrations. It now returns storedAssets directly when paused.
  • [Issue_04 / Low] setRate/setMaxRate are callable while paused and accrued the paused interval. _accrue() is now paused-aware: while paused it advances lastUpdate and mints nothing, so the paused span is dropped.
  • [Issue_05 / Info] The overridden deposit/mint/withdraw/redeem bypassed the parent ERC4626 max* checks (those live in the public functions, not the internal _deposit/_withdraw). Re-introduced the ERC4626ExceededMax* checks in each override. Follow-up (final report flagged it as partially resolved): the same max* checks now also cover the EIP-3009 entry points depositWithAuthorization and _redeemWithAuthorization (inlined in depositWithAuthorization to avoid stack-too-deep).
  • [Issue_07 / Info] A non-atomic upgrade would leave storedAssets == 0 while totalSupply != 0, breaking deposits and letting redeemers burn shares for nothing. Added an onlyInitialized guard (NotInitialized when storedAssets == 0 && totalSupply != 0) on every vault entry point, including the EIP-3009 variants.

Acknowledged (no code change)

  • [Issue_06 / Info] initializeStoredAssets remains callable once on a fresh deployment. It is restricted and only overwrites storedAssets with the current balance (already correctly seeded), so the practical risk is minimal.

Tests

Added coverage for the re-anchored lastUpdate, paused-state totalAssets/rate-setters, the non-atomic-upgrade guard, and the authorized-redeem max check (test_RedeemWithAuthorization_RevertWhen_ExceedsMaxRedeem); updated the fuzz over-withdraw assertion to the earlier ERC4626ExceededMaxWithdraw revert. Local CI green: solhint, forge build --sizes, Savings units (39/39) + fuzz (27/27).

@FabienCoutant
FabienCoutant force-pushed the audit/bailsec-june-2026-savings-donation branch from e1c1b77 to 21a1b7a Compare July 13, 2026 08:44
@FabienCoutant
FabienCoutant merged commit aeb3262 into feat/savings-donation-and-pause-refactor Jul 13, 2026
4 checks passed
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