Obelisk logo

Home

About

Blog

Audits

Contact

Blog

Apr 19, 2022

Donut

Redemption Exploit Report

Exploit report

5 min read

Article 'Redemption Exploit Report' heading image

Share

On 2022-04-18, the 2OMB team commissioned an audit of their new decentralized exchange project called Redemption. Later that day, the project was exploited for a total of 74246 FTM.

This article will explore the vulnerability which lead to the exploit as well as a summary of the investigation.

Project Summary

Redemption is a fork of Uniswap V2 with a few modifications:

  • Solidity upgraded to 0.6.12
  • Various contracts renamed to Redemption
  • Variable fee rates with a denominator of 10000
  • Custom migration code

Exploited contracts

  • WFTM-2OMB Redemption LP: 0x5D59cDaB08C8BbE4986173a628f8305D52B1b4AE
  • WFTM-2SHARE Redemption LP: 0x7F79f042Cbb13ad03a8e8dB5CBB6F737D77E9737
  • 3OMB-WFTM Redemption LP: 0xf5f2Fa1fbafD15759FaBC4ffA05fa961931c812b
  • WFTM-3SHARE Redemption LP: 0xbA770f0c07c36E5369Fd14DC5bE380cCd1aa1633
  • DEVIL-WFTM Redemption LP: 0x0c2c0bf53265FE454c24B639428682767F128DF6

Other project contracts:

  • RedemptionRouter02: 0x71F017289f8243Ccab5928a27f946617c76f17Ef
  • RedemptionFactory: 0x50B45D4E37dF16d880eD6d37C3737Bb4126033bA

Other related contracts:

  • 2OMB Token: 0x7a6e4E3CC2ac9924605DCa4bA31d1831c84b44aE
  • WFTM-2OMB Spooky LP: 0xbdC7DFb7B88183e87f003ca6B5a2F81202343478
  • 2SHARE Token: 0xc54A1684fD1bef1f077a336E6be4Bd9a3096a6Ca
  • WFTM-2SHARE Spooky LP: 0x6398ACBBAB2561553a9e458Ab67dCFbD58944e52
  • 3OMB Token: 0x14DEf7584A6c52f470Ca4F4b9671056b22f4FfDE
  • 3OMB-WFTM Spooky LP: 0x83A52eff2E9D112E9B022399A9fD22a9DB7d33Ae
  • 3SHARE Token: 0x6437ADAC543583C4b31Bf0323A0870430F5CC2e7
  • WFTM-3SHARE Spooky LP: 0xd352daC95a91AfeFb112DBBB3463ccfA5EC15b65
  • DEVIL Token: 0x174c7106AEeCdC11389f7dD21342F05f46CCB40F
  • DEVIL-WFTM Spooky LP: 0x1b952e817Af5982948fE732094dE3Fbb9411161C

Contract Vulnerability

The fundamental vulnerability occurs in the RedemptionPair contract. This is the liquidity pair (LP) contract for the exchange.

The contract’s swap() function has the following code to handle fees:

{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
    uint balance0Adjusted = balance0.mul(10000).sub(amount0In.mul(feeAmount));
    uint balance1Adjusted = balance1.mul(10000).sub(amount1In.mul(feeAmount));
    require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(10000**2), 'RedemptionV2: K');
}

{// scope for referer management
    uint controllerFeeAmount = controllerFeeAddress != address(0) ? controllerFeeShare : 0;
    if (controllerFeeAmount > 0) {
        if (amount0In > 0) {
            address _token0 = token0;
            _safeTransfer(_token0, controllerFeeAddress, amount0In.mul(controllerFeeAmount) / 10000);
            balance0 = IERC20Redemption(_token0).balanceOf(address(this));
        }
        if (amount1In > 0) {
            address _token1 = token1;
            _safeTransfer(_token1, controllerFeeAddress, amount1In.mul(controllerFeeAmount) / 10000);
            balance1 = IERC20Redemption(_token1).balanceOf(address(this));
        }
    }
}

The first part (labelled scope for reserve{0,1}) of the fee calculation matches the original Uniswap LP contract and will reward liquidity providers. However, the second part (labelled scope for referer management) is a customized fee for the project. For clarity, these fees will be referred to as the liquidity fee and the project fee.

