Skip to content

Latest commit

 

History

History

OnyxProtocol

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

Onyx Protocol

Step-by-step Overview

The Onyx Protocol vulnerability centered on manipulating the protocol's exchange rate calculation (exchangeRate = totalSupply / totalShares). An "empty market" condition was created when Proposal 22 was approved to integrate PEPE token into the protocol. Here's how the vulnerability was exploited:

  1. Setup (Get Flash Loan)

    • Flash loan 4000 WETH from Aave
    • Swap WETH for PEPE tokens
    • Create attack contract to manipulate market
  2. Market Manipulation (Empty Market Attack)

    • Mint small amount of oPEPE (1e18)
    • Redeem almost all oPEPE (leave 2 wei)
    • This creates minimal share tokens while keeping market active
  3. Price manipulation

    • Transfer large amount of PEPE to oPEPE market
    • This inflates the exchange rate since supply is minimal
    • Enter markets to enable borrowing
  4. Exploit Inflated Collateral

    • Use inflated oPEPE as collateral
    • Borrow nearly all ETH from oETH market
    • Exchange rate manipulation makes this possible
  5. Recover donated funds

    • Exploit rounding error to withdraw donated PEPE
    • Calculate exact amount needed for liquidation
    • Redeem underlying PEPE tokens
  6. Liquidation

    • Liquidate borrower position with 1 wei ETH
    • This triggers seizing of collateral
    • Mint precise amount of tokens to reset market
    • Redeem remaining collateral
    • Repay flash loan with profits

Detailed Description

  1. Gets 4000 WETH from Aave V3 and swaps it for PEPE tokens to prepare for the attack.
    AaveV3.flashLoanSimple(address(this), address(WETH), 4000 * 1e18, bytes(""), 0);
    Router.swapExactTokensForTokens(WETH.balanceOf(address(this)), amountOut, path, address(this), block.timestamp + 3600);
  1. Creates minimal share tokens while maintaining an active market by minting and immediately redeeming.
    oPEPE.mint(1e18);
    oPEPE.redeem(oPEPE.totalSupply() - 2); // Leave 2 wei
  1. Donate large amount of PEPE to artificially inflate the exchange rate due to minimal supply.
    PEPE.transfer(address(oPEPE), PEPE.balanceOf(address(this)));
    address[] memory oTokens = new address[](1);
    oTokens[0] = address(oPEPE);
    Unitroller.enterMarkets(oTokens);
  1. Uses inflated oPEPE as collateral to borrow almost all ETH from the oETH market.
    oETHER.borrow(oETHER.getCash() - 1);
    (bool success,) = msg.sender.call{value: address(this).balance}("");
  1. Exploits rounding error to withdraw donated PEPE and calculates precise liquidation amounts.
    oPEPE.redeemUnderlying(redeemAmt);
    (,,, uint256 exchangeRate) = oPEPE.getAccountSnapshot(address(this));
    (, uint256 numSeizeTokens) = Unitroller.liquidateCalculateSeizeTokens(address(oETHER), address(oPEPE), 1);
  1. Liquidates position and resets market state through precise token minting.
    uint256 mintAmount = (exchangeRate / 1e18) * numSeizeTokens - 2;
    oPEPE.mint(mintAmount);
    // Repeats process for other tokens (USDC, USDT, PAXG, DAI, WBTC, LINK)
    WETH.approve(address(AaveV3), amount + premium);

The attack was executed through three main contracts:

Exploit_Onyx_Protocol: Main contract handling flash loan and token swaps Attacker1Contracts: Handles the initial PEPE token manipulation drain ETH from the Onyx Attacker2Contracts: Replicates the attack for other tokens (USDC, USDT, PAXG, DAI, WBTC, LINK)

Possible mitigations

For new markets should consider including and preserving the order of these steps:

  1. Set CF to zero
  2. List market
  3. Mint cTokens
  4. Set CF to non-zero.

Sources and references