Skip to content

Commit 4fb7a0e

Browse files
committed
rebase circuit breaker
1 parent 852c7a1 commit 4fb7a0e

File tree

4 files changed

+1259
-1035
lines changed

4 files changed

+1259
-1035
lines changed

contracts/UFragmentsPolicy.sol

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,15 @@ contract UFragmentsPolicy is Ownable {
9898
int256 public rebaseFunctionUpperPercentage;
9999
int256 public rebaseFunctionGrowth;
100100

101+
// NOTE: This was added with v1.5 release, on-chain state will not
102+
// have the history going back to epoch(0).
103+
// Mapping between epoch and the supply at that epoch.
104+
mapping(uint256 => uint256) public supplyHistory;
105+
106+
// Circuit breaker parameters which limit supply decline within the defined look back period.
107+
uint8 public epochLookback;
108+
int256 public tolerableDeclinePercentage;
109+
101110
int256 private constant ONE = int256(10**DECIMALS);
102111

103112
modifier onlyOrchestrator() {
@@ -139,6 +148,8 @@ contract UFragmentsPolicy is Ownable {
139148

140149
uint256 supplyAfterRebase = uFrags.rebase(epoch, supplyDelta);
141150
assert(supplyAfterRebase <= MAX_SUPPLY);
151+
152+
supplyHistory[epoch] = supplyAfterRebase;
142153
emit LogRebaseV2(epoch, exchangeRate, targetRate, supplyDelta);
143154
}
144155

@@ -206,7 +217,7 @@ contract UFragmentsPolicy is Ownable {
206217
* @param minRebaseTimeIntervalSec_ More than this much time must pass between rebase
207218
* operations, in seconds.
208219
* @param rebaseWindowOffsetSec_ The number of seconds from the beginning of
209-
the rebase interval, where the rebase window begins.
220+
* the rebase interval, where the rebase window begins.
210221
* @param rebaseWindowLengthSec_ The length of the rebase window in seconds.
211222
*/
212223
function setRebaseTimingParameters(
@@ -222,6 +233,22 @@ contract UFragmentsPolicy is Ownable {
222233
rebaseWindowLengthSec = rebaseWindowLengthSec_;
223234
}
224235

236+
/**
237+
* @notice Sets the parameters which control rebase circuit breaker.
238+
* @param epochLookback_ The number of rebase epochs to look back.
239+
* @param tolerableDeclinePercentage_ The maximum supply decline percentage which is allowed
240+
* within the defined look back period.
241+
*/
242+
function setRebaseCircuitBreakerParameters(
243+
uint8 epochLookback_,
244+
int256 tolerableDeclinePercentage_
245+
) external onlyOwner {
246+
require(tolerableDeclinePercentage_ > 0 && tolerableDeclinePercentage_ <= ONE);
247+
248+
epochLookback = epochLookback_;
249+
tolerableDeclinePercentage = tolerableDeclinePercentage_;
250+
}
251+
225252
/**
226253
* @notice A multi-chain AMPL interface method. The Ampleforth monetary policy contract
227254
* on the base-chain and XC-AmpleController contracts on the satellite-chains
@@ -257,6 +284,9 @@ contract UFragmentsPolicy is Ownable {
257284
lastRebaseTimestampSec = 0;
258285
epoch = 0;
259286

287+
epochLookback = 0;
288+
tolerableDeclinePercentage = ONE;
289+
260290
uFrags = uFrags_;
261291
}
262292

@@ -328,7 +358,8 @@ contract UFragmentsPolicy is Ownable {
328358
* @return Computes the total supply adjustment in response to the exchange rate
329359
* and the targetRate.
330360
*/
331-
function computeSupplyDelta(uint256 rate, uint256 targetRate) internal view returns (int256) {
361+
function computeSupplyDelta(uint256 rate, uint256 targetRate) public view returns (int256) {
362+
// No supply change if rate is within deviation threshold
332363
if (withinDeviationThreshold(rate, targetRate)) {
333364
return 0;
334365
}
@@ -340,7 +371,28 @@ contract UFragmentsPolicy is Ownable {
340371
rebaseFunctionUpperPercentage,
341372
rebaseFunctionGrowth
342373
);
343-
return uFrags.totalSupply().toInt256Safe().mul(rebasePercentage).div(ONE);
374+
375+
int256 currentSupply = uFrags.totalSupply().toInt256Safe(); // (or) supplyHistory[epoch]
376+
int256 newSupply = ONE.add(rebasePercentage).mul(currentSupply).div(ONE);
377+
378+
// When supply is decreasing:
379+
// We limit the supply delta, based on recent supply history.
380+
if (rebasePercentage < 0) {
381+
int256 maxSupplyInHistory = currentSupply;
382+
for (uint8 i = 1; i < epochLookback && epoch > i; i++) {
383+
int256 epochSupply = supplyHistory[epoch - i].toInt256Safe();
384+
if (epochSupply > maxSupplyInHistory) {
385+
maxSupplyInHistory = epochSupply;
386+
}
387+
}
388+
int256 allowedSupplyMinimum = maxSupplyInHistory
389+
.mul(ONE.sub(tolerableDeclinePercentage))
390+
.div(ONE);
391+
newSupply = (newSupply > allowedSupplyMinimum) ? newSupply : allowedSupplyMinimum;
392+
require(newSupply <= currentSupply);
393+
}
394+
395+
return newSupply.sub(currentSupply);
344396
}
345397

346398
/**
@@ -349,11 +401,7 @@ contract UFragmentsPolicy is Ownable {
349401
* @return If the rate is within the deviation threshold from the target rate, returns true.
350402
* Otherwise, returns false.
351403
*/
352-
function withinDeviationThreshold(uint256 rate, uint256 targetRate)
353-
internal
354-
view
355-
returns (bool)
356-
{
404+
function withinDeviationThreshold(uint256 rate, uint256 targetRate) public view returns (bool) {
357405
uint256 absoluteDeviationThreshold = targetRate.mul(deviationThreshold).div(10**DECIMALS);
358406

359407
return

contracts/mocks/MockUFragments.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ contract MockUFragments is Mock {
1818
int256[] memory intVals = new int256[](1);
1919
intVals[0] = supplyDelta;
2020
emit FunctionArguments(uintVals, intVals);
21-
return uint256(int256(_supply) + int256(supplyDelta));
21+
_supply = uint256(int256(_supply) + int256(supplyDelta));
22+
return _supply;
2223
}
2324

2425
function totalSupply() public view returns (uint256) {

test/unit/SafeMathInt.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ describe('SafeMathInt', () => {
167167
await expect(safeMathInt.abs(MIN_INT256)).to.be.reverted
168168
})
169169
})
170+
170171
describe('twoPower', function () {
171172
const decimals18 = ethers.BigNumber.from('1000000000000000000')
172173
const decimals10 = ethers.BigNumber.from('10000000000')

0 commit comments

Comments
 (0)