Both fees are calculated based on the amount that a user sends to the contract during a swap. During normal swapping operations, this is the price of the tokens being purchased. However, to facilitate rebalancing multiple exchanges, the contract also allows users to request a flashloan as part of a swap. In this case, the borrowed amount is added to the fee calculation.

Unlike the liquidity fee, the project fee violates the constant product formula (aka. the Uniswap V2 invariant). For a Uniswap LP the product of the “trades must not change the product (k) of a pair’s reserve balances (x and y)”. In other words, x * y = k. (Refer to the Uniswap documentation)

In the Redemption LP contract, the project fee is taken after the invariant check. As a result, if the controllerFeeAmount is non-zero, any swap will transfer some tokens to the controllerFeeAddress. This will resulting in a reduced value of at least one of x and y as well as k. For users, this represents a decrease in the LP token’s value.

Based on the contract code, the default values of the fees are 0% for LP providers (feeAmount) and 0.3% for the project (controllerFeeAmount). At time of writing, all existing LPs have a 0% fee for LP providers and a mix of 0 and 0.3% for the project.

As a result of the vulnerability, an attacker can use the cost free flashloans to drain either of the reserves and manipulate the price. All liquidity pairs with non-zero project fees are vulnerable to similar attacks as they will also violate the Uniswap V2 invariant.

The Attack

The attacker used the following address to run the exploit: 0xD106BB2f7b4bf6Ffa12C2dB1cbD0D3c25eE18ef9

This address deployed the following exploit contract: 0x77A5D0CDd1F4069747d9236B50f09f34B6D5b378

The operation of the exploit contract is as follows:

  • The exploit contract requests a flashloan from the Redemption LP for the entire balance of a paired token.
  • The exploit contract immediately repays the flashloan. There is no liquidity fee and so this is free.
  • The liquidity pair then transfers the project fee (losing value). This changes the price of the token.
  • This process is repeated several times, causing the price to increase out of sync with other exchanges.
  • Finally, an arbitrage swap is performed with a matching Spooky LP.

As an example, the first transaction the exploit contract executed performed the flashloan step 80 times on the 2OMB-WFTM pair before doing the arbitrage, extracting 3002 FTM from the WFTM-2OMB Redemption LP. Each iteration of the flashloan swaps 0.3% less than the last.

The attack occurred over 80 transactions and stole the following amounts:

  • WFTM-2OMB Redemption LP: 35603 FTM
  • WFTM-2SHARE Redemption LP: 18088 FTM
  • 3OMB-WFTM Redemption LP: 19816 FTM
  • WFTM-3SHARE Redemption LP: 173 FTM
  • DEVIL-WFTM Redemption LP: 565 FTM

Tracking The Funds

68067 FTM was transferred to another contract over 8 transactions: 0xcc1Cc8Da6f1c9348571d1FB73253d2a0F236dfc5. The FTM was converted into 74258 fUSDT during the transfer.

35707 fUSDT was transferred to an external account (0x1CFE21CBDA526a7564A2eD43b46d863b64862905) where it currently remains (transaction)

A small portion of the fUSDT was sent to an otherwise inactive address (0xAb05b999ccA907f07C9FC64cCea269c95d631D4D).

Another 4917 FTM was transferred to 0xEBf4FBB9C81b84dd5CF89BC75588E5d0018501b3 via an intermediate address. This address holds over 31M FTM and is incredibly active, with over 900k transactions. Most of the transactions appear to be transfers of native FTM.

The remaining 1262 FTM was mostly consumed in gas fees. A quick analysis of the account shows that it spent a total of 1380 FTM on gas, with the most expensive transaction costing 31.5 FTM.

Conclusion And Recommendations

At present, Obelisk does not see any evidence that the exploit was intentional on the part of the project team. However, the state of the contracts is such that they cannot be conclusively resolved without re-deploying revised contracts.

For now, the project team can avoid further exploits on the current contracts by setting the project fee to 0 to prevent any further changes to the Uniswap V2 invariant. Setting the liquidity fee to at least the project fee would ensure that the LP token does not lose value during swaps.

Users are advised to withdraw all remaining liquidity from the pools and to avoid creating new liquidity pools.

This incident serves as a reminder to projects to fully understand contracts before making customized modifications. We also encourage projects to rigorously test their contracts before deploying. We also recommend that auditing the code is considered a key part of the development cycle.

Apr 19, 2022

