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 all 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
2 changes: 1 addition & 1 deletion docs/introduction-to-tbtc.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ The redemption flow is as follows:

The "happy paths" have been covered, but there hasn't been discussion of when *things fall apart*.

To disincentivise signers from stealing bitcoin, deposits are overcollateralised with ETH. The collateral is priced using an on-chain [ETH:BTC price feed](https://github.com/keep-network/tbtc/blob/master/solidity/contracts/price-feed/BTCETHPriceFeed.sol), which is operated by MakerDAO. This collateral also ensures guarantees of signer availability within the protocol.
To disincentivise signers from stealing bitcoin, deposits are overcollateralised with ETH. The collateral is priced using an on-chain [ETH:BTC price feed](https://github.com/keep-network/tbtc/blob/master/solidity/contracts/price-feed/SatWeiPriceFeed.sol), which works using a price-feed operated by MakerDAO. This collateral also ensures guarantees of signer availability within the protocol.

---

Expand Down
5 changes: 2 additions & 3 deletions docs/price-feed/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ ifndef::tbtc[toc::[]]

The price feed is an integral part of the system, ensuring sufficient
collateral backs all tBTC signers. For tBTC v1, the feed will be built on a
BTCETH
https://developer.makerdao.com/feeds/[https://docs.makerdao.com/smart-contract-modules/oracle-module/oracle-security-module-osm-detailed-documentation]
from MakerDAO, currently operated by MakerDAO.
https://developer.makerdao.com/feeds/[https://docs.makerdao.com/smart-contract-modules/oracle-module/oracle-security-module-osm-detailed-documentation[ETHBTC
Oracle Security Module] from MakerDAO, currently operated by MakerDAO.

The minimal price feed design is specified completely by the interface below:

Expand Down
2 changes: 1 addition & 1 deletion solidity/contracts/deposit/Deposit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ contract Deposit is DepositFactoryAuthority {
// THIS IS THE INIT FUNCTION
/// @notice The Deposit Factory can spin up a new deposit.
/// @dev Only the Deposit factory can call this.
/// @param _tbtcSystem `TBTCSystem` contract. More info in `VendingMachine`.
/// @param _tbtcSystem `TBTCSystem` contract. More info in `TBTCSystem`.
/// @param _tbtcToken `TBTCToken` contract. More info in TBTCToken`.
/// @param _tbtcDepositToken `TBTCDepositToken` (TDT) contract. More info in `TBTCDepositToken`.
/// @param _feeRebateToken `FeeRebateToken` (FRT) contract. More info in `FeeRebateToken`.
Expand Down
8 changes: 7 additions & 1 deletion solidity/contracts/external/IMedianizer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ pragma solidity 0.5.17;
interface IMedianizer {
/// @notice Get the current price.
/// @dev May revert if caller not whitelisted.
/// @return Price (USD) with 18 decimal places.
/// @return Designated price 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 Designated price with 18 decimal places.
/// @return true if price is > 0, else returns false
function peek() external view returns (uint256, bool);
}
8 changes: 0 additions & 8 deletions solidity/contracts/interfaces/IBTCETHPriceFeed.sol

This file was deleted.

14 changes: 14 additions & 0 deletions solidity/contracts/interfaces/ISatWeiPriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pragma solidity 0.5.17;

import "../external/IMedianizer.sol";

/// @notice satoshi/wei price feed interface.
interface ISatWeiPriceFeed {
Shadowfiend marked this conversation as resolved.
Show resolved Hide resolved
/// @notice Get the current price of 1 satoshi in wei.
/// @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 ETH/BTC meidanizer to the internal ethBtcFeeds array
function addEthBtcFeed(IMedianizer _ethBtcFeed) external;
}
57 changes: 0 additions & 57 deletions solidity/contracts/price-feed/BTCETHPriceFeed.sol

This file was deleted.

36 changes: 36 additions & 0 deletions solidity/contracts/price-feed/IntermediaryMedianizer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
pragma solidity 0.5.17;

import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "../external/IMedianizer.sol";

/// @title IntermediaryMedianizer is an updatable intermediary between a real
/// medianizer and IMedianizer users.
/// @dev This is used in Keep testnets where Maker has deployed a Medianizer
/// instance that needs to authorize a single consumer, to enable multiple
/// tBTC deployments to happen in the background and be pointed at a stable
/// medianizer instance that is authorized on the Maker contract. It allows
/// the updating of the backing medianizer and therefore is NOT suitable
/// for mainnet deployment.
contract IntermediaryMedianizer is Ownable, IMedianizer {
IMedianizer private _realMedianizer;

constructor(IMedianizer realMedianizer) public {
_realMedianizer = realMedianizer;
}

function getMedianizer() external view returns (IMedianizer) {
return _realMedianizer;
}

function peek() external view returns (uint256, bool) {
return _realMedianizer.peek();
}

function read() external view returns (uint256) {
return _realMedianizer.read();
}

function setMedianizer(IMedianizer realMedianizer) public onlyOwner {
_realMedianizer = realMedianizer;
}
}
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 ETHBTCPriceFeedMock is MockMedianizer {
// solium-disable-previous-line no-empty-blocks
}
89 changes: 89 additions & 0 deletions solidity/contracts/price-feed/SatWeiPriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
pragma solidity 0.5.17;

import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "../external/IMedianizer.sol";
import "../interfaces/ISatWeiPriceFeed.sol";

/// @notice satoshi/wei price feed.
/// @dev Used ETH/USD medianizer values converted to sat/wei.
contract SatWeiPriceFeed is Ownable, ISatWeiPriceFeed {
using SafeMath for uint256;

bool private _initialized = false;
address internal tbtcSystemAddress;

IMedianizer[] private ethBtcFeeds;

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

/// @notice Initialises the addresses of the ETHBTC price feeds.
/// @param _tbtcSystemAddress Address of the `TBTCSystem` contract. Used for access control.
/// @param _ETHBTCPriceFeed The ETHBTC price feed address.
Shadowfiend marked this conversation as resolved.
Show resolved Hide resolved
function initialize(
address _tbtcSystemAddress,
IMedianizer _ETHBTCPriceFeed
)
external onlyOwner
{
require(!_initialized, "Already initialized.");
tbtcSystemAddress = _tbtcSystemAddress;
ethBtcFeeds.push(_ETHBTCPriceFeed);
_initialized = true;
}

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

for(uint i = 0; i < ethBtcFeeds.length; i++){
(ethBtc, ethBtcActive) = ethBtcFeeds[i].peek();
if(ethBtcActive) {
break;
}
}

require(ethBtcActive, "Price feed offline");

// convert eth/btc to sat/wei
// We typecast down to uint128, because the first 128 bits of
// the medianizer value is unrelated to the price.
return uint256(10**28).div(uint256(uint128(ethBtc)));
}

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

for(uint i = 0; i < ethBtcFeeds.length; i++){
(, ethBtcActive) = ethBtcFeeds[i].peek();
if(ethBtcActive) {
return address(ethBtcFeeds[i]);
}
}
return address(0);
}
Shadowfiend marked this conversation as resolved.
Show resolved Hide resolved

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

/// @notice Function modifier ensures modified function is only called by tbtcSystemAddress.
modifier onlyTbtcSystem(){
require(msg.sender == tbtcSystemAddress, "Caller must be tbtcSystem contract");
_;
}
}
36 changes: 33 additions & 3 deletions solidity/contracts/system/TBTCSystem.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ 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";
import {ISatWeiPriceFeed} from "../interfaces/ISatWeiPriceFeed.sol";
import {DepositLog} from "../DepositLog.sol";

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

