Skip to content

Instantly share code, notes, and snippets.

@zobront
Last active June 28, 2023 15:19
Show Gist options
  • Save zobront/d4a1507b1872b29392ca7d598a6fd3f1 to your computer and use it in GitHub Desktop.
Save zobront/d4a1507b1872b29392ca7d598a6fd3f1 to your computer and use it in GitHub Desktop.

The current implementation of the fix of H-01 uses ulow and ucrit as the lower and upper bounds for when the AMO is active.

This seemed right on our call, but after digging into the math, it doesn't actually achieve the desired effect. This is because, there are times when manipulating the Silo utilization rate within those bounds can lead to a response that moves it far outside these bounds.

This means the fix is protective against the H-04 issue (shutting down when we face extreme unpredictable swings) but not against the intentional manipulation in H-01 and H-02.

Example

Let's look at the two examples where things can get outside the range.

First, let's imagine the flow where a user deposits to bait the AMO into withdrawing:

  • There are currently 5 OHM borrowed and 10 deposits, for perfect 0.5 uopt
  • The user deposits to lower the util as much as possible while staying within ulow to ucrit (deposits 6.66 makes util 5/16.66 = 0.3)
  • The AMO responds by withdrawing 6.66 to regain equilibrium (5/10)
  • The user withdraws 5 out of their 6.66 deposit, bringing util to 100% (5/5)

Second, we can look at the situation where a user borrows to bait the AMO into depositing:

  • There are currently 5 OHM borrowed and 10 deposits, for perfect 0.5 uopt
  • A user borrows 4 OHM to bring util up to 0.9 (9/10)
  • The AMO responds by depositing 8 OHM to bring it back to 0.5 (9/18)
  • THe user repays their 4 OHM, pushing it outside the window to 0.27 (5/18)

Clearly these are not acceptable!

Looking at the different ways in which a pool could be manipulated:

  • DEPOSIT (above): can force util up to 100%
  • WITHDRAW: can force util down to 34% (fine!)
  • BORROW (above): can force util down to 27%
  • REPAY: can force util up to 83% (fine!)

Lower Bound

What we need to do is to calculate the utilization rate that, when manipulated into that state to prompt an AMO response, results in a swing back that keeps us within the ulow to ucrit window.

This actually isn't too difficult to calculate, so we can do it on chain to get better bound values.

For the lower bound, we can calculate:

borrows / ((2 / uopt * borrows) - (borrows / ucrit))

We can derive this formula by noticing that:

  • when at uopt, deposits = borrows / uopt
  • when at ucrit, deposits = borrows / ucrit

Therefore, the swing in deposits needed to move us from uopt to ucrit is equal to (borrows / uopt) - (borrows / ucrit).

In order to prevent a manipulation from pushing the AMO to cause a swing of that size, we need to ensure that a swing of that size isn't applied by a user in the other direction. This is because, if a user deposits that amount, the AMO will withdraw that amount, and then the user can withdraw their deposit to have that magnitude of impact.

Since the most we can allow the deposits to swing is (borrows / uopt) - (borrows / ucrit), we can calculate the lowerBound by seeing where the utilization ends if a user deposits that quantity to a Silo that is current balanced at uopt:

lowerBound = borrows / (borrows / uopt + ((borrows / uopt) - (borrows / ucrit)))
lowerBound = borrows / ((2 * borrows / uopt) - (borrows / ucrit))
lowerBound = 1 / ((2 / uopt) - (1 / ucrit))

Looking at an example, if we have a Silo with 5 OHM of borrows and 10 OHM of deposits:

  • acceptable swing = (borrows / uopt) - (borrows / ucrit) = 5/0.5 - 5/0.9 = 4.4444
  • maximum acceptable deposits = 10 + 4.4444 = 14.4444
  • minimum acceptable util = 5 / 14.4444 = 0.346
  • we can also calculate this as 1 / ((2 / uopt) - (1 / ucrit)) = 1 / ((2 / 0.5) - (1 / 0.9) = 1 / (4 - 1.111) = 0.346

Upper Bound

For the upper bound, we can perform similar calculations:

  • First, we get the maximum deposits value we're comfortable with, which is borrows / ulow
  • Then, we get the deviation in deposits that would lead to this value, which is (borrows / ulow) - (borrows / uopt)
  • Then, we calculate how much would need to be borrowed to get the AMO to deposit this much: ((borrows / ulow) - (borrows / uopt)) * uopt
  • Finally, we get the util ratio when that much is borrowed: borrows + ((borrows / ulow) - (borrows / uopt)) * uopt / (borrows / uopt)
  • Simplified (thanks to Liened), this can be represented by: uopt + (uopt * ((uopt / ulow) - 1))

Looking at an example with 5 OHM of borrows and 10 OHM of deposits:

  • max acceptable borrows = borrows / ulow = 5 / 0.3 = 16.66
  • max acceptable swing = (borrows / ulow) - (borrows / uopt) = 16.66 - 10 = 6.66
  • max acceptable borrows = ((borrows / ulow) - (borrows / uopt)) * uopt = 6.66 * 0.5 = 3.33
  • max acceptable util = borrows + ((borrows / ulow) - (borrows / uopt)) * uopt / (borrows / uopt) = (5 + 3.33) / (5 / 0.5) = 0.833
  • simplified calculation = uopt + (uopt * ((uopt / ulow) - 1)) = 0.5 + (0.5 * ((0.5 / 0.3) - 1)) = 0.5 + 0.333 = 0.833

Recommendation

When checking the utilization rate, confirm that it falls within:

  • min: 1 / ((2 / uopt) - (1 / ucrit))
  • max: uopt + (uopt * ((uopt / ulow) - 1))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment