Skip to content
This repository was archived by the owner on Apr 4, 2025. It is now read-only.

Commit bae376b

Browse files
authored
Merge pull request #42 from code-423n4/feature/simProposal
Add script for simulating an existing proposal
2 parents 17c2025 + 98a565e commit bae376b

15 files changed

+123
-23
lines changed

scripts/deploy/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {BigNumber as BN, constants} from 'ethers';
2-
import {ONE_18, ONE_DAY, ONE_YEAR} from '../../test/shared/Constants';
2+
import {ONE_18, ONE_DAY, ONE_YEAR} from '../../shared/Constants';
33

44
type Config = {
55
FREE_SUPPLY: BN;

scripts/proposals/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,10 @@ task('propose', 'propose transfer')
88
const {transferProposal} = await import('./transfer');
99
await transferProposal(json, hre);
1010
});
11+
12+
task('simulateExistingProposal', 'simulates an existing proposal (by cloning it)')
13+
.addParam('id', 'Proposal ID')
14+
.setAction(async ({id}, hre) => {
15+
const {simulateExistingProposal} = await import('./simulateExistingProposal');
16+
await simulateExistingProposal(id, hre);
17+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {HardhatRuntimeEnvironment} from 'hardhat/types';
2+
import {getPolygonContracts, getForkParams} from '../../shared/Forking';
3+
import {createAndExecuteProposal} from '../../shared/Governance';
4+
5+
export async function simulateExistingProposal(proposalId: string, hre: HardhatRuntimeEnvironment) {
6+
const [user] = await hre.ethers.getSigners();
7+
const deployment = getPolygonContracts(user);
8+
9+
// attempt mainnet forking
10+
await hre.network.provider.request({
11+
method: 'hardhat_reset',
12+
params: [getForkParams()],
13+
});
14+
15+
const proposalActions = await deployment.governor.getActions(proposalId);
16+
let valuesArray = proposalActions[1].map((value) => value.toString());
17+
console.log(`proposal targets: ${proposalActions.targets}`);
18+
console.log(`proposal values: ${valuesArray}`);
19+
console.log(`proposal calldatas: ${proposalActions.calldatas}`);
20+
console.log(`cloning proposal...`);
21+
await createAndExecuteProposal({
22+
user,
23+
targets: proposalActions.targets,
24+
values: valuesArray,
25+
calldatas: proposalActions.calldatas,
26+
...deployment,
27+
});
28+
}
File renamed without changes.
File renamed without changes.

test/shared/Forking.ts renamed to shared/Forking.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
TimelockController__factory,
1111
TokenLock,
1212
TokenLock__factory,
13-
} from '../../typechain';
13+
} from '../typechain';
1414

1515
export type DeployedContracts = {
1616
governor: ArenaGovernor;
@@ -49,3 +49,17 @@ export const getPolygonContracts = (signer: Signer): DeployedContracts => {
4949
tokenLock: TokenLock__factory.connect(tokenLockAddress, signer),
5050
};
5151
};
52+
53+
export function getForkParams() {
54+
if (process.env.POLYGON_URL == undefined) {
55+
console.log(`Missing POLYGON_URL in .env`);
56+
process.exit(1);
57+
}
58+
let forkParams: any = {
59+
forking: {
60+
jsonRpcUrl: process.env.POLYGON_URL
61+
}
62+
};
63+
if (process.env.FORK_BLOCK) forkParams['forking']['blockNumber'] = Number(process.env.FORK_BLOCK);
64+
return forkParams;
65+
}

test/shared/Governance.ts renamed to shared/Governance.ts

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {Signer} from 'ethers';
22
import {ethers} from 'hardhat';
3-
import {impersonateAccountWithFunds, stopImpersonateAccount} from '../shared/AccountManipulation';
4-
import {increaseNextBlockTime, setNextBlockNumber} from '../shared/TimeManipulation';
3+
import {impersonateAccountWithFunds, stopImpersonateAccount} from './AccountManipulation';
4+
import {increaseNextBlockTime, setNextBlockNumber} from './TimeManipulation';
55
import {POLYGON_AVERAGE_BLOCK_TIME} from './Constants';
66
import {DeployedContracts} from './Forking';
7+
import {getABIFromPolygonscan} from './Polygonscan';
78

