Skip to content

Commit

Permalink
fix: limited the maximum amount of periods to bridge (#89)
Browse files Browse the repository at this point in the history
* fix: limited the maximum amount of periods to bridge

* test: clarified remainder calculation
  • Loading branch information
0xJabberwock authored Dec 28, 2022
1 parent 01d002f commit 2aa8d97
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 47 deletions.
7 changes: 7 additions & 0 deletions solidity/contracts/DataFeedStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,20 @@ contract DataFeedStrategy is IDataFeedStrategy, Governable {
/// @inheritdoc IDataFeedStrategy
function calculateSecondsAgos(uint32 _fromTimestamp) public view returns (uint32[] memory _secondsAgos) {
if (_fromTimestamp == 0) return _initializeSecondsAgos();

uint32 _secondsNow = uint32(block.timestamp); // truncation is desired
uint32 _timeSinceLastObservation = _secondsNow - _fromTimestamp;
uint32 _periodDuration = periodDuration;
uint32 _maxPeriods = strategyCooldown / _periodDuration;
uint32 _periods = _timeSinceLastObservation / _periodDuration;
uint32 _remainder = _timeSinceLastObservation % _periodDuration;
uint32 _i;

if (_periods > _maxPeriods) {
_remainder += (_periods - _maxPeriods) * _periodDuration;
_periods = _maxPeriods;
}

if (_remainder != 0) {
_secondsAgos = new uint32[](++_periods);
_timeSinceLastObservation -= _remainder;
Expand Down
6 changes: 3 additions & 3 deletions solidity/interfaces/IDataFeedStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ interface IDataFeedStrategy is IGovernable {
/// @return _dataFeed The address of the DataFeed contract
function dataFeed() external view returns (IDataFeed _dataFeed);

/// @return _strategyCooldown Time in seconds since last update required to time-trigger an update
function strategyCooldown() external view returns (uint32 _strategyCooldown);

/// @return _periodDuration The targetted amount of seconds between pool consultations
/// @dev Defines the resolution of the oracle, averaging data between consultations
function periodDuration() external view returns (uint32 _periodDuration);

/// @return _strategyCooldown Time in seconds since last update required to time-trigger an update
function strategyCooldown() external view returns (uint32 _strategyCooldown);

/// @return _twapThreshold Twap difference, in ticks, to twap-trigger an update
function twapThreshold() external view returns (uint24 _twapThreshold);

Expand Down
162 changes: 118 additions & 44 deletions test/unit/DataFeedStrategy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,8 +642,10 @@ describe('DataFeedStrategy.sol', () => {
let fromTimestamp: number;
let now: number;
let timeSinceLastObservation: number;
let totalPeriods: number;
let periods: number;
let remainder: number;
const maxPeriods = Math.trunc(initialStrategyCooldown / initialPeriodDuration);

context('when less than a period has passed since fromTimestamp', () => {
beforeEach(async () => {
Expand All @@ -658,66 +660,138 @@ describe('DataFeedStrategy.sol', () => {
});

context('when more than a period has passed since fromTimestamp', () => {
beforeEach(async () => {
fromTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
await evm.advanceToTimeAndBlock(fromTimestamp + initialPeriodDuration * 3.14);
now = (await ethers.provider.getBlock('latest')).timestamp;
timeSinceLastObservation = now - fromTimestamp;
periods = Math.trunc(timeSinceLastObservation / initialPeriodDuration);
remainder = timeSinceLastObservation % initialPeriodDuration;
periods++; // adds the bridged remainder
});
context('when the amount of periods to bridge is not above the maximum', () => {
beforeEach(async () => {
fromTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
await evm.advanceToTimeAndBlock(fromTimestamp + initialPeriodDuration * (maxPeriods + 0.14));
now = (await ethers.provider.getBlock('latest')).timestamp;
timeSinceLastObservation = now - fromTimestamp;
periods = Math.trunc(timeSinceLastObservation / initialPeriodDuration);
remainder = timeSinceLastObservation % initialPeriodDuration;
periods++; // adds the bridged remainder
});

it('should return an array with proper length', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos.length).to.eq(periods);
});
it('should return an array with proper length', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos.length).to.eq(periods);
});

it('should build the array of secondsAgos', async () => {
let expectedSecondsAgos: number[] = [];
it('should build the array of secondsAgos', async () => {
let expectedSecondsAgos: number[] = [];

for (let i = 0; i < periods; i++) {
expectedSecondsAgos[i] = timeSinceLastObservation - remainder - i * initialPeriodDuration;
}
for (let i = 0; i < periods; i++) {
expectedSecondsAgos[i] = timeSinceLastObservation - remainder - i * initialPeriodDuration;
}

const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos).to.eql(expectedSecondsAgos);
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos).to.eql(expectedSecondsAgos);
});

it('should have 0 as last item', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos[secondsAgos.length - 1]).to.eq(0);
});
});

it('should have 0 as last item', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos[secondsAgos.length - 1]).to.eq(0);
context('when the amount of periods to bridge is above the maximum', () => {
beforeEach(async () => {
fromTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
await evm.advanceToTimeAndBlock(fromTimestamp + initialPeriodDuration * (maxPeriods + 3.14));
now = (await ethers.provider.getBlock('latest')).timestamp;
timeSinceLastObservation = now - fromTimestamp;
totalPeriods = Math.trunc(timeSinceLastObservation / initialPeriodDuration);
periods = maxPeriods;
remainder = (timeSinceLastObservation % initialPeriodDuration) + (totalPeriods - maxPeriods) * initialPeriodDuration;
periods++; // adds the bridged remainder
});

it('should return an array with proper length', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos.length).to.eq(periods);
});

it('should build the array of secondsAgos', async () => {
let expectedSecondsAgos: number[] = [];

for (let i = 0; i < periods; i++) {
expectedSecondsAgos[i] = timeSinceLastObservation - remainder - i * initialPeriodDuration;
}

const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos).to.eql(expectedSecondsAgos);
});

it('should have 0 as last item', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos[secondsAgos.length - 1]).to.eq(0);
});
});
});

