DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: high
Invalid

Front-Running Risk in ERC20 Approval Mechanism Leading to Potential Transaction Failures and Exploitation

Summary

The addMigratedUnderlying function in the UnripeFacet is vulnerable to front-running attacks due to the ERC20 approval mechanism. A malicious user can exploit this by observing approval transactions and quickly using up the approved tokens before the intended transaction, causing it to fail. This issue can lead to financial exploitation, transaction failures, and increased gas costs.

Proof of concept

Step-by-Step Exploitation by a Malicious User:

1: User Sets Approval:

  • The user approves 100 tokens to be spent by a contract.

IERC20(token).approve(contractAddress, 100);

2: Malicious User Observes the Approval:

  • The malicious user monitors the blockchain for approval transactions.

3: Front-Running the Intended Transaction:

  • The malicious user submits a transaction to use up the approved tokens before the user's intended transaction is executed. This is often done by setting a higher gas price to ensure their transaction is processed first.

IERC20(token).transferFrom(userAddress, maliciousAddress, 100);

4: User’s Transaction Fails:

  • When the user’s intended transaction is processed, it fails due to insufficient allowance

contract.someFunctionRequiringTokens(100); // This fails

Test

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../contracts/UnripeFacet.sol";
import "../contracts/libraries/LibAppStorage.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract TestFrontRunning is Test {
UnripeFacet unripeFacet;
ERC20 unripeToken;
ERC20 underlyingToken;
AppStorage s;
function setUp() public {
unripeFacet = new UnripeFacet();
unripeToken = new ERC20("Unripe Token", "URT");
underlyingToken = new ERC20("Underlying Token", "ULT");
// Initialize other necessary contracts and states
}
function testFrontRunningExploit() public {
address user = address(0x1);
address maliciousUser = address(0x2);
// Mint tokens to user and malicious user
underlyingToken.mint(user, 200);
underlyingToken.mint(maliciousUser, 200);
// User approves 100 tokens to be spent by the contract
vm.startPrank(user);
underlyingToken.approve(address(unripeFacet), 100);
vm.stopPrank();
// Malicious user observes the approval and front-runs the transaction
vm.startPrank(maliciousUser);
underlyingToken.transferFrom(user, maliciousUser, 100);
vm.stopPrank();
// User attempts to call addMigratedUnderlying, but it should fail due to insufficient allowance
vm.startPrank(user);
vm.expectRevert("Insufficient token allowance");
unripeFacet.addMigratedUnderlying(address(unripeToken), 100);
vm.stopPrank();
}
}

Impact

  • Front-running transactions can deplete the approved token amount, causing subsequent transactions to fail due to insufficient allowance.

  • The protocol may experience financial losses due to the manipulation of token allowances and unauthorized transfers, reducing the overall token supply and liquidity.

  • Users may lose their tokens due to unauthorized transfers, resulting in direct financial losses and reduced trust in the protocol.

Tools Used

Manual Review

Recommendations

To mitigate this issue, the contract should be enhanced with additional checks and mechanisms. Here’s how it can be implemented:

function addMigratedUnderlying(
address unripeToken,
uint256 amount
) external payable fundsSafu noNetFlow noSupplyChange nonReentrant {
LibDiamond.enforceIsContractOwner();
AppStorage storage s = LibAppStorage.diamondStorage();
// Check if the Unripe Token exists
require(s.sys.silo.unripeSettings[unripeToken].underlyingToken != address(0), "Unripe token not configured");
// Check if the contract has sufficient allowance
uint256 allowance = IERC20(s.sys.silo.unripeSettings[unripeToken].underlyingToken).allowance(msg.sender, address(this));
require(allowance >= amount, "Insufficient token allowance");
// Transfer the underlying tokens from the caller to the contract
IERC20(s.sys.silo.unripeSettings[unripeToken].underlyingToken).safeTransferFrom(
msg.sender,
address(this),
amount
);
// Update the underlying balance
LibUnripe.incrementUnderlying(unripeToken, amount);
// Emit an event
emit MigratedUnderlyingAdded(unripeToken, amount);
}
Updates

Lead Judging Commences

inallhonesty 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.