Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebase Circuit Breaker #294

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
63 changes: 55 additions & 8 deletions contracts/UFragmentsPolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ contract UFragmentsPolicy is Ownable {
int256 public rebaseFunctionUpperPercentage;
int256 public rebaseFunctionGrowth;

// NOTE: This was added with v1.5 release, on-chain state will not
// have the history going back to epoch(0).
// Mapping between epoch and the supply at that epoch.
mapping(uint256 => uint256) public supplyHistory;

// Circuit breaker parameters which limit supply decline within the defined look back period.
uint8 public epochLookback;
int256 public tolerableDeclinePercentage;

int256 private constant ONE = int256(10**DECIMALS);

modifier onlyOrchestrator() {
Expand Down Expand Up @@ -139,6 +148,8 @@ contract UFragmentsPolicy is Ownable {

uint256 supplyAfterRebase = uFrags.rebase(epoch, supplyDelta);
assert(supplyAfterRebase <= MAX_SUPPLY);

supplyHistory[epoch] = supplyAfterRebase;
emit LogRebaseV2(epoch, exchangeRate, targetRate, supplyDelta);
}

Expand Down Expand Up @@ -206,7 +217,7 @@ contract UFragmentsPolicy is Ownable {
* @param minRebaseTimeIntervalSec_ More than this much time must pass between rebase
* operations, in seconds.
* @param rebaseWindowOffsetSec_ The number of seconds from the beginning of
the rebase interval, where the rebase window begins.
* the rebase interval, where the rebase window begins.
* @param rebaseWindowLengthSec_ The length of the rebase window in seconds.
*/
function setRebaseTimingParameters(
Expand All @@ -222,6 +233,22 @@ contract UFragmentsPolicy is Ownable {
rebaseWindowLengthSec = rebaseWindowLengthSec_;
}

/**
* @notice Sets the parameters which control rebase circuit breaker.
* @param epochLookback_ The number of rebase epochs to look back.
* @param tolerableDeclinePercentage_ The supply decline percentage which is allowed
* within the defined look back period.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* within the defined look back period.
* from the beginning of the defined look back period.

*/
function setRebaseCircuitBreakerParameters(
uint8 epochLookback_,
int256 tolerableDeclinePercentage_
) external onlyOwner {
require(tolerableDeclinePercentage_ > 0 && tolerableDeclinePercentage_ <= ONE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess 0 "should" be an allowable value... It's not a useful config per se, but there's no mechanical reason the implementation wouldn't allow it.


epochLookback = epochLookback_;
tolerableDeclinePercentage = tolerableDeclinePercentage_;
}

/**
* @notice A multi-chain AMPL interface method. The Ampleforth monetary policy contract
* on the base-chain and XC-AmpleController contracts on the satellite-chains
Expand Down Expand Up @@ -257,6 +284,9 @@ contract UFragmentsPolicy is Ownable {
lastRebaseTimestampSec = 0;
epoch = 0;

epochLookback = 0;
tolerableDeclinePercentage = ONE;

uFrags = uFrags_;
}

Expand Down Expand Up @@ -328,7 +358,8 @@ contract UFragmentsPolicy is Ownable {
* @return Computes the total supply adjustment in response to the exchange rate
* and the targetRate.
*/
function computeSupplyDelta(uint256 rate, uint256 targetRate) internal view returns (int256) {
function computeSupplyDelta(uint256 rate, uint256 targetRate) public view returns (int256) {
// No supply change if rate is within deviation threshold
if (withinDeviationThreshold(rate, targetRate)) {
return 0;
}
Expand All @@ -340,7 +371,27 @@ contract UFragmentsPolicy is Ownable {
rebaseFunctionUpperPercentage,
rebaseFunctionGrowth
);
return uFrags.totalSupply().toInt256Safe().mul(rebasePercentage).div(ONE);

int256 currentSupply = uFrags.totalSupply().toInt256Safe(); // (or) supplyHistory[epoch]
brandoniles marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int256 currentSupply = uFrags.totalSupply().toInt256Safe(); // (or) supplyHistory[epoch]
int256 currentSupply = uFrags.totalSupply().toInt256Safe();

I don't think the supplyHistory has been written to yet, in the core rebase flow.

int256 newSupply = ONE.add(rebasePercentage).mul(currentSupply).div(ONE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking for it, but I don't see this change... Where did rebasePercentage move from being 1-centered to 0-centered? (We add one here but not in the previous version's line)


// When supply is decreasing:
// We limit the supply delta, based on recent supply history.
if (rebasePercentage < 0 && epoch > epochLookback) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case that the supply history hasn't been written to yet, and is 0, then the below code basically no-ops? That's what it seems like. Do we have an explicit test case for this?

i.e.

  1. Test for supply history lookup returns 0
  2. Test for when epochLookback equals 0 (should basically be same as above since history will return 0)

int256 allowedMin = supplyHistory[epoch - epochLookback]
.toInt256Safe()
.mul(ONE.sub(tolerableDeclinePercentage))
.div(ONE);
if (allowedMin > currentSupply) {
// NOTE: Allowed minimum supply can only be at most the current supply.
allowedMin = currentSupply;
}
if (newSupply < allowedMin) {
newSupply = allowedMin;
}
}

return newSupply.sub(currentSupply);
}

/**
Expand All @@ -349,11 +400,7 @@ contract UFragmentsPolicy is Ownable {
* @return If the rate is within the deviation threshold from the target rate, returns true.
* Otherwise, returns false.
*/
function withinDeviationThreshold(uint256 rate, uint256 targetRate)
internal
view
returns (bool)
{
function withinDeviationThreshold(uint256 rate, uint256 targetRate) public view returns (bool) {
uint256 absoluteDeviationThreshold = targetRate.mul(deviationThreshold).div(10**DECIMALS);

return
Expand Down
3 changes: 2 additions & 1 deletion contracts/mocks/MockUFragments.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ contract MockUFragments is Mock {
int256[] memory intVals = new int256[](1);
intVals[0] = supplyDelta;
emit FunctionArguments(uintVals, intVals);
return uint256(int256(_supply) + int256(supplyDelta));
_supply = uint256(int256(_supply) + int256(supplyDelta));
return _supply;
}

function totalSupply() public view returns (uint256) {
Expand Down
1 change: 1 addition & 0 deletions test/unit/SafeMathInt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ describe('SafeMathInt', () => {
await expect(safeMathInt.abs(MIN_INT256)).to.be.reverted
})
})

describe('twoPower', function () {
const decimals18 = ethers.BigNumber.from('1000000000000000000')
const decimals10 = ethers.BigNumber.from('10000000000')
Expand Down
Loading