Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: Refactor oracle migration script to use subgraph #437

Merged
merged 9 commits into from
Feb 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions scripts/dataUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const fetch = require('node-fetch')

const postQuery = async (endpoint, query) => {
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query }),
}
const url = endpoint
const response = await fetch(url, options)
const data = await response.json()
if (data.errors) {
throw new Error(data.errors[0].message)
} else {
return data
}
}

const getSubgraphUrl = (network, internal) => {
const prefix = internal ? 'gamma-internal' : 'gamma'
let url = `https://api.thegraph.com/subgraphs/name/opynfinance/${prefix}-${network}`
if (network === 'mainnet' && internal) {
url = 'https://api.thegraph.com/subgraphs/name/opynfinance/playground'
}
console.log(`Using subgraph endpoint`, url)
return url
}

/**
* Get all oTokens
* @returns {Promise<any[]>}
*/
module.exports.getOTokens = async (network, internal) => {
const currentTimeStamp = Math.floor(Date.now() / 1000)

const query = `
{
otokens (
first: 1000,
where: {
expiryTimestamp_lt: ${currentTimeStamp},
}
) {
strikeAsset {
id
}
underlyingAsset {
id
}
collateralAsset {
id
}
isPut
expiryTimestamp
}
}`
try {
const url = getSubgraphUrl(network, internal)
const response = await postQuery(url, query)
return response.data.otokens
} catch (error) {
console.error(error)
return []
}
}

/**
* Get all oTokens
*/
module.exports.getAllSettlementPrices = async (asset, network, internal) => {
const query = `
{
expiryPrices(
first: 1000,
where: {
asset: "${asset}"
}
){
expiry
price
}
}

`
try {
const url = getSubgraphUrl(network, internal)
const response = await postQuery(url, query)
return response.data.expiryPrices
} catch (error) {
console.error(error)
throw 'WTF'
return null
}
}

/**
*
*/
module.exports.getCoinGeckData = async (coinId, expiry) => {
const date = new Date(expiry * 1000)
const yyyymmdd = date.toISOString().split('T')[0]
const [year, month, day] = yyyymmdd.split('-')
const dateString = `${day}-${month}-${year}`
const url = `https://api.coingecko.com/api/v3/coins/${coinId}/history?date=${dateString}&localization=false`
const data = await (await fetch(url)).json()
return Math.floor(data.market_data.current_price.usd * 1e8)
}
121 changes: 88 additions & 33 deletions scripts/migrateOracle.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,114 @@
const BigNumber = require('bignumber.js')

const yargs = require('yargs')

const OtokenFactory = artifacts.require('OtokenFactory.sol')
const Oracle = artifacts.require('Oracle.sol')
const Otoken = artifacts.require('Otoken.sol')

const apis = require('./dataUtils')

