diff --git a/scripts/deploy/config.ts b/scripts/deploy/config.ts index 75643ff..c319801 100644 --- a/scripts/deploy/config.ts +++ b/scripts/deploy/config.ts @@ -1,5 +1,5 @@ import {BigNumber as BN, constants} from 'ethers'; -import {ONE_18, ONE_DAY, ONE_YEAR} from '../../test/shared/Constants'; +import {ONE_18, ONE_DAY, ONE_YEAR} from '../../shared/Constants'; type Config = { FREE_SUPPLY: BN; diff --git a/scripts/proposals/index.ts b/scripts/proposals/index.ts index a370dbc..85deda3 100644 --- a/scripts/proposals/index.ts +++ b/scripts/proposals/index.ts @@ -8,3 +8,10 @@ task('propose', 'propose transfer') const {transferProposal} = await import('./transfer'); await transferProposal(json, hre); }); + +task('simulateExistingProposal', 'simulates an existing proposal (by cloning it)') + .addParam('id', 'Proposal ID') + .setAction(async ({id}, hre) => { + const {simulateExistingProposal} = await import('./simulateExistingProposal'); + await simulateExistingProposal(id, hre); + }); diff --git a/scripts/proposals/simulateExistingProposal.ts b/scripts/proposals/simulateExistingProposal.ts new file mode 100644 index 0000000..e88337c --- /dev/null +++ b/scripts/proposals/simulateExistingProposal.ts @@ -0,0 +1,28 @@ +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {getPolygonContracts, getForkParams} from '../../shared/Forking'; +import {createAndExecuteProposal} from '../../shared/Governance'; + +export async function simulateExistingProposal(proposalId: string, hre: HardhatRuntimeEnvironment) { + const [user] = await hre.ethers.getSigners(); + const deployment = getPolygonContracts(user); + + // attempt mainnet forking + await hre.network.provider.request({ + method: 'hardhat_reset', + params: [getForkParams()], + }); + + const proposalActions = await deployment.governor.getActions(proposalId); + let valuesArray = proposalActions[1].map((value) => value.toString()); + console.log(`proposal targets: ${proposalActions.targets}`); + console.log(`proposal values: ${valuesArray}`); + console.log(`proposal calldatas: ${proposalActions.calldatas}`); + console.log(`cloning proposal...`); + await createAndExecuteProposal({ + user, + targets: proposalActions.targets, + values: valuesArray, + calldatas: proposalActions.calldatas, + ...deployment, + }); +} diff --git a/test/shared/AccountManipulation.ts b/shared/AccountManipulation.ts similarity index 100% rename from test/shared/AccountManipulation.ts rename to shared/AccountManipulation.ts diff --git a/test/shared/Constants.ts b/shared/Constants.ts similarity index 100% rename from test/shared/Constants.ts rename to shared/Constants.ts diff --git a/test/shared/Forking.ts b/shared/Forking.ts similarity index 82% rename from test/shared/Forking.ts rename to shared/Forking.ts index effdb53..1a02d49 100644 --- a/test/shared/Forking.ts +++ b/shared/Forking.ts @@ -10,7 +10,7 @@ import { TimelockController__factory, TokenLock, TokenLock__factory, -} from '../../typechain'; +} from '../typechain'; export type DeployedContracts = { governor: ArenaGovernor; @@ -49,3 +49,17 @@ export const getPolygonContracts = (signer: Signer): DeployedContracts => { tokenLock: TokenLock__factory.connect(tokenLockAddress, signer), }; }; + +export function getForkParams() { + if (process.env.POLYGON_URL == undefined) { + console.log(`Missing POLYGON_URL in .env`); + process.exit(1); + } + let forkParams: any = { + forking: { + jsonRpcUrl: process.env.POLYGON_URL + } + }; + if (process.env.FORK_BLOCK) forkParams['forking']['blockNumber'] = Number(process.env.FORK_BLOCK); + return forkParams; +} diff --git a/test/shared/Governance.ts b/shared/Governance.ts similarity index 70% rename from test/shared/Governance.ts rename to shared/Governance.ts index 3b63499..641bc92 100644 --- a/test/shared/Governance.ts +++ b/shared/Governance.ts @@ -1,9 +1,10 @@ import {Signer} from 'ethers'; import {ethers} from 'hardhat'; -import {impersonateAccountWithFunds, stopImpersonateAccount} from '../shared/AccountManipulation'; -import {increaseNextBlockTime, setNextBlockNumber} from '../shared/TimeManipulation'; +import {impersonateAccountWithFunds, stopImpersonateAccount} from './AccountManipulation'; +import {increaseNextBlockTime, setNextBlockNumber} from './TimeManipulation'; import {POLYGON_AVERAGE_BLOCK_TIME} from './Constants'; import {DeployedContracts} from './Forking'; +import {getABIFromPolygonscan} from './Polygonscan'; export const createAndExecuteProposal = async ({ governor, @@ -23,15 +24,18 @@ export const createAndExecuteProposal = async ({ const timeLockSigner = await impersonateAccountWithFunds(timeLock.address); let originalVotingDelay = await governor.votingDelay(); let originalVotingPeriod = await governor.votingPeriod(); + console.log('setting voting delay and duration to 2 blocks...'); await governor.connect(timeLockSigner).setVotingDelay(`2`); await governor.connect(timeLockSigner).setVotingPeriod(`2`); // 1. borrow some treasury tokens to user as we need signer with min. proposalThreshold tokens to propose const quorumAmount = await governor.quorumVotes(); // careful, this sends ETH to timelock which might break real-world simulation for proposals involving Timelock ETH + console.log('transferring tokens to user for proposal creation...'); await arenaToken.connect(timeLockSigner).transfer(await user.getAddress(), quorumAmount); await arenaToken.connect(user).delegate(await user.getAddress()); const descriptionHash = ethers.utils.keccak256([]); // keccak(``) + console.log('creating proposal...'); let tx = await governor.connect(user)['propose(address[],uint256[],bytes[],string)'](targets, values, calldatas, ``); let {events} = await tx.wait(); // get first event (ProposalCreated), then get first arg of that event (proposalId) @@ -42,6 +46,7 @@ export const createAndExecuteProposal = async ({ // simulate elapsed time close to original voting delay await increaseNextBlockTime(Math.floor(POLYGON_AVERAGE_BLOCK_TIME * originalVotingDelay.toNumber())); await setNextBlockNumber(voteStartBlock.toNumber() + 1); // is a blocknumber which fits in Number + console.log('casting vote...'); tx = await governor.connect(user)['castVote'](proposalId, `1`); // 3. return borrowed tokens @@ -52,12 +57,12 @@ export const createAndExecuteProposal = async ({ // simulate elapsed time close to original voting delay await increaseNextBlockTime(Math.floor(POLYGON_AVERAGE_BLOCK_TIME * originalVotingPeriod.toNumber())); await setNextBlockNumber(voteEndBlock.toNumber() + 1); // is a blocknumber which fits in Number - tx = await governor - .connect(user) - ['queue(address[],uint256[],bytes[],bytes32)'](targets, values, calldatas, descriptionHash); + console.log('queueing proposal...'); + tx = await governor.connect(user)['queue(uint256)'](proposalId); await tx.wait(); // can revert Governor changes now + console.log('resetting voting delay and period...'); await governor.connect(timeLockSigner).setVotingDelay(originalVotingDelay); await governor.connect(timeLockSigner).setVotingPeriod(originalVotingPeriod); await stopImpersonateAccount(timeLock.address); @@ -65,9 +70,35 @@ export const createAndExecuteProposal = async ({ // 5. advance time past timelock delay and then execute const timeLockMinDelaySeconds = await timeLock.getMinDelay(); await increaseNextBlockTime(timeLockMinDelaySeconds.toNumber()); - await governor - .connect(user) - ['execute(address[],uint256[],bytes[],bytes32)'](targets, values, calldatas, descriptionHash); + console.log('executing proposal...'); + tx = await governor.connect(user)['execute(uint256)'](proposalId); + + let result = await tx.wait(1); + + for (let i = 0; i < targets.length; i++) { + let abi = await getABIFromPolygonscan(targets[i]); + let iface = new ethers.utils.Interface(abi); + let events = result.logs.map((log) => { + try { + return iface.parseLog(log); + } catch (e) { + // no matching event + } + }); + console.log(`### TARGET ${targets[i]} EVENTS ###`); + console.log(events); + console.log(`###################################`); + } + + let timelockEvents = result.logs.map((log) => { + try { + return timeLock.interface.parseLog(log); + } catch (e) { + // no matching event + } + }); + console.log(`### TIMELOCK EVENTS ###`); + console.log(timelockEvents); return proposalId; }; diff --git a/shared/Polygonscan.ts b/shared/Polygonscan.ts new file mode 100644 index 0000000..6815dfc --- /dev/null +++ b/shared/Polygonscan.ts @@ -0,0 +1,20 @@ +import fetch from 'node-fetch'; + +export async function getABIFromPolygonscan(address: string) { + if (process.env.POLYGONSCAN_API_KEY == undefined) { + console.log('Require polygonscan key, exiting...'); + process.exit(1); + } + + let abiRequest = await fetch( + `https://api.polygonscan.com/api?module=contract&action=getabi` + + `&address=${address}` + + `&apikey=${process.env.POLYGONSCAN_API_KEY}` + ); + let abi = await abiRequest.json(); + if (abi.status == '0') { + console.log(abi.result); + process.exit(1); + } + return abi.result; +} diff --git a/test/shared/TimeManipulation.ts b/shared/TimeManipulation.ts similarity index 100% rename from test/shared/TimeManipulation.ts rename to shared/TimeManipulation.ts diff --git a/test/ArenaToken.spec.ts b/test/ArenaToken.spec.ts index 49fd9ab..ec5abab 100644 --- a/test/ArenaToken.spec.ts +++ b/test/ArenaToken.spec.ts @@ -4,9 +4,9 @@ import * as fs from 'fs'; import {ethers, waffle} from 'hardhat'; import * as path from 'path'; import {ArenaToken, RevokableTokenLock} from '../typechain'; -import {impersonateAccountWithFunds, stopImpersonateAccount} from './shared/AccountManipulation'; -import {ONE_18, ONE_DAY, ONE_YEAR} from './shared/Constants'; -import {resetNetwork, setNextBlockTimeStamp} from './shared/TimeManipulation'; +import {impersonateAccountWithFunds, stopImpersonateAccount} from '../shared/AccountManipulation'; +import {ONE_18, ONE_DAY, ONE_YEAR} from '../shared/Constants'; +import {resetNetwork, setNextBlockTimeStamp} from '../shared/TimeManipulation'; import {MerkleDistributorInfo} from '../src/parse-balance-map'; const {solidity, loadFixture} = waffle; diff --git a/test/GovernanceSim.spec.ts b/test/GovernanceSim.spec.ts index 54381c2..c86d714 100644 --- a/test/GovernanceSim.spec.ts +++ b/test/GovernanceSim.spec.ts @@ -1,8 +1,8 @@ import chai, {expect} from 'chai'; import {waffle} from 'hardhat'; -import {ZERO} from './shared/Constants'; -import {getPolygonContracts} from './shared/Forking'; -import {createAndExecuteProposal} from './shared/Governance'; +import {ZERO} from '../shared/Constants'; +import {getPolygonContracts} from '../shared/Forking'; +import {createAndExecuteProposal} from '../shared/Governance'; const {solidity} = waffle; chai.use(solidity); diff --git a/test/RevokableTokenLock.spec.ts b/test/RevokableTokenLock.spec.ts index dba8c95..7ba99dd 100644 --- a/test/RevokableTokenLock.spec.ts +++ b/test/RevokableTokenLock.spec.ts @@ -2,8 +2,8 @@ import {expect} from 'chai'; import {BigNumber} from 'ethers'; import {ethers, waffle} from 'hardhat'; import {IERC20, RevokableTokenLock} from '../typechain'; -import {ZERO_ADDRESS, ONE_HOUR} from './shared/Constants'; -import {setNextBlockTimeStamp, mineBlockAt} from './shared/TimeManipulation'; +import {ZERO_ADDRESS, ONE_HOUR} from '../shared/Constants'; +import {setNextBlockTimeStamp, mineBlockAt} from '../shared/TimeManipulation'; const {loadFixture} = waffle; diff --git a/test/TokenLock.spec.ts b/test/TokenLock.spec.ts index 0ec3e79..24018eb 100644 --- a/test/TokenLock.spec.ts +++ b/test/TokenLock.spec.ts @@ -2,8 +2,8 @@ import {expect} from 'chai'; import chai from 'chai'; import hre, {ethers, waffle} from 'hardhat'; import {IERC20, TokenLock} from '../typechain'; -import {MAX_UINT, ONE, ONE_DAY, ONE_YEAR, ONE_18, ZERO, ZERO_ADDRESS} from './shared/Constants'; -import {mineBlockAt, resetNetwork, setNextBlockTimeStamp} from './shared/TimeManipulation'; +import {MAX_UINT, ONE, ONE_DAY, ONE_YEAR, ONE_18, ZERO, ZERO_ADDRESS} from '../shared/Constants'; +import {mineBlockAt, resetNetwork, setNextBlockTimeStamp} from '../shared/TimeManipulation'; const {solidity, loadFixture} = waffle; chai.use(solidity); diff --git a/test/TokenSale.spec.ts b/test/TokenSale.spec.ts index 25cda8d..c2413f5 100644 --- a/test/TokenSale.spec.ts +++ b/test/TokenSale.spec.ts @@ -3,8 +3,8 @@ import chai from 'chai'; import {ethers, waffle} from 'hardhat'; import {BigNumber as BN} from 'ethers'; import {IERC20, RevokableTokenLock, TokenSale} from '../typechain'; -import {ONE_DAY, ONE_18, MAX_UINT, ONE_YEAR} from './shared/Constants'; -import {setNextBlockTimeStamp, resetNetwork} from './shared/TimeManipulation'; +import {ONE_DAY, ONE_18, MAX_UINT, ONE_YEAR} from '../shared/Constants'; +import {setNextBlockTimeStamp, resetNetwork} from '../shared/TimeManipulation'; const {solidity, loadFixture} = waffle; chai.use(solidity); diff --git a/tsconfig.json b/tsconfig.json index 47f802e..c3e00fd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,6 @@ "outDir": "dist", "declaration": true }, - "include": ["./scripts", "./test", "./typechain"], + "include": ["./scripts", "./test", "./typechain", "shared"], "files": ["./hardhat.config.ts"] }