Skip to content

Commit

Permalink
Update the PriceOracleProxy
Browse files Browse the repository at this point in the history
  • Loading branch information
jflatow committed Sep 9, 2019
1 parent f754de9 commit 2c52f3e
Show file tree
Hide file tree
Showing 13 changed files with 438 additions and 97 deletions.
97 changes: 72 additions & 25 deletions contracts/PriceOracleProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,83 @@ import "./CErc20.sol";
import "./CToken.sol";
import "./PriceOracle.sol";
import "./Comptroller.sol";
import "./SafeMath.sol";

interface V1PriceOracleInterface {
function assetPrices(address asset) external view returns (uint);
}

contract PriceOracleProxy is PriceOracle {
using SafeMath for uint256;

/**
* @notice The v1 price oracle, which will continue to serve prices
* prices for v1 assets
* @notice The v1 price oracle, which will continue to serve prices for v1 assets
*/
V1PriceOracleInterface public v1PriceOracle;

/**
* @notice The active comptroller, which will be checked for listing status
* to short circuit and return 0 for unlisted assets.
*
* @dev Listed markets are not part of the comptroller interface used by
* cTokens, so we assumena an instance of v1 comptroller.sol instead
* @notice The comptroller which is used to white-list assets the proxy will price
* @dev Assets which are not white-listed will not be priced, to defend against abuse
*/
Comptroller public comptroller;

/**
* @notice address of the cEther contract, which has a constant price
*/
address public cEtherAddress;
address public cEthAddress;

/**
* @notice address of the cUSDC contract, which we hand pick a key for
*/
address public cUsdcAddress;

/**
* @notice address of the cDAI contract, which we hand pick a key for
*/
address public cDaiAddress;

/**
* @notice address of the USDC contract, which we hand pick a key for
*/
address constant usdcOracleKey = address(1);

/**
* @notice address of the DAI contract, which we hand pick a key for
*/
address constant daiOracleKey = address(2);

/**
* @notice address of the asset which contains the USD/ETH price from Maker
*/
address public makerUsdOracleKey;

/**
* @notice Indicator that this is a PriceOracle contract (for inspection)
*/
bool public constant isPriceOracle = true;

/**
* @param comptroller_ The address of the active comptroller, which will
* be consulted for market listing status.
* @param v1PriceOracle_ The address of the v1 price oracle, which will
* continue to operate and hold prices for collateral assets.
* @param cEtherAddress_ The address of the cEther contract, which will
* return a constant 1e18, since all prices relative to ether
* @param comptroller_ The address of the comptroller, which will be consulted for market listing status
* @param v1PriceOracle_ The address of the v1 price oracle, which will continue to operate and hold prices for collateral assets
* @param cEthAddress_ The address of cETH, which will return a constant 1e18, since all prices relative to ether
* @param cUsdcAddress_ The address of cUSDC, which will be read from a special oracle key
* @param cDaiAddress_ The address of cDAI, which will be read from a special oracle key
*/
constructor(address comptroller_, address v1PriceOracle_, address cEtherAddress_, address cUsdcAddress_) public {
constructor(address comptroller_,
address v1PriceOracle_,
address cEthAddress_,
address cUsdcAddress_,
address cDaiAddress_) public {
comptroller = Comptroller(comptroller_);
v1PriceOracle = V1PriceOracleInterface(v1PriceOracle_);
cEtherAddress = cEtherAddress_;

cEthAddress = cEthAddress_;
cUsdcAddress = cUsdcAddress_;
cDaiAddress = cDaiAddress_;

if (cDaiAddress_ != address(0)) {
makerUsdOracleKey = CErc20(cDaiAddress_).underlying();
}
}

/**
Expand All @@ -71,18 +94,42 @@ contract PriceOracleProxy is PriceOracle {
(bool isListed, ) = comptroller.markets(cTokenAddress);

if (!isListed) {
// not listed, worthless
// not white-listed, worthless
return 0;
} else if (cTokenAddress == cEtherAddress) {
}

if (cTokenAddress == cEthAddress) {
// ether always worth 1
return 1e18;
} else if (cTokenAddress == cUsdcAddress) {
// read from hand picked key
return v1PriceOracle.assetPrices(usdcOracleKey);
} else {
// read from v1 oracle
address underlying = CErc20(cTokenAddress).underlying();
return v1PriceOracle.assetPrices(underlying);
}

if (cTokenAddress == cUsdcAddress) {
// we assume USDC/USD = 1, and let DAI/ETH float based on the DAI/USDC ratio
// use the maker usd price (for a token w/ 6 decimals)
return v1PriceOracle.assetPrices(makerUsdOracleKey).mul(1e12); // 1e(18 - 6)
}

if (cTokenAddress == cDaiAddress) {
// check and bound the DAI/USDC posted price ratio
// and use that to scale the maker price (for a token w/ 18 decimals)
uint makerUsdPrice = v1PriceOracle.assetPrices(makerUsdOracleKey);
uint postedUsdcPrice = v1PriceOracle.assetPrices(usdcOracleKey);
uint postedScaledDaiPrice = v1PriceOracle.assetPrices(daiOracleKey).mul(1e12);
uint daiUsdcRatio = postedScaledDaiPrice.mul(1e18).div(postedUsdcPrice);

if (daiUsdcRatio < 0.95e18) {
return makerUsdPrice.mul(0.95e18).div(1e18);
}

if (daiUsdcRatio > 1.05e18) {
return makerUsdPrice.mul(1.05e18).div(1e18);
}

return makerUsdPrice.mul(daiUsdcRatio).div(1e18);
}

// otherwise just read from v1 oracle
address underlying = CErc20(cTokenAddress).underlying();
return v1PriceOracle.assetPrices(underlying);
}
}
161 changes: 161 additions & 0 deletions contracts/SafeMath.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
pragma solidity ^0.5.8;

// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/Math.sol
// Subject to the MIT license.

/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");

return c;
}

/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}

/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*
* NOTE: This is a feature of the next version of OpenZeppelin Contracts.
* @dev Get it via `npm install @openzeppelin/contracts@next`.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;

return c;
}

/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}

uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");

return c;
}

/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}

/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
* NOTE: This is a feature of the next version of OpenZeppelin Contracts.
* @dev Get it via `npm install @openzeppelin/contracts@next`.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold

return c;
}

/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}

/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*
* NOTE: This is a feature of the next version of OpenZeppelin Contracts.
* @dev Get it via `npm install @openzeppelin/contracts@next`.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
Loading

0 comments on commit 2c52f3e

Please sign in to comment.