What’s the simplest possible decentralized stablecoin?

Jacob Eliosoff
14 min readJul 10, 2020

--

For a while I’ve had this question in the back of my mind and, inspired by projects I admire like MakerDAO and Uniswap, over the last week I took a crack at it. In this post I go over:

  • The proposed stablecoin design, here called USM (“minimalist USD”)
  • Its minimalist set of four operations: mint/burn, which create/redeem the USM stablecoin, and fund/defund, which create/redeem a related “funding-coin”, FUM
  • The biggest design hurdle I hit, and my proposed solution
  • A proof-of-concept implementation in ~200 lines of Python

This is really just for kicks, but a real-world implementation could be cool too as long as it doesn’t lose shitloads of innocent users’ money.

(See also the updates so far to this design in part 2: protecting against price exploits, part 3: uh this is happening guys, and part 4: fee math decisions.)

A. The basic idea: riskless USM + high-risk FUM

The simplest possible stablecoin is a create/redeem token backed by a pool of ETH. You mint n new “USM” stablecoin units by depositing $n worth of ETH (as of the time of minting) into the pool, and burn m USM in exchange for $m worth of ETH (as of the time of burning) from the pool. (I’ll use Ethereum for convenience, but the design is platform-agnostic.)

Why would this not work? Well, first of all, to know how much ETH is worth $n you need a price oracle or somesuch. More on that later, but pretty much any stablecoin will need this so it’s fair for even a minimalist design to require it.

But the bigger problem is ETH price volatility. ETH is at $200; you mint 2,000 USM by adding 10 ETH to the pool; ETH drops to $150; you go to burn your 2,000 USM and whoops, there’s not enough ETH in the pool (you should get 2,000/$150 = 13.33, but the pool only contains 10). The pool of collateral dropping in value such that it can no longer cover the outstanding claims (tokens) is a very familiar problem for stablecoins, and for finance in general. How do you stay dry when your umbrella keeps shrinking and expanding?

The simplest solution is also familiar: overcollateralize the pool — give it a safety buffer, with more ETH than is needed to redeem the outstanding USM. To do this, our system needs a way for other participants — “funders” — to add ETH to the pool (in exchange for a second token, FUM — “minimalist funding”), and an incentive for them to do so. Let’s try two incentives:

  1. Fees. Charge small fees on mint/burn operations, and share them between the funders.
  2. Leverage. We have a pool of ETH with market risk (its value rises and dips with the price of ETH), backing a bunch of USM tokens whose sole purpose in life is to avoid that risk (stay priced at $1). So we’ll dump all that market risk on the funders: stablecoin users deposit risky ETH, get back (market!-)riskless USM; funders deposit risky ETH, get back even-riskier FUM. This is again a familiar financial technique — “tranching” the risk.

So now, on top of mint (ETH->USM) and burn (USM->ETH) operations to create/redeem USM tokens, we add fund (ETH->FUM) and defund (FUM->ETH) operations to create/redeem FUM.

B. A simple example

  1. ETH price is $200. A mints 2,000 USM by depositing 10 ETH into the pool. (We’ll omit fees for now.)
  2. B does a fund operation, depositing another 5 ETH in exchange for 1,000 FUM units, initially priced at $1.
    - Pool contains 15 ETH (the 10 and 5 lumped together), worth $3,000
    - A has 2,000 USM worth $2,000
    - B has 1,000 FUM worth $1,000
  3. ETH rises to $220.
    - Pool’s 15 ETH now worth $3,300
    - A’s 2,000 USM still worth $2,000
    - B’s 1,000 FUM worth $1,300 (unit price $1 -> $1.30)
  4. ETH drops to $170.
    - Pool’s 15 ETH worth $2,550
    - A’s 2,000 USM worth $2,000
    - B’s 1,000 FUM worth $550 (unit price $1.30 -> $0.55)
  5. ETH drops to $100.
    - Pool’s 15 ETH worth $1,500
    - A’s 2,000 USM worth $2,000
    - B’s 1,000 FUM worth -$500 (unit price $0.55 -> -$0.50)

