@@ -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
0 commit comments