Taking a Closer Look at WOOFi Exploit

7 min read

Learn how WOOFi was exploited, resulting in a loss of assets worth $8.75 million.

TL;DR#

On March 5, 2024, WOOFi was exploited on the Arbitrum Chain due to a smart contract vulnerability, which resulted in a loss of assets worth approximately $8.75 million.

Introduction to WOOFi#

WOOFi is a cross-chain decentralized exchange.

Vulnerability Assessment#

The root cause of the exploit is the price manipulation of the underlying assets.

Steps#

Step 1:

We attempt to analyze the attack transaction executed by the exploiter.

Step 2:


The exploiter took a flash loan of approximately $10.58 million USDC_E and 2.7 million WOO tokens and then performed frequent token swaps.

/// @inheritdoc IWooPPV2
function swap(address fromToken, address toToken, uint256 fromAmount, uint256 minToAmount, address to, address rebateTo) external override returns (uint256 realToAmount) {
  if (fromToken == quoteToken) {
    // case 1: quoteToken --> baseToken
    realToAmount = _sellQuote(toToken, fromAmount, minToAmount, to, rebateTo);
  } else if (toToken == quoteToken) {
    // case 2: fromToken --> quoteToken
    realToAmount = _sellBase(fromToken, fromAmount, minToAmount, to, rebateTo);
  } else {
    // case 3: fromToken --> toToken (base to base)
    realToAmount = _swapBaseToBase(fromToken, toToken, fromAmount, minToAmount, to, rebateTo);
  }
}

Step 3:

The sPMM algorithm in the vulnerable WooPPV2 contract, which controlled the pricing on WOOFi swaps, was exploited due to the low liquidity in the pool, thereby manipulating the price of WOO tokens.

The attacker repeatedly swapped the assets over multiple transactions and repaid the borrowed flash loan at a cheaper price thereby taking total profits of $8.75 million.

Step 4:

While calculating the token price, the `_calcQuoteAmountSellBase` function calculates the amount by performing multiplication and division operations on the base price without any slippage in the exchange process, allowing the price to freely fluctuate.

function _calcQuoteAmountSellBase(address baseToken, uint256 baseAmount, IWooracleV2.State memory state) private view returns (uint256 quoteAmount, uint256 newPrice) {
  require(state.woFeasible, "WooPPV2: !ORACLE_FEASIBLE");

  DecimalInfo memory decs = decimalInfo(baseToken);

  // quoteAmount = baseAmount * oracle.price * (1 - oracle.k * baseAmount * oracle.price - oracle.spread)
  {
    uint256 coef = uint256(1e18) - ((uint256(state.coeff) * baseAmount * state.price) / decs.baseDec / decs.priceDec) - state.spread;
    quoteAmount = (((baseAmount * decs.quoteDec * state.price) / decs.priceDec) * coef) / 1e18 / decs.baseDec;
  }

  // newPrice = (1 - 2 * k * oracle.price * baseAmount) * oracle.price
  newPrice = ((uint256(1e18) - (uint256(2) * state.coeff * state.price * baseAmount) / decs.priceDec / decs.baseDec) * state.price) / 1e18;
}

Step 5:

Conventionally, the sPMM in their contract will override the oracle price according to the notional value of the user's trade in order to adjust the slippage and keep the pool in a more balanced state.

function _sellQuote(address baseToken, uint256 quoteAmount, uint256 minBaseAmount, address to, address rebateTo) private nonReentrant whenNotPaused returns (uint256 baseAmount) {
  require(baseToken != address(0), "WooPPV2: !baseToken");
  require(to != address(0), "WooPPV2: !to");
  require(baseToken != quoteToken, "WooPPV2: baseToken==quoteToken");

  require(balance(quoteToken) - tokenInfos[quoteToken].reserve >= quoteAmount, "WooPPV2: QUOTE_BALANCE_NOT_ENOUGH");

  uint256 swapFee = (quoteAmount * tokenInfos[baseToken].feeRate) / 1e5;
  quoteAmount = quoteAmount - swapFee;
  unclaimedFee = unclaimedFee + swapFee;

  {
    uint256 newPrice;
    IWooracleV2.State memory state = IWooracleV2(wooracle).state(baseToken);
    (baseAmount, newPrice) = _calcBaseAmountSellQuote(baseToken, quoteAmount, state);
    IWooracleV2(wooracle).postPrice(baseToken, uint128(newPrice));
    // console.log('Post new price:', newPrice, newPrice/1e8);
    require(baseAmount >= minBaseAmount, "WooPPV2: baseAmount_LT_minBaseAmount");
  }

  tokenInfos[baseToken].reserve = uint192(tokenInfos[baseToken].reserve - baseAmount);
  tokenInfos[quoteToken].reserve = uint192(tokenInfos[quoteToken].reserve + quoteAmount);

  if (to != address(this)) {
    TransferHelper.safeTransfer(baseToken, to, baseAmount);
  }

  emit WooSwap(quoteToken, baseToken, quoteAmount + swapFee, baseAmount, msg.sender, to, rebateTo, quoteAmount + swapFee, swapFee);
}

Step 6:

However, due to an error in this calculation, the price was adjusted far outside the expected range of $0.00000009, due to which the fallback check that normally executes against ChainLink Oracle didn't cover the WOO token price.

