diff --git a/add-collateral.json b/add-collateral.json index 61605ac8..6558c0fc 100644 --- a/add-collateral.json +++ b/add-collateral.json @@ -1 +1 @@ -{"51":{"tokenAddress":"0x0000000000000000000000000000000000000000","testOracle":"0x0000000000000000000000000000000000000000","fathomProxyFactory":"0x0000000000000000000000000000000000000000","fathomProxyAdmin":"0x0000000000000000000000000000000000000000"},"31337":{"tokenAddress":"0x22879cD092f74aBA6d198B053f25b7e26BBbd5Da","testOracle":"0x828e4F72BC7B912f6Fde900071212aAA075BBd22","fathomProxyFactory":"0x13cdf6cA2C1c216198FB51A4c515FDa6F11B9dE0","fathomProxyAdmin":"0x28c6408131836B0f1Adbae443b65B76487723C4b"},"token":"GLD"} \ No newline at end of file +{"51":{"tokenAddress":"0x0000000000000000000000000000000000000000","testOracle":"0x0000000000000000000000000000000000000000","fathomProxyFactory":"0x0000000000000000000000000000000000000000","fathomProxyAdmin":"0x0000000000000000000000000000000000000000"},"31337":{"tokenAddress":"0x30087319dCCa6436de3803EED6B8E67FaaCE36aD","testOracle":"0x11897e6e50C42067008Ca9C93095324aE5d04Cb1","fathomProxyFactory":"0xB14d2dac60617bd7B6e67D374daFBf298Ca7DD7b","fathomProxyAdmin":"0xc9610b7E6973F7162A682279D0abF93F677dc358"},"token":"GLD"} \ No newline at end of file diff --git a/contracts/main/dao/governance/ProtocolGovernor.sol b/contracts/main/dao/governance/ProtocolGovernor.sol new file mode 100644 index 00000000..2ede6f3c --- /dev/null +++ b/contracts/main/dao/governance/ProtocolGovernor.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {Governor} from "@openzeppelin/contracts/governance/Governor.sol"; +import {GovernorSettings} from "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; +import {GovernorVotesQuorumFraction} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; +import {GovernorTimelockControl} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; + +import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol"; +import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol"; + +contract ProtocolGovernor is + Governor, + GovernorSettings, + GovernorCountingSimple, + GovernorVotes, + GovernorVotesQuorumFraction, + GovernorTimelockControl +{ + constructor(IVotes _token, TimelockController _timelock) + Governor("ProtocolGovernor") + GovernorSettings(1, /* 1 block */ 50400, /* 1 week */ 0) + GovernorVotes(_token) + GovernorVotesQuorumFraction(4) + GovernorTimelockControl(_timelock) + {} + + // The following functions are overrides required by Solidity. + + function votingDelay() public view override(IGovernor, GovernorSettings) returns (uint256) { + return super.votingDelay(); + } + + function votingPeriod() public view override(IGovernor, GovernorSettings) returns (uint256) { + return super.votingPeriod(); + } + + function quorum(uint256 blockNumber) + public + view + override(IGovernor, GovernorVotesQuorumFraction) + returns (uint256) + { + return super.quorum(blockNumber); + } + + function state(uint256 proposalId) + public + view + override(Governor, GovernorTimelockControl) + returns (ProposalState) + { + return super.state(proposalId); + } + + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public override(Governor, IGovernor) returns (uint256) { + return super.propose(targets, values, calldatas, description); + } + + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } + + function _execute( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) { + super._execute(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } + + function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { + return super._executor(); + } + + function supportsInterface(bytes4 interfaceId) + public + view + override(Governor, GovernorTimelockControl) + returns (bool) + { + return super.supportsInterface(interfaceId); + } +} \ No newline at end of file diff --git a/contracts/main/dao/governance/Timelock.sol b/contracts/main/dao/governance/Timelock.sol new file mode 100644 index 00000000..1c52dd18 --- /dev/null +++ b/contracts/main/dao/governance/Timelock.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; + +contract Timelock is TimelockController { + + // minDelay is how long you have to wait before executing + // proposers is the list of addresses that can propose + // executors is the list of addresses that can execute + // admin is msg.sender - we must give in an admin at first, so that we can move timelock controller admin so only the DAO can do anything with the timelock contorller + constructor(uint256 minDelay, address[] memory proposers, address[] memory executors) + TimelockController(minDelay, proposers, executors, msg.sender) + {} +} \ No newline at end of file diff --git a/contracts/main/dao/token/GovToken.sol b/contracts/main/dao/token/GovToken.sol new file mode 100644 index 00000000..0cdbdf82 --- /dev/null +++ b/contracts/main/dao/token/GovToken.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; +import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; + +contract GovToken is ERC20, ERC20Permit, ERC20Votes { + constructor() ERC20("Fathom Protocol Vote Token", "VFTHM") ERC20Permit("MyToken") {} + + // The following functions are overrides required by Solidity. + + function mint(address to, uint256 amount) public { + _mint(to, amount); + } + + function _afterTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) { + super._afterTokenTransfer(from, to, amount); + } + + function _mint(address to, uint256 amount) internal override(ERC20, ERC20Votes) { + super._mint(to, amount); + } + + function _burn(address account, uint256 amount) internal override(ERC20, ERC20Votes) { + super._burn(account, amount); + } +} \ No newline at end of file diff --git a/deploy/deploy-main/01_deploy.js b/deploy/deploy-main/01_deploy.js index 6af23e32..4fbd66e7 100644 --- a/deploy/deploy-main/01_deploy.js +++ b/deploy/deploy-main/01_deploy.js @@ -8,6 +8,8 @@ const { configureShowStopper } = require("../../scripts/setup/deploy/configureSh const { deployVault } = require("../../scripts/setup/deploy/deployVault"); const { initCollateralTokenAdapter } = require("../../scripts/setup/deploy/initCollateralTokenAdapter"); const { configFlashLending } = require("../../scripts/setup/deploy/configFlashLending"); +const { deployDao } = require("../../scripts/setup/deploy/deployDao"); +const { transferOwnership } = require("../../scripts/setup/transfer-ownership/transferOwnership"); // Configuration const { addCollateralPools } = require("../../scripts/configuration/deploy/addCollateralPools"); @@ -26,6 +28,10 @@ module.exports = async ({ getNamedAccounts, deployments, getChainId }) => { // Configuration await addCollateralPools(deployments, getChainId); + + //Transfer ownership of the contracts to the dao + await deployDao(getNamedAccounts, deployments); + await transferOwnership(getNamedAccounts, deployments); }; module.exports.tags = ["DeployMain"]; diff --git a/deploy/deploy-test-fixture/01_deploy_test_fixture.js b/deploy/deploy-test-fixture/01_deploy_test_fixture.js index 13d921a4..22beeae6 100644 --- a/deploy/deploy-test-fixture/01_deploy_test_fixture.js +++ b/deploy/deploy-test-fixture/01_deploy_test_fixture.js @@ -26,6 +26,10 @@ const { initialize: initializeAddCollateral } = require("../../scripts/setup/add const { addRoles: addRolesAddCollateral } = require("../../scripts/setup/add-collateral/addRoles"); const { deployVault: deployVaultAddCollateral } = require("../../scripts/setup/add-collateral/deployVault"); +// Deploy DAO +const { deployDao } = require("../../scripts/setup/deploy/deployDao"); +const { transferOwnership } = require("../../scripts/setup/transfer-ownership/transferOwnership"); + module.exports = async ({ getNamedAccounts, deployments, getChainId }) => { // Setup await deployMocks(getNamedAccounts, deployments, getChainId); @@ -63,6 +67,10 @@ module.exports = async ({ getNamedAccounts, deployments, getChainId }) => { // Configuration await addCollateralConfigPool(deployments, getChainId); + + // Transfer ownership of the contracts to the dao + await deployDao(getNamedAccounts, deployments); + await transferOwnership(getNamedAccounts, deployments, true); }; module.exports.tags = ["DeployTestFixture"]; diff --git a/hardhat.config.js b/hardhat.config.js index 67bea530..5ea8a836 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -3,6 +3,7 @@ require("@nomicfoundation/hardhat-toolbox"); require("hardhat-deploy"); require("./tasks/price-feed"); require("./tasks/fathom-solidity-sdk"); +require("./tasks/fthm"); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { diff --git a/scripts/setup/deploy/deployDao.js b/scripts/setup/deploy/deployDao.js new file mode 100644 index 00000000..ea423b89 --- /dev/null +++ b/scripts/setup/deploy/deployDao.js @@ -0,0 +1,63 @@ +const { ethers } = require("hardhat"); +const provider = ethers.provider; + +const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; + +const PROPOSER_ROLE = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("PROPOSER_ROLE")); +const EXECUTOR_ROLE = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("EXECUTOR_ROLE")); +const TIMELOCK_ADMIN_ROLE = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("TIMELOCK_ADMIN_ROLE")); + +async function deployDao(getNamedAccounts, deployments) { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + // Deploy token for voting + // Token should only be deployed for local tests, otherwise use the FTHM token deployed on mainnet/testnet + await deploy("GovToken", { + from: deployer, + args: [], + log: true, + }); + + const GovToken = await deployments.get("GovToken"); + const govToken = await ethers.getContractAt("GovToken", GovToken.address); + await govToken.mint(deployer, ethers.utils.parseEther("100")); + + await govToken.connect(provider.getSigner(deployer)).delegate(deployer); + + // Deploy Timelock + // initial minimum delay for operations + // after a vote passes, we have 1 day before we can enact + const MIN_DELAY = ethers.BigNumber.from("3600"); // 1 hour TODO: check this value + + await deploy("Timelock", { + from: deployer, + args: [ + MIN_DELAY, + [], // proposers + [], // executors + ], + log: true, + }); + + const Timelock = await deployments.get("Timelock"); + + // Deploy Governor + await deploy("ProtocolGovernor", { + from: deployer, + args: [GovToken.address, Timelock.address], + log: true, + }); + + const ProtocolGovernor = await deployments.get("ProtocolGovernor"); + + const timelock = await ethers.getContractAt("Timelock", Timelock.address); + + await timelock.grantRole(PROPOSER_ROLE, ProtocolGovernor.address); // with this we are saying that everyone can propose, but only governor can queue + await timelock.grantRole(EXECUTOR_ROLE, ZERO_ADDRESS); // anybody can execute and pass proposals + await timelock.revokeRole(TIMELOCK_ADMIN_ROLE, deployer); // deployer will no longer be the admin of the timelock +} + +module.exports = { + deployDao, +}; diff --git a/scripts/setup/transfer-ownership/transferOwnership.js b/scripts/setup/transfer-ownership/transferOwnership.js new file mode 100644 index 00000000..81aff696 --- /dev/null +++ b/scripts/setup/transfer-ownership/transferOwnership.js @@ -0,0 +1,55 @@ +const { ethers } = require("hardhat"); +const { BigNumber } = ethers; + +const { getProxy } = require("../../../common/proxies"); + +async function transferOwnership(getNamedAccounts, deployments, forFixture = false) { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + const ProxyFactory = await deployments.get("FathomProxyFactory"); + const proxyFactory = await ethers.getContractAt("FathomProxyFactory", ProxyFactory.address); + + const ProxyAdmin = await deployments.get("FathomProxyAdmin"); + const proxyAdmin = await ethers.getContractAt("FathomProxyAdmin", ProxyAdmin.address); + + const flashMintArbitrager = await getProxy(proxyFactory, "FlashMintArbitrager"); + const proxyWalletFactory = await getProxy(proxyFactory, "ProxyWalletFactory"); + const bookKeeperFlashMintArbitrager = await getProxy(proxyFactory, "BookKeeperFlashMintArbitrager"); + const accessControlConfig = await getProxy(proxyFactory, "AccessControlConfig"); + const fathomStablecoin = await getProxy(proxyFactory, "FathomStablecoin"); + const stableSwap = await getProxy(proxyFactory, "StableSwapModule"); + const stableSwapModuleWrapper = await getProxy(proxyFactory, "StableSwapModuleWrapper"); + + const Timelock = await deployments.get("Timelock"); + + // Transfer ownership of FathomProxyFactory to DAO + await proxyFactory.transferOwnership(Timelock.address); + // Transfer ownership of FathomProxyAdmin to DAO + await proxyAdmin.transferOwnership(Timelock.address); + // Transfer ownership of ProxyWalletFactory to DAO + // await proxyWalletFactory.transferOwnership(Timelock.address); TODO: issue since owner is not set on ProxyWalletFactory + // Transfer ownership of FlashMintArbitrager to DAO + await flashMintArbitrager.transferOwnership(Timelock.address); + // Transfer ownership of BookKeeperFlashMintArbitrager to DAO + await bookKeeperFlashMintArbitrager.transferOwnership(Timelock.address); + + if (forFixture) { + await fathomStablecoin.grantRole(await fathomStablecoin.MINTER_ROLE(), Timelock.address); + await accessControlConfig.grantRole(await accessControlConfig.PRICE_ORACLE_ROLE(), Timelock.address); + await accessControlConfig.grantRole(await accessControlConfig.OWNER_ROLE(), Timelock.address); + await accessControlConfig.grantRole(await accessControlConfig.MINTABLE_ROLE(), Timelock.address); + + await stableSwap.addToWhitelist(Timelock.address); + await stableSwapModuleWrapper.addToWhitelist(Timelock.address); + } + + // Grant OWNER_ROLE to DAO + await accessControlConfig.grantRole(await accessControlConfig.OWNER_ROLE(), Timelock.address); + // EOA should renounce ownership + await accessControlConfig.renounceRole(await accessControlConfig.OWNER_ROLE(), deployer); +} + +module.exports = { + transferOwnership, +}; diff --git a/tasks/fthm.js b/tasks/fthm.js new file mode 100644 index 00000000..e385f2d5 --- /dev/null +++ b/tasks/fthm.js @@ -0,0 +1,42 @@ +task("fthm", "FTHM Token Info").setAction(async () => { + const proxyWalletFactory = await ethers.getContractAt("ProxyWalletFactory", "0xA43529Ce149924051e9A56b2BE740AEDbA4A581a"); + const adminControls = await ethers.getContractAt("AdminControls", "0x32A5f5D0BdB48E0A3A79ec21364e2F9f3f6a23c5"); + const flashMintArbitrager = await ethers.getContractAt("FlashMintArbitrager", "0x25464a1Cf25D1b180a36417fAB9FFd9960627860"); + const bookKeeperFlashMintArbitrager = await ethers.getContractAt("BookKeeperFlashMintArbitrager", "0xf3D403DA1C8368Ce164dDA5bd316d582aC457a35"); + + console.log(await proxyWalletFactory.owner()); + console.log(await adminControls.owner()); + console.log(await flashMintArbitrager.owner()); + console.log(await bookKeeperFlashMintArbitrager.owner()); + + // const strategies = [ + // ethers.utils.getAddress("0x66B45F20cE90D5164bf85C582002d3620C429496"), + // ethers.utils.getAddress("0x465aeF54f7a8d9fE22B74A27fcd922c95cAaD4Be"), + // ethers.utils.getAddress("0x524ae63AB2D30853578E75eA0A758E0DA2d59814"), + // ethers.utils.getAddress("0xfe5037504E0EF5eC2DfBEEA03f9d9cB43580EF23") + // ] + // const accessControlConfig = await ethers.getContractAt("AccessControlConfig", "0x2cD89769a2D9d992790e76c6A9f55c39fdf2FDc2"); + // const collateralTokenAdapterXDC = await ethers.getContractAt("CollateralTokenAdapter", "0x2fc7e65023aFF27FA61A573B5C8E3fDe3CE9ef79"); + // const collateralTokenAdapterCGO = await ethers.getContractAt("CollateralTokenAdapter", "0x30c64659AADD8C92328859A1CEE99721083A8E0f"); + + // for (let i = 0; i < strategies.length; i++) { + // console.log("For Strategy", strategies[i]); + // console.log("has ownerRole", await accessControlConfig.hasRole(await accessControlConfig.OWNER_ROLE(), strategies[i])); + // console.log("has govRole", await accessControlConfig.hasRole(await accessControlConfig.GOV_ROLE(), strategies[i])); + // console.log("has priceOracle", await accessControlConfig.hasRole(await accessControlConfig.PRICE_ORACLE_ROLE(), strategies[i])); + // console.log("has adapterRole", await accessControlConfig.hasRole(await accessControlConfig.ADAPTER_ROLE(), strategies[i])); + // console.log("has liquidationEngineRole", await accessControlConfig.hasRole(await accessControlConfig.LIQUIDATION_ENGINE_ROLE(), strategies[i])); + // console.log("has stabilityFeeCollector", await accessControlConfig.hasRole(await accessControlConfig.STABILITY_FEE_COLLECTOR_ROLE(), strategies[i])); + // console.log("has showStopper", await accessControlConfig.hasRole(await accessControlConfig.SHOW_STOPPER_ROLE(), strategies[i])); + // console.log("has positionManager", await accessControlConfig.hasRole(await accessControlConfig.POSITION_MANAGER_ROLE(), strategies[i])); + // console.log("has mintableRole", await accessControlConfig.hasRole(await accessControlConfig.MINTABLE_ROLE(), strategies[i])); + // console.log("has bookKeeperRole", await accessControlConfig.hasRole(await accessControlConfig.BOOK_KEEPER_ROLE(), strategies[i])); + // console.log("has collateralManagerRole", await accessControlConfig.hasRole(await accessControlConfig.COLLATERAL_MANAGER_ROLE(), strategies[i])); + // console.log("is whitelisted in xdc collateral token adapter", await collateralTokenAdapterXDC.whiteListed(strategies[i])); + // console.log("is whitelisted cgo collateral token adapter", await collateralTokenAdapterCGO.whiteListed(strategies[i])); + // console.log('************************************************'); + // console.log(`\n`); + // } +}); + +module.exports = {}; diff --git a/test/integration/AdminControls.test.js b/test/integration/AdminControls.test.js index 47bc3fe6..22b205fb 100644 --- a/test/integration/AdminControls.test.js +++ b/test/integration/AdminControls.test.js @@ -1,7 +1,13 @@ const { ethers } = require("hardhat"); +const provider = ethers.provider; const { expect } = require("chai"); +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); const { getProxy } = require("../../common/proxies"); +const MIN_DELAY = 3600; // 1 hour +const VOTING_PERIOD = 50400; // This is how long voting lasts, 1 week +const VOTING_DELAY = 1; // How many blocks till a proposal vote becomes active +const VOTE_WAY = 1; describe("AdminControls", () => { // Contract @@ -14,13 +20,20 @@ describe("AdminControls", () => { let stablecoinAdapter; let stableSwapModule; let flashMintModule; + let governor; + let DeployerAddress; beforeEach(async () => { await deployments.fixture(["DeployTestFixture"]); + const { deployer } = await getNamedAccounts(); + DeployerAddress = deployer; const ProxyFactory = await deployments.get("FathomProxyFactory"); const proxyFactory = await ethers.getContractAt("FathomProxyFactory", ProxyFactory.address); + const Governor = await deployments.get("ProtocolGovernor"); + governor = await ethers.getContractAt("ProtocolGovernor", Governor.address); + adminControls = await getProxy(proxyFactory, "AdminControls"); liquidationEngine = await getProxy(proxyFactory, "LiquidationEngine"); positionManager = await getProxy(proxyFactory, "PositionManager"); @@ -35,7 +48,27 @@ describe("AdminControls", () => { describe("#pause", () => { context("pause protocol", () => { it("protocol contracts should be paused", async () => { - await adminControls.pauseProtocol(); + const encodedFunctionCall = adminControls.interface.encodeFunctionData("pauseProtocol"); + + const proposalTx = await governor.propose([adminControls.address], [0], [encodedFunctionCall], "Pause protocol"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Pause protocol")); + await governor.queue([adminControls.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute([adminControls.address], [0], [encodedFunctionCall], descriptionHash); expect(await bookKeeper.paused()).to.be.equal(true); expect(await liquidationEngine.paused()).to.be.equal(true); @@ -50,9 +83,51 @@ describe("AdminControls", () => { describe("#unpause", () => { context("unpause protocol", () => { it("protocol contracts should be unpaused", async () => { - await adminControls.pauseProtocol(); + /** =============== PAUSE PROTOCOL ===================== */ + const encodedFunctionCall = adminControls.interface.encodeFunctionData("pauseProtocol"); + + const proposalTx = await governor.propose([adminControls.address], [0], [encodedFunctionCall], "Pause protocol"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Pause protocol")); + await governor.queue([adminControls.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute([adminControls.address], [0], [encodedFunctionCall], descriptionHash); + + /** =============== UNPAUSE PROTOCOL ==================== */ + const encodedFunctionCallU = adminControls.interface.encodeFunctionData("unpauseProtocol"); + + const proposalTxU = await governor.propose([adminControls.address], [0], [encodedFunctionCallU], "Unpause protocol"); + const proposalReceiptU = await proposalTxU.wait(); + const proposalIdU = proposalReceiptU.events[0].args.proposalId; + + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalIdU, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHashU = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Unpause protocol")); + await governor.queue([adminControls.address], [0], [encodedFunctionCallU], descriptionHashU); + + await time.increase(MIN_DELAY + 1); + await mine(1); - await adminControls.unpauseProtocol(); + // Execute + await governor.execute([adminControls.address], [0], [encodedFunctionCallU], descriptionHashU); expect(await bookKeeper.paused()).to.be.equal(false); expect(await liquidationEngine.paused()).to.be.equal(false); diff --git a/test/integration/CollateralTokenAdapter.test.js b/test/integration/CollateralTokenAdapter.test.js index 666a8d32..7f303c99 100644 --- a/test/integration/CollateralTokenAdapter.test.js +++ b/test/integration/CollateralTokenAdapter.test.js @@ -1,8 +1,14 @@ const { ethers, getNamedAccounts } = require("hardhat"); const provider = ethers.provider; +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); const { expect } = require("chai"); const { getProxy } = require("../../common/proxies"); +const MIN_DELAY = 3600; // 1 hour +const VOTING_PERIOD = 50400; // This is how long voting lasts, 1 week +const VOTING_DELAY = 1; // How many blocks till a proposal vote becomes active +const VOTE_WAY = 1; + describe("CollateralTokenAdapter", () => { // Contracts let collateralTokenAdapter; @@ -11,6 +17,7 @@ describe("CollateralTokenAdapter", () => { let DeployerAddress; let AliceAddress; let BobAddress; + let governor; beforeEach(async () => { await deployments.fixture(["DeployTestFixture"]); @@ -21,6 +28,8 @@ describe("CollateralTokenAdapter", () => { const ProxyFactory = await deployments.get("FathomProxyFactory"); const proxyFactory = await ethers.getContractAt("FathomProxyFactory", ProxyFactory.address); + const Governor = await deployments.get("ProtocolGovernor"); + governor = await ethers.getContractAt("ProtocolGovernor", Governor.address); collateralTokenAdapter = await getProxy(proxyFactory, "CollateralTokenAdapter"); bookKeeper = await getProxy(proxyFactory, "BookKeeper"); const _WXDC = await deployments.get("WXDC"); @@ -31,8 +40,31 @@ describe("CollateralTokenAdapter", () => { it("should return the correct net asset valuation", async () => { //Alice wraps XDC to WXDC await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); + + const encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + const proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter - await collateralTokenAdapter.addToWhitelist(AliceAddress); + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + await WXDC.connect(provider.getSigner(AliceAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("1")); await collateralTokenAdapter .connect(provider.getSigner(AliceAddress)) @@ -51,8 +83,29 @@ describe("CollateralTokenAdapter", () => { it("should only recognized collateral tokens from deposit function", async () => { //Alice wraps XDC to WXDC await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); + const encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + const proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter - await collateralTokenAdapter.addToWhitelist(AliceAddress); + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); await WXDC.connect(provider.getSigner(AliceAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("1")); await collateralTokenAdapter .connect(provider.getSigner(AliceAddress)) @@ -77,9 +130,53 @@ describe("CollateralTokenAdapter", () => { describe("#deposit", async () => { context("when CollateralTokenAdapter is not live", async () => { it("should revert", async () => { - // Cage collateralTokenAdapter - await collateralTokenAdapter.cage(); - await collateralTokenAdapter.addToWhitelist(DeployerAddress); + /** =========== Cage collateralTokenAdapter =========== */ + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("cage"); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Cage"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Cage")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + /** =========== Add to whitelist =========== */ + encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [DeployerAddress]); + + proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + await expect( collateralTokenAdapter.deposit( DeployerAddress, @@ -96,8 +193,31 @@ describe("CollateralTokenAdapter", () => { await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); // Assuming Alice is the first one to deposit hence no rewards to be harvested yet await WXDC.connect(provider.getSigner(AliceAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("2")); + + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter - await collateralTokenAdapter.addToWhitelist(AliceAddress); + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + await collateralTokenAdapter .connect(provider.getSigner(AliceAddress)) .deposit(AliceAddress, ethers.utils.parseEther("1"), ethers.utils.defaultAbiCoder.encode(["address"], [AliceAddress])); @@ -117,8 +237,31 @@ describe("CollateralTokenAdapter", () => { //Bob wraps XDC to WXDC await WXDC.connect(provider.getSigner(BobAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(4) }); await WXDC.connect(provider.getSigner(BobAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("4")); - //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter - await collateralTokenAdapter.addToWhitelist(BobAddress); + + encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [BobAddress]); + + proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + // Bob is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + await collateralTokenAdapter .connect(provider.getSigner(BobAddress)) .deposit(BobAddress, ethers.utils.parseEther("4"), ethers.utils.defaultAbiCoder.encode(["address"], [BobAddress])); @@ -142,7 +285,29 @@ describe("CollateralTokenAdapter", () => { describe("#withdraw", async () => { context("when withdraw more than what CollateralTokenAdapter staked", async () => { it("should revert", async () => { - await collateralTokenAdapter.addToWhitelist(AliceAddress); + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); //Alice wraps XDC to WXDC await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); await WXDC.connect(provider.getSigner(AliceAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("1")); @@ -160,13 +325,60 @@ describe("CollateralTokenAdapter", () => { context("when withdraw more than what he staked", async () => { it("should revert", async () => { - await collateralTokenAdapter.addToWhitelist(AliceAddress); + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); await WXDC.connect(provider.getSigner(AliceAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("1")); await collateralTokenAdapter .connect(provider.getSigner(AliceAddress)) .deposit(AliceAddress, ethers.utils.parseEther("1"), ethers.utils.defaultAbiCoder.encode(["address"], [AliceAddress])); - await collateralTokenAdapter.addToWhitelist(BobAddress); + + encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [BobAddress]); + + proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + // Bob is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + await WXDC.connect(provider.getSigner(BobAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(4) }); await WXDC.connect(provider.getSigner(BobAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("4")); await collateralTokenAdapter @@ -183,7 +395,30 @@ describe("CollateralTokenAdapter", () => { context("when CollateralTokenAdapter is not live", async () => { it("should still allow user to withdraw", async () => { - await collateralTokenAdapter.addToWhitelist(AliceAddress); + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + // Assuming Alice is the first one to deposit hence no rewards to be harvested yet await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); await WXDC.connect(provider.getSigner(AliceAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("1")); @@ -195,8 +430,30 @@ describe("CollateralTokenAdapter", () => { let collateralPoolIdFromAdapter = await collateralTokenAdapter.collateralPoolId(); expect(await bookKeeper.collateralToken(collateralPoolIdFromAdapter, AliceAddress)).to.be.eq(ethers.utils.parseEther("1")); + encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("cage"); + + proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Cage"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Cage")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute // Cage CollateralTokenAdapter - await collateralTokenAdapter.cage(); + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + expect(await collateralTokenAdapter.live()).to.be.eq(0); // Now Alice withdraw her position. 4 blocks have been passed. @@ -217,7 +474,30 @@ describe("CollateralTokenAdapter", () => { }); it("should still allow user to withdraw with pending rewards (if any)", async () => { - await collateralTokenAdapter.addToWhitelist(AliceAddress); + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); // Assuming Alice is the first one to deposit hence no rewards to be harvested yet @@ -231,7 +511,30 @@ describe("CollateralTokenAdapter", () => { expect(await bookKeeper.collateralToken(collateralPoolIdFromAdapter, AliceAddress)).to.be.eq(ethers.utils.parseEther("1")); await WXDC.connect(provider.getSigner(BobAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(4) }); - await collateralTokenAdapter.addToWhitelist(BobAddress); + + encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [BobAddress]); + + proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + // Bob is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); // Bob join the party with 4 WXDC! 2 Blocks have been passed. await WXDC.connect(provider.getSigner(BobAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("4")); @@ -246,8 +549,29 @@ describe("CollateralTokenAdapter", () => { // advanceBlock await hre.network.provider.send("hardhat_mine", ["0x01"]); + encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("cage"); + + proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Cage"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Cage")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute // Cage CollateralTokenAdapter - await collateralTokenAdapter.cage(); + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); expect(await collateralTokenAdapter.live()).to.be.eq(0); // Now Alice withdraw her position. Only 200 FXD has been harvested from FairLaunch. @@ -287,7 +611,29 @@ describe("CollateralTokenAdapter", () => { context("when all parameters are valid", async () => { it("should work", async () => { - await collateralTokenAdapter.addToWhitelist(AliceAddress); + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); //Alice wraps XDC to WXDC await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); // Assuming Alice is the first one to deposit hence no rewards to be harvested yet @@ -315,14 +661,59 @@ describe("CollateralTokenAdapter", () => { context("when bob withdraw collateral to alice", async () => { context("when bob doesn't has collateral", () => { it("should be revert", async () => { - await collateralTokenAdapter.addToWhitelist(AliceAddress); + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); // Assuming Alice is the first one to deposit hence no rewards to be harvested yet await WXDC.connect(provider.getSigner(AliceAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("1")); await collateralTokenAdapter .connect(provider.getSigner(AliceAddress)) .deposit(AliceAddress, ethers.utils.parseEther("1"), ethers.utils.defaultAbiCoder.encode(["address"], [AliceAddress])); - await collateralTokenAdapter.addToWhitelist(BobAddress); + + encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [BobAddress]); + + proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + // Bob is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); //checking with Subik-ji let collateralPoolIdFromAdapter = await collateralTokenAdapter.collateralPoolId(); @@ -337,7 +728,29 @@ describe("CollateralTokenAdapter", () => { }); context("when bob has collateral", async () => { it("should be able to call withdraw", async () => { - await collateralTokenAdapter.addToWhitelist(AliceAddress); + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); await WXDC.connect(provider.getSigner(AliceAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("1")); await collateralTokenAdapter @@ -348,7 +761,29 @@ describe("CollateralTokenAdapter", () => { let collateralPoolIdFromAdapter = await collateralTokenAdapter.collateralPoolId(); expect(await bookKeeper.collateralToken(collateralPoolIdFromAdapter, AliceAddress)).to.be.eq(ethers.utils.parseEther("1")); - await collateralTokenAdapter.addToWhitelist(BobAddress); + encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [BobAddress]); + + proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + // Bob is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); await WXDC.connect(provider.getSigner(BobAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); await WXDC.connect(provider.getSigner(BobAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("1")); await collateralTokenAdapter @@ -374,7 +809,29 @@ describe("CollateralTokenAdapter", () => { context("when CollateralTokenAdapter is not live", async () => { it("should allow users to exit with emergencyWithdraw and normal withdraw", async () => { await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(2) }); - await collateralTokenAdapter.addToWhitelist(AliceAddress); + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); // Assuming Alice is the first one to deposit hence no rewards to be harvested yet await WXDC.connect(provider.getSigner(AliceAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("1")); await collateralTokenAdapter @@ -386,7 +843,31 @@ describe("CollateralTokenAdapter", () => { expect(await bookKeeper.collateralToken(collateralPoolIdFromAdapter, AliceAddress)).to.be.eq(ethers.utils.parseEther("1")); await WXDC.connect(provider.getSigner(BobAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(4) }); - await collateralTokenAdapter.addToWhitelist(BobAddress); + + encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [BobAddress]); + + proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + // Bob is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + // Bob join the party with 4 WXDC! 2 Blocks have been passed. await WXDC.connect(provider.getSigner(BobAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("4")); await collateralTokenAdapter @@ -402,8 +883,29 @@ describe("CollateralTokenAdapter", () => { // won't be added as CollateralTokenAdapter cage before it get harvested. await hre.network.provider.send("hardhat_mine", ["0x01"]); + encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("cage"); + + proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Cage"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Cage")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute // Cage CollateralTokenAdapter - await collateralTokenAdapter.cage(); + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); expect(await collateralTokenAdapter.live()).to.be.eq(0); // Alice panic and decided to emergencyWithdraw. @@ -439,7 +941,29 @@ describe("CollateralTokenAdapter", () => { context("when all states are normal", async () => { it("can call emergencyWithdraw but the state will stay the same", async () => { await WXDC.connect(provider.getSigner(AliceAddress)).deposit({ value: ethers.constants.WeiPerEther.mul(1) }); - await collateralTokenAdapter.addToWhitelist(AliceAddress); + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("addToWhitelist", [AliceAddress]); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Add to whitelist"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Add to whitelist")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + //Alice is whiteListed to directly call deposit function on CollateralTokenAdapter + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); await WXDC.connect(provider.getSigner(AliceAddress)).approve(collateralTokenAdapter.address, ethers.utils.parseEther("1")); await collateralTokenAdapter .connect(provider.getSigner(AliceAddress)) @@ -467,7 +991,29 @@ describe("CollateralTokenAdapter", () => { describe("#cage/#uncage", async () => { context("when whitelist cage", async () => { it("should put CollateralTokenAdapter live = 0", async () => { - await collateralTokenAdapter.cage(); + let encodedFunctionCall = collateralTokenAdapter.interface.encodeFunctionData("cage"); + + let proposalTx = await governor.propose([collateralTokenAdapter.address], [0], [encodedFunctionCall], "Cage"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Cage")); + await governor.queue([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + // Cage + await governor.execute([collateralTokenAdapter.address], [0], [encodedFunctionCall], descriptionHash); expect(await collateralTokenAdapter.live()).to.be.eq(0); }); }); diff --git a/test/integration/FathomProxyActions.test.js b/test/integration/FathomProxyActions.test.js index eb7bc870..f45a553f 100644 --- a/test/integration/FathomProxyActions.test.js +++ b/test/integration/FathomProxyActions.test.js @@ -1,6 +1,7 @@ const { ethers } = require("hardhat"); const provider = ethers.provider; const { expect } = require("chai"); +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); const { WeiPerRay, WeiPerWad } = require("../helper/unit"); const AssertHelpers = require("../helper/assert"); @@ -9,6 +10,11 @@ const PositionHelper = require("../helper/positions"); const { getProxy } = require("../../common/proxies"); const pools = require("../../common/collateral"); +const MIN_DELAY = 3600; // 1 hour +const VOTING_PERIOD = 50400; // This is how long voting lasts, 1 week +const VOTING_DELAY = 1; // How many blocks till a proposal vote becomes active +const VOTE_WAY = 1; + describe("Position Closure without collateral withdrawal", () => { // Proxy wallet let aliceProxyWallet; @@ -18,19 +24,22 @@ describe("Position Closure without collateral withdrawal", () => { let bookKeeper; let simplePriceFeed; let fathomStablecoin; + let governor; let reentrancyAttacker; let reentrancyAttacker2; let reEntrantProxyWallet; let reEntrantProxyWallet2; + let DeployerAddress; let AliceAddress; let DevAddress; beforeEach(async () => { await deployments.fixture(["DeployTestFixture"]); - const { allice, dev } = await getNamedAccounts(); + const { deployer, allice, dev } = await getNamedAccounts(); + DeployerAddress = deployer; AliceAddress = allice; DevAddress = dev; @@ -39,16 +48,46 @@ describe("Position Closure without collateral withdrawal", () => { const SimplePriceFeed = await deployments.get("SimplePriceFeed"); simplePriceFeed = await ethers.getContractAt("SimplePriceFeed", SimplePriceFeed.address); + const Governor = await deployments.get("ProtocolGovernor"); + governor = await ethers.getContractAt("ProtocolGovernor", Governor.address); + bookKeeper = await getProxy(proxyFactory, "BookKeeper"); const stabilityFeeCollector = await getProxy(proxyFactory, "StabilityFeeCollector"); positionManager = await getProxy(proxyFactory, "PositionManager"); fathomStablecoin = await getProxy(proxyFactory, "FathomStablecoin"); const proxyWalletRegistry = await getProxy(proxyFactory, "ProxyWalletRegistry"); - proxyWalletRegistry.setDecentralizedMode(true); - ({ - proxyWallets: [aliceProxyWallet], - } = await createProxyWallets([AliceAddress])); + let values = [0]; + let targets = [proxyWalletRegistry.address]; + let calldatas = [proxyWalletRegistry.interface.encodeFunctionData("setDecentralizedMode", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await proxyWalletRegistry.setDecentralizedMode(true); + + // ({ + // proxyWallets: [aliceProxyWallet], + // } = await createProxyWallets([AliceAddress])); + + await proxyWalletRegistry.build(AliceAddress); + const proxyWalletAddress = await proxyWalletRegistry.proxies(AliceAddress); + aliceProxyWallet = await ethers.getContractAt("ProxyWallet", proxyWalletAddress); const ReentrancyAttacker = await deployments.get("ReentrancyAttacker"); reentrancyAttacker = await ethers.getContractAt("ReentrancyAttacker", ReentrancyAttacker.address); @@ -65,14 +104,39 @@ describe("Position Closure without collateral withdrawal", () => { await reentrancyAttacker.setProxyWallet(reEntrantProxyWallet); await reentrancyAttacker2.setProxyWallet(reEntrantProxyWallet2); - await stabilityFeeCollector.setSystemDebtEngine(DevAddress); + values = [0, 0]; + targets = [stabilityFeeCollector.address, simplePriceFeed.address]; + calldatas = [ + stabilityFeeCollector.interface.encodeFunctionData("setSystemDebtEngine", [DevAddress]), + simplePriceFeed.interface.encodeFunctionData("setPrice", [WeiPerRay]), + ]; + proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stabilityFeeCollector.setSystemDebtEngine(DevAddress); await fathomStablecoin.connect(provider.getSigner(AliceAddress)).approve(aliceProxyWallet.address, WeiPerWad.mul(10000)); }); describe("#wipeAndUnlockXDC", () => { context("open position and pay back debt without collateral withdrawal", () => { it("should be success", async () => { - await simplePriceFeed.setPrice(WeiPerRay); + // await simplePriceFeed.setPrice(WeiPerRay); // position 1 // a. open a new position @@ -98,7 +162,7 @@ describe("Position Closure without collateral withdrawal", () => { }); context("try reentry with ReentrancyAttacker", () => { it("should not make change to the position", async () => { - await simplePriceFeed.setPrice(WeiPerRay); + // await simplePriceFeed.setPrice(WeiPerRay); // position 1 // a. open a new position @@ -128,7 +192,7 @@ describe("Position Closure without collateral withdrawal", () => { }); context("try reentry with ReentrancyAttacker2", () => { it("should fail", async () => { - await simplePriceFeed.setPrice(WeiPerRay); + // await simplePriceFeed.setPrice(WeiPerRay); // position 1 // a. open a new position @@ -158,7 +222,7 @@ describe("Position Closure without collateral withdrawal", () => { describe("#wipeAllAndUnlockXDC", () => { context("open position and pay back debt without collateral withdrawal", () => { it("should be success", async () => { - await simplePriceFeed.setPrice(WeiPerRay); + // await simplePriceFeed.setPrice(WeiPerRay); // position 1 // a. open a new position diff --git a/test/integration/FlashMintModule.test.js b/test/integration/FlashMintModule.test.js index d61ce159..7e0bb59f 100644 --- a/test/integration/FlashMintModule.test.js +++ b/test/integration/FlashMintModule.test.js @@ -1,11 +1,18 @@ const { ethers } = require("hardhat"); +const provider = ethers.provider; const { expect } = require("chai"); const { parseEther } = ethers.utils; +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); const { WeiPerRay, WeiPerRad } = require("../helper/unit"); const { getProxy } = require("../../common/proxies"); +const MIN_DELAY = 3600; // 1 hour +const VOTING_PERIOD = 50400; // This is how long voting lasts, 1 week +const VOTING_DELAY = 1; // How many blocks till a proposal vote becomes active +const VOTE_WAY = 1; + describe("FlastMintModule", () => { // Contracts let bookKeeper; @@ -19,6 +26,7 @@ describe("FlastMintModule", () => { let bookKeeperFlashMintArbitrager; let stablecoinAdapter; let DeployerAddress; + let governor; beforeEach(async () => { await deployments.fixture(["DeployTestFixture"]); @@ -28,6 +36,9 @@ describe("FlastMintModule", () => { const ProxyFactory = await deployments.get("FathomProxyFactory"); const proxyFactory = await ethers.getContractAt("FathomProxyFactory", ProxyFactory.address); + const Governor = await deployments.get("ProtocolGovernor"); + governor = await ethers.getContractAt("ProtocolGovernor", Governor.address); + bookKeeper = await getProxy(proxyFactory, "BookKeeper"); stableSwapModule = await getProxy(proxyFactory, "StableSwapModule"); stableSwapModuleWrapper = await getProxy(proxyFactory, "StableSwapModuleWrapper"); @@ -48,15 +59,46 @@ describe("FlastMintModule", () => { describe("#flashLoan", async () => { context("receiver doesn't have enough tokens to return the loan + fee", async () => { it("should revert", async () => { - await fathomStablecoin.mint(DeployerAddress, parseEther("3000")); - await bookKeeper.mintUnbackedStablecoin(stablecoinAdapter.address, stablecoinAdapter.address, WeiPerRad.mul(3500)); + const values = [0, 0, 0, 0]; + const targets = [fathomStablecoin.address, bookKeeper.address, flashMintModule.address, stableSwapModule.address]; + + const calldatas = [ + fathomStablecoin.interface.encodeFunctionData("mint", [DeployerAddress, parseEther("3000")]), + bookKeeper.interface.encodeFunctionData("mintUnbackedStablecoin", [ + stablecoinAdapter.address, + stablecoinAdapter.address, + WeiPerRad.mul(3500), + ]), + flashMintModule.interface.encodeFunctionData("addToWhitelist", [DeployerAddress]), + stableSwapModule.interface.encodeFunctionData("addToWhitelist", [flashMintArbitrager.address]), + ]; + + const proposalTx = await governor.propose(targets, values, calldatas, "Set FlashMintModule"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set FlashMintModule")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + await fathomStablecoin.approve(stableSwapModuleWrapper.address, parseEther("3000")); await USDT.mint(DeployerAddress, parseEther("3500")); await USDT.approve(stableSwapModuleWrapper.address, parseEther("3000")); await stableSwapModuleWrapper.depositTokens(parseEther("3000")); - await flashMintModule.addToWhitelist(DeployerAddress); - await stableSwapModule.addToWhitelist(flashMintArbitrager.address); await USDT.approve(router.address, parseEther("500")); await router.deposit(USDT.address, parseEther("500")); await expect( @@ -72,15 +114,45 @@ describe("FlastMintModule", () => { context("receiver has enough tokens to return the loan + fee", async () => { it("should success", async () => { - await fathomStablecoin.mint(DeployerAddress, parseEther("3500")); - await bookKeeper.mintUnbackedStablecoin(stablecoinAdapter.address, stablecoinAdapter.address, WeiPerRad.mul(3500)); + const values = [0, 0, 0, 0]; + const targets = [fathomStablecoin.address, bookKeeper.address, flashMintModule.address, stableSwapModule.address]; + + const calldatas = [ + fathomStablecoin.interface.encodeFunctionData("mint", [DeployerAddress, parseEther("3500")]), + bookKeeper.interface.encodeFunctionData("mintUnbackedStablecoin", [ + stablecoinAdapter.address, + stablecoinAdapter.address, + WeiPerRad.mul(3500), + ]), + flashMintModule.interface.encodeFunctionData("addToWhitelist", [DeployerAddress]), + stableSwapModule.interface.encodeFunctionData("addToWhitelist", [flashMintArbitrager.address]), + ]; + + const proposalTx = await governor.propose(targets, values, calldatas, "Set FlashMintModule"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set FlashMintModule")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); await fathomStablecoin.approve(stableSwapModuleWrapper.address, parseEther("3000")); await USDT.mint(DeployerAddress, parseEther("3500")); await USDT.approve(stableSwapModuleWrapper.address, parseEther("3000")); await stableSwapModuleWrapper.depositTokens(parseEther("3000")); - await flashMintModule.addToWhitelist(DeployerAddress); - await stableSwapModule.addToWhitelist(flashMintArbitrager.address); await USDT.approve(router.address, parseEther("500")); await router.deposit(USDT.address, parseEther("500")); await router.setProfit(true); @@ -103,15 +175,46 @@ describe("FlastMintModule", () => { describe("#bookKeeperFlashLoan", async () => { context("receiver doesn't have enough tokens to return the loan + fee", async () => { it("should revert", async () => { - await fathomStablecoin.mint(DeployerAddress, parseEther("3500")); - await bookKeeper.mintUnbackedStablecoin(stablecoinAdapter.address, stablecoinAdapter.address, WeiPerRad.mul(3500)); + const values = [0, 0, 0, 0]; + const targets = [fathomStablecoin.address, bookKeeper.address, flashMintModule.address, stableSwapModule.address]; + + const calldatas = [ + fathomStablecoin.interface.encodeFunctionData("mint", [DeployerAddress, parseEther("3500")]), + bookKeeper.interface.encodeFunctionData("mintUnbackedStablecoin", [ + stablecoinAdapter.address, + stablecoinAdapter.address, + WeiPerRad.mul(3500), + ]), + flashMintModule.interface.encodeFunctionData("addToWhitelist", [DeployerAddress]), + stableSwapModule.interface.encodeFunctionData("addToWhitelist", [bookKeeperFlashMintArbitrager.address]), + ]; + + const proposalTx = await governor.propose(targets, values, calldatas, "Set FlashMintModule"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set FlashMintModule")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + await fathomStablecoin.approve(stableSwapModuleWrapper.address, parseEther("3000")); await USDT.mint(DeployerAddress, parseEther("3500")); await USDT.approve(stableSwapModuleWrapper.address, parseEther("3000")); await stableSwapModuleWrapper.depositTokens(parseEther("3000")); - await flashMintModule.addToWhitelist(DeployerAddress); - await stableSwapModule.addToWhitelist(bookKeeperFlashMintArbitrager.address); await USDT.approve(router.address, parseEther("500")); await router.deposit(USDT.address, parseEther("500")); @@ -127,15 +230,46 @@ describe("FlastMintModule", () => { context("receiver has enough tokens to return the loan + fee", async () => { it("should success", async () => { - await fathomStablecoin.mint(DeployerAddress, parseEther("3500")); - await bookKeeper.mintUnbackedStablecoin(stablecoinAdapter.address, stablecoinAdapter.address, WeiPerRad.mul(3500)); + const values = [0, 0, 0, 0]; + const targets = [fathomStablecoin.address, bookKeeper.address, flashMintModule.address, stableSwapModule.address]; + + const calldatas = [ + fathomStablecoin.interface.encodeFunctionData("mint", [DeployerAddress, parseEther("3500")]), + bookKeeper.interface.encodeFunctionData("mintUnbackedStablecoin", [ + stablecoinAdapter.address, + stablecoinAdapter.address, + WeiPerRad.mul(3500), + ]), + flashMintModule.interface.encodeFunctionData("addToWhitelist", [DeployerAddress]), + stableSwapModule.interface.encodeFunctionData("addToWhitelist", [bookKeeperFlashMintArbitrager.address]), + ]; + + const proposalTx = await governor.propose(targets, values, calldatas, "Set FlashMintModule"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set FlashMintModule")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + await fathomStablecoin.approve(stableSwapModuleWrapper.address, parseEther("3000")); await USDT.mint(DeployerAddress, parseEther("3500")); await USDT.approve(stableSwapModuleWrapper.address, parseEther("3000")); await stableSwapModuleWrapper.depositTokens(parseEther("3000")); - await flashMintModule.addToWhitelist(DeployerAddress); - await stableSwapModule.addToWhitelist(bookKeeperFlashMintArbitrager.address); await USDT.approve(router.address, parseEther("500")); await router.deposit(USDT.address, parseEther("500")); await router.setProfit(true); diff --git a/test/integration/LiquidationEngine.test.js b/test/integration/LiquidationEngine.test.js index 48f92021..423be0d6 100644 --- a/test/integration/LiquidationEngine.test.js +++ b/test/integration/LiquidationEngine.test.js @@ -1,7 +1,7 @@ const { ethers } = require("hardhat"); const provider = ethers.provider; const { expect } = require("chai"); -const { time } = require("@nomicfoundation/hardhat-network-helpers"); +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); const { BigNumber } = ethers; const { parseEther, parseUnits, defaultAbiCoder, formatBytes32String } = ethers.utils; @@ -19,6 +19,11 @@ const TREASURY_FEE_BPS = BigNumber.from(5000); const COLLATERAL_POOL_ID = formatBytes32String("XDC"); const BPS = BigNumber.from(10000); +const MIN_DELAY = 3600; // 1 hour +const VOTING_PERIOD = 50400; // This is how long voting lasts, 1 week +const VOTING_DELAY = 1; // How many blocks till a proposal vote becomes active +const VOTE_WAY = 1; + describe("LiquidationEngine", () => { // Contracts let aliceProxyWallet; @@ -34,20 +39,26 @@ describe("LiquidationEngine", () => { let systemDebtEngine; let collateralPoolConfig; let priceOracle; + let governor; + let DeployerAddress; let AliceAddress; let BobAddress; beforeEach(async () => { await deployments.fixture(["DeployTestFixture"]); - const { allice, bob } = await getNamedAccounts(); + const { deployer, allice, bob } = await getNamedAccounts(); + DeployerAddress = deployer; AliceAddress = allice; BobAddress = bob; const ProxyFactory = await deployments.get("FathomProxyFactory"); const proxyFactory = await ethers.getContractAt("FathomProxyFactory", ProxyFactory.address); + const Governor = await deployments.get("ProtocolGovernor"); + governor = await ethers.getContractAt("ProtocolGovernor", Governor.address); + const _WXDC = await deployments.get("WXDC"); WXDC = await ethers.getContractAt("WXDC", _WXDC.address); @@ -61,7 +72,49 @@ describe("LiquidationEngine", () => { fathomStablecoin = await getProxy(proxyFactory, "FathomStablecoin"); priceOracle = await getProxy(proxyFactory, "PriceOracle"); const proxyWalletRegistry = await getProxy(proxyFactory, "ProxyWalletRegistry"); - proxyWalletRegistry.setDecentralizedMode(true); + + const values = [0, 0, 0, 0, 0, 0, 0]; + const targets = [ + proxyWalletRegistry.address, + collateralPoolConfig.address, + collateralPoolConfig.address, + collateralPoolConfig.address, + collateralPoolConfig.address, + collateralPoolConfig.address, + liquidationEngineAsAdmin.address, + ]; + + const calldatas = [ + proxyWalletRegistry.interface.encodeFunctionData("setDecentralizedMode", [true]), + collateralPoolConfig.interface.encodeFunctionData("setStabilityFeeRate", [pools.XDC, WeiPerRay]), + collateralPoolConfig.interface.encodeFunctionData("setLiquidationRatio", [pools.XDC, WeiPerRay]), + collateralPoolConfig.interface.encodeFunctionData("setLiquidatorIncentiveBps", [pools.XDC, LIQUIDATOR_INCENTIVE_BPS]), + collateralPoolConfig.interface.encodeFunctionData("setCloseFactorBps", [pools.XDC, CLOSE_FACTOR_BPS]), + collateralPoolConfig.interface.encodeFunctionData("setTreasuryFeesBps", [pools.XDC, TREASURY_FEE_BPS]), + liquidationEngineAsAdmin.interface.encodeFunctionData("addToWhitelist", [BobAddress]), + ]; + + const proposalTx = await governor.propose(targets, values, calldatas, "Set LiquidationEngine"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set LiquidationEngine")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); liquidationEngine = await ethers.getContractAt("LiquidationEngine", liquidationEngineAsAdmin.address, provider.getSigner(BobAddress)); @@ -74,31 +127,73 @@ describe("LiquidationEngine", () => { proxyWallets: [aliceProxyWallet], } = await createProxyWallets([AliceAddress, BobAddress])); - await collateralPoolConfig.setStabilityFeeRate(pools.XDC, WeiPerRay); - await collateralPoolConfig.setLiquidationRatio(pools.XDC, WeiPerRay); - await collateralPoolConfig.setLiquidatorIncentiveBps(pools.XDC, LIQUIDATOR_INCENTIVE_BPS); - await collateralPoolConfig.setCloseFactorBps(pools.XDC, CLOSE_FACTOR_BPS); - await collateralPoolConfig.setTreasuryFeesBps(pools.XDC, TREASURY_FEE_BPS); - await bookKeeper.connect(provider.getSigner(BobAddress)).whitelist(liquidationEngine.address); await bookKeeper.connect(provider.getSigner(BobAddress)).whitelist(fixedSpreadLiquidationStrategy.address); - await liquidationEngineAsAdmin.addToWhitelist(BobAddress); }); describe("#liquidate", async () => { context("price drop but does not make the position underwater", async () => { it("should revert", async () => { // 1. Set price for XDC to 2 USD - await simplePriceFeed.setPrice(WeiPerRay.mul(2)); - await priceOracle.setPrice(COLLATERAL_POOL_ID); + let values = [0, 0]; + let targets = [simplePriceFeed.address, priceOracle.address]; + let calldatas = [ + simplePriceFeed.interface.encodeFunctionData("setPrice", [WeiPerRay.mul(2)]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + ]; + + let proposalTx = await governor.propose(targets, values, calldatas, "Set Price"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Price")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); // 2. Alice open a new position with 1 XDC and draw 1 FXD await PositionHelper.openXDCPositionAndDraw(aliceProxyWallet, AliceAddress, COLLATERAL_POOL_ID, WeiPerWad, WeiPerWad); const alicePositionAddress = await positionManager.positions(1); - // 3. XDC price drop to 1 USD - await simplePriceFeed.setPrice(WeiPerRay); - await priceOracle.setPrice(COLLATERAL_POOL_ID); + // // 3. XDC price drop to 1 USD + calldatas = [ + simplePriceFeed.interface.encodeFunctionData("setPrice", [WeiPerRay]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + ]; + proposalTx = await governor.propose(targets, values, calldatas, "Set Price New"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Price New")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); // 3.5 whitelist bob as liquidator // 4. Bob try to liquidate Alice's position but failed due to the price did not drop low enough @@ -118,17 +213,72 @@ describe("LiquidationEngine", () => { context("safety buffer -0.1%, but liquidator does not have enough FXD to liquidate", async () => { it("should revert", async () => { // 1. Set priceWithSafetyMargin for XDC to 2 USD - await simplePriceFeed.setPrice(WeiPerRay.mul(2)); - await priceOracle.setPrice(COLLATERAL_POOL_ID); - await collateralPoolConfig.setLiquidationRatio(COLLATERAL_POOL_ID, WeiPerRay); + let values = [0, 0, 0]; + let targets = [simplePriceFeed.address, priceOracle.address, collateralPoolConfig.address]; + let calldatas = [ + simplePriceFeed.interface.encodeFunctionData("setPrice", [WeiPerRay.mul(2)]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + collateralPoolConfig.interface.encodeFunctionData("setLiquidationRatio", [COLLATERAL_POOL_ID, WeiPerRay]), + ]; + + let proposalTx = await governor.propose(targets, values, calldatas, "Set Price"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Price")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + // await simplePriceFeed.setPrice(WeiPerRay.mul(2)); + // await priceOracle.setPrice(COLLATERAL_POOL_ID); + // await collateralPoolConfig.setLiquidationRatio(COLLATERAL_POOL_ID, WeiPerRay); // 2. Alice open a new position with 1 XDC and draw 1 FXD await PositionHelper.openXDCPositionAndDraw(aliceProxyWallet, AliceAddress, COLLATERAL_POOL_ID, WeiPerWad, WeiPerWad); const alicePositionAddress = await positionManager.positions(1); // 3. XDC price drop to 0.99 USD - await simplePriceFeed.setPrice(WeiPerRay.sub(1).div(1e9)); - await priceOracle.setPrice(COLLATERAL_POOL_ID); + values = [0, 0]; + targets = [simplePriceFeed.address, priceOracle.address]; + calldatas = [ + simplePriceFeed.interface.encodeFunctionData("setPrice", [WeiPerRay.sub(1).div(1e9)]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + ]; + + proposalTx = await governor.propose(targets, values, calldatas, "Set Price New"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Price New")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); // 4. Bob liquidate Alice's position up to full close factor successfully const debtShareToRepay = parseEther("0.5"); @@ -357,16 +507,50 @@ describe("LiquidationEngine", () => { for (let i = 0; i < testParams.length; i++) { const testParam = testParams[i]; it(testParam.label, async () => { - await fathomStablecoin.connect(provider.getSigner(BobAddress)).approve(fixedSpreadLiquidationStrategy.address, ethers.constants.MaxUint256); - await fathomStablecoin.mint(BobAddress, parseUnits(testParam.debtShareToRepay, 46)); - - await collateralPoolConfig.setLiquidatorIncentiveBps(COLLATERAL_POOL_ID, testParam.liquidatorIncentiveBps); - await collateralPoolConfig.setCloseFactorBps(COLLATERAL_POOL_ID, testParam.closeFactorBps); - await simplePriceFeed.setPrice(parseUnits(testParam.startingPrice, 18)); - await priceOracle.setPrice(COLLATERAL_POOL_ID); let ratio = WeiPerRay.mul(1000).div(parseUnits(testParam.collateralFactor, 3)); - await collateralPoolConfig.setLiquidationRatio(COLLATERAL_POOL_ID, ratio); - await collateralPoolConfig.setDebtFloor(COLLATERAL_POOL_ID, parseUnits(testParam.debtFloor, 45)); + await fathomStablecoin.connect(provider.getSigner(BobAddress)).approve(fixedSpreadLiquidationStrategy.address, ethers.constants.MaxUint256); + // 1. Set priceWithSafetyMargin for XDC to 2 USD + let values = [0, 0, 0, 0, 0, 0, 0]; + let targets = [ + fathomStablecoin.address, + collateralPoolConfig.address, + collateralPoolConfig.address, + simplePriceFeed.address, + priceOracle.address, + collateralPoolConfig.address, + collateralPoolConfig.address, + ]; + let calldatas = [ + fathomStablecoin.interface.encodeFunctionData("mint", [BobAddress, parseUnits(testParam.debtShareToRepay, 46)]), + collateralPoolConfig.interface.encodeFunctionData("setLiquidatorIncentiveBps", [COLLATERAL_POOL_ID, testParam.liquidatorIncentiveBps]), + collateralPoolConfig.interface.encodeFunctionData("setCloseFactorBps", [COLLATERAL_POOL_ID, testParam.closeFactorBps]), + simplePriceFeed.interface.encodeFunctionData("setPrice", [parseUnits(testParam.startingPrice, 18)]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + collateralPoolConfig.interface.encodeFunctionData("setLiquidationRatio", [COLLATERAL_POOL_ID, ratio]), + collateralPoolConfig.interface.encodeFunctionData("setDebtFloor", [COLLATERAL_POOL_ID, parseUnits(testParam.debtFloor, 45)]), + ]; + + let proposalTx = await governor.propose(targets, values, calldatas, "Set Protocol Config"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Protocol Config")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); // 2. Alice open a new position with 1 XDC and draw 1 FXD const lockedCollateralAmount = parseEther(testParam.collateralAmount); @@ -383,8 +567,34 @@ describe("LiquidationEngine", () => { const alicePosition = await bookKeeper.positions(COLLATERAL_POOL_ID, alicePositionAddress); // 3. XDC price drop to 0.99 USD - await simplePriceFeed.setPrice(parseUnits(testParam.nextPrice, 18)); - await priceOracle.setPrice(COLLATERAL_POOL_ID); + values = [0, 0]; + targets = [simplePriceFeed.address, priceOracle.address]; + calldatas = [ + simplePriceFeed.interface.encodeFunctionData("setPrice", [parseUnits(testParam.nextPrice, 18)]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + ]; + + proposalTx = await governor.propose(targets, values, calldatas, "Set Protocol Config"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Protocol Config")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); // 4. Bob liquidate Alice's position up to full close factor successfully const debtShareToRepay = parseEther(testParam.debtShareToRepay); @@ -399,7 +609,30 @@ describe("LiquidationEngine", () => { ); const bobWETHAfterLiq = await WXDC.balanceOf(BobAddress); // 5. Settle system bad debt - await systemDebtEngine.settleSystemBadDebt(await bookKeeper.stablecoin(systemDebtEngine.address)); + values = [0]; + targets = [systemDebtEngine.address]; + calldatas = [systemDebtEngine.interface.encodeFunctionData("settleSystemBadDebt", [await bookKeeper.stablecoin(systemDebtEngine.address)])]; + proposalTx = await governor.propose(targets, values, calldatas, "Set Protocol Config"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Protocol Config")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); const bobStablecoinAfterLiquidation = await fathomStablecoin.balanceOf(BobAddress); //await bookKeeper.stablecoin(BobAddress) @@ -451,13 +684,46 @@ describe("LiquidationEngine", () => { expectedSystemBadDebt: "0", }; it(testParam.label, async () => { - await collateralPoolConfig.setLiquidatorIncentiveBps(COLLATERAL_POOL_ID, testParam.liquidatorIncentiveBps); - await collateralPoolConfig.setCloseFactorBps(COLLATERAL_POOL_ID, testParam.closeFactorBps); - await simplePriceFeed.setPrice(parseUnits(testParam.startingPrice, 18)); - await priceOracle.setPrice(COLLATERAL_POOL_ID); let ratio = WeiPerRay.mul(1000).div(parseUnits(testParam.collateralFactor, 3)); - await collateralPoolConfig.setLiquidationRatio(COLLATERAL_POOL_ID, ratio); - await collateralPoolConfig.setDebtFloor(COLLATERAL_POOL_ID, parseUnits(testParam.debtFloor, 45)); + let values = [0, 0, 0, 0, 0, 0]; + let targets = [ + collateralPoolConfig.address, + collateralPoolConfig.address, + simplePriceFeed.address, + priceOracle.address, + collateralPoolConfig.address, + collateralPoolConfig.address, + ]; + let calldatas = [ + collateralPoolConfig.interface.encodeFunctionData("setLiquidatorIncentiveBps", [COLLATERAL_POOL_ID, testParam.liquidatorIncentiveBps]), + collateralPoolConfig.interface.encodeFunctionData("setCloseFactorBps", [COLLATERAL_POOL_ID, testParam.closeFactorBps]), + simplePriceFeed.interface.encodeFunctionData("setPrice", [parseUnits(testParam.startingPrice, 18)]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + collateralPoolConfig.interface.encodeFunctionData("setLiquidationRatio", [COLLATERAL_POOL_ID, ratio]), + collateralPoolConfig.interface.encodeFunctionData("setDebtFloor", [COLLATERAL_POOL_ID, parseUnits(testParam.debtFloor, 45)]), + ]; + + let proposalTx = await governor.propose(targets, values, calldatas, "Set Protocol Config"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Protocol Config")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); // 2. Alice open a new position with 1 XDC and draw 1 FXD const lockedCollateralAmount = parseEther(testParam.collateralAmount); @@ -475,8 +741,33 @@ describe("LiquidationEngine", () => { const alicePosition = await bookKeeper.positions(COLLATERAL_POOL_ID, alicePositionAddress); // 3. XDC price drop to 0.99 USD - await simplePriceFeed.setPrice(parseUnits(testParam.nextPrice, 18)); - await priceOracle.setPrice(COLLATERAL_POOL_ID); + values = [0, 0]; + targets = [simplePriceFeed.address, priceOracle.address]; + calldatas = [ + simplePriceFeed.interface.encodeFunctionData("setPrice", [parseUnits(testParam.nextPrice, 18)]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + ]; + proposalTx = await governor.propose(targets, values, calldatas, "Set Protocol Config"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Protocol Config")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); // 4. Bob liquidate Alice's position up to full close factor successfully const debtShareToRepay = parseEther(testParam.debtShareToRepay); @@ -491,7 +782,30 @@ describe("LiquidationEngine", () => { ); // 5. Settle system bad debt - await systemDebtEngine.settleSystemBadDebt(await bookKeeper.stablecoin(systemDebtEngine.address)); + values = [0]; + targets = [systemDebtEngine.address]; + calldatas = [systemDebtEngine.interface.encodeFunctionData("settleSystemBadDebt", [await bookKeeper.stablecoin(systemDebtEngine.address)])]; + proposalTx = await governor.propose(targets, values, calldatas, "Set Protocol Config"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Protocol Config")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); const bobStablecoinAfterLiquidation = await bookKeeper.stablecoin(BobAddress); @@ -547,17 +861,56 @@ describe("LiquidationEngine", () => { context("safety buffer -20%, position is liquidated up to full close factor with some interest and debt floor", async () => { it("should success", async () => { + let ratio = WeiPerRay.mul(1000).div(parseUnits("0.8", 3)); // 0 whitelist bob as liquidator - await fathomStablecoin.mint(BobAddress, parseUnits("3000", 45)); - await fathomStablecoin.connect(provider.getSigner(BobAddress)).approve(fixedSpreadLiquidationStrategy.address, ethers.constants.MaxUint256); + let values = [0, 0, 0, 0, 0]; + let targets = [ + fathomStablecoin.address, + simplePriceFeed.address, + priceOracle.address, + collateralPoolConfig.address, + collateralPoolConfig.address, + ]; + let calldatas = [ + fathomStablecoin.interface.encodeFunctionData("mint", [BobAddress, parseUnits("3000", 45)]), + simplePriceFeed.interface.encodeFunctionData("setPrice", [parseUnits("367", 18)]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + collateralPoolConfig.interface.encodeFunctionData("setLiquidationRatio", [COLLATERAL_POOL_ID, ratio]), + collateralPoolConfig.interface.encodeFunctionData("setDebtFloor", [COLLATERAL_POOL_ID, parseEther("100").mul(WeiPerRay)]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Set Protocol Config"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Protocol Config")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + + /** await fathomStablecoin.mint(BobAddress, parseUnits("3000", 45)); */ // 1. Set priceWithSafetyMargin for XDC to 420 USD - await simplePriceFeed.setPrice(parseUnits("367", 18)); - await priceOracle.setPrice(COLLATERAL_POOL_ID); - let ratio = WeiPerRay.mul(1000).div(parseUnits("0.8", 3)); - await collateralPoolConfig.setLiquidationRatio(COLLATERAL_POOL_ID, ratio); + /**await simplePriceFeed.setPrice(parseUnits("367", 18)); */ + /** await priceOracle.setPrice(COLLATERAL_POOL_ID); */ + + /** await collateralPoolConfig.setLiquidationRatio(COLLATERAL_POOL_ID, ratio); */ // await collateralPoolConfig.setPriceWithSafetyMargin(COLLATERAL_POOL_ID, parseUnits("294", 27), { gasLimit: 1000000 }) - await collateralPoolConfig.setDebtFloor(COLLATERAL_POOL_ID, parseEther("100").mul(WeiPerRay)); + /** await collateralPoolConfig.setDebtFloor(COLLATERAL_POOL_ID, parseEther("100").mul(WeiPerRay)); */ + + await fathomStablecoin.connect(provider.getSigner(BobAddress)).approve(fixedSpreadLiquidationStrategy.address, ethers.constants.MaxUint256); // 2. Alice open a new position with 10 XDC and draw 2000 FXD const lockedCollateralAmount = parseEther("10"); @@ -566,7 +919,36 @@ describe("LiquidationEngine", () => { await PositionHelper.openXDCPositionAndDraw(aliceProxyWallet, AliceAddress, COLLATERAL_POOL_ID, lockedCollateralAmount, drawStablecoinAmount); // Set stability fee rate to 0.5% APR - await collateralPoolConfig.setStabilityFeeRate(COLLATERAL_POOL_ID, BigNumber.from("1000000000158153903837946258")); + values = [0]; + targets = [collateralPoolConfig.address]; + calldatas = [ + collateralPoolConfig.interface.encodeFunctionData("setStabilityFeeRate", [ + COLLATERAL_POOL_ID, + BigNumber.from("1000000000158153903837946258"), + ]), + ]; + proposalTx = await governor.propose(targets, values, calldatas, "Set Stability Fee Rate"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Stability Fee Rate")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + /** await collateralPoolConfig.setStabilityFeeRate(COLLATERAL_POOL_ID, BigNumber.from("1000000000158153903837946258")); */ const alicePositionAddress = await positionManager.positions(1); // const fathomStablecoinBalance = await fathomStablecoin.balanceOf(AliceAddress) @@ -579,8 +961,36 @@ describe("LiquidationEngine", () => { (await collateralPoolConfig.collateralPools(COLLATERAL_POOL_ID)).debtAccumulatedRate ); AssertHelpers.assertAlmostEqual(aliceDebtValueAfterOneYear.toString(), parseEther("2010").mul(WeiPerRay).toString()); - await simplePriceFeed.setPrice(parseEther("249.37")); - await priceOracle.setPrice(COLLATERAL_POOL_ID); + values = [0, 0]; + targets = [simplePriceFeed.address, priceOracle.address]; + calldatas = [ + simplePriceFeed.interface.encodeFunctionData("setPrice", [parseEther("249.37")]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + ]; + proposalTx = await governor.propose(targets, values, calldatas, "Set Price"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Price")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + + /** await simplePriceFeed.setPrice(parseEther("249.37")); */ + /** await priceOracle.setPrice(COLLATERAL_POOL_ID); */ // 4. Bob liquidate Alice's position up to full close factor successfully const debtShareToRepay = parseEther("1000"); @@ -596,7 +1006,33 @@ describe("LiquidationEngine", () => { const bobWETHAfterLiq = await WXDC.balanceOf(BobAddress); // // 5. Settle system bad debt - await systemDebtEngine.settleSystemBadDebt(await bookKeeper.systemBadDebt(systemDebtEngine.address)); + values = [0]; + targets = [systemDebtEngine.address]; + calldatas = [ + systemDebtEngine.interface.encodeFunctionData("settleSystemBadDebt", [await bookKeeper.systemBadDebt(systemDebtEngine.address)]), + ]; + proposalTx = await governor.propose(targets, values, calldatas, "Settle System Bad Debt"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Settle System Bad Debt")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + /** await systemDebtEngine.settleSystemBadDebt(await bookKeeper.systemBadDebt(systemDebtEngine.address)); */ AssertHelpers.assertAlmostEqual( (await bookKeeper.stablecoin(systemDebtEngine.address)).toString(), parseEther("10").mul(WeiPerRay).toString() @@ -643,16 +1079,56 @@ describe("LiquidationEngine", () => { ]; const testParam = testParams[0]; it(testParam.label, async () => { - await fathomStablecoin.mint(BobAddress, parseUnits("2000", 46)); + let ratio = WeiPerRay.mul(1000).div(parseUnits(testParam.collateralFactor, 3)); + let values = [0, 0, 0, 0, 0, 0, 0]; + let targets = [ + fathomStablecoin.address, + collateralPoolConfig.address, + collateralPoolConfig.address, + simplePriceFeed.address, + priceOracle.address, + collateralPoolConfig.address, + collateralPoolConfig.address, + ]; + let calldatas = [ + fathomStablecoin.interface.encodeFunctionData("mint", [BobAddress, parseUnits("2000", 46)]), + collateralPoolConfig.interface.encodeFunctionData("setLiquidatorIncentiveBps", [COLLATERAL_POOL_ID, testParam.liquidatorIncentiveBps]), + collateralPoolConfig.interface.encodeFunctionData("setCloseFactorBps", [COLLATERAL_POOL_ID, testParam.closeFactorBps]), + simplePriceFeed.interface.encodeFunctionData("setPrice", [parseUnits(testParam.startingPrice, 18)]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + collateralPoolConfig.interface.encodeFunctionData("setLiquidationRatio", [COLLATERAL_POOL_ID, ratio]), + collateralPoolConfig.interface.encodeFunctionData("setDebtFloor", [COLLATERAL_POOL_ID, parseUnits(testParam.debtFloor, 45)]), + ]; + /** await fathomStablecoin.mint(BobAddress, parseUnits("2000", 46)); */ await fathomStablecoin.connect(provider.getSigner(BobAddress)).approve(fixedSpreadLiquidationStrategy.address, ethers.constants.MaxUint256); + let proposalTx = await governor.propose(targets, values, calldatas, "Set Protocol Config"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; - await collateralPoolConfig.setLiquidatorIncentiveBps(COLLATERAL_POOL_ID, testParam.liquidatorIncentiveBps); - await collateralPoolConfig.setCloseFactorBps(COLLATERAL_POOL_ID, testParam.closeFactorBps); - await simplePriceFeed.setPrice(parseUnits(testParam.startingPrice, 18)); - await priceOracle.setPrice(COLLATERAL_POOL_ID); - let ratio = WeiPerRay.mul(1000).div(parseUnits(testParam.collateralFactor, 3)); - await collateralPoolConfig.setLiquidationRatio(COLLATERAL_POOL_ID, ratio); - await collateralPoolConfig.setDebtFloor(COLLATERAL_POOL_ID, parseUnits(testParam.debtFloor, 45)); + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Protocol Config")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + /** await collateralPoolConfig.setLiquidatorIncentiveBps(COLLATERAL_POOL_ID, testParam.liquidatorIncentiveBps); */ + /** await collateralPoolConfig.setCloseFactorBps(COLLATERAL_POOL_ID, testParam.closeFactorBps); */ + /** await simplePriceFeed.setPrice(parseUnits(testParam.startingPrice, 18)); */ + /** await priceOracle.setPrice(COLLATERAL_POOL_ID); */ + + /** await collateralPoolConfig.setLiquidationRatio(COLLATERAL_POOL_ID, ratio); */ + /** await collateralPoolConfig.setDebtFloor(COLLATERAL_POOL_ID, parseUnits(testParam.debtFloor, 45)); */ // 2. Alice open a new position with 1 XDC and draw 1 FXD const lockedCollateralAmount = parseEther(testParam.collateralAmount); @@ -666,8 +1142,35 @@ describe("LiquidationEngine", () => { const alicePosition = await bookKeeper.positions(COLLATERAL_POOL_ID, alicePositionAddress1); // 3. XDC price drop to 0.99 USD - await simplePriceFeed.setPrice(parseUnits(testParam.nextPrice, 18)); - await priceOracle.setPrice(COLLATERAL_POOL_ID); + values = [0, 0]; + targets = [simplePriceFeed.address, priceOracle.address]; + calldatas = [ + simplePriceFeed.interface.encodeFunctionData("setPrice", [parseUnits(testParam.nextPrice, 18)]), + priceOracle.interface.encodeFunctionData("setPrice", [COLLATERAL_POOL_ID]), + ]; + proposalTx = await governor.propose(targets, values, calldatas, "Set Price"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Price")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + /** await simplePriceFeed.setPrice(parseUnits(testParam.nextPrice, 18)); */ + /** await priceOracle.setPrice(COLLATERAL_POOL_ID); */ // 4. Bob liquidate Alice's position up to full close factor successfully const debtShareToRepay = parseEther(testParam.debtShareToRepay); @@ -684,7 +1187,31 @@ describe("LiquidationEngine", () => { const bobWETHAfterLiq = await WXDC.balanceOf(BobAddress); // 5. Settle system bad debt - await systemDebtEngine.settleSystemBadDebt(await bookKeeper.stablecoin(systemDebtEngine.address)); + values = [0]; + targets = [systemDebtEngine.address]; + calldatas = [systemDebtEngine.interface.encodeFunctionData("settleSystemBadDebt", [await bookKeeper.stablecoin(systemDebtEngine.address)])]; + proposalTx = await governor.propose(targets, values, calldatas, "Set System Debt Engine"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set System Debt Engine")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + /** await systemDebtEngine.settleSystemBadDebt(await bookKeeper.stablecoin(systemDebtEngine.address)); */ const bobStablecoinAfterLiquidation = await fathomStablecoin.balanceOf(BobAddress); diff --git a/test/integration/PositionPermissions.test.js b/test/integration/PositionPermissions.test.js index ba3040c6..4df72650 100644 --- a/test/integration/PositionPermissions.test.js +++ b/test/integration/PositionPermissions.test.js @@ -1,7 +1,7 @@ const { ethers } = require("hardhat"); const provider = ethers.provider; const { expect } = require("chai"); -const { time } = require("@nomicfoundation/hardhat-network-helpers"); +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); const { WeiPerRad, WeiPerRay, WeiPerWad } = require("../helper/unit"); const { createProxyWallets } = require("../helper/proxy-wallets"); @@ -9,6 +9,12 @@ const PositionHelper = require("../helper/positions"); const { getProxy } = require("../../common/proxies"); const pools = require("../../common/collateral"); + +const MIN_DELAY = 3600; // 1 hour +const VOTING_PERIOD = 50400; // This is how long voting lasts, 1 week +const VOTING_DELAY = 1; // How many blocks till a proposal vote becomes active +const VOTE_WAY = 1; + describe("PositionPermissions", () => { // Contracts let aliceProxyWallet; @@ -21,14 +27,17 @@ describe("PositionPermissions", () => { let stabilityFeeCollector; let simplePriceFeed; let collateralTokenAdapter2; + let governor; + let DeployerAddress; let AliceAddress; let BobAddress; beforeEach(async () => { await deployments.fixture(["DeployTestFixture"]); - const { allice, bob } = await getNamedAccounts(); + const { deployer, allice, bob } = await getNamedAccounts(); + DeployerAddress = deployer; AliceAddress = allice; BobAddress = bob; @@ -37,6 +46,9 @@ describe("PositionPermissions", () => { const SimplePriceFeed = await deployments.get("SimplePriceFeed"); simplePriceFeed = await ethers.getContractAt("SimplePriceFeed", SimplePriceFeed.address); + const Governor = await deployments.get("ProtocolGovernor"); + governor = await ethers.getContractAt("ProtocolGovernor", Governor.address); + bookKeeper = await getProxy(proxyFactory, "BookKeeper"); positionManager = await getProxy(proxyFactory, "PositionManager"); stablecoinAdapter = await getProxy(proxyFactory, "StablecoinAdapter"); @@ -46,7 +58,39 @@ describe("PositionPermissions", () => { collateralTokenAdapter = await getProxy(proxyFactory, "CollateralTokenAdapter"); const collateralPoolConfig = await getProxy(proxyFactory, "CollateralPoolConfig"); const proxyWalletRegistry = await getProxy(proxyFactory, "ProxyWalletRegistry"); - await proxyWalletRegistry.setDecentralizedMode(true); + let values = [0, 0, 0, 0]; + let targets = [ + proxyWalletRegistry.address, + simplePriceFeed.address, + collateralPoolConfig.address, + collateralPoolConfig.address, + ]; + let calldatas = [ + proxyWalletRegistry.interface.encodeFunctionData("setDecentralizedMode", [true]), + simplePriceFeed.interface.encodeFunctionData("setPrice", [WeiPerRay]), + collateralPoolConfig.interface.encodeFunctionData("setStabilityFeeRate", [pools.XDC, WeiPerRay]), + collateralPoolConfig.interface.encodeFunctionData("setStabilityFeeRate", [pools.GLD, WeiPerRay]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await proxyWalletRegistry.setDecentralizedMode(true); const collateralTokenAdapter2Addr = await collateralPoolConfig.getAdapter(pools.GLD); collateralTokenAdapter2 = await ethers.getContractAt("CollateralTokenAdapter", collateralTokenAdapter2Addr); @@ -63,9 +107,9 @@ describe("PositionPermissions", () => { await GLD.mint(BobAddress, WeiPerWad.mul(1000)); await GLD.connect(provider.getSigner(BobAddress)).approve(bobProxyWallet.address, WeiPerWad.mul(1000)); - await simplePriceFeed.setPrice(WeiPerRay); - await collateralPoolConfig.setStabilityFeeRate(pools.XDC, WeiPerRay); - await collateralPoolConfig.setStabilityFeeRate(pools.GLD, WeiPerRay); + // await simplePriceFeed.setPrice(WeiPerRay); + // await collateralPoolConfig.setStabilityFeeRate(pools.XDC, WeiPerRay); + // await collateralPoolConfig.setStabilityFeeRate(pools.GLD, WeiPerRay); await priceOracle.setPrice(pools.GLD); await time.increase(900); @@ -1928,7 +1972,33 @@ describe("PositionPermissions", () => { context("position ceiling", async () => { context("open position exceeds position ceiling", async () => { it("should revert", async () => { - await simplePriceFeed.setPrice(WeiPerRay.div(1000)); + let values = [0]; + let targets = [ + simplePriceFeed.address, + ]; + let calldatas = [ + simplePriceFeed.interface.encodeFunctionData("setPrice", [WeiPerRay.div(1000)]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await simplePriceFeed.setPrice(WeiPerRay.div(1000)); await expect( PositionHelper.openXDCPositionAndDraw(aliceProxyWallet, AliceAddress, pools.XDC, WeiPerWad.mul(2000), WeiPerWad.mul(1000001)) @@ -1937,7 +2007,34 @@ describe("PositionPermissions", () => { }); context("adjust position exceeds position ceiling", async () => { it("should revert", async () => { - await simplePriceFeed.setPrice(WeiPerRay.div(1000)); + let values = [0]; + let targets = [ + simplePriceFeed.address, + ]; + let calldatas = [ + simplePriceFeed.interface.encodeFunctionData("setPrice", [WeiPerRay.div(1000)]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await simplePriceFeed.setPrice(WeiPerRay.div(1000)); + await PositionHelper.openXDCPositionAndDraw(aliceProxyWallet, AliceAddress, pools.XDC, WeiPerWad.mul(2000), WeiPerWad.mul(1000000)); await expect( PositionHelper.adjustPosition( @@ -1952,4 +2049,4 @@ describe("PositionPermissions", () => { }); }); }); -}); +}); \ No newline at end of file diff --git a/test/integration/ProxyWallet.test.js b/test/integration/ProxyWallet.test.js index d43c2c71..f5a01afb 100644 --- a/test/integration/ProxyWallet.test.js +++ b/test/integration/ProxyWallet.test.js @@ -1,13 +1,21 @@ const { ethers } = require("hardhat"); const provider = ethers.provider; const { expect } = require("chai"); +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); const { getProxy } = require("../../common/proxies"); +const MIN_DELAY = 3600; // 1 hour +const VOTING_PERIOD = 50400; // This is how long voting lasts, 1 week +const VOTING_DELAY = 1; // How many blocks till a proposal vote becomes active +const VOTE_WAY = 1; + describe("ProxyWallet", () => { // Contract let proxyWalletRegistry; + let governor; + let DeployerAddress; let AliceAddress; let BobAddress; let AddressZero; @@ -15,7 +23,8 @@ describe("ProxyWallet", () => { beforeEach(async () => { await deployments.fixture(["DeployTestFixture"]); - const { allice, bob, a0 } = await getNamedAccounts(); + const { deployer, allice, bob, a0 } = await getNamedAccounts(); + DeployerAddress = deployer; AliceAddress = allice; BobAddress = bob; AddressZero = a0; @@ -23,8 +32,34 @@ describe("ProxyWallet", () => { const ProxyFactory = await deployments.get("FathomProxyFactory"); const proxyFactory = await ethers.getContractAt("FathomProxyFactory", ProxyFactory.address); + const Governor = await deployments.get("ProtocolGovernor"); + governor = await ethers.getContractAt("ProtocolGovernor", Governor.address); + proxyWalletRegistry = await getProxy(proxyFactory, "ProxyWalletRegistry"); - await proxyWalletRegistry.setDecentralizedMode(true); + const values = [0]; + const targets = [proxyWalletRegistry.address]; + const calldatas = [proxyWalletRegistry.interface.encodeFunctionData("setDecentralizedMode", [true])]; + const proposalTx = await governor.propose(targets, values, calldatas, "Set Decentralized Mode"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Decentralized Mode")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + // await proxyWalletRegistry.setDecentralizedMode(true); }); describe("#new user create a new proxy wallet", async () => { context("alice create a new proxy wallet", async () => { diff --git a/test/integration/ShowStopper.test.js b/test/integration/ShowStopper.test.js index 72408beb..1cac2ac9 100644 --- a/test/integration/ShowStopper.test.js +++ b/test/integration/ShowStopper.test.js @@ -1,8 +1,7 @@ const { ethers } = require("hardhat"); const provider = ethers.provider; const { expect } = require("chai"); -const { smock } = require("@defi-wonderland/smock"); -const { time } = require("@nomicfoundation/hardhat-network-helpers"); +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); const { WeiPerRad, WeiPerRay, WeiPerWad } = require("../helper/unit"); const { createProxyWallets } = require("../helper/proxy-wallets"); @@ -10,6 +9,11 @@ const PositionHelper = require("../helper/positions"); const { getProxy } = require("../../common/proxies"); const pools = require("../../common/collateral"); +const MIN_DELAY = 3600; // 1 hour +const VOTING_PERIOD = 50400; // This is how long voting lasts, 1 week +const VOTING_DELAY = 1; // How many blocks till a proposal vote becomes active +const VOTE_WAY = 1; + const WeekInSeconds = 604800; describe("ShowStopper", () => { @@ -29,20 +33,26 @@ describe("ShowStopper", () => { let collateralTokenAdapter; let MockCollateralTokenAdapter; let WXDC; + let governor; + let DeployerAddress; let AliceAddress; let BobAddress; beforeEach(async () => { await deployments.fixture(["DeployTestFixture"]); - const { allice, bob } = await getNamedAccounts(); + const { deployer, allice, bob } = await getNamedAccounts(); + DeployerAddress = deployer; AliceAddress = allice; BobAddress = bob; const ProxyFactory = await deployments.get("FathomProxyFactory"); const proxyFactory = await ethers.getContractAt("FathomProxyFactory", ProxyFactory.address); + const Governor = await deployments.get("ProtocolGovernor"); + governor = await ethers.getContractAt("ProtocolGovernor", Governor.address); + bookKeeper = await getProxy(proxyFactory, "BookKeeper"); liquidationEngine = await getProxy(proxyFactory, "LiquidationEngine"); positionManager = await getProxy(proxyFactory, "PositionManager"); @@ -61,14 +71,42 @@ describe("ShowStopper", () => { MockCollateralTokenAdapter = await ethers.getContractAt("MockCollateralTokenAdapter", _MockCollateralTokenAdapter.address); const proxyWalletRegistry = await getProxy(proxyFactory, "ProxyWalletRegistry"); - await proxyWalletRegistry.setDecentralizedMode(true); + + let values = [0, 0, 0]; + let targets = [proxyWalletRegistry.address, collateralPoolConfig.address, collateralPoolConfig.address]; + let calldatas = [ + proxyWalletRegistry.interface.encodeFunctionData("setDecentralizedMode", [true]), + collateralPoolConfig.interface.encodeFunctionData("setStabilityFeeRate", [pools.XDC, WeiPerRay]), + collateralPoolConfig.interface.encodeFunctionData("setStabilityFeeRate", [pools.WXDC, WeiPerRay]), + ]; + const proposalTx = await governor.propose(targets, values, calldatas, "Set Config"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Set Config")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + // Execute + await governor.execute(targets, values, calldatas, descriptionHash); + // await proxyWalletRegistry.setDecentralizedMode(true); ({ proxyWallets: [aliceProxyWallet, bobProxyWallet], } = await createProxyWallets([AliceAddress, BobAddress])); - await collateralPoolConfig.setStabilityFeeRate(pools.XDC, WeiPerRay); - await collateralPoolConfig.setStabilityFeeRate(pools.WXDC, WeiPerRay); + // await collateralPoolConfig.setStabilityFeeRate(pools.XDC, WeiPerRay); + // await collateralPoolConfig.setStabilityFeeRate(pools.WXDC, WeiPerRay); await fathomStablecoin.connect(provider.getSigner(AliceAddress)).approve(stablecoinAdapter.address, WeiPerWad.mul(10000)); }); @@ -76,13 +114,60 @@ describe("ShowStopper", () => { describe("#cage", () => { context("when doesn't grant showStopperRole for showStopper", () => { it("should be revert", async () => { - await accessControlConfig.revokeRole(await accessControlConfig.SHOW_STOPPER_ROLE(), showStopper.address); - await expect(showStopper.cage(WeekInSeconds)).to.be.revertedWith("!(ownerRole or showStopperRole)"); + const values = [0, 0]; + const targets = [accessControlConfig.address, showStopper.address]; + const calldatas = [ + accessControlConfig.interface.encodeFunctionData("revokeRole", [await accessControlConfig.SHOW_STOPPER_ROLE(), showStopper.address]), + showStopper.interface.encodeFunctionData("cage", [WeekInSeconds]), + ]; + const proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await expect(governor.execute(targets, values, calldatas, descriptionHash)).to.be.revertedWith( + "TimelockController: underlying transaction reverted" + ); }); }); context("when grant showStopperRole for all contract", () => { it("should be able to cage", async () => { - await showStopper.cage(WeekInSeconds); + const values = [0]; + const targets = [showStopper.address]; + const calldatas = [showStopper.interface.encodeFunctionData("cage", [WeekInSeconds])]; + const proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await showStopper.cage(WeekInSeconds); expect(await bookKeeper.live()).to.be.equal(0); expect(await liquidationEngine.live()).to.be.equal(0); @@ -92,8 +177,30 @@ describe("ShowStopper", () => { }); context("when some contract was already caged", () => { it("should be able to cage", async () => { - await systemDebtEngine.cage(); - await showStopper.cage(WeekInSeconds); + const values = [0, 0]; + const targets = [systemDebtEngine.address, showStopper.address]; + const calldatas = [systemDebtEngine.interface.encodeFunctionData("cage"), showStopper.interface.encodeFunctionData("cage", [WeekInSeconds])]; + const proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await systemDebtEngine.cage(); + // await showStopper.cage(WeekInSeconds); expect(await bookKeeper.live()).to.be.equal(0); expect(await liquidationEngine.live()).to.be.equal(0); @@ -111,8 +218,34 @@ describe("ShowStopper", () => { // c. mint FXD await PositionHelper.openXDCPositionAndDraw(aliceProxyWallet, AliceAddress, pools.XDC, WeiPerWad.mul(10), WeiPerWad.mul(5)); - await showStopper.cage(WeekInSeconds); - await showStopper.cagePool(pools.XDC); + const values = [0, 0]; + const targets = [showStopper.address, showStopper.address]; + const calldatas = [ + showStopper.interface.encodeFunctionData("cage", [WeekInSeconds]), + showStopper.interface.encodeFunctionData("cagePool", [pools.XDC]), + ]; + const proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + + // await showStopper.cage(WeekInSeconds); + // await showStopper.cagePool(pools.XDC); expect(await showStopper.cagePrice(pools.XDC)).to.be.equal(WeiPerRay); expect(await showStopper.totalDebtShare(pools.XDC)).to.be.equal(WeiPerWad.mul(5)); @@ -122,9 +255,36 @@ describe("ShowStopper", () => { it("should be able to cage", async () => { await PositionHelper.openXDCPositionAndDraw(aliceProxyWallet, AliceAddress, pools.XDC, WeiPerWad.mul(10), WeiPerWad.mul(5)); - await bookKeeper.cage(); - await showStopper.cage(WeekInSeconds); - await showStopper.cagePool(pools.XDC); + const values = [0, 0, 0]; + const targets = [bookKeeper.address, showStopper.address, showStopper.address]; + const calldatas = [ + bookKeeper.interface.encodeFunctionData("cage"), + showStopper.interface.encodeFunctionData("cage", [WeekInSeconds]), + showStopper.interface.encodeFunctionData("cagePool", [pools.XDC]), + ]; + const proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + + // await bookKeeper.cage(); + // await showStopper.cage(WeekInSeconds); + // await showStopper.cagePool(pools.XDC); expect(await showStopper.cagePrice(pools.XDC)).to.be.equal(WeiPerRay); expect(await showStopper.totalDebtShare(pools.XDC)).to.be.equal(WeiPerWad.mul(5)); @@ -144,9 +304,35 @@ describe("ShowStopper", () => { const positionId = await positionManager.ownerLastPositionId(aliceProxyWallet.address); const positionAddress = await positionManager.positions(positionId); - await showStopper.cage(WeekInSeconds); + const values = [0, 0]; + const targets = [showStopper.address, showStopper.address]; + const calldatas = [ + showStopper.interface.encodeFunctionData("cage", [WeekInSeconds]), + showStopper.interface.encodeFunctionData("cagePool", [pools.XDC]), + ]; + const proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + const proposalReceipt = await proposalTx.wait(); + const proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); - await showStopper.cagePool(pools.XDC); + // Queue the TX + const descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + + // await showStopper.cage(WeekInSeconds); + + // await showStopper.cagePool(pools.XDC); // accumulate bad debt posiion #1 await showStopper.accumulateBadDebt(pools.XDC, positionAddress); @@ -171,9 +357,35 @@ describe("ShowStopper", () => { const positionId2 = await positionManager.ownerLastPositionId(bobProxyWallet.address); const positionAddress2 = await positionManager.positions(positionId2); - await showStopper.cage(WeekInSeconds); + let values = [0, 0]; + let targets = [showStopper.address, showStopper.address]; + let calldatas = [ + showStopper.interface.encodeFunctionData("cage", [WeekInSeconds]), + showStopper.interface.encodeFunctionData("cagePool", [pools.XDC]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + + // await showStopper.cage(WeekInSeconds); - await showStopper.cagePool(pools.XDC); + // await showStopper.cagePool(pools.XDC); // accumulate bad debt posiion #1 await showStopper.accumulateBadDebt(pools.XDC, positionAddress); @@ -202,7 +414,30 @@ describe("ShowStopper", () => { expect((await bookKeeper.positions(pools.XDC, positionAddress2)).lockedCollateral).to.be.equal(0); expect(await bookKeeper.collateralToken(pools.XDC, bobProxyWallet.address)).to.be.equal(WeiPerWad.mul(5)); - await collateralTokenAdapter.cage(); + + values = [0]; + targets = [collateralTokenAdapter.address]; + calldatas = [collateralTokenAdapter.interface.encodeFunctionData("cage")]; + proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await collateralTokenAdapter.cage(); // emergency withdraw position #1 await PositionHelper.emergencyWithdraw(aliceProxyWallet, AliceAddress, collateralTokenAdapter.address); @@ -236,9 +471,34 @@ describe("ShowStopper", () => { const positionId2 = await positionManager.ownerLastPositionId(bobProxyWallet.address); const positionAddress2 = await positionManager.positions(positionId2); - await showStopper.cage(WeekInSeconds); + let values = [0, 0]; + let targets = [showStopper.address, showStopper.address]; + let calldatas = [ + showStopper.interface.encodeFunctionData("cage", [WeekInSeconds]), + showStopper.interface.encodeFunctionData("cagePool", [pools.XDC]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); - await showStopper.cagePool(pools.XDC); + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + + // await showStopper.cage(WeekInSeconds); + // await showStopper.cagePool(pools.XDC); // accumulate bad debt posiion #1 await showStopper.accumulateBadDebt(pools.XDC, positionAddress); @@ -293,9 +553,34 @@ describe("ShowStopper", () => { const positionId2 = await positionManager.ownerLastPositionId(bobProxyWallet.address); const positionAddress2 = await positionManager.positions(positionId2); - await showStopper.cage(WeekInSeconds); + let values = [0, 0]; + let targets = [showStopper.address, showStopper.address]; + let calldatas = [ + showStopper.interface.encodeFunctionData("cage", [WeekInSeconds]), + showStopper.interface.encodeFunctionData("cagePool", [pools.XDC]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); - await showStopper.cagePool(pools.XDC); + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + + // await showStopper.cage(WeekInSeconds); + // await showStopper.cagePool(pools.XDC); // accumulate bad debt posiion #1 await showStopper.accumulateBadDebt(pools.XDC, positionAddress); @@ -338,7 +623,6 @@ describe("ShowStopper", () => { expect(await bookKeeper.collateralToken(pools.XDC, AliceAddress)).to.be.equal("5000000000000000000"); }); }); - // TODO: fix this test context("when redeem stablecoin with two col types", () => { it("should be able to accumulateStablecoin, redeemStablecoin", async () => { // alice's position #1 @@ -381,11 +665,36 @@ describe("ShowStopper", () => { const positionId4 = await positionManager.ownerLastPositionId(bobProxyWallet.address); const positionAddress4 = await positionManager.positions(positionId4); - await showStopper.cage(WeekInSeconds); + let values = [0, 0, 0]; + let targets = [showStopper.address, showStopper.address, showStopper.address]; + let calldatas = [ + showStopper.interface.encodeFunctionData("cage", [WeekInSeconds]), + showStopper.interface.encodeFunctionData("cagePool", [pools.XDC]), + showStopper.interface.encodeFunctionData("cagePool", [pools.WXDC]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); - await showStopper.cagePool(pools.XDC); + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); - await showStopper.cagePool(pools.WXDC); + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + + // await showStopper.cage(WeekInSeconds); + // await showStopper.cagePool(pools.XDC); + // await showStopper.cagePool(pools.WXDC); // accumulate bad debt posiion #1 await showStopper.accumulateBadDebt(pools.XDC, positionAddress); @@ -449,10 +758,35 @@ describe("ShowStopper", () => { await showStopper.connect(provider.getSigner(AliceAddress)).redeemStablecoin(pools.WXDC, WeiPerWad.mul(5)); expect(await bookKeeper.collateralToken(pools.WXDC, AliceAddress)).to.be.equal("2500000000000000000"); - await collateralTokenAdapter.cage(); + values = [0, 0]; + targets = [collateralTokenAdapter.address, MockCollateralTokenAdapter.address]; + calldatas = [collateralTokenAdapter.interface.encodeFunctionData("cage"), MockCollateralTokenAdapter.interface.encodeFunctionData("cage")]; + proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await collateralTokenAdapter.cage(); + await collateralTokenAdapter.connect(provider.getSigner(AliceAddress)).emergencyWithdraw(AliceAddress); expect(await WXDC.balanceOf(AliceAddress)).to.be.equal("2500000000000000000"); - await MockCollateralTokenAdapter.cage(); + + // await MockCollateralTokenAdapter.cage(); + await MockCollateralTokenAdapter.connect(provider.getSigner(AliceAddress)).emergencyWithdraw(AliceAddress); expect(await WXDC.balanceOf(AliceAddress)).to.be.equal("5000000000000000000"); }); diff --git a/test/integration/StabilityFeeCollector.test.js b/test/integration/StabilityFeeCollector.test.js index 9a38b080..7386c311 100644 --- a/test/integration/StabilityFeeCollector.test.js +++ b/test/integration/StabilityFeeCollector.test.js @@ -1,6 +1,6 @@ const { ethers } = require("hardhat"); const provider = ethers.provider; -const { time } = require("@nomicfoundation/hardhat-network-helpers"); +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); const { BigNumber } = ethers; @@ -11,6 +11,11 @@ const PositionHelper = require("../helper/positions"); const { getProxy } = require("../../common/proxies"); const pools = require("../../common/collateral"); +const MIN_DELAY = 3600; // 1 hour +const VOTING_PERIOD = 50400; // This is how long voting lasts, 1 week +const VOTING_DELAY = 1; // How many blocks till a proposal vote becomes active +const VOTE_WAY = 1; + describe("Stability Fee", () => { // Proxy wallet let aliceProxyWallet; @@ -23,14 +28,18 @@ describe("Stability Fee", () => { let stabilityFeeCollector; let collateralPoolConfig; let simplePriceFeed; + let governor; + let DeployerAddress; let AliceAddress; let DevAddress; beforeEach(async () => { await deployments.fixture(["DeployTestFixture"]); + console.log("Starting timestamp:", await time.latest()); - const { allice, dev } = await getNamedAccounts(); + const { deployer, allice, dev } = await getNamedAccounts(); + DeployerAddress = deployer; AliceAddress = allice; DevAddress = dev; @@ -39,6 +48,9 @@ describe("Stability Fee", () => { const SimplePriceFeed = await deployments.get("SimplePriceFeed"); simplePriceFeed = await ethers.getContractAt("SimplePriceFeed", SimplePriceFeed.address); + const Governor = await deployments.get("ProtocolGovernor"); + governor = await ethers.getContractAt("ProtocolGovernor", Governor.address); + collateralPoolConfig = await getProxy(proxyFactory, "CollateralPoolConfig"); bookKeeper = await getProxy(proxyFactory, "BookKeeper"); stabilityFeeCollector = await getProxy(proxyFactory, "StabilityFeeCollector"); @@ -47,13 +59,44 @@ describe("Stability Fee", () => { const fathomStablecoin = await getProxy(proxyFactory, "FathomStablecoin"); const proxyWalletRegistry = await getProxy(proxyFactory, "ProxyWalletRegistry"); - await proxyWalletRegistry.setDecentralizedMode(true); + + let values = [0, 0, 0]; + let targets = [ + proxyWalletRegistry.address, + stabilityFeeCollector.address, + collateralPoolConfig.address, + ]; + let calldatas = [ + proxyWalletRegistry.interface.encodeFunctionData("setDecentralizedMode", [true]), + stabilityFeeCollector.interface.encodeFunctionData("setSystemDebtEngine", [DevAddress]), + collateralPoolConfig.interface.encodeFunctionData("setStabilityFeeRate", [pools.XDC, BigNumber.from("1000000005781378656804591713")]), + ] + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await proxyWalletRegistry.setDecentralizedMode(true); ({ proxyWallets: [aliceProxyWallet], } = await createProxyWallets([AliceAddress])); - await stabilityFeeCollector.setSystemDebtEngine(DevAddress); + // await stabilityFeeCollector.setSystemDebtEngine(DevAddress); await fathomStablecoin.connect(provider.getSigner(AliceAddress)).approve(aliceProxyWallet.address, WeiPerWad.mul(10000)); }); @@ -61,14 +104,39 @@ describe("Stability Fee", () => { describe("#collect", () => { context("when call collect directly and call deposit", () => { it("should be success", async () => { - await collateralPoolConfig.setStabilityFeeRate(pools.XDC, BigNumber.from("1000000005781378656804591713")); + // await collateralPoolConfig.setStabilityFeeRate(pools.XDC, BigNumber.from("1000000005781378656804591713")); + + // time increase 6 month - account for the time passed for the proposals to be executed + // console.log(await time.latest()) should help understand the time + const TIME_LOST_TO_EXECUTE_PROPOSALS = 108020; + await time.increase(15768000 - TIME_LOST_TO_EXECUTE_PROPOSALS); + let values = [0]; + let targets = [simplePriceFeed.address]; + let calldatas = [simplePriceFeed.interface.encodeFunctionData("setPrice", [WeiPerRay])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); - // time increase 6 month - await time.increase(15768000); - await simplePriceFeed.setPrice(WeiPerRay); + await governor.execute(targets, values, calldatas, descriptionHash); + // await simplePriceFeed.setPrice(WeiPerRay); await stabilityFeeCollector.collect(pools.XDC); const debtAccumulatedRate = await collateralPoolConfig.collateralPools(pools.XDC); // debtAccumulatedRate = RAY(1000000005781378656804591713^15768000) = 1095445115010332226911367294 + console.log("Timestamp after six months (included in those 6 months is the time for passing the proposals):", await time.latest()); AssertHelpers.assertAlmostEqual(debtAccumulatedRate.debtAccumulatedRate.toString(), "1095445115010332226911367294"); AssertHelpers.assertAlmostEqual((await bookKeeper.stablecoin(DevAddress)).toString(), "0"); @@ -88,9 +156,32 @@ describe("Stability Fee", () => { "1095445115010332226911367294" ); - // time increase 1 year - await time.increase(31536000); - await simplePriceFeed.setPrice(WeiPerRay); + // time increase 1 year - account for the time passed for the setPrice proposal to be executed + const TIME_PASSED_FOR_SET_PRICE_PROPOSAL = 54000; + await time.increase(31536000 - TIME_PASSED_FOR_SET_PRICE_PROPOSAL); + values = [0]; + targets = [simplePriceFeed.address]; + calldatas = [simplePriceFeed.interface.encodeFunctionData("setPrice", [WeiPerRay])]; + proposalTx = await governor.propose(targets, values, calldatas, "Setup 2"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup 2")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await simplePriceFeed.setPrice(WeiPerRay); // position 2 // a. open a new position @@ -102,6 +193,7 @@ describe("Stability Fee", () => { const positionAddress2 = await positionManager.positions(positionId2); // debtAccumulatedRate = RAY((1000000005781378656804591713^31536000) * 1095445115010332226911367294) = 1314534138012398672287467301 + console.log("Timestamp after year and a half in total (the time for executing setPrice proposal for second time included)",await time.latest()); AssertHelpers.assertAlmostEqual( (await collateralPoolConfig.collateralPools(pools.XDC)).debtAccumulatedRate.toString(), "1314534138012398672287467301" @@ -114,13 +206,34 @@ describe("Stability Fee", () => { // 4564354645876384278 + 3803628871563653565 = 8367983517440037843 AssertHelpers.assertAlmostEqual((await collateralPoolConfig.collateralPools(pools.XDC)).totalDebtShare.toString(), "8367983517440037843"); - // time increase 1 year - await time.increase(31536000); - await simplePriceFeed.setPrice(WeiPerRay); + // time increase 1 year - account for the time passed for the setPrice proposal to be executed + await time.increase(31536000 - TIME_PASSED_FOR_SET_PRICE_PROPOSAL); + proposalTx = await governor.propose(targets, values, calldatas, "Setup 3"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup 3")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await simplePriceFeed.setPrice(WeiPerRay); // debtAccumulatedRate ~ 20% await stabilityFeeCollector.collect(pools.XDC); + console.log("Timestamp after two and a half years in total (the time for executing setPrice proposal for third time included)",await time.latest()); + // debtAccumulatedRate = RAY((1000000005781378656804591713^31536000) * 1314534138012398672287467301) = 1577440965614878406737552619 AssertHelpers.assertAlmostEqual( (await collateralPoolConfig.collateralPools(pools.XDC)).debtAccumulatedRate.toString(), @@ -142,4 +255,4 @@ describe("Stability Fee", () => { }); }); }); -}); +}); \ No newline at end of file diff --git a/test/integration/StableSwapModule.test.js b/test/integration/StableSwapModule.test.js index 7f713232..4ad60b7b 100644 --- a/test/integration/StableSwapModule.test.js +++ b/test/integration/StableSwapModule.test.js @@ -2,7 +2,7 @@ const { ethers } = require("hardhat"); const provider = ethers.provider; const { expect } = require("chai"); const { BigNumber } = ethers; -const { time } = require("@nomicfoundation/hardhat-network-helpers"); +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); const { getProxy } = require("../../common/proxies"); const WeiPerSixDecimals = BigNumber.from(`1${"0".repeat(6)}`); @@ -15,6 +15,11 @@ const ONE_PERCENT_OF_TOTAL_DEPOSIT = ethers.utils.parseEther("200000"); const FOURTY_PERCENT_OF_TO_DEPOSIT = ethers.utils.parseEther("8000000"); const ONE_PERCENT_OF_TOTAL_DEPOSIT_SIX_DECIMALS = WeiPerSixDecimals.mul(200000); +const MIN_DELAY = 3600; // 1 hour +const VOTING_PERIOD = 50400; // This is how long voting lasts, 1 week +const VOTING_DELAY = 1; // How many blocks till a proposal vote becomes active +const VOTE_WAY = 1; + //why this divider: //right now fee is 0.001 //so for ONE_PERCENT_OF_TOTAL_DEPOSIT = 200000, fee = 200 @@ -36,6 +41,7 @@ describe("StableSwapModule", () => { let stableswapMultipleSwapsMock; let accounts; let DeployerAddress; + let governor; beforeEach(async () => { await deployments.fixture(["DeployTestFixture"]); @@ -46,6 +52,9 @@ describe("StableSwapModule", () => { const ProxyFactory = await deployments.get("FathomProxyFactory"); const proxyFactory = await ethers.getContractAt("FathomProxyFactory", ProxyFactory.address); + const Governor = await deployments.get("ProtocolGovernor"); + governor = await ethers.getContractAt("ProtocolGovernor", Governor.address); + const StableswapMultipleSwapsMock = await deployments.get("StableswapMultipleSwapsMock"); stableswapMultipleSwapsMock = await ethers.getContractAt("StableswapMultipleSwapsMock", StableswapMultipleSwapsMock.address); @@ -138,7 +147,31 @@ describe("StableSwapModule", () => { const whitelistAccount = accounts[2].address; await fathomStablecoin.connect(provider.getSigner(whitelistAccount)).approve(stableSwapModule.address, ethers.constants.MaxUint256); await fathomStablecoin.mint(whitelistAccount, TO_MINT); - await stableSwapModule.addToWhitelist(whitelistAccount); + + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("addToWhitelist", [whitelistAccount])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.addToWhitelist(whitelistAccount); + const beforeBalanceOfStablecoin = await fathomStablecoin.balanceOf(whitelistAccount); const beforeBalanceOfUSDT = await USDT.balanceOf(whitelistAccount); @@ -159,7 +192,31 @@ describe("StableSwapModule", () => { await USDT.connect(provider.getSigner(whitelistAccount)).approve(stableSwapModule.address, ethers.constants.MaxUint256); await USDT.mint(whitelistAccount, TO_MINT); await fathomStablecoin.mint(whitelistAccount, TO_MINT); - await stableSwapModule.addToWhitelist(whitelistAccount); + + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("addToWhitelist", [whitelistAccount])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.addToWhitelist(whitelistAccount); + await stableSwapModule.connect(provider.getSigner(whitelistAccount)).swapTokenToStablecoin(whitelistAccount, WeiPerSixDecimals.mul(1000000)); }); }); @@ -171,11 +228,55 @@ describe("StableSwapModule", () => { const whitelistAccount = accounts[2].address; await fathomStablecoin.connect(provider.getSigner(whitelistAccount)).approve(stableSwapModule.address, ethers.constants.MaxUint256); await fathomStablecoin.mint(whitelistAccount, TO_MINT); - await stableSwapModule.addToWhitelist(whitelistAccount); + + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("addToWhitelist", [whitelistAccount])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.addToWhitelist(whitelistAccount); await stableSwapModule .connect(provider.getSigner(whitelistAccount)) .swapStablecoinToToken(whitelistAccount, ethers.utils.parseEther("1000000")); - await stableSwapModule.removeFromWhitelist(whitelistAccount); + + calldatas = [stableSwapModule.interface.encodeFunctionData("removeFromWhitelist", [whitelistAccount])]; + proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.removeFromWhitelist(whitelistAccount); await expect( stableSwapModule.connect(provider.getSigner(whitelistAccount)).swapStablecoinToToken(whitelistAccount, ethers.utils.parseEther("1000000")) ).to.be.revertedWith("user-not-whitelisted"); @@ -188,9 +289,54 @@ describe("StableSwapModule", () => { await USDT.connect(provider.getSigner(whitelistAccount)).approve(stableSwapModule.address, ethers.constants.MaxUint256); await USDT.mint(whitelistAccount, TO_MINT); await fathomStablecoin.mint(whitelistAccount, TO_MINT); - await stableSwapModule.addToWhitelist(whitelistAccount); + + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("addToWhitelist", [whitelistAccount])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.addToWhitelist(whitelistAccount); + await stableSwapModule.connect(provider.getSigner(whitelistAccount)).swapTokenToStablecoin(whitelistAccount, WeiPerSixDecimals.mul(1000000)); - await stableSwapModule.removeFromWhitelist(whitelistAccount); + + calldatas = [stableSwapModule.interface.encodeFunctionData("removeFromWhitelist", [whitelistAccount])]; + proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.removeFromWhitelist(whitelistAccount); await expect( stableSwapModule.connect(provider.getSigner(whitelistAccount)).swapTokenToStablecoin(whitelistAccount, WeiPerSixDecimals.mul(1000000)) ).to.be.revertedWith("user-not-whitelisted"); @@ -202,7 +348,31 @@ describe("StableSwapModule", () => { context("check for daily limit", async () => { it("Should swap tokens and revert when dailyswap limit is reached", async () => { //first swap which takes all the allowance - await stableSwapModule.setDecentralizedStatesStatus(true); + + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); + console.log("Swapping twenty times to check for DailyLimit Cross"); let numberOfSwaps = 0; for (let i = 0; i < 10; i++) { @@ -248,15 +418,81 @@ describe("StableSwapModule", () => { //again swap after increasing timestamp //should succeed await stableSwapModule.swapStablecoinToToken(DeployerAddress, ONE_PERCENT_OF_TOTAL_DEPOSIT.div(DIVIDER_TO_FIT_SINGLE_SWAP_LIMIT)); - await stableSwapModule.initializeFeesAfterUpgrade(); - await expect(stableSwapModule.initializeFeesAfterUpgrade()).to.be.revertedWith("StableSwapModule/already-initialized"); + + calldatas = [stableSwapModule.interface.encodeFunctionData("initializeFeesAfterUpgrade")]; + proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.initializeFeesAfterUpgrade(); + + proposalTx = await governor.propose(targets, values, calldatas, "Initialize again"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Initialize again")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await expect(governor.execute(targets, values, calldatas, descriptionHash)).to.be.revertedWith( + "TimelockController: underlying transaction reverted" + ); }); }); context("check for daily limit - depositToken", async () => { it("Should update dailyLimit on depositing more token", async () => { await time.increase(1); - await stableSwapModule.setDecentralizedStatesStatus(true); + + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); + await stableSwapModule.swapTokenToStablecoin(DeployerAddress, ONE_PERCENT_OF_TOTAL_DEPOSIT_SIX_DECIMALS); await time.increase(1); await stableSwapModuleWrapper.depositTokens(TO_DEPOSIT); @@ -271,10 +507,52 @@ describe("StableSwapModule", () => { context("check for daily limit - setDailySwapLimitNumerator", async () => { it("Should update dailyLimit on depositing more token", async () => { - await stableSwapModule.setDecentralizedStatesStatus(true); + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); await stableSwapModuleWrapper.depositTokens(TO_DEPOSIT); await stableSwapModule.swapTokenToStablecoin(DeployerAddress, ONE_PERCENT_OF_TOTAL_DEPOSIT_SIX_DECIMALS); - await stableSwapModule.setDailySwapLimitNumerator(3000); + calldatas = [stableSwapModule.interface.encodeFunctionData("setDailySwapLimitNumerator", [3000])]; + proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDailySwapLimitNumerator(3000); //Why GreaterThanOrEqual? Because there is one swap already done which incurs fee so total pool has increased const remainingDailySwapAmount = await stableSwapModule.remainingDailySwapAmount(); expect(remainingDailySwapAmount).to.be.gte(THIRTY_PERCENT_OF_TO_DEPOSIT); @@ -286,7 +564,29 @@ describe("StableSwapModule", () => { context("check for daily limit", async () => { it("Should revert when SingleSwap Limit is reached", async () => { //first swap which takes all the allowance - await stableSwapModule.setDecentralizedStatesStatus(true); + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); await expect(stableSwapModule.swapStablecoinToToken(DeployerAddress, ONE_PERCENT_OF_TOTAL_DEPOSIT.add(1))).to.be.revertedWith( "_checkSingleSwapLimit/single-swap-exceeds-limit" ); @@ -297,7 +597,29 @@ describe("StableSwapModule", () => { describe("#singleBlockLimitCheck", async () => { context("check for block limit", async () => { it("Should revert when number of swaps per block limit is reached", async () => { - await stableSwapModule.setDecentralizedStatesStatus(true); + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); await stableSwapModule.swapStablecoinToToken(DeployerAddress, ONE_PERCENT_OF_TOTAL_DEPOSIT); //this reverts because one user can swap only once in two blocks and we have already done one swap @@ -315,11 +637,37 @@ describe("StableSwapModule", () => { context("check for block limit", async () => { it("Should revert when SingleSwap Limit is reached", async () => { //first swap which takes all the allowance - await stableSwapModule.setDecentralizedStatesStatus(true); const newNumberOfSwapsLimitPerUser = 2; const newBlocksPerLimit = 3; - await stableSwapModule.setNumberOfSwapsLimitPerUser(newNumberOfSwapsLimitPerUser); - await stableSwapModule.setBlocksPerLimit(newBlocksPerLimit); + let values = [0, 0, 0]; + let targets = [stableSwapModule.address, stableSwapModule.address, stableSwapModule.address]; + let calldatas = [ + stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true]), + stableSwapModule.interface.encodeFunctionData("setNumberOfSwapsLimitPerUser", [newNumberOfSwapsLimitPerUser]), + stableSwapModule.interface.encodeFunctionData("setBlocksPerLimit", [newBlocksPerLimit]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); + // await stableSwapModule.setNumberOfSwapsLimitPerUser(newNumberOfSwapsLimitPerUser); + // await stableSwapModule.setBlocksPerLimit(newBlocksPerLimit); await stableSwapModule.swapStablecoinToToken(DeployerAddress, ONE_PERCENT_OF_TOTAL_DEPOSIT); @@ -339,11 +687,38 @@ describe("StableSwapModule", () => { context("check for block limit", async () => { it("Should be successful and not reach limit - setting 3 swaps per 3 blocks", async () => { //first swap which takes all the allowance - await stableSwapModule.setDecentralizedStatesStatus(true); const newNumberOfSwapsLimitPerUser = 3; const newBlocksPerLimit = 3; - await stableSwapModule.setNumberOfSwapsLimitPerUser(newNumberOfSwapsLimitPerUser); - await stableSwapModule.setBlocksPerLimit(newBlocksPerLimit); + + let values = [0, 0, 0]; + let targets = [stableSwapModule.address, stableSwapModule.address, stableSwapModule.address]; + let calldatas = [ + stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true]), + stableSwapModule.interface.encodeFunctionData("setNumberOfSwapsLimitPerUser", [newNumberOfSwapsLimitPerUser]), + stableSwapModule.interface.encodeFunctionData("setBlocksPerLimit", [newBlocksPerLimit]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); + // await stableSwapModule.setNumberOfSwapsLimitPerUser(newNumberOfSwapsLimitPerUser); + // await stableSwapModule.setBlocksPerLimit(newBlocksPerLimit); await stableSwapModule.swapStablecoinToToken(DeployerAddress, ONE_PERCENT_OF_TOTAL_DEPOSIT); @@ -366,12 +741,38 @@ describe("StableSwapModule", () => { context("check for block limit", async () => { it("Should revert for extra swap in the limit and then again be sucessful after enough block passes", async () => { //first swap which takes all the allowance - await stableSwapModule.setDecentralizedStatesStatus(true); const newNumberOfSwapsLimitPerUser = 3; const newBlocksPerLimit = 10; const blockNumbersToReachForNextSwap = 12; - await stableSwapModule.setNumberOfSwapsLimitPerUser(newNumberOfSwapsLimitPerUser); - await stableSwapModule.setBlocksPerLimit(newBlocksPerLimit); + let values = [0, 0, 0]; + let targets = [stableSwapModule.address, stableSwapModule.address, stableSwapModule.address]; + let calldatas = [ + stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true]), + stableSwapModule.interface.encodeFunctionData("setNumberOfSwapsLimitPerUser", [newNumberOfSwapsLimitPerUser]), + stableSwapModule.interface.encodeFunctionData("setBlocksPerLimit", [newBlocksPerLimit]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); + // await stableSwapModule.setNumberOfSwapsLimitPerUser(newNumberOfSwapsLimitPerUser); + // await stableSwapModule.setBlocksPerLimit(newBlocksPerLimit); await stableSwapModule.swapStablecoinToToken(DeployerAddress, ONE_PERCENT_OF_TOTAL_DEPOSIT); await stableSwapModule.swapStablecoinToToken( @@ -404,9 +805,57 @@ describe("StableSwapModule", () => { describe("#stableSwapEmergencyWithdraw", async () => { context("emergency withdraw", async () => { it("Should emergency withdraw when paused", async () => { - await expect(stableSwapModule.emergencyWithdraw(accounts[5].address)).to.be.reverted; - await stableSwapModule.pause(); - await stableSwapModule.emergencyWithdraw(accounts[5].address); + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("emergencyWithdraw", [accounts[5].address])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await expect(governor.execute(targets, values, calldatas, descriptionHash)).to.be.reverted; + // await expect(stableSwapModule.emergencyWithdraw(accounts[5].address)).to.be.reverted; + + values = [0, 0]; + targets = [stableSwapModule.address, stableSwapModule.address]; + calldatas = [ + stableSwapModule.interface.encodeFunctionData("pause"), + stableSwapModule.interface.encodeFunctionData("emergencyWithdraw", [accounts[5].address]), + ]; + proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.pause(); + // await stableSwapModule.emergencyWithdraw(accounts[5].address); const balanceOfStablecoin = await fathomStablecoin.balanceOf(accounts[5].address); const balanceOfToken = await USDT.balanceOf(accounts[5].address); expect(balanceOfStablecoin).to.be.equal(ethers.utils.parseEther("10000000")); @@ -418,7 +867,30 @@ describe("StableSwapModule", () => { describe("#StableswapMultipleSwapsMock", async () => { context("twoStablecoinToTokenSwapAtSameBlock- swap tokens in same block", async () => { it("should revert if we swap tokens in same block", async () => { - await stableSwapModule.setDecentralizedStatesStatus(true); + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); + await fathomStablecoin.approve(stableswapMultipleSwapsMock.address, ethers.constants.MaxUint256); await expect( stableswapMultipleSwapsMock.twoStablecoinToTokenSwapAtSameBlock( @@ -432,7 +904,30 @@ describe("StableSwapModule", () => { context("twoTokenToStablecoinSwapAtSameBlock- swap tokens in same block", async () => { it("should revert if we swap tokens in same block", async () => { - await stableSwapModule.setDecentralizedStatesStatus(true); + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); + await USDT.approve(stableswapMultipleSwapsMock.address, ethers.constants.MaxUint256); await expect( stableswapMultipleSwapsMock.twoTokenToStablecoinSwapAtSameBlock( @@ -445,11 +940,37 @@ describe("StableSwapModule", () => { }); context("twoStablecoinToTokenSwapAtSameBlock- swap tokens in same block", async () => { it("Should be successful for two swaps in same block", async () => { - await stableSwapModule.setDecentralizedStatesStatus(true); const newNumberOfSwapsLimitPerUser = 3; const newBlocksPerLimit = 1; - await stableSwapModule.setNumberOfSwapsLimitPerUser(newNumberOfSwapsLimitPerUser); - await stableSwapModule.setBlocksPerLimit(newBlocksPerLimit); + let values = [0, 0, 0]; + let targets = [stableSwapModule.address, stableSwapModule.address, stableSwapModule.address]; + let calldatas = [ + stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true]), + stableSwapModule.interface.encodeFunctionData("setNumberOfSwapsLimitPerUser", [newNumberOfSwapsLimitPerUser]), + stableSwapModule.interface.encodeFunctionData("setBlocksPerLimit", [newBlocksPerLimit]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); + // await stableSwapModule.setNumberOfSwapsLimitPerUser(newNumberOfSwapsLimitPerUser); + // await stableSwapModule.setBlocksPerLimit(newBlocksPerLimit); await fathomStablecoin.approve(stableswapMultipleSwapsMock.address, ethers.constants.MaxUint256); await stableswapMultipleSwapsMock.twoStablecoinToTokenSwapAtSameBlock( @@ -462,11 +983,37 @@ describe("StableSwapModule", () => { context("twoTokenToStablecoinSwapAtSameBlock- swap tokens in same block", async () => { it("Should be successful for two swaps in same block", async () => { - await stableSwapModule.setDecentralizedStatesStatus(true); const newNumberOfSwapsLimitPerUser = 3; const newBlocksPerLimit = 1; - await stableSwapModule.setNumberOfSwapsLimitPerUser(newNumberOfSwapsLimitPerUser); - await stableSwapModule.setBlocksPerLimit(newBlocksPerLimit); + let values = [0, 0, 0]; + let targets = [stableSwapModule.address, stableSwapModule.address, stableSwapModule.address]; + let calldatas = [ + stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true]), + stableSwapModule.interface.encodeFunctionData("setNumberOfSwapsLimitPerUser", [newNumberOfSwapsLimitPerUser]), + stableSwapModule.interface.encodeFunctionData("setBlocksPerLimit", [newBlocksPerLimit]), + ]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); + // await stableSwapModule.setNumberOfSwapsLimitPerUser(newNumberOfSwapsLimitPerUser); + // await stableSwapModule.setBlocksPerLimit(newBlocksPerLimit); await USDT.approve(stableswapMultipleSwapsMock.address, ethers.constants.MaxUint256); await stableswapMultipleSwapsMock.twoTokenToStablecoinSwapAtSameBlock( @@ -515,7 +1062,29 @@ describe("StableSwapModule", () => { context("update total value deposited after upgrade", async () => { it("totalValueDeposited: should be same before and after upgrade", async () => { const totalValueDepositedBeforeUpdate = await stableSwapModule.totalValueDeposited(); - await stableSwapModule.udpateTotalValueDeposited(); + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("udpateTotalValueDeposited")]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.udpateTotalValueDeposited(); const totalValueDepositedAfterUpdate = await stableSwapModule.totalValueDeposited(); expect(totalValueDepositedAfterUpdate).to.be.equal(totalValueDepositedBeforeUpdate); }); @@ -525,7 +1094,29 @@ describe("StableSwapModule", () => { describe("#unitTests", async () => { context("exceed single swap limit", () => { it("should revert after setting decentralized state - single swap limit - swapStablecoinToToken", async () => { - await stableSwapModule.setDecentralizedStatesStatus(true); + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); await expect(stableSwapModule.swapStablecoinToToken(DeployerAddress, ONE_PERCENT_OF_TOTAL_DEPOSIT.add(1))).to.be.revertedWith( "_checkSingleSwapLimit/single-swap-exceeds-limit" ); @@ -534,7 +1125,29 @@ describe("StableSwapModule", () => { context("exceed single swap limit", () => { it("should revert after setting decentralized state - single swap limit - swapTokenToStablecoin", async () => { - await stableSwapModule.setDecentralizedStatesStatus(true); + let values = [0]; + let targets = [stableSwapModule.address]; + let calldatas = [stableSwapModule.interface.encodeFunctionData("setDecentralizedStatesStatus", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.setDecentralizedStatesStatus(true); await expect(stableSwapModule.swapTokenToStablecoin(DeployerAddress, ONE_PERCENT_OF_TOTAL_DEPOSIT_SIX_DECIMALS.add(1))).to.be.revertedWith( "_checkSingleSwapLimit/single-swap-exceeds-limit" ); diff --git a/test/integration/StableSwapModuleWrapper.test.js b/test/integration/StableSwapModuleWrapper.test.js index 112cfd28..69e5fcc1 100644 --- a/test/integration/StableSwapModuleWrapper.test.js +++ b/test/integration/StableSwapModuleWrapper.test.js @@ -3,7 +3,7 @@ const provider = ethers.provider; const { expect } = require("chai"); const { BigNumber } = ethers; const { MaxUint256 } = ethers.constants; -const { time } = require("@nomicfoundation/hardhat-network-helpers"); +const { time, mine } = require("@nomicfoundation/hardhat-network-helpers"); const { getProxy } = require("../../common/proxies"); const { WeiPerWad } = require("../helper/unit"); @@ -19,6 +19,11 @@ const TO_MINT = ethers.utils.parseEther("20000000"); const ONE_PERCENT_OF_TOTAL_DEPOSIT = ethers.utils.parseEther("100000"); const ONE_PERCENT_OF_TOTAL_DEPOSIT_SIX_DECIMALS = WeiPerSixDecimals.mul(100000); +const MIN_DELAY = 3600; // 1 hour +const VOTING_PERIOD = 50400; // This is how long voting lasts, 1 week +const VOTING_DELAY = 1; // How many blocks till a proposal vote becomes active +const VOTE_WAY = 1; + //why this divider: //right now fee is 0.001 //so for ONE_PERCENT_OF_TOTAL_DEPOSIT = 200000, fee = 200 @@ -48,6 +53,7 @@ describe("StableSwapModuleWrapper", () => { let fathomStablecoin; let stableSwapModuleWrapper; let stableswapMultipleSwapsMock; + let governor; let accounts; let DeployerAddress; @@ -63,6 +69,9 @@ describe("StableSwapModuleWrapper", () => { const ProxyFactory = await deployments.get("FathomProxyFactory"); const proxyFactory = await ethers.getContractAt("FathomProxyFactory", ProxyFactory.address); + const Governor = await deployments.get("ProtocolGovernor"); + governor = await ethers.getContractAt("ProtocolGovernor", Governor.address); + const StableswapMultipleSwapsMock = await deployments.get("StableswapMultipleSwapsMock"); stableswapMultipleSwapsMock = await ethers.getContractAt("StableswapMultipleSwapsMock", StableswapMultipleSwapsMock.address); @@ -111,7 +120,30 @@ describe("StableSwapModuleWrapper", () => { context("Should let whitelisted people deposit Tokens", () => { it("Should deposit from whitelisted address", async () => { - await stableSwapModuleWrapper.addToWhitelist(accounts[2].address); + let values = [0]; + let targets = [stableSwapModuleWrapper.address]; + let calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("addToWhitelist", [accounts[2].address])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.addToWhitelist(accounts[2].address); + await USDT.connect(provider.getSigner(accounts[2].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await fathomStablecoin.connect(provider.getSigner(accounts[2].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await USDT.mint(accounts[2].address, TO_DEPOSIT_USD); @@ -120,13 +152,58 @@ describe("StableSwapModuleWrapper", () => { }); it("Should deposit from whitelisted address and after its removed from whitelist, should revert", async () => { - await stableSwapModuleWrapper.addToWhitelist(accounts[2].address); + let values = [0]; + let targets = [stableSwapModuleWrapper.address]; + let calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("addToWhitelist", [accounts[2].address])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.addToWhitelist(accounts[2].address); + await USDT.connect(provider.getSigner(accounts[2].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await fathomStablecoin.connect(provider.getSigner(accounts[2].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await USDT.mint(accounts[2].address, TO_DEPOSIT_USD); await fathomStablecoin.mint(accounts[2].address, TO_DEPOSIT); await stableSwapModuleWrapper.connect(provider.getSigner(accounts[2].address)).depositTokens(TO_DEPOSIT); - await stableSwapModuleWrapper.removeFromWhitelist(accounts[2].address); + + calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("removeFromWhitelist", [accounts[2].address])]; + proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.removeFromWhitelist(accounts[2].address); + await expect(stableSwapModuleWrapper.connect(provider.getSigner(accounts[2].address)).depositTokens(TO_DEPOSIT)).to.be.revertedWith( "user-not-whitelisted" ); @@ -137,7 +214,30 @@ describe("StableSwapModuleWrapper", () => { describe("#ShouldDepositTokensAndSwap", async () => { context("Should let whitelisted people deposit Tokens and then swap", () => { it("Should deposit", async () => { - await stableSwapModuleWrapper.addToWhitelist(accounts[2].address); + let values = [0]; + let targets = [stableSwapModuleWrapper.address]; + let calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("addToWhitelist", [accounts[2].address])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.addToWhitelist(accounts[2].address); + await USDT.connect(provider.getSigner(accounts[2].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await fathomStablecoin.connect(provider.getSigner(accounts[2].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await USDT.mint(accounts[2].address, TO_DEPOSIT_USD); @@ -159,9 +259,34 @@ describe("StableSwapModuleWrapper", () => { let TOTAL_FXD_BALANCE_AFTER_DEPOSIT = Array.from(Array(5)); let TOTAL_TOKEN_BALANCE_AFTER_DEPOSIT = Array.from(Array(5)); + let values = [0]; + let targets = [stableSwapModuleWrapper.address]; + let calldatas = []; + for (let i = 1; i < 5; i++) { console.log(`depositing for account [${i}]`); - await stableSwapModuleWrapper.addToWhitelist(accounts[i].address); + calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("addToWhitelist", [accounts[i].address])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.addToWhitelist(accounts[i].address); + await USDT.connect(provider.getSigner(accounts[i].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await fathomStablecoin.connect(provider.getSigner(accounts[i].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await USDT.mint(accounts[i].address, TOTAL_DEPOSIT_FOR_EACH_ACCOUNT_USD); @@ -215,7 +340,31 @@ describe("StableSwapModuleWrapper", () => { //checking if updatingTotalValueDepositedWorks const totalValueDepositedBeforeUpdate = await stableSwapModule.totalValueDeposited(); - await stableSwapModule.udpateTotalValueDeposited(); + + values = [0]; + targets = [stableSwapModule.address]; + calldatas = [stableSwapModule.interface.encodeFunctionData("udpateTotalValueDeposited")]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModule.udpateTotalValueDeposited(); + const totalValueDepositedAfterUpdate = await stableSwapModule.totalValueDeposited(); expect(totalValueDepositedAfterUpdate).to.be.equal(totalValueDepositedBeforeUpdate); @@ -256,9 +405,34 @@ describe("StableSwapModuleWrapper", () => { it("Should withdraw tokens but the one that withdraws tokens earlier must have slightly less fees rewards", async () => { const TOTAL_DEPOSIT_FOR_EACH_ACCOUNT = WeiPerWad.mul(1000); const TOTAL_DEPOSIT_FOR_EACH_ACCOUNT_USD = WeiPerSixDecimals.mul(1000); + let values = [0]; + let targets = [stableSwapModuleWrapper.address]; + let calldatas = []; + for (let i = 1; i < 5; i++) { console.log(`depositing for account [${i}]`); - await stableSwapModuleWrapper.addToWhitelist(accounts[i].address); + calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("addToWhitelist", [accounts[i].address])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.addToWhitelist(accounts[i].address); + await USDT.connect(provider.getSigner(accounts[i].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await fathomStablecoin.connect(provider.getSigner(accounts[i].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await USDT.mint(accounts[i].address, TOTAL_DEPOSIT_FOR_EACH_ACCOUNT_USD); @@ -310,10 +484,35 @@ describe("StableSwapModuleWrapper", () => { it("Should be successful", async () => { const TOTAL_DEPOSIT_FOR_EACH_ACCOUNT = WeiPerWad.mul(1000); const TOTAL_DEPOSIT_FOR_EACH_ACCOUNT_USD = WeiPerSixDecimals.mul(1000); + + let values = [0]; + let targets = [stableSwapModuleWrapper.address]; + let calldatas = []; //whitelist and deposit for 3 accounts for (let i = 1; i < 4; i++) { console.log(`depositing for account [${i}]`); - await stableSwapModuleWrapper.addToWhitelist(accounts[i].address); + calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("addToWhitelist", [accounts[i].address])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.addToWhitelist(accounts[i].address); + await USDT.connect(provider.getSigner(accounts[i].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await fathomStablecoin.connect(provider.getSigner(accounts[i].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await USDT.mint(accounts[i].address, TOTAL_DEPOSIT_FOR_EACH_ACCOUNT_USD); @@ -338,7 +537,30 @@ describe("StableSwapModuleWrapper", () => { await stableSwapModuleWrapper.connect(provider.getSigner(accounts[1].address)).withdrawTokens(TOTAL_DEPOSIT_FOR_EACH_ACCOUNT.mul(2)); - await stableSwapModuleWrapper.addToWhitelist(accounts[4].address); + values = [0]; + targets = [stableSwapModuleWrapper.address]; + calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("addToWhitelist", [accounts[4].address])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.addToWhitelist(accounts[4].address); + await USDT.connect(provider.getSigner(accounts[4].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await fathomStablecoin.connect(provider.getSigner(accounts[4].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await USDT.mint(accounts[4].address, TOTAL_DEPOSIT_FOR_EACH_ACCOUNT_USD); @@ -567,7 +789,30 @@ describe("StableSwapModuleWrapper", () => { context("1. Whitelist one account, 2. swap tokens to generate fees, 3. withdraw tokens, 4. repeat steps 2 and 3", async () => { it("Should withdraw correct fees and check the console for verfication ", async () => { - await stableSwapModuleWrapper.addToWhitelist(accounts[2].address); + let values = [0]; + let targets = [stableSwapModuleWrapper.address]; + let calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("addToWhitelist", [accounts[2].address])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.addToWhitelist(accounts[2].address); + await USDT.connect(provider.getSigner(accounts[2].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await fathomStablecoin.connect(provider.getSigner(accounts[2].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await USDT.mint(accounts[2].address, TO_DEPOSIT_USD); @@ -639,7 +884,30 @@ describe("StableSwapModuleWrapper", () => { describe("#decentralizedState", async () => { context("set decentralized state and deposit tokens by anybody", async () => { it("Should succeed", async () => { - await stableSwapModuleWrapper.connect(provider.getSigner(DeployerAddress)).setIsDecentralizedState(true); + let values = [0]; + let targets = [stableSwapModuleWrapper.address]; + let calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("setIsDecentralizedState", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.connect(provider.getSigner(DeployerAddress)).setIsDecentralizedState(true); + await fathomStablecoin.connect(provider.getSigner(AliceAddress)).approve(stableSwapModuleWrapper.address, MaxUint256); await USDT.connect(provider.getSigner(AliceAddress)).approve(stableSwapModuleWrapper.address, MaxUint256); await stableSwapModuleWrapper.connect(provider.getSigner(AliceAddress)).depositTokens(TO_DEPOSIT); @@ -648,12 +916,57 @@ describe("StableSwapModuleWrapper", () => { context("set decentralized state and deposit tokens should succeed then, set decentralized state as false and should fail", async () => { it("Should succeed", async () => { - await stableSwapModuleWrapper.connect(provider.getSigner(DeployerAddress)).setIsDecentralizedState(true); + let values = [0]; + let targets = [stableSwapModuleWrapper.address]; + let calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("setIsDecentralizedState", [true])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.connect(provider.getSigner(DeployerAddress)).setIsDecentralizedState(true); + await fathomStablecoin.connect(provider.getSigner(accounts[1].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await USDT.connect(provider.getSigner(accounts[1].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await stableSwapModuleWrapper.connect(provider.getSigner(accounts[1].address)).depositTokens(TO_DEPOSIT); - await stableSwapModuleWrapper.connect(provider.getSigner(DeployerAddress)).setIsDecentralizedState(false); + + calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("setIsDecentralizedState", [false])]; + proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + proposalReceipt = await proposalTx.wait(); + proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.connect(provider.getSigner(DeployerAddress)).setIsDecentralizedState(false); + await expect(stableSwapModuleWrapper.connect(provider.getSigner(accounts[1].address)).depositTokens(TO_DEPOSIT)).to.be.revertedWith( "user-not-whitelisted" ); @@ -715,9 +1028,35 @@ describe("StableSwapModuleWrapper", () => { it("Should withdraw tokens with emergencyWithdraw", async () => { const TOTAL_DEPOSIT_FOR_EACH_ACCOUNT = WeiPerWad.mul(1000); const TOTAL_DEPOSIT_FOR_EACH_ACCOUNT_USD = WeiPerSixDecimals.mul(1000); + + let values = [0]; + let targets = [stableSwapModuleWrapper.address]; + let calldatas = []; + for (let i = 1; i < 5; i++) { console.log(`depositing for account [${i}]`); - await stableSwapModuleWrapper.addToWhitelist(accounts[i].address); + calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("addToWhitelist", [accounts[i].address])]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.addToWhitelist(accounts[i].address); + await USDT.connect(provider.getSigner(accounts[i].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await fathomStablecoin.connect(provider.getSigner(accounts[i].address)).approve(stableSwapModuleWrapper.address, MaxUint256); await USDT.mint(accounts[i].address, TOTAL_DEPOSIT_FOR_EACH_ACCOUNT_USD); @@ -746,8 +1085,30 @@ describe("StableSwapModuleWrapper", () => { await stableSwapModuleWrapper.connect(provider.getSigner(accounts[i].address)).withdrawClaimedFees(); } - await stableSwapModuleWrapper.pause(); - await stableSwapModule.pause(); + values = [0, 0]; + targets = [stableSwapModuleWrapper.address, stableSwapModule.address]; + calldatas = [stableSwapModuleWrapper.interface.encodeFunctionData("pause"), stableSwapModule.interface.encodeFunctionData("pause")]; + let proposalTx = await governor.propose(targets, values, calldatas, "Setup"); + let proposalReceipt = await proposalTx.wait(); + let proposalId = proposalReceipt.events[0].args.proposalId; + + // wait for the voting period to pass + await mine(VOTING_DELAY + 1); // wait for the voting period to pass + + await governor.connect(provider.getSigner(DeployerAddress)).castVote(proposalId, VOTE_WAY); + + await mine(VOTING_PERIOD + 1); + + // Queue the TX + let descriptionHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Setup")); + await governor.queue(targets, values, calldatas, descriptionHash); + + await time.increase(MIN_DELAY + 1); + await mine(1); + + await governor.execute(targets, values, calldatas, descriptionHash); + // await stableSwapModuleWrapper.pause(); + // await stableSwapModule.pause(); for (let i = 0; i < 5; i++) { console.log(`emergency withdraw for account [${i}]`);