Author: Donut

5 min read

Exploit report

Redemption Exploit Report

Article 'Redemption Exploit Report' heading image

On 2022-04-18, the 2OMB team commissioned an audit of their new decentralized exchange project called Redemption. Later that day, the project was exploited for a total of 74246 FTM.

This article will explore the vulnerability which lead to the exploit as well as a summary of the investigation.

Project Summary

Redemption is a fork of Uniswap V2 with a few modifications:

  • Solidity upgraded to 0.6.12
  • Various contracts renamed to Redemption
  • Variable fee rates with a denominator of 10000
  • Custom migration code

Exploited contracts

  • WFTM-2OMB Redemption LP: 0x5D59cDaB08C8BbE4986173a628f8305D52B1b4AE
  • WFTM-2SHARE Redemption LP: 0x7F79f042Cbb13ad03a8e8dB5CBB6F737D77E9737
  • 3OMB-WFTM Redemption LP: 0xf5f2Fa1fbafD15759FaBC4ffA05fa961931c812b
  • WFTM-3SHARE Redemption LP: 0xbA770f0c07c36E5369Fd14DC5bE380cCd1aa1633
  • DEVIL-WFTM Redemption LP: 0x0c2c0bf53265FE454c24B639428682767F128DF6

Other project contracts:

  • RedemptionRouter02: 0x71F017289f8243Ccab5928a27f946617c76f17Ef
  • RedemptionFactory: 0x50B45D4E37dF16d880eD6d37C3737Bb4126033bA

Other related contracts:

  • 2OMB Token: 0x7a6e4E3CC2ac9924605DCa4bA31d1831c84b44aE
  • WFTM-2OMB Spooky LP: 0xbdC7DFb7B88183e87f003ca6B5a2F81202343478
  • 2SHARE Token: 0xc54A1684fD1bef1f077a336E6be4Bd9a3096a6Ca
  • WFTM-2SHARE Spooky LP: 0x6398ACBBAB2561553a9e458Ab67dCFbD58944e52
  • 3OMB Token: 0x14DEf7584A6c52f470Ca4F4b9671056b22f4FfDE
  • 3OMB-WFTM Spooky LP: 0x83A52eff2E9D112E9B022399A9fD22a9DB7d33Ae
  • 3SHARE Token: 0x6437ADAC543583C4b31Bf0323A0870430F5CC2e7
  • WFTM-3SHARE Spooky LP: 0xd352daC95a91AfeFb112DBBB3463ccfA5EC15b65
  • DEVIL Token: 0x174c7106AEeCdC11389f7dD21342F05f46CCB40F
  • DEVIL-WFTM Spooky LP: 0x1b952e817Af5982948fE732094dE3Fbb9411161C

Contract Vulnerability

The fundamental vulnerability occurs in the RedemptionPair contract. This is the liquidity pair (LP) contract for the exchange.

The contract’s swap() function has the following code to handle fees:

{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
    uint balance0Adjusted = balance0.mul(10000).sub(amount0In.mul(feeAmount));
    uint balance1Adjusted = balance1.mul(10000).sub(amount1In.mul(feeAmount));
    require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(10000**2), 'RedemptionV2: K');
}

{// scope for referer management
    uint controllerFeeAmount = controllerFeeAddress != address(0) ? controllerFeeShare : 0;
    if (controllerFeeAmount > 0) {
        if (amount0In > 0) {
            address _token0 = token0;
            _safeTransfer(_token0, controllerFeeAddress, amount0In.mul(controllerFeeAmount) / 10000);
            balance0 = IERC20Redemption(_token0).balanceOf(address(this));
        }
        if (amount1In > 0) {
            address _token1 = token1;
            _safeTransfer(_token1, controllerFeeAddress, amount1In.mul(controllerFeeAmount) / 10000);
            balance1 = IERC20Redemption(_token1).balanceOf(address(this));
        }
    }
}

The first part (labelled scope for reserve{0,1}) of the fee calculation matches the original Uniswap LP contract and will reward liquidity providers. However, the second part (labelled scope for referer management) is a customized fee for the project. For clarity, these fees will be referred to as the liquidity fee and the project fee.

Both fees are calculated based on the amount that a user sends to the contract during a swap. During normal swapping operations, this is the price of the tokens being purchased. However, to facilitate rebalancing multiple exchanges, the contract also allows users to request a flashloan as part of a swap. In this case, the borrowed amount is added to the fee calculation.

