ConfidencePoolFactory checks whether a stake token is allowlisted only when a pool is created. After the pool is created, ConfidencePool assumes the token will continue behaving as a standard ERC20 for the lifetime of the pool.
The deposit paths defend against non-standard token behavior by using balance-difference accounting. However, the payout paths transfer the computed payout directly and do not verify that the claimant actually received the same amount.
If an allowlisted token behaves normally during allowlisting, staking, and bonus contribution, but later changes behavior through a proxy upgrade or token-admin configuration change to become fee-on-transfer, a claimant can be marked as fully paid while receiving less than the pool-accounted payout.
Affected payout path:
The issue is that payout is treated as fully delivered once safeTransfer() succeeds, even if the token takes an outbound transfer fee and the recipient receives less.
This causes claimant underpayment and accounting finality loss.
In the PoC:
The pool accounts Alice as having received the full 150, clears her accounting, and marks her as claimed. However, the token burns 15 on the outgoing transfer, so Alice receives only 135.
Alice cannot recover the missing 15 because:
This is not an arbitrary drain against immutable standard ERC20 tokens. The required condition is that an allowlisted stake token can change transfer semantics after pool creation, such as through an upgradeable/proxy token or token-admin configuration change.
The protocol already recognizes this class of risk on inbound deposits. For example, deposit paths use balance-difference accounting as a defense against fee-on-transfer or rebasing behavior. The same exact-transfer invariant is not enforced on outbound claim transfers.
The PoC uses the real ConfidencePoolFactory allowlist path:
Relevant PoC assertions:
The local trace shows that claimSurvived() attempts to transfer 150e18, but the token burns 15e18 and transfers only 135e18 to Alice.
Add exact-transfer validation to outbound payout paths.
Example:
Then use _safeTransferExact() instead of stakeToken.safeTransfer() in payout paths, including:
Alternatively, strictly disallow upgradeable or configurable stake tokens at allowlisting time. This is weaker unless it is enforceable on-chain, because the current allowlist check is only performed when the pool is created.
Suggested severity: Medium.
Reasoning:
The issue can cause real claimant underpayment.
It affects core payout accounting.
The claimant is marked fully paid even though they receive less than the pool-accounted payout.
It requires the allowed stake token to change behavior after allowlisting.
It does not allow arbitrary theft from pools using immutable standard ERC20 tokens.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.