diff --git a/evm/gas-service.js b/evm/gas-service.js index e3103012..f2a164ce 100644 --- a/evm/gas-service.js +++ b/evm/gas-service.js @@ -3,8 +3,10 @@ const { ethers } = require('hardhat'); const { getDefaultProvider, + BigNumber, Contract, constants: { AddressZero }, + utils: { formatEther, parseEther }, } = ethers; const { Command, Option } = require('commander'); const { @@ -210,7 +212,36 @@ async function getGasUpdates(config, env, chain, destinationChains) { } async function processCommand(config, chain, options) { - const { env, contractName, address, action, privateKey, chains, destinationChain, destinationAddress, isExpress, yes } = options; + const { + env, + contractName, + address, + action, + privateKey, + + chains, + + destinationChain, + destinationAddress, + isExpress, + + txHash, + logIndex, + + receiver, + token, + amount, + + collectorReceiver, + collectTokens, + collectAmounts, + + gasToken, + gasFeeAmount, + refundAddress, + + yes, + } = options; const executionGasLimit = parseInt(options.executionGasLimit); const contracts = chain.contracts; @@ -325,6 +356,150 @@ async function processCommand(config, chain, options) { break; } + case 'refund': { + validateParameters({ + isKeccak256Hash: { txHash }, + isNumber: { logIndex, amount }, + isValidAddress: { receiver, token }, + }); + + const refundAmount = parseEther(amount); + + const balance = await provider.getBalance(gasService.address); + + if (balance.lt(refundAmount)) { + throw new Error( + `Contract balance ${formatEther(BigNumber.from(balance))} is less than refund amount: ${formatEther( + BigNumber.from(refundAmount), + )}`, + ); + } + + const tx = await gasService.refund(txHash, logIndex, receiver, token, refundAmount, gasOptions); + + printInfo('Call refund', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = wasEventEmitted(receipt, gasService, 'Refunded'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'collectFees': { + validateParameters({ + isValidAddress: { collectorReceiver }, + isNonEmptyAddressArray: { collectTokens }, + isNonEmptyNumberArray: { collectAmounts }, + }); + + const tx = await gasService.collectFees(collectorReceiver, collectTokens, collectAmounts, gasOptions); + + printInfo('Call collectFees', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = wasEventEmitted(receipt, gasService, 'FeesCollected'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'addGas': { + validateParameters({ + isKeccak256Hash: { txHash }, + isNumber: { logIndex, gasFeeAmount }, + isValidAddress: { gasToken, refundAddress }, + }); + + const tx = await gasService.addGas(txHash, logIndex, gasToken, gasFeeAmount, refundAddress, gasOptions); + + printInfo('Call addGas', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = wasEventEmitted(receipt, gasService, 'GasAdded'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'addNativeGas': { + validateParameters({ + isKeccak256Hash: { txHash }, + isNumber: { logIndex }, + isValidAddress: { refundAddress }, + }); + + const tx = await gasService.addNativeGas(txHash, logIndex, refundAddress, { ...gasOptions, value: amount }); + + printInfo('Call addNativeGas', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = wasEventEmitted(receipt, gasService, 'NativeGasAdded'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'addExpressGas': { + validateParameters({ + isKeccak256Hash: { txHash }, + isNumber: { logIndex, gasFeeAmount }, + isValidAddress: { gasToken, refundAddress }, + }); + + const tx = await gasService.addExpressGas(txHash, logIndex, gasToken, gasFeeAmount, refundAddress, gasOptions); + + printInfo('Call addExpressGas', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = wasEventEmitted(receipt, gasService, 'ExpressGasAdded'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'addNativeExpressGas': { + validateParameters({ + isKeccak256Hash: { txHash }, + isNumber: { logIndex }, + isValidAddress: { refundAddress }, + }); + + const tx = await gasService.addNativeExpressGas(txHash, logIndex, refundAddress, { ...gasOptions, value: amount }); + + printInfo('Call addNativeExpressGas', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = wasEventEmitted(receipt, gasService, 'NativeExpressGasAdded'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + default: throw new Error(`Unknown action: ${action}`); } @@ -345,11 +520,27 @@ if (require.main === module) { program.addOption(new Option('-c, --contractName ', 'contract name').default('AxelarGasService')); program.addOption( - new Option('--action ', 'GasService action').choices(['estimateGasFee', 'updateGasInfo']).makeOptionMandatory(true), + new Option('--action ', 'GasService action') + .choices([ + 'estimateGasFee', + 'updateGasInfo', + 'refund', + 'collectFees', + 'addGas', + 'addNativeGas', + 'addExpressGas', + 'addNativeExpressGas', + ]) + .makeOptionMandatory(true), ); program.addOption(new Option('--offline', 'run script in offline mode')); program.addOption(new Option('--nonceOffset ', 'The value to add in local nonce if it deviates from actual wallet nonce')); + // common options + program.addOption(new Option('--txHash ', 'Transaction hash').makeOptionMandatory(false)); + program.addOption(new Option('--logIndex ', 'Log index').makeOptionMandatory(false)); + program.addOption(new Option('--receiver ', 'Receiver address').makeOptionMandatory(false)); + // options for estimateGasFee program.addOption(new Option('--destinationChain ', 'Destination chain name')); program.addOption(new Option('--destinationAddress ', 'Destination contract address')); @@ -361,6 +552,20 @@ if (require.main === module) { program.addOption(new Option('--chains ', 'Chain names')); program.addOption(new Option('--relayerAPI ', 'Relay the tx through an external relayer API').env('RELAYER_API')); + // options for refund + program.addOption(new Option('--token ', 'Refund token address').makeOptionMandatory(false)); + program.addOption(new Option('--amount ', 'Refund amount').makeOptionMandatory(false)); + + // options for collectFees + program.addOption(new Option('--collectorReceiver ', 'Collector receiver address').makeOptionMandatory(false)); + program.addOption(new Option('--collectTokens ', 'Tokens to collect').makeOptionMandatory(false)); + program.addOption(new Option('--collectAmounts ', 'Amounts to collect').makeOptionMandatory(false)); + + // options for adding gas + program.addOption(new Option('--gasToken ', 'Gas token address').makeOptionMandatory(false)); + program.addOption(new Option('--gasFeeAmount ', 'Gas fee amount').makeOptionMandatory(false)); + program.addOption(new Option('--refundAddress ', 'Refund address').makeOptionMandatory(false)); + program.action((options) => { main(options); });