Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contracts/modules/governance/ProposalCategory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ contract ProposalCategory is IProposalCategory, Governed, LegacyMasterAware {
* @param _contractAddress address of contract to call after proposal is accepted
* @param _contractName name of contract to be called after proposal is accepted
* @param _incentives rewards to distributed after proposal is accepted
* [Minimum stake, Incentives, Advisory Board required, Is Special Resolution]
* @param _functionHash function signature to be executed
*/
function editCategory(
Expand Down
5 changes: 3 additions & 2 deletions scripts/create2/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { keccak256 } = require('ethereum-cryptography/keccak');
const { bytesToHex, hexToBytes } = require('ethereum-cryptography/utils');
const linker = require('solc/linker');

const { getSigner, SIGNER_TYPE } = require('./get-signer');
const { SIGNER_TYPE, getSigner } = require('./get-signer');

const ADDRESS_REGEX = /^0x[a-f0-9]{40}$/i;

Expand Down Expand Up @@ -186,6 +186,8 @@ async function main() {
process.exit(1);
});

const signer = await getSigner(opts.kms ? SIGNER_TYPE.AWS_KMS : SIGNER_TYPE.LOCAL);

// make sure the contracts are compiled and we're not deploying an outdated artifact
await run('compile');

Expand Down Expand Up @@ -213,7 +215,6 @@ async function main() {
const maxPriorityFeePerGas = ethers.utils.parseUnits(opts.priorityFee, 'gwei');
const maxFeePerGas = baseFee.add(maxPriorityFeePerGas);

const signer = await getSigner(opts.kms ? SIGNER_TYPE.AWS_KMS : SIGNER_TYPE.LOCAL);
const deployer = await ethers.getContractAt('Deployer', opts.factory, signer);
const deployTx = await deployer.deployAt(bytecode, opts.salt, opts.address, {
maxFeePerGas,
Expand Down
12 changes: 8 additions & 4 deletions scripts/create2/get-signer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ const SIGNER_TYPE = {
AWS_KMS: 'aws-kms',
};

const CONTRACT_DEPLOYER = '0x46842a7d9372bb7dba08f0729393deda230a03b5';

const getSigner = async (kind = SIGNER_TYPE.LOCAL) => {
const provider = ethers.provider;
const { AWS_KMS_KEY_ID, AWS_REGION } = process.env;

// Use contract deployer address to closely mimic main-net deployment
if (kind === SIGNER_TYPE.LOCAL) {
const [signer] = await ethers.getSigners();
await provider.send('hardhat_impersonateAccount', [CONTRACT_DEPLOYER]);
const signer = await provider.getSigner(CONTRACT_DEPLOYER);
return signer;
}

const provider = ethers.provider;
const { AWS_KMS_KEY_ID, AWS_REGION } = process.env;

if (kind === SIGNER_TYPE.AWS_KMS) {
return new AwsKmsSigner(AWS_KMS_KEY_ID, AWS_REGION, provider);
}
Expand Down
51 changes: 51 additions & 0 deletions scripts/governance/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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';

/**
* @dev Extend with other Governance Proposal Categories as necessary
*/
const PROPOSAL_CATEGORY = {
4: {
actionParamTypes: [
'uint',
'string',
'uint',
'uint',
'uint',
'uint[]',
'uint',
'string',
'address',
'bytes2',
'uint[]',
'string',
],
description: 'Edit Category',
},
29: {
actionParamTypes: ['bytes2[]', 'address[]'],
description: 'Release new smart contract code',
},
40: {
actionParamTypes: ['bytes8', 'address'],
description: 'Update Pool address Parameters',
},
42: {
actionParamTypes: ['address', 'bool', 'uint', 'uint', 'uint'],
description: 'Add Asset To Pool',
},
43: {
actionParamTypes: ['bytes2[]', 'address[]', 'uint256[]'],
description: 'Add new internal contracts',
},
};

module.exports = {
AB_MEMBER,
GOVERNANCE_ADDRESS,
IPFS_API_URL,
PROPOSAL_CATEGORY,
};
46 changes: 46 additions & 0 deletions scripts/governance/execute-createProposalWithSolution-txdata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require('dotenv').config();
const { ethers, network } = require('hardhat');
const { sendTransaction, prepareProposalTransaction } = require('./helpers');

/**
* Generates and executes the tx data for the Governance.createProposalWithSolution transaction
*
* node execute-createProposalWithSolution-txdata.js <PATH_PROPOSAL_JSON> <CATEGORY_ID> <ENCODED_ACTION_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
*/
const main = async (proposalFilePath, categoryId, actionParamsRaw, solutionHash = '') => {
if (network.name === 'tenderly') {
const { TENDERLY_SNAPSHOT_ID } = process.env;
if (TENDERLY_SNAPSHOT_ID) {
await ethers.provider.send('evm_revert', [TENDERLY_SNAPSHOT_ID]);
console.info(`Reverted to snapshot ${TENDERLY_SNAPSHOT_ID}`);
} else {
console.info('Snapshot ID: ', await ethers.provider.send('evm_snapshot', []));
}
}

const { transaction } = await prepareProposalTransaction(proposalFilePath, categoryId, actionParamsRaw, solutionHash);

console.log(`Tx data:\n${transaction.data}`);

// Execute the transaction
const receipt = await sendTransaction(transaction.data);
console.log('Transaction receipt:', receipt);

return transaction;
};

if (require.main === module) {
main(process.argv[2], process.argv[3], process.argv[4], process.argv[5])
.then(() => process.exit(0))
.catch(e => {
console.log('Unhandled error encountered: ', e.stack);
process.exit(1);
});
}

module.exports = main;
114 changes: 0 additions & 114 deletions scripts/governance/get-createProposalWithSolution-txdata.js

This file was deleted.

62 changes: 45 additions & 17 deletions scripts/governance/get-decoded-action-data.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
const util = require('node:util');
const { ethers } = require('hardhat');

const { PROPOSAL_CATEGORY } = require('./constants');
const { defaultAbiCoder, toUtf8String } = ethers.utils;

const HEX_REGEX = /^0x[a-f0-9]+$/i;
const CATEGORIES_HANDLERS = {
29: decodeReleaseNewContractCode,
};
// Prefixed and non-prefixed hex are both valid
const HEX_REGEX = /^(?:0x)?[a-f0-9]+$/i;

/**
* Decodes the given encoded hex action data according to the categoryId
*
* Execute command:
* node scripts/governance/get-decoded-action-data -i <CATEGORY_ID> -d '<HEX_ENCODED_ACTION_DATA>'
*/
const usage = () => {
console.log(`
Usage:
Expand Down Expand Up @@ -42,10 +47,10 @@ const parseArgs = async args => {
}

if (['--category-id', '-i'].includes(arg)) {
opts.category = argsArray.shift();
if (!CATEGORIES_HANDLERS[opts.category]) {
const supportedCategories = Object.keys(CATEGORIES_HANDLERS).join(', ');
throw new Error(`Category ${opts.category} not yet supported. Supported categories: ${supportedCategories}`);
opts.categoryId = argsArray.shift();
if (!PROPOSAL_CATEGORY[opts.categoryId]) {
const supportedCategories = Object.keys(PROPOSAL_CATEGORY).join(', ');
throw new Error(`Category ${opts.categoryId} not yet supported. Supported categories: ${supportedCategories}`);
}
continue;
}
Expand All @@ -55,11 +60,12 @@ const parseArgs = async args => {
if (!hexData.match(HEX_REGEX)) {
throw new Error('Invalid hex data');
}
opts.data = hexData;
// Add '0x' prefix if its missing
opts.data = opts.data.startsWith('0x') ? hexData : '0x' + hexData;
}
}

if (!opts.category) {
if (!opts.categoryId) {
throw new Error('Missing required argument: --category-id');
}

Expand All @@ -76,16 +82,38 @@ async function main() {
process.exit(1);
});

CATEGORIES_HANDLERS[opts.category](opts);
decodeParamData(opts);
}

/* Category Handlers */
/**
* Function to decode action parameters from a governance proposal
* @param {Object} options - options object containing categoryId and data
* @returns an array of processed values, converting bytes to UTF8 where applicable
*/
function decodeParamData(options) {
const actionParamTypes = PROPOSAL_CATEGORY[options.categoryId].actionParamTypes;

const decodedValues = defaultAbiCoder.decode(actionParamTypes, options.data);

// NOTE: we're assuming here that bytes needs to be converted to UTF8
const processedValues = decodedValues.map((value, index) => {
const paramType = actionParamTypes[index];

// Handle bytes[] array
if (paramType.startsWith('bytes') && paramType.endsWith('[]')) {
return value.map(bytes => toUtf8String(bytes));
}
// Handle single bytes
if (paramType.startsWith('bytes')) {
return toUtf8String(value);
}

return value;
});

function decodeReleaseNewContractCode(options) {
const [codes, addresses] = defaultAbiCoder.decode(['bytes2[]', 'address[]'], options.data);
const contractCodesUtf8 = codes.map(code => toUtf8String(code));
console.log(`Decoded ${options.categoryId}:\n${util.inspect(processedValues, { depth: 2 })}`);

console.log(`Decoded Release New Contract Code (29):\n${util.inspect([contractCodesUtf8, addresses], { depth: 2 })}`);
return processedValues;
}

if (require.main === module) {
Expand All @@ -100,5 +128,5 @@ if (require.main === module) {
}

module.exports = {
decodeReleaseNewContractCode,
decodeParamData,
};
Loading