From 9858c67bbe43b4e42150c59701d33e86647bafe4 Mon Sep 17 00:00:00 2001 From: "hickuphh3@gmail.com" Date: Wed, 19 Jan 2022 18:18:06 +0800 Subject: [PATCH 1/4] Add script for simulating an existing proposal --- scripts/deploy/config.ts | 2 +- scripts/proposals/index.ts | 7 ++++ scripts/proposals/simulateExistingProposal.ts | 28 ++++++++++++++++ .../shared => shared}/AccountManipulation.ts | 0 {test/shared => shared}/Constants.ts | 0 {test/shared => shared}/Forking.ts | 16 +++++++++- {test/shared => shared}/Governance.ts | 32 ++++++++++++++----- shared/Polygonscan.ts | 18 +++++++++++ {test/shared => shared}/TimeManipulation.ts | 0 test/ArenaToken.spec.ts | 6 ++-- test/GovernanceSim.spec.ts | 6 ++-- test/RevokableTokenLock.spec.ts | 4 +-- test/TokenLock.spec.ts | 4 +-- test/TokenSale.spec.ts | 4 +-- tsconfig.json | 2 +- 15 files changed, 106 insertions(+), 23 deletions(-) create mode 100644 scripts/proposals/simulateExistingProposal.ts rename {test/shared => shared}/AccountManipulation.ts (100%) rename {test/shared => shared}/Constants.ts (100%) rename {test/shared => shared}/Forking.ts (82%) rename {test/shared => shared}/Governance.ts (75%) create mode 100644 shared/Polygonscan.ts rename {test/shared => shared}/TimeManipulation.ts (100%) 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..2490772 --- /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 75% rename from test/shared/Governance.ts rename to shared/Governance.ts index 3b63499..8379aa6 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,20 @@ 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); + targets.map(async (target) => { + // get ABI + let abi = await getABIFromPolygonscan(target); + let iface = new ethers.utils.Interface(abi); + let events = result.logs.map((log) => iface.parseLog(log)); + console.log(events); + }); + + let timelockEvents = result.logs.map((log) => timeLock.interface.parseLog(log)); + console.log(timelockEvents); return proposalId; }; diff --git a/shared/Polygonscan.ts b/shared/Polygonscan.ts new file mode 100644 index 0000000..0c26352 --- /dev/null +++ b/shared/Polygonscan.ts @@ -0,0 +1,18 @@ +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"] } From 96c0e246315161bb6f0d1be6c368cab02bb779b0 Mon Sep 17 00:00:00 2001 From: "hickuphh3@gmail.com" Date: Mon, 31 Jan 2022 12:44:17 +0800 Subject: [PATCH 2/4] CR fix --- scripts/proposals/simulateExistingProposal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/proposals/simulateExistingProposal.ts b/scripts/proposals/simulateExistingProposal.ts index 2490772..e88337c 100644 --- a/scripts/proposals/simulateExistingProposal.ts +++ b/scripts/proposals/simulateExistingProposal.ts @@ -13,7 +13,7 @@ export async function simulateExistingProposal(proposalId: string, hre: HardhatR }); const proposalActions = await deployment.governor.getActions(proposalId); - let valuesArray = [...proposalActions[1]].map((value) => value.toString()); + 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}`); From 9356562f609830e4014d063fa21dc2df440faa93 Mon Sep 17 00:00:00 2001 From: "hickuphh3@gmail.com" Date: Mon, 31 Jan 2022 13:54:58 +0800 Subject: [PATCH 3/4] try-catch parsing events because reverts happen for no matching events --- shared/Governance.ts | 19 +++++++++++++++++-- shared/Polygonscan.ts | 2 ++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/shared/Governance.ts b/shared/Governance.ts index 8379aa6..5c3c0ed 100644 --- a/shared/Governance.ts +++ b/shared/Governance.ts @@ -78,11 +78,26 @@ export const createAndExecuteProposal = async ({ // get ABI let abi = await getABIFromPolygonscan(target); let iface = new ethers.utils.Interface(abi); - let events = result.logs.map((log) => iface.parseLog(log)); + let events = result.logs.map((log) => { + try { + return iface.parseLog(log); + } catch (e) { + // no matching event + } + }); + console.log(`### TARGET ${target} EVENTS ###`); console.log(events); + console.log(`###################################`); }); - let timelockEvents = result.logs.map((log) => timeLock.interface.parseLog(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 index 0c26352..6815dfc 100644 --- a/shared/Polygonscan.ts +++ b/shared/Polygonscan.ts @@ -1,3 +1,5 @@ +import fetch from 'node-fetch'; + export async function getABIFromPolygonscan(address: string) { if (process.env.POLYGONSCAN_API_KEY == undefined) { console.log('Require polygonscan key, exiting...'); From 98a565ed92f279bb6a86733af9368c48bea2a0f8 Mon Sep 17 00:00:00 2001 From: "hickuphh3@gmail.com" Date: Mon, 31 Jan 2022 13:58:52 +0800 Subject: [PATCH 4/4] Explicit for loop bcos map doesnt seem to work --- shared/Governance.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shared/Governance.ts b/shared/Governance.ts index 5c3c0ed..641bc92 100644 --- a/shared/Governance.ts +++ b/shared/Governance.ts @@ -74,9 +74,9 @@ export const createAndExecuteProposal = async ({ tx = await governor.connect(user)['execute(uint256)'](proposalId); let result = await tx.wait(1); - targets.map(async (target) => { - // get ABI - let abi = await getABIFromPolygonscan(target); + + 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 { @@ -85,10 +85,10 @@ export const createAndExecuteProposal = async ({ // no matching event } }); - console.log(`### TARGET ${target} EVENTS ###`); + console.log(`### TARGET ${targets[i]} EVENTS ###`); console.log(events); console.log(`###################################`); - }); + } let timelockEvents = result.logs.map((log) => { try {