Skip to content

Commit

Permalink
combine tx creation and ledger signing, code refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Ayush Tiwari committed Sep 18, 2023
1 parent a4b6848 commit add0c02
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 105 deletions.
51 changes: 19 additions & 32 deletions evm/broadcast-transactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,49 @@

const chalk = require('chalk');
const { Command, Option } = require('commander');
const fs = require('fs');
const { ethers } = require('hardhat');
const {
providers: { JsonRpcProvider },
} = ethers;
const readlineSync = require('readline-sync');

const { printError, printInfo, printObj } = require('./utils');
const {
sendTx,
getTxsWithUpdatedNonceAndStatus,
getNonceFromData,
getNonceFromProvider,
getFilePath,
getAllSignersData,
isValidJSON,
updateSignersData,
} = require('./offline-sign-utils');

async function processTransactions(filePath, provider) {
try {
const signersData = await getAllSignersData(filePath);

for (const [signerAddress, transactions] of Object.entries(signersData)) {
const firstPendingnonceFromData = await getNonceFromData(transactions);
const nonce = parseInt(await getNonceFromProvider(provider, signerAddress));

if (nonce > firstPendingnonceFromData) {
transactions = getTxsWithUpdatedNonceAndStatus(transactions, nonce);
}

for (const transaction of transactions) {
if (transaction.status === 'PENDING') {
printInfo('Broadcasting transaction: ');
printObj(transaction.baseTx);
printObj(transaction.unsignedTx);

try {
// Send the signed transaction
const response = await sendTx(transaction.signedTx, provider);

// Update the transaction status and store transaction hash
transaction.status = 'SUCCESS';
transaction.transactionHash = response.transactionHash;
printInfo(`Transactions executed successfully ${response.transactionHash}`);
} catch (error) {
// Update the transaction status and store error message
transaction.status = 'FAILED';
transaction.error = error.message;
printError(`Transaction failed with error: ${error.message}`);
}
const {success, response} = await sendTx(transaction.signedTx, provider);

if(success) {
// Update the transaction status and store transaction hash
transaction.status = 'SUCCESS';
transaction.transactionHash = response.transactionHash;
printInfo(`Transaction executed successfully ${response.transactionHash}`);
}
else {
// Update the transaction status and store error message
transaction.status = 'FAILED';
printError("Error broadcasting tx: ", transaction.signedTx);
}
}
}
// Write back the updated JSON object to the file
signersData[signerAddress] = transactions;
}
fs.writeFileSync(filePath, JSON.stringify(signersData, null, 2));
updateSignersData(filePath, signersData);

} catch (error) {
printError('Error processing transactions:', error.message);
Expand All @@ -69,10 +58,8 @@ async function main(options) {
const network = await provider.getNetwork();

if (!options.yes) {
const anwser = fs.readlineSync.question(
`Proceed with the broadcasting of all pending signed transactions for address ${chalk.green(
signerAddress,
)} on network ${chalk.green(network.name)} with chainId ${chalk.green(network.chainId)} ${chalk.green('(y/n)')} `,
const anwser = readlineSync.question(
`Proceed with the broadcasting of all pending signed transactions for file ${chalk.green(options.filePath)} on network ${chalk.green(network.name)} with chainId ${chalk.green(network.chainId)} ${chalk.green('(y/n)')} `,
);
if (anwser !== 'y') return;
}
Expand Down
64 changes: 36 additions & 28 deletions evm/deploy-gateway-v5.0.x.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const {
ContractFactory,
Contract,
Wallet,
utils: { defaultAbiCoder, getContractAddress, AddressZero, parseUnits },
utils: { defaultAbiCoder, getContractAddress, AddressZero },
getDefaultProvider,
} = ethers;
const readlineSync = require('readline-sync');
Expand All @@ -26,15 +26,16 @@ const {
printError,
printWalletInfo,
printWarn,
printObj,
} = require('./utils');
const {
getAllSignersData,
getNonceFromProvider,
updateSignersData,
getLatestNonceAndUpdateData,
ledgerSign,
getTransactions,
getWallet,
getUnsignedTx,
getLocalNonce,
updateLocalNonce,
} = require('./offline-sign-utils.js');

const AxelarGatewayProxy = require('@axelar-network/axelar-cgp-solidity/artifacts/contracts/AxelarGatewayProxy.sol/AxelarGatewayProxy.json');
Expand Down Expand Up @@ -311,17 +312,22 @@ async function deploy(config, options) {
}

async function upgrade(config, options) {
const { chainName, privateKey, yes, ledgerPath, offline, env } = options;
let filePath = options.filePath;
const { chainName, privateKey, yes, ledgerPath, offline, env, nonceFilePath, filePath, nonceOffset } = options;
const contractName = 'AxelarGateway';

const chain = config.chains[chainName] || { contracts: {}, name: chainName, id: chainName, rpc: options.rpc, tokenSymbol: 'ETH' };
const rpc = options.rpc || chain.rpc;
const provider = getDefaultProvider(rpc);

const wallet = getWallet(privateKey, provider, ledgerPath);

const {wallet, providerNonce} = await getWallet(privateKey, provider, ledgerPath);
const signerAddress = await wallet.getAddress();

let nonce = getLocalNonce(nonceFilePath, signerAddress);

if(providerNonce > nonce) {
updateLocalNonce(nonceFilePath, signerAddress, providerNonce);
nonce = providerNonce;
}
await printWalletInfo(wallet);

const contractConfig = chain.contracts[contractName];
Expand Down Expand Up @@ -371,39 +377,40 @@ async function upgrade(config, options) {
if (anwser !== 'y') return;
}

let nonce = await getNonceFromProvider(provider, signerAddress);
const nonceData = getAllSignersData(nonceFilePath);
nonce = (nonce > nonceData[signerAddress]) ? nonce : nonceData[signerAddress];
let signersData, transactions;

if (offline) {
filePath = filePath || env.toLowerCase() + '-' + chain.name.toLowerCase() + '-' + 'unsignedTransactions.json';
if(!filePath) {
throw new Error("FilePath is not provided in user info");
}
if(nonceOffset ) {
if(!isValidNumber(nonceOffset)) {
throw new Error("Provided nonce offset is not a valid number");
}
nonce += parseInt(nonceOffset);
}
printInfo(`Storing signed Txs offline in file ${filePath}`);
nonce = await getLatestNonceAndUpdateData(filePath, wallet, nonce);
signersData = await getAllSignersData(filePath);
transactions = await getTransactions(filePath, signerAddress);
const network = await provider.getNetwork();
const chainId = network.chainId;

const data = {}, tx = {};
tx = await gateway.populateTransaction['upgrade'](contractConfig.implementation, implementationCodehash, setupParams);
const staticGasOptions = chain.staticGasOptions || {};
const data = {};
let tx = await gateway.populateTransaction['upgrade'](contractConfig.implementation, implementationCodehash, setupParams);
tx.nonce = nonce;
tx.chainId = chainId;
const unsignedTx = getUnsignedTx(chain, tx);
tx.chainId = chain.chainId;
const {baseTx, signedTx} = await ledgerSign(wallet, chain, tx, staticGasOptions);
// Storing the fields in the data that will be stored in file
data.msg = `This transaction will perform upgrade of AxelarGateway contract having address ${gateway.address} with implementation ${contractConfig.implementation} on chain ${chain.name} with chainId ${chain.chainId}`;
data.unsignedTx = unsignedTx;
data.status = 'NOT_SIGNED';

data.unsignedTx = baseTx;
data.signedTx = signedTx;
data.status = "PENDING";
transactions.push(data);

if (transactions) {
signersData[signerAddress] = transactions;
await updateSignersData(filePath, signersData);
}
// Updating Nonce data for this Address
nonceData[signerAddress] = nonce;
await updateSignersData(nonceFilePath, nonceData);
updateLocalNonce(nonceFilePath, signerAddress, nonce);
} else {
const tx = await gateway.upgrade(contractConfig.implementation, implementationCodehash, setupParams, gasOptions);
printInfo('Upgrade transaction', tx.hash);
Expand Down Expand Up @@ -464,13 +471,14 @@ async function programHandler() {
program.addOption(new Option('--prevKeyIDs <prevKeyIDs>', 'previous key IDs to be used for auth contract'));
program.addOption(new Option('-u, --upgrade', 'upgrade gateway').env('UPGRADE'));
program.addOption(
new Option('-o, --offline <offline>', 'If this option is set as true, then ').choices(['true', 'false']).makeOptionMandatory(false),
new Option('--offline', 'Run in offline mode'),
);
program.addOption(new Option('-l, --ledgerPath <ledgerPath>', 'The path to identify the account in ledger').makeOptionMandatory(false));
program.addOption(new Option('--nonceFilePath <nonceFilePath>', 'The File where nonce value to use for each address is stored').makeOptionMandatory(false));
program.addOption(
new Option('--filePath <filePath>', 'The file where the signed tx will be stored').makeOptionMandatory(false),
new Option('--filePath <filePath>', 'The filePath where the signed tx will be stored').makeOptionMandatory(false),
);
program.addOption(new Option('--nonceFilePath <nonceFilePath>', 'The File where nonce value to use for each address is stored').makeOptionMandatory(false));
program.addOption(new Option('--nonceOffset <nonceOffset>', 'The value to add in local nonce if it deviates from actual wallet nonce').makeOptionMandatory(false));

program.action((options) => {
main(options);
Expand Down
73 changes: 56 additions & 17 deletions evm/offline-sign-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,41 +29,60 @@ function getLedgerWallet(provider, path) {
}
}

function getUnsignedTx(chain, tx) {
async function ledgerSign(wallet, chain, tx, gasOptions) {
if (!tx.to || !isAddress(tx.to)) {
throw new Error('Target address is missing/not provided as valid address for the tx in function arguments');
}

if(gasOptions) {
tx.gasLimit = gasOptions.gasLimit;
tx.gasPrice = gasOptions.gasPrice;
}

const baseTx = {
chainId: tx.chainId || chain.chainId || undefined,
data: tx.data || undefined,
gasLimit: tx.gasLimit || chain.gasOptions?.gasLimit || undefined,
gasPrice: tx.gasPrice || undefined,
nonce: tx.nonce ? BigNumber.from(tx.nonce).toNumber() : undefined,
nonce: (tx.nonce !== undefined && tx.nonce !== null) ? BigNumber.from(tx.nonce).toNumber() : undefined,
to: tx.to || undefined,
value: tx.value || undefined,
};

return baseTx;
let signedTx;

try {
signedTx = await wallet.signTransaction(baseTx);
printInfo(`Signed Tx from ledger with signedTxHash as: ${signedTx}`);
} catch (error) {
printError('Failed to sign tx from ledger');
printObj(error);
}

return { baseTx, signedTx };
}

async function sendTx(tx, provider) {
let success;
try {
const receipt = await provider.sendTransaction(tx).then((tx) => tx.wait());
if(!isValidJSON(receipt) || response.status !== 1) {
const response = await provider.sendTransaction(tx).then((tx) => tx.wait());
if(response.error || !isValidJSON(response) || response.status !== 1) {
const error = `Execution failed${
response.status ? ` with txHash: ${response.transactionHash}` : ` with msg: ${response.message}`
}`;
throw new Error(error);
}
return receipt;
success = true;
return { success, response};
} catch (errorObj) {
printError('Error while broadcasting signed tx');
printObj(errorObj);
success = false;
return {success, undefined};
}
}

async function updateSignersData(filePath, signersData) {
function updateSignersData(filePath, signersData) {

fs.writeFileSync(filePath, JSON.stringify(signersData, null, 2), (err) => {
if (err) {
Expand All @@ -77,23 +96,28 @@ async function updateSignersData(filePath, signersData) {
}

async function getNonceFromProvider(provider, address) {
const nonce = await provider.getTransactionCount(address);
let nonce = 0;
try {
nonce = await provider.getTransactionCount(address);
} catch(error) {
printError("Could not fetch nonnce from provider", error.message);
}
return nonce;
}

async function getLatestNonceAndUpdateData(filePath, wallet, nonce) {
async function getLatestNonceAndUpdateData(filePath, wallet) {
try {
const signerAddress = await wallet.getAddress();
const provider = wallet.provider;
const providerNonce = getNonceFromProvider(provider, signerAddress);
const signersData = await getAllSignersData(filePath);
let transactions = signersData[signerAddress];
const firstPendingnonceFromData = getNonceFromData(transactions);
let latestTransactionNonce = transactions[transactions.length - 1].unsignedTx.nonce;

if (nonce >= firstPendingnonceFromData) {
if (providerNonce >= transactions.unsignedTx.nonce && (transactions.status === "NOT_SIGNED" || transactions.status === "PENDING")) {
transactions = getTxsWithUpdatedNonceAndStatus(transactions, nonce);
signersData[signerAddress] = transactions;
await updateSignersData(filePath, signersData);
} else {
nonce = firstPendingnonceFromData + 1;
}

return nonce;
Expand Down Expand Up @@ -135,7 +159,7 @@ function getNonceFromData(transactions) {
return 0;
}

async function getAllSignersData(filePath) {
function getAllSignersData(filePath) {
const signersData = {};

try {
Expand Down Expand Up @@ -223,7 +247,7 @@ function isValidJSON(obj) {
return true;
}

const getWallet = (privateKey, provider, ledgerPath) => {
const getWallet = async(privateKey, provider, ledgerPath) => {
let wallet;
if (privateKey === 'ledger') {
wallet = getLedgerWallet(provider, ledgerPath || undefined);
Expand All @@ -234,7 +258,20 @@ const getWallet = (privateKey, provider, ledgerPath) => {

wallet = new Wallet(privateKey, provider);
}
return wallet;
const signerAddress = await wallet.getAddress();
const providerNonce = await getNonceFromProvider(provider, signerAddress);
return {wallet, providerNonce};
}

const getLocalNonce = (nonceFilePath, signerAddress) => {
const nonceData = getAllSignersData(nonceFilePath);
return nonceData[signerAddress] || 0;
}

const updateLocalNonce = (nonceFilePath, signerAddress, nonce) => {
let nonceData = getAllSignersData(nonceFilePath);
nonceData[signerAddress] = nonce;
updateSignersData(nonceFilePath, nonceData);
}

module.exports = {
Expand All @@ -249,5 +286,7 @@ module.exports = {
getLatestNonceAndUpdateData,
isValidJSON,
getWallet,
getUnsignedTx,
getLocalNonce,
updateLocalNonce,
ledgerSign,
};
Loading

0 comments on commit add0c02

Please sign in to comment.