diff --git a/contracts/UFragmentsPolicy.sol b/contracts/UFragmentsPolicy.sol index cae68a39..7af894b5 100644 --- a/contracts/UFragmentsPolicy.sol +++ b/contracts/UFragmentsPolicy.sol @@ -94,9 +94,13 @@ contract UFragmentsPolicy is Ownable { // 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; + /// @custom:oz-renamed-from rebaseFunctionLowerPercentage + int256 public rebaseFunctionNegativePercentageLimit; + /// @custom:oz-renamed-from rebaseFunctionUpperPercentage + int256 public rebaseFunctionPositivePercentageLimit; + /// @custom:oz-renamed-from rebaseFunctionGrowth + int256 public rebaseFunctionPositiveGrowth; + int256 public rebaseFunctionNegativeGrowth; int256 private constant ONE = int256(10**DECIMALS); @@ -166,25 +170,30 @@ contract UFragmentsPolicy is Ownable { orchestrator = orchestrator_; } - function setRebaseFunctionGrowth(int256 rebaseFunctionGrowth_) external onlyOwner { - require(rebaseFunctionGrowth_ >= 0); - rebaseFunctionGrowth = rebaseFunctionGrowth_; - } - - function setRebaseFunctionLowerPercentage(int256 rebaseFunctionLowerPercentage_) + function setRebaseFunctionNegativePercentageLimit(int256 rebaseFunctionNegativePercentageLimit_) external onlyOwner { - require(rebaseFunctionLowerPercentage_ <= 0); - rebaseFunctionLowerPercentage = rebaseFunctionLowerPercentage_; + require(rebaseFunctionNegativePercentageLimit_ <= 0); + rebaseFunctionNegativePercentageLimit = rebaseFunctionNegativePercentageLimit_; } - function setRebaseFunctionUpperPercentage(int256 rebaseFunctionUpperPercentage_) + function setRebaseFunctionPositivePercentageLimit(int256 rebaseFunctionPositivePercentageLimit_) external onlyOwner { - require(rebaseFunctionUpperPercentage_ >= 0); - rebaseFunctionUpperPercentage = rebaseFunctionUpperPercentage_; + require(rebaseFunctionPositivePercentageLimit_ >= 0); + rebaseFunctionPositivePercentageLimit = rebaseFunctionPositivePercentageLimit_; + } + + function setRebaseFunctionPositiveGrowth(int256 rebaseFunctionPositiveGrowth_) external onlyOwner { + require(rebaseFunctionPositiveGrowth_ >= 0); + rebaseFunctionPositiveGrowth = rebaseFunctionPositiveGrowth_; + } + + function setRebaseFunctionNegativeGrowth(int256 rebaseFunctionNegativeGrowth_) external onlyOwner { + require(rebaseFunctionNegativeGrowth_ >= 0); + rebaseFunctionNegativeGrowth = rebaseFunctionNegativeGrowth_; } /** @@ -246,9 +255,10 @@ contract UFragmentsPolicy is Ownable { // deviationThreshold = 0.05e18 = 5e16 deviationThreshold = 25 * 10**(DECIMALS - 3); - rebaseFunctionGrowth = int256(45 * (10**DECIMALS)); - rebaseFunctionUpperPercentage = int256(5 * (10**(DECIMALS - 2))); // 0.05 - rebaseFunctionLowerPercentage = int256((-77) * int256(10**(DECIMALS - 3))); // -0.077 + rebaseFunctionPositiveGrowth = int256(45 * (10**DECIMALS)); // Positive growth + rebaseFunctionNegativeGrowth = int256(45 * (10**DECIMALS)); // Negative growth + rebaseFunctionPositivePercentageLimit = int256(5 * (10**(DECIMALS - 2))); // 0.05 + rebaseFunctionNegativePercentageLimit = int256((-77) * int256(10**(DECIMALS - 3))); // -0.077 minRebaseTimeIntervalSec = 1 days; rebaseWindowOffsetSec = 7200; // 2AM UTC @@ -334,12 +344,25 @@ 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, + -rebaseFunctionPositivePercentageLimit, + rebaseFunctionPositivePercentageLimit, + rebaseFunctionPositiveGrowth + ); + } else { + rebasePercentage = computeRebasePercentage( + normalizedRate, + rebaseFunctionNegativePercentageLimit, + -rebaseFunctionNegativePercentageLimit, + rebaseFunctionNegativeGrowth + ); + } + return uFrags.totalSupply().toInt256Safe().mul(rebasePercentage).div(ONE); } diff --git a/scripts/deploy.ts b/scripts/deploy.ts index ed131837..0ea1469f 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -33,9 +33,10 @@ task('deploy:amplforce:testnet', 'Deploy ampleforth contract suite for testnet') // Policy const DEVIATION_TRESHOLD = utils.parseUnits('0.002', 18) // 0.002% (ie) 0.05/24) - const LOWER = utils.parseUnits('-0.005', 18) - const UPPER = utils.parseUnits('0.005', 18) - const GROWTH = utils.parseUnits('3', 18) + const LOWER = utils.parseUnits('-0.005', 18) // rebaseFunctionNegativePercentageLimit + const UPPER = utils.parseUnits('0.005', 18) // rebaseFunctionPositivePercentageLimit + const POSITIVE_GROWTH = utils.parseUnits('31', 18) // rebaseFunctionPositiveGrowth; + const NEGATIVE_GROWTH = utils.parseUnits('41', 18) // rebaseFunctionNegativeGrowth; const MIN_REBASE_INTERVAL = 1200 // 20 mins const REBASE_WINDOW_OFFSET = 0 const REBASE_WINDOW_LEN = 2400 // 40 mins @@ -119,9 +120,10 @@ task('deploy:amplforce:testnet', 'Deploy ampleforth contract suite for testnet') // configure parameters await waitFor(policy.setDeviationThreshold(DEVIATION_TRESHOLD)) - await waitFor(policy.setRebaseFunctionGrowth(GROWTH)) - await waitFor(policy.setRebaseFunctionLowerPercentage(LOWER)) - await waitFor(policy.setRebaseFunctionUpperPercentage(UPPER)) + await waitFor(policy.setRebaseFunctionPositiveGrowth(POSITIVE_GROWTH)) + await waitFor(policy.setRebaseFunctionNegativeGrowth(NEGATIVE_GROWTH)) + await waitFor(policy.setRebaseFunctionNegativePercentageLimit(LOWER)) + await waitFor(policy.setRebaseFunctionPositivePercentageLimit(UPPER)) await waitFor( policy.setRebaseTimingParameters( MIN_REBASE_INTERVAL, diff --git a/test/unit/UFragmentsPolicy.ts b/test/unit/UFragmentsPolicy.ts index cf8ca894..8147baeb 100644 --- a/test/unit/UFragmentsPolicy.ts +++ b/test/unit/UFragmentsPolicy.ts @@ -396,131 +396,70 @@ 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 rebaseFunctionLowerPercentage is more than 0', async function () { + 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) - .setRebaseFunctionLowerPercentage(1000), + uFragmentsPolicy.connect(deployer).setRebaseFunctionNegativeGrowth(-1), ).to.be.reverted }) }) - describe('when rebaseFunctionLowerPercentage is less than 0', async function () { - it('should setRebaseFunctionLowerPercentage', async function () { + describe('when rebaseFunctionNegativePercentageLimit is less than 0', async function () { + it('should setRebaseFunctionNegativePercentageLimit', async function () { await uFragmentsPolicy .connect(deployer) - .setRebaseFunctionLowerPercentage(-1) - expect(await uFragmentsPolicy.rebaseFunctionLowerPercentage()).to.eq(-1) + .setRebaseFunctionNegativePercentageLimit(-1) + expect(await uFragmentsPolicy.rebaseFunctionNegativePercentageLimit()).to.eq(-1) }) }) - describe('when rebaseFunctionUpperPercentage is less than 0', async function () { + describe('when rebaseFunctionNegativePercentageLimit is more than 0', async function () { it('should fail', async function () { await expect( - uFragmentsPolicy.connect(deployer).setRebaseFunctionUpperPercentage(-1), + uFragmentsPolicy + .connect(deployer) + .setRebaseFunctionNegativePercentageLimit(1000), ).to.be.reverted }) }) - describe('when rebaseFunctionUpperPercentage is more than 0', async function () { - it('should setRebaseFunctionUpperPercentage', async function () { - await uFragmentsPolicy - .connect(deployer) - .setRebaseFunctionUpperPercentage(1000) - expect(await uFragmentsPolicy.rebaseFunctionUpperPercentage()).to.eq(1000) + describe('when rebaseFunctionPositivePercentageLimit is less than 0', async function () { + it('should fail', async function () { + await expect( + uFragmentsPolicy.connect(deployer).setRebaseFunctionPositivePercentageLimit(-1), + ).to.be.reverted }) }) -}) - -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('when rebaseFunctionPositivePercentageLimit is more than 0', async function () { + it('should setRebaseFunctionPositivePercentageLimit', async function () { + await uFragmentsPolicy + .connect(deployer) + .setRebaseFunctionPositivePercentageLimit(1000) + expect(await uFragmentsPolicy.rebaseFunctionPositivePercentageLimit()).to.eq(1000) + }) }) }) @@ -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) }) @@ -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) }) @@ -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) + .setRebaseFunctionPositivePercentageLimit('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) }) @@ -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 + }) +})