@holographxyz/cli
Version:
Holograph operator CLI
169 lines (168 loc) • 9.34 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const inquirer = tslib_1.__importStar(require("inquirer"));
const fs = tslib_1.__importStar(require("fs-extra"));
const core_1 = require("@oclif/core");
const bignumber_1 = require("@ethersproject/bignumber");
const units_1 = require("@ethersproject/units");
const networks_1 = require("@holographxyz/networks");
const config_1 = require("../../utils/config");
const utils_1 = require("../../utils/utils");
const network_monitor_1 = require("../../utils/network-monitor");
const validation_1 = require("../../utils/validation");
class BridgeContract extends core_1.Command {
static description = 'Bridge a Holographable contract from source chain to destination chain. You need to have a deployment config JSON file. Use the "contract:create" command to create or extract one.';
static examples = [
'$ <%= config.bin %> <%= command.id %> --sourceNetwork="goerli" --destinationNetwork="fuji" --deploymentConfig="./MyContract.json"',
];
static flags = {
sourceNetwork: core_1.Flags.string({
description: 'The network from which contract deploy request will be sent',
parse: validation_1.validateNetwork,
options: networks_1.supportedShortNetworks,
multiple: false,
required: false,
}),
destinationNetwork: core_1.Flags.string({
description: 'The network on which the contract will be deployed',
parse: validation_1.validateNetwork,
options: networks_1.supportedShortNetworks,
multiple: false,
required: false,
}),
deploymentConfig: core_1.Flags.string({
description: 'The config file to use',
parse: validation_1.validateNonEmptyString,
multiple: false,
required: false,
}),
};
/**
* Contract class variables
*/
networkMonitor;
async run() {
this.log('Loading user configurations...');
const { userWallet, configFile, supportedNetworksOptions } = await (0, config_1.ensureConfigFileIsValid)(this.config.configDir, undefined, true);
const { flags } = await this.parse(BridgeContract);
this.log('User configurations loaded');
const sourceNetwork = await (0, validation_1.checkOptionFlag)(supportedNetworksOptions, flags.sourceNetwork, 'Select the network from which contract deploy request will be sent');
const destinationNetwork = await (0, validation_1.checkOptionFlag)(supportedNetworksOptions, flags.destinationNetwork, 'Select the network on which the contract will be deployed', sourceNetwork);
let deploymentConfig;
const deploymentConfigFile = await (0, validation_1.checkStringFlag)(flags.deploymentConfig, 'Enter the config file to use');
if (await fs.pathExists(deploymentConfigFile)) {
deploymentConfig = (await fs.readJson(deploymentConfigFile));
}
else {
throw new Error('The file "' + deploymentConfigFile + '" does not exist');
}
const configHash = utils_1.web3.utils.keccak256('0x' +
deploymentConfig.config.contractType.slice(2) +
deploymentConfig.config.chainType.slice(2) +
deploymentConfig.config.salt.slice(2) +
utils_1.web3.utils.keccak256(deploymentConfig.config.byteCode).slice(2) +
utils_1.web3.utils.keccak256(deploymentConfig.config.initCode).slice(2) +
deploymentConfig.signer.slice(2));
this.networkMonitor = new network_monitor_1.NetworkMonitor({
parent: this,
configFile,
networks: [sourceNetwork, destinationNetwork],
debug: this.debug,
userWallet,
verbose: false,
});
core_1.CliUx.ux.action.start('Loading network RPC providers');
await this.networkMonitor.run(true);
core_1.CliUx.ux.action.stop();
core_1.CliUx.ux.action.start(`Checking that contract is not already deployed on ${networks_1.networks[destinationNetwork].shortKey} network`);
const contractAddress = await this.networkMonitor.registryContract
.connect(this.networkMonitor.providers[destinationNetwork])
.getContractTypeAddress(configHash);
core_1.CliUx.ux.action.stop();
if (contractAddress !== utils_1.zeroAddress) {
this.networkMonitor.structuredLogError(destinationNetwork, `Contract already deployed at ${contractAddress}`);
this.exit();
}
const data = (0, utils_1.generateInitCode)(['tuple(bytes32,uint32,bytes32,bytes,bytes)', 'tuple(bytes32,bytes32,uint8)', 'address'], [
[
deploymentConfig.config.contractType,
deploymentConfig.config.chainType,
deploymentConfig.config.salt,
deploymentConfig.config.byteCode,
deploymentConfig.config.initCode,
],
[deploymentConfig.signature.r, deploymentConfig.signature.s, deploymentConfig.signature.v],
deploymentConfig.signer,
]);
const TESTGASLIMIT = bignumber_1.BigNumber.from('10000000');
let payload = await this.networkMonitor.bridgeContract
.connect(this.networkMonitor.providers[sourceNetwork])
.callStatic.getBridgeOutRequestPayload(networks_1.networks[destinationNetwork].holographId, this.networkMonitor.factoryAddress, '0x' + 'ff'.repeat(32), '0x' + 'ff'.repeat(32), data);
let estimatedGas = TESTGASLIMIT.sub(await this.networkMonitor.operatorContract
.connect(this.networkMonitor.providers[destinationNetwork])
.callStatic.jobEstimator(payload, { gasLimit: TESTGASLIMIT }));
const gasPricing = this.networkMonitor.gasPrices[destinationNetwork];
let gasPrice = gasPricing.isEip1559 ? gasPricing.maxFeePerGas : gasPricing.gasPrice;
gasPrice = gasPrice.add(gasPrice.div(bignumber_1.BigNumber.from('4')));
payload = await this.networkMonitor.bridgeContract
.connect(this.networkMonitor.providers[sourceNetwork])
.callStatic.getBridgeOutRequestPayload(networks_1.networks[destinationNetwork].holographId, this.networkMonitor.factoryAddress, estimatedGas,
// allow LZ module to set gas price
// '0x' + '00'.repeat(32),
gasPrice, data);
const fees = await this.networkMonitor.bridgeContract
.connect(this.networkMonitor.providers[sourceNetwork])
.callStatic.getMessageFee(networks_1.networks[destinationNetwork].holographId, estimatedGas, gasPrice, payload);
const total = fees[0].add(fees[1]);
estimatedGas = TESTGASLIMIT.sub(await this.networkMonitor.operatorContract
.connect(this.networkMonitor.providers[destinationNetwork])
.callStatic.jobEstimator(payload, { value: total, gasLimit: TESTGASLIMIT }));
this.log('hlg fee', (0, units_1.formatUnits)(fees[0], 'ether'), 'ether');
this.log('lz fee', (0, units_1.formatUnits)(fees[1], 'ether'), 'ether');
this.log('lz gasPrice', (0, units_1.formatUnits)(fees[2], 'gwei'), 'GWEI');
this.log('our estimated gasPrice', (0, units_1.formatUnits)(gasPrice, 'gwei'), 'GWEI');
this.log('estimated gas usage', estimatedGas.toNumber());
const blockchainPrompt = await inquirer.prompt([
{
name: 'shouldContinue',
message: 'Next steps submit the transaction, would you like to proceed?',
type: 'confirm',
default: true,
},
]);
if (!blockchainPrompt.shouldContinue) {
this.log('Dropping command, no blockchain transactions executed');
this.exit();
}
core_1.CliUx.ux.action.start('Making beam request...');
const receipt = await this.networkMonitor.executeTransaction({
network: sourceNetwork,
contract: this.networkMonitor.bridgeContract.connect(this.networkMonitor.providers[destinationNetwork]),
methodName: 'bridgeOutRequest',
args: [
networks_1.networks[destinationNetwork].holographId,
this.networkMonitor.factoryAddress,
estimatedGas,
gasPrice,
data,
],
waitForReceipt: true,
value: total.add(total.div(bignumber_1.BigNumber.from('4'))),
});
core_1.CliUx.ux.action.stop();
if (receipt === null) {
this.networkMonitor.structuredLogError(sourceNetwork, `Failed to confirm that the transaction was mined`);
this.exit();
}
else {
const jobHash = this.networkMonitor.decodeCrossChainMessageSentEvent(receipt, this.networkMonitor.operatorAddress);
if (jobHash === undefined) {
this.log('Failed to extract cross-chain job hash transaction receipt');
}
this.log(`Cross-chain beaming from ${networks_1.networks[sourceNetwork].shortKey} network, to ${networks_1.networks[destinationNetwork].shortKey} network has started under job hash ${jobHash}`);
}
this.exit();
}
}
exports.default = BridgeContract;