diff --git a/contracts/lbp/LBPManager.sol b/contracts/lbp/LBPManager.sol index 8e0da1b5..e226bb4c 100644 --- a/contracts/lbp/LBPManager.sol +++ b/contracts/lbp/LBPManager.sol @@ -25,6 +25,17 @@ import "../utils/interface/ILBP.sol"; */ // solhint-disable-next-line max-states-count contract LBPManager { + /* + Waiting:- current time < start time - 5 minutes, swapping is disabled + Started:- start time - 5 minutes <= current time < endTime, swapping enabled, can also be updated by admin + Ended:- current time > end time, swapping disableds + */ + enum TaskState { + Waiting, + Started, + Ended + } + TaskState public state; // Constants uint256 private constant HUNDRED_PERCENT = 1e18; // Used in calculating the fee. @@ -180,7 +191,6 @@ contract LBPManager { */ function initializeLBP(address _sender) external onlyAdmin { // solhint-disable-next-line reason-string - require(initialized == true, "LBPManager: LBPManager not initialized"); require(!poolFunded, "LBPManager: pool already funded"); poolFunded = true; @@ -192,7 +202,7 @@ contract LBPManager { startWeights, swapFeePercentage, address(this), - true // SwapEnabled is set to true at pool creation. + false // SwapEnabled is set to true at pool creation. ) ); @@ -234,6 +244,45 @@ contract LBPManager { vault.joinPool(lbp.getPoolId(), address(this), address(this), request); } + /** + * @dev start LBP before 5 minutes of start time + * @notice it can be invoked by anyone only once + */ + function startLbp() external { + uint256 startTime; + uint256 buffer = 5 minutes; + bool isSwapEnabled = lbp.getSwapEnabled(); + (startTime, , ) = lbp.getGradualWeightUpdateParams(); + require(state == TaskState.Waiting, "LBPManager: started"); + require( + block.timestamp > startTimeEndTime[0] - buffer, + "LBPManager: not the right time" + ); + state = TaskState.Started; + if (!isSwapEnabled) { + lbp.setSwapEnabled(true); + } + } + + /** + * @dev ends LBP once end time is reached + * @notice it can be invoked by anyone only once + */ + function endLbp() external { + uint256 endTime; + bool isSwapEnabled = lbp.getSwapEnabled(); + (, endTime, ) = lbp.getGradualWeightUpdateParams(); + require(state == TaskState.Started, "LBPManager: !started or ended"); + require( + block.timestamp >= startTimeEndTime[1], + "LBPManager: after >= end time" + ); + state = TaskState.Ended; + if (isSwapEnabled) { + lbp.setSwapEnabled(false); + } + } + /** * @dev Exit pool or remove liquidity from pool. * @param _receiver Address of the liquidity receiver, after exiting the LBP. @@ -302,6 +351,11 @@ contract LBPManager { * @param _swapEnabled Enables/disables swapping. */ function setSwapEnabled(bool _swapEnabled) external onlyAdmin { + require( + block.timestamp >= startTimeEndTime[0] && + block.timestamp < startTimeEndTime[1], + "LBPManager: only between start time and end time" + ); lbp.setSwapEnabled(_swapEnabled); } diff --git a/test/unit/lbp-manager.spec.js b/test/unit/lbp-manager.spec.js index 918cc302..0a8799ea 100644 --- a/test/unit/lbp-manager.spec.js +++ b/test/unit/lbp-manager.spec.js @@ -1003,6 +1003,78 @@ describe(">> Contract: LBPManager", () => { }); }); }); + describe("# startLbp", () => { + beforeEach(async () => { + const fundingAmount = { + initialBalances: INITIAL_BALANCES, + feePercentage: FEE_PERCENTAGE_ZERO, + }; + + const startTime = (await time.latest()).add( + await time.duration.minutes(10) + ); + const endTime = startTime.add(await time.duration.minutes(20)); + + const initializeLBPManagerParams = paramGenerator.initializeParams( + lbpFactoryInstance.address, + NAME, + SYMBOL, + tokenAddresses, + INITIAL_BALANCES, + START_WEIGHTS, + startTime.toString(), + endTime.toString(), + END_WEIGHTS, + fees, + beneficiary.address, + METADATA + ); + + const initialState = { + initializeLBPManagerParams, + fundingAmount, + poolFunded: true, + }; + ({ lbpManagerInstance, tokenInstances, amountToAddForFee } = + await setupInitialState(contractInstances, initialState)); + }); + it("$ reverts when invoked before condition currentTime > startTime - 5 minutes is met", async () => { + await expect( + lbpManagerInstance.connect(owner).startLbp() + ).to.be.revertedWith("LBPManager: not the right time"); + }); + it("$ starts lbp swapping", async () => { + await time.increase(await time.duration.minutes(5)); + expect(await lbpManagerInstance.getSwapEnabled()).to.be.false; + await expect(lbpManagerInstance.connect(owner).startLbp()).to.not.be + .reverted; + expect(await lbpManagerInstance.state()).to.be.equal(1); + expect(await lbpManagerInstance.getSwapEnabled()).to.be.true; + }); + it("$ can only be invoked once", async () => { + await time.increase(await time.duration.minutes(5)); + await expect(lbpManagerInstance.connect(owner).startLbp()).to.not.be + .reverted; + await expect( + lbpManagerInstance.connect(owner).startLbp() + ).to.be.revertedWith("LBPManager: started"); + }); + it("$ can be invoked by anyone", async () => { + await time.increase(await time.duration.minutes(5)); + await expect(lbpManagerInstance.connect(beneficiary).startLbp()).to.not.be + .reverted; + }); + it("$ updates state even if swap is already enabled", async () => { + await time.increase(await time.duration.minutes(10)); + await expect(lbpManagerInstance.connect(admin).setSwapEnabled(true)).to + .not.be.reverted; + expect(await lbpManagerInstance.getSwapEnabled()).to.be.true; + expect(await lbpManagerInstance.state()).to.be.equal(0); + await expect(lbpManagerInstance.connect(owner).startLbp()).to.not.be + .reverted; + expect(await lbpManagerInstance.state()).to.be.equal(1); + }); + }); describe("# setSwapEnabled", () => { beforeEach(async () => { const fundingAmount = { @@ -1010,6 +1082,26 @@ describe(">> Contract: LBPManager", () => { feePercentage: FEE_PERCENTAGE_ZERO, }; + const startTime = (await time.latest()).add( + await time.duration.minutes(10) + ); + const endTime = startTime.add(await time.duration.minutes(10)); + + const initializeLBPManagerParams = paramGenerator.initializeParams( + lbpFactoryInstance.address, + NAME, + SYMBOL, + tokenAddresses, + INITIAL_BALANCES, + START_WEIGHTS, + startTime.toString(), + endTime.toString(), + END_WEIGHTS, + fees, + beneficiary.address, + METADATA + ); + const initialState = { initializeLBPManagerParams, fundingAmount, @@ -1023,16 +1115,116 @@ describe(">> Contract: LBPManager", () => { lbpManagerInstance.connect(owner).setSwapEnabled(false) ).to.be.revertedWith("LBPManager: caller is not admin"); }); + it("» reverts when invoked before start time", async () => { + await expect( + lbpManagerInstance.connect(admin).setSwapEnabled(false) + ).to.be.revertedWith("LBPManager: only between start time and end time"); + }); it("» setSwapEnabled to false", async () => { + await time.increase(time.duration.minutes(10)); expect(await lbpManagerInstance.admin()).to.equal(admin.address); await lbpManagerInstance.connect(admin).setSwapEnabled(false); expect(await lbpManagerInstance.getSwapEnabled()).to.equal(false); }); it("» setSwapEnabled to true", async () => { + await time.increase(time.duration.minutes(10)); expect(await lbpManagerInstance.admin()).to.equal(admin.address); await lbpManagerInstance.connect(admin).setSwapEnabled(true); expect(await lbpManagerInstance.getSwapEnabled()).to.equal(true); }); + it("» reverts when invoked after end time", async () => { + await time.increase(time.duration.minutes(20)); + await expect( + lbpManagerInstance.connect(admin).setSwapEnabled(false) + ).to.be.revertedWith("LBPManager: only between start time and end time"); + }); + }); + describe("# endLbp", () => { + beforeEach(async () => { + const fundingAmount = { + initialBalances: INITIAL_BALANCES, + feePercentage: FEE_PERCENTAGE_ZERO, + }; + + const startTime = await time.latest(); + const endTime = startTime.add(await time.duration.minutes(20)); + + const initializeLBPManagerParams = paramGenerator.initializeParams( + lbpFactoryInstance.address, + NAME, + SYMBOL, + tokenAddresses, + INITIAL_BALANCES, + START_WEIGHTS, + startTime.toString(), + endTime.toString(), + END_WEIGHTS, + fees, + beneficiary.address, + METADATA + ); + + const initialState = { + initializeLBPManagerParams, + fundingAmount, + poolFunded: true, + }; + ({ lbpManagerInstance, tokenInstances, amountToAddForFee } = + await setupInitialState(contractInstances, initialState)); + }); + it("$ reverts when startLBP wasn't invoked", async () => { + await expect( + lbpManagerInstance.connect(owner).endLbp() + ).to.be.revertedWith("LBPManager: !started or ended"); + }); + it("$ reverts when current time < end time", async () => { + await expect(lbpManagerInstance.connect(owner).startLbp()).to.not.be + .reverted; + await expect( + lbpManagerInstance.connect(owner).endLbp() + ).to.be.revertedWith("LBPManager: after >= end time"); + }); + it("$ ends lbp swapping", async () => { + expect(await lbpManagerInstance.getSwapEnabled()).to.be.false; + await expect(lbpManagerInstance.connect(owner).startLbp()).to.not.be + .reverted; + expect(await lbpManagerInstance.state()).to.be.equal(1); + await time.increase(await time.duration.minutes(20)); + expect(await lbpManagerInstance.getSwapEnabled()).to.be.true; + await expect(lbpManagerInstance.connect(owner).endLbp()).to.not.be + .reverted; + expect(await lbpManagerInstance.getSwapEnabled()).to.be.false; + expect(await lbpManagerInstance.state()).to.be.equal(2); + }); + it("$ can only be invoked once", async () => { + await expect(lbpManagerInstance.connect(owner).startLbp()).to.not.be + .reverted; + await time.increase(await time.duration.minutes(20)); + await expect(lbpManagerInstance.connect(owner).endLbp()).to.not.be + .reverted; + await expect( + lbpManagerInstance.connect(owner).endLbp() + ).to.be.revertedWith("LBPManager: !started or ended"); + }); + it("$ can be invoked by anyone", async () => { + await expect(lbpManagerInstance.connect(owner).startLbp()).to.not.be + .reverted; + await time.increase(await time.duration.minutes(20)); + await expect(lbpManagerInstance.connect(beneficiary).endLbp()).to.not.be + .reverted; + }); + it("$ updates state even if swap is already disabled", async () => { + await expect(lbpManagerInstance.connect(owner).startLbp()).to.not.be + .reverted; + await expect(lbpManagerInstance.connect(admin).setSwapEnabled(false)).to + .not.be.reverted; + await time.increase(await time.duration.minutes(20)); + expect(await lbpManagerInstance.state()).to.be.equal(1); + expect(await lbpManagerInstance.getSwapEnabled()).to.be.false; + await expect(lbpManagerInstance.connect(owner).endLbp()).to.not.be + .reverted; + expect(await lbpManagerInstance.state()).to.be.equal(2); + }); }); describe("# withdraw liquidity from the pool", () => { describe("$ fails on call exit pool", () => {