Skip to content

Commit

Permalink
Price oracle proxy to allow decoupling SAI/DAI
Browse files Browse the repository at this point in the history
  • Loading branch information
hayesgm authored and jflatow committed Apr 1, 2020
1 parent afbe8a4 commit eee83bc
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 151 deletions.
115 changes: 39 additions & 76 deletions contracts/PriceOracleProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,141 +3,104 @@ pragma solidity ^0.5.16;
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 Indicator that this is a PriceOracle contract (for inspection)
bool public constant isPriceOracle = true;

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

/**
* @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 guardian, which may set the SAI price once
address public guardian;

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

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

/**
* @notice address of the cSAI contract, which we hand pick a key for
*/
/// @notice Address of the cSAI contract, which we hand pick a key for
address public cSaiAddress;

/**
* @notice address of the cDAI contract, which we peg to the SAI price
*/
/// @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 SAI contract, which we hand pick a key for
*/
address constant saiOracleKey = address(2);
/// @notice Handpicked key for USDC
address public constant usdcOracleKey = address(1);

/**
* @notice address of the asset which contains the USD/ETH price from Maker
*/
address public makerUsdOracleKey;
/// @notice Handpicked key for DAI
address public constant daiOracleKey = address(2);

/**
* @notice Indicator that this is a PriceOracle contract (for inspection)
*/
bool public constant isPriceOracle = true;
/// @notice Frozen SAI price (or 0 if not set yet)
uint public saiPrice;

/**
* @param comptroller_ The address of the comptroller, which will be consulted for market listing status
* @param guardian_ The address of the guardian, which may set the SAI price once
* @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 cSaiAddress_ The address of cSAI, which will be read from a special oracle key
* @param cDaiAddress_ The address of cDAI, which will be pegged to the SAI price
* @param cSaiAddress_ The address of cSAI, which will be read directly from storage
* @param cDaiAddress_ The address of cDAI, which will be read from a special oracle key
*/
constructor(address comptroller_,
constructor(address guardian_,
address v1PriceOracle_,
address cEthAddress_,
address cUsdcAddress_,
address cSaiAddress_,
address cDaiAddress_) public {
comptroller = Comptroller(comptroller_);
guardian = guardian_;
v1PriceOracle = V1PriceOracleInterface(v1PriceOracle_);

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

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

/**
* @notice Get the underlying price of a listed cToken asset
* @param cToken The cToken to get the underlying price of
* @return The underlying asset price mantissa (scaled by 1e18).
* Zero means the price is unavailable.
* @return The underlying asset price mantissa (scaled by 1e18)
*/
function getUnderlyingPrice(CToken cToken) public view returns (uint) {
address cTokenAddress = address(cToken);
(bool isListed, ) = comptroller.markets(cTokenAddress);

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

if (cTokenAddress == cEthAddress) {
// ether always worth 1
return 1e18;
}

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)
return v1PriceOracle.assetPrices(usdcOracleKey);
}

if (cTokenAddress == cSaiAddress || 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(saiOracleKey).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);
}
if (cTokenAddress == cDaiAddress) {
return v1PriceOracle.assetPrices(daiOracleKey);
}

return makerUsdPrice.mul(daiUsdcRatio).div(1e18);
if (cTokenAddress == cSaiAddress) {
// use the frozen SAI price if set, otherwise use the DAI price
return saiPrice > 0 ? saiPrice : v1PriceOracle.assetPrices(daiOracleKey);
}

// otherwise just read from v1 oracle
address underlying = CErc20(cTokenAddress).underlying();
return v1PriceOracle.assetPrices(underlying);
}

