diff --git a/config.example.yaml b/config.example.yaml index 2ab98616..63ba9c4c 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -32,6 +32,22 @@ networks: denom: 'untrn' domain: 1302 +# Networks that already have hyperlane deployments +# but might require new warp route connections +evm_networks: + - name: 'mantasepolia' + chain_id: 3441006 + rpc_endpoint: "http://localhost:8545" + network: 'sepolia' + nativeCurrency: + name: 'Sepolia Ether' + symbol: 'ETH' + decimals: 18 + mailbox_address: "0x123..." + multisig_ism_factory_address: "0x123..." + + + signer: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef deploy: diff --git a/example/src/recipient.ts b/example/src/recipient.ts index 72feed00..55164d24 100644 --- a/example/src/recipient.ts +++ b/example/src/recipient.ts @@ -8,11 +8,17 @@ import { HYP_MULTSIG_ISM_FACTORY } from './constants'; import { CONTAINER, Dependencies } from './ioc'; import { expectNextContractAddr, logTx } from './utils'; -export const recipientCmd = new Command('deploy-test-recipient').action( +export const recipientCmd = new Command('deploy-test-recipient') + .option('--validator-address ', 'validator address to use') + .action( deployTestRecipient, ); -async function deployTestRecipient() { +type DeployTestRecipientArgs = { + validatorAddress?: `0x{string}` +} + +async function deployTestRecipient({validatorAddress}: DeployTestRecipientArgs) { const { account, provider: { query, exec }, @@ -37,7 +43,7 @@ async function deployTestRecipient() { abi: StaticMessageIdMultisigIsmFactory__factory.abi, address: HYP_MULTSIG_ISM_FACTORY, functionName: 'getAddress', - args: [[account.address], 1], + args: [[validatorAddress || account.address], 1], }); console.log(`Deploying multisigIsm at "${multisigIsmAddr.green}"...`); @@ -46,7 +52,7 @@ async function deployTestRecipient() { abi: StaticMessageIdMultisigIsmFactory__factory.abi, address: HYP_MULTSIG_ISM_FACTORY, functionName: 'deploy', - args: [[account.address], 1], + args: [[validatorAddress || account.address], 1], }); logTx('Deploy multisig ism', tx); await query.waitForTransactionReceipt({ hash: tx }); diff --git a/example/src/warp.ts b/example/src/warp.ts index 6a771038..0c244c07 100644 --- a/example/src/warp.ts +++ b/example/src/warp.ts @@ -1,8 +1,11 @@ -import { HypERC20__factory } from '@hyperlane-xyz/core'; -import { Command } from 'commander'; +import { + HypERC20__factory, + StaticMessageIdMultisigIsmFactory__factory, +} from '@hyperlane-xyz/core'; +import { Command, Option} from 'commander'; import { isAddress } from 'viem'; -import { HYP_MAILBOX } from './constants'; +import { HYP_MAILBOX, HYP_MULTSIG_ISM_FACTORY } from './constants'; import { CONTAINER, Dependencies } from './ioc'; import { expectNextContractAddr, @@ -12,7 +15,32 @@ import { const warpCmd = new Command('warp'); -warpCmd.command('deploy').action(deployWarpRoute); +warpCmd.command('deploy') + .option( + '--contract-name ', + 'Warp contract name e.g. Hyperlane Bridged TIA', + 'Hyperlane Bridged Osmo' + ) + .option( + '--asset-name ', + 'Warp route asset name e.g. TIA', + 'TIA' + ) + .option( + '--create-new-ism', + 'Option to create a new ISM for the the warp route', + false + ) + .option( + '--warp-ism-address ', + 'ISM to set on the warp route recipient' + ) + .option( + '--ism-validator-address ', + 'Validator address on the ism', + ) + .action(deployWarpRoute); + warpCmd .command('link') .argument('', 'address of warp route') @@ -29,16 +57,33 @@ warpCmd export { warpCmd }; -async function deployWarpRoute() { +type DeployWarpRouteArgs = { + contractName: string, + assetName: string, + createNewIsm?: boolean, + warpIsmAddress?: `0x${string}`, + ismValidatorAddress?: `0x${string}`, +}; + +async function deployWarpRoute({ + contractName, + assetName, + createNewIsm, + warpIsmAddress, + ismValidatorAddress, +}: DeployWarpRouteArgs) { const { account, provider: { query, exec }, } = CONTAINER.get(Dependencies); - // deploy hyp erc20 (implementation) + if (createNewIsm && warpIsmAddress !== undefined) { + throw new Error("invalid options: cannot create a new ISM and pass a custom ISM address at the same time") + } - const hypErc20OsmoAddr = await expectNextContractAddr(query, account); - console.log(`Deploying HypERC20 at "${hypErc20OsmoAddr.green}"...`); + // deploy hyp erc20 (implementation) + const hypErc20Addr = await expectNextContractAddr(query, account); + console.log(`Deploying HypERC20 at "${hypErc20Addr.green}"...`); { const tx = await exec.deployContract({ @@ -46,25 +91,65 @@ async function deployWarpRoute() { bytecode: HypERC20__factory.bytecode, args: [6, HYP_MAILBOX], }); - logTx('Deploying HypERC20Osmo', tx); + logTx('Deploying HypERC20', tx); await query.waitForTransactionReceipt({ hash: tx }); } { const tx = await exec.writeContract({ abi: HypERC20__factory.abi, - address: hypErc20OsmoAddr, + address: hypErc20Addr, functionName: 'initialize', - args: [0n, 'Hyperlane Bridged Osmosis', 'OSMO'], + args: [0n, contractName ? contractName : 'Hyperlane Bridged OSMO', assetName ? assetName : 'OSMO'], + }); + logTx('Initialize HypERC20', tx); + await query.waitForTransactionReceipt({ hash: tx }); + } + + // If the option was specifed to create a new ISM, deploy the multisig ISM contract + if (createNewIsm) { + ismValidatorAddress = ismValidatorAddress ? ismValidatorAddress : account.address + + const multisigIsmAddr = await query.readContract({ + abi: StaticMessageIdMultisigIsmFactory__factory.abi, + address: HYP_MULTSIG_ISM_FACTORY, + functionName: 'getAddress', + args: [[ismValidatorAddress], 1], + }); + console.log(`Deploying multisigIsm at "${multisigIsmAddr.green}"...`); + + { + const tx = await exec.writeContract({ + abi: StaticMessageIdMultisigIsmFactory__factory.abi, + address: HYP_MULTSIG_ISM_FACTORY, + functionName: 'deploy', + args: [[ismValidatorAddress], 1], + }); + logTx('Deploy multisig ism', tx); + await query.waitForTransactionReceipt({ hash: tx }); + } + + warpIsmAddress = multisigIsmAddr + } + + // If a custom ISM address was specified or if a new ISM was created, + // register that address in the warp contract + // Otherwise, the default ISM will be used + if (warpIsmAddress !== undefined) { + const tx = await exec.writeContract({ + abi: HypERC20__factory.abi, + address: hypErc20Addr, + functionName: 'setInterchainSecurityModule', + args: [warpIsmAddress], }); - logTx('Initialize HypERC20Osmo', tx); + logTx('Set ism for warp route', tx); await query.waitForTransactionReceipt({ hash: tx }); } console.log('== Done! =='); console.log({ - hypErc20Osmo: hypErc20OsmoAddr, + hypErc20: hypErc20Addr, }); } diff --git a/script/commands/evm.ts b/script/commands/evm.ts new file mode 100644 index 00000000..b5b58efe --- /dev/null +++ b/script/commands/evm.ts @@ -0,0 +1,227 @@ +import { + HypERC20__factory, + StaticMessageIdMultisigIsmFactory__factory, +} from '@hyperlane-xyz/core'; +import { Command, Option } from 'commander'; +import { + Account, + Chain, + Hex, + createPublicClient, + createWalletClient, + http, +} from 'viem'; +import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts'; +import { config, getEvmNetwork } from '../shared/config'; +import { + expectNextContractAddr, + logTx, +} from '../../example/src/utils'; + +const evmCmd = new Command('evm') + +evmCmd.command('deploy-ism') + .addOption( + new Option( + '--evm-network-name ', + 'specify the EVM network name', + ) + .choices(config.evm_networks ? config.evm_networks.map((v) => v.name) : []) + .makeOptionMandatory() + ) + .option( + '--validator-addresses ', + 'Comma separated list of validator address on the ism', + ) + .option( + '--threshold ', + 'Threshold for the number of validators in the ISM', + "1", + ) + .action(deployIsm); + +evmCmd.command('deploy-warp') + .addOption( + new Option( + '--evm-network-name ', + 'specify the EVM network name', + ) + .choices(config.evm_networks ? config.evm_networks.map((v) => v.name) : []) + .makeOptionMandatory() + ) + .option( + '--contract-name ', + 'Warp contract name e.g. Hyperlane Bridged TIA', + 'Hyperlane Bridged Osmo' + ) + .option( + '--asset-name ', + 'Warp route asset name e.g. TIA', + 'TIA' + ) + .option( + '--ism-address ', + 'ISM to set on the warp route recipient' + ) + .action(deployWarpRoute); + +export { evmCmd }; + +type DeployIsmArgs = { + evmNetworkName: string, + validatorAddresses?: `0x${string}`, + threshold?: number, +}; + +async function deployIsm({ + evmNetworkName, + validatorAddresses, + threshold, +}: DeployIsmArgs) { + const { signer } = config; + const evmNetwork = getEvmNetwork(evmNetworkName) + + const account: Account = + signer.split(' ').length > 1 + ? mnemonicToAccount(signer) + : privateKeyToAccount(`0x${signer}` as Hex); + + const chain: Chain = { + id: evmNetwork.chain_id, + network: evmNetwork.network, + name: evmNetwork.name, + nativeCurrency: evmNetwork.native_currency, + rpcUrls: { + default: { + http: [evmNetwork.rpc_endpoint], + }, + public: { + http: [evmNetwork.rpc_endpoint], + }, + }, + } + + const query = createPublicClient({ + chain: chain, + transport: http(evmNetwork.rpc_endpoint), + }) + + const exec = createWalletClient({ + chain: chain, + account, + transport: http(evmNetwork.rpc_endpoint), + }); + + const validatorAddressesString = validatorAddresses ? validatorAddresses : account.address + const validatorAddressesList = validatorAddressesString.split(",").map(address => address as `0x${string}`) + + const multisigIsmAddr = await query.readContract({ + abi: StaticMessageIdMultisigIsmFactory__factory.abi, + address: evmNetwork.multisig_ism_factory_address, + functionName: 'getAddress', + args: [validatorAddressesList, Number(threshold)], + }); + console.log(`Multisig ISM Address to be deployed at: ${multisigIsmAddr.green}`); + + { + const tx = await exec.writeContract({ + abi: StaticMessageIdMultisigIsmFactory__factory.abi, + address: evmNetwork.multisig_ism_factory_address, + functionName: 'deploy', + args: [validatorAddressesList, Number(threshold)], + }); + logTx('Deploying multisig ISM', tx); + await query.waitForTransactionReceipt({ hash: tx }); + } + + console.log(`\nMultisig ISM Address: ${multisigIsmAddr.blue}`); +} + + +type DeployWarpRouteArgs = { + evmNetworkName: string, + contractName: string, + assetName: string, + ismAddress?: `0x${string}`, +}; + +async function deployWarpRoute({ + evmNetworkName, + contractName, + assetName, + ismAddress, +}: DeployWarpRouteArgs) { + const { signer } = config; + const evmNetwork = getEvmNetwork(evmNetworkName) + + const account: Account = + signer.split(' ').length > 1 + ? mnemonicToAccount(signer) + : privateKeyToAccount(`0x${signer}` as Hex); + + const chain: Chain = { + id: evmNetwork.chain_id, + network: evmNetwork.network, + name: evmNetwork.name, + nativeCurrency: evmNetwork.native_currency, + rpcUrls: { + default: { + http: [evmNetwork.rpc_endpoint], + }, + public: { + http: [evmNetwork.rpc_endpoint], + }, + }, + } + + const query = createPublicClient({ + chain: chain, + transport: http(evmNetwork.rpc_endpoint), + }) + + const exec = createWalletClient({ + chain: chain, + account, + transport: http(evmNetwork.rpc_endpoint), + }); + + // deploy hyp erc20 (implementation) + const hypErc20Addr = await expectNextContractAddr(query, account); + console.log(`Deploying HypERC20 at "${hypErc20Addr.green}"...`); + + { + const tx = await exec.deployContract({ + abi: HypERC20__factory.abi, + bytecode: HypERC20__factory.bytecode, + args: [6, evmNetwork.mailbox_address], + }); + logTx('Deploying HypERC20', tx); + await query.waitForTransactionReceipt({ hash: tx }); + } + + { + const tx = await exec.writeContract({ + abi: HypERC20__factory.abi, + address: hypErc20Addr, + functionName: 'initialize', + args: [0n, contractName ? contractName : 'Hyperlane Bridged OSMO', assetName ? assetName : 'OSMO'], + }); + logTx('Initialize HypERC20', tx); + await query.waitForTransactionReceipt({ hash: tx }); + } + + // If a custom ISM address was specified, register that address in the warp contract + // Otherwise, the default ISM will be used + if (ismAddress !== undefined) { + const tx = await exec.writeContract({ + abi: HypERC20__factory.abi, + address: hypErc20Addr, + functionName: 'setInterchainSecurityModule', + args: [ismAddress], + }); + logTx('Set ism for warp route', tx); + await query.waitForTransactionReceipt({ hash: tx }); + } + + console.log(`\nWarp ERC20: ${hypErc20Addr.blue}`); +} diff --git a/script/commands/index.ts b/script/commands/index.ts index f769ae04..b560f9ba 100644 --- a/script/commands/index.ts +++ b/script/commands/index.ts @@ -5,3 +5,4 @@ export { migrateCmd } from './migrate'; export { uploadCmd } from './upload'; export { walletCmd } from './wallet'; export { warpCmd } from './warp'; +export { evmCmd } from './evm'; diff --git a/script/commands/update.ts b/script/commands/update.ts new file mode 100644 index 00000000..b6a53fa4 --- /dev/null +++ b/script/commands/update.ts @@ -0,0 +1,140 @@ +import { Command } from 'commander'; + +import { IgpHookType, config } from '../shared/config'; +import { typed, ContextHook } from '../shared/context'; +import { executeMultiMsg } from '../shared/contract'; +import { CONTAINER, Dependencies } from '../shared/ioc'; + +export const updateCmd = new Command('update') + .description('Register new chain to existing contracts') + .configureHelp({ showGlobalOptions: true }) +; + +updateCmd.command('igp-oracle').action(handleRegisterIgpOracle); +updateCmd.command('ism-multisig').action(handleRegisterIsm); + +type AggregateHook = typed<'hpl_hook_aggregate'> & { + hooks: ContextHook[]; +}; + +type IgpHook = typed<'hpl_igp'> & { oracle: typed<'hpl_igp_oracle'> }; + +function isIgpHookType(hook: ContextHook): hook is IgpHook { + return hook.type === 'hpl_igp'; +} + +function getIsmsMultisigConfig() { + if (!config.deploy.ism || config.deploy.ism.type != "multisig") { + throw new Error('Ism multisig config not found'); + } + return config.deploy.ism; +} + +function findIgpHookInAggregate() { + const defaultHook = config.deploy.hooks?.default; + if (defaultHook && defaultHook.type === 'aggregate') { + const igpHook = defaultHook.hooks.find( + (hook): hook is IgpHookType => hook.type === 'igp' + ); + if (!igpHook) { + throw new Error('igpHook not found under aggregate hook'); + } + return igpHook; + } + throw new Error('Aggregate igp hook not found'); +} + +async function handleRegisterIsm(_: object, cmd: Command) { + const { ctx, client } = CONTAINER.get(Dependencies); + + if (!ctx.deployments.isms || ctx.deployments.isms.type != "hpl_ism_multisig") { + throw new Error('Ism multisig context not found'); + } + const ismMultisigAddress = ctx.deployments.isms.address + const ismsConfig = getIsmsMultisigConfig(); + + const multisig = { + type: 'hpl_ism_multisig', + address: ismMultisigAddress as string, + }; + await executeMultiMsg( + client, + Object.entries(ismsConfig.validators).map(([domain, { addrs, threshold }]) => ({ + contract: multisig, + msg: { + set_validators: { + domain: Number(domain), + threshold, + validators: addrs.map((v) => + v === '' ? client.signer_addr : v + ), + }, + }, + })) + ); + } + +async function handleRegisterIgpOracle(_: object, cmd: Command) { + const { ctx, client } = CONTAINER.get(Dependencies); + + const defaultHook = ctx.deployments.hooks?.default; + let igpHookDeployment; + let aggregateHook; + let igpOracleAddress: string | undefined; + if (defaultHook && defaultHook.type === 'hpl_hook_aggregate') { + igpHookDeployment = defaultHook.hooks.find(isIgpHookType) as IgpHook; + igpOracleAddress = igpHookDeployment?.oracle?.address; + aggregateHook = defaultHook as AggregateHook; + } + if (!igpHookDeployment) { + throw new Error('igpHook is undefined in context'); + } + if (!igpOracleAddress) { + throw new Error('igpOracleAddress is undefined in context'); + } + if (!aggregateHook) { + throw new Error('aggregateHook is undefined in context'); + } + + const igpType: IgpHook | undefined = aggregateHook?.hooks.find( + (hook): hook is IgpHook => hook.type === 'hpl_igp' + ); + if (!igpType) { + throw new Error('igpType is undefined'); + } + + const igpHookConfig = findIgpHookInAggregate(); + await executeMultiMsg(client, [ + { + contract: { + type: 'hpl_igp_oracle', + address: igpOracleAddress, + }, + msg: { + set_remote_gas_data_configs: { + configs: Object.entries(igpHookConfig.configs).map(([domain, v]) => ({ + remote_domain: Number(domain), + token_exchange_rate: v.exchange_rate.toString(), + gas_price: v.gas_price.toString(), + })), + }, + }, + }, + { + contract: { + type: 'hpl_igp', + address: igpHookDeployment.address, + }, + msg: { + router: { + set_routes: { + set: Object.keys(igpHookConfig.configs).map((domain) => ({ + domain: Number(domain), + route: igpOracleAddress, + })), + }, + }, + }, + }, + ]); +} diff --git a/script/commands/upload.ts b/script/commands/upload.ts index 88638e95..c6adf390 100644 --- a/script/commands/upload.ts +++ b/script/commands/upload.ts @@ -8,6 +8,7 @@ * - list available releases from github (check `../common/github.ts` to see how it works) */ import { CodeDetails } from '@cosmjs/cosmwasm-stargate'; +import { AccessConfig, AccessType } from "cosmjs-types/cosmwasm/wasm/v1/types"; import { Command } from 'commander'; import * as fs from 'fs'; @@ -35,6 +36,7 @@ uploadCmd .command('local') .description('upload artifacts from local') .option('-a --artifacts ', 'artifacts', defaultArtifactPath) + .option('--set-instantiate-admin', 'Sets instantiate permissions to be admin address only') .action(async (_, cmd) => upload(cmd.optsWithGlobals())); uploadCmd @@ -42,6 +44,7 @@ uploadCmd .description('upload artifacts from remote') .argument('', `name of release tag. min: ${REMOTE_MIN_VERSION}`) .option('-o --out ', 'artifact output directory', defaultTmpDir) + .option('--set-instantiate-admin', 'Sets instantiate permissions to be admin address only') .action(handleRemote); uploadCmd @@ -58,7 +61,7 @@ async function handleRemote( _: object, cmd: Command, ): Promise { - const opts = cmd.optsWithGlobals() as { networkId: string; out: string }; + const opts = cmd.optsWithGlobals() as { networkId: string; out: string; setInstantiateAdmin?: boolean }; if (tagName < REMOTE_MIN_VERSION) throw new Error(`${tagName} < ${REMOTE_MIN_VERSION}`); @@ -77,7 +80,7 @@ async function handleRemote( console.log('Downloaded artifacts to', artifactPath.green); - return upload({ ...opts, artifacts: artifactPath }); + return upload({ ...opts, artifacts: artifactPath, setInstantiateAdmin: opts.setInstantiateAdmin }); } async function handleRemoteList() { @@ -96,12 +99,14 @@ type UploadArgs = { artifacts: string; contracts?: ContractNames[]; networkId: string; + setInstantiateAdmin?: boolean; }; async function upload({ artifacts: artifactPath, contracts: uploadTargets, networkId, + setInstantiateAdmin = false, }: UploadArgs) { (uploadTargets || []).forEach((v) => { if (!contractNames.includes(v)) @@ -171,12 +176,20 @@ async function upload({ } console.log('Proceeding to upload...'); + const restrictedInstantiationPermissions: AccessConfig = { + permission: AccessType.ACCESS_TYPE_ANY_OF_ADDRESSES, + address: "", // + addresses: [client.signer] + }; + let okCount = 0; for (const diff of listDiff) { const upload = await client.wasm.upload( client.signer, fs.readFileSync(getWasmPath(diff, { artifactPath })), 'auto', + undefined, + setInstantiateAdmin ? restrictedInstantiationPermissions : undefined, ); const receipt = await waitTx(upload.transactionHash, client.stargate); diff --git a/script/commands/wallet.ts b/script/commands/wallet.ts index b05d49bb..e6ed6617 100644 --- a/script/commands/wallet.ts +++ b/script/commands/wallet.ts @@ -7,6 +7,7 @@ import { Command } from 'commander'; import { getKeyPair } from '../shared/crypto'; import { CONTAINER, Dependencies } from '../shared/ioc'; +import { addPad, extractByte32AddrFromBech32 } from '../shared/utils'; const walletCmd = new Command('wallet') .description('Wallet commands') @@ -68,4 +69,16 @@ walletCmd console.log(account.address); }); +walletCmd + .command('zero-pad') + .argument('address', 'eth address of length 20 bytes') + .description('zero pads an ETH address to length 64 bytes') + .action((address: string) => console.log(`0x${addPad(address)}`)); + +walletCmd + .command('convert-cosmos-to-eth') + .argument('address', 'converts a bech32 cosmos address to a 64 byte length eth address') + .description('zero pads an ETH address to length 64 bytes') + .action((address: string) => console.log(`0x${extractByte32AddrFromBech32(address)}`)); + export { walletCmd }; diff --git a/script/commands/warp.ts b/script/commands/warp.ts index 6be742c8..a232b030 100644 --- a/script/commands/warp.ts +++ b/script/commands/warp.ts @@ -19,6 +19,12 @@ warpCmd .command('create') .description('Create a new warp route') .argument('', 'path to the warp route config file') + .addOption( + new Option( + '--ism ', + 'ISM to set on warp route (in bech32 format)', + ) + ) .action(handleCreate); warpCmd @@ -75,6 +81,24 @@ warpCmd 'target domain id to link', ).makeOptionMandatory(), ) + .addOption( + new Option( + '--amount ', + 'amount to send', + ) + ) + .addOption( + new Option( + '--bridged-denom ', + 'denom to transfer' + ) + ) + .addOption( + new Option( + '--fee-denom ', + 'fee denom' + ) + ) .action(handleTransfer); export { warpCmd }; @@ -90,7 +114,12 @@ function checkConfigType< return config.type === tokenType && config.mode === tokenMode; } -async function handleCreate(configFile: string) { +async function handleCreate(configFile: string, _: object, cmd: Command) { + type Option = { + ismAddress?: `0x{string}`; + }; + + const opts: Option = cmd.optsWithGlobals(); const deps = CONTAINER.get(Dependencies); const warpConfigFile = readFileSync(configFile, 'utf-8'); @@ -113,6 +142,13 @@ async function handleCreate(configFile: string) { cw20: [], }; + type WarpContract = { + type: string, + address: string, + hexed: string + } + let newWarp: WarpContract; + switch (warpType) { case 'native': { if (!checkConfigType(warpConfig, 'native', mode)) @@ -128,6 +164,7 @@ async function handleCreate(configFile: string) { id: warpConfig.id, ...nativeWarp, }); + newWarp = nativeWarp; break; } case 'cw20': { @@ -144,10 +181,27 @@ async function handleCreate(configFile: string) { id: warpConfig.id, ...cw20Warp, }); + newWarp = cw20Warp; break; } } + if (opts.ismAddress) { + console.log(`Setting ISM address to ${opts.ismAddress}`) + const response = await executeContract( + deps.client, + newWarp, + { + connection: { + set_ism: { + ism: opts.ismAddress + } + } + } + ); + console.log(`Code: ${response.code}, Hash: ${response.hash}`); + } + saveContext(deps.network.id, deps.ctx); } @@ -206,6 +260,9 @@ async function handleTransfer(_: object, cmd: Command) { assetType: 'native' | 'cw20'; assetId: string; targetDomain: string; + amount?: number; + bridgedDenom?: string; + feeDenom?: string; }; const opts: Option = cmd.optsWithGlobals(); @@ -243,9 +300,18 @@ async function handleTransfer(_: object, cmd: Command) { transfer_remote: { dest_domain: parseInt(opts.targetDomain), recipient: addPad(deps.client.signer_addr), - amount: `${1_000_000n}`, + amount: opts.amount ? `${opts.amount}` : `${1_000_000n}`, }, }, - [{ amount: `${1_000_001n}`, denom: 'uosmo' }], + [ + { + amount: opts.amount ? `${opts.amount}` : `${1_000_000n}`, + denom: opts.bridgedDenom || 'uosmo' + }, + { + amount: `${50n}`, + denom: opts.feeDenom || 'uosmo' + }, + ], ); } diff --git a/script/deploy/igp.ts b/script/deploy/igp.ts index a034fa01..a914117d 100644 --- a/script/deploy/igp.ts +++ b/script/deploy/igp.ts @@ -25,33 +25,36 @@ export const deployIgp = async ( owner: client.signer, }); - await executeMultiMsg(client, [ - { - contract: igpOracle, - msg: { - set_remote_gas_data_configs: { - configs: Object.entries(igpType.configs).map(([domain, v]) => ({ - remote_domain: Number(domain), - token_exchange_rate: v.exchange_rate.toString(), - gas_price: v.gas_price.toString(), - })), - }, - }, - }, - { - contract: igp, - msg: { - router: { - set_routes: { - set: Object.keys(igpType.configs).map((domain) => ({ - domain: Number(domain), - route: igpOracle.address, + if (igpType.configs != undefined) { + await executeMultiMsg(client, [ + { + contract: igpOracle, + msg: { + set_remote_gas_data_configs: { + configs: Object.entries(igpType.configs).map(([domain, v]) => ({ + remote_domain: Number(domain), + token_exchange_rate: v.exchange_rate.toString(), + gas_price: v.gas_price.toString(), })), }, }, }, - }, - ]); + { + contract: igp, + msg: { + router: { + set_routes: { + set: Object.keys(igpType.configs).map((domain) => ({ + domain: Number(domain), + route: igpOracle.address, + })), + }, + }, + }, + } + ], + ); + }; return { ...igp, oracle: igpOracle }; }; diff --git a/script/deploy/ism.ts b/script/deploy/ism.ts index ea12efa3..3c9bff92 100644 --- a/script/deploy/ism.ts +++ b/script/deploy/ism.ts @@ -41,6 +41,7 @@ export async function deployIsm( owner: ism.owner === '' ? client.signer : ism.owner, }); + if (ism.validators != undefined) { await executeMultiMsg( client, Object.entries(ism.validators).map( @@ -58,6 +59,7 @@ export async function deployIsm( }), ), ); + }; return multisig; } diff --git a/script/index.ts b/script/index.ts index f2d57140..febaffe1 100644 --- a/script/index.ts +++ b/script/index.ts @@ -6,6 +6,7 @@ import { version } from '../package.json'; import { contextCmd, contractCmd, + evmCmd, deployCmd, migrateCmd, uploadCmd, @@ -15,6 +16,7 @@ import { import { config, getNetwork, getSigningClient } from './shared/config'; import { loadContext } from './shared/context'; import { CONTAINER, Dependencies } from './shared/ioc'; +import { updateCmd } from './commands/update'; colors.enable(); @@ -38,9 +40,11 @@ cli.addCommand(contextCmd); cli.addCommand(contractCmd); cli.addCommand(deployCmd); cli.addCommand(migrateCmd); +cli.addCommand(updateCmd); cli.addCommand(uploadCmd); cli.addCommand(walletCmd); cli.addCommand(warpCmd); +cli.addCommand(evmCmd); cli.parseAsync(process.argv).catch(console.error); diff --git a/script/shared/config.ts b/script/shared/config.ts index 11a018fe..c283ec63 100644 --- a/script/shared/config.ts +++ b/script/shared/config.ts @@ -116,6 +116,20 @@ export type Config = { tm_version?: '34' | '37' | '38'; }[]; + evm_networks: { + name: string; + chain_id: number; + rpc_endpoint: string; + network: string; + native_currency: { + name: string; + symbol: string; + decimals: number; + }; + mailbox_address: `0x${string}`; + multisig_ism_factory_address: `0x${string}`; + }[]; + signer: string; deploy: { @@ -146,6 +160,13 @@ export const getNetwork = (networkId: string): Config['networks'][number] => { export const config = yaml.load(readFileSync(path, 'utf-8')) as Config; +export const getEvmNetwork = (networkName: string): Config['evm_networks'][number] => { + const ret = config.evm_networks.find((v) => v.name === networkName); + if (!ret) + throw new Error(`EVM Network ${networkName} not found in the config file`); + return ret; +} + export async function getSigningClient( networkId: string, { signer }: Config, diff --git a/script/shared/context.ts b/script/shared/context.ts index 0410bc0d..c90ee614 100644 --- a/script/shared/context.ts +++ b/script/shared/context.ts @@ -4,7 +4,7 @@ import path from 'path'; import { defaultContextPath } from './constants'; import { ContractNames } from './contract'; -type typed = { +export type typed = { type: T; address: string; hexed: string;