From f1a08792b3dba111c0d86f9cd6aa881a5848059c Mon Sep 17 00:00:00 2001 From: Ivan <39461389+AlcibiadesCleinias@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:47:48 +0100 Subject: [PATCH] feat: add veto power [CHAIN-264] (#15) * add deploy * grant role flow diagram * add multisig into config * add multisig into config * * add proposal diagram with TODO * add veto power * upd example config with fluence multisig entity * add role grant on deploy script * add test on feature * typing * enable tests * add check that veto does not block governor * add check that veto does not block bump scheme * bump version of mythx * fix mythc * add emergency withdraw for multisig (#16) * add role test to Deploy.spec.ts --- .github/workflows/audit.yaml | 2 +- contracts/README.md | 144 +++++++++++++++++- contracts/config.example.yaml | 2 + contracts/contracts/DevRewardDistributor.sol | 34 ++++- contracts/contracts/Governor.sol | 2 +- contracts/deploy/0003_DevRewardDistributor.ts | 1 + contracts/deploy/0007_Governor.ts | 3 +- contracts/hardhat.config.ts | 2 +- contracts/test/Deploy.spec.ts | 15 +- contracts/test/DevRewardDestributor.spec.ts | 48 ++++-- contracts/test/Governor.spec.ts | 131 ++++++++++++++-- contracts/test/Vesting.spec.ts | 5 +- contracts/test/VestingWithVoting.spec.ts | 5 +- contracts/test/e2e/E2E_LPController.spec.ts | 16 +- contracts/utils/config.ts | 14 +- 15 files changed, 375 insertions(+), 49 deletions(-) diff --git a/.github/workflows/audit.yaml b/.github/workflows/audit.yaml index 0ac47f5..f19d9f9 100644 --- a/.github/workflows/audit.yaml +++ b/.github/workflows/audit.yaml @@ -35,5 +35,5 @@ jobs: - run: npm i working-directory: contracts - - run: mythx --config .mythx.yml --api-key ${{ secrets.MYTHX_API_KEY }} analyze --remap-import "@openzeppelin=$(pwd)/node_modules/@openzeppelin" --remap-import "@uniswap=$(pwd)/node_modules/@uniswap" + - run: mythx --config .mythx.yml --api-key ${{ secrets.MYTHX_API_KEY }} analyze --solc-version 0.8.20 --remap-import "@openzeppelin=$(pwd)/node_modules/@openzeppelin" --remap-import "@uniswap=$(pwd)/node_modules/@uniswap" working-directory: contracts diff --git a/contracts/README.md b/contracts/README.md index 0fcfc81..26d1b16 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -1,6 +1,148 @@ # Fluence DAO Contracts -This is solidity contracts for Fluence DAO. +This is solidity contracts for Fluence DAO. For DAO it uses OpenZeppelin contracts with modifications. + +## Feature +- timelock DAO governor based on **TimelockControllerUpgradeable** and **GovernorUpgradeable** (OpenZeppelin contracts). +- everyone could **execute a proposal** +- **veto power**: Finally CANCELLER_ROLE is granted to `Governor` contract & `Fluence Multisig`. Veto could be applied after proposal is queued to execute (before it is executed). +- LBP Vesting with moving funds after specified time to Uniswap (TODO: write more precisely) +- 3 Vesting Contract (TODO: write more precisely) +- [0003_DevRewardDistributor.ts](deploy%2F0003_DevRewardDistributor.ts) (TODO: write more precisely) +- **FluenceToken** based on **ERC20VotesUpgradeable** (OpenZeppelin) for the DAO purposes. + +## DAO Proposal Flow + +TODO: add other options like: threshold not reached, etc. +```mermaid +sequenceDiagram + actor user + box Fluence Contracts + participant Governor + participant Executor as Executor (TimelockController) + end + participant FluenceMultisig + + user -->> Governor: propose (..., data, description) + + loop wait voting deplay + Governor ->> Governor: wait for votingDelay() + end + + user -->> Governor: castVote (proposalId, {Against, For, Abstain}) + + Note over user,Governor: ...other votes... + + loop wait voting period + Governor ->> Governor: wait for votingPeriod() + end + + user -->>+ Governor: queue (..., data, description) + Governor ->>- Executor: scheduleBatch(..., data, salt [based on description]) + + opt veto + FluenceMultisig -->> Executor: cancel (..., data, description) + end + + loop wait minDelay + Executor ->> Executor: wait for minDelay() + end + + alt no veto + user -->>+ Governor: execute(..., data, description) + Governor ->>- Executor: check status + end + + alt veto + user -->>+ Governor: execute(..., data, description) + Governor -x- Executor: check status: cancelled + end +``` + +## Deploy & Role Delegation Flow +Deploy Flow according to [deploy scripts](deploy). + +```mermaid +sequenceDiagram + + actor deployer + box Fluence Contracts + participant FluenceToken + participant LPController + participant Uniswap + participant DevRewardDistributor + participant FluenceVesting + participant InvestorsVesting + participant TeamVesting as Vesting with Voting + participant Executor as Executor (TimelockController) + participant Governor + end + participant FluenceMultisig + + alt 0000_FluenceToken.ts + deployer ->> FluenceToken: deploy (totalSupply) + Note over FluenceToken: Owner: deployer + FluenceToken -->> deployer: mint (totalSupply) + end + + alt 0001_Executor.ts + deployer ->> Executor: deploy (minDelay) + Note over Executor: Admin Role: deployer, Executor Role: [0x0] + end + + alt 0002_LPController.ts + deployer ->>+ LPController: deploy (...Uniswap, balancer, Executor, FluenceToken, token, weights...) + Note over LPController: Owner: deployer, Withdraw address: Executor + LPController ->>- Uniswap: uniswapPositionManager.mint() + Note over Uniswap: Recipient: Executor + end + + alt 0003_DevRewardDistributor.ts + deployer ->> DevRewardDistributor: deploy (FluenceToken, Executor, merkle root, halvePeriod, initialReward, claimingPeriod) + Note over DevRewardDistributor: unclaimed reward receiver: Executor + deployer ->>+ FluenceToken: transfer (totalRewards) to DevRewardDistributor + FluenceToken ->>- DevRewardDistributor: transfer (totalRewards) + end + + alt Vesting + alt 0004_FluenceVesting.ts + deployer ->> FluenceVesting: deploy (FluenceToken, cliffDuration , vestingDuration , accounts , amounts) + deployer ->>+ FluenceToken: transfer (totalAmounts) to FluenceVesting + FluenceToken ->>- FluenceVesting: transfer (totalAmounts) + Note over FluenceVesting: could transfer(0x00, amount) to release FluenceToken for accounts accordingly: accounts + end + + alt 0005_InvestorsVesting.ts + deployer ->> InvestorsVesting: deploy (FluenceToken, cliffDuration , vestingDuration , accounts , amounts) + deployer ->>+ FluenceToken: transfer (totalAmounts) to FluenceVesting + FluenceToken ->>- InvestorsVesting: transfer (totalAmounts) + Note over InvestorsVesting: could transfer(0x00, amount) to release FluenceToken for accounts accordingly: accounts + end + + alt 0006_TeamVesting.ts + deployer ->> TeamVesting: deploy (FluenceToken, cliffDuration , vestingDuration , accounts , amounts) + deployer ->>+ FluenceToken: transfer (totalAmounts) to FluenceVesting + FluenceToken ->>- TeamVesting: transfer (totalAmounts) + Note over TeamVesting: could transfer(0x00, amount) to release FluenceToken for accounts accordingly: accounts + end + end + + alt 0007_Governor.ts + deployer ->> Governor: deploy (FluenceToken, TeamVesting, Executor, quorum, initialVotingDelay, initialVotingPeriod, initialProposalThreshold) + + deployer ->> Executor: grantRole(RPROPOSER_ROLE, Governor) + deployer ->> Executor: grantRole(CANCELLER_ROLE, Governor) + deployer ->> Executor: grantRole(CANCELLER_ROLE, FluenceMultisig) + deployer ->> Executor: revokeRole(ADMIN_ROLE, deployer) + deployer ->>+ FluenceToken: transfer (balance) to Governor + FluenceToken ->>- Governor: transfer (balance) + + Note over Governor: Owner: Executor + Note over Executor: Admin Role: [0x0] + Note over Executor: Proposer Role: [Governor] + Note over Executor: Canceller Role: [Governor, FluenceMultisig] + end +``` ## Develop diff --git a/contracts/config.example.yaml b/contracts/config.example.yaml index 6182cc5..5057092 100644 --- a/contracts/config.example.yaml +++ b/contracts/config.example.yaml @@ -69,3 +69,5 @@ deployment: votingDelayDays: 1 votingPeriodDays: 1 proposalThreshold: 100 + +fluenceMultisig: "0x0000000000000000000000000000000000000000" diff --git a/contracts/contracts/DevRewardDistributor.sol b/contracts/contracts/DevRewardDistributor.sol index 420a0ef..a6d275b 100644 --- a/contracts/contracts/DevRewardDistributor.sol +++ b/contracts/contracts/DevRewardDistributor.sol @@ -25,6 +25,11 @@ contract DevRewardDistributor { **/ Executor public immutable executor; + /** + * @notice Canceler address (e.g. FluenceMultisig) + **/ + address public immutable canceler; + /** * @notice Claiming end time **/ @@ -82,6 +87,7 @@ contract DevRewardDistributor { * @param _halvePeriod - period for dividing the reward * @param _initialReward - initial user reward * @param _claimingPeriod - claiming period + * @param _canceler - can cancel distribution, and withdraw to _executor. **/ constructor( FluenceToken _token, @@ -89,10 +95,12 @@ contract DevRewardDistributor { bytes32 _merkleRoot, uint256 _halvePeriod, uint256 _initialReward, - uint256 _claimingPeriod + uint256 _claimingPeriod, + address _canceler ) { token = _token; executor = _executor; + canceler = _canceler; merkleRoot = _merkleRoot; halvePeriod = _halvePeriod; @@ -149,15 +157,18 @@ contract DevRewardDistributor { } /** - * @notice used to move any remaining tokens out of the contract after expiration + * @notice used to move any remaining tokens out of the contract to Executor after expiration **/ function transferUnclaimed() external whenClaimingIs(false) { - IERC20 rewardToken = IERC20(token); //gas saving - - uint256 remainingBalance = rewardToken.balanceOf(address(this)); - rewardToken.safeTransfer(address(executor), remainingBalance); + _withdraw(); + } - emit TransferUnclaimed(remainingBalance); + /** + * @notice used to move any remaining tokens out of the contract to Executor (DAO) in emergency situation. + **/ + function withdraw() external { + require(msg.sender == canceler, "Only canceler can withdraw"); + _withdraw(); } /** @@ -207,4 +218,13 @@ contract DevRewardDistributor { claimedBitMap[claimedWordIndex] | (1 << claimedBitIndex); } + + function _withdraw() private { + IERC20 rewardToken = IERC20(token); //gas saving + + uint256 remainingBalance = rewardToken.balanceOf(address(this)); + rewardToken.safeTransfer(address(executor), remainingBalance); + + emit TransferUnclaimed(remainingBalance); + } } diff --git a/contracts/contracts/Governor.sol b/contracts/contracts/Governor.sol index e85a272..9621050 100644 --- a/contracts/contracts/Governor.sol +++ b/contracts/contracts/Governor.sol @@ -39,7 +39,7 @@ contract Governor is * @param teamVesting_ - team * @param executor_ - DAO timelock contract * @param quorum_ - minimum percentage of quorum to accept a proposal - * @param initialVotingDelay - delay beetween the creation of a proposal and the start of voting + * @param initialVotingDelay - delay between the creation of a proposal and the start of voting * @param initialVotingPeriod - voting duration * @param initialProposalThreshold - tokens threshold for creating a proposal **/ diff --git a/contracts/deploy/0003_DevRewardDistributor.ts b/contracts/deploy/0003_DevRewardDistributor.ts index 90b4426..74b7b6f 100644 --- a/contracts/deploy/0003_DevRewardDistributor.ts +++ b/contracts/deploy/0003_DevRewardDistributor.ts @@ -31,6 +31,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { Math.floor( config.deployment!.devRewardDistributor!.claimingPeriodMonths * MONTH ), + config.fluenceMultisig!, ], log: true, autoMine: true, diff --git a/contracts/deploy/0007_Governor.ts b/contracts/deploy/0007_Governor.ts index ef91113..08f4f43 100644 --- a/contracts/deploy/0007_Governor.ts +++ b/contracts/deploy/0007_Governor.ts @@ -76,6 +76,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { governorDeployment.address ); + // Grant role of proposal canceller to the Fluence multisig. await hre.deployments.execute( "Executor", { @@ -86,7 +87,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }, "grantRole", CANCELLER_ROLE, - governorDeployment.address + config.fluenceMultisig! ); await hre.deployments.execute( diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 7f9d940..c250b7b 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -19,7 +19,7 @@ let config: Config | null = null; try { const file = fs.readFileSync("./config.yaml", "utf8"); const c = YAML.parse(file); - config = Config.get(c.networks, c.deployment); + config = Config.get(c.networks, c.deployment, c.fluenceMultisig); } catch (e) { console.log("No config file found"); } diff --git a/contracts/test/Deploy.spec.ts b/contracts/test/Deploy.spec.ts index a048776..84c052a 100644 --- a/contracts/test/Deploy.spec.ts +++ b/contracts/test/Deploy.spec.ts @@ -41,17 +41,19 @@ describe("Deploy script", () => { let fluenceVesting: Vesting; let teamVesting: VestingWithVoting; let governor: Governor; + let fluenceMultisig: ethers.Signer; let config: Config; const setupTest = deployments.createFixture( async (hre: HardhatRuntimeEnvironment) => { + const hardhatSigners = await hre.ethers.getSigners(); usdToken = await new DevERC20__factory( - ( - await ethers.getSigners() - )[0] + hardhatSigners[0] ).deploy("USD", "USD", ethers.utils.parseEther(String(lbpUSDAmount))); + fluenceMultisig = hardhatSigners[hardhatSigners.length - 1]; + Config.reset( { etherscanApiKey: "", @@ -121,7 +123,8 @@ describe("Deploy script", () => { votingPeriodDays: 7, proposalThreshold: 12, }, - } + }, + fluenceMultisig.address ); config = Config.get(); @@ -264,7 +267,7 @@ describe("Deploy script", () => { //TODO: veify balancer }); - it("executor is correct", async () => { + it("executor roles are correct", async () => { const { deployer } = await getNamedAccounts(); expect(await executor.getMinDelay()).to.eq( @@ -290,6 +293,8 @@ describe("Deploy script", () => { "0x0000000000000000000000000000000000000000" ) ).to.eq(true); + + expect(await executor.hasRole(CANCELLER_ROLE, fluenceMultisig.address)).to.eq(true); }); it("DevRewardDistributor is correct", async () => { diff --git a/contracts/test/DevRewardDestributor.spec.ts b/contracts/test/DevRewardDestributor.spec.ts index 3828489..1a974e9 100644 --- a/contracts/test/DevRewardDestributor.spec.ts +++ b/contracts/test/DevRewardDestributor.spec.ts @@ -33,6 +33,7 @@ const setupTest = deployments.createFixture( devAccounts: Array; token: FluenceToken; executor: Executor; + fluenceMultisig: ethers.Signer; }> => { const devAccounts = Array(100) .fill(1) @@ -42,6 +43,9 @@ const setupTest = deployments.createFixture( devAccounts.map((x) => x.address) ); + const hardhatSigners = await hre.ethers.getSigners(); + const fluenceMultisig = hardhatSigners[hardhatSigners.length - 1]; + Config.reset( { etherscanApiKey: "", @@ -63,7 +67,8 @@ const setupTest = deployments.createFixture( halvePeriodMonths: 1, claimingPeriodMonths: 3, }, - } + }, + fluenceMultisig.address ); await hre.deployments.fixture([ @@ -95,6 +100,7 @@ const setupTest = deployments.createFixture( await hre.deployments.get("Executor") ).address )) as Executor, + fluenceMultisig: fluenceMultisig, }; } ); @@ -119,6 +125,7 @@ describe("DevRewardDistributor", () => { let tree: MerkleTree; let developerAccount: Wallet; + let fluenceMultisig: ethers.Signer; before(async () => { const { mainAccount } = await getNamedAccounts(); @@ -137,6 +144,7 @@ describe("DevRewardDistributor", () => { tree = settings.merkleTree; token = settings.token; executor = settings.executor; + fluenceMultisig = settings.fluenceMultisig; rewardDistributor = settings.rewardDistributor.connect(developerAccount); }); @@ -324,6 +332,24 @@ describe("DevRewardDistributor", () => { ); }); + async function _isTransferedToExecutor( + transaction: any, + amountBefore: BigNumber + ) { + await expect(transaction) + .to.emit(rewardDistributor, "TransferUnclaimed") + .withArgs(amountBefore); + + await expect(transaction) + .to.emit(token, "Transfer") + .withArgs(rewardDistributor.address, executor.address, amountBefore); + + expect(await token.balanceOf(executor.address)).to.eq(amountBefore); + expect(await token.balanceOf(rewardDistributor.address)).to.eq( + BigNumber.from(0) + ); + } + it("transfer unclaimed when claiming is not active", async () => { const period = await rewardDistributor.claimingEndTime(); @@ -333,17 +359,19 @@ describe("DevRewardDistributor", () => { const amount = await token.balanceOf(rewardDistributor.address); const tx = await rewardDistributor.transferUnclaimed(); - await expect(tx) - .to.emit(rewardDistributor, "TransferUnclaimed") - .withArgs(amount); + await _isTransferedToExecutor(tx, amount); + }); - await expect(tx) - .to.emit(token, "Transfer") - .withArgs(rewardDistributor.address, executor.address, amount); + it("transfer unclaimed when fluence multisig used (#withdraw)", async () => { + const amount = await token.balanceOf(rewardDistributor.address); - expect(await token.balanceOf(executor.address)).to.eq(amount); - expect(await token.balanceOf(rewardDistributor.address)).to.eq( - BigNumber.from(0) + const tx = await rewardDistributor.connect(fluenceMultisig).withdraw(); + await _isTransferedToExecutor(tx, amount); + }); + + it("throw when transfer unclaimed not from multisig", async () => { + await expect(rewardDistributor.withdraw()).to.be.revertedWith( + `${THROW_ERROR_PREFIX} 'Only canceler can withdraw'` ); }); }); diff --git a/contracts/test/Governor.spec.ts b/contracts/test/Governor.spec.ts index a24f6cf..d044647 100644 --- a/contracts/test/Governor.spec.ts +++ b/contracts/test/Governor.spec.ts @@ -21,6 +21,7 @@ describe("Deploy script", () => { let executor: Executor; let teamVesting: VestingWithVoting; let governor: Governor; + let fluenceMultisig: ethers.Signer; let config: Config; @@ -28,6 +29,8 @@ describe("Deploy script", () => { const setupTest = deployments.createFixture( async (hre: HardhatRuntimeEnvironment) => { + const hardhatSigners = await hre.ethers.getSigners(); + fluenceMultisig = hardhatSigners[hardhatSigners.length - 1]; Config.reset( { etherscanApiKey: "", @@ -54,7 +57,8 @@ describe("Deploy script", () => { votingPeriodDays: 50 / 86400, proposalThreshold: 1, }, - } + }, + fluenceMultisig.address ); config = Config.get(); @@ -104,7 +108,7 @@ describe("Deploy script", () => { } ); - const createVoteAndWaitProposal = async ( + const createVoteAndQueueProposal = async ( targets: string[], values: BigNumberish[], calldatas: BytesLike[], @@ -137,11 +141,6 @@ describe("Deploy script", () => { calldatas, ethers.utils.keccak256(ethers.utils.toUtf8Bytes(description)) ); - - delay = (await executor.getMinDelay()).toNumber(); - for (let i = 0; i <= delay; i++) { - await ethers.provider.send("evm_mine", []); - } }; before(async () => { @@ -245,6 +244,110 @@ describe("Deploy script", () => { ); }); + it("It allows to cancel proposal by FluenceMultisig (veto). Proposal can not been executed after.", async () => { + // Check that role is granted. + expect( + await executor.hasRole( + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("CANCELLER_ROLE")), + fluenceMultisig.address + ) + ).to.be.true; + + // Compose proposal data. + await teamVesting.delegate(account.address); + await ethers.provider.send("evm_mine", []); + + const newImp = await new Governor__factory( + ethers.provider.getSigner(account.address) + ).deploy(); + + const data = ( + await governor.populateTransaction.upgradeToAndCall(newImp.address, "0x") + ).data!; + const description = ""; + + await createVoteAndQueueProposal( + [governor.address], + [0], + [data], + description + ); + + // Preparation before FluenceMultisig will cancel the proposal from perspective of the TimeLock Contract. + // Get salt that TimeLock contract emitted when propsal queued. + const filter = executor.filters.CallSalt(); + const queryCallSalt = await executor.queryFilter(filter, "latest"); + expect(queryCallSalt.length).to.eq(1); + const saltEventOnProposalQueued = queryCallSalt[0]; + const salt = saltEventOnProposalQueued.args?.salt; + const timelockIdFromEvent = saltEventOnProposalQueued.args?.id; + + // Calculate timelockId that is stored in TimeLock Contract (additional check, since we already new timelockId from the event) + const timelockIdCalculated = await executor.hashOperationBatch( + [governor.address], + [0], + [data], + // ref to `$._timelock.scheduleBatch(targets, values, calldatas, 0, salt, delay);` predecessor is 0. + "0x0000000000000000000000000000000000000000000000000000000000000000", + salt + ); + expect(timelockIdCalculated).to.eq(timelockIdFromEvent); + const state = await executor.getOperationState(timelockIdFromEvent); + expect(state).to.eq(1); // i.e. waiting. + + // The cancel action itself. + await executor.connect(fluenceMultisig).cancel(timelockIdFromEvent); + + // Cancel from Governor prospective: does not work with GovernorUnexpectedProposalState. + // await governor.connect(fluenceMultisig).cancel( + // [governor.address], + // [0], + // [data], + // ethers.utils.keccak256(ethers.utils.toUtf8Bytes(description)) + // ); + + // Check further that proposal is not executable any more. + const executorMinDelay = (await executor.getMinDelay()).toNumber(); + for (let i = 0; i <= executorMinDelay; i++) { + await ethers.provider.send("evm_mine", []); + } + + await expect( + governor.execute( + [governor.address], + [0], + [data], + ethers.utils.keccak256(ethers.utils.toUtf8Bytes(description)) + ) + ).to.be.reverted; + // `VM Exception while processing transaction: reverted with custom error + // 'GovernorUnexpectedProposalState(...)'` + + // Check that after veto other proposals can be executed, even the same one. + const newDescription = "New description"; + await createVoteAndQueueProposal( + [governor.address], + [0], + [data], + newDescription + ); + + for (let i = 0; i <= executorMinDelay; i++) { + await ethers.provider.send("evm_mine", []); + } + + await expect( + governor.execute( + [governor.address], + [0], + [data], + ethers.utils.keccak256(ethers.utils.toUtf8Bytes(newDescription)) + ) + ) + .to.emit(governor, "Upgraded") + .withArgs(newImp.address); + }); + it("Update governor", async () => { await teamVesting.delegate(account.address); await ethers.provider.send("evm_mine", []); @@ -257,7 +360,12 @@ describe("Deploy script", () => { await governor.populateTransaction.upgradeToAndCall(newImp.address, "0x") ).data!; - await createVoteAndWaitProposal([governor.address], [0], [data], ""); + await createVoteAndQueueProposal([governor.address], [0], [data], ""); + + const delay = (await executor.getMinDelay()).toNumber(); + for (let i = 0; i <= delay; i++) { + await ethers.provider.send("evm_mine", []); + } await expect( governor.execute( @@ -283,7 +391,12 @@ describe("Deploy script", () => { await executor.populateTransaction.upgradeToAndCall(newImp.address, "0x") ).data!; - await createVoteAndWaitProposal([executor.address], [0], [data], ""); + await createVoteAndQueueProposal([executor.address], [0], [data], ""); + + const delay = (await executor.getMinDelay()).toNumber(); + for (let i = 0; i <= delay; i++) { + await ethers.provider.send("evm_mine", []); + } await expect( governor.execute( diff --git a/contracts/test/Vesting.spec.ts b/contracts/test/Vesting.spec.ts index a176eff..2fd229d 100644 --- a/contracts/test/Vesting.spec.ts +++ b/contracts/test/Vesting.spec.ts @@ -23,6 +23,8 @@ const setupTest = deployments.createFixture( token: FluenceToken; vesting: Vesting; }> => { + const hardhatSigners = await hre.ethers.getSigners(); + const fluenceMultisig = hardhatSigners[hardhatSigners.length - 1]; Config.reset( { etherscanApiKey: "", @@ -34,7 +36,8 @@ const setupTest = deployments.createFixture( token: { totalSupply: 100000000, }, - } + }, + fluenceMultisig.address ); await hre.deployments.fixture(["FluenceToken"]); diff --git a/contracts/test/VestingWithVoting.spec.ts b/contracts/test/VestingWithVoting.spec.ts index a4478d6..6b7d2a8 100644 --- a/contracts/test/VestingWithVoting.spec.ts +++ b/contracts/test/VestingWithVoting.spec.ts @@ -20,6 +20,8 @@ const setupTest = deployments.createFixture( token: FluenceToken; vesting: VestingWithVoting; }> => { + const hardhatSigners = await hre.ethers.getSigners(); + const fluenceMultisig = hardhatSigners[hardhatSigners.length - 1]; Config.reset( { etherscanApiKey: "", @@ -31,7 +33,8 @@ const setupTest = deployments.createFixture( token: { totalSupply: 1000000, }, - } + }, + fluenceMultisig.address ); await hre.deployments.fixture(["FluenceToken"]); diff --git a/contracts/test/e2e/E2E_LPController.spec.ts b/contracts/test/e2e/E2E_LPController.spec.ts index 7b7b69d..6d029a9 100644 --- a/contracts/test/e2e/E2E_LPController.spec.ts +++ b/contracts/test/e2e/E2E_LPController.spec.ts @@ -43,12 +43,15 @@ const setupTest = deployments.createFixture( usdToken: IERC20Metadata; }> => { await deployments.fixture([]); + const hardhatSigners = await hre.ethers.getSigners(); - const token = await new DevERC20__factory( - ( - await ethers.getSigners() - )[0] - ).deploy("USD", "USD", ethers.utils.parseEther(String(lbpUSDAmount))); + const token = await new DevERC20__factory(hardhatSigners[0]).deploy( + "USD", + "USD", + ethers.utils.parseEther(String(lbpUSDAmount)) + ); + + const fluenceMultisig = hardhatSigners[hardhatSigners.length - 1]; Config.reset( { @@ -84,7 +87,8 @@ const setupTest = deployments.createFixture( executor: { delayDays: 4, }, - } + }, + fluenceMultisig.address ); await hre.deployments.fixture(["FluenceToken", "Executor", "LPController"]); diff --git a/contracts/utils/config.ts b/contracts/utils/config.ts index 703ec0d..2c07251 100644 --- a/contracts/utils/config.ts +++ b/contracts/utils/config.ts @@ -3,27 +3,31 @@ class Config { readonly networks: Networks | null; readonly deployment: Deployment | null; + readonly fluenceMultisig: string | null = null; - constructor(_networks: Networks, _deployment: Deployment) { + constructor(_networks: Networks, _deployment: Deployment, _fluenceMultisig: string) { this.deployment = _deployment; this.networks = _networks; + this.fluenceMultisig = _fluenceMultisig; } public static get( _networks: Networks | null = null, - _deployment: Deployment | null = null + _deployment: Deployment | null = null, + _fluenceMultisig: string | null = null, ): Config { if (this._config === null || this._config == undefined) { - this._config = new Config(_networks!, _deployment!); + this._config = new Config(_networks!, _deployment!, _fluenceMultisig!); } return this._config; } public static reset( _networks: Networks | null = null, - _deployment: Deployment | null = null + _deployment: Deployment | null = null, + _fluenceMultisig: string | null = null ) { - this._config = new Config(_networks!, _deployment!); + this._config = new Config(_networks!, _deployment!, _fluenceMultisig!); } public static isInited(): boolean {