Oops! B’s funding turned out to be insufficient in the face of a big ETH price drop: the pool went underwater anyway (pool_value < usm_outstanding) and no longer has enough ETH to redeem all of A’s USM. Plus B got wiped out.

C. Why going underwater is bad

  1. It means not all USM holders can redeem their USM for ETH: the stablecoin is no longer “fully backed”. This may be OK as long as they don’t all withdraw at once (“fractional-reserve banking”), but…
  2. …The fact that late redeemers may end up empty-handed incentivizes holders to redeem early — potentially causing a system-ending bank run.
  3. FUM holders like B get wiped out. And again this may incentivize a bad feedback loop where they head for the exits on any signs of a drop.
  4. With the FUM price so low, or even negative, fresh funders may be able to buy FUM at crazy bargain prices, grievously diluting earlier funders.

Most of the rest of this post is about how to avoid these scenarios, which makes sense, because — aside from “keeping the thing at $1” — preventing/dealing with collateral depreciation and funder wipeout is the most fundamental challenge in stablecoin design.

D. More details on mint/burn/fund/defund

This system essentially has three moving parts:

  1. ETH price moving up/down, changing the $-value of the pool.
  2. mint/burn operations, creating/destroying USM (while adding/removing ETH to/from the pool).
  3. fund/defund operations, creating/destroying FUM (while adding/removing ETH).

More details on these operations:

  • Dollar peg: Note that under healthy operation (especially: not underwater, price oracle accurate), mint and burn should keep’s USM’s dollar peg tight: they let a user exchange 1 USM for $1 worth of ETH instantly at any time, small fees aside. The challenge is in keeping the system capitalized.
  • Debt ratio is the key measure for this: usm_outstanding / pool_value. When this ratio rises above 100% (or above a MAX_DEBT_RATIO we specify, like 80%), the system can cap it by disabling defund and burn operations until it goes back down, eg because fresh funders step in. But disabling redemptions this way, frequently or for any length of time, could certainly hurt trust in the system and in the peg. USM might start trading < $1 on exchanges… Recovery from such a state might occur, but it might not. No one should bet the farm on a system like this! It’s an experiment.
  • Specific mechanics of mint/burn are simple: put in $n of ETH, get back n USM (minus fee), or vice versa. Mechanics of fund/defund are more subtle. The basic model is that these operations change the quantity of FUM, but not its unit price, which is just the dollar-value of the pool’s excess buffer, buffer_value = pool_value - usm_outstanding, divided by the total number of FUM, fum_outstanding. For example (again omitting fees):
  1. Suppose there are 15 ETH in the pool @ $220 for a total pool value of $3,300, 2,000 USM outstanding, and 1,000 FUM. Then:
    - Debt ratio = $2,000 / $3,300 = 60.61%
    - Buffer value = $3,300 - $2,000 = $1,300
    - FUM unit price = $1,300 / 1,000 = $1.30
  2. C funds another 13 ETH (value $2,860) into the pool, getting back $2,860 / $1.30 = 2,200 newly created FUM units. Now:
    - 28 ETH in the pool @ $220, value $6,160
    - Still 2,000 USM outstanding
    - 3,200 FUM outstanding
    - Debt ratio = $2,000 / $6,160 = 32.47% (reduced: good)
    - Buffer value = $6,160 - $2,000 = $4,160 (up)
    - FUM unit price = $4,160 / 3,200 = $1.30 (unchanged)
  3. Price drops to $200:
    - 28 ETH in the pool @ $200, value $5,600
    - 2,000 USM outstanding, 3,200 FUM
    - Debt ratio = $2,000 / $5,600 = 35.71%
    - Buffer value = $5,600 - $2,000 = $3,600
    - FUM unit price = $3,600 / 3,200 = $1.125 (down)
  4. C defunds 1,000 of their 2,200 FUM units (value: 1,000 * $1.125 = $1,125), getting back $1,125 / $200 = 5.625 ETH (same value — $1,125):
    - 22.375 ETH in the pool @ $200, value $4,475
    - 2,000 USM outstanding, 2,200 FUM
    - Debt ratio = $2,000 / $4,475 = 44.69%
    - Buffer value = $4,475 - $2,000 = $2,475
    - FUM unit price = $2,475 / 2,200 = $1.125 (unchanged)

