Skip to content

Commit eee83bc

Browse files
hayesgmjflatow
authored andcommitted
Price oracle proxy to allow decoupling SAI/DAI
1 parent afbe8a4 commit eee83bc

File tree

10 files changed

+172
-151
lines changed

10 files changed

+172
-151
lines changed

contracts/PriceOracleProxy.sol

Lines changed: 39 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,141 +3,104 @@ pragma solidity ^0.5.16;
33
import "./CErc20.sol";
44
import "./CToken.sol";
55
import "./PriceOracle.sol";
6-
import "./Comptroller.sol";
7-
import "./SafeMath.sol";
86

97
interface V1PriceOracleInterface {
108
function assetPrices(address asset) external view returns (uint);
119
}
1210

1311
contract PriceOracleProxy is PriceOracle {
14-
using SafeMath for uint256;
12+
/// @notice Indicator that this is a PriceOracle contract (for inspection)
13+
bool public constant isPriceOracle = true;
1514

16-
/**
17-
* @notice The v1 price oracle, which will continue to serve prices for v1 assets
18-
*/
15+
/// @notice The v1 price oracle, which will continue to serve prices for v1 assets
1916
V1PriceOracleInterface public v1PriceOracle;
2017

21-
/**
22-
* @notice The comptroller which is used to white-list assets the proxy will price
23-
* @dev Assets which are not white-listed will not be priced, to defend against abuse
24-
*/
25-
Comptroller public comptroller;
18+
/// @notice Address of the guardian, which may set the SAI price once
19+
address public guardian;
2620

27-
/**
28-
* @notice address of the cEther contract, which has a constant price
29-
*/
21+
/// @notice Address of the cEther contract, which has a constant price
3022
address public cEthAddress;
3123

32-
/**
33-
* @notice address of the cUSDC contract, which we hand pick a key for
34-
*/
24+
/// @notice Address of the cUSDC contract, which we hand pick a key for
3525
address public cUsdcAddress;
3626

37-
/**
38-
* @notice address of the cSAI contract, which we hand pick a key for
39-
*/
27+
/// @notice Address of the cSAI contract, which we hand pick a key for
4028
address public cSaiAddress;
4129

42-
/**
43-
* @notice address of the cDAI contract, which we peg to the SAI price
44-
*/
30+
/// @notice Address of the cDAI contract, which we hand pick a key for
4531
address public cDaiAddress;
4632

47-
/**
48-
* @notice address of the USDC contract, which we hand pick a key for
49-
*/
50-
address constant usdcOracleKey = address(1);
51-
52-
/**
53-
* @notice address of the SAI contract, which we hand pick a key for
54-
*/
55-
address constant saiOracleKey = address(2);
33+
/// @notice Handpicked key for USDC
34+
address public constant usdcOracleKey = address(1);
5635

57-
/**
58-
* @notice address of the asset which contains the USD/ETH price from Maker
59-
*/
60-
address public makerUsdOracleKey;
36+
/// @notice Handpicked key for DAI
37+
address public constant daiOracleKey = address(2);
6138

62-
/**
63-
* @notice Indicator that this is a PriceOracle contract (for inspection)
64-
*/
65-
bool public constant isPriceOracle = true;
39+
/// @notice Frozen SAI price (or 0 if not set yet)
40+
uint public saiPrice;
6641

6742
/**
68-
* @param comptroller_ The address of the comptroller, which will be consulted for market listing status
43+
* @param guardian_ The address of the guardian, which may set the SAI price once
6944
* @param v1PriceOracle_ The address of the v1 price oracle, which will continue to operate and hold prices for collateral assets
7045
* @param cEthAddress_ The address of cETH, which will return a constant 1e18, since all prices relative to ether
7146
* @param cUsdcAddress_ The address of cUSDC, which will be read from a special oracle key
72-
* @param cSaiAddress_ The address of cSAI, which will be read from a special oracle key
73-
* @param cDaiAddress_ The address of cDAI, which will be pegged to the SAI price
47+
* @param cSaiAddress_ The address of cSAI, which will be read directly from storage
48+
* @param cDaiAddress_ The address of cDAI, which will be read from a special oracle key
7449
*/
75-
constructor(address comptroller_,
50+
constructor(address guardian_,
7651
address v1PriceOracle_,
7752
address cEthAddress_,
7853
address cUsdcAddress_,
7954
address cSaiAddress_,
8055
address cDaiAddress_) public {
81-
comptroller = Comptroller(comptroller_);
56+
guardian = guardian_;
8257
v1PriceOracle = V1PriceOracleInterface(v1PriceOracle_);
8358

8459
cEthAddress = cEthAddress_;
8560
cUsdcAddress = cUsdcAddress_;
8661
cSaiAddress = cSaiAddress_;
8762
cDaiAddress = cDaiAddress_;
88-
89-
if (cSaiAddress_ != address(0)) {
90-
makerUsdOracleKey = CErc20(cSaiAddress_).underlying();
91-
}
9263
}
9364

9465
/**
9566
* @notice Get the underlying price of a listed cToken asset
9667
* @param cToken The cToken to get the underlying price of
97-
* @return The underlying asset price mantissa (scaled by 1e18).
98-
* Zero means the price is unavailable.
68+
* @return The underlying asset price mantissa (scaled by 1e18)
9969
*/
10070
function getUnderlyingPrice(CToken cToken) public view returns (uint) {
10171
address cTokenAddress = address(cToken);
102-
(bool isListed, ) = comptroller.markets(cTokenAddress);
103-
104-
if (!isListed) {
105-
// not white-listed, worthless
106-
return 0;
107-
}
10872

10973
if (cTokenAddress == cEthAddress) {
11074
// ether always worth 1
11175
return 1e18;
11276
}
11377

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

120-
if (cTokenAddress == cSaiAddress || cTokenAddress == cDaiAddress) {
121-
// check and bound the DAI/USDC posted price ratio
122-
// and use that to scale the maker price (for a token w/ 18 decimals)
123-
uint makerUsdPrice = v1PriceOracle.assetPrices(makerUsdOracleKey);
124-
uint postedUsdcPrice = v1PriceOracle.assetPrices(usdcOracleKey);
125-
uint postedScaledDaiPrice = v1PriceOracle.assetPrices(saiOracleKey).mul(1e12);
126-
uint daiUsdcRatio = postedScaledDaiPrice.mul(1e18).div(postedUsdcPrice);
127-
128-
if (daiUsdcRatio < 0.95e18) {
129-
return makerUsdPrice.mul(0.95e18).div(1e18);
130-
}
131-
132-
if (daiUsdcRatio > 1.05e18) {
133-
return makerUsdPrice.mul(1.05e18).div(1e18);
134-
}
82+
if (cTokenAddress == cDaiAddress) {
83+
return v1PriceOracle.assetPrices(daiOracleKey);
84+
}
13585

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

13991
// otherwise just read from v1 oracle
14092
address underlying = CErc20(cTokenAddress).underlying();
14193
return v1PriceOracle.assetPrices(underlying);
14294
}
95+
96+
/**
97+
* @notice Set the price of SAI, permanently
98+
* @param price The price for SAI
99+
*/
100+
function setSaiPrice(uint price) public {
101+
require(msg.sender == guardian, "only guardian may set the SAI price");
102+
require(saiPrice == 0, "SAI price may only be set once");
103+
require(price < 0.1e18, "SAI price must be < 0.1 ETH");
104+
saiPrice = price;
105+
}
143106
}

networks/mainnet-abi.json

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1941,7 +1941,7 @@
19411941
"inputs": [
19421942
{
19431943
"internalType": "address",
1944-
"name": "comptroller_",
1944+
"name": "guardian_",
19451945
"type": "address"
19461946
},
19471947
{
@@ -2042,18 +2042,18 @@
20422042
{
20432043
"constant": true,
20442044
"inputs": [],
2045-
"name": "comptroller",
2045+
"name": "daiOracleKey",
20462046
"outputs": [
20472047
{
2048-
"internalType": "contract Comptroller",
2048+
"internalType": "address",
20492049
"name": "",
20502050
"type": "address"
20512051
}
20522052
],
20532053
"payable": false,
20542054
"stateMutability": "view",
20552055
"type": "function",
2056-
"signature": "0x5fe3b567"
2056+
"signature": "0xe5ee0f6e"
20572057
},
20582058
{
20592059
"constant": true,
@@ -2077,6 +2077,22 @@
20772077
"type": "function",
20782078
"signature": "0xfc57d4df"
20792079
},
2080+
{
2081+
"constant": true,
2082+
"inputs": [],
2083+
"name": "guardian",
2084+
"outputs": [
2085+
{
2086+
"internalType": "address",
2087+
"name": "",
2088+
"type": "address"
2089+
}
2090+
],
2091+
"payable": false,
2092+
"stateMutability": "view",
2093+
"type": "function",
2094+
"signature": "0x452a9320"
2095+
},
20802096
{
20812097
"constant": true,
20822098
"inputs": [],
@@ -2096,7 +2112,39 @@
20962112
{
20972113
"constant": true,
20982114
"inputs": [],
2099-
"name": "makerUsdOracleKey",
2115+
"name": "saiPrice",
2116+
"outputs": [
2117+
{
2118+
"internalType": "uint256",
2119+
"name": "",
2120+
"type": "uint256"
2121+
}
2122+
],
2123+
"payable": false,
2124+
"stateMutability": "view",
2125+
"type": "function",
2126+
"signature": "0xa86b1944"
2127+
},
2128+
{
2129+
"constant": false,
2130+
"inputs": [
2131+
{
2132+
"internalType": "uint256",
2133+
"name": "price",
2134+
"type": "uint256"
2135+
}
2136+
],
2137+
"name": "setSaiPrice",
2138+
"outputs": [],
2139+
"payable": false,
2140+
"stateMutability": "nonpayable",
2141+
"type": "function",
2142+
"signature": "0xf9c99e9c"
2143+
},
2144+
{
2145+
"constant": true,
2146+
"inputs": [],
2147+
"name": "usdcOracleKey",
21002148
"outputs": [
21012149
{
21022150
"internalType": "address",
@@ -2107,7 +2155,7 @@
21072155
"payable": false,
21082156
"stateMutability": "view",
21092157
"type": "function",
2110-
"signature": "0xbc8a4ef4"
2158+
"signature": "0x3e76f255"
21112159
},
21122160
{
21132161
"constant": true,

networks/mainnet.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"ZRX": "0xE41d2489571d322189246DaFA5ebDe1F4699F498",
44
"cUSDC": "0x39AA39c021dfbaE8faC545936693aC917d5E7563",
55
"PriceOracle": "0x02557a5e05defeffd4cae6d83ea3d173b272c904",
6-
"PriceOracleProxy": "0x1D8aEdc9E924730DD3f9641CDb4D1B92B848b4bd",
6+
"PriceOracleProxy": "0xdA17fbEdA95222f331Cb1D252401F4b44F49f7A0",
77
"Maximillion": "0xf859A1AD94BcF445A406B892eF0d3082f4174088",
88
"GovernorAlpha": "0xc0dA01a04C3f3E0be433606045bB7017A7323E38",
99
"cDAI": "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643",
@@ -42,7 +42,7 @@
4242
"Blocks": {
4343
"cUSDC": 7710760,
4444
"PriceOracle": 6747538,
45-
"PriceOracleProxy": 8983577,
45+
"PriceOracleProxy": 9788084,
4646
"Maximillion": 7710775,
4747
"GovernorAlpha": 9601447,
4848
"cDAI": 8983575,
@@ -73,7 +73,7 @@
7373
"cUSDC": "0x39AA39c021dfbaE8faC545936693aC917d5E7563",
7474
"cSAI": "0xF5DCe57282A584D2746FaF1593d3121Fcac444dC",
7575
"cDAI": "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643",
76-
"address": "0x1D8aEdc9E924730DD3f9641CDb4D1B92B848b4bd"
76+
"address": "0xdA17fbEdA95222f331Cb1D252401F4b44F49f7A0"
7777
},
7878
"Maximillion": {
7979
"description": "Maximillion",
@@ -136,7 +136,7 @@
136136
},
137137
"Constructors": {
138138
"cUSDC": "0x000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000003d9819210a31b4961b30ef54be2aed79b9c9cd3b000000000000000000000000c64c4cba055efa614ce01f4bad8a9f519c4f8fab0000000000000000000000000000000000000000000000000000b5e620f4800000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000011436f6d706f756e642055534420436f696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056355534443000000000000000000000000000000000000000000000000000000",
139-
"PriceOracleProxy": "0x0000000000000000000000003d9819210a31b4961b30ef54be2aed79b9c9cd3b00000000000000000000000002557a5e05defeffd4cae6d83ea3d173b272c9040000000000000000000000004ddc2d193948926d02f9b1fe9e1daa0718270ed500000000000000000000000039aa39c021dfbae8fac545936693ac917d5e7563000000000000000000000000f5dce57282a584d2746faf1593d3121fcac444dc0000000000000000000000005d3a536e4d6dbd6114cc1ead35777bab948e3643",
139+
"PriceOracleProxy": "0x0000000000000000000000008b8592e9570e96166336603a1b4bd1e8db20fa2000000000000000000000000002557a5e05defeffd4cae6d83ea3d173b272c9040000000000000000000000004ddc2d193948926d02f9b1fe9e1daa0718270ed500000000000000000000000039aa39c021dfbae8fac545936693ac917d5e7563000000000000000000000000f5dce57282a584d2746faf1593d3121fcac444dc0000000000000000000000005d3a536e4d6dbd6114cc1ead35777bab948e3643",
140140
"Maximillion": "0x0000000000000000000000004ddc2d193948926d02f9b1fe9e1daa0718270ed5",
141141
"GovernorAlpha": "0x0000000000000000000000006d903f6003cca6255d85cca4d3b5e5146dc33925000000000000000000000000c00e94cb662c3520282e6f5717214004a7f268880000000000000000000000008b8592e9570e96166336603a1b4bd1e8db20fa20",
142142
"cDAI": "0x0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000003d9819210a31b4961b30ef54be2aed79b9c9cd3b0000000000000000000000005562024784cc914069d67d89a28e3201bf7b57e7000000000000000000000000000000000000000000a56fa5b99019a5c80000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000080000000000000000000000006d903f6003cca6255d85cca4d3b5e5146dc3392500000000000000000000000099ee778b9a6205657dd03b2b91415c8646d521ec00000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000c436f6d706f756e642044616900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004634441490000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000",

scenario/src/Builder/PriceOracleProxyBuilder.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,24 @@ export interface PriceOracleProxyData {
2222

2323
export async function buildPriceOracleProxy(world: World, from: string, event: Event): Promise<{world: World, priceOracleProxy: PriceOracleProxy, invokation: Invokation<PriceOracleProxy>}> {
2424
const fetchers = [
25-
new Fetcher<{comptroller: AddressV, priceOracle: AddressV, cETH: AddressV, cUSDC: AddressV, cSAI: AddressV, cDAI: AddressV}, PriceOracleProxyData>(`
25+
new Fetcher<{guardian: AddressV, priceOracle: AddressV, cETH: AddressV, cUSDC: AddressV, cSAI: AddressV, cDAI: AddressV}, PriceOracleProxyData>(`
2626
#### Price Oracle Proxy
2727
28-
* "Deploy <Comptroller:Address> <PriceOracle:Address> <cETH:Address> <cUSDC:Address> <cSAI:Address> <cDAI:Address>" - The Price Oracle which proxies to a backing oracle
28+
* "Deploy <Guardian:Address> <PriceOracle:Address> <cETH:Address> <cUSDC:Address> <cSAI:Address> <cDAI:Address>" - The Price Oracle which proxies to a backing oracle
2929
* E.g. "PriceOracleProxy Deploy (Unitroller Address) (PriceOracle Address) cETH cUSDC cSAI cDAI"
3030
`,
3131
"PriceOracleProxy",
3232
[
33-
new Arg("comptroller", getAddressV),
33+
new Arg("guardian", getAddressV),
3434
new Arg("priceOracle", getAddressV),
3535
new Arg("cETH", getAddressV),
3636
new Arg("cUSDC", getAddressV),
3737
new Arg("cSAI", getAddressV),
3838
new Arg("cDAI", getAddressV)
3939
],
40-
async (world, {comptroller, priceOracle, cETH, cUSDC, cSAI, cDAI}) => {
40+
async (world, {guardian, priceOracle, cETH, cUSDC, cSAI, cDAI}) => {
4141
return {
42-
invokation: await PriceOracleProxyContract.deploy<PriceOracleProxy>(world, from, [comptroller.val, priceOracle.val, cETH.val, cUSDC.val, cSAI.val, cDAI.val]),
42+
invokation: await PriceOracleProxyContract.deploy<PriceOracleProxy>(world, from, [guardian.val, priceOracle.val, cETH.val, cUSDC.val, cSAI.val, cDAI.val]),
4343
description: "Price Oracle Proxy",
4444
cETH: cETH.val,
4545
cUSDC: cUSDC.val,

scenario/src/Contract/PriceOracleProxy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {Contract} from '../Contract';
22
import {Callable, Sendable} from '../Invokation';
3+
import {encodedNumber} from '../Encoding';
34

45
interface PriceOracleProxyMethods {
56
getUnderlyingPrice(asset: string): Callable<number>
67
v1PriceOracle(): Callable<string>;
8+
setSaiPrice(amount: encodedNumber): Sendable<number>
79
}
810

911
export interface PriceOracleProxy extends Contract {

scenario/src/Event/PriceOracleProxyEvent.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ async function verifyPriceOracleProxy(world: World, priceOracleProxy: PriceOracl
4545
return world;
4646
}
4747

48+
async function setSaiPrice(world: World, from: string, priceOracleProxy: PriceOracleProxy, amount: NumberV): Promise<World> {
49+
return addAction(
50+
world,
51+
`Set price oracle SAI price to ${amount.show()}`,
52+
await invoke(world, priceOracleProxy.methods.setSaiPrice(amount.encode()), from)
53+
);
54+
}
55+
4856
export function priceOracleProxyCommands() {
4957
return [
5058
new Command<{params: EventV}>(`
@@ -73,6 +81,20 @@ export function priceOracleProxyCommands() {
7381
new Arg("contractName", getStringV, {default: new StringV("PriceOracleProxy")})
7482
],
7583
(world, {priceOracleProxy, apiKey, contractName}) => verifyPriceOracleProxy(world, priceOracleProxy, apiKey.val, contractName.val)
84+
),
85+
86+
new Command<{priceOracleProxy: PriceOracleProxy, amount: NumberV}>(`
87+
#### SetSaiPrice
88+
89+
* "SetSaiPrice <Amount>" - Sets the per-ether price for SAI
90+
* E.g. "PriceOracleProxy SetSaiPrice 1.0"
91+
`,
92+
"SetSaiPrice",
93+
[
94+
new Arg("priceOracleProxy", getPriceOracleProxy, {implicit: true}),
95+
new Arg("amount", getExpNumberV)
96+
],
97+
(world, from, {priceOracleProxy, amount}) => setSaiPrice(world, from, priceOracleProxy, amount)
7698
)
7799
];
78100
}

0 commit comments

Comments
 (0)