-
Notifications
You must be signed in to change notification settings - Fork 72
/
index.ts
144 lines (128 loc) · 5.91 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
* @notice Entry point for executing a single proposal against a forked mainnet
*/
import dotenv from 'dotenv'
dotenv.config()
import { BigNumber, Contract } from 'ethers'
import { DAO_NAME, GOVERNOR_ADDRESS, SIM_NAME } from './utils/constants'
import { provider } from './utils/clients/ethers'
import { simulate } from './utils/clients/tenderly'
import { AllCheckResults, GovernorType, SimulationConfig, SimulationConfigBase, SimulationData } from './types'
import ALL_CHECKS from './checks'
import { generateAndSaveReports } from './presentation/report'
import { PROPOSAL_STATES } from './utils/contracts/governor-bravo'
import {
formatProposalId,
getGovernor,
getProposalIds,
getTimelock,
inferGovernorType,
} from './utils/contracts/governor'
import { getAddress } from '@ethersproject/address'
/**
* @notice Simulate governance proposals and run proposal checks against them
*/
async function main() {
// --- Run simulations ---
// Prepare array to store all simulation outputs
const simOutputs: SimulationData[] = []
let governor: Contract
let governorType: GovernorType
// Determine if we are running a specific simulation or all on-chain proposals for a specified governor.
if (SIM_NAME) {
// If a SIM_NAME is provided, we run that simulation
const configPath = `./sims/${SIM_NAME}.sim.ts`
const config: SimulationConfig = await import(configPath).then((d) => d.config) // dynamic path `import` statements not allowed
const { sim, proposal, latestBlock } = await simulate(config)
simOutputs.push({ sim, proposal, latestBlock, config })
governorType = await inferGovernorType(config.governorAddress)
governor = await getGovernor(governorType, config.governorAddress)
} else {
// If no SIM_NAME is provided, we get proposals to simulate from the chain
if (!GOVERNOR_ADDRESS) throw new Error('Must provider a GOVERNOR_ADDRESS')
if (!DAO_NAME) throw new Error('Must provider a DAO_NAME')
const latestBlock = await provider.getBlock('latest')
// Fetch all proposal IDs
governorType = await inferGovernorType(GOVERNOR_ADDRESS)
const proposalIds = await getProposalIds(governorType, GOVERNOR_ADDRESS, latestBlock.number)
governor = getGovernor(governorType, GOVERNOR_ADDRESS)
// If we aren't simulating all proposals, filter down to just the active ones. For now we
// assume we're simulating all by default
const states = await Promise.all(proposalIds.map((id) => governor.state(id)))
const simProposals: { id: BigNumber; simType: SimulationConfigBase['type'] }[] = proposalIds.map((id, i) => {
// If state is `Executed` (state 7), we use the executed sim type and effectively just
// simulate the real transaction. For all other states, we use the `proposed` type because
// state overrides are required to simulate the transaction
const state = String(states[i]) as keyof typeof PROPOSAL_STATES
const isExecuted = PROPOSAL_STATES[state] === 'Executed'
return { id, simType: isExecuted ? 'executed' : 'proposed' }
})
const simProposalsIds = simProposals.map((sim) => sim.id)
// Simulate them
// We intentionally do not run these in parallel to avoid hitting Tenderly API rate limits or flooding
// them with requests if we e.g. simulate all proposals for a governor (instead of just active ones)
const numProposals = simProposals.length
console.log(
`Simulating ${numProposals} ${DAO_NAME} proposals: IDs of ${simProposalsIds
.map((id) => formatProposalId(governorType, id))
.join(', ')}`
)
for (const simProposal of simProposals) {
if (simProposal.simType === 'new') throw new Error('Simulation type "new" is not supported in this branch')
// Determine if this proposal is already `executed` or currently in-progress (`proposed`)
console.log(` Simulating ${DAO_NAME} proposal ${simProposal.id}...`)
const config: SimulationConfig = {
type: simProposal.simType,
daoName: DAO_NAME,
governorAddress: getAddress(GOVERNOR_ADDRESS),
governorType,
proposalId: simProposal.id,
}
const { sim, proposal, latestBlock } = await simulate(config)
simOutputs.push({ sim, proposal, latestBlock, config })
console.log(` done`)
}
}
// --- Run proposal checks and save output ---
// Generate the proposal data and dependencies needed by checks
const proposalData = { governor, provider, timelock: await getTimelock(governorType, governor.address) }
console.log('Starting proposal checks and report generation...')
for (const simOutput of simOutputs) {
// Run checks
const { sim, proposal, latestBlock, config } = simOutput
console.log(` Running for proposal ID ${formatProposalId(governorType, proposal.id!)}...`)
const checkResults: AllCheckResults = Object.fromEntries(
await Promise.all(
Object.keys(ALL_CHECKS).map(async (checkId) => [
checkId,
{
name: ALL_CHECKS[checkId].name,
result: await ALL_CHECKS[checkId].checkProposal(proposal, sim, proposalData),
},
])
)
)
// Generate markdown report.
const [startBlock, endBlock] = await Promise.all([
proposal.startBlock.toNumber() <= latestBlock.number ? provider.getBlock(proposal.startBlock.toNumber()) : null,
proposal.endBlock.toNumber() <= latestBlock.number ? provider.getBlock(proposal.endBlock.toNumber()) : null,
])
// Save markdown report to a file.
// GitHub artifacts are flattened (folder structure is not preserved), so we include the DAO name in the filename.
const dir = `./reports/${config.daoName}/${config.governorAddress}`
await generateAndSaveReports(
governorType,
{ start: startBlock, end: endBlock, current: latestBlock },
proposal,
checkResults,
dir
)
}
console.log('Done!')
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})