Skip to content

Spearbit Audit 2 fixes 1-9#29

Merged
amiecorso merged 8 commits intomainfrom
audit2-fixes
Mar 28, 2026
Merged

Spearbit Audit 2 fixes 1-9#29
amiecorso merged 8 commits intomainfrom
audit2-fixes

Conversation

@amiecorso
Copy link
Copy Markdown
Collaborator

@amiecorso amiecorso commented Mar 27, 2026

Summary

Addresses all 9 findings from the second audit (Red Swan / Giovanni Di Siena).

Findings addressed

Low severity

  • 2 — Swallowed policy hook reverts emit no events: All 4 catch blocks in _uninstall / _uninstallForReplace now capture revert data and emit PolicyUninstallHookReverted(policyId, account, policy, revertData).
  • 6 — Morpho policies overlook non-standard ERC-20 approve semantics: Both MorphoLendPolicy and MorphoLoanProtectionPolicy now prepend approve(spender, 0) before the actual approval, handling USDT-style tokens.
  • 7 — setPolicyManager ambiguities and executor signature replay: Removed setPolicyManager entirely; policyManager is now immutable.
  • 9 — setPolicyManager insufficient for mass migration: Resolved by removing setPolicyManager.

Informational

  • 1 — ERC-6492 depends on Solady's pre-deployed verifier: PublicERC6492Validator constructor now checks for code at the Solady verifier address.
  • 3 — Repeated MorphoLendPolicy validation: Removed redundant ZeroAmount check (already covered by RecurringAllowance.ZeroValue).
  • 4 — Policies not checked for persistent code upon installation: _install and _replace now call _requirePersistentCode to reject empty or EIP-7702-only addresses.
  • 5 — Impossible validity windows: _install and _installForReplace now revert with InvalidValidityWindow when validAfter >= validUntil (both non-zero).
  • 8 — setPolicyManager missing same-value check: Resolved by removing setPolicyManager.

… flows

Move the install-only vs install+execute decision into the manager:
installWithSignature and replaceWithSignature now only call _execute
when executionData is non-empty. This removes the requirement for
policies to implement a sentinel check on empty executionData, and
ensures every onExecute call represents a genuinely intended execution.

Also moves the validity window check before onExecute in _execute,
since the deferred-check workaround is no longer needed — install-only
flows never reach _execute at all.

Removes sentinel checks from SingleExecutorAuthorizedPolicy and
CallForwardingPolicy. Updates NatSpec throughout.
Addresses audit findings #7 (signature replay on manager change),
#8 (missing same-value check), and #9 (insufficient for migration).

Rather than patching setPolicyManager, remove it entirely — live
migration via this mechanism is too fragile and we will use other
approaches. With no mutation site, policyManager becomes immutable,
saving gas on every onlyPolicyManager modifier check.
…llowed

Addresses audit finding #2 (swallowed policy hook reverts emit no events).

All 4 catch blocks in _uninstall and _uninstallForReplace now capture
revert data and emit PolicyUninstallHookReverted(policyId, account,
policy, revertData), giving on-chain observers visibility into forced
uninstalls where policy cleanup logic did not run.

Also fixes a pre-existing fuzz collision in
test_reverts_whenOldPolicyNotInstalled where oldPolicyId could equal
newPolicyId, hitting InvalidPayload before PolicyNotInstalled.
Addresses audit finding #6 (non-standard ERC-20 approve semantics).

Tokens like USDT revert on approve(spender, nonZero) when the existing
allowance is non-zero. Both MorphoLendPolicy and MorphoLoanProtectionPolicy
now prepend an approve(spender, 0) call before the actual approval,
preventing stuck policies when the account has an outstanding allowance.

Adds ApprovalResetToken mock and tests for both policies proving
deposits/top-ups succeed with a pre-existing non-zero allowance.
…lidator

Addresses audit finding #1 (ERC-6492 validation depends on Solady's
pre-deployed verifier). Reverts at deploy time with
ERC6492VerifierNotDeployed() if the verifier at
0x0000bc370E4DC924F427d84e2f4B9Ec81626ba7E has no code, preventing
silent signature validation failures on chains where it is absent.
Addresses audit finding #3 (repeated validation). The zero deposit
check in _onSingleExecutorExecute is already performed by
RecurringAllowance.useLimit() which reverts with ZeroValue.
Addresses audit finding #4 (policies not checked for persistent code
upon installation). Adds _requirePersistentCode helper that rejects
addresses with no code or only an EIP-7702 delegation prefix. Called
in _install and _replace (for the new policy) to fail fast rather
than leaving accounts with an unexecutable binding.
Addresses audit finding #5 (unusable bindings with validAfter >= validUntil).
Both _install and _installForReplace now revert with
InvalidValidityWindow when both bounds are non-zero and
validAfter >= validUntil, preventing permanently unusable bindings.
@amiecorso amiecorso changed the title Audit 2 fixes (Red Swan / Giovanni) Spearbit Audit 2 fixes 1-9 Mar 27, 2026
Base automatically changed from refactor/execute-gating to main March 28, 2026 00:40
@amiecorso amiecorso merged commit 0579a77 into main Mar 28, 2026
3 checks passed
@amiecorso amiecorso deleted the audit2-fixes branch March 28, 2026 00:41
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