fallback(bytes calldata _input) external onlyAdmin returns (bytes memory _output) {
  /*
            2 bit: 0: post prices, 1: post states, 2,3: TBD
            6 bits: length

            post prices:
               [price] -->
                  base token: 8 bites (1 byte)
                  price data: 32 bits = (27, 5)

            post states:
               [states] -->
                  base token: 8 bites (1 byte)
                  price:      32 bits (4 bytes) = (27, 5)
                  k coeff:    16 bits (2 bytes) = (11, 5)
                  s spread:   16 bits (2 bytes) = (11, 5)
        */

  uint256 x = _input.length;
  require(x > 0, "WooracleV2_1_ZipInherit: !calldata");

  uint8 firstByte = uint8(bytes1(_input[0]));
  uint8 op = firstByte >> 6; // 11000000
  uint8 len = firstByte & 0x3F; // 00111111
  if (op == 0) {
    // post prices list
    address base;
    uint128 p;

    for (uint256 i = 0; i < len; ++i) {
      base = getBase(uint8(bytes1(_input[1 + i * 5:1 + i * 5 + 1])));
      p = _price(uint32(bytes4(_input[1 + i * 5 + 1:1 + i * 5 + 5])));
      infos[base].price = p;
    }

    timestamp = block.timestamp;
  } else if (op == 1) {
    // post states list
    address base;
    uint128 p;
    uint64 s;
    uint64 k;

    for (uint256 i = 0; i < len; ++i) {
      base = getBase(uint8(bytes1(_input[1 + i * 9:1 + i * 9 + 1])));
      p = _price(uint32(bytes4(_input[1 + i * 9 + 1:1 + i * 9 + 5])));
      s = _ks(uint16(bytes2(_input[1 + i * 9 + 5:1 + i * 9 + 7])));
      k = _ks(uint16(bytes2(_input[1 + i * 9 + 7:1 + i * 9 + 9])));
      _setState(base, p, s, k);
    }

    timestamp = block.timestamp;
  } else {
    // not supported
  }
}

Aftermath#

The team acknowledged the occurrence of the incident and was quick to pause the affected pools, thereby limiting the extent of the damage from the exploit. They have also shared a detailed post-mortem of the incident.

The address tagged as `WooFi Exploiter 1` had held approximately $764,832 worth of assets on the Ethereum Mainnet. They sent approximately 199.52 ETH to yet another EOA. This `WooFi Exploiter 1` had further sent 2,023.59 ETH, totaling $7,748,290, to the address tagged as `WooFi Exploiter 2`. The `WooFi Exploiter 2` further sent approximately 523 ETH to another EOA.

Solution#

Addressing the exploit experienced by WOOFi necessitates a holistic and multifaceted approach to bolstering the security, functionality, and resilience of decentralized finance platforms. To mitigate similar vulnerabilities in the future, a combination of enhanced smart contract security measures, sophisticated pricing mechanisms, improved liquidity management strategies, and robust emergency response protocols is essential.

Starting with smart contract security, the implementation of comprehensive security audits is paramount. These audits should leverage both automated tools and the expertise of seasoned auditors to meticulously examine smart contracts for vulnerabilities, particularly those that could be exploited for price or oracle manipulation. Additionally, adopting formal verification for smart contracts can provide mathematical assurance of their correctness and intended behavior across all possible scenarios, significantly reducing the risk of exploitation.

Improving the pricing mechanism and liquidity management involve several innovative steps. The introduction of dynamic oracle guardrails that adjust based on the performance and reliability of multiple oracles can significantly reduce dependence on a single price feed and enhance overall system resilience. An advanced sPMM algorithm equipped with adaptive logic capable of detecting and responding to abnormal trading patterns can prevent manipulation by adjusting trading parameters in real time. Furthermore, implementing health checks for liquidity pools ensures that trading parameters are automatically adjusted or trading is paused if liquidity levels drop below predefined safe thresholds, safeguarding against potential exploits.

The establishment of robust emergency response protocols is also crucial. Introducing time-locked transactions for critical contract functions allows for a review period during which suspicious actions can be scrutinized and, if necessary, reversed. Smart contract-based circuit breakers can halt trading or specific contract functionalities in the event of extreme price movements, saving valuable time for human intervention and assessment.

Despite the rigorous security measures in place, the complete eradication of risks in the blockchain domain remains a challenging endeavor. This persistent uncertainty underscores the critical role played by Neptune Mutual in the ecosystem. Through the establishment of a dedicated cover pool within our marketplace, the detrimental effects of incidents such as the WOOFi exploit could be substantially alleviated. Our offerings provide an essential layer of protection, effectively reducing the financial and digital asset losses associated with smart contract vulnerabilities, thanks to our cutting-edge parametric insurance solutions.

Neptune Mutual differentiates itself by offering a significant benefit through our parametric insurance policies. Users are relieved from the burden of providing proof of loss when initiating a claim. Upon the verification and definitive resolution of an incident through our comprehensive resolution system, affected individuals can promptly claim their compensation. This streamlined approach simplifies the traditionally complex and time-consuming claims process seen in conventional insurance models, presenting a straightforward and efficient solution to our clientele.

We have extended our marketplace across several prominent blockchain networks, including EthereumArbitrum, and the BNB chain. Our extended presence across these networks ensures that we can offer our solutions to a wide range of DeFi participants, catering to the diverse needs of the blockchain community.

Reference Source WOOFi

By

Tags