Unlike the liquidity fee, the project fee violates the constant product formula (aka. the Uniswap V2 invariant). For a Uniswap LP the product of the “trades must not change the product (k) of a pair’s reserve balances (x and y)”. In other words, x * y = k. (Refer to the Uniswap documentation)

In the Redemption LP contract, the project fee is taken after the invariant check. As a result, if the controllerFeeAmount is non-zero, any swap will transfer some tokens to the controllerFeeAddress. This will resulting in a reduced value of at least one of x and y as well as k. For users, this represents a decrease in the LP token’s value.

Based on the contract code, the default values of the fees are 0% for LP providers (feeAmount) and 0.3% for the project (controllerFeeAmount). At time of writing, all existing LPs have a 0% fee for LP providers and a mix of 0 and 0.3% for the project.

As a result of the vulnerability, an attacker can use the cost free flashloans to drain either of the reserves and manipulate the price. All liquidity pairs with non-zero project fees are vulnerable to similar attacks as they will also violate the Uniswap V2 invariant.

The Attack

The attacker used the following address to run the exploit: 0xD106BB2f7b4bf6Ffa12C2dB1cbD0D3c25eE18ef9

This address deployed the following exploit contract: 0x77A5D0CDd1F4069747d9236B50f09f34B6D5b378

The operation of the exploit contract is as follows:

  • The exploit contract requests a flashloan from the Redemption LP for the entire balance of a paired token.
  • The exploit contract immediately repays the flashloan. There is no liquidity fee and so this is free.
  • The liquidity pair then transfers the project fee (losing value). This changes the price of the token.
  • This process is repeated several times, causing the price to increase out of sync with other exchanges.
  • Finally, an arbitrage swap is performed with a matching Spooky LP.

As an example, the first transaction the exploit contract executed performed the flashloan step 80 times on the 2OMB-WFTM pair before doing the arbitrage, extracting 3002 FTM from the WFTM-2OMB Redemption LP. Each iteration of the flashloan swaps 0.3% less than the last.

The attack occurred over 80 transactions and stole the following amounts:

  • WFTM-2OMB Redemption LP: 35603 FTM
  • WFTM-2SHARE Redemption LP: 18088 FTM
  • 3OMB-WFTM Redemption LP: 19816 FTM
  • WFTM-3SHARE Redemption LP: 173 FTM
  • DEVIL-WFTM Redemption LP: 565 FTM

Tracking The Funds

68067 FTM was transferred to another contract over 8 transactions: 0xcc1Cc8Da6f1c9348571d1FB73253d2a0F236dfc5. The FTM was converted into 74258 fUSDT during the transfer.

35707 fUSDT was transferred to an external account (0x1CFE21CBDA526a7564A2eD43b46d863b64862905) where it currently remains (transaction)

A small portion of the fUSDT was sent to an otherwise inactive address (0xAb05b999ccA907f07C9FC64cCea269c95d631D4D).

Another 4917 FTM was transferred to 0xEBf4FBB9C81b84dd5CF89BC75588E5d0018501b3 via an intermediate address. This address holds over 31M FTM and is incredibly active, with over 900k transactions. Most of the transactions appear to be transfers of native FTM.

The remaining 1262 FTM was mostly consumed in gas fees. A quick analysis of the account shows that it spent a total of 1380 FTM on gas, with the most expensive transaction costing 31.5 FTM.

Conclusion And Recommendations

At present, Obelisk does not see any evidence that the exploit was intentional on the part of the project team. However, the state of the contracts is such that they cannot be conclusively resolved without re-deploying revised contracts.

For now, the project team can avoid further exploits on the current contracts by setting the project fee to 0 to prevent any further changes to the Uniswap V2 invariant. Setting the liquidity fee to at least the project fee would ensure that the LP token does not lose value during swaps.

Users are advised to withdraw all remaining liquidity from the pools and to avoid creating new liquidity pools.

This incident serves as a reminder to projects to fully understand contracts before making customized modifications. We also encourage projects to rigorously test their contracts before deploying. We also recommend that auditing the code is considered a key part of the development cycle.

Share

Obelisk logo

Follow

© 2022 Obelisk - All rights reserved.
Obelisk is part of the Tibereum Group.