Good Entry - R-Nemes's results

The best day trading platform to make every trade entry a Good Entry.

General Information

Platform: Code4rena

Start Date: 01/08/2023

Pot Size: $91,500 USDC

Total HM: 14

Participants: 80

Period: 6 days

Judge: gzeon

Total Solo HM: 6

Id: 269

League: ETH

Good Entry

Findings Distribution

Researcher Performance

Rank: 24/80

Findings: 1

Award: $482.48

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: 3docSec

Also found by: R-Nemes, Vagner, auditsea, hassan-truscova, n1punp, nadin

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-58

Awards

482.4792 USDC - $482.48

External Links

Lines of code

https://github.com/code-423n4/2023-08-goodentry/blob/4b785d455fff04629d8675f21ef1d1632749b252/contracts/TokenisableRange.sol#L333-L339

Vulnerability details

Impact

This vulnerability could lead to unexpected results or behaviors due to the overflow incident. In the worst-case scenario, this could potentially result in losses of funds, due to inaccurate fee calculation when claiming fees.

When converting to uint160 if the inner calculation is larger than type(uint160).max the value will be truncated returning an underestimated value.

Proof of Concept

In TokenisableRange.sol:337, when the balance of underlying assets based on the assets price is calculated, there is a potential for silent overflow.