using SafeMath for uint256;

event EthBtcPriceFeedAdditionStarted(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 EthBtcPriceFeedAdded(address _priceFeed);
event LotSizesUpdated(uint64[] _lotSizes);
event AllowNewDepositsUpdated(bool _allowNewDeposits);
event SignerFeeDivisorUpdated(uint16 _signerFeeDivisor);
Expand All @@ -45,11 +48,12 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog {
uint16 _severelyUndercollateralizedThresholdPercent
);


bool _initialized = false;
uint256 pausedTimestamp;
uint256 constant pausedDuration = 10 days;

IBTCETHPriceFeed public priceFeed;
ISatWeiPriceFeed public priceFeed;
IBondedECDSAKeepVendor public keepVendor;
IRelay public relay;

Expand All @@ -73,8 +77,13 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog {
uint16 private newUndercollateralizedThresholdPercent;
uint16 private newSeverelyUndercollateralizedThresholdPercent;

// price feed
uint256 priceFeedGovernanceTimeDelay = 90 days;
uint256 appendEthBtcFeedTimer;
IMedianizer nextEthBtcFeed;

constructor(address _priceFeed, address _relay) public {
priceFeed = IBTCETHPriceFeed(_priceFeed);
priceFeed = ISatWeiPriceFeed(_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 ETH/BTC price feed contract to the priecFeed.
/// @dev `FinalizeAddEthBtcFeed` must be called to finalize.
function initializeAddEthBtcFeed(IMedianizer _ethBtcFeed) external {
nextEthBtcFeed = _ethBtcFeed;
appendEthBtcFeedTimer = block.timestamp + priceFeedGovernanceTimeDelay;
emit EthBtcPriceFeedAdditionStarted(address(_ethBtcFeed), block.timestamp);
}

/// @notice Finish adding a new price feed contract to the priceFeed.
/// @dev `InitializeAddEthBtcFeed` must be called first, once `appendEthBtcFeedTimer`
/// has passed, this function can be called to append a new price feed.
function finalizeAddEthBtcFeed() external {
require(block.timestamp > appendEthBtcFeedTimer, "Timeout not yet elapsed");
priceFeed.addEthBtcFeed(nextEthBtcFeed);
emit EthBtcPriceFeedAdded(address(nextEthBtcFeed));
}

// 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;
}
}
Loading