Skip to content

Yearn yUSDT exploit-20230412 #2

Open
@zt-9

Description

@zt-9

On April 12, 2023, Yearn yUSDT was exploited, resulting in a loss of 11.6 million.
attacking transaction

The Yearn yUSDT pool mints yUSDT share tokens for users when they deposit USDT into the pool. Due to a misconfiguration of the Fulcrum token, the attacker is able to manipulate the share price and profits 11.6 million from the attack. The attacker withdraws from the yUSDT pool after inflating the share price and deposits into yUSDT after deflating the share price.

Unlike normal exploits, the attacker is not the only one who benefits from the exploit. AAVE V1 users' USDT loans were repaid by the attacker.

The attacker first deposits USDT into the pool to get some yUSDT. To inflate the share, the attacker mints some Fulcrum USDC to the yUSDT pool. Because of the share inflation, attacker is able to withdraw more USDT from the yUSDT pool.

function _calcPoolValueInToken() internal view returns (uint) {
    return _balanceCompoundInToken() // Compound USDT balance
      .add(_balanceFulcrumInToken()) // Fulcrum USDC iToken (iUSDC)
      .add(_balanceDydx()) // Dydx balance
      .add(_balanceAave()) // aUSDT 
      .add(_balance()); // USDT balance
  }

Next step is to deflate the share price.

yUSDT pool supplies USDT in the pool to providers like AAVE based on the provider APR. If a new provider has better APR , users can call rebalance() function to redeem all the token from all providers and supply those USDT to the new Provider. If everything’s configured right, the pool value shouldn’t change after rebalance.

Before the attack, AAVE V1 is the only provider with non-zero APR. To manipulate the provider APR and switch the provider, the attacker repay USDT loans for AAVE V1 users and lower the AAVE apr to zero. Now all the provider has zero APR. So after rebalance(), USDC tokens are redeemed from the Fulcrum USDC pool. Because of the misconfiguration of the Fulcrum USDC, pool balance become zero after rebalance. Share price is one for empty Pool, which means attacker can get yUSDT with a much cheaper price.

Attacker now can deposit USDT into the yUSDT pool after deflating the share. Attacker later on swap the yUSDT to other tokens in the Curve y Swap pool.

function rebalance() public {
    Lender newProvider = recommend();

    if (newProvider != provider) {
      _withdrawAll();
    }

    if (balance() > 0) { // USDT balance
      if (newProvider == Lender.DYDX) {
        supplyDydx(balance());
      } else if (newProvider == Lender.FULCRUM) {
        supplyFulcrum(balance());
      } else if (newProvider == Lender.COMPOUND) {
        supplyCompound(balance());
      } else if (newProvider == Lender.AAVE) {
        supplyAave(balance());
      }
    }

    provider = newProvider;
  }

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions