Skip to content

Latest commit

 

History

History
77 lines (54 loc) · 6.21 KB

2021-01-17.md

File metadata and controls

77 lines (54 loc) · 6.21 KB

Incident disclosure 2021-01-16

Summary

  • Due to an oversight in the migration process, the previous GUSD strategy[1] attached to the GUSD vault[2] ended up incorrectly being affected by calls to the crvGUSD vault[3].
  • This led to incorrect share price calculations for both vaults that resulted in losses of 11,435.95 GUSD for vault depositors.
  • Affected depositors have been compensated from Yearn's Operations Fund for the full amounts.
  • As a result of this incident, Yearn developers are reviewing the process for migrating between strategies.

Background

  • On 2020-10-26, funds were withdrawn from strategy[1] and deposits into strategy for GUSD vault[2] were disabled as a result of a decimal issue in the strategy[4].
  • On 2020-12-07, the crvGUSD vault[3] and strategy[5] were deployed.
  • On 2020-12-22, the crvGUSD vault called earn for the first time[6], incorrectly altering the share price of the GUSD vault. Because the previous GUSD strategy was not revoked properly, it led to incorrect calculations of GUSD vault share prices. From here on out, each earn or withdrawAll call on crvGUSD altered share price of yGUSD.

Details of incident

The root cause was more than one active strategy using the same Curve Gauge. All veCRV boosts are handled by the CurveYCRVVoter[6] -- сontract that is whitelisted by Curve, and each pool has only one corresponding gauge. Therefore, if more than one strategy uses the same gauge, this causes issues because strategies will see balances from other strategies as their own.

GUSD vault share price discrepancies

Each earn() and withdrawAll() call on the crvGUSD Vault incorrectly affected share price of the GUSD Vault.

This occured because:

  1. getPricePerFullShare() on yGUSD calls balance().
  2. balance() calls balanceOf() from the v1 Vault Controller[7].
  3. The Controller in turn calls balanceOf() on the GUSD strategy, StrategyCurveGUSDProxy[8].
  4. The strategy's balanceOf calls and adds balanceOfPoolInWant() and balanceOfWant().
  5. balanceOfPoolInWant() calls balanceOfPool().
  6. The strategy's balanceOfPool() calls balanceOf() in the StrategyProxy contract[9].
  7. StrategyProxy's balanceOf() calls the gauge balance for CurveYCRVVoter[10], and stakes LP tokens for Curve pools in the GUSD Gauge[11] to earn CRV emissions.

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

crvGUSD share price discrepancies

When the last user drained the old GUSD strategy[12], their withdrawAll() call also incorrectly 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. As getPricePerFullShare = balance / totalSupply, this results in a share value <1.

When a user withdraws, the vault checks to see if there are enough free funds to withdraw directly from the vault. If not, it pulls funds from the strategy, in this cause withdrawing from the GUSD Curve gauge. Additionally, this withdrawal value was much smaller than intended due to previously mentioned decimal issues.

Since the balance value was incorrectly skewed by the crvGUSD vault, this led to the following:

  • GUSD Vault calculates r = shares * balance / totalSupply = 2422549
  • Compares against b = token.balanceOf(address(this)) = 836599, which was the GUSD sitting free in the vault
  • As b < r, it 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.

Details of fix

Strategy contract GUSDRescue[13] was deployed as a mock strategy for yGUSD to use. This strategy hardcoded balanceOf and withdrawAll(), with a mock deposit() to rescue the locked GUSD in yGUSD vault [14].

Timeline of events

  • Jan 3, 2021: Yearn developers notice an issue with GUSD share price and begin investigation.
  • Jan 13: The remainder of funds in the vault is transferred to the executive multi-sig, and GUSD strategy is replaced [13], [14].
  • Jan 15: All affected vault depositors are compensated[15].
  • Jan 16: This disclosure is published.

References

  1. StrategyCurveGUSDProxy
  2. GUSD vault
  3. crvGUSD Vault
  4. GUSD Strategy Decimals Bug
  5. StrategyCurveGUSDVoterProxy
  6. First crvGUSD earn call
  7. Vulnerability disclosure 2020-10-30
  8. CurveYCRVVoter
  9. v1 Vault Controller
  10. StrategyProxy
  11. GUSD gauge
  12. Transaction to drain old GUSD strat
  13. StrategyGUSDRescue
  14. Transaction rescuing funds and attaching new GUSD strategy
  15. Transaction compensating all outstanding yGUSD holders