735546619e
Signed-off-by: T-Hax <>
799 lines
34 KiB
TypeScript
799 lines
34 KiB
TypeScript
import { BigNumber, BigNumberish, Wallet } from 'ethers'
|
|
import { ethers, waffle } from 'hardhat'
|
|
import { OracleTest } from '../typechain/OracleTest'
|
|
import checkObservationEquals from './shared/checkObservationEquals'
|
|
import { expect } from './shared/expect'
|
|
import { TEST_POOL_START_TIME } from './shared/fixtures'
|
|
import snapshotGasCost from './shared/snapshotGasCost'
|
|
import { MaxUint128 } from './shared/utilities'
|
|
|
|
describe('Oracle', () => {
|
|
let wallet: Wallet, other: Wallet
|
|
|
|
let loadFixture: ReturnType<typeof waffle.createFixtureLoader>
|
|
before('create fixture loader', async () => {
|
|
;[wallet, other] = await (ethers as any).getSigners()
|
|
loadFixture = waffle.createFixtureLoader([wallet, other])
|
|
})
|
|
|
|
const oracleFixture = async () => {
|
|
const oracleTestFactory = await ethers.getContractFactory('OracleTest')
|
|
return (await oracleTestFactory.deploy()) as OracleTest
|
|
}
|
|
|
|
const initializedOracleFixture = async () => {
|
|
const oracle = await oracleFixture()
|
|
await oracle.initialize({
|
|
time: 0,
|
|
tick: 0,
|
|
liquidity: 0,
|
|
})
|
|
return oracle
|
|
}
|
|
|
|
describe('#initialize', () => {
|
|
let oracle: OracleTest
|
|
beforeEach('deploy test oracle', async () => {
|
|
oracle = await loadFixture(oracleFixture)
|
|
})
|
|
it('index is 0', async () => {
|
|
await oracle.initialize({ liquidity: 1, tick: 1, time: 1 })
|
|
expect(await oracle.index()).to.eq(0)
|
|
})
|
|
it('cardinality is 1', async () => {
|
|
await oracle.initialize({ liquidity: 1, tick: 1, time: 1 })
|
|
expect(await oracle.cardinality()).to.eq(1)
|
|
})
|
|
it('cardinality next is 1', async () => {
|
|
await oracle.initialize({ liquidity: 1, tick: 1, time: 1 })
|
|
expect(await oracle.cardinalityNext()).to.eq(1)
|
|
})
|
|
it('sets first slot timestamp only', async () => {
|
|
await oracle.initialize({ liquidity: 1, tick: 1, time: 1 })
|
|
checkObservationEquals(await oracle.observations(0), {
|
|
initialized: true,
|
|
blockTimestamp: 1,
|
|
tickCumulative: 0,
|
|
secondsPerLiquidityCumulativeX128: 0,
|
|
})
|
|
})
|
|
it('gas', async () => {
|
|
await snapshotGasCost(oracle.initialize({ liquidity: 1, tick: 1, time: 1 }))
|
|
})
|
|
})
|
|
|
|
describe('#grow', () => {
|
|
let oracle: OracleTest
|
|
beforeEach('deploy initialized test oracle', async () => {
|
|
oracle = await loadFixture(initializedOracleFixture)
|
|
})
|
|
|
|
it('increases the cardinality next for the first call', async () => {
|
|
await oracle.grow(5)
|
|
expect(await oracle.index()).to.eq(0)
|
|
expect(await oracle.cardinality()).to.eq(1)
|
|
expect(await oracle.cardinalityNext()).to.eq(5)
|
|
})
|
|
|
|
it('does not touch the first slot', async () => {
|
|
await oracle.grow(5)
|
|
checkObservationEquals(await oracle.observations(0), {
|
|
secondsPerLiquidityCumulativeX128: 0,
|
|
tickCumulative: 0,
|
|
blockTimestamp: 0,
|
|
initialized: true,
|
|
})
|
|
})
|
|
|
|
it('is no op if oracle is already gte that size', async () => {
|
|
await oracle.grow(5)
|
|
await oracle.grow(3)
|
|
expect(await oracle.index()).to.eq(0)
|
|
expect(await oracle.cardinality()).to.eq(1)
|
|
expect(await oracle.cardinalityNext()).to.eq(5)
|
|
})
|
|
|
|
it('adds data to all the slots', async () => {
|
|
await oracle.grow(5)
|
|
for (let i = 1; i < 5; i++) {
|
|
checkObservationEquals(await oracle.observations(i), {
|
|
secondsPerLiquidityCumulativeX128: 0,
|
|
tickCumulative: 0,
|
|
blockTimestamp: 1,
|
|
initialized: false,
|
|
})
|
|
}
|
|
})
|
|
|
|
it('grow after wrap', async () => {
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 2, liquidity: 1, tick: 1 }) // index is now 1
|
|
await oracle.update({ advanceTimeBy: 2, liquidity: 1, tick: 1 }) // index is now 0 again
|
|
expect(await oracle.index()).to.eq(0)
|
|
await oracle.grow(3)
|
|
expect(await oracle.index()).to.eq(0)
|
|
expect(await oracle.cardinality()).to.eq(2)
|
|
expect(await oracle.cardinalityNext()).to.eq(3)
|
|
})
|
|
|
|
it('gas for growing by 1 slot when index == cardinality - 1', async () => {
|
|
await snapshotGasCost(oracle.grow(2))
|
|
})
|
|
|
|
it('gas for growing by 10 slots when index == cardinality - 1', async () => {
|
|
await snapshotGasCost(oracle.grow(11))
|
|
})
|
|
|
|
it('gas for growing by 1 slot when index != cardinality - 1', async () => {
|
|
await oracle.grow(2)
|
|
await snapshotGasCost(oracle.grow(3))
|
|
})
|
|
|
|
it('gas for growing by 10 slots when index != cardinality - 1', async () => {
|
|
await oracle.grow(2)
|
|
await snapshotGasCost(oracle.grow(12))
|
|
})
|
|
})
|
|
|
|
describe('#write', () => {
|
|
let oracle: OracleTest
|
|
|
|
beforeEach('deploy initialized test oracle', async () => {
|
|
oracle = await loadFixture(initializedOracleFixture)
|
|
})
|
|
|
|
it('single element array gets overwritten', async () => {
|
|
await oracle.update({ advanceTimeBy: 1, tick: 2, liquidity: 5 })
|
|
expect(await oracle.index()).to.eq(0)
|
|
checkObservationEquals(await oracle.observations(0), {
|
|
initialized: true,
|
|
secondsPerLiquidityCumulativeX128: '340282366920938463463374607431768211456',
|
|
tickCumulative: 0,
|
|
blockTimestamp: 1,
|
|
})
|
|
await oracle.update({ advanceTimeBy: 5, tick: -1, liquidity: 8 })
|
|
expect(await oracle.index()).to.eq(0)
|
|
checkObservationEquals(await oracle.observations(0), {
|
|
initialized: true,
|
|
secondsPerLiquidityCumulativeX128: '680564733841876926926749214863536422912',
|
|
tickCumulative: 10,
|
|
blockTimestamp: 6,
|
|
})
|
|
await oracle.update({ advanceTimeBy: 3, tick: 2, liquidity: 3 })
|
|
expect(await oracle.index()).to.eq(0)
|
|
checkObservationEquals(await oracle.observations(0), {
|
|
initialized: true,
|
|
secondsPerLiquidityCumulativeX128: '808170621437228850725514692650449502208',
|
|
tickCumulative: 7,
|
|
blockTimestamp: 9,
|
|
})
|
|
})
|
|
|
|
it('does nothing if time has not changed', async () => {
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 1, tick: 3, liquidity: 2 })
|
|
expect(await oracle.index()).to.eq(1)
|
|
await oracle.update({ advanceTimeBy: 0, tick: -5, liquidity: 9 })
|
|
expect(await oracle.index()).to.eq(1)
|
|
})
|
|
|
|
it('writes an index if time has changed', async () => {
|
|
await oracle.grow(3)
|
|
await oracle.update({ advanceTimeBy: 6, tick: 3, liquidity: 2 })
|
|
expect(await oracle.index()).to.eq(1)
|
|
await oracle.update({ advanceTimeBy: 4, tick: -5, liquidity: 9 })
|
|
|
|
expect(await oracle.index()).to.eq(2)
|
|
checkObservationEquals(await oracle.observations(1), {
|
|
tickCumulative: 0,
|
|
secondsPerLiquidityCumulativeX128: '2041694201525630780780247644590609268736',
|
|
initialized: true,
|
|
blockTimestamp: 6,
|
|
})
|
|
})
|
|
|
|
it('grows cardinality when writing past', async () => {
|
|
await oracle.grow(2)
|
|
await oracle.grow(4)
|
|
expect(await oracle.cardinality()).to.eq(1)
|
|
await oracle.update({ advanceTimeBy: 3, tick: 5, liquidity: 6 })
|
|
expect(await oracle.cardinality()).to.eq(4)
|
|
await oracle.update({ advanceTimeBy: 4, tick: 6, liquidity: 4 })
|
|
expect(await oracle.cardinality()).to.eq(4)
|
|
expect(await oracle.index()).to.eq(2)
|
|
checkObservationEquals(await oracle.observations(2), {
|
|
secondsPerLiquidityCumulativeX128: '1247702012043441032699040227249816775338',
|
|
tickCumulative: 20,
|
|
initialized: true,
|
|
blockTimestamp: 7,
|
|
})
|
|
})
|
|
|
|
it('wraps around', async () => {
|
|
await oracle.grow(3)
|
|
await oracle.update({ advanceTimeBy: 3, tick: 1, liquidity: 2 })
|
|
await oracle.update({ advanceTimeBy: 4, tick: 2, liquidity: 3 })
|
|
await oracle.update({ advanceTimeBy: 5, tick: 3, liquidity: 4 })
|
|
|
|
expect(await oracle.index()).to.eq(0)
|
|
|
|
checkObservationEquals(await oracle.observations(0), {
|
|
secondsPerLiquidityCumulativeX128: '2268549112806256423089164049545121409706',
|
|
tickCumulative: 14,
|
|
initialized: true,
|
|
blockTimestamp: 12,
|
|
})
|
|
})
|
|
|
|
it('accumulates liquidity', async () => {
|
|
await oracle.grow(4)
|
|
|
|
await oracle.update({ advanceTimeBy: 3, tick: 3, liquidity: 2 })
|
|
await oracle.update({ advanceTimeBy: 4, tick: -7, liquidity: 6 })
|
|
await oracle.update({ advanceTimeBy: 5, tick: -2, liquidity: 4 })
|
|
|
|
expect(await oracle.index()).to.eq(3)
|
|
|
|
checkObservationEquals(await oracle.observations(1), {
|
|
initialized: true,
|
|
tickCumulative: 0,
|
|
secondsPerLiquidityCumulativeX128: '1020847100762815390390123822295304634368',
|
|
blockTimestamp: 3,
|
|
})
|
|
checkObservationEquals(await oracle.observations(2), {
|
|
initialized: true,
|
|
tickCumulative: 12,
|
|
secondsPerLiquidityCumulativeX128: '1701411834604692317316873037158841057280',
|
|
blockTimestamp: 7,
|
|
})
|
|
checkObservationEquals(await oracle.observations(3), {
|
|
initialized: true,
|
|
tickCumulative: -23,
|
|
secondsPerLiquidityCumulativeX128: '1984980473705474370203018543351981233493',
|
|
blockTimestamp: 12,
|
|
})
|
|
checkObservationEquals(await oracle.observations(4), {
|
|
initialized: false,
|
|
tickCumulative: 0,
|
|
secondsPerLiquidityCumulativeX128: 0,
|
|
blockTimestamp: 0,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('#observe', () => {
|
|
describe('before initialization', async () => {
|
|
let oracle: OracleTest
|
|
beforeEach('deploy test oracle', async () => {
|
|
oracle = await loadFixture(oracleFixture)
|
|
})
|
|
|
|
const observeSingle = async (secondsAgo: number) => {
|
|
const {
|
|
tickCumulatives: [tickCumulative],
|
|
secondsPerLiquidityCumulativeX128s: [secondsPerLiquidityCumulativeX128],
|
|
} = await oracle.observe([secondsAgo])
|
|
return { secondsPerLiquidityCumulativeX128, tickCumulative }
|
|
}
|
|
|
|
it('fails before initialize', async () => {
|
|
await expect(observeSingle(0)).to.be.revertedWith('I')
|
|
})
|
|
|
|
it('fails if an older observation does not exist', async () => {
|
|
await oracle.initialize({ liquidity: 4, tick: 2, time: 5 })
|
|
await expect(observeSingle(1)).to.be.revertedWith('OLD')
|
|
})
|
|
|
|
it('does not fail across overflow boundary', async () => {
|
|
await oracle.initialize({ liquidity: 4, tick: 2, time: 2 ** 32 - 1 })
|
|
await oracle.advanceTime(2)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(1)
|
|
expect(tickCumulative).to.be.eq(2)
|
|
expect(secondsPerLiquidityCumulativeX128).to.be.eq('85070591730234615865843651857942052864')
|
|
})
|
|
|
|
it('interpolates correctly at max liquidity', async () => {
|
|
await oracle.initialize({ liquidity: MaxUint128, tick: 0, time: 0 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 13, tick: 0, liquidity: 0 })
|
|
let { secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(13)
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(6))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(7)
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(12))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(1)
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(13))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(0)
|
|
})
|
|
|
|
it('interpolates correctly at min liquidity', async () => {
|
|
await oracle.initialize({ liquidity: 0, tick: 0, time: 0 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 13, tick: 0, liquidity: MaxUint128 })
|
|
let { secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(BigNumber.from(13).shl(128))
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(6))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(BigNumber.from(7).shl(128))
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(12))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(BigNumber.from(1).shl(128))
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(13))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(0)
|
|
})
|
|
|
|
it('interpolates the same as 0 liquidity for 1 liquidity', async () => {
|
|
await oracle.initialize({ liquidity: 1, tick: 0, time: 0 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 13, tick: 0, liquidity: MaxUint128 })
|
|
let { secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(BigNumber.from(13).shl(128))
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(6))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(BigNumber.from(7).shl(128))
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(12))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(BigNumber.from(1).shl(128))
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(13))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(0)
|
|
})
|
|
|
|
it('interpolates correctly across uint32 seconds boundaries', async () => {
|
|
// setup
|
|
await oracle.initialize({ liquidity: 0, tick: 0, time: 0 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 2 ** 32 - 6, tick: 0, liquidity: 0 })
|
|
let { secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(BigNumber.from(2 ** 32 - 6).shl(128))
|
|
await oracle.update({ advanceTimeBy: 13, tick: 0, liquidity: 0 })
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(0))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(BigNumber.from(7).shl(128))
|
|
|
|
// interpolation checks
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(3))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(BigNumber.from(4).shl(128))
|
|
;({ secondsPerLiquidityCumulativeX128 } = await observeSingle(8))
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(BigNumber.from(2 ** 32 - 1).shl(128))
|
|
})
|
|
|
|
it('single observation at current time', async () => {
|
|
await oracle.initialize({ liquidity: 4, tick: 2, time: 5 })
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(tickCumulative).to.eq(0)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(0)
|
|
})
|
|
|
|
it('single observation in past but not earlier than secondsAgo', async () => {
|
|
await oracle.initialize({ liquidity: 4, tick: 2, time: 5 })
|
|
await oracle.advanceTime(3)
|
|
await expect(observeSingle(4)).to.be.revertedWith('OLD')
|
|
})
|
|
|
|
it('single observation in past at exactly seconds ago', async () => {
|
|
await oracle.initialize({ liquidity: 4, tick: 2, time: 5 })
|
|
await oracle.advanceTime(3)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(3)
|
|
expect(tickCumulative).to.eq(0)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(0)
|
|
})
|
|
|
|
it('single observation in past counterfactual in past', async () => {
|
|
await oracle.initialize({ liquidity: 4, tick: 2, time: 5 })
|
|
await oracle.advanceTime(3)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(1)
|
|
expect(tickCumulative).to.eq(4)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('170141183460469231731687303715884105728')
|
|
})
|
|
|
|
it('single observation in past counterfactual now', async () => {
|
|
await oracle.initialize({ liquidity: 4, tick: 2, time: 5 })
|
|
await oracle.advanceTime(3)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(tickCumulative).to.eq(6)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('255211775190703847597530955573826158592')
|
|
})
|
|
|
|
it('two observations in chronological order 0 seconds ago exact', async () => {
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: 5 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 4, tick: 1, liquidity: 2 })
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(tickCumulative).to.eq(-20)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('272225893536750770770699685945414569164')
|
|
})
|
|
|
|
it('two observations in chronological order 0 seconds ago counterfactual', async () => {
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: 5 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 4, tick: 1, liquidity: 2 })
|
|
await oracle.advanceTime(7)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(tickCumulative).to.eq(-13)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('1463214177760035392892510811956603309260')
|
|
})
|
|
|
|
it('two observations in chronological order seconds ago is exactly on first observation', async () => {
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: 5 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 4, tick: 1, liquidity: 2 })
|
|
await oracle.advanceTime(7)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(11)
|
|
expect(tickCumulative).to.eq(0)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq(0)
|
|
})
|
|
|
|
it('two observations in chronological order seconds ago is between first and second', async () => {
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: 5 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 4, tick: 1, liquidity: 2 })
|
|
await oracle.advanceTime(7)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(9)
|
|
expect(tickCumulative).to.eq(-10)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('136112946768375385385349842972707284582')
|
|
})
|
|
|
|
it('two observations in reverse order 0 seconds ago exact', async () => {
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: 5 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 4, tick: 1, liquidity: 2 })
|
|
await oracle.update({ advanceTimeBy: 3, tick: -5, liquidity: 4 })
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(tickCumulative).to.eq(-17)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('782649443918158465965761597093066886348')
|
|
})
|
|
|
|
it('two observations in reverse order 0 seconds ago counterfactual', async () => {
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: 5 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 4, tick: 1, liquidity: 2 })
|
|
await oracle.update({ advanceTimeBy: 3, tick: -5, liquidity: 4 })
|
|
await oracle.advanceTime(7)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(tickCumulative).to.eq(-52)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('1378143586029800777026667160098661256396')
|
|
})
|
|
|
|
it('two observations in reverse order seconds ago is exactly on first observation', async () => {
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: 5 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 4, tick: 1, liquidity: 2 })
|
|
await oracle.update({ advanceTimeBy: 3, tick: -5, liquidity: 4 })
|
|
await oracle.advanceTime(7)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(10)
|
|
expect(tickCumulative).to.eq(-20)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('272225893536750770770699685945414569164')
|
|
})
|
|
|
|
it('two observations in reverse order seconds ago is between first and second', async () => {
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: 5 })
|
|
await oracle.grow(2)
|
|
await oracle.update({ advanceTimeBy: 4, tick: 1, liquidity: 2 })
|
|
await oracle.update({ advanceTimeBy: 3, tick: -5, liquidity: 4 })
|
|
await oracle.advanceTime(7)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(9)
|
|
expect(tickCumulative).to.eq(-19)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('442367076997220002502386989661298674892')
|
|
})
|
|
|
|
it('can fetch multiple observations', async () => {
|
|
await oracle.initialize({ time: 5, tick: 2, liquidity: BigNumber.from(2).pow(15) })
|
|
await oracle.grow(4)
|
|
await oracle.update({ advanceTimeBy: 13, tick: 6, liquidity: BigNumber.from(2).pow(12) })
|
|
await oracle.advanceTime(5)
|
|
|
|
const { tickCumulatives, secondsPerLiquidityCumulativeX128s } = await oracle.observe([0, 3, 8, 13, 15, 18])
|
|
expect(tickCumulatives).to.have.lengthOf(6)
|
|
expect(tickCumulatives[0]).to.eq(56)
|
|
expect(tickCumulatives[1]).to.eq(38)
|
|
expect(tickCumulatives[2]).to.eq(20)
|
|
expect(tickCumulatives[3]).to.eq(10)
|
|
expect(tickCumulatives[4]).to.eq(6)
|
|
expect(tickCumulatives[5]).to.eq(0)
|
|
expect(secondsPerLiquidityCumulativeX128s).to.have.lengthOf(6)
|
|
expect(secondsPerLiquidityCumulativeX128s[0]).to.eq('550383467004691728624232610897330176')
|
|
expect(secondsPerLiquidityCumulativeX128s[1]).to.eq('301153217795020002454768787094765568')
|
|
expect(secondsPerLiquidityCumulativeX128s[2]).to.eq('103845937170696552570609926584401920')
|
|
expect(secondsPerLiquidityCumulativeX128s[3]).to.eq('51922968585348276285304963292200960')
|
|
expect(secondsPerLiquidityCumulativeX128s[4]).to.eq('31153781151208965771182977975320576')
|
|
expect(secondsPerLiquidityCumulativeX128s[5]).to.eq(0)
|
|
})
|
|
|
|
it('gas for observe since most recent', async () => {
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: 5 })
|
|
await oracle.advanceTime(2)
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([1]))
|
|
})
|
|
|
|
it('gas for single observation at current time', async () => {
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: 5 })
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([0]))
|
|
})
|
|
|
|
it('gas for single observation at current time counterfactually computed', async () => {
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: 5 })
|
|
await oracle.advanceTime(5)
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([0]))
|
|
})
|
|
})
|
|
|
|
for (const startingTime of [5, 2 ** 32 - 5]) {
|
|
describe(`initialized with 5 observations with starting time of ${startingTime}`, () => {
|
|
const oracleFixture5Observations = async () => {
|
|
const oracle = await oracleFixture()
|
|
await oracle.initialize({ liquidity: 5, tick: -5, time: startingTime })
|
|
await oracle.grow(5)
|
|
await oracle.update({ advanceTimeBy: 3, tick: 1, liquidity: 2 })
|
|
await oracle.update({ advanceTimeBy: 2, tick: -6, liquidity: 4 })
|
|
await oracle.update({ advanceTimeBy: 4, tick: -2, liquidity: 4 })
|
|
await oracle.update({ advanceTimeBy: 1, tick: -2, liquidity: 9 })
|
|
await oracle.update({ advanceTimeBy: 3, tick: 4, liquidity: 2 })
|
|
await oracle.update({ advanceTimeBy: 6, tick: 6, liquidity: 7 })
|
|
return oracle
|
|
}
|
|
let oracle: OracleTest
|
|
beforeEach('set up observations', async () => {
|
|
oracle = await loadFixture(oracleFixture5Observations)
|
|
})
|
|
|
|
const observeSingle = async (secondsAgo: number) => {
|
|
const {
|
|
tickCumulatives: [tickCumulative],
|
|
secondsPerLiquidityCumulativeX128s: [secondsPerLiquidityCumulativeX128],
|
|
} = await oracle.observe([secondsAgo])
|
|
return { secondsPerLiquidityCumulativeX128, tickCumulative }
|
|
}
|
|
|
|
it('index, cardinality, cardinality next', async () => {
|
|
expect(await oracle.index()).to.eq(1)
|
|
expect(await oracle.cardinality()).to.eq(5)
|
|
expect(await oracle.cardinalityNext()).to.eq(5)
|
|
})
|
|
it('latest observation same time as latest', async () => {
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(tickCumulative).to.eq(-21)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('2104079302127802832415199655953100107502')
|
|
})
|
|
it('latest observation 5 seconds after latest', async () => {
|
|
await oracle.advanceTime(5)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(5)
|
|
expect(tickCumulative).to.eq(-21)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('2104079302127802832415199655953100107502')
|
|
})
|
|
it('current observation 5 seconds after latest', async () => {
|
|
await oracle.advanceTime(5)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(0)
|
|
expect(tickCumulative).to.eq(9)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('2347138135642758877746181518404363115684')
|
|
})
|
|
it('between latest observation and just before latest observation at same time as latest', async () => {
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(3)
|
|
expect(tickCumulative).to.eq(-33)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('1593655751746395137220137744805447790318')
|
|
})
|
|
it('between latest observation and just before latest observation after the latest observation', async () => {
|
|
await oracle.advanceTime(5)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(8)
|
|
expect(tickCumulative).to.eq(-33)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('1593655751746395137220137744805447790318')
|
|
})
|
|
it('older than oldest reverts', async () => {
|
|
await expect(observeSingle(15)).to.be.revertedWith('OLD')
|
|
await oracle.advanceTime(5)
|
|
await expect(observeSingle(20)).to.be.revertedWith('OLD')
|
|
})
|
|
it('oldest observation', async () => {
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(14)
|
|
expect(tickCumulative).to.eq(-13)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('544451787073501541541399371890829138329')
|
|
})
|
|
it('oldest observation after some time', async () => {
|
|
await oracle.advanceTime(6)
|
|
const { tickCumulative, secondsPerLiquidityCumulativeX128 } = await observeSingle(20)
|
|
expect(tickCumulative).to.eq(-13)
|
|
expect(secondsPerLiquidityCumulativeX128).to.eq('544451787073501541541399371890829138329')
|
|
})
|
|
|
|
it('fetch many values', async () => {
|
|
await oracle.advanceTime(6)
|
|
const { tickCumulatives, secondsPerLiquidityCumulativeX128s } = await oracle.observe([
|
|
20,
|
|
17,
|
|
13,
|
|
10,
|
|
5,
|
|
1,
|
|
0,
|
|
])
|
|
expect({
|
|
tickCumulatives: tickCumulatives.map((tc) => tc.toNumber()),
|
|
secondsPerLiquidityCumulativeX128s: secondsPerLiquidityCumulativeX128s.map((lc) => lc.toString()),
|
|
}).to.matchSnapshot()
|
|
})
|
|
|
|
it('gas all of last 20 seconds', async () => {
|
|
await oracle.advanceTime(6)
|
|
await snapshotGasCost(
|
|
oracle.getGasCostOfObserve([20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
|
|
)
|
|
})
|
|
|
|
it('gas latest equal', async () => {
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([0]))
|
|
})
|
|
it('gas latest transform', async () => {
|
|
await oracle.advanceTime(5)
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([0]))
|
|
})
|
|
it('gas oldest', async () => {
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([14]))
|
|
})
|
|
it('gas between oldest and oldest + 1', async () => {
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([13]))
|
|
})
|
|
it('gas middle', async () => {
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([5]))
|
|
})
|
|
})
|
|
}
|
|
})
|
|
|
|
describe.skip('full oracle', function () {
|
|
this.timeout(1_200_000)
|
|
|
|
let oracle: OracleTest
|
|
|
|
const BATCH_SIZE = 300
|
|
|
|
const STARTING_TIME = TEST_POOL_START_TIME
|
|
|
|
const maxedOutOracleFixture = async () => {
|
|
const oracle = await oracleFixture()
|
|
await oracle.initialize({ liquidity: 0, tick: 0, time: STARTING_TIME })
|
|
let cardinalityNext = await oracle.cardinalityNext()
|
|
while (cardinalityNext < 65535) {
|
|
const growTo = Math.min(65535, cardinalityNext + BATCH_SIZE)
|
|
console.log('growing from', cardinalityNext, 'to', growTo)
|
|
await oracle.grow(growTo)
|
|
cardinalityNext = growTo
|
|
}
|
|
|
|
for (let i = 0; i < 65535; i += BATCH_SIZE) {
|
|
console.log('batch update starting at', i)
|
|
const batch = Array(BATCH_SIZE)
|
|
.fill(null)
|
|
.map((_, j) => ({
|
|
advanceTimeBy: 13,
|
|
tick: -i - j,
|
|
liquidity: i + j,
|
|
}))
|
|
await oracle.batchUpdate(batch)
|
|
}
|
|
|
|
return oracle
|
|
}
|
|
|
|
beforeEach('create a full oracle', async () => {
|
|
oracle = await loadFixture(maxedOutOracleFixture)
|
|
})
|
|
|
|
it('has max cardinality next', async () => {
|
|
expect(await oracle.cardinalityNext()).to.eq(65535)
|
|
})
|
|
|
|
it('has max cardinality', async () => {
|
|
expect(await oracle.cardinality()).to.eq(65535)
|
|
})
|
|
|
|
it('index wrapped around', async () => {
|
|
expect(await oracle.index()).to.eq(165)
|
|
})
|
|
|
|
async function checkObserve(
|
|
secondsAgo: number,
|
|
expected?: { tickCumulative: BigNumberish; secondsPerLiquidityCumulativeX128: BigNumberish }
|
|
) {
|
|
const { tickCumulatives, secondsPerLiquidityCumulativeX128s } = await oracle.observe([secondsAgo])
|
|
const check = {
|
|
tickCumulative: tickCumulatives[0].toString(),
|
|
secondsPerLiquidityCumulativeX128: secondsPerLiquidityCumulativeX128s[0].toString(),
|
|
}
|
|
if (typeof expected === 'undefined') {
|
|
expect(check).to.matchSnapshot()
|
|
} else {
|
|
expect(check).to.deep.eq({
|
|
tickCumulative: expected.tickCumulative.toString(),
|
|
secondsPerLiquidityCumulativeX128: expected.secondsPerLiquidityCumulativeX128.toString(),
|
|
})
|
|
}
|
|
}
|
|
|
|
it('can observe into the ordered portion with exact seconds ago', async () => {
|
|
await checkObserve(100 * 13, {
|
|
secondsPerLiquidityCumulativeX128: '60465049086512033878831623038233202591033',
|
|
tickCumulative: '-27970560813',
|
|
})
|
|
})
|
|
|
|
it('can observe into the ordered portion with unexact seconds ago', async () => {
|
|
await checkObserve(100 * 13 + 5, {
|
|
secondsPerLiquidityCumulativeX128: '60465023149565257990964350912969670793706',
|
|
tickCumulative: '-27970232823',
|
|
})
|
|
})
|
|
|
|
it('can observe at exactly the latest observation', async () => {
|
|
await checkObserve(0, {
|
|
secondsPerLiquidityCumulativeX128: '60471787506468701386237800669810720099776',
|
|
tickCumulative: '-28055903863',
|
|
})
|
|
})
|
|
|
|
it('can observe at exactly the latest observation after some time passes', async () => {
|
|
await oracle.advanceTime(5)
|
|
await checkObserve(5, {
|
|
secondsPerLiquidityCumulativeX128: '60471787506468701386237800669810720099776',
|
|
tickCumulative: '-28055903863',
|
|
})
|
|
})
|
|
|
|
it('can observe after the latest observation counterfactual', async () => {
|
|
await oracle.advanceTime(5)
|
|
await checkObserve(3, {
|
|
secondsPerLiquidityCumulativeX128: '60471797865298117996489508104462919730461',
|
|
tickCumulative: '-28056035261',
|
|
})
|
|
})
|
|
|
|
it('can observe into the unordered portion of array at exact seconds ago of observation', async () => {
|
|
await checkObserve(200 * 13, {
|
|
secondsPerLiquidityCumulativeX128: '60458300386499273141628780395875293027404',
|
|
tickCumulative: '-27885347763',
|
|
})
|
|
})
|
|
|
|
it('can observe into the unordered portion of array at seconds ago between observations', async () => {
|
|
await checkObserve(200 * 13 + 5, {
|
|
secondsPerLiquidityCumulativeX128: '60458274409952896081377821330361274907140',
|
|
tickCumulative: '-27885020273',
|
|
})
|
|
})
|
|
|
|
it('can observe the oldest observation 13*65534 seconds ago', async () => {
|
|
await checkObserve(13 * 65534, {
|
|
secondsPerLiquidityCumulativeX128: '33974356747348039873972993881117400879779',
|
|
tickCumulative: '-175890',
|
|
})
|
|
})
|
|
|
|
it('can observe the oldest observation 13*65534 + 5 seconds ago if time has elapsed', async () => {
|
|
await oracle.advanceTime(5)
|
|
await checkObserve(13 * 65534 + 5, {
|
|
secondsPerLiquidityCumulativeX128: '33974356747348039873972993881117400879779',
|
|
tickCumulative: '-175890',
|
|
})
|
|
})
|
|
|
|
it('gas cost of observe(0)', async () => {
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([0]))
|
|
})
|
|
it('gas cost of observe(200 * 13)', async () => {
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([200 + 13]))
|
|
})
|
|
it('gas cost of observe(200 * 13 + 5)', async () => {
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([200 + 13 + 5]))
|
|
})
|
|
it('gas cost of observe(0) after 5 seconds', async () => {
|
|
await oracle.advanceTime(5)
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([0]))
|
|
})
|
|
it('gas cost of observe(5) after 5 seconds', async () => {
|
|
await oracle.advanceTime(5)
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([5]))
|
|
})
|
|
it('gas cost of observe(oldest)', async () => {
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([65534 * 13]))
|
|
})
|
|
it('gas cost of observe(oldest) after 5 seconds', async () => {
|
|
await oracle.advanceTime(5)
|
|
await snapshotGasCost(oracle.getGasCostOfObserve([65534 * 13 + 5]))
|
|
})
|
|
})
|
|
})
|