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

[contractkit] SortedOraclesWrapper + tests #1405

Merged
merged 18 commits into from
Oct 23, 2019
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
44cf9a7
pull all the contractkit work out of #1394
yerdua Oct 18, 2019
5d45823
Merge branch 'master' into yerdua/sorted-oracles-wrapper-contractkit
yerdua Oct 18, 2019
e5fee87
don't need to use ts-ignore if you fill an empty block with a comment
yerdua Oct 18, 2019
86c1a57
start with an empty array for priceOracleAccounts just to have this c…
yerdua Oct 18, 2019
1459059
I don't know how versioning works
yerdua Oct 21, 2019
c9054dc
bump version of contractkit
yerdua Oct 21, 2019
234ce6f
bumping the version isn't necessary
yerdua Oct 21, 2019
a7d27b0
pass in the oracle address to report from, rather than relying on kit…
yerdua Oct 21, 2019
0aa7fd3
tests that the numerator/denominator division happens
yerdua Oct 21, 2019
60992ab
simplify test expectations - use expected string values
yerdua Oct 21, 2019
203875c
Merge branch 'master' into yerdua/sorted-oracles-wrapper-contractkit
yerdua Oct 22, 2019
0434e81
Merge branch 'master' into yerdua/sorted-oracles-wrapper-contractkit
yerdua Oct 22, 2019
3077458
Merge branch 'master' into yerdua/sorted-oracles-wrapper-contractkit
yerdua Oct 22, 2019
be794b1
numRates can just be a number, rather than a BigNumber
yerdua Oct 22, 2019
34b8907
Merge branch 'master' into yerdua/sorted-oracles-wrapper-contractkit
yerdua Oct 23, 2019
f752247
console.info is prefered over console.log, so replace .log instances
yerdua Oct 23, 2019
f73da85
Merge branch 'master' into yerdua/sorted-oracles-wrapper-contractkit
yerdua Oct 23, 2019
ff148d0
Merge branch 'master' into yerdua/sorted-oracles-wrapper-contractkit
yerdua Oct 23, 2019
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
2 changes: 1 addition & 1 deletion packages/contractkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"clean:all": "yarn clean && rm -rf src/generated",
"build:gen": "yarn --cwd ../protocol build",
"prepublishOnly": "yarn build:gen && yarn build",
"test:prepare": "yarn --cwd ../protocol devchain generate .devchain",
"test:prepare": "yarn --cwd ../protocol devchain generate .devchain --migration_override src/test-utils/migration-override.json",
"test": "jest --runInBand",
"lint": "tslint -c tslint.json --project ."
},
Expand Down
7 changes: 7 additions & 0 deletions packages/contractkit/src/test-utils/ganache-test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import * as fs from 'fs'
import Web3 from 'web3'
import { JsonRPCResponse } from 'web3/providers'
import { injectDebugProvider } from '../providers/debug-provider'

// This file specifies accounts available when ganache is running. These are derived
// from the MNEMONIC
export const NetworkConfig = JSON.parse(
fs.readFileSync('src/test-utils/migration-override.json').toString()
)

