Skip to content

Commit 6fd7665

Browse files
Create fee_math_requirements.md
1 parent 4d2b9aa commit 6fd7665

File tree

1 file changed

+100
-0
lines changed

1 file changed

+100
-0
lines changed

Diff for: fee_math_requirements.md

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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

Comments
 (0)