/**
* @notice Set the price of SAI, permanently
* @param price The price for SAI
*/
function setSaiPrice(uint price) public {
require(msg.sender == guardian, "only guardian may set the SAI price");
require(saiPrice == 0, "SAI price may only be set once");
require(price < 0.1e18, "SAI price must be < 0.1 ETH");
saiPrice = price;
}
}
60 changes: 54 additions & 6 deletions networks/mainnet-abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1941,7 +1941,7 @@
"inputs": [
{
"internalType": "address",
"name": "comptroller_",
"name": "guardian_",
"type": "address"
},
{
Expand Down Expand Up @@ -2042,18 +2042,18 @@
{
"constant": true,
"inputs": [],
"name": "comptroller",
"name": "daiOracleKey",
"outputs": [
{
"internalType": "contract Comptroller",
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function",
"signature": "0x5fe3b567"
"signature": "0xe5ee0f6e"
},
{
"constant": true,
Expand All @@ -2077,6 +2077,22 @@
"type": "function",
"signature": "0xfc57d4df"
},
{
"constant": true,
"inputs": [],
"name": "guardian",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function",
"signature": "0x452a9320"
},
{
"constant": true,
"inputs": [],
Expand All @@ -2096,7 +2112,39 @@
{
"constant": true,
"inputs": [],
"name": "makerUsdOracleKey",
"name": "saiPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function",
"signature": "0xa86b1944"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "price",
"type": "uint256"
}
],
"name": "setSaiPrice",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function",
"signature": "0xf9c99e9c"
},
{
"constant": true,
"inputs": [],
"name": "usdcOracleKey",
"outputs": [
{
"internalType": "address",
Expand All @@ -2107,7 +2155,7 @@
"payable": false,
"stateMutability": "view",
"type": "function",
"signature": "0xbc8a4ef4"
"signature": "0x3e76f255"
},
{
"constant": true,
Expand Down
8 changes: 4 additions & 4 deletions networks/mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"ZRX": "0xE41d2489571d322189246DaFA5ebDe1F4699F498",
"cUSDC": "0x39AA39c021dfbaE8faC545936693aC917d5E7563",
"PriceOracle": "0x02557a5e05defeffd4cae6d83ea3d173b272c904",
"PriceOracleProxy": "0x1D8aEdc9E924730DD3f9641CDb4D1B92B848b4bd",
"PriceOracleProxy": "0xdA17fbEdA95222f331Cb1D252401F4b44F49f7A0",
"Maximillion": "0xf859A1AD94BcF445A406B892eF0d3082f4174088",
"GovernorAlpha": "0xc0dA01a04C3f3E0be433606045bB7017A7323E38",
"cDAI": "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643",
Expand Down Expand Up @@ -42,7 +42,7 @@
"Blocks": {
"cUSDC": 7710760,
"PriceOracle": 6747538,
"PriceOracleProxy": 8983577,
"PriceOracleProxy": 9788084,
"Maximillion": 7710775,
"GovernorAlpha": 9601447,
"cDAI": 8983575,
Expand Down Expand Up @@ -73,7 +73,7 @@
"cUSDC": "0x39AA39c021dfbaE8faC545936693aC917d5E7563",
"cSAI": "0xF5DCe57282A584D2746FaF1593d3121Fcac444dC",
"cDAI": "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643",
"address": "0x1D8aEdc9E924730DD3f9641CDb4D1B92B848b4bd"
"address": "0xdA17fbEdA95222f331Cb1D252401F4b44F49f7A0"
},
"Maximillion": {
"description": "Maximillion",
Expand Down Expand Up @@ -136,7 +136,7 @@
},
"Constructors": {
"cUSDC": "0x000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000003d9819210a31b4961b30ef54be2aed79b9c9cd3b000000000000000000000000c64c4cba055efa614ce01f4bad8a9f519c4f8fab0000000000000000000000000000000000000000000000000000b5e620f4800000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000011436f6d706f756e642055534420436f696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056355534443000000000000000000000000000000000000000000000000000000",
"PriceOracleProxy": "0x0000000000000000000000003d9819210a31b4961b30ef54be2aed79b9c9cd3b00000000000000000000000002557a5e05defeffd4cae6d83ea3d173b272c9040000000000000000000000004ddc2d193948926d02f9b1fe9e1daa0718270ed500000000000000000000000039aa39c021dfbae8fac545936693ac917d5e7563000000000000000000000000f5dce57282a584d2746faf1593d3121fcac444dc0000000000000000000000005d3a536e4d6dbd6114cc1ead35777bab948e3643",
"PriceOracleProxy": "0x0000000000000000000000008b8592e9570e96166336603a1b4bd1e8db20fa2000000000000000000000000002557a5e05defeffd4cae6d83ea3d173b272c9040000000000000000000000004ddc2d193948926d02f9b1fe9e1daa0718270ed500000000000000000000000039aa39c021dfbae8fac545936693ac917d5e7563000000000000000000000000f5dce57282a584d2746faf1593d3121fcac444dc0000000000000000000000005d3a536e4d6dbd6114cc1ead35777bab948e3643",
"Maximillion": "0x0000000000000000000000004ddc2d193948926d02f9b1fe9e1daa0718270ed5",
"GovernorAlpha": "0x0000000000000000000000006d903f6003cca6255d85cca4d3b5e5146dc33925000000000000000000000000c00e94cb662c3520282e6f5717214004a7f268880000000000000000000000008b8592e9570e96166336603a1b4bd1e8db20fa20",
"cDAI": "0x0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000003d9819210a31b4961b30ef54be2aed79b9c9cd3b0000000000000000000000005562024784cc914069d67d89a28e3201bf7b57e7000000000000000000000000000000000000000000a56fa5b99019a5c80000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000080000000000000000000000006d903f6003cca6255d85cca4d3b5e5146dc3392500000000000000000000000099ee778b9a6205657dd03b2b91415c8646d521ec00000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000c436f6d706f756e642044616900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004634441490000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000",
Expand Down
10 changes: 5 additions & 5 deletions scenario/src/Builder/PriceOracleProxyBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@ export interface PriceOracleProxyData {

export async function buildPriceOracleProxy(world: World, from: string, event: Event): Promise<{world: World, priceOracleProxy: PriceOracleProxy, invokation: Invokation<PriceOracleProxy>}> {
const fetchers = [
new Fetcher<{comptroller: AddressV, priceOracle: AddressV, cETH: AddressV, cUSDC: AddressV, cSAI: AddressV, cDAI: AddressV}, PriceOracleProxyData>(`
new Fetcher<{guardian: AddressV, priceOracle: AddressV, cETH: AddressV, cUSDC: AddressV, cSAI: AddressV, cDAI: AddressV}, PriceOracleProxyData>(`
#### Price Oracle Proxy
* "Deploy <Comptroller:Address> <PriceOracle:Address> <cETH:Address> <cUSDC:Address> <cSAI:Address> <cDAI:Address>" - The Price Oracle which proxies to a backing oracle
* "Deploy <Guardian:Address> <PriceOracle:Address> <cETH:Address> <cUSDC:Address> <cSAI:Address> <cDAI:Address>" - The Price Oracle which proxies to a backing oracle
* E.g. "PriceOracleProxy Deploy (Unitroller Address) (PriceOracle Address) cETH cUSDC cSAI cDAI"
`,
"PriceOracleProxy",
[
new Arg("comptroller", getAddressV),
new Arg("guardian", getAddressV),
new Arg("priceOracle", getAddressV),
new Arg("cETH", getAddressV),
new Arg("cUSDC", getAddressV),
new Arg("cSAI", getAddressV),
new Arg("cDAI", getAddressV)
],
async (world, {comptroller, priceOracle, cETH, cUSDC, cSAI, cDAI}) => {
async (world, {guardian, priceOracle, cETH, cUSDC, cSAI, cDAI}) => {
return {
invokation: await PriceOracleProxyContract.deploy<PriceOracleProxy>(world, from, [comptroller.val, priceOracle.val, cETH.val, cUSDC.val, cSAI.val, cDAI.val]),
invokation: await PriceOracleProxyContract.deploy<PriceOracleProxy>(world, from, [guardian.val, priceOracle.val, cETH.val, cUSDC.val, cSAI.val, cDAI.val]),
description: "Price Oracle Proxy",
cETH: cETH.val,
cUSDC: cUSDC.val,
Expand Down
2 changes: 2 additions & 0 deletions scenario/src/Contract/PriceOracleProxy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {Contract} from '../Contract';
import {Callable, Sendable} from '../Invokation';
import {encodedNumber} from '../Encoding';

interface PriceOracleProxyMethods {
getUnderlyingPrice(asset: string): Callable<number>
v1PriceOracle(): Callable<string>;
setSaiPrice(amount: encodedNumber): Sendable<number>
}

export interface PriceOracleProxy extends Contract {
Expand Down
22 changes: 22 additions & 0 deletions scenario/src/Event/PriceOracleProxyEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ async function verifyPriceOracleProxy(world: World, priceOracleProxy: PriceOracl
return world;
}

async function setSaiPrice(world: World, from: string, priceOracleProxy: PriceOracleProxy, amount: NumberV): Promise<World> {
return addAction(
world,
`Set price oracle SAI price to ${amount.show()}`,
await invoke(world, priceOracleProxy.methods.setSaiPrice(amount.encode()), from)
);
}

export function priceOracleProxyCommands() {
return [
new Command<{params: EventV}>(`
Expand Down Expand Up @@ -73,6 +81,20 @@ export function priceOracleProxyCommands() {
new Arg("contractName", getStringV, {default: new StringV("PriceOracleProxy")})
],
(world, {priceOracleProxy, apiKey, contractName}) => verifyPriceOracleProxy(world, priceOracleProxy, apiKey.val, contractName.val)
),

new Command<{priceOracleProxy: PriceOracleProxy, amount: NumberV}>(`
#### SetSaiPrice
* "SetSaiPrice <Amount>" - Sets the per-ether price for SAI
* E.g. "PriceOracleProxy SetSaiPrice 1.0"
`,
"SetSaiPrice",
[
new Arg("priceOracleProxy", getPriceOracleProxy, {implicit: true}),
new Arg("amount", getExpNumberV)
],
(world, from, {priceOracleProxy, amount}) => setSaiPrice(world, from, priceOracleProxy, amount)
)
];
}
Expand Down
Loading

0 comments on commit eee83bc

Please sign in to comment.