Skip to content
Merged
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
125 changes: 92 additions & 33 deletions contracts/UFragmentsPolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ interface IOracle {
/**
* @title uFragments Monetary Supply Policy
* @dev This is an implementation of the uFragments Ideal Money protocol.
* uFragments operates symmetrically on expansion and contraction. It will both split and
* combine coins to maintain a stable unit price.
*
* This component regulates the token supply of the uFragments ERC20 token in response to
* market oracles.
Expand Down Expand Up @@ -56,10 +54,7 @@ contract UFragmentsPolicy is Ownable {
// DECIMALS Fixed point number.
uint256 public deviationThreshold;

// The rebase lag parameter, used to dampen the applied supply adjustment by 1 / rebaseLag
// Check setRebaseLag comments for more details.
// Natural number, no decimal places.
uint256 public rebaseLag;
uint256 private rebaseLagDeprecated;

// More than this much time must pass between rebase operations.
uint256 public minRebaseTimeIntervalSec;
Expand Down Expand Up @@ -88,17 +83,23 @@ contract UFragmentsPolicy is Ownable {
// This module orchestrates the rebase execution and downstream notification.
address public orchestrator;

// DECIMALS decimal fixed point numbers.
// Used in computation of (Upper-Lower)/(1-(Upper/Lower)/2^(Growth*delta))) + Lower
int256 public rebaseFunctionLowerPercentage;
int256 public rebaseFunctionUpperPercentage;
int256 public rebaseFunctionGrowth;

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

modifier onlyOrchestrator() {
require(msg.sender == orchestrator);
_;
}

/**
* @notice Initiates a new rebase operation, provided the minimum time period has elapsed.
*
* @dev The supply adjustment equals (_totalSupply * DeviationFromTargetRate) / rebaseLag
* Where DeviationFromTargetRate is (MarketOracleRate - targetRate) / targetRate
* and targetRate is CpiOracleRate / baseCpi
* @dev Changes supply with percentage of:
* (Upper-Lower)/(1-(Upper/Lower)/2^(Growth*NormalizedPriceDelta))) + Lower
*/
function rebase() external onlyOrchestrator {
require(inRebaseWindow());
Expand Down Expand Up @@ -132,9 +133,6 @@ contract UFragmentsPolicy is Ownable {

int256 supplyDelta = computeSupplyDelta(exchangeRate, targetRate);

// Apply the Dampening factor.
supplyDelta = supplyDelta.div(rebaseLag.toInt256Safe());

if (supplyDelta > 0 && uFrags.totalSupply().add(uint256(supplyDelta)) > MAX_SUPPLY) {
supplyDelta = (MAX_SUPPLY.sub(uFrags.totalSupply())).toInt256Safe();
}
Expand Down Expand Up @@ -168,6 +166,27 @@ contract UFragmentsPolicy is Ownable {
orchestrator = orchestrator_;
}

function setRebaseFunctionGrowth(int256 rebaseFunctionGrowth_) external onlyOwner {
require(rebaseFunctionGrowth_ >= 0);
rebaseFunctionGrowth = rebaseFunctionGrowth_;
}

function setRebaseFunctionLowerPercentage(int256 rebaseFunctionLowerPercentage_)
external
onlyOwner
{
require(rebaseFunctionLowerPercentage_ <= 0);
rebaseFunctionLowerPercentage = rebaseFunctionLowerPercentage_;
}

function setRebaseFunctionUpperPercentage(int256 rebaseFunctionUpperPercentage_)
external
onlyOwner
{
require(rebaseFunctionUpperPercentage_ >= 0);
rebaseFunctionUpperPercentage = rebaseFunctionUpperPercentage_;
}

/**
* @notice Sets the deviation threshold fraction. If the exchange rate given by the market
* oracle is within this fractional distance from the targetRate, then no supply
Expand All @@ -178,19 +197,6 @@ contract UFragmentsPolicy is Ownable {
deviationThreshold = deviationThreshold_;
}

/**
* @notice Sets the rebase lag parameter.
It is used to dampen the applied supply adjustment by 1 / rebaseLag
If the rebase lag R, equals 1, the smallest value for R, then the full supply
correction is applied on each rebase cycle.
If it is greater than 1, then a correction of 1/R of is applied on each rebase.
* @param rebaseLag_ The new rebase lag parameter.
*/
function setRebaseLag(uint256 rebaseLag_) external onlyOwner {
require(rebaseLag_ > 0);
rebaseLag = rebaseLag_;
}

/**
* @notice Sets the parameters which control the timing and frequency of
* rebase operations.
Expand Down Expand Up @@ -244,7 +250,10 @@ contract UFragmentsPolicy is Ownable {
// deviationThreshold = 0.05e18 = 5e16
deviationThreshold = 5 * 10**(DECIMALS - 2);

rebaseLag = 30;
rebaseFunctionGrowth = int256(3 * (10**DECIMALS));
rebaseFunctionUpperPercentage = int256(10 * (10**(DECIMALS - 2))); // 0.11
rebaseFunctionLowerPercentage = int256((-10) * int256(10**(DECIMALS - 2))); // -0.11

minRebaseTimeIntervalSec = 1 days;
rebaseWindowOffsetSec = 72000; // 8PM UTC
rebaseWindowLengthSec = 15 minutes;
Expand All @@ -265,6 +274,46 @@ contract UFragmentsPolicy is Ownable {
(rebaseWindowOffsetSec.add(rebaseWindowLengthSec)));
}

/**
* Computes the percentage of supply to be added or removed:
* Using the function in https://github.com/ampleforth/AIPs/blob/master/AIPs/aip-5.md
* @param normalizedRate value of rate/targetRate in DECIMALS decimal fixed point number
* @return The percentage of supply to be added or removed.
*/
function computeRebasePercentage(
int256 normalizedRate,
int256 lower,
int256 upper,
int256 growth
) public pure returns (int256) {
int256 delta;

delta = (normalizedRate.sub(ONE));

// Compute: (Upper-Lower)/(1-(Upper/Lower)/2^(Growth*delta))) + Lower

int256 exponent = growth.mul(delta).div(ONE);
// Cap exponent to guarantee it is not too big for twoPower
if (exponent > ONE.mul(100)) {
exponent = ONE.mul(100);
}
if (exponent < ONE.mul(-100)) {
exponent = ONE.mul(-100);
}

int256 pow = SafeMathInt.twoPower(exponent, ONE); // 2^(Growth*Delta)
if (pow == 0) {
return lower;
}
int256 numerator = upper.sub(lower); //(Upper-Lower)
int256 intermediate = upper.mul(ONE).div(lower);
intermediate = intermediate.mul(ONE).div(pow);
int256 denominator = ONE.sub(intermediate); // (1-(Upper/Lower)/2^(Growth*delta)))

int256 rebasePercentage = (numerator.mul(ONE).div(denominator)).add(lower);
return rebasePercentage;
}

/**
* @return Computes the total supply adjustment in response to the exchange rate
* and the targetRate.
Expand All @@ -273,13 +322,16 @@ contract UFragmentsPolicy is Ownable {
if (withinDeviationThreshold(rate, targetRate)) {
return 0;
}

// supplyDelta = totalSupply * (rate - targetRate) / targetRate
int256 targetRateSigned = targetRate.toInt256Safe();
return
uFrags.totalSupply().toInt256Safe().mul(rate.toInt256Safe().sub(targetRateSigned)).div(
targetRateSigned
);
int256 normalizedRate = rate.toInt256Safe().mul(ONE).div(targetRateSigned);
int256 rebasePercentage = computeRebasePercentage(
normalizedRate,
rebaseFunctionLowerPercentage,
rebaseFunctionUpperPercentage,
rebaseFunctionGrowth
);

return uFrags.totalSupply().toInt256Safe().mul(rebasePercentage).div(ONE);
}

/**
Expand All @@ -299,4 +351,11 @@ contract UFragmentsPolicy is Ownable {
(rate >= targetRate && rate.sub(targetRate) < absoluteDeviationThreshold) ||
(rate < targetRate && targetRate.sub(rate) < absoluteDeviationThreshold);
}

/**
* To maintain abi backward compatibility
*/
function rebaseLag() public pure returns (uint256) {
return 1;
}
}
40 changes: 40 additions & 0 deletions contracts/lib/SafeMathInt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,44 @@ library SafeMathInt {
require(a != MIN_INT256);
return a < 0 ? -a : a;
}

/**
* @dev Computes 2^exp with limited precision where -100 <= exp <= 100 * one
* @param one 1.0 represented in the same fixed point number format as exp
* @param exp The power to raise 2 to -100 <= exp <= 100 * one
* @return 2^exp represented with same number of decimals after the point as one
*/
function twoPower(int256 exp, int256 one) internal pure returns (int256) {
bool reciprocal = false;
if (exp < 0) {
reciprocal = true;
exp = abs(exp);
}

// Precomputed values for 2^(1/2^i) in 18 decimals fixed point numbers
int256[5] memory ks = [
int256(1414213562373095049),
1189207115002721067,
1090507732665257659,
1044273782427413840,
1021897148654116678
];
int256 whole = div(exp, one);
require(whole <= 100);
int256 result = mul(int256(uint256(1) << uint256(whole)), one);
int256 remaining = sub(exp, mul(whole, one));

int256 current = div(one, 2);
for (uint256 i = 0; i < 5; i++) {
if (remaining >= current) {
remaining = sub(remaining, current);
result = div(mul(result, ks[i]), 10**18); // 10**18 to match hardcoded ks values
}
current = div(current, 2);
}
if (reciprocal) {
result = div(mul(one, one), result);
}
return result;
}
}
6 changes: 6 additions & 0 deletions contracts/mocks/SafeMathIntMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ contract SafeMathIntMock is Mock {
emit ReturnValueInt256(result);
return result;
}

function twoPower(int256 e, int256 one) external returns (int256) {
int256 result = SafeMathInt.twoPower(e, one);
emit ReturnValueInt256(result);
return result;
}
}
85 changes: 85 additions & 0 deletions test/unit/SafeMathInt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,89 @@ 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')
it('2^0', async function () {
const e = ethers.BigNumber.from(0)
const one = ethers.BigNumber.from(1).mul(decimals18)
await expect(safeMathInt.twoPower(e, one))
.to.emit(safeMathInt, 'ReturnValueInt256')
.withArgs(one)
})
it('2^1', async function () {
const e = ethers.BigNumber.from(1).mul(decimals18)
const one = ethers.BigNumber.from(1).mul(decimals18)
const result = ethers.BigNumber.from(2).mul(decimals18)
;(await expect(safeMathInt.twoPower(e, one))).to
.emit(safeMathInt, 'ReturnValueInt256')
.withArgs(result)
})
it('2^30', async function () {
const e = ethers.BigNumber.from(30).mul(decimals18)
const one = ethers.BigNumber.from(1).mul(decimals18)
const result = ethers.BigNumber.from(2 ** 30).mul(decimals18)
;(await expect(safeMathInt.twoPower(e, one))).to
.emit(safeMathInt, 'ReturnValueInt256')
.withArgs(result)
})
it('2^2.5', async function () {
const e = ethers.BigNumber.from('25000000000')
const one = ethers.BigNumber.from(1).mul(decimals10)
const result = ethers.BigNumber.from('56568542494')
;(await expect(safeMathInt.twoPower(e, one))).to
.emit(safeMathInt, 'ReturnValueInt256')
.withArgs(result)
})
it('2^2.25', async function () {
const e = ethers.BigNumber.from('22500000000')
const one = ethers.BigNumber.from(1).mul(decimals10)
const result = ethers.BigNumber.from('47568284600')
;(await expect(safeMathInt.twoPower(e, one))).to
.emit(safeMathInt, 'ReturnValueInt256')
.withArgs(result)
})
it('2^-2.25', async function () {
const e = ethers.BigNumber.from('-22500000000')
const one = ethers.BigNumber.from(1).mul(decimals10)
const result = ethers.BigNumber.from('2102241038')
;(await expect(safeMathInt.twoPower(e, one))).to
.emit(safeMathInt, 'ReturnValueInt256')
.withArgs(result)
})
it('2^-0.6', async function () {
const e = ethers.BigNumber.from('-6000000000')
const one = ethers.BigNumber.from(1).mul(decimals10)
const result = ethers.BigNumber.from('6626183216')
;(await expect(safeMathInt.twoPower(e, one))).to
.emit(safeMathInt, 'ReturnValueInt256')
.withArgs(result)
})
it('2^2.96875', async function () {
const e = ethers.BigNumber.from('29687500000')
const one = ethers.BigNumber.from(1).mul(decimals10)
const result = ethers.BigNumber.from('78285764964')
;(await expect(safeMathInt.twoPower(e, one))).to
.emit(safeMathInt, 'ReturnValueInt256')
.withArgs(result)
})
it('2^2.99', async function () {
const e = ethers.BigNumber.from('29900000000')
const one = ethers.BigNumber.from(1).mul(decimals10)
const result = ethers.BigNumber.from('78285764964')
;(await expect(safeMathInt.twoPower(e, one))).to
.emit(safeMathInt, 'ReturnValueInt256')
.withArgs(result)
})
it('should fail on too small exponents', async function () {
const e = ethers.BigNumber.from('-1011000000000')
const one = ethers.BigNumber.from(1).mul(decimals10)
await expect(safeMathInt.twoPower(e, one)).to.be.reverted
})
it('should fail on too large exponents', async function () {
const e = ethers.BigNumber.from('1011000000000')
const one = ethers.BigNumber.from(1).mul(decimals10)
await expect(safeMathInt.twoPower(e, one)).to.be.reverted
})
})
})
Loading