/**
* @example
* truffle exec scripts/migrateOracle.js
* --network kovan
* --internal true
* --newOracle 0x8b71104e11931775957932728717b0F81461bc0d
* --asset 0x50570256f0da172a1908207aAf0c80d4b279f303
* --coinId bitcoin
*/
module.exports = async function(callback) {
try {
const options = yargs
.usage('Usage: --network <network> --factory <otokenFactory> --oldOracle <oldOracle> --newOracle <newOracle> --asset <asset> --gasPrice <gasPrice>')
.option('network', {describe: '0x exchange address', type: 'string', demandOption: true})
.option('factory', {describe: 'Otoken factory address', type: 'string', demandOption: true})
.option('oldOracle', {describe: 'Old oracle module address', type: 'string', demandOption: true})
.option('newOracle', {describe: 'New oracle module address', type: 'string', demandOption: true})
.option('asset', {describe: 'Asset address to migrate', type: 'string', demandOption: true})
.option('gasPrice', {describe: 'Gas price in WEI', type: 'string', demandOption: false}).argv

.usage(
'Usage: --network <network> --internal <internal> --newOracle <newOracle> --asset <asset> --coinId <coinId> --gasPrice <gasPrice>',
)
.option('network', { describe: '0x exchange address', type: 'string', demandOption: true })
.option('newOracle', { describe: 'New oracle module address', type: 'string', demandOption: true })
.option('asset', { describe: 'Asset address to migrate', type: 'string', demandOption: true })
.option('internal', { describe: 'Internal network or not', type: 'string', demandOption: true })
.option('coinId', {
describe: 'coingecko id used to search for price if it was not reported in our oracle',
type: 'string',
})
.option('gasPrice', { describe: 'Gas price in WEI', type: 'string', demandOption: false }).argv

console.log('Init contracts 🍕')

const factory = await OtokenFactory.at(options.factory)
const oldOracle = await Oracle.at(options.oldOracle)
const asset = options.asset.toLowerCase()

const newOracle = await Oracle.at(options.newOracle)

console.log(`Getting list of create Otokens with asset ${options.asset} 🍕`)
console.log(`Getting list of create Otokens with asset ${asset} 🍕`)

const otokens = await apis.getOTokens(options.network, options.internal === 'true')

console.log(`# of otokens from subgraph: ${otokens.length}`)

const prices = await apis.getAllSettlementPrices(asset, options.network, options.internal === 'true')

const oTokensWithAsset = otokens.filter(
otoken =>
otoken.underlyingAsset.id === asset || otoken.collateralAsset.id === asset || otoken.strikeAsset.id === asset,
)

const filteredExpiries = oTokensWithAsset.reduce((prev, curr) => {
if (!prev.includes(curr.expiryTimestamp)) {
return [...prev, curr.expiryTimestamp]
} else {
return [...prev]
}
}, [])

console.log(`expires needed to fill in ${filteredExpiries.length}`)

const knownRecordSubgraph = prices.filter(priceObj => filteredExpiries.includes(priceObj.expiry))

const knownPrices = knownRecordSubgraph.map(obj => obj.price)
const knownExpires = knownRecordSubgraph.map(obj => obj.expiry)

let finalPrices = []
let finalExpires = []

const currentTimeStamp = Math.floor(Date.now() / 1000);
const otokenLength = new BigNumber(await factory.getOtokensLength())

let expiriesToMigrate = []
let pricesToMigrate = []
const missingExpires = filteredExpiries.filter(
expiry => !knownRecordSubgraph.map(obj => obj.expiry).includes(expiry),
)

for(let i = 0; i < otokenLength.toFixed(); i++) {
const otokenAddress = await factory.otokens(i)
const otokenDetails = await (await Otoken.at(otokenAddress)).getOtokenDetails()
const expiry = new BigNumber(otokenDetails[4])
if (options.coinId) {
console.log(`Using coinID ${options.coinId} to fill in ${missingExpires.length} missing prices! `)

if((otokenDetails[0] == options.asset || otokenDetails[1] == options.asset || otokenDetails[2] == options.asset) && (expiry.toNumber() < currentTimeStamp) && (!expiriesToMigrate.includes(expiry.toString()))) {
const priceToMigrate = new BigNumber((await oldOracle.getExpiryPrice(options.asset, expiry.toString()))[0])
const missingPrices = []
for (const expiry of missingExpires) {
const price = await apis.getCoinGeckData(options.coinId, expiry)
missingPrices.push(price.toString())
console.log(`Got price for expiry ${expiry} from CoinGeck: ${price}`)
}

expiriesToMigrate.push(expiry.toString())
pricesToMigrate.push(priceToMigrate.toString())
finalPrices = [...knownPrices, ...missingPrices]
finalExpires = [...knownExpires, ...missingExpires]
} else {
if (missingExpires.length > 0) {
console.log(
`Missing ${missingExpires.length} prices to support redeeming all options. Please put in coinId to fetch those `,
)
}

finalPrices = knownPrices
finalExpires = knownExpires
}

console.log(`Found ${expiriesToMigrate.length} expiry price 🎉`)
console.log(`Final expires`, finalExpires)
console.log(`Final prices`, finalPrices)

// console.log(`Found ${expiriesToMigrate.length} expiry price 🎉`)

if (finalPrices.length === 0) throw 'Nothing to Migrate'

console.log(`Migrating prices to new Oracle at ${newOracle.address} 🎉`)

const tx = await newOracle.migrateOracle(options.asset, expiriesToMigrate, pricesToMigrate)
const overrideOptions = options.gasPrice ? { gasPrice: options.gasPrice } : undefined
const tx = await newOracle.migrateOracle(options.asset, finalExpires, finalPrices, overrideOptions)

console.log(`Oracle prices migration done for asset ${options.asset}! 🎉`)
console.log(`Transaction hash: ${tx.transactionHash}`)
console.log(`Transaction hash: ${tx.tx}`)

callback()
} catch (err) {
callback(err)
}
}
}
2 changes: 1 addition & 1 deletion truffle-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ module.exports = {
confirmations: 2,
timeoutBlocks: 50,
skipDryRun: false,
gasPrice: 100000000000,
gasPrice: 25000000000,
},
avax: {
provider: () => new HDWalletProvider(mnemonic, 'https://api.avax.network/ext/bc/C/rpc'),
Expand Down