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

Handle stale price #147

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 24 additions & 11 deletions contracts/PriceOracle/PriceOracleProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,15 @@ contract PriceOracleProxy is PriceOracle, Exponential, Denominations {

AggregatorInfo memory aggregatorInfo = aggregators[token];
if (aggregatorInfo.isUsed) {
uint256 price = getPriceFromChainlink(aggregatorInfo.base, aggregatorInfo.quote);
if (aggregatorInfo.quote == Denominations.USD) {
// Convert the price to ETH based if it's USD based.
price = mul_(price, Exp({mantissa: getUsdcEthPrice()}));
(bool success, uint256 price) = getPriceFromChainlink(aggregatorInfo.base, aggregatorInfo.quote);
if (success) {
if (aggregatorInfo.quote == Denominations.USD) {
// Convert the price to ETH based if it's USD based.
price = mul_(price, Exp({mantissa: getUsdcEthPrice()}));
}
uint256 underlyingDecimals = EIP20Interface(token).decimals();
return mul_(price, 10**(18 - underlyingDecimals));
}
uint256 underlyingDecimals = EIP20Interface(token).decimals();
return mul_(price, 10**(18 - underlyingDecimals));
}
return getPriceFromV1(token);
}
Expand All @@ -187,14 +189,17 @@ contract PriceOracleProxy is PriceOracle, Exponential, Denominations {
* @notice Get price from ChainLink
* @param base The base token that ChainLink aggregator gets the price of
* @param quote The quote token, currenlty support ETH and USD
* @return The price, scaled by 1e18
* @return (Success, The price, scaled by 1e18)
*/
function getPriceFromChainlink(address base, address quote) internal view returns (uint256) {
(, int256 price, , , ) = registry.latestRoundData(base, quote);
require(price > 0, "invalid price");
function getPriceFromChainlink(address base, address quote) internal view returns (bool, uint256) {
(, int256 price, , uint256 updatedAt, ) = registry.latestRoundData(base, quote);
// If the price from ChainLink hasn't been updated for 1 day, we consider it stale.
if (price == 0 || add_(updatedAt, 1 days) < getBlockTimestamp()) {
return (false, 0);
}

// Extend the decimals to 1e18.
return mul_(uint256(price), 10**(18 - uint256(registry.decimals(base, quote))));
return (true, mul_(uint256(price), 10**(18 - uint256(registry.decimals(base, quote)))));
}

/**
Expand Down Expand Up @@ -279,6 +284,14 @@ contract PriceOracleProxy is PriceOracle, Exponential, Denominations {
return v1PriceOracle.assetPrices(token);
}

/**
* @notice Get the curent block timestamp
* @return The curent block timestamp
*/
function getBlockTimestamp() internal view returns (uint256) {
return block.timestamp;
}

/*** Admin or guardian functions ***/

event AggregatorUpdated(address tokenAddress, address base, address quote, bool isUsed);
Expand Down
18 changes: 14 additions & 4 deletions tests/Contracts/MockAggregator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ contract MockAggregator {

uint8 public decimals = 18;
int256 public answer;
uint256 public blockTimestamp = 100000;

constructor(int256 _answer) public {
answer = _answer;
Expand All @@ -61,7 +62,7 @@ contract MockAggregator {
// Shh
_roundId;

return (roundId, answer, block.timestamp, block.timestamp, roundId);
return (roundId, answer, blockTimestamp, blockTimestamp, roundId);
}

function latestRoundData()
Expand All @@ -75,7 +76,7 @@ contract MockAggregator {
uint80
)
{
return (roundId, answer, block.timestamp, block.timestamp, roundId);
return (roundId, answer, blockTimestamp, blockTimestamp, roundId);
}

function setAnswer(int256 _answer) external {
Expand All @@ -85,6 +86,10 @@ contract MockAggregator {
function setDecimals(uint8 _decimals) external {
decimals = _decimals;
}

function setBlockTimestamp(uint256 newBlockTimestamp) external {
blockTimestamp = newBlockTimestamp;
}
}

contract MockRegistry is FeedRegistryInterface {
Expand All @@ -93,6 +98,7 @@ contract MockRegistry is FeedRegistryInterface {
mapping(address => mapping(address => int256)) private answer;
bool public getFeedFailed;
bool public feedDisabled;
uint256 public blockTimestamp = 100000;

function getRoundData(
address base,
Expand All @@ -109,7 +115,7 @@ contract MockRegistry is FeedRegistryInterface {
uint80
)
{
return (roundId, answer[base][quote], block.timestamp, block.timestamp, _roundId);
return (roundId, answer[base][quote], blockTimestamp, blockTimestamp, _roundId);
}

function latestRoundData(address base, address quote)
Expand All @@ -123,7 +129,7 @@ contract MockRegistry is FeedRegistryInterface {
uint80
)
{
return (roundId, answer[base][quote], block.timestamp, block.timestamp, roundId);
return (roundId, answer[base][quote], blockTimestamp, blockTimestamp, roundId);
}

function decimals(address base, address quote) external view returns (uint8) {
Expand Down Expand Up @@ -183,4 +189,8 @@ contract MockRegistry is FeedRegistryInterface {
) external {
answer[base][quote] = _answer;
}

function setBlockTimestamp(uint256 newBlockTimestamp) external {
blockTimestamp = newBlockTimestamp;
}
}
22 changes: 22 additions & 0 deletions tests/Contracts/PriceOracleProxyHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
pragma solidity ^0.5.16;

import "../../contracts/PriceOracle/PriceOracleProxy.sol";

contract PriceOracleProxyHarness is PriceOracleProxy {
uint256 public blockTimestamp = 100000;

constructor(
address admin_,
address v1PriceOracle_,
address cEthAddress_,
address registry_
) public PriceOracleProxy(admin_, v1PriceOracle_, cEthAddress_, registry_) {}

function getBlockTimestamp() internal view returns (uint256) {
return blockTimestamp;
}

function setBlockTimestamp(uint256 newBlockTimestamp) public {
blockTimestamp = newBlockTimestamp;
}
}
26 changes: 25 additions & 1 deletion tests/PriceOracleProxyTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('PriceOracleProxy', () => {
cYv2CrvLP = await makeCToken({comptroller: cEth.comptroller, supportMarket: true, underlying: yv2CrvLP});

backingOracle = await makePriceOracle();
oracle = await deploy('PriceOracleProxy',
oracle = await deploy('PriceOracleProxyHarness',
[
root,
backingOracle._address,
Expand Down Expand Up @@ -196,6 +196,30 @@ describe('PriceOracleProxy', () => {
let proxyPrice = await call(oracle, "getUnderlyingPrice", [cOther._address]);
expect(Number(proxyPrice)).toEqual(0.0005 * 1e18);
});

it("fallbacks to v1 if the price from chainlink is invalid", async () => {
const v1Price = 10;
const oraclePrice = '0';

await setAndVerifyBackingPrice(cOther, v1Price);
await setPrice(cOther.underlying._address, cOther.underlying._address, ethAddress, oraclePrice);
await readAndVerifyProxyPrice(cOther, v1Price);
});

it("fallbacks to v1 if the price from chainlink is stale", async () => {
const v1Price = 10;
const oraclePrice = '2';

const timestamp1 = 10000;
const timestamp2 = 96500; // greater than timestamp1 + 86400 (1 day)
await send(mockAggregator, "setBlockTimestamp", [timestamp1]);
await send(oracle, "setBlockTimestamp", [timestamp2]);

await setAndVerifyBackingPrice(cOther, v1Price);
await setPrice(cOther.underlying._address, cOther.underlying._address, ethAddress, oraclePrice);

await readAndVerifyProxyPrice(cOther, v1Price);
});
});

describe("_setAdmin", () => {
Expand Down