diff --git a/README.md b/README.md index 4a50526f8..179b077ee 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,37 @@ Proceed by executing the following command: npx hardhat run scripts/executeMultiSigTx.ts --network sepolia ``` -After executing the commant, provide the proposal name to be executed and press enter: +After executing the command, provide the proposal name to be executed and press enter: ``` Name of tx file (from ./multisig// dir) to execute => ``` + +### Export Gnosis Safe tx JSON + +Script to export a VIP in the form of a gnosis safe tx JSON format. + +Before executing this script make sure that: + +- The network you want to execute the multisig tx exist in `hardhat.config.ts` configuration +- The gnosis safe wallet address exists in `getSafeAddress` function in `multisig/helpers/utils.ts`. +- Make sure that the name of the network in `getSafeAddress` matches the network name in the `hardhat.config.ts` and also `chainID` for this network is correct. + +Proceed by executing the following command: + +``` +npx hardhat run scripts/createProposal.ts --network +``` + +After executing the command, enter the needed information for the script. +Here is example input for exporting an example (`multisig/proposals/sepolia/example.ts`) VIP into a JSON Gnosis Safe format: + +``` +npx hardhat run scripts/createProposal.ts --network sepolia +Number of the VIP to propose (if using gnosisTXBuilder press enter to skip ) => +Type of the proposal txBuilder/venusApp/bsc/gnosisTXBuilder => gnosisTXBuilder +Address of the governance contract (optional, press enter to skip) => +Name of tx file (from ./multisig/network(available)/ dir) to execute => example +``` + +The script should output a file `gnosisTXBuilder.json` that you can import in your Gnosis Safe UI. diff --git a/multisig/helpers/utils.ts b/multisig/helpers/utils.ts index e18ab6f3e..6f0d201ad 100644 --- a/multisig/helpers/utils.ts +++ b/multisig/helpers/utils.ts @@ -1,3 +1,13 @@ +import Safe, { ContractNetworksConfig, EthersAdapter } from "@safe-global/protocol-kit"; +import { MetaTransactionData } from "@safe-global/safe-core-sdk-types"; +import { ethers, network } from "hardhat"; + +import { Proposal } from "../../src/types"; + +const readline = require("readline-sync"); + +const DEFAULT_OPERATION = 0; // Call + export const loadMultisigTx = async (txName: string, networkName: string) => { const x = await import(`../proposals/${networkName}/${txName}.ts`); return x[txName](); @@ -17,3 +27,56 @@ export const getSafeAddress = (networkName: string): string => { throw new Error(`Safe address for network ${networkName} is not defined.`); } }; + +export const buildMultiSigTx = async (proposal: Proposal): Promise => { + const { signatures, targets, params, values } = proposal; + const safeTransactionData: MetaTransactionData[] = []; + for (let i = 0; i < signatures.length; ++i) { + const abi = new ethers.utils.Interface([`function ${signatures[i]}`]); + const safeTxData: MetaTransactionData = { + to: targets[i], + data: abi.encodeFunctionData(signatures[i], params[i]), + value: values[i].toString(), + operation: DEFAULT_OPERATION, + }; + + safeTransactionData.push(safeTxData); + } + return safeTransactionData; +}; + +export const createGnosisTx = async (ethAdapter: EthersAdapter, safeSdk: Safe): Promise => { + const txName = readline.question("Name of tx file (from ./multisig/network(available)/ dir) to execute => "); + + const proposal = await loadMultisigTx(txName, network.name); + + const safeTransactionData = await buildMultiSigTx(proposal); + + return await safeSdk.createTransaction({ safeTransactionData }); +}; + +export const getContractNetworks = (chainId: number): ContractNetworksConfig => { + // Define contract addresses for different networks here + const networks: Record = { + // Sepolia network + sepolia: { + [chainId]: { + safeMasterCopyAddress: "0x42f9B1A23193465A4049DA3af93f9faBF3054951", + safeProxyFactoryAddress: "0x4cEeffCE2e51cFaD71bF23C816756b9D789395cC", + multiSendAddress: "0xE4BDFeD788718f1FA72C249e100B21eAE5a549e4", + multiSendCallOnlyAddress: "0x028664f9c577698Ae250cAA51ADC22377B03ec4A", + fallbackHandlerAddress: "0x1259Aa9FaCd0feFB5a91da65682C7EDD51608D4b", + signMessageLibAddress: "0xaF838B48F16728169E78985Cc8eB1bda25D75B29", + createCallAddress: "0x6B95D96C78F6433992A5F81aEcF82bAE449016Df", + simulateTxAccessorAddress: "0x249b0178432e34320D7d30A4A9699cAf23Bcf04c", + }, + }, + // Add more networks as needed + }; + + if (network.name in networks) { + return networks[network.name]; + } else { + throw new Error(`Network ${network.name} is not supported.`); + } +}; diff --git a/scripts/createProposal.ts b/scripts/createProposal.ts index 82f468966..ce1f45a31 100644 --- a/scripts/createProposal.ts +++ b/scripts/createProposal.ts @@ -2,7 +2,9 @@ import { TxBuilder } from "@morpho-labs/gnosis-tx-builder"; import Ajv from "ajv"; import { BigNumber } from "ethers"; import fs from "fs/promises"; +import { network } from "hardhat"; +import { buildMultiSigTx, getSafeAddress, loadMultisigTx } from "../multisig/helpers/utils"; import { loadProposal, proposeVIP } from "../src/transactions"; import { getCalldatas, proposalSchema } from "../src/utils"; @@ -16,8 +18,8 @@ let transactionType: string; function processInputs(): Promise { return new Promise(resolve => { - vipNumber = readline.question("Number of the VIP to propose => "); - transactionType = readline.question("Type of the proposal txBuilder/venusApp/bsc => "); + vipNumber = readline.question("Number of the VIP to propose (if using gnosisTXBuilder press enter to skip ) => "); + transactionType = readline.question("Type of the proposal txBuilder/venusApp/bsc/gnosisTXBuilder => "); governorAddress = readline.question("Address of the governance contract (optional, press enter to skip) => "); if (!governorAddress) { governorAddress = null; @@ -56,6 +58,18 @@ const processTxBuilder = async () => { return processJson(batchJson); }; +const processGnosisTxBuilder = async () => { + const safeAddress = getSafeAddress(network.name); + + const txName = readline.question("Name of tx file (from ./multisig/network(available)/ dir) to execute => "); + + const proposal = await loadMultisigTx(txName, network.name); + const multisigTx = await buildMultiSigTx(proposal); + const batchJson = TxBuilder.batch(safeAddress, multisigTx, { chainId: network.config.chainId }); + + return processJson(batchJson); +}; + const processVenusAppProposal = async () => { const proposal = await loadProposal(vipNumber); const validate = new Ajv().compile(proposalSchema); @@ -89,6 +103,8 @@ const createProposal = async () => { result = await processTxBuilder(); } else if (transactionType === "venusApp") { result = await processVenusAppProposal(); + } else if (transactionType === "gnosisTXBuilder") { + result = await processGnosisTxBuilder(); } else { result = await processBscProposal(); } diff --git a/scripts/executeMultiSigTx.ts b/scripts/executeMultiSigTx.ts index d765400be..fb0ee6b58 100644 --- a/scripts/executeMultiSigTx.ts +++ b/scripts/executeMultiSigTx.ts @@ -1,18 +1,10 @@ import Safe, { ContractNetworksConfig, EthersAdapter } from "@safe-global/protocol-kit"; -import { MetaTransactionData } from "@safe-global/safe-core-sdk-types"; import { ethers, network } from "hardhat"; -import { getSafeAddress, loadMultisigTx } from "../multisig/helpers/utils"; -import { Proposal } from "../src/types"; - -const readline = require("readline-sync"); - -const DEFAULT_OPERATION = 0; // Call -let txName: string; +import { createGnosisTx, getContractNetworks, getSafeAddress } from "../multisig/helpers/utils"; const executeMultiSigTx = async () => { const safeOwner = ethers.provider.getSigner(0); - txName = readline.question("Name of tx file (from ./multisig/network(available)/ dir) to execute => "); const ethAdapter = new EthersAdapter({ ethers, @@ -24,11 +16,7 @@ const executeMultiSigTx = async () => { const safeSdk = await Safe.create({ ethAdapter, safeAddress, contractNetworks }); - const proposal = await loadMultisigTx(txName, network.name); - - const safeTransactionData = await buildMultiSigTx(proposal); - - const safeTransaction = await safeSdk.createTransaction({ safeTransactionData }); + const safeTransaction = await createGnosisTx(ethAdapter, safeSdk); const safeTxHash = await safeSdk.getTransactionHash(safeTransaction); @@ -39,47 +27,4 @@ const executeMultiSigTx = async () => { console.log(`Multisig transaction (txId: ${receipt?.transactionHash}) has been successfully submitted.`); }; -const buildMultiSigTx = async (proposal: Proposal): Promise => { - const { signatures, targets, params, values } = proposal; - const safeTransactionData: MetaTransactionData[] = []; - for (let i = 0; i < signatures.length; ++i) { - const abi = new ethers.utils.Interface([`function ${signatures[i]}`]); - const safeTxData: MetaTransactionData = { - to: targets[i], - data: abi.encodeFunctionData(signatures[i], params[i]), - value: values[i].toString(), - operation: DEFAULT_OPERATION, - }; - - safeTransactionData.push(safeTxData); - } - return safeTransactionData; -}; - -const getContractNetworks = (chainId: number): ContractNetworksConfig => { - // Define contract addresses for different networks here - const networks: Record = { - // Sepolia network - sepolia: { - [chainId]: { - safeMasterCopyAddress: "0x42f9B1A23193465A4049DA3af93f9faBF3054951", - safeProxyFactoryAddress: "0x4cEeffCE2e51cFaD71bF23C816756b9D789395cC", - multiSendAddress: "0xE4BDFeD788718f1FA72C249e100B21eAE5a549e4", - multiSendCallOnlyAddress: "0x028664f9c577698Ae250cAA51ADC22377B03ec4A", - fallbackHandlerAddress: "0x1259Aa9FaCd0feFB5a91da65682C7EDD51608D4b", - signMessageLibAddress: "0xaF838B48F16728169E78985Cc8eB1bda25D75B29", - createCallAddress: "0x6B95D96C78F6433992A5F81aEcF82bAE449016Df", - simulateTxAccessorAddress: "0x249b0178432e34320D7d30A4A9699cAf23Bcf04c", - }, - }, - // Add more networks as needed - }; - - if (network.name in networks) { - return networks[network.name]; - } else { - throw new Error(`Network ${network.name} is not supported.`); - } -}; - executeMultiSigTx();