E. Negative FUM prices

But what about when we’re underwater — the case from the first example diagram where the FUM unit price plunged to -$0.50? We can’t fund/defund at a negative price, or at a near-$0 price. The short answer is that in such situations (ie, when debt ratio > MAX_DEBT_RATIO) we 1. disable defunds (the pool needs the funding!) and 2. add a premium to the fum_price for the purpose of fund operations, keeping fum_price > $0.

But the long answer is that there are many different ways to define such a premium, and 5 of my 7 days working on this whole system were spent exploring them. The details are out of the scope of this post, but in short my preferred rule so far is this:

When debt ratio > MAX_DEBT_RATIO, fund operations (adding ETH in exchange for fresh FUM) are required to pay at least min_fum_buy_price, defined as the FUM unit price in ETH at the moment debt ratio crossed MAX_DEBT_RATIO.

Once debt ratio falls back below MAX_DEBT_RATIO, the min_fum_buy_price is cleared and fund operations happen at the normal FUM price again (buffer_value / fum_outstanding). If you want all the gory details, read through this example (if not, you can skip it):

  1. Start again with 15 ETH in the pool @ $220 (value: $3,300), 2,000 USM outstanding, 1,000 FUM:
    - Debt ratio = $2,000 / $3,300 = 60.61%
    - Buffer value = $3,300 - $2,000 = $1,300
    - FUM unit price = $1,300 / 1,000 = $1.30
  2. ETH price drops to $100:
    - 15 ETH in the pool @ $100, value $1,500
    - 2,000 USM outstanding, 1,000 FUM
    - Debt ratio = $2,000 / $1,500 = 133.33%
    - Buffer value = $1,500 - $2,000 = -$500
    - Theoretical FUM unit price = -$500 / 1,000 = -$0.50

    Because debt ratio has risen above MAX_DEBT_RATIO = 80%, we set min_fum_buy_price to the FUM price (in ETH) as of the moment 80% was crossed. In this example that happened when the price hit $166.67, when buffer_value was (15 * $166.67) - $2,000 = $500 and therefore the FUM price was $500 / 1,000 = $0.50 = 0.003 ETH.
  3. D funds 6 ETH = $600. Because min_fum_buy_price = 0.003, D pays 0.003 ETH ($0.30) per FUM, rather than the negative theoretical price of -$0.50. So D gets back 6 / 0.003 = 2,000 newly created FUM:
    - 21 ETH in the pool @ $100, value $2,100
    - 2,000 USM outstanding, 3,000 FUM
    - Debt ratio = $2,000 / $2,100 = 95.24%
    - Buffer value = $2,100 - $2,000 = $100
    - Theoretical FUM price = $100 / 3,000 = $0.0333
    - min_fum_buy_price = 0.003 ETH (currently $0.30)
  4. ETH drops further to $60:
    - 21 ETH in the pool @ $60, value $1,260
    - 2,000 USM outstanding, 3,000 FUM
    - Debt ratio = $2,000 / $1,260 = 158.73%
    - Buffer value = $1,260 - $2,000 = -$740
    - Theoretical FUM price = -$740 / 3,000 = -$0.2467
    - min_fum_buy_price = 0.003 ETH ($0.18)
  5. D funds another 6 ETH = $360, again getting back 6 / 0.003 = 2,000 FUM. D is funding at the same rate as before in ETH terms (ie, at min_fum_buy_price = 0.003 ETH), but — with ETH down to $60 — at a cheaper rate in $ terms (0.003 ETH = $0.18, vs $0.30 before):
    - 27 ETH in the pool @ $60, value $1,620
    - 2,000 USM outstanding, 5,000 FUM
    - Debt ratio = $2,000 / $1,620 = 123.46%
    - Buffer value = $1,620 - $2,000 = -$380
    - Theoretical FUM price = -$380 / 5,000 = -$0.076
    - min_fum_buy_price = 0.003 ETH ($0.18)

