|
| 1 | +# Requirements for the mint/burn fee math |
| 2 | + |
| 3 | + |
| 4 | +## The two key outputs: output amount (of USM/FUM/ETH), and `adjChangeFactor` |
| 5 | + |
| 6 | +The formulas in `USM.sol`'s mint/burn fee math functions (`usmFromMint()`, `ethFromBurn()`, `fumFromFund()`, `ethFromDefund()`) |
| 7 | +are confusing. Perhaps they could safely be simplified, and perhaps in the future we'll find ways to simplify them. But |
| 8 | +meanwhile, here are the key criteria we were attempting to satisfy in devising them, so you can understand where the complexity |
| 9 | +comes from. |
| 10 | + |
| 11 | +Let's look at the most complex of the four: `ethFromDefund()`. This function takes one input: |
| 12 | +* `fumIn`, the amount of FUM the user is burning |
| 13 | + |
| 14 | +And returns two outputs: |
| 15 | +* `ethOut`, the amount of ETH to give back in return |
| 16 | +* `adjShrinkFactor`, the factor by which to reduce the ETH/USD price in response to the burn operation |
| 17 | + |
| 18 | +(The below includes some math-speak about integrals etc: feel free to skim over that stuff. A lot of this was also touched on |
| 19 | +in |
| 20 | +[USM post #4: fee math decisions](https://jacob-eliosoff.medium.com/usm-minimalist-decentralized-stablecoin-part-4-fee-math-decisions-a5be6ecfdd6f): |
| 21 | +you may find the examples there helpful.) |
| 22 | + |
| 23 | + |
| 24 | +## The "fee" is implied by the output amount/`adjChangeFactor` curves |
| 25 | + |
| 26 | +We never actually calculate a "fee" "paid" by the user. Instead we calculate `adjShrinkFactor` and `ethOut`, which *imply* a |
| 27 | +fee: the further `adjShrinkFactor` gets below 1, the more the user's FUM sell price is dropping as they burn, and the less |
| 28 | +`ethOut` they end up getting. Conceptually, the "fee" is the difference between the ("zero-fee") `ethOut` they'd get if we |
| 29 | +just calculated it as `fumIn` * `midFumPrice` (without sliding price), and the `ethOut` they actually get based on the price |
| 30 | +sliding away from them. |
| 31 | + |
| 32 | +In theory, if we know the `adjShrinkFactor` for any given input `fumIn`, that mathematical curve implies `ethOut`: the initial |
| 33 | +FUM sell price and the `adjShrinkFactor` for each given `fumIn` gives us the marginal price, and `ethOut` is just the integral |
| 34 | +of the resulting marginal price curve. In practice, calculating exact integrals is often painful. For `usmFromMint()` and |
| 35 | +`ethFromBurn()` the integral is simple and we can calculate `usmOut`/`ethOut` exactly; for the FUM operations, `fumFromFund()` |
| 36 | +and `ethFromDefund()`, we need approximations. |
| 37 | + |
| 38 | +Whether we calculate the output amount from the `adjChangeFactor` or vice versa is a question of mathematical convenience. For |
| 39 | +`ethFromBurn()`, `ethOut` is easier to calculate first, so we calculate that and then derive the `adjGrowthFactor` from it. |
| 40 | +For the other three (`usmFromMint()`, `fumFromFund()`, `ethFromDefund()`), the `adjChangeFactor` is easier to calculate, so we |
| 41 | +derive the output amount from it. |
| 42 | + |
| 43 | + |
| 44 | +## Requirements for the output amount/`adjChangeFactor` curves |
| 45 | + |
| 46 | +So, what are some requirements for (eg) `ethFromDefund()`'s `ethOut` and `adjShrinkFactor` formulas? (This list is a work in |
| 47 | +progress...) |
| 48 | + |
| 49 | +1. **Arbitrage-free.** There must be no sequence of operations that lets a user reliably end up with strictly more tokens (eg, |
| 50 | + more USM and the same amount of ETH) than they started with. |
| 51 | + - Similar: the fee (implied, as described above) for every operation must be ≥ 0. |
| 52 | + <br><br> |
| 53 | +2. **Increasing % fee.** The fee should increase, *as a percentage of the input size,* as the input size increases. So, tiny |
| 54 | + operations (not immediately preceded by similar operations jacking up the `bidAskAdjustment`) should pay a ~0% fee; larger |
| 55 | + operations should pay an increasing % fee. |
| 56 | + - As discussed in |
| 57 | + [USM post #2](https://jacob-eliosoff.medium.com/usm-minimalist-stablecoin-part-2-protecting-against-price-exploits-a16f55408216), |
| 58 | + rising fees like this are important to prevent small inaccuracies/delays in the price oracle from "being exploited in |
| 59 | + size." |
| 60 | + - One way of characterizing this is: if you do three of the same operation in quick succession (eg, call `mint(1 ETH)` three |
| 61 | + times), the *middle* operation should get a better average price (get more output USM per input ETH) than the three |
| 62 | + operations added together. |
| 63 | + <br><br> |
| 64 | +3. **Increasing output amount.** A larger input amount should get ≥ the output amount. |
| 65 | + - This seems trivial, but some fee formulas do risk violating it. See eg |
| 66 | + [`ethFromDefund()`](https://github.com/usmfum/USM/blob/v0.4.0/contracts/USM.sol#L740): "Taking the geometric average is |
| 67 | + dicey in the `defund()` case: `fumSellPrice2` could be arbitrarily close to 0, which would make `avgFumSellPrice` |
| 68 | + arbitrarily close to 0, which would have the highly perverse result that a *larger* `fumIn` returns strictly *less* ETH!" |
| 69 | + <br><br> |
| 70 | +4. **Path (near-)independence.** Calling (eg) `mint(x ETH)` followed by `mint(y ETH)` should have approximately the same net |
| 71 | + effect (gas fees aside) as calling `mint(x+y ETH)`. |
| 72 | + - This is not a strict requirement: to perfectly achieve it would require always calculating exact integrals. But |
| 73 | + avoiding glaringly different outcomes between the two is desirable. In particular, it's ugly if the system incentivizes |
| 74 | + users to break up large operations into many small operations one after the other. (That is, immediately after each |
| 75 | + other. Whereas spreading out small operations over time, rather than one huge operation, is something we *do* want to |
| 76 | + incentivize, since most exploits we want to protect against take the form of sudden huge operations.) |
| 77 | + <br><br> |
| 78 | +5. **No "shortcuts".** There should be no roundabout way for a user to convert (eg) *x* ETH to USM, that yields more USM, than |
| 79 | + by directly calling `mint(x ETH)`. |
| 80 | + - [USM post #4](https://jacob-eliosoff.medium.com/usm-minimalist-decentralized-stablecoin-part-4-fee-math-decisions-a5be6ecfdd6f) |
| 81 | + imagined a scenario that might violate this requirement: where a user calling `mint(e2 ETH) -> u USM` (pushing down the |
| 82 | + ETH/USD price), `fund(e3 ETH) -> f FUM` (taking advantage of the depressed ETH/USD price), `burn(u USM) -> e4 ETH`, could |
| 83 | + (hypothetically) get back more FUM than the simple `fund(e2+e3−e4 ETH)`. We don't want to incentivize this: the functions |
| 84 | + should do their jobs well enough to make this sort of end run pointless. |
| 85 | + - The purpose of the ["FUM delta"](https://github.com/usmfum/USM/blob/v0.4.0/contracts/USM.sol#L654) we calculate in |
| 86 | + `fumFromFund()` and `ethFromDefund()` is to ensure that USM and FUM operations which loosely offset each other in terms of |
| 87 | + ETH exposure, like a `fund()` followed by a `burn()`, add up to sensible net results, rather than sneaky savings like |
| 88 | + above. |
| 89 | + <br><br> |
| 90 | +6. **Gas efficiency.** |
| 91 | + - So, for example, the reason we [use an approximation](https://github.com/usmfum/USM/blob/v0.4.0/contracts/USM.sol#L597) |
| 92 | + for `usmOut` in `usmFromMint()`, rather than the (tractable) exact integral, is that testing showed the approximation |
| 93 | + [saved ~4k gas per `mint()` call](https://github.com/usmfum/USM/blob/v0.4.0/contracts/USM.sol#L593). (And, of course, |
| 94 | + that the approximation is close enough for our purposes.) |
| 95 | + <br><br> |
| 96 | +7. **Legible/intuitive math.** |
| 97 | + - We would love to do better on this front... If, after browsing the comments in these four functions in |
| 98 | + [USM.sol](https://github.com/usmfum/USM/blob/master/contracts/USM.sol), you have simplifications/alternatives to suggest |
| 99 | + (that seem consistent with the rest of the requirements here), let us know! (Eg, |
| 100 | + [open an issue.](https://github.com/usmfum/USM/issues/new)) |
0 commit comments