89
export const createAndExecuteProposal = async ({
910
governor,
@@ -23,15 +24,18 @@ export const createAndExecuteProposal = async ({
2324
const timeLockSigner = await impersonateAccountWithFunds(timeLock.address);
2425
let originalVotingDelay = await governor.votingDelay();
2526
let originalVotingPeriod = await governor.votingPeriod();
27+
console.log('setting voting delay and duration to 2 blocks...');
2628
await governor.connect(timeLockSigner).setVotingDelay(`2`);
2729
await governor.connect(timeLockSigner).setVotingPeriod(`2`);
2830

2931
// 1. borrow some treasury tokens to user as we need signer with min. proposalThreshold tokens to propose
3032
const quorumAmount = await governor.quorumVotes();
3133
// careful, this sends ETH to timelock which might break real-world simulation for proposals involving Timelock ETH
34+
console.log('transferring tokens to user for proposal creation...');
3235
await arenaToken.connect(timeLockSigner).transfer(await user.getAddress(), quorumAmount);
3336
await arenaToken.connect(user).delegate(await user.getAddress());
3437
const descriptionHash = ethers.utils.keccak256([]); // keccak(``)
38+
console.log('creating proposal...');
3539
let tx = await governor.connect(user)['propose(address[],uint256[],bytes[],string)'](targets, values, calldatas, ``);
3640
let {events} = await tx.wait();
3741
// get first event (ProposalCreated), then get first arg of that event (proposalId)
@@ -42,6 +46,7 @@ export const createAndExecuteProposal = async ({
4246
// simulate elapsed time close to original voting delay
4347
await increaseNextBlockTime(Math.floor(POLYGON_AVERAGE_BLOCK_TIME * originalVotingDelay.toNumber()));
4448
await setNextBlockNumber(voteStartBlock.toNumber() + 1); // is a blocknumber which fits in Number
49+
console.log('casting vote...');
4550
tx = await governor.connect(user)['castVote'](proposalId, `1`);
4651

4752
// 3. return borrowed tokens
@@ -52,22 +57,48 @@ export const createAndExecuteProposal = async ({
5257
// simulate elapsed time close to original voting delay
5358
await increaseNextBlockTime(Math.floor(POLYGON_AVERAGE_BLOCK_TIME * originalVotingPeriod.toNumber()));
5459
await setNextBlockNumber(voteEndBlock.toNumber() + 1); // is a blocknumber which fits in Number
55-
tx = await governor
56-
.connect(user)
57-
['queue(address[],uint256[],bytes[],bytes32)'](targets, values, calldatas, descriptionHash);
60+
console.log('queueing proposal...');
61+
tx = await governor.connect(user)['queue(uint256)'](proposalId);
5862
await tx.wait();
5963

6064
// can revert Governor changes now
65+
console.log('resetting voting delay and period...');
6166
await governor.connect(timeLockSigner).setVotingDelay(originalVotingDelay);
6267
await governor.connect(timeLockSigner).setVotingPeriod(originalVotingPeriod);
6368
await stopImpersonateAccount(timeLock.address);
6469

6570
// 5. advance time past timelock delay and then execute
6671
const timeLockMinDelaySeconds = await timeLock.getMinDelay();
6772
await increaseNextBlockTime(timeLockMinDelaySeconds.toNumber());
68-
await governor
69-
.connect(user)
70-
['execute(address[],uint256[],bytes[],bytes32)'](targets, values, calldatas, descriptionHash);
73+
console.log('executing proposal...');
74+
tx = await governor.connect(user)['execute(uint256)'](proposalId);
75+
76+
let result = await tx.wait(1);
77+
78+
for (let i = 0; i < targets.length; i++) {
79+
let abi = await getABIFromPolygonscan(targets[i]);
80+
let iface = new ethers.utils.Interface(abi);
81+
let events = result.logs.map((log) => {
82+
try {
83+
return iface.parseLog(log);
84+
} catch (e) {
85+
// no matching event
86+
}
87+
});
88+
console.log(`### TARGET ${targets[i]} EVENTS ###`);
89+
console.log(events);
90+
console.log(`###################################`);
91+
}
92+
93+
let timelockEvents = result.logs.map((log) => {
94+
try {
95+
return timeLock.interface.parseLog(log);
96+
} catch (e) {
97+
// no matching event
98+
}
99+
});
100+
console.log(`### TIMELOCK EVENTS ###`);
101+
console.log(timelockEvents);
71102

72103
return proposalId;
73104
};

shared/Polygonscan.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import fetch from 'node-fetch';
2+
3+
export async function getABIFromPolygonscan(address: string) {
4+
if (process.env.POLYGONSCAN_API_KEY == undefined) {
5+
console.log('Require polygonscan key, exiting...');
6+
process.exit(1);
7+
}
8+
9+
let abiRequest = await fetch(
10+
`https://api.polygonscan.com/api?module=contract&action=getabi` +
11+
`&address=${address}` +
12+
`&apikey=${process.env.POLYGONSCAN_API_KEY}`
13+
);
14+
let abi = await abiRequest.json();
15+
if (abi.status == '0') {
16+
console.log(abi.result);
17+
process.exit(1);
18+
}
19+
return abi.result;
20+
}
File renamed without changes.

test/ArenaToken.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import * as fs from 'fs';
44
import {ethers, waffle} from 'hardhat';
55
import * as path from 'path';
66
import {ArenaToken, RevokableTokenLock} from '../typechain';
7-
import {impersonateAccountWithFunds, stopImpersonateAccount} from './shared/AccountManipulation';
8-
import {ONE_18, ONE_DAY, ONE_YEAR} from './shared/Constants';
9-
import {resetNetwork, setNextBlockTimeStamp} from './shared/TimeManipulation';
7+
import {impersonateAccountWithFunds, stopImpersonateAccount} from '../shared/AccountManipulation';
8+
import {ONE_18, ONE_DAY, ONE_YEAR} from '../shared/Constants';
9+
import {resetNetwork, setNextBlockTimeStamp} from '../shared/TimeManipulation';
1010
import {MerkleDistributorInfo} from '../src/parse-balance-map';
1111

1212
const {solidity, loadFixture} = waffle;

0 commit comments

Comments
 (0)