Skip to content

Commit 4dfb247

Browse files
authored
Merge pull request #29 from perpetual-protocol/feat/add-back-original-chainlink-priceFeed
add back original ChainlinkPriceFeed
2 parents 12e6dbe + 9a6f238 commit 4dfb247

12 files changed

+377
-70
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [unreleased]
9+
- Change contract name `ChainlinkPriceFeed` to `ChainlinkPriceFeedV2`.
10+
- Add origin `ChainlinkPriceFeed`, which calculates the twap by round data instead of cached twap.
911

1012
## [0.3.4] - 2022-04-01
1113
- Add `cacheTwap(uint256)` to `IPriceFeed.sol` and `EmergencyPriceFeed.sol`

contracts/BandPriceFeed.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ pragma experimental ABIEncoderV2;
44

55
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
66
import { BlockContext } from "./base/BlockContext.sol";
7-
import { IPriceFeed } from "./interface/IPriceFeed.sol";
7+
import { IPriceFeedV2 } from "./interface/IPriceFeedV2.sol";
88
import { IStdReference } from "./interface/bandProtocol/IStdReference.sol";
99
import { CachedTwap } from "./twap/CachedTwap.sol";
1010

11-
contract BandPriceFeed is IPriceFeed, BlockContext, CachedTwap {
11+
contract BandPriceFeed is IPriceFeedV2, BlockContext, CachedTwap {
1212
using Address for address;
1313

1414
//

contracts/ChainlinkPriceFeed.sol

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,83 @@
22
pragma solidity 0.7.6;
33

44
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
5+
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
56
import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
67
import { IPriceFeed } from "./interface/IPriceFeed.sol";
78
import { BlockContext } from "./base/BlockContext.sol";
8-
import { CachedTwap } from "./twap/CachedTwap.sol";
99

10-
contract ChainlinkPriceFeed is IPriceFeed, BlockContext, CachedTwap {
10+
contract ChainlinkPriceFeed is IPriceFeed, BlockContext {
11+
using SafeMath for uint256;
1112
using Address for address;
1213

1314
AggregatorV3Interface private immutable _aggregator;
1415

15-
constructor(AggregatorV3Interface aggregator, uint80 cacheTwapInterval) CachedTwap(cacheTwapInterval) {
16+
constructor(AggregatorV3Interface aggregator) {
1617
// CPF_ANC: Aggregator address is not contract
1718
require(address(aggregator).isContract(), "CPF_ANC");
1819

1920
_aggregator = aggregator;
2021
}
2122

22-
/// @dev anyone can help update it.
23-
function update() external {
24-
(, uint256 latestPrice, uint256 latestTimestamp) = _getLatestRoundData();
25-
_update(latestPrice, latestTimestamp);
23+
function decimals() external view override returns (uint8) {
24+
return _aggregator.decimals();
2625
}
2726

28-
function cacheTwap(uint256 interval) external override returns (uint256) {
27+
function getPrice(uint256 interval) external view override returns (uint256) {
28+
// there are 3 timestamps: base(our target), previous & current
29+
// base: now - _interval
30+
// current: the current round timestamp from aggregator
31+
// previous: the previous round timestamp from aggregator
32+
// now >= previous > current > = < base
33+
//
34+
// while loop i = 0
35+
// --+------+-----+-----+-----+-----+-----+
36+
// base current now(previous)
37+
//
38+
// while loop i = 1
39+
// --+------+-----+-----+-----+-----+-----+
40+
// base current previous now
41+
2942
(uint80 round, uint256 latestPrice, uint256 latestTimestamp) = _getLatestRoundData();
43+
uint256 timestamp = _blockTimestamp();
44+
uint256 baseTimestamp = timestamp.sub(interval);
3045

31-
if (interval == 0 || round == 0) {
46+
// if the latest timestamp <= base timestamp, which means there's no new price, return the latest price
47+
if (interval == 0 || round == 0 || latestTimestamp <= baseTimestamp) {
3248
return latestPrice;
3349
}
34-
return _cacheTwap(interval, latestPrice, latestTimestamp);
35-
}
3650

37-
function decimals() external view override returns (uint8) {
38-
return _aggregator.decimals();
39-
}
51+
// rounds are like snapshots, latestRound means the latest price snapshot; follow Chainlink's namings here
52+
uint256 previousTimestamp = latestTimestamp;
53+
uint256 cumulativeTime = timestamp.sub(previousTimestamp);
54+
uint256 weightedPrice = latestPrice.mul(cumulativeTime);
55+
uint256 timeFraction;
56+
while (true) {
57+
if (round == 0) {
58+
// to prevent from div 0 error, return the latest price if `cumulativeTime == 0`
59+
return cumulativeTime == 0 ? latestPrice : weightedPrice.div(cumulativeTime);
60+
}
4061

41-
function getPrice(uint256 interval) external view override returns (uint256) {
42-
(uint80 round, uint256 latestPrice, uint256 latestTimestamp) = _getLatestRoundData();
62+
round = round - 1;
63+
(, uint256 currentPrice, uint256 currentTimestamp) = _getRoundData(round);
4364

44-
if (interval == 0 || round == 0) {
45-
return latestPrice;
65+
// check if the current round timestamp is earlier than the base timestamp
66+
if (currentTimestamp <= baseTimestamp) {
67+
// the weighted time period is (base timestamp - previous timestamp)
68+
// ex: now is 1000, interval is 100, then base timestamp is 900
69+
// if timestamp of the current round is 970, and timestamp of NEXT round is 880,
70+
// then the weighted time period will be (970 - 900) = 70 instead of (970 - 880)
71+
weightedPrice = weightedPrice.add(currentPrice.mul(previousTimestamp.sub(baseTimestamp)));
72+
break;
73+
}
74+
75+
timeFraction = previousTimestamp.sub(currentTimestamp);
76+
weightedPrice = weightedPrice.add(currentPrice.mul(timeFraction));
77+
cumulativeTime = cumulativeTime.add(timeFraction);
78+
previousTimestamp = currentTimestamp;
4679
}
4780

48-
return _getCachedTwap(interval, latestPrice, latestTimestamp);
81+
return weightedPrice == 0 ? latestPrice : weightedPrice.div(interval);
4982
}
5083

5184
function _getLatestRoundData()

contracts/ChainlinkPriceFeedV2.sol

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
pragma solidity 0.7.6;
3+
4+
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
5+
import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
6+
import { IPriceFeedV2 } from "./interface/IPriceFeedV2.sol";
7+
import { BlockContext } from "./base/BlockContext.sol";
8+
import { CachedTwap } from "./twap/CachedTwap.sol";
9+
10+
contract ChainlinkPriceFeedV2 is IPriceFeedV2, BlockContext, CachedTwap {
11+
using Address for address;
12+
13+
AggregatorV3Interface private immutable _aggregator;
14+
15+
constructor(AggregatorV3Interface aggregator, uint80 cacheTwapInterval) CachedTwap(cacheTwapInterval) {
16+
// CPF_ANC: Aggregator address is not contract
17+
require(address(aggregator).isContract(), "CPF_ANC");
18+
19+
_aggregator = aggregator;
20+
}
21+
22+
/// @dev anyone can help update it.
23+
function update() external {
24+
(, uint256 latestPrice, uint256 latestTimestamp) = _getLatestRoundData();
25+
_update(latestPrice, latestTimestamp);
26+
}
27+
28+
function cacheTwap(uint256 interval) external override returns (uint256) {
29+
(uint80 round, uint256 latestPrice, uint256 latestTimestamp) = _getLatestRoundData();
30+
31+
if (interval == 0 || round == 0) {
32+
return latestPrice;
33+
}
34+
return _cacheTwap(interval, latestPrice, latestTimestamp);
35+
}
36+
37+
function decimals() external view override returns (uint8) {
38+
return _aggregator.decimals();
39+
}
40+
41+
function getPrice(uint256 interval) external view override returns (uint256) {
42+
(uint80 round, uint256 latestPrice, uint256 latestTimestamp) = _getLatestRoundData();
43+
44+
if (interval == 0 || round == 0) {
45+
return latestPrice;
46+
}
47+
48+
return _getCachedTwap(interval, latestPrice, latestTimestamp);
49+
}
50+
51+
function _getLatestRoundData()
52+
private
53+
view
54+
returns (
55+
uint80,
56+
uint256 finalPrice,
57+
uint256
58+
)
59+
{
60+
(uint80 round, int256 latestPrice, , uint256 latestTimestamp, ) = _aggregator.latestRoundData();
61+
finalPrice = uint256(latestPrice);
62+
if (latestPrice < 0) {
63+
_requireEnoughHistory(round);
64+
(round, finalPrice, latestTimestamp) = _getRoundData(round - 1);
65+
}
66+
return (round, finalPrice, latestTimestamp);
67+
}
68+
69+
function _getRoundData(uint80 _round)
70+
private
71+
view
72+
returns (
73+
uint80,
74+
uint256,
75+
uint256
76+
)
77+
{
78+
(uint80 round, int256 latestPrice, , uint256 latestTimestamp, ) = _aggregator.getRoundData(_round);
79+
while (latestPrice < 0) {
80+
_requireEnoughHistory(round);
81+
round = round - 1;
82+
(, latestPrice, , latestTimestamp, ) = _aggregator.getRoundData(round);
83+
}
84+
return (round, uint256(latestPrice), latestTimestamp);
85+
}
86+
87+
function _requireEnoughHistory(uint80 _round) private pure {
88+
// CPF_NEH: no enough history
89+
require(_round > 0, "CPF_NEH");
90+
}
91+
}

contracts/EmergencyPriceFeed.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3
77
import { FixedPoint96 } from "@uniswap/v3-core/contracts/libraries/FixedPoint96.sol";
88
import { FullMath } from "@uniswap/v3-core/contracts/libraries/FullMath.sol";
99
import { TickMath } from "@uniswap/v3-core/contracts/libraries/TickMath.sol";
10-
import { IPriceFeed } from "./interface/IPriceFeed.sol";
10+
import { IPriceFeedV2 } from "./interface/IPriceFeedV2.sol";
1111
import { BlockContext } from "./base/BlockContext.sol";
1212

13-
contract EmergencyPriceFeed is IPriceFeed, BlockContext {
13+
contract EmergencyPriceFeed is IPriceFeedV2, BlockContext {
1414
using Address for address;
1515

1616
//

contracts/interface/IPriceFeed.sol

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22
pragma solidity 0.7.6;
33

44
interface IPriceFeed {
5-
/// @dev Returns the cached index price of the token.
6-
/// @param interval The interval represents twap interval.
7-
function cacheTwap(uint256 interval) external returns (uint256);
8-
95
function decimals() external view returns (uint8);
106

117
/// @dev Returns the index price of the token.

contracts/interface/IPriceFeedV2.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
pragma solidity 0.7.6;
3+
4+
import "./IPriceFeed.sol";
5+
6+
interface IPriceFeedV2 is IPriceFeed {
7+
/// @dev Returns the cached index price of the token.
8+
/// @param interval The interval represents twap interval.
9+
function cacheTwap(uint256 interval) external returns (uint256);
10+
}

contracts/test/TestPriceFeed.sol

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: GPL-3.0-or-later
22
pragma solidity 0.7.6;
33

4-
import { IPriceFeed } from "../interface/IPriceFeed.sol";
4+
import { IPriceFeedV2 } from "../interface/IPriceFeedV2.sol";
55

66
contract TestPriceFeed {
77
address public chainlink;
@@ -20,30 +20,30 @@ contract TestPriceFeed {
2020
//
2121
function fetchChainlinkPrice(uint256 interval) external {
2222
for (uint256 i = 0; i < 17; i++) {
23-
IPriceFeed(chainlink).getPrice(interval);
23+
IPriceFeedV2(chainlink).getPrice(interval);
2424
}
25-
currentPrice = IPriceFeed(chainlink).getPrice(interval);
25+
currentPrice = IPriceFeedV2(chainlink).getPrice(interval);
2626
}
2727

2828
function fetchBandProtocolPrice(uint256 interval) external {
2929
for (uint256 i = 0; i < 17; i++) {
30-
IPriceFeed(bandProtocol).getPrice(interval);
30+
IPriceFeedV2(bandProtocol).getPrice(interval);
3131
}
32-
currentPrice = IPriceFeed(bandProtocol).getPrice(interval);
32+
currentPrice = IPriceFeedV2(bandProtocol).getPrice(interval);
3333
}
3434

3535
function cachedChainlinkPrice(uint256 interval) external {
3636
for (uint256 i = 0; i < 17; i++) {
37-
IPriceFeed(chainlink).cacheTwap(interval);
37+
IPriceFeedV2(chainlink).cacheTwap(interval);
3838
}
39-
currentPrice = IPriceFeed(chainlink).cacheTwap(interval);
39+
currentPrice = IPriceFeedV2(chainlink).cacheTwap(interval);
4040
}
4141

4242
function cachedBandProtocolPrice(uint256 interval) external {
4343
for (uint256 i = 0; i < 17; i++) {
44-
IPriceFeed(bandProtocol).cacheTwap(interval);
44+
IPriceFeedV2(bandProtocol).cacheTwap(interval);
4545
}
46-
currentPrice = IPriceFeed(bandProtocol).cacheTwap(interval);
46+
currentPrice = IPriceFeedV2(bandProtocol).cacheTwap(interval);
4747
}
4848

4949
//
@@ -53,7 +53,7 @@ contract TestPriceFeed {
5353
// having this function for testing getPrice() and cacheTwap()
5454
// timestamp moves if any txs happen in hardhat env and which causes cacheTwap() will recalculate all the time
5555
function getPrice(uint256 interval) external returns (uint256 twap, uint256 cachedTwap) {
56-
twap = IPriceFeed(bandProtocol).getPrice(interval);
57-
cachedTwap = IPriceFeed(bandProtocol).cacheTwap(interval);
56+
twap = IPriceFeedV2(bandProtocol).getPrice(interval);
57+
cachedTwap = IPriceFeedV2(bandProtocol).cacheTwap(interval);
5858
}
5959
}

test/CachedTwap.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { expect } from "chai"
22
import { parseEther } from "ethers/lib/utils"
33
import { ethers, waffle } from "hardhat"
4-
import { BandPriceFeed, ChainlinkPriceFeed, TestAggregatorV3, TestPriceFeed, TestStdReference } from "../typechain"
4+
import { BandPriceFeed, ChainlinkPriceFeedV2, TestAggregatorV3, TestPriceFeed, TestStdReference } from "../typechain"
55

66
interface PriceFeedFixture {
77
bandPriceFeed: BandPriceFeed
88
bandReference: TestStdReference
99
baseAsset: string
1010

1111
// chainlinik
12-
chainlinkPriceFeed: ChainlinkPriceFeed
12+
chainlinkPriceFeed: ChainlinkPriceFeedV2
1313
aggregator: TestAggregatorV3
1414
}
1515
async function priceFeedFixture(): Promise<PriceFeedFixture> {
@@ -30,11 +30,11 @@ async function priceFeedFixture(): Promise<PriceFeedFixture> {
3030
const testAggregatorFactory = await ethers.getContractFactory("TestAggregatorV3")
3131
const testAggregator = await testAggregatorFactory.deploy()
3232

33-
const chainlinkPriceFeedFactory = await ethers.getContractFactory("ChainlinkPriceFeed")
33+
const chainlinkPriceFeedFactory = await ethers.getContractFactory("ChainlinkPriceFeedV2")
3434
const chainlinkPriceFeed = (await chainlinkPriceFeedFactory.deploy(
3535
testAggregator.address,
3636
twapInterval,
37-
)) as ChainlinkPriceFeed
37+
)) as ChainlinkPriceFeedV2
3838

3939
return { bandPriceFeed, bandReference: testStdReference, baseAsset, chainlinkPriceFeed, aggregator: testAggregator }
4040
}
@@ -44,7 +44,7 @@ describe("Cached Twap Spec", () => {
4444
const loadFixture: ReturnType<typeof waffle.createFixtureLoader> = waffle.createFixtureLoader([admin])
4545
let bandPriceFeed: BandPriceFeed
4646
let bandReference: TestStdReference
47-
let chainlinkPriceFeed: ChainlinkPriceFeed
47+
let chainlinkPriceFeed: ChainlinkPriceFeedV2
4848
let aggregator: TestAggregatorV3
4949
let currentTime: number
5050
let testPriceFeed: TestPriceFeed

0 commit comments

Comments
 (0)