…But anyway, there’s room for further study on the cleanest and safest way to ensure funders pay a reasonable (positive) FUM price. The big picture is, when the system is underwater it should price FUM low enough to attract new funders, but not so low as to completely wipe out the old ones.

(A great suggestion from Elliot Olds: in these situations where debt ratio > MAX_DEBT_RATIO, ie, the system is in need of funding, make the FUM buy price decline over time. This avoids an important failure case where min_fum_buy_price stays above what funders are willing to pay, so the system/peg dies for lack of funding. With the price declining over time, funding should eventually arrive, even if it’s at a price that significantly dilutes existing FUM holders — all part of the risk/reward of holding FUM. And the fact that funding is coming, and therefore USM burns will resume, should reassure USM holders and faith in the peg. I suspect even a slowly declining price would suffice: say 50% per day = 2.85% per hour, updated continuously, or every minute/hour. Anyway there’s more fiddling that could be done here but I think a time component to the FUM buy price is a fine idea.)

F. Other parts of the system

  • What price oracle to use (for ETH/USD, the only external data feed the system relies on) is a question others would know more about. Off the top of my head I’d suggest using the median of the Uniswap prices of several other stablecoins: say USDT, USDC, PAX, DAI, TUSD.
  • Funding USM->FUM: It’s probably a good idea to make fund support USM as an input (so, USM->FUM), along with ETH as described above (ETH->FUM). In particular, most of the worst cases for the system occur when ETH’s price is dropping, ie, times when funders are probably better off spending USM ($) than ETH. This is not a deep change, basically just a convenience function wrapping burn + fund.

    (Note that a USM->FUM fund op has zero effect on the amount of ETH in the pool, even after fees. It just burns some of the user’s USM and gives them fresh FUM in return, leaving the ETH pool untouched.)
  • Limit buy orders: A promising addition to the design would be a funding “order book”, where users could place (cancelable) limit buy orders of the form “create 2,000 FUM for 6 ETH” or “create 2,000 FUM for 600 USM”, to be triggered if the price dropped to the specified rate. This amounts to adding an optional max_price_in_eth/max_price_in_usm parameter to the fund op. A “buy wall” like this could provide a second line of defense against insolvency, on top of the buffer ETH already in the pool. And again, because the prices of ETH and FUM are likely to be strongly correlated, USM-denominated bids should be particularly stabilizing.
  • Governance should be, in the spirit of our goal here (and of Uniswap), minimal — “deploy and forget.” No “override” freeze/withdraw functions: if fixes are needed, a new smart contract should be launched and users can choose to migrate to it. The only administrator-restricted operation I would envision is one to specify a “voluntary upgrade” contract, which users would then have the option of auto-migrating their holdings to by themselves calling a second function. But otherwise the system should be a true, decentralized, governance-less DAO.
  • Other, non-USD pegs ought to be easy: the only challenge is to find a good price source (oracle) for, eg, ETH/EUR. In fact easier than a EUR-coin would be a BTC-pegged coin, since ETH/BTC is easy to source from Uniswap. So the pool would still contain ETH, but depositing 1 ETH would earn your pick of:
    a) mint: 0.025 (or whatever the ETH/BTC rate was at the time) “BTC-pegcoins”, pegged to BTC in the same way USM is pegged to USD; or
    b) fund: 1 ETH worth of “BTC funding-coins” — leveraged bets on ETH/BTC in the same way FUM is a leveraged bet on ETH/USD.

G. Attack surface

