Yearn GUSD Vaults Write-up

Problem #1: yGUSD share price is broken and vault is drained

Transaction in question, on bloxy. I also used tenderly to trace it, which was very helpful as well.

Cause: earn and withdrawAll calls on the yvCurve-GUSD Vault affect share price on the yGUSD Vault. This occurs because:

  • getPricePerFullShare on yGUSD calls balance
  • balance calls balanceOf from the Controller
  • The controller's balanceOf calls balanceOf from the StrategyCurveGUSDProxy (this Vault's strategy)
  • The strategy's balanceOf calls and adds balanceOfPoolInWant and balanceOfWant.
    • balanceOfPoolInWant calls balanceOfPool
  • The strategy's balanceOfPool calls balanceOf in the StrategyProxy contract
  • The StrategyProxy's balanceOf calls the gauge balance for Yearn's veCRV voter address (CurveYCRVVoter). Yearn stakes LP tokens for Curve pools in the GUSD gauge to earn CRV emissions.

Both the yGUSD and yvCurve-GUSD Vaults ultimately deposit their LP tokens in the same gauge from the same address, so yGUSD saw the LP tokens yvCurve-GUSD deposited and thought those were its own, rapidly increasing the share price because no new yGUSD tokens were minted.

Currently, there are 7,371.81 outstanding yGUSD shares and only 1,357.33 GUSD in the vault. The current share price is ~37.76.

Proposed Solution: If we can just shut everything down completely, and send users the funds directly, that is easiest. Here's the list of holders of yGUSD. Edit Jan 6th—share price is back to 0, all funds removed from v2 strat.

If that's not possible, then I would suggest we deploy a new strategy with a hard-coded values for balanceOf. Simply put, we are breaking the connection between yGUSD and the GUSD Curve gauge.

Hard-code the balanceOf value to 1.77, and seed the vault with the appropriate amount of GUSD from treasury. Based on users with outstanding shares, only two users deposited with average share prices above 1.77 (almost all were at 1.76xx). For one user, 0xddb7ffb26e58c9c71673f1f3965293dcfc860586, they already received more than their share, as they originally withdrew all shares (and drained the vault), getting ~7x their investment, then only to re-deposit 2,000 GUSD later (I can only imagine hoping to get a similar bonus? Maybe as a "thank you"?).

The only other user is 0x7b004ea66389ca8263632719fc2ded63af288912, who deposited once at a share price of ~1.76, but then deposited again a few days ago, leaving their average share price at 2.85. In the ethos of vaults never leaving users with less than they deposited, I propose we send them an extra 50 GUSD, since they will be able to redeem 77 at a share price of 1.77.

Problem #2: yvCurve-GUSD share price is below 1

Cause: Whenever the last user drained the old GUSD vault here, their withdrawAll transaction also removed 0.00000000000158595 GUSD/3Crv from the Curve gauge—this is the difference between the balance and totalSupply values for the new GUSD/3Crv vault. getPricePerFullShare is equal to balance / totalSupply, so this explains why the value is below 1.

This occurred because when a user withdraws, the vault checks to see if there are enough free funds to withdraw directly from the vault, or if it needs to pull from the strategy (in this cause withdrawing from the GUSD Curve gauge). Since the balance value was incorrectly skewed by the yvCurve-GUSD vault, this led to the following:

  • yGUSD Vault calculates r = shares * balance / totalSupply = 2422549
  • Compares against b = token.balanceOf(address(this)) = 836599 <- this is the GUSD sitting free in the vault
  • In this case, b < r, which told the vault to pull funds from the strategy, equal to r - b = 1585950, so the strategy pulled 0.00000000000158595 gusd3CRV from the gauge and burned it.

In conclusion, we appeared to get super fucking lucky that there was both a bug in calculating balance and an issue with decimals—if the decimals weren't bugged as well, then this transaction would've withdrawn and burned (or maybe given the user?) 15,859.50 gusd3CRV.

Why did this burn happen though? Appears to be something that has been around since the vault launched, and was the same issue that broke it in the first place here. My best guess is that the amount that's burned is too small to generate and tokens in return from Curve, but idk about that.

Problem #3: yGUSD Vault and Strategy balances are incorrect on yearn.finance/stats

This should be fixed once the yGUSD vault and strategy are no longer pointing to the GUSD gauge.

Problem #4: Setting GUSD strategy to zero address broke registry

Batch functions like getVaultsInfo in the vault registry (registry.ychad.eth) have stopped working.

Cause: Registry calls strategy.want() which reverts since 0x0 has no code.

Select a repo