Skip to content
This repository has been archived by the owner on Mar 28, 2023. It is now read-only.

Price feed governable addition #545

Merged
merged 23 commits into from
Apr 16, 2020
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions solidity/contracts/external/IMedianizer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@ interface IMedianizer {
/// @dev May revert if caller not whitelisted.
/// @return Price (USD) with 18 decimal places.
function read() external view returns (uint256);

/// @notice Get the current price and check if the price feed is active
/// @dev May revert if caller not whitelisted.
/// @return Price (USD) with 18 decimal places.
/// @return true if price is > 0, else returns false
function peek() external view returns (uint256, bool);
}
4 changes: 4 additions & 0 deletions solidity/contracts/interfaces/IBTCETHPriceFeed.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
pragma solidity ^0.5.10;
import "../external/IMedianizer.sol";

interface IBTCETHPriceFeed {
/// @notice Get the current price of bitcoin in ether.
/// @dev This does not account for any 'Flippening' event.
/// @return The price of one satoshi in wei.
function getPrice() external view returns (uint256);

/// @notice add a new BTC/ETH meidanizer to the internal btcEthFeeds array
function addBtcEthFeed(IMedianizer _btcEthFeed) external;
}
68 changes: 49 additions & 19 deletions solidity/contracts/price-feed/BTCETHPriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,25 @@ contract BTCETHPriceFeed is Ownable, IBTCETHPriceFeed {
using SafeMath for uint256;

bool private _initialized = false;
address internal tbtcSystemAddress;

IMedianizer btcPriceFeed;
IMedianizer ethPriceFeed;
IMedianizer[] private btcEthFeeds;

constructor() public {
// solium-disable-previous-line no-empty-blocks
}

/// @notice Initialises the addresses of the BTC/USD and ETH/USD price feeds.
/// @param _BTCUSDPriceFeed The BTC/USD price feed address.
/// @param _ETHUSDPriceFeed The ETH/USD price feed address.
/// @param _BTCETHPriceFeed The BTC/USD price feed address.
function initialize(
address _BTCUSDPriceFeed,
address _ETHUSDPriceFeed
address _tbtcSystemAddress,
IMedianizer _BTCETHPriceFeed
)
external onlyOwner
{
require(!_initialized, "Already initialized.");

btcPriceFeed = IMedianizer(_BTCUSDPriceFeed);
ethPriceFeed = IMedianizer(_ETHUSDPriceFeed);
tbtcSystemAddress = _tbtcSystemAddress;
btcEthFeeds.push(_BTCETHPriceFeed);
_initialized = true;
}

Expand All @@ -41,17 +39,49 @@ contract BTCETHPriceFeed is Ownable, IBTCETHPriceFeed {
function getPrice()
external view returns (uint256)
{
bool btcEthActive;
uint256 btcEth;

for(uint i = 0; i < btcEthFeeds.length; i++){
(btcEth, btcEthActive) = btcEthFeeds[i].peek();
if(btcEthActive) {
break;
}
}

require(btcEthActive, "Price feed offline");

// We typecast down to uint128, because the first 128 bits of
// the medianizer oracle value is unrelated to the price.
uint256 btcUsd = uint256(uint128(btcPriceFeed.read()));
uint256 ethUsd = uint256(uint128(ethPriceFeed.read()));
// The price is a ratio of bitcoin to ether is expressed as:
// x btc : y eth
// Bitcoin has 10 decimal places, ether has 18. Normalising the units, we have:
// x * 10^8 : y * 10^18
// Simplfying down, we can express it as:
// x : y * 10^10
// Due to order-of-ops, we can move the multiplication to get some more precision.
return btcUsd.mul(10**10).div(ethUsd);
return uint256(uint128(btcEth));
}

/// @notice Get the first active Medianizer contract from the btcEthFeeds array.
/// @return The address of the first Active Medianizer. address(0) if none found
function getWorkingBtcEthFeed() external view returns (address){
bool btcEthActive;

for(uint i = 0; i < btcEthFeeds.length; i++){
(, btcEthActive) = btcEthFeeds[i].peek();
if(btcEthActive) {
return address(btcEthFeeds[i]);
}
}
return address(0);
}

/// @notice Add _btcEthFeed to internal btcEthFeeds array.
/// @dev IMedianizer must be active in order to add.
function addBtcEthFeed(IMedianizer _btcEthFeed) external onlyTbtcSystem {
bool btcEthActive;
(, btcEthActive) = _btcEthFeed.peek();
require(btcEthActive, "Cannot add inactive feed");
btcEthFeeds.push(_btcEthFeed);
}

/// @notice Function modifier ensures modified function is only called by tbtcSystemAddress.
modifier onlyTbtcSystem(){
require(msg.sender == tbtcSystemAddress, "Caller must be tbtcSystem contract");
_;
}
}
10 changes: 5 additions & 5 deletions solidity/contracts/price-feed/MockMedianizer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ contract MockMedianizer is Ownable, IMedianizer {
return value;
}

function peek() external view returns (uint256, bool) {
return (value, value > 0);
}

function setValue(uint256 _value) external onlyOwner{
value = _value;
}
}

