Christmas Dinner

First Flight #31
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Severity: high
Invalid

[H-1] Vulnerability in Refund Mechanism: Back-running Exploit Leading to Incorrect Fund Distribution

Summary

The scenario involves two users, User1 and User2, interacting with a smart contract that handles deposits and refunds of ERC20 tokens (e.g., WBTC). User1 deposits 1 WBTC, and then User2 back-runs with a higher deposit of 2 WBTC. After both deposits, User2 attempts to refund the entire balance of the contract, and both users end up with an equal amount of WBTC (2 WBTC), effectively splitting the contract's balance.

Vulnerability Details

  • Back-running Attack: User2 is able to front-run the contract by depositing a larger amount than User1, and then exploiting a refund mechanism to take more funds than their deposit.

  • Refund Mechanism Flaw: The refund logic appears to refund the full contract balance without properly verifying or distinguishing the individual user’s balance. As a result, both User1 and User2 receive an equal portion of the refunded amount, even though they deposited different amounts.

  • State Mismanagement: The system incorrectly allocates the entire contract balance to User2, but User1’s balance should have been refunded separately based on their own deposit, not the entire contract balance.

Impact

Users will not receive the correct refund amount.

Tools Used

Foundry

Here is the POC:

function test_backRunningTakeEntireBalanceWithRefund() public {
uint256 depositAmount = 1e18;
// User1 deposits first
vm.startPrank(user1);
console.log("User1 depositing %s WBTC", depositAmount);
cd.deposit(address(wbtc), depositAmount);
assertEq(wbtc.balanceOf(address(cd)), depositAmount);
console.log("User1 deposit successful, WBTC balance of contract: %s", wbtc.balanceOf(address(cd)));
vm.stopPrank();
// Now User2 attempts to back-run by observing User1's deposit
uint256 backRunAmount = 2e18; // Assume User2 decides to deposit more to take advantage
vm.startPrank(user2);
console.log("User2 back-running with a deposit of %s WBTC", backRunAmount);
cd.deposit(address(wbtc), backRunAmount);
assertEq(wbtc.balanceOf(address(cd)), depositAmount + backRunAmount);
console.log("User2 deposit successful, WBTC balance of contract: %s", wbtc.balanceOf(address(cd)));
vm.stopPrank();
// Now, User2 attempts to refund the entire balance
uint256 contractBalanceBeforeRefund = wbtc.balanceOf(address(cd));
console.log("User2 attempting to refund the entire contract balance of %s WBTC", contractBalanceBeforeRefund);
// Refund attempt by User2
vm.startPrank(user2);
cd.refund(); // User2 should receive a proportional refund
uint256 user2BalanceAfterRefund = wbtc.balanceOf(user2);
console.log("User2's balance after refund: %s WBTC", user2BalanceAfterRefund);
uint256 expectedUser2Refund = (contractBalanceBeforeRefund * backRunAmount) / (depositAmount + backRunAmount);
assertEq(user2BalanceAfterRefund, expectedUser2Refund, "User2's refund balance mismatch");
// Now User1 attempts to refund their own balance
vm.startPrank(user1);
cd.refund(); // User1 should also receive a proportional refund
uint256 user1BalanceAfterRefund = wbtc.balanceOf(user1);
console.log("User1's balance after refund: %s WBTC", user1BalanceAfterRefund);
uint256 expectedUser1Refund = (contractBalanceBeforeRefund * depositAmount) / (depositAmount + backRunAmount);
assertEq(user1BalanceAfterRefund, expectedUser1Refund, "User1's balance mismatch after refund");
// Check if contract balance is zero after refund
uint256 finalContractBalance = wbtc.balanceOf(address(cd));
assertEq(finalContractBalance, 0, "Refund failed, contract balance not zero");
vm.stopPrank();
}

Output is following

Recommendation:

The refund logic need to be worked on so that the balance for respective user can be updated accordingly.

Updates

Lead Judging Commences

0xtimefliez Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

syedghufranhassan Submitter
about 1 year ago
0xtimefliez Lead Judge
about 1 year ago
syedghufranhassan Submitter
about 1 year ago
0xtimefliez Lead Judge
about 1 year ago
syedghufranhassan Submitter
about 1 year ago
0xtimefliez Lead Judge
about 1 year ago
0xtimefliez Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!