FoundrySolidityLayer 2
7.25 ETH
Submission Details
Impact: low
Likelihood: low

QA Report: informational hardening notes and gas optimizations (expiry-lock asymmetry, stale totalBonus, empty-pool bounty gate, asymmetric clamp guard, unbounded scope, redundant latch writes)

Author Revealed upon completion

Description

Non-critical hardening and gas notes; no fund loss. Filed as a single QA report.

Informational

  • I-01 — contributeBonus does not lock expiry. stake sets expiryLocked (:229-231) but
    contributeBonus (:266-285) does not, so a bonus contributor's funded deadline can still be moved
    by the sponsor until the first stake.

  • I-03 — totalBonus left stale-high. Only decremented in the totalEligibleStake == 0 || riskWindowStart == 0 branch (:499-501); non-authoritative after a normal SURVIVED distribution.

  • I-04 — empty good-faith pool bypasses MustClaimBountyFirst. With bountyEntitlement == 0 the
    gate at :410 passes trivially, letting claimCorrupted sweep a later donation to recoveryAddress.

  • I-05 — _clampUserSums asymmetric guard. :681-684 tests only userSumStakeTime, never
    userSumStakeTimeSq; verified safe but fragile to future edits.

Gas

  • G-01 — unbounded pool scope _replaceScope :749-776 (cap accounts.length).

  • G-02 — if (!claimsStarted) claimsStarted = true; at :402,422,448,467,600 → unconditional assign.

  • G-03 — flagOutcome external call without nonReentrant :322 (registry trusted, note only).

  • G-04 — un-asserted invariant claimedBonus <= snapshotTotalBonus at :486 (add a defensive note).

Risk

Informational — no direct fund loss; hardening against future refactors and minor gas savings.

Proof of Concept

Coverage evidence in test/audit/ (differential, conservation, underflow, escalation suites — 22 tests).

Recommended Mitigation

Representative fixes (full list per-item above):

// I-01 — lock the term on a bonus deposit too, if bonus contributors should commit the deadline
function contributeBonus(uint256 amount) external nonReentrant whenPoolNotPaused {
// ...
+ if (!expiryLocked) expiryLocked = true;
totalBonus += received;
emit BonusContributed(msg.sender, received);
}
// G-01 — cap the scope length to bound gas / OOG on _replaceScope
function _replaceScope(address[] calldata accounts) internal {
if (accounts.length == 0) revert EmptyScope();
+ if (accounts.length > MAX_SCOPE_ACCOUNTS) revert ScopeTooLarge();
// G-02 — the conditional latch write is equivalent to an unconditional assign
- if (!claimsStarted) claimsStarted = true;
+ claimsStarted = true;
// G-04 — assert the invariant the subtraction relies on
+ // claimedBonus never exceeds snapshotTotalBonus (Σ shares <= pot); assert to harden refactors
+ assert(claimedBonus <= snapshotTotalBonus);
reserved += snapshotTotalBonus - claimedBonus;

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!