Published
Edited
Sep 22, 2020
Importers
1 star
Insert cell
Insert cell
contractsJSON = await FileAttachment("open_oracle.contracts@2.json").json()
Insert cell
Insert cell
async function setupFork(aliases, network = 'mainnet') {
const world = new World(await GanacheEnv())
await world.env.read(github(`networks/${network}.json`, 'compound-finance/compound-protocol', 'uniswap-oracle'))
await world.env.load(contractsJSON)
await world.env.assign(aliases)
await world.fork(network, {unlocked_accounts: Object.values(aliases), useProxy: true})
await assert(await world.network() == 'mainnet', `not actually attached to mainnet`)
return world;
}
Insert cell
world = setupFork({
CompHolder: 'Reservoir',
ETHWhale: '0xF977814e90dA44bFA03b6295A0616a897441aceC',
USDTWhale: '0x7b8c69a0f660cd43ef67948976daae77bc6a019b',
USDCWhale: '0xa700a7e660669ff27025b2db95af2e61bce910e6'
})
Insert cell
basicCompoundScenario && world.history.slice(-20)
Insert cell
basicCompoundScenario = {
const Compound = await world.env.library(CompoundLib)

const {
address,
deployAndSanityCheck,
postPricesAndSanityCheck,
voteIntoProtocol
} = await world.env.library(UniswapAnchoredViewLib)
await world.env.assign({
// settings for deployment
coinbaseReporter: address('0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC'),
reporter: address('0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'),
reporterKey: '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d',
anchorToleranceMantissa: exp(.2),
anchorPeriod: 30 * 60,
tokenConfigs: await tokenConfigs(world),

// old settings for sanity checks
btcUsdPrice: exp(9685.38, 6),
ethUsdPrice: exp(303.30, 6)
})
// check this scenario
const accountLiquidityHolds = [
// XXX ETH -> USD
//Compound.$getAccountLiquidity(address('0x40f5bb61333fdcece77d12adcf6c9c309ec3d387')),
//Compound.$getAccountLiquidity(address('0x7d5024bfb6512211acb7521a76a8d60f8980fd7c'))
]
await world.invariant([accountLiquidityHolds], [
//deployAndSanityCheck, // done
//postPricesAndSanityCheck, // slow
voteIntoProtocol
], areEqual)

await world.exec([
Compound.$$basicLiquidate(address('ETHWhale'), 'cETH', exp(1), 'cUSDT', exp(250, 6), exp(100, 6), address('USDTWhale')), // depends on ETH price
Compound.$$basicLiquidate(address('ETHWhale'), 'cETH', exp(1), 'cUSDC', exp(250, 6), exp(100, 6), address('USDCWhale')) // depends on ETH price
])

return world;
}
Insert cell
Insert cell
async function UniswapAnchoredViewLib({abi, keccak, val, address, array, bytes, string, uint, Enum, Struct}, env) {
const PriceSource = new Enum('PriceSource', ['FIXED_ETH', 'FIXED_USD', 'REPORTER'])
const TokenConfig = new Struct('TokenConfig', {
cToken: 'address',
underlying: 'address',
symbolHash: 'bytes32',
baseUnit: 'uint256',
priceSource: 'PriceSource',
fixedPrice: 'uint256',
uniswapMarket: 'address',
isUniswapReversed: 'bool'
})

const Observation = new Struct('Observation', {
timestamp: 'uint256',
acc: 'uint256'
})

const CompGov = await env.library(CompGovLib)

const $deployPriceData = () => ({deploy: 'OpenOraclePriceData', as: 'PriceData', recycle: true})
const $deployPriceFeed = () => ({deploy: 'UniswapAnchoredView', as: 'PriceFeed', recycle: true, args: () => [
address('PriceData'),
address('coinbaseReporter'),
uint('anchorToleranceMantissa'),
uint('anchorPeriod'),
array('TokenConfig')(Object.values(val('tokenConfigs')))
]}) // thunked, to defer until priceData is deployed
const $getTokenConfig = (i) => ({call: 'getTokenConfig', on: 'PriceFeed', args: [i], returns: TokenConfig})
const $price = (symbol) => ({call: 'price', on: 'PriceFeed', args: [symbol], returns: p => p / 1e6})
const $postPrices = (messages, signatures, symbols) => {
return {send: 'postPrices', to: 'PriceFeed', args: [
array(bytes)(messages),
array(bytes)(signatures),
array(string)(symbols)
], emits: ['PriceUpdated', '!PriceGuarded']}
}
const $postRawPrices = (prices, timestamp) => {
function encode(prices) {
return Object.entries(prices).map(([symbol, price]) => {
return abi.encode(['prices', uint(timestamp), symbol, uint(price)])
})
}

function sign(encoded, network = 'mainnet') {
const web3 = w3i(network)
return encoded.map((message) => {
const hash = keccak(message)
const {r, s, v} = web3.eth.accounts.sign(hash, val('reporterKey'))
const signature = abi.encode([bytes(r, 32), bytes(s, 32), uint(v, 8)])
const signatory = web3.eth.accounts.recover(hash, v, r, s)
return {signature, signatory}
})
}

const messages = encode(prices)
const signatures = sign(messages).map(s => s.signature)
const symbols = Object.keys(prices)
return $postPrices(messages, signatures, symbols)
}
const $$sanityCheckDeploy = () => Object.values(val('tokenConfigs')).map( // XXX what else?
(cfg, i) => ({...$getTokenConfig(i), expect: cfg})
)

const $$sanityCheckPrices = (prices) => Object.keys(prices).map(
(symbol) => ({...$price(symbol), expect: x => Math.abs(x - prices[symbol]) < 0.01})
)

const $$proposeOracle = () => [
{...CompGov.$delegate(address('CompHolder')), from: 'CompHolder'},
{...CompGov.$propose(
"Migrate to Compound Price Feed",
{Comptroller: '_setPriceOracle(address)', args: [address('PriceFeed')]}
), from: 'CompHolder'}
]

async function deployAndSanityCheck(world) {
return world.exec([$deployPriceData(), $deployPriceFeed(), $$sanityCheckDeploy()])
}
async function postPricesAndSanityCheck(world, symbols = ['ETH', 'BTC', 'ZRX', 'BAT', 'REP', 'DAI', 'KNC', 'LINK', 'COMP']) {
const reported = await (await fetch('https://prices.compound.finance/coinbase')).json()
return world.exec([
$postPrices(reported.messages, reported.signatures, symbols),
$$sanityCheckPrices(select(reported.prices, symbols))
])
}

async function postFakePricesAndSanityCheck(world) {
const recent = await world.lastBlock(false)
const prices = {
ETH: uint('ethUsdPrice', 64),
BTC: uint('btcUsdPrice', 64)
}
return world.exec([$postRawPrices(prices, recent.timestamp), $$sanityCheckPrices(prices)])
}
async function voteIntoProtocol(world) {
return world.emits([
//$$proposeOracle(),
//(w) => CompGov.whipLastProposal(address('CompHolder'), w)
CompGov.$$waitAndExecute(19)
], 'NewPriceOracle')
}

return {
// Types
PriceSource,
TokenConfig,

// Functions
$deployPriceFeed,
$deployPriceData,

$getTokenConfig,
$price,
$postPrices,
$postRawPrices,

$$sanityCheckDeploy,
$$sanityCheckPrices,
$$proposeOracle,
deployAndSanityCheck,
postPricesAndSanityCheck,
voteIntoProtocol
}
}
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more