Skip to content

Commit f0df3ef

Browse files
authored
Merge pull request #127 from liquity/link_aggregator
Link aggregator
2 parents 60704b8 + 5c84b3a commit f0df3ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+382
-386
lines changed

README.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,7 @@ The three main contracts - `BorrowerOperations.sol`, `TroveManager.sol` and `Sta
285285

286286
`SortedTroves.sol` - a doubly linked list that stores addresses of trove owners, sorted by their individual collateral ratio (ICR). It inserts and re-inserts troves at the correct position, based on their ICR.
287287

288-
**TODO: Description of PriceFeed.sol to be eventually updated.**
289-
290-
`PriceFeed.sol` - Contains functionality for obtaining the current ETH:USD price, which the system uses for calculating collateral ratios. Currently, the price is a state variable that can be manually set by the admin. The PriceFeed contract will eventually store no price data, and when called from within other Liquity contracts, will automatically pull the current and decentralized ETH:USD price data from the Chainlink contract.
288+
`PriceFeed.sol` - Contains functionality for obtaining the current ETH:USD price, which the system uses for calculating collateral ratios.
291289

292290
`HintHelpers.sol` - Helper contract, containing the read-only functionality for calculation of accurate hints to be supplied to borrower operations and redemptions.
293291

@@ -307,12 +305,9 @@ Along with `StabilityPool.sol`, these contracts hold Ether and/or tokens for the
307305

308306
### PriceFeed and Oracle
309307

310-
Liquity functions that require the most current ETH:USD price data fetch the price dynamically, as needed, via the core `PriceFeed.sol` contract.
311-
312-
**TODO: To be updated**
313-
Currently, provisional plans are to use the Chainlink ETH:USD reference contract for the price data source, however, other options are under consideration.
308+
Liquity functions that require the most current ETH:USD price data fetch the price dynamically, as needed, via the core `PriceFeed.sol` contract using the Chainlink ETH:USD reference contract for the price data source, however, other options are under consideration.
314309

315-
The current `PriceFeed.sol` contract is a placeholder and contains a manual price setter, `setPrice()`. Price can be manually set, and `getPrice()` returns the latest stored price. In the final deployed version, no price will be stored or set, and will only have a getter, `getLatestPrice()`, will fetch the latest ETH:USD price from the Chainlink reference contract.
310+
The current `PriceFeed.sol` contract has a `getPrice()` that through a helper method calls and asserts on an AggregatorV3 `getLatestRoundData()` and multiplies by 10^10 to get the required number of digits. The `PriceFeedTestnet.sol` contains additionally, a manual price setter, `setPrice()`. Price can be manually set, and `getPrice()` returns the latest stored price.
316311

317312
### Keeping a sorted list of troves ordered by ICR
318313

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
"check-abi": "yarn workspace @liquity/lib-ethers check-abi",
2525
"build": "run-s build:*",
2626
"build:dev-frontend": "yarn workspace @liquity/dev-frontend build",
27-
"build:subgraph": "yarn workspace @liquity/subgraph build",
2827
"deploy": "yarn workspace @liquity/lib-ethers buidler deploy",
2928
"fuzzer": "yarn workspace @liquity/fuzzer fuzzer",
3029
"prepare": "run-s prepare:*",
@@ -35,7 +34,6 @@
3534
"prepare:lib-react": "yarn workspace @liquity/lib-react prepare",
3635
"prepare:lib-subgraph": "yarn workspace @liquity/lib-subgraph prepare",
3736
"prepare:providers": "yarn workspace @liquity/providers prepare",
38-
"prepare:subgraph": "yarn workspace @liquity/subgraph prepare",
3937
"rebuild": "run-s prepare build",
4038
"save-live-version": "yarn workspace @liquity/lib-ethers save-live-version",
4139
"start-dev-chain": "run-s start-dev-chain:*",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// SPDX-License-Identifier: MIT
2+
// Code from https://github.com/smartcontractkit/chainlink/blob/master/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol
3+
4+
pragma solidity 0.6.11;
5+
6+
interface AggregatorV3Interface {
7+
8+
function decimals() external view returns (uint8);
9+
function description() external view returns (string memory);
10+
function version() external view returns (uint256);
11+
12+
// getRoundData and latestRoundData should both raise "No data present"
13+
// if they do not have data to report, instead of returning unset values
14+
// which could be misinterpreted as actual reported values.
15+
function getRoundData(uint80 _roundId)
16+
external
17+
view
18+
returns (
19+
uint80 roundId,
20+
int256 answer,
21+
uint256 startedAt,
22+
uint256 updatedAt,
23+
uint80 answeredInRound
24+
);
25+
26+
function latestRoundData()
27+
external
28+
view
29+
returns (
30+
uint80 roundId,
31+
int256 answer,
32+
uint256 startedAt,
33+
uint256 updatedAt,
34+
uint80 answeredInRound
35+
);
36+
}

packages/contracts/contracts/Interfaces/AggregatorInterface.sol

Lines changed: 0 additions & 24 deletions
This file was deleted.

packages/contracts/contracts/Interfaces/IDeployedAggregator.sol

Lines changed: 0 additions & 17 deletions
This file was deleted.

packages/contracts/contracts/Interfaces/IPriceFeed.sol

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,6 @@
33
pragma solidity 0.6.11;
44

55
interface IPriceFeed {
6-
7-
// --- Events ---
8-
9-
event PriceUpdated(uint _newPrice);
10-
event TroveManagerAddressChanged(address _troveManagerAddress);
11-
12-
// --- Functions ---
13-
14-
function setAddresses(
15-
address _troveManagerAddress,
16-
address _priceAggregatorAddress,
17-
address _priceAggregatorAddressTestnet
18-
) external;
19-
20-
function setPrice(uint _price) external returns (bool);
216

227
function getPrice() external view returns (uint);
238
}

packages/contracts/contracts/PriceFeed.sol

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,56 @@
22

33
pragma solidity 0.6.11;
44

5-
import "./Interfaces/ITroveManager.sol";
65
import "./Interfaces/IPriceFeed.sol";
7-
import "./Interfaces/IDeployedAggregator.sol";
8-
import "./Interfaces/AggregatorInterface.sol";
6+
import "./Dependencies/AggregatorV3Interface.sol";
97
import "./Dependencies/SafeMath.sol";
108
import "./Dependencies/Ownable.sol";
119
import "./Dependencies/console.sol";
1210

1311
/*
14-
*
15-
* Placeholder PriceFeed for development and testing.
16-
*
17-
* Will eventually be replaced by a contract that fetches the current price from the Chainlink ETH:USD aggregator
18-
* reference contract, and does not save price in a state variable.
19-
*
12+
* PriceFeed for mainnet deployment, to be connected to Chainlink's live ETH:USD aggregator reference contract.
2013
*/
2114
contract PriceFeed is Ownable, IPriceFeed {
2215
using SafeMath for uint256;
2316

24-
uint256 public price = 200 * 1e18; // initial ETH:USD price of 200
17+
// Mainnet Chainlink aggregator
18+
AggregatorV3Interface public priceAggregator;
2519

26-
address public troveManagerAddress;
27-
address public priceAggregatorAddress; // unused
28-
address public priceAggregatorAddressTestnet; // unused
29-
30-
event PriceUpdated(uint256 _newPrice);
31-
event TroveManagerAddressChanged(address _troveManagerAddress);
20+
// Use to convert to 18-digit precision uints
21+
uint constant public TARGET_DIGITS = 18;
3222

3323
// --- Dependency setters ---
3424

3525
function setAddresses(
36-
address _troveManagerAddress,
37-
address _priceAggregatorAddress, // passed 0x0 in tests
38-
address _priceAggregatorAddressTestnet // passed 0x0 in tests
26+
address _priceAggregatorAddress
3927
)
4028
external
41-
override
4229
onlyOwner
4330
{
44-
troveManagerAddress = _troveManagerAddress;
45-
priceAggregatorAddress = _priceAggregatorAddress;
46-
priceAggregatorAddressTestnet = _priceAggregatorAddressTestnet;
47-
48-
emit TroveManagerAddressChanged(_troveManagerAddress);
49-
31+
priceAggregator = AggregatorV3Interface(_priceAggregatorAddress);
5032
_renounceOwnership();
5133
}
5234

53-
// --- Functions ---
54-
55-
function getPrice() external view override returns (uint256) {
35+
/**
36+
* Returns the latest price obtained from the Chainlink ETH:USD aggregator reference contract.
37+
* https://docs.chain.link/docs/get-the-latest-price
38+
*/
39+
function getPrice() public view override returns (uint) {
40+
(, int priceAnswer,, uint timeStamp,) = priceAggregator.latestRoundData();
41+
42+
require(timeStamp > 0 && timeStamp <= block.timestamp, "PriceFeed: price timestamp from aggregator is 0, or in future");
43+
require(priceAnswer >= 0, "PriceFeed: price answer from aggregator is negative");
44+
45+
uint8 answerDigits = priceAggregator.decimals();
46+
uint price = uint256(priceAnswer);
47+
48+
// currently the Aggregator returns an 8-digit precision, but we handle the case of future changes
49+
if (answerDigits > TARGET_DIGITS) {
50+
price = price.div(10 ** (answerDigits - TARGET_DIGITS));
51+
}
52+
else if (answerDigits < TARGET_DIGITS) {
53+
price = price.mul(10 ** (TARGET_DIGITS - answerDigits));
54+
}
5655
return price;
5756
}
58-
59-
// Manual external price setter.
60-
function setPrice(uint256 _price) external override returns (bool) {
61-
price = _price;
62-
emit PriceUpdated(price);
63-
return true;
64-
}
6557
}

packages/contracts/contracts/TestContracts/EchidnaTester.sol

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import "../DefaultPool.sol";
99
import "../StabilityPool.sol";
1010
import "../CollSurplusPool.sol";
1111
import "../LUSDToken.sol";
12-
import "../PriceFeed.sol";
12+
import "./PriceFeedTestnet.sol";
1313
import "../SortedTroves.sol";
1414
import "./EchidnaProxy.sol";
1515
//import "../Dependencies/console.sol";
@@ -35,7 +35,7 @@ contract EchidnaTester {
3535
StabilityPool public stabilityPool;
3636
CollSurplusPool public collSurplusPool;
3737
LUSDToken public lusdToken;
38-
PriceFeed priceFeed;
38+
PriceFeedTestnet priceFeedTestnet;
3939
SortedTroves sortedTroves;
4040

4141
EchidnaProxy[NUMBER_OF_ACTORS] public echidnaProxies;
@@ -53,19 +53,36 @@ contract EchidnaTester {
5353
address(stabilityPool),
5454
address(borrowerOperations)
5555
);
56+
5657
collSurplusPool = new CollSurplusPool();
57-
priceFeed = new PriceFeed();
58+
priceFeedTestnet = new PriceFeedTestnet();
59+
5860
sortedTroves = new SortedTroves();
5961

60-
troveManager.setAddresses(address(borrowerOperations), address(activePool), address(defaultPool), address(stabilityPool), address(collSurplusPool), address(priceFeed), address(lusdToken), address(sortedTroves), address(0));
62+
troveManager.setAddresses(address(borrowerOperations),
63+
address(activePool), address(defaultPool),
64+
address(stabilityPool), address(collSurplusPool),
65+
address(priceFeedTestnet), address(lusdToken),
66+
address(sortedTroves), address(0));
6167

62-
borrowerOperations.setAddresses(address(troveManager), address(activePool), address(defaultPool), address(stabilityPool), address(collSurplusPool), address(priceFeed), address(sortedTroves), address(lusdToken), address(0));
63-
activePool.setAddresses(address(borrowerOperations), address(troveManager), address(stabilityPool), address(defaultPool));
68+
borrowerOperations.setAddresses(address(troveManager),
69+
address(activePool), address(defaultPool),
70+
address(stabilityPool), address(collSurplusPool),
71+
address(priceFeedTestnet), address(sortedTroves),
72+
address(lusdToken), address(0));
73+
74+
activePool.setAddresses(address(borrowerOperations),
75+
address(troveManager), address(stabilityPool), address(defaultPool));
76+
6477
defaultPool.setAddresses(address(troveManager), address(activePool));
6578

66-
stabilityPool.setAddresses(address(borrowerOperations), address(troveManager), address(activePool), address(lusdToken), address(sortedTroves), address(priceFeed), address(0));
67-
collSurplusPool.setAddresses(address(borrowerOperations), address(troveManager), address(activePool));
68-
priceFeed.setAddresses(address(troveManager), address(0), address(0));
79+
stabilityPool.setAddresses(address(borrowerOperations),
80+
address(troveManager), address(activePool), address(lusdToken),
81+
address(sortedTroves), address(priceFeedTestnet), address(0));
82+
83+
collSurplusPool.setAddresses(address(borrowerOperations),
84+
address(troveManager), address(activePool));
85+
6986
sortedTroves.setParams(1e18, address(troveManager), address(borrowerOperations));
7087

7188
for (uint i = 0; i < NUMBER_OF_ACTORS; i++) {
@@ -83,7 +100,7 @@ contract EchidnaTester {
83100
GAS_POOL_ADDRESS = troveManager.GAS_POOL_ADDRESS();
84101

85102
// TODO:
86-
priceFeed.setPrice(1e22);
103+
priceFeedTestnet.setPrice(1e22);
87104
}
88105

89106
// TroveManager
@@ -117,7 +134,7 @@ contract EchidnaTester {
117134
// Borrower Operations
118135

119136
function getAdjustedETH(uint actorBalance, uint _ETH, uint ratio) internal view returns (uint) {
120-
uint price = priceFeed.getPrice();
137+
uint price = priceFeedTestnet.getPrice();
121138
require(price > 0);
122139
uint minETH = ratio.mul(LUSD_GAS_COMPENSATION).div(price);
123140
require(actorBalance > minETH);
@@ -126,7 +143,7 @@ contract EchidnaTester {
126143
}
127144

128145
function getAdjustedLUSD(uint ETH, uint _LUSDAmount, uint ratio) internal view returns (uint) {
129-
uint price = priceFeed.getPrice();
146+
uint price = priceFeedTestnet.getPrice();
130147
uint LUSDAmount = _LUSDAmount;
131148
uint compositeDebt = LUSDAmount.add(LUSD_GAS_COMPENSATION);
132149
uint ICR = LiquityMath._computeCR(ETH, compositeDebt, price);
@@ -259,7 +276,7 @@ contract EchidnaTester {
259276
// PriceFeed
260277

261278
function setPriceExt(uint256 _price) external {
262-
bool result = priceFeed.setPrice(_price);
279+
bool result = priceFeedTestnet.setPrice(_price);
263280
assert(result);
264281
}
265282

@@ -283,7 +300,7 @@ contract EchidnaTester {
283300
}
284301

285302
function echidna_troves_order() external view returns(bool) {
286-
uint price = priceFeed.getPrice();
303+
uint price = priceFeedTestnet.getPrice();
287304

288305
address currentTrove = sortedTroves.getFirst();
289306
address nextTrove = sortedTroves.getNext(currentTrove);
@@ -360,11 +377,11 @@ contract EchidnaTester {
360377
if (address(lusdToken).balance > 0) {
361378
return false;
362379
}
363-
364-
if (address(priceFeed).balance > 0) {
380+
381+
if (address(priceFeedTestnet).balance > 0) {
365382
return false;
366383
}
367-
384+
368385
if (address(sortedTroves).balance > 0) {
369386
return false;
370387
}
@@ -374,7 +391,8 @@ contract EchidnaTester {
374391

375392
// TODO: What should we do with this? Should it be allowed? Should it be a canary?
376393
function echidna_price() public view returns(bool) {
377-
uint price = priceFeed.getPrice();
394+
uint price = priceFeedTestnet.getPrice();
395+
378396
if (price == 0) {
379397
return false;
380398
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity 0.6.11;
4+
5+
import "../Interfaces/IPriceFeed.sol";
6+
7+
/*
8+
* PriceFeed placeholder for testnet and development. The price is simply set manually and saved in a state
9+
* variable. The contract does not connect to a live Chainlink price feed.
10+
*/
11+
contract PriceFeedTestnet is IPriceFeed {
12+
13+
uint256 private _price = 200 * 1e18;
14+
15+
// --- Functions ---
16+
17+
function getPrice() external view override returns (uint256) {
18+
return _price;
19+
}
20+
21+
// Manual external price setter.
22+
function setPrice(uint256 price) external returns (bool) {
23+
_price = price;
24+
return true;
25+
}
26+
}

0 commit comments

Comments
 (0)