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

`InvalidAmount` Used for "No Stake" in `withdraw()`

Author Revealed upon completion

Root + Impact

Description

withdraw() checks eligibleStake[msg.sender] == 0 and reverts with InvalidAmount. The caller passes no amount parameter — the function withdraws their full eligible stake. The actual failure is that the caller has no stake to withdraw. Using InvalidAmount implies the caller passed an invalid parameter value, which is inaccurate. A dedicated error like NothingToWithdraw or NoEligibleStake would be more precise.

// ConfidencePool.sol — withdraw() lines 302-303
function withdraw() external nonReentrant {
// ...
@> uint256 amount = eligibleStake[msg.sender];
@> if (amount == 0) revert InvalidAmount(); // <-- no amount passed by caller
// ...
}

Risk

Likelihood: Low — only triggers when a user with no stake calls withdraw().

Impact: Low — no funds lost; off-chain debugging is degraded because the error suggests input validation failure rather than state conflict.

Proof of Concept

File: L21-InvalidAmount-Withdraw-NoStake.poc.t.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {IAttackRegistry} from "@battlechain/interface/IAttackRegistry.sol";
import {IConfidencePool} from "src/interfaces/IConfidencePool.sol";
import {BaseConfidencePoolTest} from "test/helpers/BaseConfidencePoolTest.sol";
/// @notice PoC for L-21: `InvalidAmount` used for "no stake" in withdraw()
contract L21_InvalidAmount_Withdraw_NoStake_POC is BaseConfidencePoolTest {
function testPOC_L21_invalidAmount_whenNoStakeToWithdraw() external {
// Alice has no stake — withdraw reverts with InvalidAmount
// But she never passed an amount; the issue is she has nothing to withdraw
vm.prank(alice);
vm.expectRevert(IConfidencePool.InvalidAmount.selector);
pool.withdraw();
}
function testPOC_L21_withdrawSucceedsAfterStake() external {
// After staking, withdraw succeeds — confirming the error path above
_stake(alice, 10 * ONE);
attackRegistry.setAgreementState(IAttackRegistry.ContractState.NOT_DEPLOYED);
uint256 beforeBal = token.balanceOf(alice);
vm.prank(alice);
pool.withdraw();
assertEq(token.balanceOf(alice) - beforeBal, 10 * ONE);
}
}

Run: forge test --match-path 'L21-InvalidAmount-Withdraw-NoStake.poc.t.sol' -vv

Recommended Mitigation

Add a dedicated error:

+ error NothingToWithdraw();
- if (amount == 0) revert InvalidAmount();
+ if (amount == 0) revert NothingToWithdraw();

Support

FAQs

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

Give us feedback!