Skip to content

Commit

Permalink
Add script for proposal simulation
Browse files Browse the repository at this point in the history
  • Loading branch information
MilGard91 committed Jul 25, 2024
1 parent c6e901c commit adb1d1a
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 0 deletions.
114 changes: 114 additions & 0 deletions scripts/governance/get-createProposalWithSolution-txdata.js
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;
58 changes: 58 additions & 0 deletions scripts/governance/helpers.js
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,
},
};
5 changes: 5 additions & 0 deletions scripts/governance/proposal-sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[{
"title": "",
"shortDescription": "",
"description": ""
}]

0 comments on commit adb1d1a

Please sign in to comment.