export function jsonRpcCall<O>(web3: Web3, method: string, params: any[]): Promise<O> {
return new Promise<O>((resolve, reject) => {
web3.currentProvider.send(
Expand Down
11 changes: 11 additions & 0 deletions packages/contractkit/src/test-utils/migration-override.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"stableToken": {
"initialAccounts": [],
"priceOracleAccounts": [
"0x5409ED021D9299bf6814279A6A1411A7e866A631",
"0xE36Ea790bc9d7AB70C55260C66D52b1eca985f84",
"0x06cEf8E666768cC40Cc78CF93d9611019dDcB628",
"0x7457d5E02197480Db681D3fdF256c7acA21bDc12"
]
}
}
yerdua marked this conversation as resolved.
Show resolved Hide resolved
240 changes: 240 additions & 0 deletions packages/contractkit/src/wrappers/SortedOracles.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import { Address, CeloContract } from '../base'
import { newKitFromWeb3 } from '../kit'
import { NetworkConfig, testWithGanache } from '../test-utils/ganache-test'
import { OracleRate, SortedOraclesWrapper } from './SortedOracles'

/*
TEST NOTES:
- In migrations: The only account that has cUSD is accounts[0]
*/

testWithGanache('SortedOracles Wrapper', (web3) => {
// NOTE: These values are set in test-utils/network-config.json, and are derived
// from the MNEMONIC. If the MNEMONIC has changed, these will need to be reset.
// To do that, look at the output of web3.eth.getAccounts(), and pick a few
// addresses from that set to be oracles
const stableTokenOracles: Address[] = NetworkConfig.stableToken.priceOracleAccounts
const oracleAddress = stableTokenOracles[stableTokenOracles.length - 1]

const kit = newKitFromWeb3(web3)
let allAccounts: Address[]
let sortedOracles: SortedOraclesWrapper
let stableTokenAddress: Address
let nonOracleAddress: Address

beforeAll(async () => {
sortedOracles = await kit.contracts.getSortedOracles()
stableTokenAddress = await kit.registry.addressFor(CeloContract.StableToken)
allAccounts = await web3.eth.getAccounts()
nonOracleAddress = allAccounts.find((addr) => {
return !stableTokenOracles.includes(addr)
})!
})

describe('#report', () => {
const numerator = 16
const denominator = 1

describe('when reporting from a whitelisted Oracle', () => {
it('should be able to report a rate', async () => {
const initialRates: OracleRate[] = await sortedOracles.getRates(CeloContract.StableToken)

const tx = await sortedOracles.report(
CeloContract.StableToken,
numerator,
denominator,
oracleAddress
)
await tx.sendAndWaitForReceipt()

const resultingRates: OracleRate[] = await sortedOracles.getRates(CeloContract.StableToken)
expect(resultingRates).not.toMatchObject(initialRates)
})

describe('when inserting into the middle of the existing rates', () => {
beforeEach(async () => {
const rates = [15, 20, 17]
for (let i = 0; i < stableTokenOracles.length - 1; i++) {
const tx = await sortedOracles.report(
CeloContract.StableToken,
rates[i],
denominator,
stableTokenOracles[i]
)
await tx.sendAndWaitForReceipt()
}
})

const expectedLesserKey = stableTokenOracles[0]
const expectedGreaterKey = stableTokenOracles[2]

const expectedOracleOrder = [
stableTokenOracles[1],
stableTokenOracles[2],
oracleAddress,
stableTokenOracles[0],
]

it('passes the correct lesserKey and greaterKey as args', async () => {
const tx = await sortedOracles.report(
CeloContract.StableToken,
numerator,
denominator,
oracleAddress
)
const actualArgs = tx.txo.arguments
expect(actualArgs[3]).toEqual(expectedLesserKey)
expect(actualArgs[4]).toEqual(expectedGreaterKey)

await tx.sendAndWaitForReceipt()
})

it('inserts the new record in the right place', async () => {
const tx = await sortedOracles.report(
CeloContract.StableToken,
numerator,
denominator,
oracleAddress
)
await tx.sendAndWaitForReceipt()

const resultingRates: OracleRate[] = await sortedOracles.getRates(
CeloContract.StableToken
)

expect(resultingRates.map((r) => r.address)).toEqual(expectedOracleOrder)
})
})
})

describe('when reporting from a non-oracle address', () => {
it('should raise an error', async () => {
const tx = await sortedOracles.report(
CeloContract.StableToken,
numerator,
denominator,
nonOracleAddress
)
await expect(tx.sendAndWaitForReceipt()).rejects.toThrow('sender was not an oracle')
})

it('should not change the list of rates', async () => {
const initialRates = await sortedOracles.getRates(CeloContract.StableToken)
try {
const tx = await sortedOracles.report(
CeloContract.StableToken,
numerator,
denominator,
nonOracleAddress
)
await tx.sendAndWaitForReceipt()
} catch (err) {
// We don't need to do anything with this error other than catch it so
// it doesn't fail this test.
} finally {
const resultingRates = await sortedOracles.getRates(CeloContract.StableToken)
expect(resultingRates).toMatchObject(initialRates)
}
})
})
})

/**
* Proxy Calls to view methods
*
* The purpose of these tests is to verify that these wrapper functions exist,
* are calling the contract methods correctly, and get some value back. The
* values checked here are often dependent on setup occuring in the protocol
* migrations run in `yarn test:prepare`. If these tests are failing, the first
* thing to check is if there have been changes to the migrations
*/
describe('#getRates', () => {
beforeEach(async () => {
for (let i = 0; i < stableTokenOracles.length; i++) {
// reports these values:
// 1/2, 2/2, 3/2, 4/2
// resulting in: 0.5, 1, 1.5, 2
const tx = await sortedOracles.report(
CeloContract.StableToken,
i + 1,
2,
stableTokenOracles[i]
)
await tx.sendAndWaitForReceipt()
}
})
it('SBAT getRates', async () => {
const rates = await sortedOracles.getRates(CeloContract.StableToken)
expect(rates.length).toBeGreaterThan(0)
for (const rate of rates) {
expect(rate).toHaveProperty('address')
expect(rate).toHaveProperty('rate')
expect(rate).toHaveProperty('medianRelation')
}
})

it('returns the rate as the result of the calculation numerator/denominator', async () => {
const expectedRates = ['2', '1.5', '1', '0.5']
const response = await sortedOracles.getRates(CeloContract.StableToken)
const actualRates = response.map((r) => r.rate.toString())
expect(actualRates).toEqual(expectedRates)
})
})

describe('#isOracle', () => {
it('returns true when this address is a whitelisted oracle for this token', async () => {
expect(await sortedOracles.isOracle(CeloContract.StableToken, oracleAddress)).toEqual(true)
})
it('returns false when this address is not an oracle', async () => {
expect(await sortedOracles.isOracle(CeloContract.StableToken, nonOracleAddress)).toEqual(
false
)
})
})

describe('#numRates', () => {
it('returns a count of rates reported for the specified token', async () => {
// Why 1? In packages/protocol/08_stabletoken, a single rate is reported
expect(await sortedOracles.numRates(CeloContract.StableToken)).toEqBigNumber(1)
})
})

describe('#medianRate', () => {
it('returns the key for the median', async () => {
const returnedMedian = await sortedOracles.medianRate(CeloContract.StableToken)
// The value `10` comes from: packages/protocol/migrationsConfig.js:
// stableToken.goldPrice
expect(returnedMedian.rate).toEqBigNumber(10)
})
})

describe('#reportExpirySeconds', () => {
it('returns the number of seconds after which a report expires', async () => {
const result = await sortedOracles.reportExpirySeconds()
expect(result).toEqBigNumber(3600)
})
})

/**
* Helper Functions
*
* These are functions in the wrapper that call other functions, passing in
* some regularly used arguments. The purpose of these tests is to verify that
* those arguments are being set correctly.
*/
describe('getStableTokenRates', () => {
it('gets rates for Stable Token', async () => {
const usdRatesResult = await sortedOracles.getStableTokenRates()
const getRatesResult = await sortedOracles.getRates(CeloContract.StableToken)
expect(usdRatesResult).toEqual(getRatesResult)
})
})

describe('reportStableToken', () => {
it('calls report with the address for StableToken', async () => {
const tx = await sortedOracles.reportStableToken(14, 1, oracleAddress)
await tx.sendAndWaitForReceipt()
expect(tx.txo.arguments[0]).toEqual(stableTokenAddress)
})
})
})
Loading