Skip to content

Commit

Permalink
Add maticX chainlink pricer, test cases and doc
Browse files Browse the repository at this point in the history
  • Loading branch information
mavvverick committed Aug 26, 2022
1 parent 841f81d commit 9cf7178
Show file tree
Hide file tree
Showing 4 changed files with 404 additions and 1 deletion.
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
Loading

0 comments on commit 9cf7178

Please sign in to comment.