context('when exactly n periods have passed since fromTimestamp', () => {
beforeEach(async () => {
fromTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
await evm.advanceToTimeAndBlock(fromTimestamp + initialPeriodDuration * 3);
now = (await ethers.provider.getBlock('latest')).timestamp;
timeSinceLastObservation = now - fromTimestamp;
periods = Math.trunc(timeSinceLastObservation / initialPeriodDuration);
});
context('when the amount of periods to bridge is not above the maximum', () => {
beforeEach(async () => {
fromTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
await evm.advanceToTimeAndBlock(fromTimestamp + initialPeriodDuration * maxPeriods);
now = (await ethers.provider.getBlock('latest')).timestamp;
timeSinceLastObservation = now - fromTimestamp;
periods = Math.trunc(timeSinceLastObservation / initialPeriodDuration);
});

it('should return an array with proper length', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos.length).to.eq(periods);
});
it('should return an array with proper length', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos.length).to.eq(periods);
});

it('should build the array of secondsAgos', async () => {
let expectedSecondsAgos: number[] = [];
it('should build the array of secondsAgos', async () => {
let expectedSecondsAgos: number[] = [];

for (let i = 0; i < periods; i++) {
expectedSecondsAgos[i] = timeSinceLastObservation - (i + 1) * initialPeriodDuration;
}
for (let i = 0; i < periods; i++) {
expectedSecondsAgos[i] = timeSinceLastObservation - (i + 1) * initialPeriodDuration;
}

const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos).to.eql(expectedSecondsAgos);
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos).to.eql(expectedSecondsAgos);
});

it('should have 0 as last item', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos[secondsAgos.length - 1]).to.eq(0);
});
});

it('should have 0 as last item', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos[secondsAgos.length - 1]).to.eq(0);
context('when the amount of periods to bridge is above the maximum', () => {
beforeEach(async () => {
fromTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
await evm.advanceToTimeAndBlock(fromTimestamp + initialPeriodDuration * (maxPeriods + 3));
now = (await ethers.provider.getBlock('latest')).timestamp;
timeSinceLastObservation = now - fromTimestamp;
totalPeriods = Math.trunc(timeSinceLastObservation / initialPeriodDuration);
periods = maxPeriods;
remainder = (totalPeriods - maxPeriods) * initialPeriodDuration;
periods++; // adds the bridged remainder
});

it('should return an array with proper length', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos.length).to.eq(periods);
});

it('should build the array of secondsAgos', async () => {
let expectedSecondsAgos: number[] = [];

for (let i = 0; i < periods; i++) {
expectedSecondsAgos[i] = timeSinceLastObservation - remainder - i * initialPeriodDuration;
}

const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos).to.eql(expectedSecondsAgos);
});

it('should have 0 as last item', async () => {
const secondsAgos = await dataFeedStrategy.calculateSecondsAgos(fromTimestamp);
expect(secondsAgos[secondsAgos.length - 1]).to.eq(0);
});
});
});

Expand Down

0 comments on commit 2aa8d97

Please sign in to comment.