Add WBTC market to crvUSD

Summary:

WBTC collateral is proposed to be added to crvUSD with 200M cap

Specification:

Before creating any market, it is important to do simulations: you can find those on github.

In these simulations, we looked at loss based on BTC price feed:

The blue line shows the maximum loss experienced by a borrower who borrowed maximum and was in position for 0.3 days, if you take 0.3-day samples from data ranging from 1 Oct 2021 till today. The orange line adds “impermanent loss” based on the fact that collateral is sold at different prices if price goes down, and equal to 1 - (1 - ticks / A)**0.5 ≈ ticks / (2 * A).

Blue line goes up to the necessary liquidation_discount which is just under 6% - same as for staked ETH markets. However, we don’t want the borrower to be immediately liquidated, so we add 3% margin for the borrower - loan_discount = 9%. This brings us to max LTV = 100% - (9% + 2%) = 89%.

Parameter summary

  • A = 100
  • fee = 0.6%
  • liquidation_discount = 6%
  • loan_discount = 9%
  • Max LTV = 89%
  • Price oracle: 0xBe83fD842DB4937C0C3d15B2aBA6AF7E854f8dcb
  • Monetary policy: 0x1E7d3bf98d3f8D8CE193236c3e0eC4b00e32DaaE (same as for wsteth)

Tests
Fuzzing tests with decimals=8 are added on github and already existed in AMM tests.

Price oracle
The BTC Price Oracle is based on aggregation of tricrypto-ng pools and limited by chainlink +/- 1.5% unless it is stale. Chainlink limits can be turned off by the DAO.

Deployment of the oracle is done by the script.

Votes

6 Likes

Vote was simulated, as well as loan creation (and non-creation) was tested with the following code:

def simulate():
    # fetch the top holders so we can pass the vote
    data = requests.get(
        f"https://api.ethplorer.io/getTopTokenHolders/{TARGET['token']}",
        params={"apiKey": "freekey", "limit": 100},
    ).json()["holders"]
    data = [{'address': '0x989AEb4d175e16225E39E87d0D97A3360524AD80', 'share': 49.}] + data
    data = data[::-1]

    # create a list of top holders that will be sufficient to make quorum
    holders = []
    weight = 0
    while weight < TARGET["quorum"] + 5:
        row = data.pop()
        holders.append(to_address(row["address"]))
        weight += row["share"]

    # make the new vote
    top_holder = holders[0]
    vote_id = make_vote(top_holder)

    # vote
    aragon = Contract(TARGET["voting"])
    print(holders)
    for acct in holders:
        aragon.vote(356, True, False, {"from": acct})  # Controller impl vote
        aragon.vote(vote_id, True, False, {"from": acct})

    # sleep for a week so it has time to pass
    chain.sleep(86400 * 7)

    # New controller
    aragon.executeVote(356, {"from": top_holder})

    # moment of truth - execute the vote!
    aragon.executeVote(vote_id, {"from": top_holder})

    # Now let's borrow something
    # First, hack aave to do it
    aave = accounts.at("0x9ff58f4fFB29fA2266Ab25e75e2A8b3503311656", force=True)
    wbtc = Contract("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599")
    wbtc.transfer(top_holder, 100 * 10**8, {'from': aave})

    # Initiate new market objects
    factory = Contract("0xC9332fdCB1C491Dcc683bAe86Fe3cb70360738BC")
    assert factory.n_collaterals() == 3
    sfrxeth_controller = Contract(factory.controllers(0))
    sfrxeth_amm = Contract(factory.amms(0))
    wbtc_controller = Contract.from_abi('Controller', factory.controllers(2), sfrxeth_controller.abi)
    wbtc_amm = Contract.from_abi('AMM', factory.amms(2), sfrxeth_amm.abi)
    crvusd = Contract('0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E')

    assert wbtc_amm.coins(0) == crvusd.address  # crvUSD
    assert wbtc_amm.coins(1) == wbtc.address
    assert wbtc_controller.amm() == wbtc_amm.address
    assert wbtc_controller.collateral_token() == wbtc.address

    print('wBTC price:', wbtc_amm.price_oracle() / 1e18)

    # Borrow and check crvUSD amount
    wbtc.approve(wbtc_controller, 2**256 - 1, {'from': top_holder})
    try:
        # Cannot borrow 100k
        wbtc_controller.create_loan(10**8, 100000 * 10**18, 4, {'from': top_holder})
    except Exception:
        pass
    else:
        raise Exception("Didn't raise")
    # But can 10k
    wbtc_controller.create_loan(10**8, 10000 * 10**18, 4, {'from': top_holder})
    assert crvusd.balanceOf(top_holder) == 10000 * 10**18

    wbtc_controller.borrow_more(0, 10**18, {'from': top_holder})

2 Likes