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

Non-Atomic Transaction in switchUnderlyingToken Function Leading to Financial Exploitation

Summary

A vulnerability exists in the switchUnderlyingToken function of the UnripeFacet contract, where a timing gap between the balance check and the token switch can be exploited by a malicious user. This manipulation can lead to state inconsistencies and financial exploitation, where the non-atomic nature of the transaction can be exploited by a malicious user to increase their balance between checks. This can result in unintended gains and compromises the integrity of the underlying token switch process.

Vulnerability Details

The switchUnderlyingToken function checks the underlying balance of the specified Unripe Token and then switches the underlying token in separate steps, making the transaction non-atomic.

Proof of concept

1: Balance Check:

  • The function checks if the balance of the underlying tokens for the specified Unripe Token is zero.

2: Balance Manipulation:

  • After the balance check, the malicious user exploits the time gap to increase their balance by transferring tokens or using another function.

3: Underlying Token Switch:

  • The function then switches the underlying token based on the assumption that the balance was zero at the time of the check.

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 TestSwitchUnderlyingToken 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 testSwitchUnderlyingTokenExploit() public {
address user = address(0x1);
// Mint tokens to user
underlyingToken.mint(user, 100);
// Mock setup: set underlying balance to zero
s.sys.silo.unripeSettings[address(unripeToken)].balanceOfUnderlying = 0;
// User increases balance between check and switch
vm.startPrank(user);
underlyingToken.transfer(address(unripeFacet), 100); // Simulate balance increase
vm.stopPrank();
// Attempt to switch underlying token
vm.startPrank(user);
vm.expectRevert("Underlying balance must be zero");
unripeFacet.switchUnderlyingToken(address(unripeToken), address(underlyingToken));
vm.stopPrank();
}
}

Impact

  • Malicious users can exploit the time gap between the balance check and the token switch to manipulate their balance, leading to unintended gains and compromising the integrity of the token switch process.

Tools Used

Manual review

Recommendations

The function should be made atomic, ensuring that the balance check and the underlying token switch occur in a single transaction.

Enhanced switchUnderlyingToken Function

function switchUnderlyingToken(
address unripeToken,
address newUnderlyingToken
) 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");
// Ensure the underlying balance is zero before allowing the switch
require(s.sys.silo.unripeSettings[unripeToken].balanceOfUnderlying == 0, "Underlying balance must be zero");
// Perform the switch of the underlying token
LibUnripe.switchUnderlyingToken(unripeToken, newUnderlyingToken);
// Emit an event for tracking
emit SwitchUnderlyingToken(unripeToken, newUnderlyingToken);
}
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.