diff --git a/proposals/dao/fip_34.ts b/proposals/dao/fip_34.ts index d4b365683..91a7a301d 100644 --- a/proposals/dao/fip_34.ts +++ b/proposals/dao/fip_34.ts @@ -8,6 +8,8 @@ import { TeardownUpgradeFunc, ValidateUpgradeFunc } from '../../types/types'; +import { FeiDAOTimelock } from '@custom-types/contracts'; +import { getImpersonatedSigner } from '@test/helpers'; chai.use(CBN(ethers.BigNumber)); @@ -26,6 +28,7 @@ DEPLOY ACTIONS: DAO ACTIONS: 1. Make OwnableTimedMinter a minter 2. Mint initial 100M FEI + */ export const deploy: DeployUpgradeFunc = async (deployAddress, addresses, logging = false) => { @@ -49,18 +52,25 @@ export const deploy: DeployUpgradeFunc = async (deployAddress, addresses, loggin }; export const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { - logging && console.log('No setup for FIP-35'); + const timelock: FeiDAOTimelock = contracts.feiDAOTimelock as FeiDAOTimelock; + await (await timelock.connect(await getImpersonatedSigner(addresses.multisig)).rollback()).wait(); }; export const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { - logging && console.log('No teardown for FIP-35'); + logging && console.log('No teardown for FIP-34'); }; export const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts) => { - const { fei, optimisticMinter, optimisticTimelock } = contracts; + const { fei, optimisticMinter, optimisticTimelock, feiDAOTimelock, feiDAO, timelock, tribe } = contracts; + expect(await fei.balanceOf(optimisticTimelock.address)).to.be.bignumber.greaterThan( ethers.constants.WeiPerEther.mul(100_000_000) ); + expect(await optimisticMinter.owner()).to.be.equal(optimisticTimelock.address); expect(await optimisticMinter.isTimeStarted()).to.be.true; + expect(await timelock.admin()).to.be.equal(feiDAO.address); + expect(await feiDAOTimelock.admin()).to.be.equal(feiDAO.address); + expect(await feiDAO.timelock()).to.be.equal(feiDAOTimelock.address); + expect(await tribe.minter()).to.be.equal(feiDAOTimelock.address); }; diff --git a/proposals/dao/fip_37.ts b/proposals/dao/fip_37.ts index 75f673716..51b9509db 100644 --- a/proposals/dao/fip_37.ts +++ b/proposals/dao/fip_37.ts @@ -203,11 +203,11 @@ export const deploy: DeployUpgradeFunc = async (deployAddress, addresses, loggin }; export const setup: SetupUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { - logging && console.log('No setup for FIP-33'); + logging && console.log('No setup for FIP-37'); }; export const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts, logging) => { - logging && console.log('No teardown for FIP-33'); + logging && console.log('No teardown for FIP-37'); }; export const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts) => { diff --git a/proposals/description/fip_34.json b/proposals/description/fip_34.json index e4db4d3d6..e22eb81dc 100644 --- a/proposals/description/fip_34.json +++ b/proposals/description/fip_34.json @@ -1,12 +1,19 @@ { - "proposal_title": "FIP-35: Optimistic Minter", + "proposal_title": "FIP-34: Optimistic Minter", "proposal_commands": [ { "target": "core", "values": "0", "method": "grantMinter(address)", "arguments": ["{optimisticMinter}"], - "description": "Grant Minter optimistic Minter" + "description": "Grant FEI Minter role to optimistic Minter" + }, + { + "target": "core", + "values": "0", + "method": "grantMinter(address)", + "arguments": ["{timelock}"], + "description": "Grant FEI Minter to timelock" }, { "target": "fei", @@ -14,6 +21,27 @@ "method": "mint(address,uint256)", "arguments": ["{optimisticTimelock}", "100000000000000000000000000"], "description": "Mint 100M FEI to OA timelock" - } + }, + { + "target": "core", + "values": "0", + "method": "revokeMinter(address)", + "arguments": ["{timelock}"], + "description": "Revoke FEI Minter from timelock" + }, + { + "target": "tribe", + "values": "0", + "method": "setMinter(address)", + "arguments": ["{feiDAOTimelock}"], + "description": "Set TRIBE minter to FEI DAO timelock" + }, + { + "target": "feiDAO", + "values": "0", + "method": "updateTimelock(address)", + "arguments": ["{feiDAOTimelock}"], + "description": "Restore FEI DAO timelock" + } ] } \ No newline at end of file diff --git a/proposals/description/fip_34.txt b/proposals/description/fip_34.txt index 0c0290613..818787660 100644 --- a/proposals/description/fip_34.txt +++ b/proposals/description/fip_34.txt @@ -2,9 +2,13 @@ Summary: Grant optimistic approval the rate limited ability to mint FEI, to continue to fund DAO operations like FIP-13 lending deployments and potentially Liquidity-as-a-Service. Additionally mint an initial 100M FEI to the timelock. +Also transitions remaining roles from FIP-31. + Motivation: Instead of continually going back to the DAO to ask for more funding, Fei Protocol can deploy a contract which allows the OA timelock to mint FEI periodically. This minter will have a hard rate limit on the amount minted. These mintings will still be subject to the 4 day timelock, but would not require governance intervention. +Unrelated to the main proposal, this proposal also transitions the Tribe minter role from the old DAO timelock to the new DAO timelock. + Forum discussion: https://tribe.fei.money/t/fip-34-fei-minting-for-optimistic-approval/3565 Code: https://github.com/fei-protocol/fei-protocol-core/pull/259 diff --git a/proposals/description/fip_37.json b/proposals/description/fip_37.json index 38c85cd93..925958563 100644 --- a/proposals/description/fip_37.json +++ b/proposals/description/fip_37.json @@ -1,5 +1,5 @@ { - "proposal_title": "FIP-33: TRIBE buybacks", + "proposal_title": "FIP-37: TRIBE buybacks", "proposal_commands": [ { "target": "core", diff --git a/scripts/utils/checkProposal.ts b/scripts/utils/checkProposal.ts index b39f7fe89..8f4cafdaf 100644 --- a/scripts/utils/checkProposal.ts +++ b/scripts/utils/checkProposal.ts @@ -1,7 +1,6 @@ -import { getAllContracts } from '../../test/integration/setup/loadContracts'; -import hre, { ethers } from 'hardhat'; -import { time } from '@openzeppelin/test-helpers'; -import { NamedContracts, namedContractsToNamedAddresses, UpgradeFuncs } from '../../types/types'; +import { getAllContracts, getAllContractAddresses } from '@test/integration/setup/loadContracts'; +import { getImpersonatedSigner, time } from '@test/helpers'; +import { NamedContracts, UpgradeFuncs } from '@custom-types/types'; import * as dotenv from 'dotenv'; @@ -22,16 +21,26 @@ async function checkProposal() { throw new Error('DEPLOY_FILE or PROPOSAL_NUMBER env variable not set'); } + // Get the upgrade setup, run and teardown scripts + const proposalFuncs: UpgradeFuncs = await import(`@proposals/dao/${proposalName}`); + const contracts = (await getAllContracts()) as NamedContracts; - const { feiDAO } = contracts; + const contractAddresses = await getAllContractAddresses(); - await hre.network.provider.request({ - method: 'hardhat_impersonateAccount', - params: [voterAddress] - }); + if (process.env.DO_SETUP) { + console.log('Setup'); + await proposalFuncs.setup( + contractAddresses, + contracts as unknown as NamedContracts, + contracts as unknown as NamedContracts, + true + ); + } - const voterSigner = await ethers.getSigner(voterAddress); + const { feiDAO } = contracts; + + const voterSigner = await getImpersonatedSigner(voterAddress); console.log(`Proposal Number: ${proposalNo}`); @@ -76,11 +85,6 @@ async function checkProposal() { await feiDAO['execute(uint256)'](proposalNo); console.log('Success'); - // Get the upgrade setup, run and teardown scripts - const proposalFuncs: UpgradeFuncs = await import(`../../proposals/dao/${proposalName}`); - - const contractAddresses = namedContractsToNamedAddresses(contracts); - console.log('Teardown'); await proposalFuncs.teardown( contractAddresses, diff --git a/scripts/utils/constructProposalCalldata.ts b/scripts/utils/constructProposalCalldata.ts new file mode 100644 index 000000000..47b76ede3 --- /dev/null +++ b/scripts/utils/constructProposalCalldata.ts @@ -0,0 +1,39 @@ +import constructProposal from './constructProposal'; +import { BigNumber } from 'ethers'; +import { Interface } from '@ethersproject/abi'; +import { utils } from 'ethers'; + +type ExtendedAlphaProposal = { + targets: string[]; + values: BigNumber[]; + signatures: string[]; + calldatas: string[]; + description: string; +}; + +/** + * Take in a hardhat proposal object and output the proposal calldatas + * See `proposals/utils/getProposalCalldata.js` on how to construct the proposal calldata + */ +export async function constructProposalCalldata(proposalName: string): Promise { + const proposal = (await constructProposal(proposalName)) as ExtendedAlphaProposal; + + const proposeFuncFrag = new Interface([ + 'function propose(address[] memory targets,uint256[] memory values,bytes[] memory calldatas,string memory description) public returns (uint256)' + ]); + + const combinedCalldatas = []; + for (let i = 0; i < proposal.targets.length; i++) { + const sighash = utils.id(proposal.signatures[i]).slice(0, 10); + combinedCalldatas.push(`${sighash}${proposal.calldatas[i].slice(2)}`); + } + + const calldata = proposeFuncFrag.encodeFunctionData('propose', [ + proposal.targets, + proposal.values, + combinedCalldatas, + proposal.description + ]); + + return calldata; +} diff --git a/scripts/utils/exec.ts b/scripts/utils/exec.ts index 15e70d2d5..87fabdba7 100644 --- a/scripts/utils/exec.ts +++ b/scripts/utils/exec.ts @@ -1,5 +1,5 @@ import hre, { ethers } from 'hardhat'; -import { time } from '@openzeppelin/test-helpers'; +import { time } from '@test/helpers'; import * as dotenv from 'dotenv'; diff --git a/scripts/utils/getProposalCalldata.ts b/scripts/utils/getProposalCalldata.ts index c1acc55c3..bc0a45f8d 100644 --- a/scripts/utils/getProposalCalldata.ts +++ b/scripts/utils/getProposalCalldata.ts @@ -1,19 +1,8 @@ -import constructProposal from './constructProposal'; import * as dotenv from 'dotenv'; -import { BigNumber } from 'ethers'; -import { Interface } from '@ethersproject/abi'; -import { utils } from 'ethers'; +import { constructProposalCalldata } from './constructProposalCalldata'; dotenv.config(); -type ExtendedAlphaProposal = { - targets: string[]; - values: BigNumber[]; - signatures: string[]; - calldatas: string[]; - description: string; -}; - /** * Take in a hardhat proposal object and output the proposal calldatas * See `proposals/utils/getProposalCalldata.js` on how to construct the proposal calldata @@ -25,28 +14,7 @@ async function getProposalCalldata() { throw new Error('DEPLOY_FILE env variable not set'); } - const proposal = (await constructProposal(proposalName)) as ExtendedAlphaProposal; - - const proposeFuncFrag = new Interface([ - 'function propose(address[] memory targets,uint256[] memory values,bytes[] memory calldatas,string memory description) public returns (uint256)' - ]); - - const combinedCalldatas = []; - for (let i = 0; i < proposal.targets.length; i++) { - const sighash = utils.id(proposal.signatures[i]).slice(0, 10); - combinedCalldatas.push(`${sighash}${proposal.calldatas[i].slice(2)}`); - } - - console.log(combinedCalldatas); - - const calldata = proposeFuncFrag.encodeFunctionData('propose', [ - proposal.targets, - proposal.values, - combinedCalldatas, - proposal.description - ]); - - console.log(calldata); + console.log(await constructProposalCalldata(proposalName)); } getProposalCalldata() diff --git a/test/integration/proposals_config.json b/test/integration/proposals_config.json index adb5ce182..c72399b73 100644 --- a/test/integration/proposals_config.json +++ b/test/integration/proposals_config.json @@ -1,8 +1,8 @@ { - "fip_33" : { + "fip_34" : { "deploy" : false }, - "fip_35" : { + "fip_37" : { "deploy" : false } } \ No newline at end of file diff --git a/test/integration/tests/bondingcurve.ts b/test/integration/tests/bondingcurve.ts index 73aaf3bce..04050d71c 100644 --- a/test/integration/tests/bondingcurve.ts +++ b/test/integration/tests/bondingcurve.ts @@ -7,6 +7,7 @@ import { expectApprox, resetFork, time } from '@test/helpers'; import proposals from '@test/integration/proposals_config.json'; import { TestEndtoEndCoordinator } from '@test/integration/setup'; import { forceEth } from '@test/integration/setup/utils'; +import { UniswapPCVDeposit } from '@custom-types/contracts'; const toBN = ethers.BigNumber.from; @@ -193,9 +194,11 @@ describe('e2e-bondingcurve', function () { it('should transfer allocation from dpi bonding curve to the uniswap deposit and Fuse', async function () { const bondingCurve = contracts.dpiBondingCurve; - const uniswapPCVDeposit = contracts.dpiUniswapPCVDeposit; + const uniswapPCVDeposit: UniswapPCVDeposit = contracts.dpiUniswapPCVDeposit as UniswapPCVDeposit; const fusePCVDeposit = contracts.indexCoopFusePoolDpiPCVDeposit; + await uniswapPCVDeposit.setMaxBasisPointsFromPegLP(10_000); + const pcvAllocations = await bondingCurve.getAllocation(); expect(pcvAllocations[0].length).to.be.equal(2); diff --git a/test/integration/tests/dao.ts b/test/integration/tests/dao.ts index e1c6e35ec..1cc76ac18 100644 --- a/test/integration/tests/dao.ts +++ b/test/integration/tests/dao.ts @@ -275,12 +275,10 @@ describe('e2e-dao', function () { } } - /* doLogging && console.log(`Testing tribe minter address...`); const tribe = contracts.tribe; const tribeMinter = await tribe.minter(); - expect(tribeMinter).to.equal(contractAddresses.tribeReserveStabilizer); - */ // TODO re-enable after tribe reserve stabilizer is deployed + expect(tribeMinter).to.equal(contractAddresses.feiDAOTimelock); }); }); }); diff --git a/test/integration/tests/fip_34.ts.disabled b/test/integration/tests/fip_34.ts.disabled new file mode 100644 index 000000000..6d825577d --- /dev/null +++ b/test/integration/tests/fip_34.ts.disabled @@ -0,0 +1,163 @@ +import chai, { expect } from 'chai'; +import CBN from 'chai-bn'; +import { solidity } from 'ethereum-waffle'; +import { ethers } from 'hardhat'; +import { NamedContracts } from '@custom-types/types'; +import { getImpersonatedSigner, resetFork, time } from '@test/helpers'; +import { + Fei, + FeiDAO, + FeiDAOTimelock, + OptimisticTimelock, + Timelock, + OwnableTimedMinter, + Tribe +} from '@custom-types/contracts'; +import { getAllContracts } from '../setup/loadContracts'; +import { constructProposalCalldata } from '@scripts/utils/constructProposalCalldata'; + +before(async () => { + chai.use(CBN(ethers.BigNumber)); + chai.use(solidity); + await resetFork(); +}); + +describe('e2e-fip-34', function () { + let contracts: NamedContracts; + let deployAddress: string; + let doLogging: boolean; + + before(async function () { + // Setup test environment and get contracts + const version = 1; + deployAddress = (await ethers.getSigners())[0].address; + if (!deployAddress) throw new Error(`No deploy address!`); + + doLogging = Boolean(process.env.LOGGING); + + // const config = { + // logging: doLogging, + // deployAddress: deployAddress, + // version: version + // }; + + contracts = await getAllContracts(); + + /* + e2eCoord = new TestEndtoEndCoordinator(config, proposals); + doLogging && console.log(`Loading environment...`); + ({ contracts, contractAddresses } = await e2eCoord.loadEnvironment()); + doLogging && console.log(`Environment loaded.`); + */ + + doLogging && console.log(`Environment loading skipped; this is a pure forked-mainnet test.`); + doLogging && console.log(`(no impersonating of contract addresses here except the guardian)`); + }); + + describe('fip-34', async function () { + it('works when we roll back the timelock just before scheduling the vote result', async function () { + const feiDAO = contracts.feiDAO as FeiDAO; + const feiDAOTimelock = contracts.feiDAOTimelock as FeiDAOTimelock; + const governorAlphaTimelock = contracts.timelock as Timelock; + const fei = contracts.fei as Fei; + const optimisticTimelock = contracts.optimisticTimelock as OptimisticTimelock; + const optimisticMinter = contracts.optimisticMinter as OwnableTimedMinter; + const tribe = contracts.tribe as Tribe; + + const joeyAddress = '0xe0ac4559739bD36f0913FB0A3f5bFC19BCBaCD52'; + const calebAddress = '0xb81cf4981Ef648aaA73F07a18B03970f04d5D8bF'; + const stormAddress = '0xC64Ed730e030BdCB66E9B5703798bb4275A5a484'; + const briAddress = '0x90300D66AF91d5EAB695A07c274E61e1563967C9'; + const nascentAddress = '0x70b6ab736be7672c917a1ab11e67b5bc9fddeca9'; + const buckleyAddress = '0x66b9d411e14fbc86424367b67933945fd7e40b11'; + const frameworkAddress = '0x961bcb93666e0ea73b6d88a03817cb36f93a6dd9'; + const guardianAddress = '0xB8f482539F2d3Ae2C9ea6076894df36D1f632775'; + + const joeySigner = await getImpersonatedSigner(joeyAddress); + const calebSigner = await getImpersonatedSigner(calebAddress); + const stormSigner = await getImpersonatedSigner(stormAddress); + const briSigner = await getImpersonatedSigner(briAddress); + const nascentSigner = await getImpersonatedSigner(nascentAddress); + const buckleySigner = await getImpersonatedSigner(buckleyAddress); + const frameworkSigner = await getImpersonatedSigner(frameworkAddress); + const guardianSigner = await getImpersonatedSigner(guardianAddress); + + // Guardian rolls back the timelock to the old timelock + + // Queue FIP-34 (calldata generated by running the calldata npm script) + const calldata = await constructProposalCalldata('fip_34'); + + const proposeTxReceipt = await (await joeySigner.sendTransaction({ to: feiDAO.address, data: calldata })).wait(); + const proposeTxLog = proposeTxReceipt.logs[0]; + const parsedLog = feiDAO.interface.parseLog(proposeTxLog); + const proposalId = parsedLog.args[0]; + + doLogging && console.log(`ProposalID: ${parsedLog}`); + + // Send eth to voters + const vitalikAddress = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'; + const vitalikSigner = await getImpersonatedSigner(vitalikAddress); + + await vitalikSigner.sendTransaction({ to: nascentAddress, value: ethers.utils.parseEther('5') }); + await vitalikSigner.sendTransaction({ to: buckleyAddress, value: ethers.utils.parseEther('5') }); + await vitalikSigner.sendTransaction({ to: frameworkAddress, value: ethers.utils.parseEther('5') }); + + // Wait 1 hour + await time.increase(3600); + + // Vote + doLogging && console.log(`Voting for proposal (joey)`); + await (await feiDAO.connect(joeySigner).castVote(proposalId, 1)).wait(); + + doLogging && console.log(`Voting for proposal (caleb)`); + await (await feiDAO.connect(calebSigner).castVote(proposalId, 1)).wait(); + + doLogging && console.log(`Voting for proposal (storm)`); + await (await feiDAO.connect(stormSigner).castVote(proposalId, 1)).wait(); + + doLogging && console.log(`Voting for proposal (bri)`); + await (await feiDAO.connect(briSigner).castVote(proposalId, 1)).wait(); + + doLogging && console.log(`Voting for proposal (buckley)`); + await (await feiDAO.connect(buckleySigner).castVote(proposalId, 1)).wait(); + + doLogging && console.log(`Voting for proposal (framework)`); + await (await feiDAO.connect(frameworkSigner).castVote(proposalId, 1)).wait(); + + doLogging && console.log(`Voting for proposal (nascent)`); + await (await feiDAO.connect(nascentSigner).castVote(proposalId, 1)).wait(); + + const proposalData = await feiDAO.proposals(proposalId); + + const endBlock = proposalData[4]; + const votesFor = ethers.utils.parseUnits(proposalData[5].toString(), 'wei'); + + doLogging && console.log(`# of votes so far: ${votesFor}`); + + // Advance to end of voting period and roll back the timelock via the guardian + await time.advanceBlockTo(endBlock.toNumber() + 1); + await (await feiDAOTimelock.connect(guardianSigner).rollback()).wait(); + + // Queue FIP-34 + await (await feiDAO.connect(joeySigner)['queue(uint256)'](proposalId)).wait(); + + // Wait 3 days + await time.increase(259200); + + // Execute FIP-34 + await (await feiDAO.connect(joeySigner)['execute(uint256)'](proposalId)).wait(); + + // Check everything + expect(await fei.balanceOf(optimisticTimelock.address)).to.be.bignumber.greaterThan( + ethers.constants.WeiPerEther.mul(100_000_000) + ); + + expect(await optimisticMinter.owner()).to.be.equal(optimisticTimelock.address); + expect(await optimisticMinter.isTimeStarted()).to.be.true; + expect(await governorAlphaTimelock.admin()).to.be.equal(feiDAO.address); + expect(await feiDAOTimelock.admin()).to.be.equal(feiDAO.address); + expect(await feiDAO.timelock()).to.be.equal(feiDAOTimelock.address); + expect(await tribe.minter()).to.be.equal(feiDAOTimelock.address); + }); + }); +}); diff --git a/test/unit/staking/feirari/RewardsDistributorAdmin.test.ts b/test/unit/staking/feirari/RewardsDistributorAdmin.test.ts index 50ad2d620..3da5e4e95 100644 --- a/test/unit/staking/feirari/RewardsDistributorAdmin.test.ts +++ b/test/unit/staking/feirari/RewardsDistributorAdmin.test.ts @@ -1,17 +1,11 @@ -import { expectRevert, getAddresses, getCore } from '../../../helpers'; +import { expectRevert, getAddresses, getCore, ZERO_ADDRESS } from '@test/helpers'; import { expect } from 'chai'; import hre, { ethers } from 'hardhat'; import { Signer, utils } from 'ethers'; -import testHelpers from '@openzeppelin/test-helpers'; -import { Core } from '../../../../types/contracts/Core'; -import { RewardsDistributorAdmin } from '../../../../types/contracts/RewardsDistributorAdmin'; -import { MockRewardsDistributor } from '../../../../types/contracts/MockRewardsDistributor'; +import { Core, RewardsDistributorAdmin, MockRewardsDistributor } from '@custom-types/contracts'; import { keccak256 } from 'ethers/lib/utils'; const toBN = ethers.BigNumber.from; -const { - constants: { ZERO_ADDRESS } -} = testHelpers; describe('RewardsDistributorAdmin', function () { let governorAddress: string;