Allow funds recorded in the contract’s failedTransferCredits mapping (from prior failed payouts) to be safely paid out to the appropriate recipient (or withdrawn by the recipient themself).
The function withdrawAllFailedCredits(address _receiver) reads failedTransferCredits[_receiver] but clears and sends funds to msg.sender. That is, the mapping key used for reading (the victim) does not match the key used for clearing (the caller). This is a basic, critical logic bug: the contract will transfer a victim’s accumulated “failed credits” to the caller while leaving the victim’s record intact (or clearing the wrong slot).
Likelihood:
When the contract’s failedTransferCredits mapping contains any non-zero entries and the mapping is publicly readable, an attacker can enumerate those addresses and immediately call withdrawAllFailedCredits(_receiver) to collect the corresponding amount.
When an attacker initiates the call from a malicious contract whose receive()/fallback() reenters the vulnerable function, the lack of correct key clearing and absence of nonReentrant protection enables repeated withdrawals within a single transaction until the victim’s credited amount or the contract’s ETH is drained.
Impact:
Direct fund theft — An attacker can divert other users’ failedTransferCredits to their own account, potentially automating bulk extraction to drain many users’ balances or the contract’s ETH pool.
On-chain accounting inconsistency and operational/legal risk — The mapping may continue to show balances for victims (or enable double payouts), leading to bookkeeping errors, customer claims, regulatory/legal exposure, forensic work, and potential compensation liabilities.
Minimal exploit (explain in words + minimal contract):
Find an address victim with failedTransferCredits[victim] > 0 (public getter or scanning on-chain).
Deploy a malicious contract with a fallback() that calls withdrawAllFailedCredits(victim) again (for reentrancy) or simply receives the ETH.
Call withdrawAllFailedCredits(victim) from attacker account / contract. The contract will read victim’s amount and send it to msg.sender (attacker), but not clear failedTransferCredits[victim]. Reentering allows repeating the theft.
Change the API so callers can only withdraw their own credits. Remove the _receiver parameter or restrict usage: provide a no-arg withdrawFailedCredits() that acts on msg.sender.
Add reentrancy protection: use OpenZeppelin ReentrancyGuard and mark withdrawal functions nonReentrant.
Emit events on storing failed payouts and on successful withdrawals for monitoring.
(Optional) If admin withdrawal is required, implement adminWithdrawFailedCredits(address _receiver) with onlyOwner: it must clear failedTransferCredits[_receiver] and send funds to _receiver (not msg.sender), with the same CEI and reentrancy protections.
withdrawAllFailedCredits allows any user to withdraw another account’s failed transfer credits due to improper use of msg.sender instead of _receiver for balance reset and transfer.
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.