Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ITS upgrade script #75

Closed
wants to merge 5 commits into from
Closed
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
205 changes: 203 additions & 2 deletions evm/governance.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require('dotenv').config();
const { ethers } = require('hardhat');
const {
getDefaultProvider,
utils: { defaultAbiCoder, keccak256, Interface, parseEther },
utils: { defaultAbiCoder, keccak256, Interface, parseEther, arrayify },
Contract,
BigNumber,
constants: { AddressZero },
Expand All @@ -31,6 +31,7 @@ const {
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');
const IUpgradable = require('@axelar-network/axelar-gmp-sdk-solidity/interfaces/IUpgradable.json');

let proposals = [];

Expand Down Expand Up @@ -63,6 +64,31 @@ async function getGatewaySetupParams(governance, gateway, contracts, options) {
return setupParams;
}

async function getSetupParams(options) {
const toUpgrade = options.contractToUpgrade;
let setupParams;

switch (toUpgrade) {
case 'InterchainTokenService': {
const newOperator = options.newOperator;

if (newOperator) {
if (!isValidAddress(newOperator)) {
throw new Error(`Invalid new operator address: ${newOperator}`);
}

setupParams = arrayify(newOperator);
} else {
setupParams = '0x';
}

return setupParams;
}
}

throw new Error(`${toUpgrade} is not supported.`);
}

async function processCommand(_, chain, options) {
const { env, contractName, address, action, date, privateKey, yes } = options;

Expand Down Expand Up @@ -417,7 +443,7 @@ async function processCommand(_, chain, options) {
return;
}

case 'executeUpgrade': {
case 'executeGatewayUpgrade': {
target = contracts.AxelarGateway?.address;
const gateway = new Contract(target, IGateway.abi, wallet);
const implementation = options.implementation || chain.contracts.AxelarGateway?.implementation;
Expand Down Expand Up @@ -489,6 +515,172 @@ async function processCommand(_, chain, options) {
break;
}

case 'upgrade': {
const isTimeLock = options.isTimeLock;

if (!isTimeLock && contractName === 'InterchainGovernance') {
throw new Error(`Invalid governance action for InterchainGovernance: ${action}`);
}

let eta = 0;

if (isTimeLock) {
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.`);
}

printInfo('Time difference between current time and eta', etaToDate(eta - currTime));
}

const toUpgrade = options.contractToUpgrade;

const implementation = options.implementation || chain.contracts[toUpgrade]?.implementation;

if (!isValidAddress(implementation)) {
throw new Error(`Invalid new ${toUpgrade} implementation address: ${implementation}`);
}

const toUpgradeContract = new Contract(target, IUpgradable.abi, wallet);

printInfo(`Current ${toUpgrade} implementation`, await toUpgradeContract.implementation());
printInfo(`New ${toUpgrade} implementation`, implementation);

const newImplementationCodeHash = await getBytecodeHash(implementation, chain.name, provider);
printInfo(`New ${toUpgrade} implementation code hash`, newImplementationCodeHash);

const setupParams = await getSetupParams(options);

printInfo(`Setup Params for upgrading ${toUpgrade}`, setupParams);

calldata = toUpgradeContract.interface.encodeFunctionData('upgrade', [implementation, newImplementationCodeHash, setupParams]);

const commandType = 0;
const types = ['uint256', 'address', 'bytes', 'uint256', 'uint256'];
const values = [commandType, target, calldata, nativeValue, eta];

gmpPayload = defaultAbiCoder.encode(types, values);
const proposalEta = await governance.getProposalEta(target, calldata, nativeValue);

if (!BigNumber.from(proposalEta).eq(0)) {
printWarn('The proposal already exixts', etaToDate(proposalEta));
}

title = `Chain ${chain.name} ${toUpgrade} upgrade proposal`;
description = `This proposal upgrades the ${toUpgrade} contract ${toUpgradeContract.address} on chain ${chain.name} to a new implementation contract ${implementation}`;

break;
}

case 'executeUpgrade': {
const isTimeLock = options.isTimeLock;

if (!isTimeLock && contractName === 'InterchainGovernance') {
throw new Error(`Invalid governance action for InterchainGovernance: ${action}`);
}

const toUpgrade = options.contractToUpgrade;

target = contracts[toUpgrade]?.address;
const toUpgradeContract = new Contract(target, IUpgradable.abi, wallet);
const implementation = options.implementation || chain.contracts[toUpgrade]?.implementation;
const implementationCodehash = chain.contracts[toUpgrade]?.implementationCodehash;
printInfo(`New ${toUpgrade} implementation code hash`, implementationCodehash);

if (!isValidAddress(implementation)) {
throw new Error(`Invalid new ${toUpgrade} implementation address: ${implementation}`);
}

const setupParams = await getSetupParams(options);

printInfo(`Setup Params for upgrading ${toUpgrade}`, setupParams);

calldata = toUpgradeContract.interface.encodeFunctionData('upgrade', [implementation, implementationCodehash, setupParams]);

const proposalHash = keccak256(defaultAbiCoder.encode(['address', 'bytes', 'uint256'], [target, calldata, nativeValue]));
const eta = await governance.getTimeLock(proposalHash);

if (!calldata) {
throw new Error(`Calldata required for this governance action: ${action}`);
}

let tx;

if (isTimeLock) {
if (eta.eq(0)) {
printError('TimeLock proposal does not exist.');
return;
}

printInfo('Proposal ETA', etaToDate(eta));

const currTime = getCurrentTimeInSeconds();
printInfo('Current time', etaToDate(currTime));

if (currTime < eta) {
throw new Error(`Upgrade timeLock proposal is not yet eligible for execution.`);
}

if (prompt('Proceed with executing this proposal?', yes)) {
throw new Error('Proposal execution cancelled.');
}

tx = await governance.executeProposal(target, calldata, nativeValue, gasOptions);
} else {
const isApproved = await governance.multisigApprovals(proposalHash);

if (!isApproved) {
throw new Error('Upgrade multisig proposal has not been approved.');
}

const isSigner = await governance.isSigner(wallet.address);

if (!isSigner) {
throw new Error(`Caller is not a valid signer address: ${wallet.address}`);
}

const executeInterface = new Interface(governance.interface.fragments);
const executeCalldata = executeInterface.encodeFunctionData('executeMultisigProposal', [target, calldata, nativeValue]);
const topic = keccak256(executeCalldata);

const hasSignerVoted = await governance.hasSignerVoted(wallet.address, topic);

if (hasSignerVoted) {
throw new Error(`Signer has already voted: ${wallet.address}`);
}

const signerVoteCount = await governance.getSignerVotesCount(topic);
printInfo(`${signerVoteCount} signers have already voted.`);

if (prompt('Proceed with executing this proposal?', yes)) {
throw new Error('Proposal execution cancelled.');
}

tx = await governance.executeMultisigProposal(target, calldata, nativeValue, gasOptions);
}

printInfo('Proposal execution tx', tx.hash);

const receipt = await tx.wait(chain.confirmations);

const eventEmitted = wasEventEmitted(receipt, governance, 'ProposalExecuted');

if (!eventEmitted) {
throw new Error('Proposal execution failed.');
}

printInfo('Proposal executed.');

break;
}

case 'withdraw': {
if (!isValidTimeFormat(date)) {
throw new Error(`Invalid ETA: ${date}. Please pass the eta in the format YYYY-MM-DDTHH:mm:ss`);
Expand Down Expand Up @@ -618,6 +810,11 @@ program.addOption(
.choices(['InterchainGovernance', 'AxelarServiceGovernance'])
.default('InterchainGovernance'),
);
program.addOption(
new Option('-u, --contractToUpgrade <contractToUpgrade>', 'contracted to be upgraded')
.choices(['InterchainTokenService'])
.default('InterchainTokenService'),
);
program.addOption(new Option('-a, --address <address>', 'override address'));
program.addOption(new Option('-p, --privateKey <privateKey>', 'private key').makeOptionMandatory(true).env('PRIVATE_KEY'));
program.addOption(new Option('-y, --yes', 'skip deployment prompt confirmation').env('YES'));
Expand All @@ -632,6 +829,8 @@ program.addOption(
'executeProposal',
'executeMultisigProposal',
'gatewayUpgrade',
'executeGatewayUpgrade',
'upgrade',
'submitUpgrade',
'executeUpgrade',
'cancelUpgrade',
Expand All @@ -641,6 +840,8 @@ program.addOption(
);
program.addOption(new Option('--newGovernance <governance>', 'governance address').env('GOVERNANCE'));
program.addOption(new Option('--newMintLimiter <mintLimiter>', 'mint limiter address').env('MINT_LIMITER'));
program.addOption(new Option('--newOperator <operator>', 'new operator address'));
program.addOption(new Option('--isTimeLock <isTimeLock>', 'ITS upgrade proposal type').default(false));
program.addOption(new Option('--commandId <commandId>', 'command id'));
program.addOption(new Option('--target <target>', 'governance execution target'));
program.addOption(new Option('--calldata <calldata>', 'calldata'));
Expand Down
Loading