Bid Beasts

First Flight #49
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Incorrect mapping key usage in withdrawAllFailedCredits allows mismatch between _receiver and msg.sender

Root + Impact

Description

  • Expected behavior: Users should only be able to withdraw their own accumulated credits from failedTransferCredits.

  • Issue: The current implementation reads the balance from failedTransferCredits[_receiver] but resets failedTransferCredits[msg.sender]. This creates a mismatch between the retrieved amount and the cleared balance, which can lead to stuck funds or unauthorized withdrawals.

function withdrawAllFailedCredits(address _receiver) external {
@> uint256 amount = failedTransferCredits[_receiver]; // allows reading credits of arbitrary address
require(amount > 0, "No credits to withdraw");
@> failedTransferCredits[msg.sender] = 0; // clears msg.sender balance instead of _receiver
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Withdraw failed");
}

Risk

Likelihood: High

  • Every time the function is called with _receiver != msg.sender, balances will not align correctly.

  • This can either delete the wrong balance or leave funds stuck in the contract permanently.

Impact: Medium

  • Users may not be able to withdraw their credits correctly.

  • Potential for lost funds or the need for a contract upgrade/manual intervention.

Proof of Concept

// Setup:
// UserA has failedTransferCredits[UserA] = 1 ether;
// UserB has failedTransferCredits[UserB] = 0;
// UserB calls:
contract.withdrawAllFailedCredits(UserA);
// Inside the function:
// amount = failedTransferCredits[UserA]; // 1 ether
// failedTransferCredits[UserB] = 0; // clears the wrong account
// sends 1 ether to UserB
// Result: UserB receives UserA's credits
// failedTransferCredits[UserA] remains = 1 ether => funds stuck

Recommended Mitigation

  • Remove _receiver parameter → users can only withdraw their own credits (msg.sender).

  • Fix balance reset → zero out failedTransferCredits[msg.sender] before sending funds, preventing stuck funds or double payouts.

  • Add eventFailedCreditsWithdrawn logs withdrawals for transparency and easier auditing.

-function withdrawAllFailedCredits(address _receiver) external {
- uint256 amount = failedTransferCredits[_receiver];
- require(amount > 0, "No credits to withdraw");
-
- failedTransferCredits[msg.sender] = 0;
-
- (bool success, ) = payable(msg.sender).call{value: amount}("");
- require(success, "Withdraw failed");
-}
+function withdrawAllFailedCredits() external {
+ uint256 amount = failedTransferCredits[msg.sender];
+ require(amount > 0, "No credits to withdraw");
+
+ failedTransferCredits[msg.sender] = 0;
+
+ (bool success, ) = payable(msg.sender).call{value: amount}("");
+ require(success, "Withdraw failed");
+ emit FailedCreditsWithdrawn(msg.sender, amount);
+}
Updates

Lead Judging Commences

cryptoghost Lead Judge 21 days ago
Submission Judgement Published
Validated
Assigned finding tags:

BidBeast Marketplace: Unrestricted FailedCredits Withdrawal

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.

cryptoghost Lead Judge 21 days ago
Submission Judgement Published
Validated
Assigned finding tags:

BidBeast Marketplace: Unrestricted FailedCredits Withdrawal

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.

Support

FAQs

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