How Was the Zunami Protocol Exploited?

5 min read

Learn how the Zunami Protocol was exploited, resulting in a loss of $2.1 million.

TL;DR#

On August 13, 2023, the Zunami Protocol was exploited, which resulted in a loss of 1174 ETH and 1265 USDT, totaling approximately $2.1 million.

Introduction to Zunami Protocol#

Zunami is a decentralized protocol that issues aggregated stablecoins (UZD and zETH), whose collateral is utilized in omnipools and differentiated among various profit-generating strategies.

Vulnerability Assessment#

The root cause of the exploit is a price manipulation issue caused by donations that incorrectly calculate the price.

Steps#

Step 1:

We attempt to analyze one of the attack transactions executed by the exploiter.

Step 2:

The exploiter initially borrowed $7 million USDT from Uniswap V3, as well as $7 million USDC and 10,011 WETH from Balancer.

Step 3:

The attacker then added liquidity in the CurveFinance with $5.75 million USDC in order to mint approximately 5,746,896 crvFRAX and then swapped it for roughly 4,082,046 UZD and the remaining $1.25 million USDC for 791,280 UZD in the Curve.

Step 4:

The balanceOf function in the UZD contract relied on the incorrect price in the cache, allowing for the price manipulation of the underlying assets.

function balanceOf(address account) public view virtual override returns (uint256) {
  if (!containRigidAddress(account)) return super.balanceOf(account);

  return _balancesRigid[account];
}
function _convertFromNominalWithCaching(uint256 nominal, Math.Rounding rounding) internal virtual returns (uint256 value) {
  if (nominal == type(uint256).max) return type(uint256).max;
  _cacheAssetPriceByBlock();
  return nominal.mulDiv(assetPriceCached(), DEFAULT_DECIMALS_FACTOR, rounding);
}
function assetPriceCached() public view virtual returns (uint256) {
  return _assetPriceCached;
}

Step 5:

The zETH (LP) value calculation depends on both the CRV price and the exchange rate calculated from the CRV and ETH balances in the `CurveConvexStratBase` contract. The attacker was able to manipulate the price of $CRV as well as the $CRV balance, thus increasing the `_assetPriceCached` value.

function cacheAssetPrice() public virtual {
  _blockCached = block.number;
  uint256 currentAssetPrice = assetPrice();
  if (_assetPriceCached < currentAssetPrice) {
    _assetPriceCached = currentAssetPrice;
    emit CachedAssetPrice(_blockCached, _assetPriceCached);
  }
}
function assetPrice() public view override returns (uint256) {
  return priceOracle.lpPrice();
}

function lpPrice() external view returns (uint256) {
  return (totalHoldings() * 1e18) / totalSupply();
}
function totalHoldings() public view returns (uint256) {
  uint256 length = _poolInfo.length;
  uint256 totalHold = 0;
  for (uint256 pid = 0; pid < length; pid++) {
    totalHold += _poolInfo[pid].strategy.totalHoldings();
  }
  return totalHold;
}
function totalHoldings() public view virtual returns (uint256) {
  uint256 crvLpHoldings = (cvxRewards.balanceOf(address(this)) * getCurvePoolPrice()) / CURVE_PRICE_DENOMINATOR;

  uint256 crvEarned = cvxRewards.earned(address(this));

  uint256 cvxTotalCliffs = _config.cvx.totalCliffs();
  uint256 cvxRemainCliffs = cvxTotalCliffs - _config.cvx.totalSupply() / _config.cvx.reductionPerCliff();

  uint256 amountIn = (crvEarned * cvxRemainCliffs) / cvxTotalCliffs + _config.cvx.balanceOf(address(this));
  uint256 cvxEarningsUSDT = priceTokenByExchange(amountIn, _config.cvxToUsdtPath);

  amountIn = crvEarned + _config.crv.balanceOf(address(this));
  uint256 crvEarningsUSDT = priceTokenByExchange(amountIn, _config.crvToUsdtPath);

  uint256 tokensHoldings = 0;
  for (uint256 i = 0; i < 3; i++) {
    tokensHoldings += _config.tokens[i].balanceOf(address(this)) * decimalsMultipliers[i];
  }

  return tokensHoldings + crvLpHoldings + (cvxEarningsUSDT + crvEarningsUSDT) * decimalsMultipliers[ZUNAMI_USDT_TOKEN_ID];
}

Step 6:

As the SDT balance affects the UZD price, the exploiter was able to swap 11 WETH for 55,981 SDT in the Curve, then donate all of the 55,598 SDT into the MIMCurveStakeDao. The remaining 10,000 WETH were swapped for 58,043 SDT, and the other part of $7 million USDT was swapped for 2,154 WETH in the SushiSwap.

Step 7: 

The attack transaction in reference yielded the attacker approximately 1,152.93 ETH and 1,265.4 USDT, and the attacker added another 22.33 ETH from this attack transaction.

Step 8: 

The attacker has already laundered funds worth approximately 1183.8 ETH into Tornado Cash.

Aftermath#

The team acknowledged the occurrence of the exploit on zStables and stated that the Omnipool collateral remained secure.

They further stated that the UZD and zETH collateral will be distributed to holders just before the hack and that equal compensation will be distributed to all participants, irrespective of liquidity pool usage or address.

Solution#

In the wake of the Zunami Protocol exploit, which was grounded in a self-implemented price oracle vulnerability, we find it crucial to emphasize the significance of utilizing reputable, multifaceted, and robust oracle solutions. The unfortunate price manipulation incident, stemming from such a vulnerability, highlights the importance of creating resilient systems that can anticipate and guard against malicious actors.

In scenarios like these, where price manipulation stands central, it becomes imperative to leverage oracle systems like ChainLink, which amalgamate data from numerous sources to provide accurate price feeds. Time-weighted average prices (TWAPs) play a pivotal role in ensuring price stability, mitigating abrupt, and likely manipulative, price changes. Adequate liquidity within pools, which the oracle leans on for price determination, is also a cardinal element in maintaining a fortified defense against manipulation.

What’s even more alarming about this exploit is that SlowMist had already flagged the vulnerability. It's essential that DeFi protocols forge strong ties with auditing entities. Such a partnership ensures that potential vulnerabilities are promptly addressed and rectified, protecting users and maintaining trust in the ecosystem.

While the above measures are paramount, vulnerabilities, at times, may still surface. This is where Neptune Mutual steps in to protect the end-users. If the team associated with Zunami Protocol had established a dedicated cover pool with us prior to the exploit, the aftermath of the incident could have been notably alleviated. At Neptune Mutual, we understand the intricacies of the DeFi landscape, which is why we offer coverage to users who might face losses from vulnerabilities in smart contracts.

Our avant-garde parametric policies ensure that users are not mired in bureaucratic processes when proving their losses. Instead, they can readily claim their payouts, post an incident's confirmation and resolution through our rigorous incident resolution framework.

Our marketplace operates on numerous popular blockchain networks, including EthereumArbitrum, and the BNB chain. This extensive reach allows us to serve a broad spectrum of DeFi users, offering protection from potential vulnerabilities and enhancing their faith in the ecosystem.

Reference Source ZunamiBlockSec

By

Tags