-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
177 additions
and
0 deletions.
There are no files selected for viewing
114 changes: 114 additions & 0 deletions
114
scripts/governance/get-createProposalWithSolution-txdata.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
require('dotenv').config(); | ||
const path = require('node:path'); | ||
|
||
const { ethers } = require('hardhat'); | ||
const ipfsClient = require('ipfs-http-client'); | ||
|
||
const { simulateTransaction, constants } = require('./helpers'); | ||
const fs = require('fs'); | ||
const { GOVERNANCE_ADDRESS, IPFS_API_URL, CATEGORY_PARAM_TYPES } = constants; | ||
|
||
const ipfs = ipfsClient({ url: IPFS_API_URL }); | ||
|
||
const verifyDecodedTxInputs = (inputs, decodedTxInputs) => { | ||
if (decodedTxInputs[0] !== inputs[0]) { | ||
throw new Error(`Title mismatch: ${decodedTxInputs[0]} !== ${inputs[0]}`); | ||
} | ||
|
||
if (decodedTxInputs[1] !== inputs[1]) { | ||
throw new Error(`Short description mismatch: ${decodedTxInputs[1]} !== ${inputs[1]}`); | ||
} | ||
|
||
if (decodedTxInputs[2] !== inputs[2]) { | ||
throw new Error(`Ipfs hash mismatch: ${decodedTxInputs[2]} !== ${inputs[2]}`); | ||
} | ||
|
||
if (decodedTxInputs[3] !== inputs[3]) { | ||
throw new Error(`Category mismatch: ${decodedTxInputs[3]} !== ${inputs[3]}`); | ||
} | ||
|
||
if (decodedTxInputs[4] !== inputs[4]) { | ||
throw new Error(`Solution Hash mismatch: ${decodedTxInputs[4]} !== ${inputs[4]}`); | ||
} | ||
|
||
if (decodedTxInputs[5] !== inputs[5]) { | ||
throw new Error(`Action mismatch: ${decodedTxInputs[5]} !== ${inputs[5]}`); | ||
} | ||
}; | ||
|
||
/** | ||
* | ||
* Generate the tx data for the Governance.createProposalWithSolution transaction using the provided proposal data | ||
* | ||
* @param proposalFilePath path for file of proposal data containing title, shortDescription, and description | ||
* @param categoryId category id for the proposal | ||
* @param actionParamsRaw action params for the proposal as stringified JSON | ||
* @param solutionHash hash of the solution for the proposal | ||
* @returns {Promise<{createProposalWithSolution: *}>} | ||
*/ | ||
const main = async (proposalFilePath, categoryId, actionParamsRaw, solutionHash = '') => { | ||
const governance = await ethers.getContractAt('Governance', GOVERNANCE_ADDRESS); | ||
const [proposal] = require(path.resolve(proposalFilePath)); | ||
const actionParams = JSON.parse(actionParamsRaw); | ||
|
||
// check for any missing required data before processing and uploading files to IPFS | ||
if (Object.keys(proposal).length > 3) { | ||
throw new Error('Proposal data should only contain title, shortDescription, and description'); | ||
} | ||
|
||
if (!proposal.title) { | ||
throw new Error('Proposal title is required'); | ||
} | ||
|
||
if (!proposal.shortDescription) { | ||
throw new Error('Proposal short description is required'); | ||
} | ||
|
||
if (!proposal.description) { | ||
throw new Error('Proposal description is required'); | ||
} | ||
|
||
if (!categoryId) { | ||
throw new Error('Category ID is required'); | ||
} | ||
|
||
if (!actionParams) { | ||
throw new Error('Action is required'); | ||
} | ||
|
||
if (CATEGORY_PARAM_TYPES[categoryId].length !== actionParams.length) { | ||
throw new Error( | ||
`Action Params length mismatch: ${CATEGORY_PARAM_TYPES[categoryId].length} !== ${actionParams.length}`, | ||
); | ||
} | ||
|
||
const encodedActionParams = ethers.utils.defaultAbiCoder.encode(CATEGORY_PARAM_TYPES[categoryId], actionParams); | ||
|
||
// upload proposal file to IPFS | ||
const file = await ipfs.add(fs.readFileSync(proposalFilePath)); | ||
await ipfs.pin.add(file.path); | ||
|
||
// group the inputs for the createProposalWithSolution transaction | ||
const inputs = [proposal.title, proposal.shortDescription, file.path, categoryId, solutionHash, encodedActionParams]; | ||
|
||
// create the transaction data for createProposalwithSolution | ||
const createProposalTransaction = await governance.populateTransaction.createProposalwithSolution(...inputs); | ||
console.log(`Tx data:\n${createProposalTransaction.data}`); | ||
|
||
// simulate the transaction | ||
const decodedTxInputs = await simulateTransaction(createProposalTransaction.data); | ||
|
||
// verify the decoded inputs match the inputs | ||
verifyDecodedTxInputs(inputs, decodedTxInputs); | ||
|
||
return createProposalTransaction; | ||
}; | ||
|
||
if (require.main === module) { | ||
main(process.argv[2], process.argv[3], process.argv[4], process.argv[5]).catch(e => { | ||
console.log('Unhandled error encountered: ', e.stack); | ||
process.exit(1); | ||
}); | ||
} | ||
|
||
module.exports = main; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
const axios = require('axios'); | ||
const { inspect } = require('node:util'); | ||
const nexusSdk = require('@nexusmutual/deployments'); | ||
|
||
const AB_MEMBER = '0x87B2a7559d85f4653f13E6546A14189cd5455d45'; | ||
const GOVERNANCE_ADDRESS = nexusSdk.addresses.Governance; | ||
|
||
const IPFS_API_URL = 'https://api.nexusmutual.io/ipfs-api/api/v0'; | ||
|
||
const CATEGORY_PARAM_TYPES = { | ||
29: ['bytes2[]', 'address[]'], | ||
43: ['bytes2[]', 'address[]', 'uint256[]'], | ||
}; | ||
|
||
/** | ||
* NOTE: requires TENDERLY_ACCESS_KEY env | ||
* @param {HexString} input - the tx.data | ||
*/ | ||
const simulateTransaction = async input => { | ||
const payload = { | ||
save: true, // save result to dashboard | ||
save_if_fails: true, // show reverted txs in dashboard | ||
simulation_type: 'full', | ||
network_id: '1', | ||
from: AB_MEMBER, | ||
to: GOVERNANCE_ADDRESS, | ||
gas: 8000000, | ||
gas_price: 0, | ||
value: 0, | ||
input, | ||
}; | ||
|
||
const response = await axios.post( | ||
`https://api.tenderly.co/api/v1/account/NexusMutual/project/nexusmutual/simulate`, | ||
payload, | ||
{ headers: { 'X-Access-Key': process.env.TENDERLY_ACCESS_KEY } }, | ||
); | ||
|
||
const { transaction, simulation } = response.data; | ||
const decodedTxInputs = transaction.transaction_info.call_trace.decoded_input.map(input => input.value); | ||
console.info('governance.createProposal input:\n', inspect(decodedTxInputs, { depth: null })); | ||
console.info( | ||
'\nTenderly Simulated transaction:\n', | ||
`https://dashboard.tenderly.co/NexusMutual/nexusmutual/simulator/${simulation.id}`, | ||
); | ||
|
||
return decodedTxInputs; | ||
}; | ||
|
||
module.exports = { | ||
simulateTransaction, | ||
constants: { | ||
GOVERNANCE_ADDRESS, | ||
AB_MEMBER, | ||
IPFS_API_URL, | ||
CATEGORY_PARAM_TYPES, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
[{ | ||
"title": "", | ||
"shortDescription": "", | ||
"description": "" | ||
}] |