Skip to content

Commit

Permalink
Merge pull request #86 from mento-protocol/feature/static-value-breaker
Browse files Browse the repository at this point in the history
feat(breaker): ValueDeltaBreaker
  • Loading branch information
ninabarbakadze authored Dec 14, 2022
2 parents 897901b + 568e389 commit 9aefa17
Show file tree
Hide file tree
Showing 21 changed files with 855 additions and 227 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/publish_npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ jobs:

- name: "publish"
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*.log
.DS_Store
.pnp.*
.tool-versions
yarn-debug.log*
yarn-error.log*

Expand Down
2 changes: 1 addition & 1 deletion contracts/BreakerBox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ contract BreakerBox is IBreakerBox, Initializable, Ownable {
if (info.tradingMode != 0) {
IBreaker breaker = IBreaker(tradingModeBreaker[info.tradingMode]);

uint256 cooldown = breaker.getCooldown();
uint256 cooldown = breaker.getCooldown(rateFeedID);

// If the cooldown == 0, then a manual reset is required.
if (((cooldown > 0) && (cooldown + info.lastUpdatedTime) <= block.timestamp)) {
Expand Down
134 changes: 41 additions & 93 deletions contracts/MedianDeltaBreaker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.5.13;

import { IBreaker } from "./interfaces/IBreaker.sol";
import { WithCooldown } from "./common/breakers/WithCooldown.sol";
import { WithThreshold } from "./common/breakers/WithThreshold.sol";

import { ISortedOracles } from "./interfaces/ISortedOracles.sol";

Expand All @@ -16,91 +18,75 @@ import { FixidityLib } from "./common/FixidityLib.sol";
* more than a configured relative threshold from the previous one. If this
* breaker is triggered for a rate feed it should be set to no trading mode.
*/
contract MedianDeltaBreaker is IBreaker, Ownable {
contract MedianDeltaBreaker is IBreaker, WithCooldown, WithThreshold, Ownable {
using SafeMath for uint256;
using FixidityLib for FixidityLib.Fraction;

/* ==================== State Variables ==================== */

// The amount of time that must pass before the breaker can be reset for a rate feed.
// Should be set to 0 to force a manual reset.
uint256 public cooldownTime;

// The default allowed threshold for the median rate change as a Fixidity fraction.
FixidityLib.Fraction public defaultRateChangeThreshold;

// Maps rate feed to a threshold.
mapping(address => FixidityLib.Fraction) public rateChangeThreshold;

// Address of the Mento SortedOracles contract
ISortedOracles public sortedOracles;

// Emitted when the default rate threshold is updated.
event DefaultRateChangeThresholdUpdated(uint256 defaultRateChangeThreshold);

// Emitted when the rate threshold is updated.
event RateChangeThresholdUpdated(address rateFeedID, uint256 rateChangeThreshold);
// The previous median recorded for a ratefeed.
mapping(address => uint256) public previousMedianRates;

/* ==================== Constructor ==================== */

constructor(
uint256 _cooldownTime,
uint256 _defaultCooldownTime,
uint256 _defaultRateChangeThreshold,
ISortedOracles _sortedOracles,
address[] memory rateFeedIDs,
uint256[] memory rateChangeThresholds,
ISortedOracles _sortedOracles
uint256[] memory cooldownTimes
) public {
_transferOwnership(msg.sender);
setCooldownTime(_cooldownTime);
setDefaultRateChangeThreshold(_defaultRateChangeThreshold);
setSortedOracles(_sortedOracles);
setRateChangeThresholds(rateFeedIDs, rateChangeThresholds);

_setDefaultCooldownTime(_defaultCooldownTime);
_setDefaultRateChangeThreshold(_defaultRateChangeThreshold);
_setRateChangeThresholds(rateFeedIDs, rateChangeThresholds);
_setCooldownTimes(rateFeedIDs, cooldownTimes);
}

/* ==================== Restricted Functions ==================== */

/**
* @notice Sets the cooldownTime to the specified value.
* @param _cooldownTime The new cooldownTime value.
* @notice Sets the cooldown time to the specified value for a rate feed.
* @param rateFeedIDs the targeted rate feed.
* @param cooldownTimes The new cooldownTime value.
* @dev Should be set to 0 to force a manual reset.
*/
function setCooldownTime(address[] calldata rateFeedIDs, uint256[] calldata cooldownTimes) external onlyOwner {
_setCooldownTimes(rateFeedIDs, cooldownTimes);
}

/**
* @notice Sets the cooldownTime to the specified value for a rate feed.
* @param cooldownTime The new cooldownTime value.
* @dev Should be set to 0 to force a manual reset.
*/
function setCooldownTime(uint256 _cooldownTime) public onlyOwner {
cooldownTime = _cooldownTime;
emit CooldownTimeUpdated(_cooldownTime);
function setDefaultCooldownTime(uint256 cooldownTime) external onlyOwner {
_setDefaultCooldownTime(cooldownTime);
}

/**
* @notice Sets rateChangeThreshold.
* @param _defaultRateChangeThreshold The new rateChangeThreshold value.
*/
function setDefaultRateChangeThreshold(uint256 _defaultRateChangeThreshold) public onlyOwner {
defaultRateChangeThreshold = FixidityLib.wrap(_defaultRateChangeThreshold);
require(defaultRateChangeThreshold.lt(FixidityLib.fixed1()), "rate change threshold must be less than 1");
emit DefaultRateChangeThresholdUpdated(_defaultRateChangeThreshold);
function setDefaultRateChangeThreshold(uint256 _defaultRateChangeThreshold) external onlyOwner {
_setDefaultRateChangeThreshold(_defaultRateChangeThreshold);
}

/**
* @notice Configures rate feed to rate shreshold pairs.
* @param rateFeedIDs Collection of the addresses rate feeds.
* @param rateChangeThresholds Collection of the rate thresholds.
*/
function setRateChangeThresholds(address[] memory rateFeedIDs, uint256[] memory rateChangeThresholds)
public
function setRateChangeThresholds(address[] calldata rateFeedIDs, uint256[] calldata rateChangeThresholds)
external
onlyOwner
{
require(
rateFeedIDs.length == rateChangeThresholds.length,
"rate feeds and rate change thresholds have to be the same length"
);
for (uint256 i = 0; i < rateFeedIDs.length; i++) {
if (rateFeedIDs[i] != address(0) && rateChangeThresholds[i] != 0) {
FixidityLib.Fraction memory _rateChangeThreshold = FixidityLib.wrap(rateChangeThresholds[i]);
require(sortedOracles.getOracles(rateFeedIDs[i]).length > 0, "rate feed ID does not exist as it has 0 oracles");
require(_rateChangeThreshold.lt(FixidityLib.fixed1()), "rate change threshold must be less than 1");
rateChangeThreshold[rateFeedIDs[i]] = _rateChangeThreshold;
emit RateChangeThresholdUpdated(rateFeedIDs[i], rateChangeThresholds[i]);
}
}
_setRateChangeThresholds(rateFeedIDs, rateChangeThresholds);
}

/**
Expand All @@ -115,14 +101,6 @@ contract MedianDeltaBreaker is IBreaker, Ownable {

/* ==================== View Functions ==================== */

/**
* @notice Gets the cooldown time for the breaker.
* @return Returns the time in seconds.
*/
function getCooldown() external view returns (uint256) {
return cooldownTime;
}

/**
* @notice Check if the current median report rate for a rate feed change, relative
* to the last median report, is greater than the configured threshold.
Expand All @@ -131,17 +109,18 @@ contract MedianDeltaBreaker is IBreaker, Ownable {
* @return triggerBreaker A bool indicating whether or not this breaker
* should be tripped for the rate feed.
*/
function shouldTrigger(address rateFeedID) public view returns (bool triggerBreaker) {
uint256 previousMedian = sortedOracles.previousMedianRate(rateFeedID);
function shouldTrigger(address rateFeedID) public returns (bool triggerBreaker) {
(uint256 currentMedian, ) = sortedOracles.medianRate(rateFeedID);

uint256 previousMedian = previousMedianRates[rateFeedID];
previousMedianRates[rateFeedID] = currentMedian;

if (previousMedian == 0) {
// Previous median will be 0 if this rate feed is new and has not had at least two median updates yet.
// Previous median will be 0 the first time rate is checked.
return false;
}

(uint256 currentMedian, ) = sortedOracles.medianRate(rateFeedID);

// Check if current median is within allowed threshold of last median
triggerBreaker = !isWithinThreshold(previousMedian, currentMedian, rateFeedID);
return exceedsThreshold(previousMedian, currentMedian, rateFeedID);
}

/**
Expand All @@ -150,38 +129,7 @@ contract MedianDeltaBreaker is IBreaker, Ownable {
* @return resetBreaker A bool indicating whether or not
* this breaker can be reset for the given rate feed.
*/
function shouldReset(address rateFeedID) external view returns (bool resetBreaker) {
function shouldReset(address rateFeedID) external returns (bool resetBreaker) {
return !shouldTrigger(rateFeedID);
}

/**
* @notice Checks if the specified current median rate is within the allowed threshold.
* @param prevRate The previous median rate.
* @param currentRate The current median rate.
* @param rateFeedID The specific rate ID to check threshold for.
* @return Returns a bool indicating whether or not the current rate
* is within the allowed threshold.
*/
function isWithinThreshold(
uint256 prevRate,
uint256 currentRate,
address rateFeedID
) public view returns (bool) {
uint256 allowedThreshold = defaultRateChangeThreshold.unwrap();

uint256 rateSpecificThreshold = rateChangeThreshold[rateFeedID].unwrap();

// checks if a given rate feed id has a threshold set and reassignes it
if (rateSpecificThreshold != 0) allowedThreshold = rateSpecificThreshold;

uint256 fixed1 = FixidityLib.fixed1().unwrap();

uint256 maxPercent = uint256(fixed1).add(allowedThreshold);
uint256 maxValue = (prevRate.mul(maxPercent)).div(10**24);

uint256 minPercent = uint256(fixed1).sub(allowedThreshold);
uint256 minValue = (prevRate.mul(minPercent)).div(10**24);

return (currentRate >= minValue && currentRate <= maxValue);
}
}
3 changes: 0 additions & 3 deletions contracts/SortedOracles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ contract SortedOracles is ISortedOracles, ICeloVersionedContract, Ownable, Initi
mapping(address => uint256) public tokenReportExpirySeconds;

IBreakerBox public breakerBox;
mapping(address => uint256) public previousMedianRate;

event OracleAdded(address indexed token, address indexed oracleAddress);
event OracleRemoved(address indexed token, address indexed oracleAddress);
Expand Down Expand Up @@ -245,7 +244,6 @@ contract SortedOracles is ISortedOracles, ICeloVersionedContract, Ownable, Initi
emit OracleReported(token, msg.sender, now, value);
uint256 newMedian = rates[token].getMedianValue();
if (newMedian != originalMedian) {
previousMedianRate[token] = originalMedian;
emit MedianUpdated(token, newMedian);
}

Expand Down Expand Up @@ -375,7 +373,6 @@ contract SortedOracles is ISortedOracles, ICeloVersionedContract, Ownable, Initi
emit OracleReportRemoved(token, oracle);
uint256 newMedian = rates[token].getMedianValue();
if (newMedian != originalMedian) {
previousMedianRate[token] = newMedian;
emit MedianUpdated(token, newMedian);
}
}
Expand Down
Loading

0 comments on commit 9aefa17

Please sign in to comment.