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