diff --git a/evm/deploy-contract.js b/evm/deploy-contract.js index 15b643812..5ae42beda 100644 --- a/evm/deploy-contract.js +++ b/evm/deploy-contract.js @@ -99,13 +99,13 @@ async function getConstructorArgs(contractName, chain, wallet, options) { throw new Error(`Missing InterchainGovernance.governanceChain in the chain info.`); } - const governanceAddress = contractConfig.governanceAddress || 'axelar10d07y265gmmuvt4z0w9aw880jnsr700j7v9daj'; + const governanceAddress = contractConfig.governanceAddress || wallet.address || 'axelar10d07y265gmmuvt4z0w9aw880jnsr700j7v9daj'; if (!isString(governanceAddress)) { throw new Error(`Missing InterchainGovernance.governanceAddress in the chain info.`); } - const minimumTimeDelay = contractConfig.minimumTimeDelay || parseInt(options.args, 10); + const minimumTimeDelay = contractConfig.minimumTimeDelay; if (!isNumber(minimumTimeDelay)) { throw new Error(`Missing InterchainGovernance.minimumTimeDelay in the chain info.`); diff --git a/evm/governance.js b/evm/governance.js index d81c33c6a..1699709b5 100644 --- a/evm/governance.js +++ b/evm/governance.js @@ -9,14 +9,13 @@ const { Contract, BigNumber, } = ethers; -const readlineSync = require('readline-sync'); const { Command, Option } = require('commander'); const { printInfo, printWalletInfo, isValidTimeFormat, - etaToUnixTimestamp, - unixTimestampToEta, + dateToEta, + etaToDate, getCurrentTimeInSeconds, wasEventEmitted, printWarn, @@ -25,13 +24,14 @@ const { isValidAddress, mainProcessor, isValidDecimal, + prompt, } = require('./utils'); -const { storeSignedTx, getWallet, signTransaction } = require('./sign-utils.js'); +const { getWallet } = require('./sign-utils.js'); const IGovernance = require('@axelar-network/axelar-gmp-sdk-solidity/interfaces/IAxelarServiceGovernance.json'); const IGateway = require('@axelar-network/axelar-gmp-sdk-solidity/interfaces/IAxelarGateway.json'); async function processCommand(_, chain, options) { - const { contractName, address, newGovernance, newMintLimiter, action, calldata, nativeValue, eta, privateKey, yes } = options; + const { contractName, address, action, calldata, nativeValue, date, privateKey, yes } = options; const contracts = chain.contracts; const contractConfig = contracts[contractName]; @@ -58,17 +58,13 @@ async function processCommand(_, chain, options) { throw new Error(`Invalid native value: ${nativeValue}`); } - if (!isValidTimeFormat(eta)) { - throw new Error(`Invalid ETA: ${eta}. Please pass the eta in the format YYYY-MM-DDTHH:mm:ss`); - } - const rpc = chain.rpc; const provider = getDefaultProvider(rpc); printInfo('Chain', chain.name); const wallet = await getWallet(privateKey, provider, options); - const { address: walletAddress } = await printWalletInfo(wallet, options); + await printWalletInfo(wallet, options); printInfo('Contract name', contractName); printInfo('Contract address', governanceAddress); @@ -80,11 +76,6 @@ async function processCommand(_, chain, options) { printInfo('Proposal Action', action); - const unixEta = etaToUnixTimestamp(eta); - - const types = ['uint256', 'address', 'bytes', 'uint256', 'uint256']; - const values = [0, target, calldata, nativeValue, unixEta]; - let gmpPayload; switch (action) { @@ -93,15 +84,33 @@ async function processCommand(_, chain, options) { throw new Error(`Calldata required for this governance action: ${action}`); } - if (unixEta < getCurrentTimeInSeconds() + contractConfig?.minimumTimeDelay && !yes) { - printWarn(`${eta} is less than the minimum eta.`); - const answer = readlineSync.question(`Proceed with ${action}?`); - if (answer !== 'y') return; + if (!isValidTimeFormat(date)) { + throw new Error(`Invalid ETA: ${date}. Please pass the eta in the format YYYY-MM-DDTHH:mm:ss`); } - gmpPayload = defaultAbiCoder.encode(types, values); + const eta = dateToEta(date); + + const currTime = getCurrentTimeInSeconds(); + printInfo('Current time', etaToDate(currTime)); + + const minEta = currTime + contractConfig?.minimumTimeDelay; + printInfo('Minimum eta', etaToDate(minEta)); + + if (eta < minEta) { + printWarn(`${date} is less than the minimum eta.`); + } + + const existingProposalEta = await governance.getProposalEta(target, calldata, nativeValue); + + if (!existingProposalEta.eq(BigNumber.from(0))) { + throw new Error(`Proposal already exists with eta: ${existingProposalEta}.`); + } + + const commandType = 0; + const types = ['uint256', 'address', 'bytes', 'uint256', 'uint256']; + const values = [commandType, target, calldata, nativeValue, eta]; - printInfo(`Destination chain: ${chain.name}\nDestination governance address: ${governanceAddress}\nGMP payload: ${gmpPayload}`); + gmpPayload = defaultAbiCoder.encode(types, values); break; } @@ -113,22 +122,24 @@ async function processCommand(_, chain, options) { throw new Error(`Calldata required for this governance action: ${action}`); } - if (unixEta < getCurrentTimeInSeconds() && !yes) { - printWarn(`${eta} has already passed.`); - const answer = readlineSync.question(`Proceed with ${action}?`); - if (answer !== 'y') return; - } + const currTime = getCurrentTimeInSeconds(); + printInfo('Current time', etaToDate(currTime)); const proposalEta = await governance.getProposalEta(target, calldata, nativeValue); + printInfo('Proposal eta', etaToDate(proposalEta)); if (proposalEta.eq(BigNumber.from(0))) { throw new Error(`Proposal does not exist.`); } - values[0] = commandType; - gmpPayload = defaultAbiCoder.encode(types, values); + if (proposalEta <= currTime) { + printWarn(`Proposal eta has already passed.`); + } - printInfo(`Destination chain: ${chain.name}\nDestination governance address: ${governanceAddress}\nGMP payload: ${gmpPayload}`); + const types = ['uint256', 'address', 'bytes', 'uint256', 'uint256']; + const values = [commandType, target, calldata, nativeValue, proposalEta]; + + gmpPayload = defaultAbiCoder.encode(types, values); break; } @@ -144,10 +155,10 @@ async function processCommand(_, chain, options) { const commandType = 2; - values[0] = commandType; - gmpPayload = defaultAbiCoder.encode(types, values); + const types = ['uint256', 'address', 'bytes', 'uint256', 'uint256']; + const values = [commandType, target, calldata, nativeValue, 0]; - printInfo(`Destination chain: ${chain.name}\nDestination governance address: ${governanceAddress}\nGMP payload: ${gmpPayload}`); + gmpPayload = defaultAbiCoder.encode(types, values); break; } @@ -163,10 +174,10 @@ async function processCommand(_, chain, options) { const commandType = 3; - values[0] = commandType; - gmpPayload = defaultAbiCoder.encode(types, values); + const types = ['uint256', 'address', 'bytes', 'uint256', 'uint256']; + const values = [commandType, target, calldata, nativeValue, 0]; - printInfo(`Destination chain: ${chain.name}\nDestination governance address: ${governanceAddress}\nGMP payload: ${gmpPayload}`); + gmpPayload = defaultAbiCoder.encode(types, values); break; } @@ -175,7 +186,7 @@ async function processCommand(_, chain, options) { const proposalHash = keccak256(defaultAbiCoder.encode(['address', 'bytes', 'uint256'], [target, calldata, nativeValue])); const eta = await governance.getTimeLock(proposalHash); - if (eta === 0) { + if (eta.eq(0)) { throw new Error('Proposal does not exist.'); } @@ -183,21 +194,24 @@ async function processCommand(_, chain, options) { throw new Error(`Calldata required for this governance action: ${action}`); } - printInfo('Proposal ETA', unixTimestampToEta(eta)); + printInfo('Proposal ETA', etaToDate(eta)); - if (getCurrentTimeInSeconds() < eta) { + const currTime = getCurrentTimeInSeconds(); + printInfo('Current time', etaToDate(currTime)); + + if (currTime < eta) { throw new Error(`TimeLock proposal is not yet eligible for execution.`); } - let receipt; - - try { - const tx = await governance.executeProposal(target, calldata, nativeValue, gasOptions); - receipt = tx.wait(); - } catch (error) { - printError(error); + if (prompt('Proceed with executing this proposal?', yes)) { + throw new Error('Proposal execution cancelled.'); } + const tx = await governance.executeProposal(target, calldata, nativeValue, { gasLimit: 1e6 }); + printInfo('Proposal execution tx', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + const eventEmitted = wasEventEmitted(receipt, governance, 'ProposalExecuted'); if (!eventEmitted) { @@ -269,37 +283,63 @@ async function processCommand(_, chain, options) { throw new Error(`Invalid governance action for AxelarServiceGovernance: ${action}`); } - if (unixEta < getCurrentTimeInSeconds() + contractConfig?.minimumTimeDelay && !yes) { - printWarn(`${eta} is less than the minimum eta.`); - const answer = readlineSync.question(`Proceed with ${action}?`); - if (answer !== 'y') return; + if (!isValidTimeFormat(date)) { + throw new Error(`Invalid ETA: ${date}. Please pass the eta in the format YYYY-MM-DDTHH:mm:ss`); + } + + const eta = dateToEta(date); + + const currTime = getCurrentTimeInSeconds(); + printInfo('Current time', etaToDate(currTime)); + + const minEta = currTime + contractConfig?.minimumTimeDelay; + printInfo('Minimum eta', etaToDate(minEta)); + + if (eta < minEta) { + printWarn(`${date} is less than the minimum eta.`); } const implementation = options.implementation || chain.contracts.AxelarGateway?.implementation; + if (!isValidAddress(implementation)) { throw new Error(`Invalid new gateway implementation address: ${implementation}`); } const gateway = new Contract(target, IGateway.abi, wallet); - const implementationCode = await provider.getCode(implementation); + printInfo('Current gateway implementation', await gateway.implementation()); + printInfo('New gateway implementation', implementation); + + const newGatewayImplementationCodeHash = await getBytecodeHash(implementation, chain.name, provider); + printInfo('New gateway implementation code hash', newGatewayImplementationCodeHash); - if (implementationCode === '0x') { - printWarn(`There is no code deployed at ${implementation}`); - const answer = readlineSync.question(`Proceed with ${action}?`); - if (answer !== 'y') return; + const currGovernance = await gateway.governance(); + const currMintLimiter = await gateway.mintLimiter(); + + if (currGovernance !== governance.address) { + printWarn(`Gateway governor ${currGovernance} does not match governance contract: ${governance.address}`); } - const newGatewayImplementationCodeHash = await getBytecodeHash(implementation, chain.name, provider); + let newGovernance = options.newGovernance; + + if (contracts.AxelarGateway?.governance === currGovernance) { + newGovernance = contracts.AxelarGateway?.governance; + } else { + newGovernance = '0x'; + } - const governance = newGovernance || contracts.AxelarGateway?.governance || undefined; - const mintLimiter = newMintLimiter || contracts.AxelarGateway?.mintLimiter || undefined; - let setupParams; + let newMintLimiter = options.newMintLimiter; - if (governance && mintLimiter) { - setupParams = defaultAbiCoder.encode(['address', 'address', 'bytes'], [governance, mintLimiter, '0x']); + if (contracts.AxelarGateway?.mintLimiter === currMintLimiter) { + newMintLimiter = contracts.AxelarGateway?.mintLimiter; } else { - setupParams = '0x'; + newMintLimiter = '0x'; + } + + let setupParams = '0x'; + + if (newGovernance !== '0x' || newMintLimiter !== '0x') { + setupParams = defaultAbiCoder.encode(['address', 'address', 'bytes'], [newGovernance, newMintLimiter, '0x']); } printInfo('Setup Params for upgrading AxelarGateway', setupParams); @@ -310,17 +350,17 @@ async function processCommand(_, chain, options) { setupParams, ]); - values[2] = upgradeCalldata; + const commandType = 0; + const types = ['uint256', 'address', 'bytes', 'uint256', 'uint256']; + const values = [commandType, target, upgradeCalldata, nativeValue, eta]; gmpPayload = defaultAbiCoder.encode(types, values); const proposalEta = await governance.getProposalEta(target, upgradeCalldata, nativeValue); - if (BigNumber.from(proposalEta).gt(0)) { - printWarn("The eta for this proposal already exixts and it's value is", proposalEta); + if (!BigNumber.from(proposalEta).eq(0)) { + printWarn('The proposal already exixts', etaToDate(proposalEta)); } - printInfo(`Destination chain: ${chain.name}\nDestination governance address: ${governanceAddress}\nGMP payload: ${gmpPayload}`); - break; } @@ -332,11 +372,11 @@ async function processCommand(_, chain, options) { const proposalHash = keccak256(defaultAbiCoder.encode(['address', 'bytes', 'uint256'], [target, calldata, nativeValue])); const eta = await governance.getTimeLock(proposalHash); - if (eta === 0) { - throw new Error('Proposal does not exist.'); + if (eta.eq(0)) { + printWarn('Proposal does not exist.'); } - printInfo('Proposal ETA', unixTimestampToEta(eta)); + printInfo('Proposal ETA', etaToDate(eta)); break; } @@ -345,6 +385,15 @@ async function processCommand(_, chain, options) { throw new Error(`Unknown governance action ${action}`); } } + + if (gmpPayload) { + printInfo('Destination chain', chain.name); + printInfo('Destination governance address', governanceAddress); + printInfo('GMP payload', gmpPayload); + printInfo('Target contract', target); + printInfo('Target calldata', upgradeCalldata); + printInfo('Native value', nativeValue); + } } async function main(options) { @@ -388,7 +437,7 @@ program.addOption(new Option('--newMintLimiter ', 'mint limiter add program.addOption(new Option('--target ', 'governance execution target')); program.addOption(new Option('--calldata ', 'calldata')); program.addOption(new Option('--nativeValue ', 'nativeValue').default(0)); -program.addOption(new Option('--eta ', 'eta')); +program.addOption(new Option('--date ', 'proposal activation date')); program.addOption(new Option('--implementation ', 'new gateway implementation')); program.action((options) => { diff --git a/evm/utils.js b/evm/utils.js index a0a932d68..5ef94fd3f 100644 --- a/evm/utils.js +++ b/evm/utils.js @@ -11,6 +11,7 @@ const https = require('https'); const http = require('http'); const { outputJsonSync } = require('fs-extra'); const zkevm = require('@0xpolygonhermez/zkevm-commonjs'); +const readlineSync = require('readline-sync'); const chalk = require('chalk'); const { create3DeployContract, @@ -613,7 +614,7 @@ function isValidPrivateKey(privateKey) { return true; } -const etaToUnixTimestamp = (utcTimeString) => { +const dateToEta = (utcTimeString) => { if (utcTimeString === '0') { return 0; } @@ -627,7 +628,7 @@ const etaToUnixTimestamp = (utcTimeString) => { return Math.floor(date.getTime() / 1000); }; -const unixTimestampToEta = (timestamp) => { +const etaToDate = (timestamp) => { const date = new Date(timestamp * 1000); if (isNaN(date.getTime())) { @@ -706,6 +707,18 @@ const mainProcessor = async (options, processCommand, save = true, catchErr = fa } }; +const prompt = (question, yes = false) => { + // skip the prompt if yes was passed + if (yes) { + return false; + } + + const answer = readlineSync.question(`${question} ${chalk.green('(y/n)')} `); + console.log(); + + return answer !== 'y'; +}; + module.exports = { deployCreate, deployCreate2, @@ -738,13 +751,14 @@ module.exports = { saveConfig, printWalletInfo, isValidTimeFormat, - etaToUnixTimestamp, - unixTimestampToEta, + dateToEta, + etaToDate, getCurrentTimeInSeconds, wasEventEmitted, isContract, isValidAddress, isValidPrivateKey, verifyContract, + prompt, mainProcessor, };