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

feat: pricer v2.0 #444

Closed
wants to merge 8 commits into from
Closed
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
13 changes: 8 additions & 5 deletions contracts/core/Oracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ contract Oracle is Ownable {
struct Price {
uint256 price;
uint256 timestamp; // timestamp at which the price is pushed to this oracle
bool isDisputed;
}

//// @dev disputer is a role defined by the owner that has the ability to dispute a price during the dispute period
Expand Down Expand Up @@ -82,7 +83,8 @@ contract Oracle is Ownable {
require(_expiries.length == _prices.length, "Oracle: invalid migration data");

for (uint256 i; i < _expiries.length; i++) {
storedPrice[_asset][_expiries[i]] = Price(_prices[i], now);
// set migrated prices as disputed to no allow disputing old pirces
storedPrice[_asset][_expiries[i]] = Price(_prices[i], now, true);
}
}

Expand Down Expand Up @@ -179,10 +181,11 @@ contract Oracle is Ownable {

Price storage priceToUpdate = storedPrice[_asset][_expiryTimestamp];

require(priceToUpdate.timestamp != 0, "Oracle: price to dispute does not exist");
require(!priceToUpdate.isDisputed, "Oracle: price already disputed");

uint256 oldPrice = priceToUpdate.price;
priceToUpdate.price = _price;
priceToUpdate.isDisputed = true;

emit ExpiryPriceDisputed(_asset, _expiryTimestamp, oldPrice, _price, now);
}
Expand All @@ -203,7 +206,7 @@ contract Oracle is Ownable {
require(isLockingPeriodOver(_asset, _expiryTimestamp), "Oracle: locking period is not over yet");
require(storedPrice[_asset][_expiryTimestamp].timestamp == 0, "Oracle: dispute period started");

storedPrice[_asset][_expiryTimestamp] = Price(_price, now);
storedPrice[_asset][_expiryTimestamp] = Price(_price, now, false);
emit ExpiryPriceUpdated(_asset, _expiryTimestamp, _price, now);
}

Expand Down Expand Up @@ -329,9 +332,9 @@ contract Oracle is Ownable {
* @return True if dispute period is over, False if not
*/
function isDisputePeriodOver(address _asset, uint256 _expiryTimestamp) public view returns (bool) {
uint256 price = stablePrice[_asset];
uint256 stableAssetPrice = stablePrice[_asset];

if (price == 0) {
if (stableAssetPrice == 0) {
// check if the pricer has a price for this expiry timestamp
Price memory price = storedPrice[_asset][_expiryTimestamp];
if (price.timestamp == 0) {
Expand Down
33 changes: 16 additions & 17 deletions contracts/pricers/ChainlinkPricer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,53 +25,52 @@ contract ChainLinkPricer is OpynPricerInterface {

/// @notice asset that this pricer will a get price for
address public asset;
/// @notice bot address that is allowed to call setExpiryPriceInOracle
address public bot;

/**
* @param _bot priveleged address that can call setExpiryPriceInOracle
* @param _asset asset that this pricer will get a price for
* @param _aggregator Chainlink aggregator contract for the asset
* @param _oracle Opyn Oracle address
*/
constructor(
address _bot,
address _asset,
address _aggregator,
address _oracle
) public {
require(_bot != address(0), "ChainLinkPricer: Cannot set 0 address as bot");
require(_oracle != address(0), "ChainLinkPricer: Cannot set 0 address as oracle");
require(_aggregator != address(0), "ChainLinkPricer: Cannot set 0 address as aggregator");

bot = _bot;
oracle = OracleInterface(_oracle);
aggregator = AggregatorInterface(_aggregator);
asset = _asset;

aggregatorDecimals = uint256(aggregator.decimals());
}

/**
* @notice modifier to check if sender address is equal to bot address
*/
modifier onlyBot() {
require(msg.sender == bot, "ChainLinkPricer: unauthorized sender");

_;
}

/**
* @notice set the expiry price in the oracle, can only be called by Bot address
* @dev a roundId must be provided to confirm price validity, which is the first Chainlink price provided after the expiryTimestamp
* @param _expiryTimestamp expiry to set a price for
* @param _roundId the first roundId after expiryTimestamp
*/
function setExpiryPriceInOracle(uint256 _expiryTimestamp, uint80 _roundId) external onlyBot {
function setExpiryPriceInOracle(uint256 _expiryTimestamp, uint80 _roundId) external {
(, int256 price, , uint256 roundTimestamp, ) = aggregator.getRoundData(_roundId);

require(_expiryTimestamp <= roundTimestamp, "ChainLinkPricer: invalid roundId");

bool isCorrectRoundId;
uint80 previousRoundId = uint80(uint256(_roundId).sub(1));

while (!isCorrectRoundId) {
(, , , uint256 previousRoundTimestamp, ) = aggregator.getRoundData(previousRoundId);

if (previousRoundTimestamp == 0) {
previousRoundId = uint80(uint256(previousRoundId).sub(1));
} else if (previousRoundTimestamp > _expiryTimestamp) {
revert("ChainLinkPricer: invalid roundId");
} else {
isCorrectRoundId = true;
}
}

oracle.setExpiryPrice(asset, _expiryTimestamp, uint256(price));
}

Expand Down
14 changes: 1 addition & 13 deletions test/integration-tests/pricer-oracle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ contract('Pricer + Oracle', ([owner, bot, disputer, random]) => {
ceth = await MockCToken.new('cETH', 'cETH')

oracle = await Oracle.new()
wethPricer = await ChainlinkPricer.new(bot, weth.address, wethAggregator.address, oracle.address)
wethPricer = await ChainlinkPricer.new(weth.address, wethAggregator.address, oracle.address)
cethPricer = await CompoundPricer.new(ceth.address, weth.address, oracle.address)
})

Expand Down Expand Up @@ -162,18 +162,6 @@ contract('Pricer + Oracle', ([owner, bot, disputer, random]) => {
)
})

it('should revert set weth price if sender is not bot address', async () => {
const expiryTimestamp = (t0 + t1) / 2 // between t0 and t1
if ((await time.latest()) < expiryTimestamp + lockingPeriod) {
await time.increaseTo(expiryTimestamp + lockingPeriod + 10)
}
const roundId = 1
await expectRevert(
wethPricer.setExpiryPriceInOracle(expiryTimestamp, roundId, { from: random }),
'ChainLinkPricer: unauthorized sender',
)
})

it('should set weth price when sender is bot address', async () => {
const expiryTimestamp = (t0 + t1) / 2 // between t0 and t1
if ((await time.latest()) < expiryTimestamp + lockingPeriod) {
Expand Down
27 changes: 6 additions & 21 deletions test/unit-tests/chainlinkPricer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,38 +29,30 @@ contract('ChainlinkPricer', ([owner, bot, random]) => {
wethAggregator = await MockChainlinkAggregator.new()
weth = await MockERC20.new('WETH', 'WETH', 18)
// deploy pricer
pricer = await ChainlinkPricer.new(bot, weth.address, wethAggregator.address, oracle.address)
pricer = await ChainlinkPricer.new(weth.address, wethAggregator.address, oracle.address)
})

describe('constructor', () => {
it('should set the config correctly', async () => {
const asset = await pricer.asset()
assert.equal(asset, weth.address)
const bot = await pricer.bot()
assert.equal(bot, bot)
const aggregator = await pricer.aggregator()
assert.equal(aggregator, wethAggregator.address)
const oracleModule = await pricer.oracle()
assert.equal(oracleModule, oracle.address)
})
it('should revert if initializing aggregator with 0 address', async () => {
await expectRevert(
ChainlinkPricer.new(bot, weth.address, ZERO_ADDR, wethAggregator.address),
ChainlinkPricer.new(weth.address, ZERO_ADDR, wethAggregator.address),
'ChainLinkPricer: Cannot set 0 address as aggregator',
)
})
it('should revert if initializing oracle with 0 address', async () => {
await expectRevert(
ChainlinkPricer.new(bot, weth.address, oracle.address, ZERO_ADDR),
ChainlinkPricer.new(weth.address, oracle.address, ZERO_ADDR),
'ChainLinkPricer: Cannot set 0 address as oracle',
)
})
it('should revert if initializing bot with 0 address', async () => {
await expectRevert(
ChainlinkPricer.new(ZERO_ADDR, weth.address, oracle.address, wethAggregator.address),
'ChainLinkPricer: Cannot set 0 address as bot',
)
})
})

describe('getPrice', () => {
Expand Down Expand Up @@ -100,6 +92,7 @@ contract('ChainlinkPricer', ([owner, bot, random]) => {
before('setup history in aggregator', async () => {
// set t0, t1, t2, expiry, t3, t4
t0 = (await time.latest()).toNumber()
console.log('t0', t0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

// set round answers
await wethAggregator.setRoundAnswer(0, p0)
await wethAggregator.setRoundAnswer(1, p1)
Expand All @@ -120,23 +113,15 @@ contract('ChainlinkPricer', ([owner, bot, random]) => {
})

it('should set the correct price to the oracle', async () => {
const expiryTimestamp = (t0 + t1) / 2 // between t0 and t1
const expiryTimestamp = t0 + 30 // between t0 and t1
console.log('expiryTimestamp', expiryTimestamp)
const roundId = 1

await pricer.setExpiryPriceInOracle(expiryTimestamp, roundId, { from: bot })
const priceFromOracle = await oracle.getExpiryPrice(weth.address, expiryTimestamp)
assert.equal(p1.toString(), priceFromOracle[0].toString())
})

it('should revert if sender is not bot address', async () => {
const expiryTimestamp = (t1 + t2) / 2 // between t0 and t1
const roundId = 1
await expectRevert(
pricer.setExpiryPriceInOracle(expiryTimestamp, roundId, { from: random }),
'ChainLinkPricer: unauthorized sender',
)
})

it('should revert if round ID is incorrect: price[roundId].timestamp < expiry', async () => {
const expiryTimestamp = (t1 + t2) / 2 // between t0 and t1
const roundId = 1
Expand Down
15 changes: 8 additions & 7 deletions test/unit-tests/oracle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,6 @@ contract('Oracle', ([owner, disputer, random, collateral, strike]) => {
)
})

it('should revert disputing price before it get pushed by pricer', async () => {
await expectRevert(
oracle.disputeExpiryPrice(weth.address, otokenExpiry.plus(10), disputePrice, { from: disputer }),
'Oracle: price to dispute does not exist',
)
})

it('should dispute price during dispute period', async () => {
await oracle.disputeExpiryPrice(weth.address, otokenExpiry, disputePrice, { from: disputer })

Expand Down Expand Up @@ -307,6 +300,14 @@ contract('Oracle', ([owner, disputer, random, collateral, strike]) => {
'Oracle: caller is not authorized to set expiry price',
)
})

it('should set exiry price by disputing in case price not pushed', async () => {
await oracle.disputeExpiryPrice(weth.address, otokenExpiry.plus(10), new BigNumber(700), { from: disputer })

const [price, isFinalized] = await oracle.getExpiryPrice(weth.address, otokenExpiry.plus(10))
assert.equal(price.toString(), '700')
assert.equal(isFinalized, false)
})
})

describe('Stable prices', () => {
Expand Down