Just some quick preliminary thoughts:

  1. Price oracle manipulation. If an attacker can manipulate the price source, they can break the peg, buy low and sell high, drain the pool — lots of good stuff. Flash loans might also help exploits. So the oracle is the most obvious point of weakness of the system, and becomes a greater target as the ETH in the pool grows. In our favor is that this is a shared weakness with many other systems/smart contracts, so we can probably piggyback on their work. One obvious thing that might help is to make some or all of the four operations impose a delay: eg, rather than “give USM, get back ETH”, the logic for burncould be “give USM, wait one hour, get back ETH.” This would be a less pleasant UX, but the safety benefit against price manipulations might be worth it.
  2. Price oracle lag. Daniel Goldman notes that it’s not just a matter of manipulating the oracle: even a pretty-accurate oracle could still produce brief price inaccuracies that a trading bot might be able to pick off. Basically a real-time trading-data feed is quite a sensitive thing, that Ethereum wasn’t really designed to deliver on-chain. And the USM system is quite sensitive to real-time price. Some possible ways this sensitivity could be reduced:
    a) Delays between initiation and execution, as mentioned above.
    b) Randomizing that delay, eg, “your mint will be processed at the ETH/USD price as of a random time in the next hour.”
    c) Taking the median of a larger number of source prices: eg, multiple stablecoins on multiple platforms.
    d) Higher fees. The round-trip buy-and-sell fee in my example code below is 0.6%, but it’s just a proof of concept.
    e) In general, this is again a challenge shared to some degree by many other on-chain systems, and it’s worth following their best practices.
  3. ETH price drop. The most obvious way for a collateral-backed system to fail is for the collateral to lose value. In this system however the damage seems somewhat contained. With redemptions disabled above MAX_DEBT_RATIO, if ETH plunges the system just halts until fresh capital is injected. FUM holders can certainly lose most of their money, but that’s a compensated risk, like a borrower defaulting on a loan, rather than a systemic failure like a big insurer going under. In particular, as long as 1. the price oracle remains accurate and 2. new funders always eventually materialize, USM’s peg to the dollar should be robust against ETH price drops. So the price oracle seems like a greater vulnerability.
  4. A bug in the smart contract. But really how likely is that?

H. Proof-of-concept implementation

I’ve put together a simple Python command-line implementation, usm.py. I’m sure there are still bugs and I may make further changes, but it works well enough for you to get the hang of how the system works. Here’s a sample transcript mirroring operations from the examples above:

I. Predecessors

MakerDAO, BitShares (which influenced MakerDAO) and Uniswap are three projects I had in mind while designing this, but one of the reasons to put a writeup like this out there is to find out about all the others who’ve done related work! A couple that have cropped up so far — please do let me know others:

  • Gastón Zanitti noted that MoneyOnChain (“DeFi for Bitcoin”), a project dating back to at least May 2018, has a substantially similar design, dividing a pool of capital — in their case RBTC, a representation of Bitcoin on the Rootstock platform — between a stablecoin DOC and a leveraged-coin BPRO. There are significant differences but it’s a definite predecessor. See eg this deep dive.
  • Harry Glynn invoked Ameen Soleimani’s Metacoin, and I see some strong similarities in structure and ethos, though MetaCoin is more explicitly modeled on MakerDAO with its variable interest rate. Read more in Ameen’s launch post.

J. Next steps

I have no further plans for this idea at the moment (eg, a production version), but I might sometime, and I’d be happy if others did! Feel free to send me questions or ideas on Twitter or by email and I’ll reply if I get a chance. Or of course you can always do further work on it yourself.

Finally, a big 🙏❤️ to the new open-source technologies that have made experiments like this possible, like MakerDao, BitShares, Uniswap and Ethereum, and to the living legends who built and shared them!

--

--

Jacob Eliosoff
Jacob Eliosoff

Written by Jacob Eliosoff

Cryptocurrency enthusiast with a background in software development, finance and teaching. @JaEsf on Twitter, work http://calibratedmarkets.com/.

Responses (3)