Skip to content
35 changes: 23 additions & 12 deletions contracts/UFragmentsPolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ contract UFragmentsPolicy is Ownable {
// Used in computation of (Upper-Lower)/(1-(Upper/Lower)/2^(Growth*delta))) + Lower
int256 public rebaseFunctionLowerPercentage;
int256 public rebaseFunctionUpperPercentage;
int256 public rebaseFunctionGrowth;
int256 public rebaseFunctionPositiveGrowth;
int256 public rebaseFunctionNegativeGrowth;

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

Expand Down Expand Up @@ -166,10 +167,6 @@ contract UFragmentsPolicy is Ownable {
orchestrator = orchestrator_;
}

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

function setRebaseFunctionLowerPercentage(int256 rebaseFunctionLowerPercentage_)
external
Expand All @@ -187,6 +184,16 @@ contract UFragmentsPolicy is Ownable {
rebaseFunctionUpperPercentage = rebaseFunctionUpperPercentage_;
}

function setRebaseFunctionPositiveGrowth(int256 rebaseFunctionPositiveGrowth_) external onlyOwner {
require(rebaseFunctionPositiveGrowth_ >= 0);
rebaseFunctionPositiveGrowth = rebaseFunctionPositiveGrowth_;
}

function setRebaseFunctionNegativeGrowth(int256 rebaseFunctionNegativeGrowth_) external onlyOwner {
require(rebaseFunctionNegativeGrowth_ >= 0);
rebaseFunctionNegativeGrowth = rebaseFunctionNegativeGrowth_;
}

/**
* @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 Down Expand Up @@ -246,7 +253,8 @@ contract UFragmentsPolicy is Ownable {
// deviationThreshold = 0.05e18 = 5e16
deviationThreshold = 25 * 10**(DECIMALS - 3);

rebaseFunctionGrowth = int256(45 * (10**DECIMALS));
rebaseFunctionPositiveGrowth = int256(45 * (10**DECIMALS)); // Positive growth
rebaseFunctionNegativeGrowth = int256(45 * (10**DECIMALS)); // Negative growth
rebaseFunctionUpperPercentage = int256(5 * (10**(DECIMALS - 2))); // 0.05
rebaseFunctionLowerPercentage = int256((-77) * int256(10**(DECIMALS - 3))); // -0.077

Expand Down Expand Up @@ -334,12 +342,15 @@ contract UFragmentsPolicy is Ownable {
}
int256 targetRateSigned = targetRate.toInt256Safe();
int256 normalizedRate = rate.toInt256Safe().mul(ONE).div(targetRateSigned);
int256 rebasePercentage = computeRebasePercentage(
normalizedRate,
rebaseFunctionLowerPercentage,
rebaseFunctionUpperPercentage,
rebaseFunctionGrowth
);

// Determine growth and bounds based on positive or negative rebase
int256 rebasePercentage;
if (normalizedRate >= ONE) {
rebasePercentage = computeRebasePercentage(normalizedRate, -rebaseFunctionUpperPercentage, rebaseFunctionUpperPercentage, rebaseFunctionPositiveGrowth);
} else {
rebasePercentage = computeRebasePercentage(normalizedRate, rebaseFunctionLowerPercentage, -rebaseFunctionLowerPercentage, rebaseFunctionNegativeGrowth);
}

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

Expand Down
248 changes: 164 additions & 84 deletions test/unit/UFragmentsPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,17 +396,32 @@ describe('UFragmentsPolicy:CurveParameters', async function () {
} = await waffle.loadFixture(mockedUpgradablePolicy))
})

describe('when rebaseFunctionGrowth is more than 0', async function () {
describe('when rebaseFunctionRebasePositiveGrowth is more than 0', async function () {
it('should setRebaseFunctionGrowth', async function () {
await uFragmentsPolicy.connect(deployer).setRebaseFunctionGrowth('42000000000000000000')
expect(await uFragmentsPolicy.rebaseFunctionGrowth()).to.eq('42000000000000000000')
await uFragmentsPolicy.connect(deployer).setRebaseFunctionPositiveGrowth('42000000000000000000')
expect(await uFragmentsPolicy.rebaseFunctionPositiveGrowth()).to.eq('42000000000000000000')
})
})

describe('when rebaseFunctionGrowth is less than 0', async function () {
describe('when rebaseFunctionRebasePositiveGrowth is less than 0', async function () {
it('should fail', async function () {
await expect(
uFragmentsPolicy.connect(deployer).setRebaseFunctionGrowth(-1),
uFragmentsPolicy.connect(deployer).setRebaseFunctionPositiveGrowth(-1),
).to.be.reverted
})
})

describe('when rebaseFunctionRebaseNegativeGrowth is more than 0', async function () {
it('should setRebaseFunctionGrowth', async function () {
await uFragmentsPolicy.connect(deployer).setRebaseFunctionNegativeGrowth('42000000000000000000')
expect(await uFragmentsPolicy.rebaseFunctionNegativeGrowth()).to.eq('42000000000000000000')
})
})

describe('when rebaseFunctionRebaseNegativeGrowth is less than 0', async function () {
it('should fail', async function () {
await expect(
uFragmentsPolicy.connect(deployer).setRebaseFunctionNegativeGrowth(-1),
).to.be.reverted
})
})
Expand Down Expand Up @@ -448,82 +463,6 @@ describe('UFragmentsPolicy:CurveParameters', async function () {
})
})

describe('UFragments:setRebaseFunctionGrowth:accessControl', function () {
before('setup UFragmentsPolicy contract', async () => {
;({
deployer,
user,
orchestrator,
mockUFragments,
mockMarketOracle,
mockCpiOracle,
uFragmentsPolicy,
} = await waffle.loadFixture(mockedUpgradablePolicy))
})

it('should be callable by owner', async function () {
await expect(uFragmentsPolicy.connect(deployer).setRebaseFunctionGrowth(1))
.to.not.be.reverted
})

it('should NOT be callable by non-owner', async function () {
await expect(uFragmentsPolicy.connect(user).setRebaseFunctionGrowth(1)).to
.be.reverted
})
})

describe('UFragments:setRebaseFunctionLowerPercentage:accessControl', function () {
before('setup UFragmentsPolicy contract', async () => {
;({
deployer,
user,
orchestrator,
mockUFragments,
mockMarketOracle,
mockCpiOracle,
uFragmentsPolicy,
} = await waffle.loadFixture(mockedUpgradablePolicy))
})

it('should be callable by owner', async function () {
await expect(
uFragmentsPolicy.connect(deployer).setRebaseFunctionLowerPercentage(-1),
).to.not.be.reverted
})

it('should NOT be callable by non-owner', async function () {
await expect(
uFragmentsPolicy.connect(user).setRebaseFunctionLowerPercentage(-1),
).to.be.reverted
})
})

describe('UFragments:setRebaseFunctionUpperPercentage:accessControl', function () {
before('setup UFragmentsPolicy contract', async () => {
;({
deployer,
user,
orchestrator,
mockUFragments,
mockMarketOracle,
mockCpiOracle,
uFragmentsPolicy,
} = await waffle.loadFixture(mockedUpgradablePolicy))
})

it('should be callable by owner', async function () {
await expect(
uFragmentsPolicy.connect(deployer).setRebaseFunctionUpperPercentage(1),
).to.not.be.reverted
})

it('should NOT be callable by non-owner', async function () {
await expect(
uFragmentsPolicy.connect(user).setRebaseFunctionUpperPercentage(1),
).to.be.reverted
})
})

describe('UFragmentsPolicy:setRebaseTimingParameters', async function () {
before('setup UFragmentsPolicy contract', async function () {
;({
Expand Down Expand Up @@ -1053,7 +992,7 @@ describe('UFragmentsPolicy:Rebase', async function () {
await mockExternalData(INITIAL_RATE_2X, INITIAL_TARGET_RATE, 1000)
await uFragmentsPolicy
.connect(deployer)
.setRebaseFunctionGrowth('100' + '000000000000000000')
.setRebaseFunctionPositiveGrowth('100' + '000000000000000000')
await increaseTime(60)
})

Expand All @@ -1073,7 +1012,7 @@ describe('UFragmentsPolicy:Rebase', async function () {
await mockExternalData(0, INITIAL_TARGET_RATE, 1000)
await uFragmentsPolicy
.connect(deployer)
.setRebaseFunctionGrowth('75' + '000000000000000000')
.setRebaseFunctionNegativeGrowth('75' + '000000000000000000')
await increaseTime(60)
})

Expand All @@ -1088,12 +1027,57 @@ describe('UFragmentsPolicy:Rebase', async function () {
})
})

describe('when normalizedRate is greater than ONE (positive rebase)', function () {
beforeEach(async function () {
await mockExternalData(
INITIAL_RATE_30P_MORE,
INITIAL_TARGET_RATE,
1000,
)
await uFragmentsPolicy
.connect(deployer)
.setRebaseFunctionPositiveGrowth('25' + '000000000000000000') // Positive growth
await uFragmentsPolicy
.connect(deployer)
.setRebaseFunctionUpperPercentage('10' + '0000000000000000')
await increaseTime(60)
})

it('should compute positive rebase percentage correctly', async function () {
const rebaseEvent = await parseRebaseEvent(
uFragmentsPolicy.connect(orchestrator).rebase(),
)
expect(rebaseEvent.requestedSupplyAdjustment).to.eq(98)
})
})

describe('when normalizedRate is less than ONE (negative rebase)', function () {
beforeEach(async function () {
await mockExternalData(
INITIAL_RATE_30P_LESS,
INITIAL_TARGET_RATE,
1000,
)
await uFragmentsPolicy
.connect(deployer)
.setRebaseFunctionNegativeGrowth('30' + '000000000000000000') // Negative growth
await increaseTime(60)
})

it('should compute negative rebase percentage correctly', async function () {
const rebaseEvent = await parseRebaseEvent(
uFragmentsPolicy.connect(orchestrator).rebase(),
)
expect(rebaseEvent.requestedSupplyAdjustment).to.eq(-76);
})
})

describe('exponent less than -100', function () {
before(async function () {
await mockExternalData(0, INITIAL_TARGET_RATE, 1000)
await uFragmentsPolicy
.connect(deployer)
.setRebaseFunctionGrowth('150' + '000000000000000000')
.setRebaseFunctionNegativeGrowth('150' + '000000000000000000')
await increaseTime(60)
})

Expand Down Expand Up @@ -1328,3 +1312,99 @@ describe('UFragmentsPolicy:Rebase', async function () {
})
})
})

describe('UFragmentsPolicy:CurveParameters', async function () {
before('setup UFragmentsPolicy contract', async function () {
;({
deployer,
user,
orchestrator,
mockUFragments,
mockMarketOracle,
mockCpiOracle,
uFragmentsPolicy,
} = await waffle.loadFixture(mockedUpgradablePolicy))
})

describe('when rebaseFunctionPositiveGrowth is more than 0', async function () {
it('should setRebaseFunctionPositiveGrowth', async function () {
await uFragmentsPolicy.connect(deployer).setRebaseFunctionPositiveGrowth('42000000000000000000')
expect(await uFragmentsPolicy.rebaseFunctionPositiveGrowth()).to.eq('42000000000000000000')
})
})

describe('when rebaseFunctionNegativeGrowth is more than 0', async function () {
it('should setRebaseFunctionNegativeGrowth', async function () {
await uFragmentsPolicy.connect(deployer).setRebaseFunctionNegativeGrowth('42000000000000000000')
expect(await uFragmentsPolicy.rebaseFunctionNegativeGrowth()).to.eq('42000000000000000000')
})
})

describe('when rebaseFunctionPositiveGrowth is less than 0', async function () {
it('should fail', async function () {
await expect(
uFragmentsPolicy.connect(deployer).setRebaseFunctionPositiveGrowth(-1),
).to.be.reverted
})
})

describe('when rebaseFunctionNegativeGrowth is less than 0', async function () {
it('should fail', async function () {
await expect(
uFragmentsPolicy.connect(deployer).setRebaseFunctionNegativeGrowth(-1),
).to.be.reverted
})
})
})

describe('UFragments:setRebaseFunctionPositiveGrowth:accessControl', function () {
before('setup UFragmentsPolicy contract', async () => {
;({
deployer,
user,
orchestrator,
mockUFragments,
mockMarketOracle,
mockCpiOracle,
uFragmentsPolicy,
} = await waffle.loadFixture(mockedUpgradablePolicy))
})

it('should be callable by owner', async function () {
await expect(
uFragmentsPolicy.connect(deployer).setRebaseFunctionPositiveGrowth(1),
).to.not.be.reverted
})

it('should NOT be callable by non-owner', async function () {
await expect(
uFragmentsPolicy.connect(user).setRebaseFunctionPositiveGrowth(1),
).to.be.reverted
})
})

describe('UFragments:setRebaseFunctionNegativeGrowth:accessControl', function () {
before('setup UFragmentsPolicy contract', async () => {
;({
deployer,
user,
orchestrator,
mockUFragments,
mockMarketOracle,
mockCpiOracle,
uFragmentsPolicy,
} = await waffle.loadFixture(mockedUpgradablePolicy))
})

it('should be callable by owner', async function () {
await expect(
uFragmentsPolicy.connect(deployer).setRebaseFunctionNegativeGrowth(1),
).to.not.be.reverted
})

it('should NOT be callable by non-owner', async function () {
await expect(
uFragmentsPolicy.connect(user).setRebaseFunctionNegativeGrowth(1),
).to.be.reverted
})
})