Repository navigation
audit(sPRL2V2): Bailsec June 2026 remediation (Bailsec-02/03/05/06) - #13
Merged
Merged
Conversation
During the migration window both sPRL2 and sPRL2V2 are live and shared the same "sPRL2" symbol. Any downstream consumer keying on the token symbol (explorers, the reward-merkle distributor's off-chain eligibility input, dashboards) could conflate the two tokens, excluding or double-counting balances. Renames the SYMBOL constant to "sPRL2V2" so the new token is unambiguously distinguishable on-chain. NAME already differed from the legacy "...Aura Deposit Vault" so it is left untouched. The constructor integration test asserts the new symbol.
… requests requestWithdraw could be called with amount = 0, minting an empty WithdrawalRequest that occupies a request ID and can even be cancelled. It serves no purpose in normal operation and only adds tail risk / a precondition for other invalid states. Reverts with NullAmount() when the unlocking amount is zero. The guard lives in the shared base so sPRL1/sPRL2/sPRL2V2 all inherit it. Adds an integration test asserting the revert.
… zero address Premature exits transfer the BPT slash to feeReceiver inside the withdrawal flow. If feeReceiver were address(0), every early withdrawPRLAndWeth / withdrawBPT would revert on the slash transfer, DoS-ing all premature withdrawals until governance fixes the address. Adds a zero-address check both in the base constructor and in updateFeeReceiver so the fee receiver can never be set to address(0) at deploy time or via governance. Reverts with FeeReceiverZeroAddress(). Adds constructor and updateFeeReceiver revert tests.
…le tokens The BPT backing sPRL2V2 sits idle in the contract, so reward tokens claimed on the contract's behalf (e.g. MERKL distributions routed onBehalf to the holder) would otherwise be permanently locked with no way to retrieve them. Adds a restricted recoverERC20(token) that forwards the full balance of an arbitrary token to feeReceiver. The guard reverts with CannotRecoverUnderlying() when token == underlying, so the share- backing asset (BPT for sPRL2V2, PRL for sPRL1) can never be drained. Placed in the shared base keyed on `underlying` rather than a contract-specific BPT check, so every derived staking token benefits. Adds integration tests in the timeLockPenaltyERC20 suite (matching the base contract): successful recovery to feeReceiver, revert on the underlying token, and revert for an unauthorized caller.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Audit remediation for sPRL2V2 following the Bailsec sPRLV2 1st report (June 2026). Targets
feat/sprl2v2.Report:
docs/audits/Bailsec - Parallel - sPRL2V2 - 1st Report - June 2026.pdfFindings: 0 High · 2 Medium · 5 Low · 9 Informational. The four findings below are fixed; the remainder are acknowledged (see table).
Audit remediation
Code changes addressing the report's findings:
sPRL2symbol while both tokens are live during the migration window, risking conflation by any symbol-keyed consumer (explorers, the reward-merkle distributor's off-chain eligibility input). TheSYMBOLconstant is nowsPRL2V2.NAMEalready differed from the legacy...Aura Deposit Vault, so it is left untouched.onBehalfdistributions) would be permanently locked. Adds arestricted recoverERC20(token)that forwards the full balance tofeeReceiver. The guard reverts withCannotRecoverUnderlying()whentoken == underlying, so the share-backing asset (BPT for sPRL2V2, PRL for sPRL1) can never be drained. Placed in the sharedTimeLockPenaltyERC20base keyed onunderlyingrather than a contract-specific BPT check, so every derived staking token benefits.requestWithdraw(0)minted an empty, cancellable request occupying a request ID. Now reverts withNullAmount(). Guard added to the shared base.address(0)feeReceiverwould revert the slash transfer in every premature exit, DoS-ing all early withdrawals. Adds a zero-address guard (FeeReceiverZeroAddress()) in both the base constructor andupdateFeeReceiver.Acknowledged (no code change)
ERC20Votes+ERC20Permit; recoverable, no fund loss. Document for users.balanceOf.isReversedBalancerPairfindUnlockingIDsempty array on start-from-end_startFromEnd=false.uint160cast truncation2^160; consistency note.uint208supply capTests
recoverERC20(recover to feeReceiver, revert on underlying, revert on unauthorized) in thetimeLockPenaltyERC20suite (matching the base contract).requestWithdrawrevert, zero-addressfeeReceiverrevert (constructor +updateFeeReceiver), updated symbol assertion.forge build --sizes+forge test): 188 passed, 0 failed (fuzz 1000 runs, forks included). sPRL2V2 runtime size 21,423 < 24,576.Note
PR intended for audit review — do not merge until the audit is finalized.