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

Add maticX chainlink pricer, test cases and doc #460

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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ specs/.certora_verify
.certora_config/
.certora_build.json
.coverage_contracts/
.coverage_artifacts/
.coverage_artifacts/
*.DS_Store
126 changes: 126 additions & 0 deletions contracts/pricers/MaticXPricer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.10;

import {AggregatorInterface} from "../interfaces/AggregatorInterface.sol";
import {OracleInterface} from "../interfaces/OracleInterface.sol";
import {OpynPricerInterface} from "../interfaces/OpynPricerInterface.sol";
import {SafeMath} from "../packages/oz/SafeMath.sol";

/**
* @notice A Pricer contract for maticX as reported by Chainlink
*/
contract MaticXPricer is OpynPricerInterface {
using SafeMath for uint256;

/// @dev base decimals
uint256 internal constant BASE = 8;

/// @notice maticX chainlink response decimals
uint256 public aggregatorDecimals;

/// @notice the opyn oracle address
OracleInterface public oracle;
/// @notice the aggregator for an asset
AggregatorInterface public aggregator;

/// @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 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 {
(, int256 price, , uint256 roundTimestamp, ) = aggregator.getRoundData(_roundId);

require(_expiryTimestamp <= roundTimestamp, "ChainLinkPricer: roundId not first after expiry");
require(price >= 0, "ChainLinkPricer: invalid price");

if (msg.sender != bot) {
bool isCorrectRoundId;
uint80 previousRoundId = uint80(uint256(_roundId).sub(1));

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

if (previousRoundTimestamp == 0) {
require(previousRoundId > 0, "ChainLinkPricer: Invalid previousRoundId");
previousRoundId = previousRoundId - 1;
} else if (previousRoundTimestamp > _expiryTimestamp) {
revert("ChainLinkPricer: previousRoundId not last before expiry");
} else {
isCorrectRoundId = true;
}
}
}

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

/**
* @notice get the live price for the asset
* @dev overides the getPrice function in OpynPricerInterface
* @return price of the asset in USD, scaled by 1e8
*/
function getPrice() external view override returns (uint256) {
(, int256 answer, , , ) = aggregator.latestRoundData();
require(answer > 0, "ChainLinkPricer: price is lower than 0");
// chainlink's answer is already 1e8
return _scaleToBase(uint256(answer));
}

/**
* @notice get historical chainlink price
* @param _roundId chainlink round id
* @return round price and timestamp
*/
function getHistoricalPrice(uint80 _roundId) external view override returns (uint256, uint256) {
(, int256 price, , uint256 roundTimestamp, ) = aggregator.getRoundData(_roundId);
return (_scaleToBase(uint256(price)), roundTimestamp);
}

/**
* @notice scale aggregator response to base decimals (1e8)
* @param _price aggregator price
* @return price scaled to 1e8
*/
function _scaleToBase(uint256 _price) internal view returns (uint256) {
if (aggregatorDecimals > BASE) {
uint256 exp = aggregatorDecimals.sub(BASE);
_price = _price.div(10**exp);
} else if (aggregatorDecimals < BASE) {
uint256 exp = BASE.sub(aggregatorDecimals);
_price = _price.mul(10**exp);
}

return _price;
}
}
81 changes: 81 additions & 0 deletions docs/contracts-documentation/pricers/MaticXPricer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# `MaticXPricer`

A Pricer contract for maticX as reported by Chainlink

## Modifiers:

- `onlyBot()`

## Functions:

- `constructor(address _bot, address _asset, address _aggregator, address _oracle) (public)`

- `setExpiryPriceInOracle(uint256 _expiryTimestamp, uint80 _roundId) (external)`

- `getPrice() (external)`

- `getHistoricalPrice(uint80 _roundId) (external)`

- `_scaleToBase(uint256 _price) (internal)`

### Modifier `onlyBot()`

modifier to check if sender address is equal to bot address

### Function `constructor(address _bot, address _asset, address _aggregator, address _oracle) public`

#### Parameters:

- `_bot`: priveleged address that can call setExpiryPriceInOracle

- `_asset`: asset that this pricer will get a price for

- `_aggregator`: Chainlink aggregator contract for the asset

- `_oracle`: Opyn Oracle address

### Function `setExpiryPriceInOracle(uint256 _expiryTimestamp, uint80 _roundId) external`

set the expiry price in the oracle, can only be called by Bot address

a roundId must be provided to confirm price validity, which is the first Chainlink price provided after the expiryTimestamp

#### Parameters:

- `_expiryTimestamp`: expiry to set a price for

- `_roundId`: the first roundId after expiryTimestamp

### Function `getPrice() → uint256 external`

get the live price for the asset

overides the getPrice function in OpynPricerInterface

#### Return Values:

- price of the asset in USD, scaled by 1e8

### Function `getHistoricalPrice(uint80 _roundId) → uint256, uint256 external`

get historical chainlink price

#### Parameters:

- `_roundId`: chainlink round id

#### Return Values:

- round price and timestamp

### Function `_scaleToBase(uint256 _price) → uint256 internal`

scale aggregator response to base decimals (1e8)

#### Parameters:

- `_price`: aggregator price

#### Return Values:

- price scaled to 1e8
34 changes: 34 additions & 0 deletions scripts/deployMaticX.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const yargs = require('yargs')

const MaticXPricer = artifacts.require('MaticX.sol')

module.exports = async function(callback) {
try {
const options = yargs
.usage(
'Usage: --network <network> --bot <bot> --asset <asset> --aggregator <aggregator> --oracle <oracle> --gasPrice <gasPrice> --gasLimit <gasLimit>',
)
.option('network', {describe: 'Network name', type: 'string', demandOption: true})
.option('bot', {describe: 'Bot address', type: 'string', demandOption: true})
.option('asset', {describe: 'Asset address', type: 'string', demandOption: true})
.option('aggregator', {describe: 'maticX aggregator address', type: 'string', demandOption: true})
.option('oracle', {describe: 'Oracle module address', type: 'string', demandOption: true})
.option('gasPrice', {describe: 'Gas price in WEI', type: 'string', demandOption: false})
.option('gasLimit', {describe: 'Gas Limit in WEI', type: 'string', demandOption: false}).argv

console.log(`Deploying maticX pricer contract on ${options.network} 🍕`)

const tx = await MaticXPricer.new(options.bot, options.asset, options.aggregator, options.oracle, {
gasPrice: options.gasPrice,
gas: options.gasLimit,
})

console.log('maticX pricer deployed! 🎉')
console.log(`Transaction hash: ${tx.transactionHash}`)
console.log(`Deployed contract address: ${tx.address}`)

callback()
} catch (err) {
callback(err)
}
}
Loading