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')))
]})
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(
(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([
CompGov.$$waitAndExecute(19)
], 'NewPriceOracle')
}
return {
PriceSource,
TokenConfig,
$deployPriceFeed,
$deployPriceData,
$getTokenConfig,
$price,
$postPrices,
$postRawPrices,
$$sanityCheckDeploy,
$$sanityCheckPrices,
$$proposeOracle,
deployAndSanityCheck,
postPricesAndSanityCheck,
voteIntoProtocol
}
}