uint160( sqrt( (2 ** 192 * ((TOKEN0_PRICE * 10 ** TOKEN1.decimals) / TOKEN1_PRICE)) / ( 10 ** TOKEN0.decimals ) )

https://github.com/code-423n4/2023-08-goodentry/blob/4b785d455fff04629d8675f21ef1d1632749b252/contracts/TokenisableRange.sol#L333-L339

  function returnExpectedBalanceWithoutFees(uint TOKEN0_PRICE, uint TOKEN1_PRICE) internal view returns (uint256 amt0, uint256 amt1) {
    // if 0 get price from oracle
    if (TOKEN0_PRICE == 0) TOKEN0_PRICE = ORACLE.getAssetPrice(address(TOKEN0.token));
    if (TOKEN1_PRICE == 0) TOKEN1_PRICE = ORACLE.getAssetPrice(address(TOKEN1.token));
    // @audit-issue silent overflow
    (amt0, amt1) = LiquidityAmounts.getAmountsForLiquidity( uint160( sqrt( (2 ** 192 * ((TOKEN0_PRICE * 10 ** TOKEN1.decimals) / TOKEN1_PRICE)) / ( 10 ** TOKEN0.decimals ) ) ), TickMath.getSqrtRatioAtTick(lowerTick), TickMath.getSqrtRatioAtTick(upperTick),  liquidity);
  }
function claimFee() public {
  (uint256 newFee0, uint256 newFee1) = POS_MGR.collect( 
    INonfungiblePositionManager.CollectParams({
      tokenId: tokenId,
      recipient: address(this),
      amount0Max: type(uint128).max,
      amount1Max: type(uint128).max
    })
  );
  // If there's no new fees generated, skip compounding logic;
  if ((newFee0 == 0) && (newFee1 == 0)) return;  
  uint tf0 = newFee0 * treasuryFee / 100;
  uint tf1 = newFee1 * treasuryFee / 100;
  if (tf0 > 0) TOKEN0.token.safeTransfer(treasury, tf0);
  if (tf1 > 0) TOKEN1.token.safeTransfer(treasury, tf1);
  
  fee0 = fee0 + newFee0 - tf0;
  fee1 = fee1 + newFee1 - tf1;
  
  // Calculate expected balance,  
---> (uint256 bal0, uint256 bal1) = returnExpectedBalanceWithoutFees(0, 0);
  
  // If accumulated more than 1% worth of fees, compound by adding fees to Uniswap position
  if ((fee0 * 100 > bal0 ) && (fee1 * 100 > bal1)) {
    TOKEN0.token.safeIncreaseAllowance(address(POS_MGR), fee0);
    TOKEN1.token.safeIncreaseAllowance(address(POS_MGR), fee1);
    (uint128 newLiquidity, uint256 added0, uint256 added1) = POS_MGR.increaseLiquidity(
      INonfungiblePositionManager.IncreaseLiquidityParams({
        tokenId: tokenId,
        amount0Desired: fee0,
        amount1Desired: fee1,
        amount0Min: 0,
        amount1Min: 0,
        deadline: block.timestamp
      })
    );
    // check slippage: validate against value since token amounts can move widely
    uint token0Price = ORACLE.getAssetPrice(address(TOKEN0.token));
    uint token1Price = ORACLE.getAssetPrice(address(TOKEN1.token));
    uint addedValue = added0 * token0Price / 10**TOKEN0.decimals + added1 * token1Price / 10**TOKEN1.decimals;
    uint totalValue =   bal0 * token0Price / 10**TOKEN0.decimals +   bal1 * token1Price / 10**TOKEN1.decimals;
    uint liquidityValue = totalValue * newLiquidity / liquidity;
    require(addedValue > liquidityValue * 95 / 100 && liquidityValue > addedValue * 95 / 100, "TR: Claim Fee Slippage"); // @audit-issue loss of precision
    fee0 -= added0;
    fee1 -= added1;
    liquidity = liquidity + newLiquidity; 
  }
  emit ClaimFees(newFee0, newFee1);
}

Tools Used

Manual Review Foundry Fuzz testing

Adding safemath checks (or a more recent version of solidity's built-in overflow checks) for all arithmetic operations or use libraries known for handling such situations like OpenZeppelin's SafeMath library can help prevent these overflow issues.

Also use a larger value type such as uint256.

Assessed type

Under/Overflow

#0 - c4-pre-sort

2023-08-10T04:39:43Z

141345 marked the issue as duplicate of #162

#1 - c4-pre-sort

2023-08-10T13:48:19Z

141345 marked the issue as not a duplicate

#2 - c4-pre-sort

2023-08-10T13:48:34Z

141345 marked the issue as duplicate of #58

#3 - c4-judge

2023-08-20T17:30:19Z

gzeon-c4 changed the severity to 3 (High Risk)

#4 - c4-judge

2023-08-20T17:30:23Z

gzeon-c4 marked the issue as satisfactory

Findings Information

🌟 Selected for report: 3docSec

Also found by: R-Nemes, Vagner, auditsea, hassan-truscova, n1punp, nadin

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-58

Awards

482.4792 USDC - $482.48

External Links

Lines of code

https://github.com/code-423n4/2023-08-goodentry/blob/4b785d455fff04629d8675f21ef1d1632749b252/contracts/TokenisableRange.sol#L333-L339 https://github.com/code-423n4/2023-08-goodentry/blob/4b785d455fff04629d8675f21ef1d1632749b252/contracts/TokenisableRange.sol#L167-L215

Vulnerability details

Impact

The calculation of sqrt( (2 ** 192 * ((TOKEN0_PRICE * 10 ** TOKEN1.decimals) / TOKEN1_PRICE)) / ( 10 ** TOKEN0.decimals ) ) in the function returnExpectedBalanceWithoutFees can easily overflow cuasing the function to revert and thereby preventing users from claiming fees. This is most likely with low market cap tokens. The sponser has indicated that they would like to include low market cap tokens in the future.

Proof of Concept

In TokenisableRange.sol:337, when the balance of underlying assets based on the assets price is calculated, there is a potential for overflow, causing a revert.

sqrt( (2 ** 192 * ((TOKEN0_PRICE * 10 ** TOKEN1.decimals) / TOKEN1_PRICE)) / ( 10 ** TOKEN0.decimals )

https://github.com/code-423n4/2023-08-goodentry/blob/4b785d455fff04629d8675f21ef1d1632749b252/contracts/TokenisableRange.sol#L333-L339

  function returnExpectedBalanceWithoutFees(uint TOKEN0_PRICE, uint TOKEN1_PRICE) internal view returns (uint256 amt0, uint256 amt1) {
    // if 0 get price from oracle
    if (TOKEN0_PRICE == 0) TOKEN0_PRICE = ORACLE.getAssetPrice(address(TOKEN0.token));
    if (TOKEN1_PRICE == 0) TOKEN1_PRICE = ORACLE.getAssetPrice(address(TOKEN1.token));
    // @audit-issue silent overflow
    (amt0, amt1) = LiquidityAmounts.getAmountsForLiquidity( uint160( sqrt( (2 ** 192 * ((TOKEN0_PRICE * 10 ** TOKEN1.decimals) / TOKEN1_PRICE)) / ( 10 ** TOKEN0.decimals ) ) ), TickMath.getSqrtRatioAtTick(lowerTick), TickMath.getSqrtRatioAtTick(upperTick),  liquidity);
  }

https://github.com/code-423n4/2023-08-goodentry/blob/4b785d455fff04629d8675f21ef1d1632749b252/contracts/TokenisableRange.sol#L167-L215

function claimFee() public {
    (uint256 newFee0, uint256 newFee1) = POS_MGR.collect( 
      INonfungiblePositionManager.CollectParams({
        tokenId: tokenId,
        recipient: address(this),
        amount0Max: type(uint128).max,
        amount1Max: type(uint128).max
      })
    );
    // If there's no new fees generated, skip compounding logic;
    if ((newFee0 == 0) && (newFee1 == 0)) return;  
    uint tf0 = newFee0 * treasuryFee / 100;
    uint tf1 = newFee1 * treasuryFee / 100;
    if (tf0 > 0) TOKEN0.token.safeTransfer(treasury, tf0);
    if (tf1 > 0) TOKEN1.token.safeTransfer(treasury, tf1);
    
    fee0 = fee0 + newFee0 - tf0;
    fee1 = fee1 + newFee1 - tf1;
    
    // Calculate expected balance,  
    (uint256 bal0, uint256 bal1) = returnExpectedBalanceWithoutFees(0, 0);
    
    // If accumulated more than 1% worth of fees, compound by adding fees to Uniswap position
    if ((fee0 * 100 > bal0 ) && (fee1 * 100 > bal1)) { // @audit-info this checks for at least one percent on each token
      TOKEN0.token.safeIncreaseAllowance(address(POS_MGR), fee0);
      TOKEN1.token.safeIncreaseAllowance(address(POS_MGR), fee1);
      (uint128 newLiquidity, uint256 added0, uint256 added1) = POS_MGR.increaseLiquidity(
        INonfungiblePositionManager.IncreaseLiquidityParams({
          tokenId: tokenId,
          amount0Desired: fee0,
          amount1Desired: fee1,
          amount0Min: 0,
          amount1Min: 0,
          deadline: block.timestamp
        })
      );
      // check slippage: validate against value since token amounts can move widely
      uint token0Price = ORACLE.getAssetPrice(address(TOKEN0.token));
      uint token1Price = ORACLE.getAssetPrice(address(TOKEN1.token));
      uint addedValue = added0 * token0Price / 10**TOKEN0.decimals + added1 * token1Price / 10**TOKEN1.decimals;
      uint totalValue =   bal0 * token0Price / 10**TOKEN0.decimals +   bal1 * token1Price / 10**TOKEN1.decimals;
      uint liquidityValue = totalValue * newLiquidity / liquidity; // @audit-issue precision error
      require(addedValue > liquidityValue * 95 / 100 && liquidityValue > addedValue * 95 / 100, "TR: Claim Fee Slippage");
      fee0 -= added0;
      fee1 -= added1;
      liquidity = liquidity + newLiquidity;
    }
    emit ClaimFees(newFee0, newFee1);
  }

Tools Used

Manual Review Foundry Fuzz testing

re formalate the equation to ensure no overflow.

Assessed type

Under/Overflow

#0 - c4-pre-sort

2023-08-10T04:40:40Z

141345 marked the issue as duplicate of #162

#1 - c4-pre-sort

2023-08-10T13:46:41Z

141345 marked the issue as not a duplicate

#2 - c4-pre-sort

2023-08-10T13:47:20Z

141345 marked the issue as duplicate of #58

#3 - c4-judge

2023-08-20T17:30:19Z

gzeon-c4 changed the severity to 3 (High Risk)

#4 - c4-judge

2023-08-20T17:30:23Z

gzeon-c4 marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter