Skip to content
This repository has been archived by the owner on Sep 27, 2022. It is now read-only.

Commit

Permalink
Add P1ChainlinkOracle (#253)
Browse files Browse the repository at this point in the history
* Add P1ChainlinkOracle

* update client and tests

* CR: update decimals

* add files

* Fix test

* Update Artifacts

* Bump version

* Fix coverage
  • Loading branch information
Kenadia authored Aug 19, 2020
1 parent 27b0293 commit 0d85832
Show file tree
Hide file tree
Showing 19 changed files with 471 additions and 76 deletions.
41 changes: 41 additions & 0 deletions contracts/external/chainlink/I_Aggregator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
Copyright 2020 dYdX Trading Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

pragma solidity 0.5.16;
pragma experimental ABIEncoderV2;


/**
* @title I_Aggregator
* @author dYdX
*
* Partial interface for a Chainlink Aggregator.
*/
interface I_Aggregator {

// ============ Getter Functions ============

/**
* @notice Get the most recent answer from the aggregator.
* @dev Warning: Will return 0 if no answer has been reached.
*/
function latestAnswer()
external
view
returns (int256);
}
109 changes: 109 additions & 0 deletions contracts/protocol/v1/oracles/P1ChainlinkOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
Copyright 2020 dYdX Trading Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

pragma solidity 0.5.16;
pragma experimental ABIEncoderV2;

import { I_Aggregator } from "../../../external/chainlink/I_Aggregator.sol";
import { BaseMath } from "../../lib/BaseMath.sol";
import { I_P1Oracle } from "../intf/I_P1Oracle.sol";


/**
* @title P1ChainlinkOracle
* @author dYdX
*
* @notice P1Oracle that reads the price from a Chainlink aggregator.
*/
contract P1ChainlinkOracle is
I_P1Oracle
{
using BaseMath for uint256;

// ============ Storage ============

// The underlying aggregator to get the price from.
address public _ORACLE_;

// The address with permission to read the oracle price.
address public _READER_;

// A constant factor to adjust the price by, as a fixed-point number with 18 decimal places.
uint256 public _ADJUSTMENT_;

// Compact storage for the above parameters.
mapping (address => bytes32) public _MAPPING_;

// ============ Constructor ============

constructor(
address oracle,
address reader,
uint96 adjustmentExponent
)
public
{
_ORACLE_ = oracle;
_READER_ = reader;
_ADJUSTMENT_ = 10 ** uint256(adjustmentExponent);

bytes32 oracleAndAdjustment =
bytes32(bytes20(oracle)) |
bytes32(uint256(adjustmentExponent));
_MAPPING_[reader] = oracleAndAdjustment;
}

// ============ Public Functions ============

/**
* @notice Returns the oracle price from the aggregator.
*
* @return The adjusted price as a fixed-point number with 18 decimals.
*/
function getPrice()
external
view
returns (uint256)
{
bytes32 oracleAndExponent = _MAPPING_[msg.sender];
require(
oracleAndExponent != bytes32(0),
"P1ChainlinkOracle: Sender not authorized to get price"
);
(address oracle, uint256 adjustment) = getOracleAndAdjustment(oracleAndExponent);
int256 answer = I_Aggregator(oracle).latestAnswer();
require(
answer > 0,
"P1ChainlinkOracle: Invalid answer from aggregator"
);
uint256 rawPrice = uint256(answer);
return rawPrice.baseMul(adjustment);
}

function getOracleAndAdjustment(
bytes32 oracleAndExponent
)
private
pure
returns (address, uint256)
{
address oracle = address(bytes20(oracleAndExponent));
uint256 exponent = uint256(uint96(uint256(oracleAndExponent)));
return (oracle, 10 ** exponent);
}
}
56 changes: 56 additions & 0 deletions contracts/test/external/Test_ChainlinkAggregator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
Copyright 2020 dYdX Trading Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

pragma solidity 0.5.16;
pragma experimental ABIEncoderV2;

import { I_Aggregator } from "../../external/chainlink/I_Aggregator.sol";


/**
* @title Test_ChainlinkAggregator
* @author dYdX
*
* Chainlink Aggregator for testing
*/
/* solium-disable-next-line camelcase */
contract Test_ChainlinkAggregator is
I_Aggregator
{
int256 public _ANSWER_ = 0;

// ============ Test Data Setter Functions ============

function setAnswer(
int256 newAnswer
)
external
{
_ANSWER_ = newAnswer;
}

// ============ Getter Functions ============

function latestAnswer()
external
view
returns (int256)
{
return _ANSWER_;
}
}
20 changes: 17 additions & 3 deletions migrations/2_deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
const {
getChainId,
isDevNetwork,
getChainlinkPriceOracleAddress,
getMakerPriceOracleAddress,
getDeployerAddress,
getOracleAdjustment,
getChainlinkOracleAdjustmentExponent,
getInverseOracleAdjustmentExponent,
getTokenAddress,
getWethAddress,
Expand Down Expand Up @@ -54,6 +56,7 @@ const P1Deleveraging = artifacts.require('P1Deleveraging');
const P1Liquidation = artifacts.require('P1Liquidation');

// Price Oracles
const P1ChainlinkOracle = artifacts.require('P1ChainlinkOracle');
const P1MakerOracle = artifacts.require('P1MakerOracle');
const P1OracleInverter = artifacts.require('P1OracleInverter');
const P1MirrorOracleETHUSD = artifacts.require('P1MirrorOracleETHUSD');
Expand All @@ -75,6 +78,7 @@ const TestSolo = artifacts.require('Test_Solo');
const TestToken = artifacts.require('Test_Token');
const TestToken2 = artifacts.require('Test_Token2');
const TestMakerOracle = artifacts.require('Test_MakerOracle');
const TestChainlinkAggregator = artifacts.require('Test_ChainlinkAggregator');
const WETH9 = artifacts.require('WETH9');

// ============ Main Migration ============
Expand Down Expand Up @@ -104,6 +108,7 @@ async function deployTestContracts(deployer, network) {
deployer.deploy(TestToken),
deployer.deploy(TestToken2),
deployer.deploy(TestMakerOracle),
deployer.deploy(TestChainlinkAggregator),
deployer.deploy(WETH9),
]);
}
Expand All @@ -120,7 +125,11 @@ async function deployProtocol(deployer, network, accounts) {
}

async function deployOracles(deployer, network) {
// Deploy funding oracles and Maker oracle wrapper.
// Get external oracle addresses.
const chainlinkOracle = getChainlinkPriceOracleAddress(network, TestChainlinkAggregator);
const makerOracle = getMakerPriceOracleAddress(network, TestMakerOracle);

// Deploy funding oracles, Maker oracle wrapper, and Chainlink oracle wrapper.
await Promise.all([
deployer.deploy(
P1FundingOracle,
Expand All @@ -130,19 +139,24 @@ async function deployOracles(deployer, network) {
P1InverseFundingOracle,
getFundingRateProviderAddress(network),
),
deployer.deploy(
P1ChainlinkOracle,
chainlinkOracle,
PerpetualProxy.address,
getChainlinkOracleAdjustmentExponent(network),
),
deployer.deploy(P1MakerOracle),
]);

// Deploy oracle inverter.
await deployer.deploy(
P1OracleInverter,
P1MakerOracle.address,
PerpetualProxy.address, // TODO: Supply inverse perpetual address.
PerpetualProxy.address,
getInverseOracleAdjustmentExponent(network),
);

// Deploy mirror oracle.
const makerOracle = getMakerPriceOracleAddress(network, TestMakerOracle);
await deployer.deploy(
P1MirrorOracleETHUSD,
makerOracle,
Expand Down
21 changes: 20 additions & 1 deletion migrations/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ function getPartiallyDelayedMultisigAddress(network) {
throw new Error('Cannot find Admin Multisig');
}

function getChainlinkPriceOracleAddress(network, devContract) {
if (isMainnet(network)) {
return '0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c'; // LINK-USD price aggregator
}
if (isDevNetwork(network)) {
return devContract.address;
}
throw new Error('Cannot find Chainlink price oracle');
}

function getMakerPriceOracleAddress(network, devContract) {
if (isMainnet(network)) {
return '0x064409168198A7E9108036D072eF59F923dEDC9A';
Expand All @@ -84,7 +94,7 @@ function getMakerPriceOracleAddress(network, devContract) {
if (isDevNetwork(network)) {
return devContract.address;
}
throw new Error('Cannot find MakerPriceOracle');
throw new Error('Cannot find Maker price oracle');
}

function getDeployerAddress(network, accounts) {
Expand All @@ -107,6 +117,13 @@ function getOracleAdjustment(network) {
throw new Error('Cannot find oracle adjustment');
}

function getChainlinkOracleAdjustmentExponent() {
// Aggregator provides “natural” price with 8 decimals of precision.
// PLINK uses 6 decimals (by convention).
// USDC uses 6 decimals.
return '28';
}

function getInverseOracleAdjustmentExponent(network) {
if (isMainnet(network) || isKovan(network)) {
return '30'; // 1e18
Expand Down Expand Up @@ -207,9 +224,11 @@ module.exports = {
getChainId,
isDevNetwork,
getPartiallyDelayedMultisigAddress,
getChainlinkPriceOracleAddress,
getMakerPriceOracleAddress,
getDeployerAddress,
getOracleAdjustment,
getChainlinkOracleAdjustmentExponent,
getInverseOracleAdjustmentExponent,
getTokenAddress,
getWethAddress,
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dydxprotocol/perpetual",
"version": "1.1.4",
"version": "1.2.0",
"description": "Ethereum Smart Contracts and TypeScript library used for the dYdX Perpetual Contracts",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
Expand Down
2 changes: 2 additions & 0 deletions scripts/Artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { default as PerpetualProxy } from '../build/contracts/PerpetualProxy.jso
import { default as PerpetualV1 } from '../build/contracts/PerpetualV1.json';
import { default as P1FundingOracle } from '../build/contracts/P1FundingOracle.json';
import { default as P1InverseFundingOracle } from '../build/contracts/P1InverseFundingOracle.json';
import { default as P1ChainlinkOracle } from '../build/contracts/P1ChainlinkOracle.json';
import { default as P1MakerOracle } from '../build/contracts/P1MakerOracle.json';
import { default as P1MirrorOracleETHUSD } from '../build/contracts/P1MirrorOracleETHUSD.json';
import { default as P1OracleInverter } from '../build/contracts/P1OracleInverter.json';
Expand All @@ -26,6 +27,7 @@ export default {
PerpetualV1,
P1FundingOracle,
P1InverseFundingOracle,
P1ChainlinkOracle,
P1MakerOracle,
P1MirrorOracleETHUSD,
P1OracleInverter,
Expand Down
Loading

0 comments on commit 0d85832

Please sign in to comment.