contract BTCUSDPriceFeed is MockMedianizer {
// solium-disable-previous-line no-empty-blocks
}

contract ETHUSDPriceFeed is MockMedianizer {
contract BTCETHPriceFeedMock is MockMedianizer {
// solium-disable-previous-line no-empty-blocks
}
30 changes: 30 additions & 0 deletions solidity/contracts/system/TBTCSystem.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {VendingMachine} from "./VendingMachine.sol";
import {DepositFactory} from "../proxy/DepositFactory.sol";

import {IRelay} from "@summa-tx/relay-sol/contracts/Relay.sol";
import "../external/IMedianizer.sol";

import {ITBTCSystem} from "../interfaces/ITBTCSystem.sol";
import {IBTCETHPriceFeed} from "../interfaces/IBTCETHPriceFeed.sol";
Expand All @@ -27,6 +28,7 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog {

using SafeMath for uint256;

event BtcEthPriceFeedAdditionStarted(address _priceFeed, uint256 _timestamp);
event LotSizesUpdateStarted(uint64[] _lotSizes, uint256 _timestamp);
event SignerFeeDivisorUpdateStarted(uint16 _signerFeeDivisor, uint256 _timestamp);
event CollateralizationThresholdsUpdateStarted(
Expand All @@ -36,6 +38,7 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog {
uint256 _timestamp
);

event BtcEthPriceFeedAdded(address _priceFeed);
event LotSizesUpdated(uint64[] _lotSizes);
event AllowNewDepositsUpdated(bool _allowNewDeposits);
event SignerFeeDivisorUpdated(uint16 _signerFeeDivisor);
Expand All @@ -45,6 +48,7 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog {
uint16 _severelyUndercollateralizedThresholdPercent
);


bool _initialized = false;
uint256 pausedTimestamp;
uint256 constant pausedDuration = 10 days;
Expand Down Expand Up @@ -73,6 +77,11 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog {
uint16 private newUndercollateralizedThresholdPercent;
uint16 private newSeverelyUndercollateralizedThresholdPercent;

// price feed
uint256 priceFeedGovernanceTimeDelay = 90 days;
uint256 appendBtcEthFeedTimer;
IMedianizer nextBtcEthFeed;

constructor(address _priceFeed, address _relay) public {
priceFeed = IBTCETHPriceFeed(_priceFeed);
relay = IRelay(_relay);
Expand Down Expand Up @@ -351,6 +360,10 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog {
return governanceTimeDelay;
}

function getPriceFeedGovernanceTimeDelay() public view returns (uint256) {
return priceFeedGovernanceTimeDelay;
}

// Price Feed

/// @notice Get the price of one satoshi in wei.
Expand All @@ -369,6 +382,23 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog {
return price;
}

/// @notice Initialize the addition of a new BTC/ETH price feed contract to the priecFeed.
/// @dev `FinalizeAddBtcEthFeed` must be called to finalize.
function initializeAddBtcEthFeed(IMedianizer _btcEthFeed) external {
nextBtcEthFeed = _btcEthFeed;
appendBtcEthFeedTimer = block.timestamp + priceFeedGovernanceTimeDelay;
emit BtcEthPriceFeedAdditionStarted(address(_btcEthFeed), block.timestamp);
}

/// @notice Finish adding a new price feed contract to the priceFeed.
/// @dev `InitializeAddBtcEthFeed` must be called first, once `appendBtcEthFeedTimer`
/// has passed, this function can be called to append a new price feed.
function finalizeAddBtcEthFeed() external {
require(block.timestamp > appendBtcEthFeedTimer, "Timeout not yet elapsed");
priceFeed.addBtcEthFeed(nextBtcEthFeed);
emit BtcEthPriceFeedAdded(address(nextBtcEthFeed));
}

// Difficulty Oracle
// TODO: This is a workaround. It will be replaced by tbtc-difficulty-oracle.
function fetchRelayCurrentDifficulty() external view returns (uint256) {
Expand Down
8 changes: 6 additions & 2 deletions solidity/contracts/test/price-feed/MockMedianizer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "../../../contracts/external/IMedianizer.sol";
/// @notice A mock implementation of a medianizer price oracle
/// @dev This is used in the Keep testnets only. Mainnet uses the MakerDAO medianizer.
contract MockMedianizer is Ownable, IMedianizer {
uint256 private value;
uint128 private value;

constructor() public {
// solium-disable-previous-line no-empty-blocks
Expand All @@ -16,7 +16,11 @@ contract MockMedianizer is Ownable, IMedianizer {
return value;
}

function setValue(uint256 _value) external onlyOwner {
function peek() external view returns (uint256, bool) {
return (value, value > 0);
}

function setValue(uint128 _value) external onlyOwner {
value = _value;
}
}
17 changes: 5 additions & 12 deletions solidity/migrations/2_deploy_contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ const Deposit = artifacts.require("Deposit")
const VendingMachine = artifacts.require("VendingMachine")

// price feed
const BTCETHPriceFeed = artifacts.require("BTCETHPriceFeed")
const BTCUSDPriceFeed = artifacts.require("BTCUSDPriceFeed")
const ETHUSDPriceFeed = artifacts.require("ETHUSDPriceFeed")
const MockBTCETHPriceFeed = artifacts.require("BTCETHPriceFeedMock")
const prices = require("./prices")

const MockRelay = artifacts.require("MockRelay")
Expand Down Expand Up @@ -83,18 +81,13 @@ module.exports = (deployer, network, accounts) => {
let difficultyRelay
// price feeds
if (network !== "mainnet") {
// On mainnet, we use the MakerDAO-deployed price feeds.
// On mainnet, we use the MakerDAO-deployed price feed.
// See: https://github.com/makerdao/oracles-v2#live-mainnet-oracles
// Otherwise, we deploy our own mock price feeds, which are simpler
// to maintain.
await deployer.deploy(BTCUSDPriceFeed)
await deployer.deploy(ETHUSDPriceFeed)

const btcPriceFeed = await BTCUSDPriceFeed.deployed()
const ethPriceFeed = await ETHUSDPriceFeed.deployed()

await btcPriceFeed.setValue(web3.utils.toWei(prices.BTCUSD))
await ethPriceFeed.setValue(web3.utils.toWei(prices.ETHUSD))
await deployer.deploy(MockBTCETHPriceFeed)
const btcEthPriceFeed = await MockBTCETHPriceFeed.deployed()
await btcEthPriceFeed.setValue(prices.BTCETH)

// On mainnet, we use the Summa-provided relay; see
// https://github.com/summa-tx/relays . On testnet, we use a local mock.
Expand Down
18 changes: 6 additions & 12 deletions solidity/migrations/3_initialize.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
const TBTCSystem = artifacts.require("TBTCSystem")

const BTCETHPriceFeed = artifacts.require("BTCETHPriceFeed")
const MockBTCUSDPriceFeed = artifacts.require("BTCUSDPriceFeed")
const MockETHUSDPriceFeed = artifacts.require("ETHUSDPriceFeed")
const MockBTCETHPriceFeed = artifacts.require("BTCETHPriceFeedMock")

const DepositFactory = artifacts.require("DepositFactory")
const Deposit = artifacts.require("Deposit")
Expand All @@ -11,11 +10,7 @@ const TBTCDepositToken = artifacts.require("TBTCDepositToken")
const FeeRebateToken = artifacts.require("FeeRebateToken")
const VendingMachine = artifacts.require("VendingMachine")

const {
BondedECDSAKeepVendorAddress,
BTCUSDPriceFeed,
ETHUSDPriceFeed,
} = require("./externals")
const {BondedECDSAKeepVendorAddress, BTCETHMedianizer} = require("./externals")

module.exports = async function(deployer, network) {
// Don't enact this setup during unit testing.
Expand Down Expand Up @@ -57,14 +52,13 @@ module.exports = async function(deployer, network) {
const btcEthPriceFeed = await BTCETHPriceFeed.deployed()
if (network === "mainnet") {
// Inject mainnet price feeds.
await btcEthPriceFeed.initialize(BTCUSDPriceFeed, ETHUSDPriceFeed)
await btcEthPriceFeed.initialize(tbtcSystem.address, BTCETHMedianizer)
} else {
// Inject mock price feeds.
const mockBtcPriceFeed = await MockBTCUSDPriceFeed.deployed()
const mockEthPriceFeed = await MockETHUSDPriceFeed.deployed()
const mockBtcEthPriceFeed = await MockBTCETHPriceFeed.deployed()
await btcEthPriceFeed.initialize(
mockBtcPriceFeed.address,
mockEthPriceFeed.address,
tbtcSystem.address,
mockBtcEthPriceFeed.address,
)
}
}
6 changes: 2 additions & 4 deletions solidity/migrations/externals.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ const BondedECDSAKeepVendorAddress = "0xZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
// Medianized price feeds.
// These are deployed and maintained by Maker.
// See: https://github.com/makerdao/oracles-v2#live-mainnet-oracles
const BTCUSDPriceFeed = "0xe0F30cb149fAADC7247E953746Be9BbBB6B5751f"
const ETHUSDPriceFeed = "0x64de91f5a373cd4c28de3600cb34c7c6ce410c85"
const BTCETHMedianizer = "0xABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE"

module.exports = {
BondedECDSAKeepVendorAddress,
BTCUSDPriceFeed,
ETHUSDPriceFeed,
BTCETHMedianizer,
}
3 changes: 1 addition & 2 deletions solidity/migrations/prices.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{
"BTCUSD": "7152.55",
"ETHUSD": "142.28"
"BTCETH": "502709446162"
Shadowfiend marked this conversation as resolved.
Show resolved Hide resolved
}
Loading