From 528a19022be131efb5dba6b6fa9e3f52372adbec Mon Sep 17 00:00:00 2001 From: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:24:51 +0000 Subject: [PATCH] Deploy eclipse testnet (#3172) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --------- Co-authored-by: Trevor Porter --- rust/Cargo.lock | 1 + rust/config/testnet4_config.json | 46 ++ rust/sealevel/.gitignore | 3 +- rust/sealevel/client/Cargo.toml | 1 + rust/sealevel/client/src/artifacts.rs | 11 +- rust/sealevel/client/src/context.rs | 19 +- rust/sealevel/client/src/core.rs | 3 +- rust/sealevel/client/src/helloworld.rs | 25 +- rust/sealevel/client/src/igp.rs | 610 ++++++++++++++++++ rust/sealevel/client/src/main.rs | 373 ++++------- rust/sealevel/client/src/multisig_ism.rs | 6 +- rust/sealevel/client/src/router.rs | 67 +- rust/sealevel/client/src/serde.rs | 2 +- rust/sealevel/client/src/warp_route.rs | 27 +- .../environments/testnet4/chain-config.json | 465 +++++++++++++ .../eclipsetestnet/core/program-ids.json | 8 + .../testnet4/gas-oracle-configs.json | 29 + .../eclipsetestnet/default/igp-accounts.json | 5 + .../solanatestnet/default/igp-accounts.json | 5 + .../hyperlane/multisig-config.json | 117 ++++ .../hyperlane/multisig-config.json | 117 ++++ .../solanatestnet/core/program-ids.json | 8 + .../eclipsetestnetsol/program-ids.json | 10 + .../eclipsetestnetsol/token-config.json | 14 + .../hyperlane-sealevel-igp/src/instruction.rs | 53 ++ .../config/environments/testnet4/agent.ts | 8 +- .../config/environments/testnet4/chains.ts | 5 +- .../environments/testnet4/gas-oracle.ts | 6 +- .../environments/testnet4/validators.ts | 41 +- typescript/sdk/src/consts/chainMetadata.ts | 28 + typescript/sdk/src/consts/chains.ts | 4 + typescript/sdk/src/consts/multisigIsm.ts | 10 + 32 files changed, 1814 insertions(+), 313 deletions(-) create mode 100644 rust/sealevel/client/src/igp.rs create mode 100644 rust/sealevel/environments/testnet4/chain-config.json create mode 100644 rust/sealevel/environments/testnet4/eclipsetestnet/core/program-ids.json create mode 100644 rust/sealevel/environments/testnet4/gas-oracle-configs.json create mode 100644 rust/sealevel/environments/testnet4/igp/eclipsetestnet/default/igp-accounts.json create mode 100644 rust/sealevel/environments/testnet4/igp/solanatestnet/default/igp-accounts.json create mode 100644 rust/sealevel/environments/testnet4/multisig-ism-message-id/eclipsetestnet/hyperlane/multisig-config.json create mode 100644 rust/sealevel/environments/testnet4/multisig-ism-message-id/solanatestnet/hyperlane/multisig-config.json create mode 100644 rust/sealevel/environments/testnet4/solanatestnet/core/program-ids.json create mode 100644 rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/program-ids.json create mode 100644 rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/token-config.json diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 0aeaa30cc..fd566bc0b 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -4314,6 +4314,7 @@ dependencies = [ "borsh 0.9.3", "bs58 0.5.0", "clap 4.4.11", + "ethers", "hex 0.4.3", "hyperlane-core", "hyperlane-sealevel-connection-client", diff --git a/rust/config/testnet4_config.json b/rust/config/testnet4_config.json index d0fbd1259..9fd537ab6 100644 --- a/rust/config/testnet4_config.json +++ b/rust/config/testnet4_config.json @@ -1007,6 +1007,52 @@ "index": { "from": 4558491 } + }, + "solanatestnet": { + "name": "solanatestnet", + "chainId": 1399811150, + "domainId": 1399811150, + "mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy", + "validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3", + "protocol": "sealevel", + "blocks": { + "reorgPeriod": 0, + "confirmations": 0 + }, + "rpcUrls": [ + { + "http": "https://api.testnet.solana.com" + } + ], + "index": { + "from": 1, + "mode": "sequence" + } + }, + "eclipsetestnet": { + "name": "eclipsetestnet", + "chainId": 239092742, + "domainId": 239092742, + "mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy", + "validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3", + "protocol": "sealevel", + "blocks": { + "reorgPeriod": 0, + "confirmations": 0 + }, + "rpcUrls": [ + { + "http": "https://testnet.dev2.eclipsenetwork.xyz" + } + ], + "index": { + "from": 1, + "mode": "sequence" + } } }, "defaultRpcConsensusType": "fallback" diff --git a/rust/sealevel/.gitignore b/rust/sealevel/.gitignore index b21c3b013..b8a7e200a 100644 --- a/rust/sealevel/.gitignore +++ b/rust/sealevel/.gitignore @@ -1,2 +1,3 @@ /target -environments/**/deploy-logs.txt \ No newline at end of file +environments/**/deploy-logs.txt +**/**/keys \ No newline at end of file diff --git a/rust/sealevel/client/Cargo.toml b/rust/sealevel/client/Cargo.toml index 976bf1163..8c14541fa 100644 --- a/rust/sealevel/client/Cargo.toml +++ b/rust/sealevel/client/Cargo.toml @@ -10,6 +10,7 @@ borsh.workspace = true bs58.workspace = true bincode.workspace = true clap = { workspace = true, features = ["derive"] } +ethers.workspace = true hex.workspace = true pretty_env_logger.workspace = true serde.workspace = true diff --git a/rust/sealevel/client/src/artifacts.rs b/rust/sealevel/client/src/artifacts.rs index 7caf4806f..d6e39beee 100644 --- a/rust/sealevel/client/src/artifacts.rs +++ b/rust/sealevel/client/src/artifacts.rs @@ -54,6 +54,13 @@ pub(crate) fn read_json(path: &Path) -> T where T: DeserializeOwned, { - let file = File::open(path).expect("Failed to open JSON file"); - serde_json::from_reader(file).expect("Failed to read JSON file") + try_read_json(path).expect("Failed to read JSON from file") +} + +pub(crate) fn try_read_json(path: &Path) -> std::io::Result +where + T: DeserializeOwned, +{ + let file = File::open(path)?; + Ok(serde_json::from_reader(file)?) } diff --git a/rust/sealevel/client/src/context.rs b/rust/sealevel/client/src/context.rs index 80b0ea9e2..5cb9ba383 100644 --- a/rust/sealevel/client/src/context.rs +++ b/rust/sealevel/client/src/context.rs @@ -239,14 +239,17 @@ impl<'ctx, 'rpc> TxnBuilder<'ctx, 'rpc> { fn wait_for_user_confirmation() { println!("Continue? [y/n] then press Enter"); let mut input = [0u8; 1]; - std::io::stdin().read_exact(&mut input).unwrap(); - match input[0] { - b'y' => { - println!("Continuing..."); + loop { + std::io::stdin().read_exact(&mut input).unwrap(); + match input[0] { + b'y' => { + println!("Continuing..."); + break; + } + b'n' => { + panic!("User requested exit"); + } + _ => {} } - b'n' => { - panic!("User requested exit"); - } - _ => {} } } diff --git a/rust/sealevel/client/src/core.rs b/rust/sealevel/client/src/core.rs index 8aaf51adb..783fd3a5f 100644 --- a/rust/sealevel/client/src/core.rs +++ b/rust/sealevel/client/src/core.rs @@ -18,7 +18,8 @@ use hyperlane_sealevel_igp::accounts::{SOL_DECIMALS, TOKEN_EXCHANGE_RATE_SCALE}; pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) { match cmd.cmd { CoreSubCmd::Deploy(core) => { - let environments_dir = create_new_directory(&core.environments_dir, &core.environment); + let environments_dir = + create_new_directory(&core.env_args.environments_dir, &core.env_args.environment); let chain_dir = create_new_directory(&environments_dir, &core.chain); let core_dir = create_new_directory(&chain_dir, "core"); let key_dir = create_new_directory(&core_dir, "keys"); diff --git a/rust/sealevel/client/src/helloworld.rs b/rust/sealevel/client/src/helloworld.rs index 6226b4828..999e623f7 100644 --- a/rust/sealevel/client/src/helloworld.rs +++ b/rust/sealevel/client/src/helloworld.rs @@ -10,6 +10,7 @@ use hyperlane_sealevel_hello_world::{ }, program_storage_pda_seeds, }; +use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType; use serde::{Deserialize, Serialize}; use solana_sdk::{instruction::Instruction, pubkey::Pubkey}; @@ -180,6 +181,26 @@ impl ConnectionClient for HelloWorldDeployer { set_interchain_security_module_instruction(*program_id, storage.owner.unwrap(), ism) .unwrap() } + + fn get_interchain_gas_paymaster( + &self, + client: &RpcClient, + program_id: &Pubkey, + ) -> Option<(Pubkey, InterchainGasPaymasterType)> { + let storage = self.get_storage(client, program_id); + + storage.igp + } + + fn set_interchain_gas_paymaster_instruction( + &self, + _client: &RpcClient, + _program_id: &Pubkey, + _igp_config: Option<(Pubkey, InterchainGasPaymasterType)>, + ) -> Option { + // There is no way to set the IGP on HelloWorld + None + } } fn deploy_helloworld(ctx: &mut Context, deploy: HelloWorldDeploy) { @@ -190,8 +211,8 @@ fn deploy_helloworld(ctx: &mut Context, deploy: HelloWorldDeploy) { &deploy.context, deploy.config_file, deploy.chain_config_file, - deploy.environments_dir, - &deploy.environment, + deploy.env_args.environments_dir, + &deploy.env_args.environment, deploy.built_so_dir, ) } diff --git a/rust/sealevel/client/src/igp.rs b/rust/sealevel/client/src/igp.rs new file mode 100644 index 000000000..f41ddad2f --- /dev/null +++ b/rust/sealevel/client/src/igp.rs @@ -0,0 +1,610 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use crate::{ + artifacts::{read_json, try_read_json, write_json, SingularProgramIdArtifact}, + cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program}, + read_core_program_ids, + router::ChainMetadata, + Context, GasOverheadSubCmd, GetSetCmd, IgpCmd, IgpSubCmd, +}; + +use std::{ + fs::File, + path::{Path, PathBuf}, + str::FromStr, +}; + +use solana_sdk::{ + pubkey::Pubkey, + signature::{Keypair, Signer as _}, +}; + +use hyperlane_core::H256; + +use hyperlane_sealevel_igp::{ + accounts::{ + GasOracle, GasPaymentAccount, IgpAccount, InterchainGasPaymasterType, OverheadIgpAccount, + ProgramDataAccount as IgpProgramDataAccount, RemoteGasData, + }, + igp_program_data_pda_seeds, + instruction::{GasOracleConfig, GasOverheadConfig}, +}; + +#[derive(Debug, Serialize, Deserialize, Default)] +struct IgpAccountsArtifacts { + salt: H256, + #[serde(default)] + #[serde(with = "crate::serde::serde_option_pubkey")] + igp_account: Option, + #[serde(default)] + #[serde(with = "crate::serde::serde_option_pubkey")] + overhead_igp_account: Option, +} + +fn get_context_salt(context: Option<&String>) -> H256 { + context + .map(|c| { + if c == "default" { + H256::zero() + } else { + ethers::utils::keccak256(c.as_bytes()).into() + } + }) + .unwrap_or_else(H256::zero) +} + +fn get_context_dir_name(context: Option<&String>) -> &str { + context.map(|c| c.as_str()).unwrap_or("default") +} + +pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { + match cmd.cmd { + IgpSubCmd::DeployProgram(deploy) => { + let environments_dir = create_new_directory( + &deploy.env_args.environments_dir, + &deploy.env_args.environment, + ); + let ism_dir = create_new_directory(&environments_dir, "igp"); + let chain_dir = create_new_directory(&ism_dir, &deploy.chain); + let key_dir = create_new_directory(&chain_dir, "keys"); + + let program_id = deploy_igp_program(&mut ctx, &deploy.built_so_dir, true, &key_dir); + + write_json::( + &chain_dir.join("program-ids.json"), + program_id.into(), + ); + } + IgpSubCmd::InitIgpAccount(init) => { + let environments_dir = + create_new_directory(&init.env_args.environments_dir, &init.env_args.environment); + let ism_dir = create_new_directory(&environments_dir, "igp"); + let chain_dir = create_new_directory(&ism_dir, &init.chain); + let context_dir = + create_new_directory(&chain_dir, get_context_dir_name(init.context.as_ref())); + + let artifacts_path = context_dir.join("igp-accounts.json"); + + let existing_artifacts = try_read_json::(&artifacts_path).ok(); + + let salt = get_context_salt(init.context.as_ref()); + + let chain_configs = + read_json::>(&init.chain_config_file); + + let igp_account = init_and_configure_igp_account( + &mut ctx, + init.program_id, + chain_configs.get(&init.chain).unwrap().domain_id(), + salt, + init.gas_oracle_config_file, + ); + + let artifacts = IgpAccountsArtifacts { + salt, + igp_account: Some(igp_account), + overhead_igp_account: existing_artifacts.and_then(|a| a.overhead_igp_account), + }; + + write_json(&artifacts_path, artifacts); + } + IgpSubCmd::InitOverheadIgpAccount(init) => { + let environments_dir = + create_new_directory(&init.env_args.environments_dir, &init.env_args.environment); + let ism_dir = create_new_directory(&environments_dir, "igp"); + let chain_dir = create_new_directory(&ism_dir, &init.chain); + let context_dir = + create_new_directory(&chain_dir, get_context_dir_name(init.context.as_ref())); + + let artifacts_path = context_dir.join("igp-accounts.json"); + + let existing_artifacts = try_read_json::(&artifacts_path).ok(); + + let salt = get_context_salt(init.context.as_ref()); + + let chain_configs = + read_json::>(&init.chain_config_file); + + let overhead_igp_account = init_and_configure_overhead_igp_account( + &mut ctx, + init.program_id, + init.inner_igp_account, + chain_configs.get(&init.chain).unwrap().domain_id(), + salt, + init.overhead_config_file, + ); + + let artifacts = IgpAccountsArtifacts { + salt, + igp_account: existing_artifacts.and_then(|a| a.igp_account), + overhead_igp_account: Some(overhead_igp_account), + }; + + write_json(&artifacts_path, artifacts); + } + IgpSubCmd::Query(query) => { + let (program_data_account_pda, _program_data_account_bump) = + Pubkey::find_program_address(igp_program_data_pda_seeds!(), &query.program_id); + + let accounts = ctx + .client + .get_multiple_accounts_with_commitment( + &[program_data_account_pda, query.igp_account], + ctx.commitment, + ) + .unwrap() + .value; + + let igp_program_data = + IgpProgramDataAccount::fetch(&mut &accounts[0].as_ref().unwrap().data[..]) + .unwrap() + .into_inner(); + + println!("IGP program data: {:?}", igp_program_data); + + let igp = IgpAccount::fetch(&mut &accounts[1].as_ref().unwrap().data[..]) + .unwrap() + .into_inner(); + + println!("IGP account: {:?}", igp); + + if let Some(gas_payment_account_pubkey) = query.gas_payment_account { + let account = ctx + .client + .get_account_with_commitment(&gas_payment_account_pubkey, ctx.commitment) + .unwrap() + .value + .unwrap(); + let gas_payment_account = GasPaymentAccount::fetch(&mut &account.data[..]) + .unwrap() + .into_inner(); + println!("Gas payment account: {:?}", gas_payment_account); + } + } + IgpSubCmd::PayForGas(payment_details) => { + let unique_gas_payment_keypair = Keypair::new(); + let salt = H256::zero(); + let (igp_account, _igp_account_bump) = Pubkey::find_program_address( + hyperlane_sealevel_igp::igp_pda_seeds!(salt), + &payment_details.program_id, + ); + + let (overhead_igp_account, _) = Pubkey::find_program_address( + hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt), + &payment_details.program_id, + ); + let (ixn, gas_payment_data_account) = + hyperlane_sealevel_igp::instruction::pay_for_gas_instruction( + payment_details.program_id, + ctx.payer_pubkey, + igp_account, + Some(overhead_igp_account), + unique_gas_payment_keypair.pubkey(), + H256::from_str(&payment_details.message_id).unwrap(), + payment_details.destination_domain, + payment_details.gas, + ) + .unwrap(); + + ctx.new_txn() + .add(ixn) + .send(&[&*ctx.payer_signer(), &unique_gas_payment_keypair]); + + println!( + "Made a payment for message {} with gas payment data account {}", + payment_details.message_id, gas_payment_data_account + ); + } + IgpSubCmd::Claim(claim) => { + let igp_account = ctx + .client + .get_account_with_commitment(&claim.igp_account, ctx.commitment) + .unwrap() + .value + .unwrap(); + let igp_account = IgpAccount::fetch(&mut &igp_account.data[..]) + .unwrap() + .into_inner(); + + let ixn = hyperlane_sealevel_igp::instruction::claim_instruction( + claim.program_id, + claim.igp_account, + igp_account.beneficiary, + ) + .unwrap(); + + ctx.new_txn() + .add_with_description( + ixn, + format!( + "Claiming from IGP account {} to beneficiary {}", + claim.igp_account, igp_account.beneficiary + ), + ) + .send_with_payer(); + } + IgpSubCmd::SetIgpBeneficiary(set_beneficiary) => { + let igp_account = ctx + .client + .get_account_with_commitment(&set_beneficiary.igp_account, ctx.commitment) + .unwrap() + .value + .unwrap(); + let igp_account = IgpAccount::fetch(&mut &igp_account.data[..]) + .unwrap() + .into_inner(); + + let ixn = hyperlane_sealevel_igp::instruction::set_beneficiary_instruction( + set_beneficiary.program_id, + set_beneficiary.igp_account, + igp_account.owner.unwrap(), + set_beneficiary.new_beneficiary, + ) + .unwrap(); + + ctx.new_txn() + .add_with_description( + ixn, + format!( + "Change beneficiary of IGP account {} to beneficiary {}", + set_beneficiary.igp_account, set_beneficiary.new_beneficiary + ), + ) + .send_with_payer(); + } + IgpSubCmd::GasOracleConfig(args) => { + let core_program_ids = read_core_program_ids( + &args.env_args.environments_dir, + &args.env_args.environment, + &args.chain_name, + ); + match args.cmd { + GetSetCmd::Set(set_args) => { + let remote_gas_data = RemoteGasData { + token_exchange_rate: set_args.token_exchange_rate, + gas_price: set_args.gas_price, + token_decimals: set_args.token_decimals, + }; + let gas_oracle_config = GasOracleConfig { + domain: args.remote_domain, + gas_oracle: Some(GasOracle::RemoteGasData(remote_gas_data)), + }; + let instruction = + hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( + core_program_ids.igp_program_id, + core_program_ids.igp_account, + ctx.payer_pubkey, + vec![gas_oracle_config], + ) + .unwrap(); + ctx.new_txn().add(instruction).send_with_payer(); + println!("Set gas oracle for remote domain {:?}", args.remote_domain); + } + GetSetCmd::Get(_) => { + let igp_account = ctx + .client + .get_account_with_commitment(&core_program_ids.igp_account, ctx.commitment) + .unwrap() + .value + .expect( + "IGP account not found. Make sure you are connected to the right RPC.", + ); + + let igp_account = IgpAccount::fetch(&mut &igp_account.data[..]) + .unwrap() + .into_inner(); + + println!( + "IGP account gas oracle: {:#?}", + igp_account.gas_oracles.get(&args.remote_domain) + ); + } + } + } + IgpSubCmd::DestinationGasOverhead(args) => { + let core_program_ids = read_core_program_ids( + &args.env_args.environments_dir, + &args.env_args.environment, + &args.chain_name, + ); + match args.cmd { + GasOverheadSubCmd::Get => { + // Read the gas overhead config + let overhead_igp_account = ctx + .client + .get_account_with_commitment( + &core_program_ids.overhead_igp_account, + ctx.commitment, + ) + .unwrap() + .value + .expect("Overhead IGP account not found. Make sure you are connected to the right RPC."); + let overhead_igp_account = + OverheadIgpAccount::fetch(&mut &overhead_igp_account.data[..]) + .unwrap() + .into_inner(); + println!( + "Overhead IGP account gas oracle: {:#?}", + overhead_igp_account.gas_overheads.get(&args.remote_domain) + ); + } + GasOverheadSubCmd::Set(set_args) => { + let overhead_config = GasOverheadConfig { + destination_domain: args.remote_domain, + gas_overhead: Some(set_args.gas_overhead), + }; + // Set the gas overhead config + let instruction = + hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( + core_program_ids.igp_program_id, + core_program_ids.overhead_igp_account, + ctx.payer_pubkey, + vec![overhead_config], + ) + .unwrap(); + ctx.new_txn().add(instruction).send_with_payer(); + println!( + "Set gas overheads for remote domain {:?}", + args.remote_domain + ) + } + } + } + IgpSubCmd::TransferIgpOwnership(ref transfer_ownership) + | IgpSubCmd::TransferOverheadIgpOwnership(ref transfer_ownership) => { + let igp_account_type = match cmd.cmd { + IgpSubCmd::TransferIgpOwnership(_) => { + InterchainGasPaymasterType::Igp(transfer_ownership.igp_account) + } + IgpSubCmd::TransferOverheadIgpOwnership(_) => { + InterchainGasPaymasterType::OverheadIgp(transfer_ownership.igp_account) + } + _ => unreachable!(), + }; + let instruction = + hyperlane_sealevel_igp::instruction::transfer_igp_account_ownership_instruction( + transfer_ownership.program_id, + igp_account_type.clone(), + ctx.payer_pubkey, + Some(transfer_ownership.new_owner), + ) + .unwrap(); + ctx.new_txn() + .add_with_description( + instruction, + format!( + "Transfer ownership of {:?} to {}", + igp_account_type, transfer_ownership.new_owner + ), + ) + .send_with_payer(); + } + } +} + +#[allow(clippy::too_many_arguments)] +fn deploy_igp_program( + ctx: &mut Context, + built_so_dir: &Path, + use_existing_keys: bool, + key_dir: &Path, +) -> Pubkey { + let (keypair, keypair_path) = create_and_write_keypair( + key_dir, + "hyperlane_sealevel_igp-keypair.json", + use_existing_keys, + ); + let program_id = keypair.pubkey(); + + deploy_program( + ctx.payer_keypair_path(), + keypair_path.to_str().unwrap(), + built_so_dir + .join("hyperlane_sealevel_igp.so") + .to_str() + .unwrap(), + &ctx.client.url(), + ); + + println!("Deployed IGP at program ID {}", program_id); + + let (program_data_account, _program_data_bump) = Pubkey::find_program_address( + hyperlane_sealevel_igp::igp_program_data_pda_seeds!(), + &program_id, + ); + + // Initialize the program data + let instruction = + hyperlane_sealevel_igp::instruction::init_instruction(program_id, ctx.payer_pubkey) + .unwrap(); + + ctx.new_txn() + .add_with_description( + instruction, + format!("Initializing IGP program data {}", program_data_account), + ) + .send_with_payer(); + + program_id +} + +fn init_and_configure_igp_account( + ctx: &mut Context, + program_id: Pubkey, + local_domain: u32, + salt: H256, + gas_oracle_config_file: Option, +) -> Pubkey { + let gas_oracle_configs = gas_oracle_config_file + .as_deref() + .map(|p| { + let file = File::open(p).expect("Failed to open oracle config file"); + serde_json::from_reader::<_, Vec>(file) + .expect("Failed to parse oracle config file") + }) + .unwrap_or_default() + .into_iter() + .filter(|c| c.domain != local_domain) + .collect::>(); + + // Initialize IGP with the given salt + let (igp_account_pda, _igp_account_bump) = + Pubkey::find_program_address(hyperlane_sealevel_igp::igp_pda_seeds!(salt), &program_id); + + if ctx + .client + .get_account_with_commitment(&igp_account_pda, ctx.commitment) + .unwrap() + .value + .is_none() + { + let instruction = hyperlane_sealevel_igp::instruction::init_igp_instruction( + program_id, + ctx.payer_pubkey, + salt, + Some(ctx.payer_pubkey), + ctx.payer_pubkey, + ) + .unwrap(); + + ctx.new_txn() + .add_with_description( + instruction, + format!("Initializing IGP account {}", igp_account_pda), + ) + .send_with_payer(); + } else { + println!( + "IGP account {} already exists, not creating", + igp_account_pda + ); + } + + if !gas_oracle_configs.is_empty() { + // TODO: idempotency + + let domains = gas_oracle_configs + .iter() + .map(|c| c.domain) + .collect::>(); + let instruction = hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( + program_id, + igp_account_pda, + ctx.payer_pubkey, + gas_oracle_configs, + ) + .unwrap(); + + ctx.new_txn().add(instruction).send_with_payer(); + + println!("Set gas oracle for remote domains {domains:?}",); + } else { + println!("Skipping settings gas oracle config"); + } + + igp_account_pda +} + +fn init_and_configure_overhead_igp_account( + ctx: &mut Context, + program_id: Pubkey, + inner_igp_account: Pubkey, + local_domain: u32, + salt: H256, + overhead_config_file: Option, +) -> Pubkey { + let overhead_configs = overhead_config_file + .as_deref() + .map(|p| { + let file = File::open(p).expect("Failed to open overhead config file"); + serde_json::from_reader::<_, Vec>(file) + .expect("Failed to parse overhead config file") + }) + .unwrap_or_default() + .into_iter() + .filter(|c| c.destination_domain != local_domain) + .map(|c| (c.destination_domain, c)) + .collect::>() // dedup + .into_values() + .collect::>(); + + let (overhead_igp_account, _) = Pubkey::find_program_address( + hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt), + &program_id, + ); + + if ctx + .client + .get_account_with_commitment(&overhead_igp_account, ctx.commitment) + .unwrap() + .value + .is_none() + { + let instruction = hyperlane_sealevel_igp::instruction::init_overhead_igp_instruction( + program_id, + ctx.payer_pubkey, + salt, + Some(ctx.payer_pubkey), + inner_igp_account, + ) + .unwrap(); + + ctx.new_txn() + .add_with_description( + instruction, + format!("Initializing overhead IGP account {}", overhead_igp_account), + ) + .send_with_payer(); + } else { + println!( + "Overhead IGP account {} already exists, not creating", + overhead_igp_account + ); + } + + if !overhead_configs.is_empty() { + // TODO: idempotency + + let domains = overhead_configs + .iter() + .map(|c| c.destination_domain) + .collect::>(); + + let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( + program_id, + overhead_igp_account, + ctx.payer_pubkey, + overhead_configs, + ) + .unwrap(); + + ctx.new_txn().add(instruction).send_with_payer(); + + println!("Set gas overheads for remote domains {domains:?}",) + } else { + println!("Skipping setting gas overheads"); + } + + overhead_igp_account +} diff --git a/rust/sealevel/client/src/main.rs b/rust/sealevel/client/src/main.rs index 88e75c7f4..74e3de720 100644 --- a/rust/sealevel/client/src/main.rs +++ b/rust/sealevel/client/src/main.rs @@ -23,12 +23,8 @@ use account_utils::DiscriminatorEncode; use hyperlane_core::{H160, H256}; use hyperlane_sealevel_connection_client::router::RemoteRouterConfig; use hyperlane_sealevel_igp::{ - accounts::{ - GasOracle, GasPaymentAccount, IgpAccount, InterchainGasPaymasterType, OverheadIgpAccount, - ProgramDataAccount as IgpProgramDataAccount, RemoteGasData, - }, + accounts::{InterchainGasPaymasterType, OverheadIgpAccount}, igp_gas_payment_pda_seeds, igp_program_data_pda_seeds, - instruction::{GasOracleConfig, GasOverheadConfig}, }; use hyperlane_sealevel_mailbox::{ accounts::{InboxAccount, OutboxAccount}, @@ -67,12 +63,14 @@ mod cmd_utils; mod context; mod r#core; mod helloworld; +mod igp; mod multisig_ism; mod router; mod serde; mod warp_route; use crate::helloworld::process_helloworld_cmd; +use crate::igp::process_igp_cmd; use crate::multisig_ism::process_multisig_ism_message_id_cmd; use crate::warp_route::process_warp_route_cmd; pub(crate) use crate::{context::*, core::*}; @@ -115,6 +113,14 @@ enum HyperlaneSealevelCmd { HelloWorld(HelloWorldCmd), } +#[derive(Args)] +struct EnvironmentArgs { + #[arg(long)] + environment: String, + #[arg(long)] + environments_dir: PathBuf, +} + #[derive(Args)] pub(crate) struct WarpRouteCmd { #[command(subcommand)] @@ -129,10 +135,8 @@ pub(crate) enum WarpRouteSubCmd { #[derive(Args)] pub(crate) struct WarpRouteDeploy { - #[arg(long)] - environment: String, - #[arg(long)] - environments_dir: PathBuf, + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] built_so_dir: PathBuf, #[arg(long)] @@ -168,8 +172,8 @@ enum CoreSubCmd { struct CoreDeploy { #[arg(long)] local_domain: u32, - #[arg(long)] - environment: String, + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] gas_oracle_config_file: Option, #[arg(long)] @@ -178,8 +182,6 @@ struct CoreDeploy { chain: String, #[arg(long)] use_existing_keys: bool, - #[arg(long)] - environments_dir: PathBuf, #[arg(long, num_args = 1.., value_delimiter = ',')] remote_domains: Vec, #[arg(long)] @@ -381,14 +383,63 @@ struct IgpCmd { #[derive(Subcommand)] enum IgpSubCmd { + DeployProgram(IgpDeployProgramArgs), + InitIgpAccount(InitIgpAccountArgs), + InitOverheadIgpAccount(InitOverheadIgpAccountArgs), Query(IgpQueryArgs), PayForGas(PayForGasArgs), + Claim(ClaimArgs), + SetIgpBeneficiary(SetIgpBeneficiaryArgs), GasOracleConfig(GasOracleConfigArgs), DestinationGasOverhead(DestinationGasOverheadArgs), TransferIgpOwnership(TransferIgpOwnership), TransferOverheadIgpOwnership(TransferIgpOwnership), } +#[derive(Args)] +struct IgpDeployProgramArgs { + #[command(flatten)] + env_args: EnvironmentArgs, + #[arg(long)] + chain: String, + #[arg(long)] + built_so_dir: PathBuf, +} + +#[derive(Args)] +struct InitIgpAccountArgs { + #[arg(long)] + program_id: Pubkey, + #[command(flatten)] + env_args: EnvironmentArgs, + #[arg(long)] + chain: String, + #[arg(long)] + chain_config_file: PathBuf, + #[arg(long)] + context: Option, + #[arg(long)] + gas_oracle_config_file: Option, +} + +#[derive(Args)] +struct InitOverheadIgpAccountArgs { + #[arg(long)] + program_id: Pubkey, + #[command(flatten)] + env_args: EnvironmentArgs, + #[arg(long)] + chain: String, + #[arg(long)] + chain_config_file: PathBuf, + #[arg(long)] + inner_igp_account: Pubkey, + #[arg(long)] + context: Option, + #[arg(long)] + overhead_config_file: Option, +} + #[derive(Args)] struct IgpQueryArgs { #[arg(long)] @@ -423,11 +474,26 @@ struct PayForGasArgs { } #[derive(Args)] -struct GasOracleConfigArgs { +struct ClaimArgs { #[arg(long)] - environment: String, + program_id: Pubkey, #[arg(long)] - environments_dir: PathBuf, + igp_account: Pubkey, +} + +#[derive(Args)] +struct SetIgpBeneficiaryArgs { + #[arg(long)] + program_id: Pubkey, + #[arg(long)] + igp_account: Pubkey, + new_beneficiary: Pubkey, +} + +#[derive(Args)] +struct GasOracleConfigArgs { + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] chain_name: String, #[arg(long)] @@ -451,10 +517,8 @@ struct GetGasOracleArgs; #[derive(Args)] struct DestinationGasOverheadArgs { - #[arg(long)] - environment: String, - #[arg(long)] - environments_dir: PathBuf, + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] chain_name: String, #[arg(long)] @@ -535,10 +599,8 @@ enum MultisigIsmMessageIdSubCmd { #[derive(Args)] struct MultisigIsmMessageIdDeploy { - #[arg(long)] - environment: String, - #[arg(long)] - environments_dir: PathBuf, + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] built_so_dir: PathBuf, #[arg(long)] @@ -597,10 +659,8 @@ pub(crate) enum HelloWorldSubCmd { #[derive(Args)] pub(crate) struct HelloWorldDeploy { - #[arg(long)] - environment: String, - #[arg(long)] - environments_dir: PathBuf, + #[command(flatten)] + env_args: EnvironmentArgs, #[arg(long)] built_so_dir: PathBuf, #[arg(long)] @@ -991,23 +1051,23 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) { // Burns the tokens from the sender's associated token account and // then dispatches a message to the remote recipient. // - // 0. `[executable]` The system program. - // 1. `[executable]` The spl_noop program. - // 2. `[]` The token PDA account. - // 3. `[executable]` The mailbox program. - // 4. `[writeable]` The mailbox outbox account. - // 5. `[]` Message dispatch authority. - // 6. `[signer]` The token sender and mailbox payer. - // 7. `[signer]` Unique message / gas payment account. - // 8. `[writeable]` Message storage PDA. + // 0. [executable] The system program. + // 1. [executable] The spl_noop program. + // 2. [] The token PDA account. + // 3. [executable] The mailbox program. + // 4. [writeable] The mailbox outbox account. + // 5. [] Message dispatch authority. + // 6. [signer] The token sender and mailbox payer. + // 7. [signer] Unique message / gas payment account. + // 8. [writeable] Message storage PDA. // ---- If using an IGP ---- - // 9. `[executable]` The IGP program. - // 10. `[writeable]` The IGP program data. - // 11. `[writeable]` Gas payment PDA. - // 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. - // 13. `[writeable]` The IGP account. + // 9. [executable] The IGP program. + // 10. [writeable] The IGP program data. + // 11. [writeable] Gas payment PDA. + // 12. [] OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. + // 13. [writeable] The IGP account. // ---- End if ---- - // 14..N `[??..??]` Plugin-specific accounts. + // 14..N [??..??] Plugin-specific accounts. let mut accounts = vec![ AccountMeta::new_readonly(system_program::id(), false), AccountMeta::new_readonly(spl_noop::id(), false), @@ -1059,8 +1119,8 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) { match xfer.token_type { TokenType::Native => { - // 5. `[executable]` The system program. - // 6. `[writeable]` The native token collateral PDA account. + // 5. [executable] The system program. + // 6. [writeable] The native token collateral PDA account. let (native_collateral_account, _native_collateral_bump) = Pubkey::find_program_address( hyperlane_token_native_collateral_pda_seeds!(), @@ -1072,9 +1132,9 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) { ]); } TokenType::Synthetic => { - // 5. `[executable]` The spl_token_2022 program. - // 6. `[writeable]` The mint / mint authority PDA account. - // 7. `[writeable]` The token sender's associated token account, from which tokens will be burned. + // 5. [executable] The spl_token_2022 program. + // 6. [writeable] The mint / mint authority PDA account. + // 7. [writeable] The token sender's associated token account, from which tokens will be burned. let (mint_account, _mint_bump) = Pubkey::find_program_address( hyperlane_token_mint_pda_seeds!(), &xfer.program_id, @@ -1092,10 +1152,10 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) { ]); } TokenType::Collateral => { - // 5. `[executable]` The SPL token program for the mint. - // 6. `[writeable]` The mint. - // 7. `[writeable]` The token sender's associated token account, from which tokens will be sent. - // 8. `[writeable]` The escrow PDA account. + // 5. [executable] The SPL token program for the mint. + // 6. [writeable] The mint. + // 7. [writeable] The token sender's associated token account, from which tokens will be sent. + // 8. [writeable] The escrow PDA account. let token = HyperlaneTokenAccount::::fetch( &mut &fetched_token_account.data[..], ) @@ -1266,11 +1326,11 @@ fn process_validator_announce_cmd(ctx: Context, cmd: ValidatorAnnounceCmd) { let ixn = ValidatorAnnounceInstruction::Announce(announce_instruction); // Accounts: - // 0. `[signer]` The payer. - // 1. `[executable]` The system program. - // 2. `[]` The ValidatorAnnounce PDA account. - // 3. `[writeable]` The validator-specific ValidatorStorageLocationsAccount PDA account. - // 4. `[writeable]` The ReplayProtection PDA account specific to the announcement being made. + // 0. [signer] The payer. + // 1. [executable] The system program. + // 2. [] The ValidatorAnnounce PDA account. + // 3. [writeable] The validator-specific ValidatorStorageLocationsAccount PDA account. + // 4. [writeable] The ReplayProtection PDA account specific to the announcement being made. let accounts = vec![ AccountMeta::new_readonly(ctx.payer_pubkey, true), AccountMeta::new_readonly(system_program::id(), false), @@ -1313,202 +1373,3 @@ fn process_validator_announce_cmd(ctx: Context, cmd: ValidatorAnnounceCmd) { } } } - -fn process_igp_cmd(ctx: Context, cmd: IgpCmd) { - match cmd.cmd { - IgpSubCmd::Query(query) => { - let (program_data_account_pda, _program_data_account_bump) = - Pubkey::find_program_address(igp_program_data_pda_seeds!(), &query.program_id); - - let accounts = ctx - .client - .get_multiple_accounts_with_commitment( - &[program_data_account_pda, query.igp_account], - ctx.commitment, - ) - .unwrap() - .value; - - let igp_program_data = - IgpProgramDataAccount::fetch(&mut &accounts[0].as_ref().unwrap().data[..]) - .unwrap() - .into_inner(); - - println!("IGP program data: {:?}", igp_program_data); - - let igp = IgpAccount::fetch(&mut &accounts[1].as_ref().unwrap().data[..]) - .unwrap() - .into_inner(); - - println!("IGP account: {:?}", igp); - - if let Some(gas_payment_account_pubkey) = query.gas_payment_account { - let account = ctx - .client - .get_account_with_commitment(&gas_payment_account_pubkey, ctx.commitment) - .unwrap() - .value - .unwrap(); - let gas_payment_account = GasPaymentAccount::fetch(&mut &account.data[..]) - .unwrap() - .into_inner(); - println!("Gas payment account: {:?}", gas_payment_account); - } - } - IgpSubCmd::PayForGas(payment_details) => { - let unique_gas_payment_keypair = Keypair::new(); - let salt = H256::zero(); - let (igp_account, _igp_account_bump) = Pubkey::find_program_address( - hyperlane_sealevel_igp::igp_pda_seeds!(salt), - &payment_details.program_id, - ); - - let (overhead_igp_account, _) = Pubkey::find_program_address( - hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt), - &payment_details.program_id, - ); - let (ixn, gas_payment_data_account) = - hyperlane_sealevel_igp::instruction::pay_for_gas_instruction( - payment_details.program_id, - ctx.payer_pubkey, - igp_account, - Some(overhead_igp_account), - unique_gas_payment_keypair.pubkey(), - H256::from_str(&payment_details.message_id).unwrap(), - payment_details.destination_domain, - payment_details.gas, - ) - .unwrap(); - - ctx.new_txn() - .add(ixn) - .send(&[&*ctx.payer_signer(), &unique_gas_payment_keypair]); - - println!( - "Made a payment for message {} with gas payment data account {}", - payment_details.message_id, gas_payment_data_account - ); - } - IgpSubCmd::GasOracleConfig(args) => { - let core_program_ids = - read_core_program_ids(&args.environments_dir, &args.environment, &args.chain_name); - match args.cmd { - GetSetCmd::Set(set_args) => { - let remote_gas_data = RemoteGasData { - token_exchange_rate: set_args.token_exchange_rate, - gas_price: set_args.gas_price, - token_decimals: set_args.token_decimals, - }; - let gas_oracle_config = GasOracleConfig { - domain: args.remote_domain, - gas_oracle: Some(GasOracle::RemoteGasData(remote_gas_data)), - }; - let instruction = - hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( - core_program_ids.igp_program_id, - core_program_ids.igp_account, - ctx.payer_pubkey, - vec![gas_oracle_config], - ) - .unwrap(); - ctx.new_txn().add(instruction).send_with_payer(); - println!("Set gas oracle for remote domain {:?}", args.remote_domain); - } - GetSetCmd::Get(_) => { - let igp_account = ctx - .client - .get_account_with_commitment(&core_program_ids.igp_account, ctx.commitment) - .unwrap() - .value - .expect( - "IGP account not found. Make sure you are connected to the right RPC.", - ); - - let igp_account = IgpAccount::fetch(&mut &igp_account.data[..]) - .unwrap() - .into_inner(); - - println!( - "IGP account gas oracle: {:#?}", - igp_account.gas_oracles.get(&args.remote_domain) - ); - } - } - } - IgpSubCmd::DestinationGasOverhead(args) => { - let core_program_ids = - read_core_program_ids(&args.environments_dir, &args.environment, &args.chain_name); - match args.cmd { - GasOverheadSubCmd::Get => { - // Read the gas overhead config - let overhead_igp_account = ctx - .client - .get_account_with_commitment( - &core_program_ids.overhead_igp_account, - ctx.commitment, - ) - .unwrap() - .value - .expect("Overhead IGP account not found. Make sure you are connected to the right RPC."); - let overhead_igp_account = - OverheadIgpAccount::fetch(&mut &overhead_igp_account.data[..]) - .unwrap() - .into_inner(); - println!( - "Overhead IGP account gas oracle: {:#?}", - overhead_igp_account.gas_overheads.get(&args.remote_domain) - ); - } - GasOverheadSubCmd::Set(set_args) => { - let overhead_config = GasOverheadConfig { - destination_domain: args.remote_domain, - gas_overhead: Some(set_args.gas_overhead), - }; - // Set the gas overhead config - let instruction = - hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( - core_program_ids.igp_program_id, - core_program_ids.overhead_igp_account, - ctx.payer_pubkey, - vec![overhead_config], - ) - .unwrap(); - ctx.new_txn().add(instruction).send_with_payer(); - println!( - "Set gas overheads for remote domain {:?}", - args.remote_domain - ) - } - } - } - IgpSubCmd::TransferIgpOwnership(ref transfer_ownership) - | IgpSubCmd::TransferOverheadIgpOwnership(ref transfer_ownership) => { - let igp_account_type = match cmd.cmd { - IgpSubCmd::TransferIgpOwnership(_) => { - InterchainGasPaymasterType::Igp(transfer_ownership.igp_account) - } - IgpSubCmd::TransferOverheadIgpOwnership(_) => { - InterchainGasPaymasterType::OverheadIgp(transfer_ownership.igp_account) - } - _ => unreachable!(), - }; - let instruction = - hyperlane_sealevel_igp::instruction::transfer_igp_account_ownership_instruction( - transfer_ownership.program_id, - igp_account_type.clone(), - ctx.payer_pubkey, - Some(transfer_ownership.new_owner), - ) - .unwrap(); - ctx.new_txn() - .add_with_description( - instruction, - format!( - "Transfer ownership of {:?} to {}", - igp_account_type, transfer_ownership.new_owner - ), - ) - .send_with_payer(); - } - } -} diff --git a/rust/sealevel/client/src/multisig_ism.rs b/rust/sealevel/client/src/multisig_ism.rs index d5676b281..e354f39bb 100644 --- a/rust/sealevel/client/src/multisig_ism.rs +++ b/rust/sealevel/client/src/multisig_ism.rs @@ -45,8 +45,10 @@ impl From for ValidatorsAndThreshold { pub(crate) fn process_multisig_ism_message_id_cmd(mut ctx: Context, cmd: MultisigIsmMessageIdCmd) { match cmd.cmd { MultisigIsmMessageIdSubCmd::Deploy(deploy) => { - let environments_dir = - create_new_directory(&deploy.environments_dir, &deploy.environment); + let environments_dir = create_new_directory( + &deploy.env_args.environments_dir, + &deploy.env_args.environment, + ); let ism_dir = create_new_directory(&environments_dir, "multisig-ism-message-id"); let chain_dir = create_new_directory(&ism_dir, &deploy.chain); let context_dir = create_new_directory(&chain_dir, &deploy.context); diff --git a/rust/sealevel/client/src/router.rs b/rust/sealevel/client/src/router.rs index 4eaf4cae7..ae20abc5b 100644 --- a/rust/sealevel/client/src/router.rs +++ b/rust/sealevel/client/src/router.rs @@ -268,6 +268,21 @@ pub(crate) trait ConnectionClient: Ownable { program_id: &Pubkey, ism: Option, ) -> Instruction; + + /// Gets the IGP configured on-chain. + fn get_interchain_gas_paymaster( + &self, + client: &RpcClient, + program_id: &Pubkey, + ) -> Option<(Pubkey, InterchainGasPaymasterType)>; + + /// Gets an instruction to set the IGP. + fn set_interchain_gas_paymaster_instruction( + &self, + client: &RpcClient, + program_id: &Pubkey, + igp_config: Option<(Pubkey, InterchainGasPaymasterType)>, + ) -> Option; } /// Idempotently deploys routers on multiple Sealevel chains and enrolls all routers (including @@ -426,8 +441,6 @@ fn configure_connection_client( router_config: &RouterConfig, chain_config: &ChainMetadata, ) { - // Just ISM for now - let client = chain_config.client(); let actual_ism = deployer.get_interchain_security_module(&client, program_id); @@ -451,6 +464,35 @@ fn configure_connection_client( .with_client(&client) .send_with_payer(); } + + let actual_igp = deployer.get_interchain_gas_paymaster(&client, program_id); + let expected_igp = router_config + .connection_client + .interchain_gas_paymaster_config(&client); + + if actual_igp != expected_igp { + let instruction = deployer.set_interchain_gas_paymaster_instruction( + &client, + program_id, + expected_igp.clone(), + ); + if let Some(instruction) = instruction { + ctx.new_txn() + .add_with_description( + instruction, + format!( + "Setting IGP for chain: {} ({}) to {:?}", + chain_config.name, + chain_config.domain_id(), + expected_igp + ), + ) + .with_client(&client) + .send_with_payer(); + } else { + println!("WARNING: Invalid configured IGP {:?}, expected {:?} for chain {} ({}), but cannot craft instruction to change it", actual_igp, expected_igp, chain_config.name, chain_config.domain_id()); + } + } } // Idempotent. @@ -541,17 +583,18 @@ fn enroll_all_remote_routers< .collect::>(); if !router_configs.is_empty() { - println!( - "Enrolling routers for chain: {}, program_id {}, routers: {:?}", - chain_name, program_id, router_configs, - ); - ctx.new_txn() - .add(deployer.enroll_remote_routers_instruction( - program_id, - ctx.payer_pubkey, - router_configs, - )) + .add_with_description( + deployer.enroll_remote_routers_instruction( + program_id, + ctx.payer_pubkey, + router_configs.clone(), + ), + format!( + "Enrolling routers for chain: {}, program_id {}, routers: {:?}", + chain_name, program_id, router_configs, + ), + ) .with_client(&chain_config.client()) .send_with_payer(); } else { diff --git a/rust/sealevel/client/src/serde.rs b/rust/sealevel/client/src/serde.rs index b4c412f09..b73a25bd5 100644 --- a/rust/sealevel/client/src/serde.rs +++ b/rust/sealevel/client/src/serde.rs @@ -24,7 +24,7 @@ pub(crate) mod serde_pubkey { } } -/// For serializing and deserializing `Option` +/// For serializing and deserializing Option pub(crate) mod serde_option_pubkey { use borsh::BorshDeserialize; use serde::{Deserialize, Deserializer, Serializer}; diff --git a/rust/sealevel/client/src/warp_route.rs b/rust/sealevel/client/src/warp_route.rs index 5e67fcf37..4425363c2 100644 --- a/rust/sealevel/client/src/warp_route.rs +++ b/rust/sealevel/client/src/warp_route.rs @@ -20,7 +20,7 @@ use hyperlane_sealevel_token_lib::{ accounts::{HyperlaneToken, HyperlaneTokenAccount}, hyperlane_token_pda_seeds, instruction::{ - enroll_remote_routers_instruction, set_destination_gas_configs, + enroll_remote_routers_instruction, set_destination_gas_configs, set_igp_instruction, set_interchain_security_module_instruction, transfer_ownership_instruction, Init, }, }; @@ -125,8 +125,8 @@ pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) { &deploy.warp_route_name, deploy.token_config_file, deploy.chain_config_file, - deploy.environments_dir, - &deploy.environment, + deploy.env_args.environments_dir, + &deploy.env_args.environment, deploy.built_so_dir, ); } @@ -434,6 +434,27 @@ impl ConnectionClient for WarpRouteDeployer { set_interchain_security_module_instruction(*program_id, token_data.owner.unwrap(), ism) .unwrap() } + + fn get_interchain_gas_paymaster( + &self, + client: &RpcClient, + program_id: &Pubkey, + ) -> Option<(Pubkey, InterchainGasPaymasterType)> { + let token_data = get_token_data::<()>(client, program_id); + + token_data.interchain_gas_paymaster + } + + fn set_interchain_gas_paymaster_instruction( + &self, + client: &RpcClient, + program_id: &Pubkey, + igp_config: Option<(Pubkey, InterchainGasPaymasterType)>, + ) -> Option { + let token_data = get_token_data::<()>(client, program_id); + + Some(set_igp_instruction(*program_id, token_data.owner.unwrap(), igp_config).unwrap()) + } } fn get_token_data(client: &RpcClient, program_id: &Pubkey) -> HyperlaneToken diff --git a/rust/sealevel/environments/testnet4/chain-config.json b/rust/sealevel/environments/testnet4/chain-config.json new file mode 100644 index 000000000..032f414d6 --- /dev/null +++ b/rust/sealevel/environments/testnet4/chain-config.json @@ -0,0 +1,465 @@ +{ + "alfajores": { + "blockExplorers": [ + { + "apiUrl": "https://api-alfajores.celoscan.io/api", + "family": "etherscan", + "name": "CeloScan", + "url": "https://alfajores.celoscan.io" + }, + { + "apiUrl": "https://explorer.celo.org/alfajores/api", + "family": "blockscout", + "name": "Blockscout", + "url": "https://explorer.celo.org/alfajores" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 5, + "reorgPeriod": 0 + }, + "chainId": 44787, + "displayName": "Alfajores", + "domainId": 44787, + "isTestnet": true, + "name": "alfajores", + "nativeToken": { + "decimals": 18, + "name": "CELO", + "symbol": "CELO" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://alfajores-forno.celo-testnet.org" + } + ] + }, + "basegoerli": { + "blockExplorers": [ + { + "apiUrl": "https://api-goerli.basescan.org/api", + "family": "etherscan", + "name": "BaseScan", + "url": "https://goerli.basescan.org" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, + "chainId": 84531, + "displayName": "Base Goerli", + "domainId": 84531, + "isTestnet": true, + "name": "basegoerli", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://base-goerli.publicnode.com" + }, + { + "http": "https://goerli.base.org" + } + ] + }, + "fuji": { + "blockExplorers": [ + { + "apiUrl": "https://api-testnet.snowtrace.io/api", + "family": "etherscan", + "name": "SnowTrace", + "url": "https://testnet.snowtrace.io" + } + ], + "blocks": { + "confirmations": 3, + "estimateBlockTime": 2, + "reorgPeriod": 3 + }, + "chainId": 43113, + "displayName": "Fuji", + "domainId": 43113, + "isTestnet": true, + "name": "fuji", + "nativeToken": { + "decimals": 18, + "name": "Avalanche", + "symbol": "AVAX" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://api.avax-test.network/ext/bc/C/rpc", + "pagination": { + "maxBlockRange": 2048 + } + } + ] + }, + "mumbai": { + "blockExplorers": [ + { + "apiUrl": "https://api-testnet.polygonscan.com/api", + "family": "etherscan", + "name": "PolygonScan", + "url": "https://mumbai.polygonscan.com" + } + ], + "blocks": { + "confirmations": 3, + "estimateBlockTime": 5, + "reorgPeriod": 32 + }, + "chainId": 80001, + "displayName": "Mumbai", + "domainId": 80001, + "isTestnet": true, + "name": "mumbai", + "nativeToken": { + "decimals": 18, + "name": "MATIC", + "symbol": "MATIC" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.ankr.com/polygon_mumbai", + "pagination": { + "maxBlockRange": 10000, + "minBlockNumber": 22900000 + } + } + ], + "transactionOverrides": { + "maxFeePerGas": 150000000000, + "maxPriorityFeePerGas": 40000000000 + } + }, + "bsctestnet": { + "blockExplorers": [ + { + "apiUrl": "https://api-testnet.bscscan.com/api", + "family": "etherscan", + "name": "BscScan", + "url": "https://testnet.bscscan.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 9 + }, + "chainId": 97, + "displayName": "BSC Testnet", + "domainId": 97, + "isTestnet": true, + "name": "bsctestnet", + "nativeToken": { + "decimals": 18, + "name": "BNB", + "symbol": "BNB" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://bsc-testnet.publicnode.com" + }, + { + "http": "https://bsc-testnet.blockpi.network/v1/rpc/public" + } + ], + "transactionOverrides": { + "gasPrice": 80000000000 + } + }, + "goerli": { + "blockExplorers": [ + { + "apiUrl": "https://api-goerli.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://goerli.etherscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 13, + "reorgPeriod": 2 + }, + "chainId": 5, + "displayName": "Goerli", + "domainId": 5, + "isTestnet": true, + "name": "goerli", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161" + }, + { + "http": "https://rpc.ankr.com/eth_goerli" + } + ] + }, + "scrollsepolia": { + "blockExplorers": [ + { + "apiUrl": "https://api-sepolia.scrollscan.com/api", + "family": "etherscan", + "name": "Scroll Explorer", + "url": "https://sepolia.scrollscan.dev/" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, + "chainId": 534351, + "displayName": "Scroll Sepolia", + "domainId": 534351, + "isTestnet": true, + "name": "scrollsepolia", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://sepolia-rpc.scroll.io" + } + ] + }, + "sepolia": { + "blockExplorers": [ + { + "apiUrl": "https://api-sepolia.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://sepolia.etherscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 13, + "reorgPeriod": 2 + }, + "chainId": 11155111, + "displayName": "Sepolia", + "domainId": 11155111, + "isTestnet": true, + "name": "sepolia", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://ethereum-sepolia.blockpi.network/v1/rpc/public" + }, + { + "http": "https://rpc.sepolia.org" + } + ] + }, + "moonbasealpha": { + "blockExplorers": [ + { + "apiUrl": "https://api-moonbase.moonscan.io/api", + "family": "etherscan", + "name": "MoonScan", + "url": "https://moonbase.moonscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 12, + "reorgPeriod": 1 + }, + "chainId": 1287, + "displayName": "Moonbase Alpha", + "displayNameShort": "Moonbase", + "domainId": 1287, + "isTestnet": true, + "name": "moonbasealpha", + "nativeToken": { + "decimals": 18, + "name": "DEV", + "symbol": "DEV" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.api.moonbase.moonbeam.network" + } + ] + }, + "optimismgoerli": { + "blockExplorers": [ + { + "apiUrl": "https://api-goerli-optimism.etherscan.io/api", + "family": "etherscan", + "name": "Etherscan", + "url": "https://goerli-optimism.etherscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, + "chainId": 420, + "displayName": "Optimism Goerli", + "displayNameShort": "Opt. Goerli", + "domainId": 420, + "isTestnet": true, + "name": "optimismgoerli", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://goerli.optimism.io" + } + ] + }, + "arbitrumgoerli": { + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, + "chainId": 421613, + "displayName": "Arbitrum Goerli", + "displayNameShort": "Arb. Goerli", + "domainId": 421613, + "isTestnet": true, + "name": "arbitrumgoerli", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://goerli-rollup.arbitrum.io/rpc" + } + ] + }, + "polygonzkevmtestnet": { + "blockExplorers": [ + { + "apiUrl": "https://api-testnet-zkevm.polygonscan.com/api", + "family": "etherscan", + "name": "PolygonScan", + "url": "https://testnet-zkevm.polygonscan.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 1 + }, + "chainId": 1442, + "displayName": "Polygon zkEVM Testnet", + "displayNameShort": "ZkEvm Testnet", + "domainId": 1442, + "isTestnet": true, + "name": "polygonzkevmtestnet", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.public.zkevm-test.net" + } + ] + }, + "solanatestnet": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.solana.com", + "family": "other", + "name": "Solana Explorer", + "url": "https://explorer.solana.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 0.4, + "reorgPeriod": 0 + }, + "chainId": 1399811150, + "displayName": "Solana Testnet", + "displayNameShort": "Sol Testnet", + "domainId": 1399811150, + "isTestnet": true, + "name": "solanatestnet", + "nativeToken": { + "decimals": 9, + "name": "Sol", + "symbol": "SOL" + }, + "protocol": "sealevel", + "rpcUrls": [ + { + "http": "https://api.testnet.solana.com" + } + ] + }, + "eclipsetestnet": { + "blockExplorers": [ + { + "apiUrl": "https://testnet.dev2.eclipsenetwork.xyz", + "family": "other", + "name": "Eclipse Testnet Explorer", + "url": "https://explorer.dev.eclipsenetwork.xyz/?cluster=testnet" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 0.4, + "reorgPeriod": 0 + }, + "chainId": 239092742, + "displayName": "Eclipse Testnet", + "domainId": 239092742, + "isTestnet": true, + "name": "eclipsetestnet", + "nativeToken": { + "decimals": 9, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "sealevel", + "rpcUrls": [ + { + "http": "https://testnet.dev2.eclipsenetwork.xyz" + } + ] + } +} diff --git a/rust/sealevel/environments/testnet4/eclipsetestnet/core/program-ids.json b/rust/sealevel/environments/testnet4/eclipsetestnet/core/program-ids.json new file mode 100644 index 000000000..095ae366c --- /dev/null +++ b/rust/sealevel/environments/testnet4/eclipsetestnet/core/program-ids.json @@ -0,0 +1,8 @@ +{ + "mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "validator_announce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3", + "multisig_ism_message_id": "4GHxwWyKB9exhKG4fdyU2hfLgfFzhHp2WcsSKc2uNR1k", + "igp_program_id": "5p7Hii6CJL4xGBYYTGEQmH9LnUSZteFJUu9AVLDExZX2", + "overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB", + "igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy" +} \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/gas-oracle-configs.json b/rust/sealevel/environments/testnet4/gas-oracle-configs.json new file mode 100644 index 000000000..9e1cb7313 --- /dev/null +++ b/rust/sealevel/environments/testnet4/gas-oracle-configs.json @@ -0,0 +1,29 @@ +[ + { + "domain": 11155111, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "15000000000", + "tokenDecimals": 18 + } + }, + { + "domain": 1399811150, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "28", + "tokenDecimals": 9 + } + }, + { + "domain": 239092742, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "28", + "tokenDecimals": 9 + } + } +] \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/igp/eclipsetestnet/default/igp-accounts.json b/rust/sealevel/environments/testnet4/igp/eclipsetestnet/default/igp-accounts.json new file mode 100644 index 000000000..9d06cd936 --- /dev/null +++ b/rust/sealevel/environments/testnet4/igp/eclipsetestnet/default/igp-accounts.json @@ -0,0 +1,5 @@ +{ + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy", + "overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB" +} \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/igp/solanatestnet/default/igp-accounts.json b/rust/sealevel/environments/testnet4/igp/solanatestnet/default/igp-accounts.json new file mode 100644 index 000000000..9d06cd936 --- /dev/null +++ b/rust/sealevel/environments/testnet4/igp/solanatestnet/default/igp-accounts.json @@ -0,0 +1,5 @@ +{ + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy", + "overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB" +} \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/multisig-ism-message-id/eclipsetestnet/hyperlane/multisig-config.json b/rust/sealevel/environments/testnet4/multisig-ism-message-id/eclipsetestnet/hyperlane/multisig-config.json new file mode 100644 index 000000000..0fe6c5977 --- /dev/null +++ b/rust/sealevel/environments/testnet4/multisig-ism-message-id/eclipsetestnet/hyperlane/multisig-config.json @@ -0,0 +1,117 @@ +{ + "alfajores": { + "threshold": 2, + "validators": [ + "0x2233a5ce12f814bd64c9cdd73410bb8693124d40", + "0xba279f965489d90f90490e3c49e860e0b43c2ae6", + "0x86485dcec5f7bb8478dd251676372d054dea6653" + ], + "type": 3 + }, + "arbitrumgoerli": { + "threshold": 2, + "validators": [ + "0x071c8d135845ae5a2cb73f98d681d519014c0a8b", + "0x1bcf03360989f15cbeb174c188288f2c6d2760d7", + "0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d" + ], + "type": 3 + }, + "basegoerli": { + "threshold": 2, + "validators": [ + "0xf6eddda696dcd3bf10f7ce8a02db31ef2e775a03", + "0x5a7d05cebf5db4dde9b2fedcefa76fb58fa05071", + "0x9260a6c7d54cbcbed28f8668679cd1fa3a203b25" + ], + "type": 3 + }, + "bsctestnet": { + "threshold": 2, + "validators": [ + "0x242d8a855a8c932dec51f7999ae7d1e48b10c95e", + "0xf620f5e3d25a3ae848fec74bccae5de3edcd8796", + "0x1f030345963c54ff8229720dd3a711c15c554aeb" + ], + "type": 3 + }, + "fuji": { + "threshold": 2, + "validators": [ + "0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e", + "0x895ae30bc83ff1493b9cf7781b0b813d23659857", + "0x43e915573d9f1383cbf482049e4a012290759e7f" + ], + "type": 3 + }, + "goerli": { + "threshold": 2, + "validators": [ + "0x05a9b5efe9f61f9142453d8e9f61565f333c6768", + "0x43a96c7dfbd8187c95013d6ee8665650cbdb2673", + "0x7940a12c050e24e1839c21ecb12f65afd84e8c5b" + ], + "type": 3 + }, + "moonbasealpha": { + "threshold": 2, + "validators": [ + "0x521877064bd7ac7500d300f162c8c47c256a2f9c", + "0xbc1c70f58ae0459d4b8a013245420a893837d568", + "0x01e42c2c44af81dda1ac16fec76fea2a7a54a44c" + ], + "type": 3 + }, + "mumbai": { + "threshold": 2, + "validators": [ + "0xebc301013b6cd2548e347c28d2dc43ec20c068f2", + "0x315db9868fc8813b221b1694f8760ece39f45447", + "0x17517c98358c5937c5d9ee47ce1f5b4c2b7fc9f5" + ], + "type": 3 + }, + "optimismgoerli": { + "threshold": 2, + "validators": [ + "0x79e58546e2faca865c6732ad5f6c4951051c4d67", + "0x7bbfe1bb7146aad7df309c637987d856179ebbc1", + "0xf3d2fb4d53c2bb6a88cec040e0d87430fcee4e40" + ], + "type": 3 + }, + "polygonzkevmtestnet": { + "threshold": 2, + "validators": [ + "0x3f06b725bc9648917eb11c414e9f8d76fd959550", + "0x27bfc57679d9dd4ab2e870f5ed7ec0b339a0b636", + "0xd476548222f43206d0abaa30e46e28670aa7859c" + ], + "type": 3 + }, + "scrollsepolia": { + "threshold": 2, + "validators": [ + "0xbe18dbd758afb367180260b524e6d4bcd1cb6d05", + "0x9a11ed23ae962974018ab45bc133caabff7b3271", + "0x7867bea3c9761fe64e6d124b171f91fd5dd79644" + ], + "type": 3 + }, + "sepolia": { + "threshold": 2, + "validators": [ + "0xb22b65f202558adf86a8bb2847b76ae1036686a5", + "0x469f0940684d147defc44f3647146cb90dd0bc8e", + "0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83" + ], + "type": 3 + }, + "solanatestnet": { + "threshold": 1, + "validators": [ + "0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5" + ], + "type": 3 + } +} diff --git a/rust/sealevel/environments/testnet4/multisig-ism-message-id/solanatestnet/hyperlane/multisig-config.json b/rust/sealevel/environments/testnet4/multisig-ism-message-id/solanatestnet/hyperlane/multisig-config.json new file mode 100644 index 000000000..a61ae3cd2 --- /dev/null +++ b/rust/sealevel/environments/testnet4/multisig-ism-message-id/solanatestnet/hyperlane/multisig-config.json @@ -0,0 +1,117 @@ +{ + "alfajores": { + "threshold": 2, + "validators": [ + "0x2233a5ce12f814bd64c9cdd73410bb8693124d40", + "0xba279f965489d90f90490e3c49e860e0b43c2ae6", + "0x86485dcec5f7bb8478dd251676372d054dea6653" + ], + "type": 3 + }, + "arbitrumgoerli": { + "threshold": 2, + "validators": [ + "0x071c8d135845ae5a2cb73f98d681d519014c0a8b", + "0x1bcf03360989f15cbeb174c188288f2c6d2760d7", + "0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d" + ], + "type": 3 + }, + "basegoerli": { + "threshold": 2, + "validators": [ + "0xf6eddda696dcd3bf10f7ce8a02db31ef2e775a03", + "0x5a7d05cebf5db4dde9b2fedcefa76fb58fa05071", + "0x9260a6c7d54cbcbed28f8668679cd1fa3a203b25" + ], + "type": 3 + }, + "bsctestnet": { + "threshold": 2, + "validators": [ + "0x242d8a855a8c932dec51f7999ae7d1e48b10c95e", + "0xf620f5e3d25a3ae848fec74bccae5de3edcd8796", + "0x1f030345963c54ff8229720dd3a711c15c554aeb" + ], + "type": 3 + }, + "fuji": { + "threshold": 2, + "validators": [ + "0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e", + "0x895ae30bc83ff1493b9cf7781b0b813d23659857", + "0x43e915573d9f1383cbf482049e4a012290759e7f" + ], + "type": 3 + }, + "goerli": { + "threshold": 2, + "validators": [ + "0x05a9b5efe9f61f9142453d8e9f61565f333c6768", + "0x43a96c7dfbd8187c95013d6ee8665650cbdb2673", + "0x7940a12c050e24e1839c21ecb12f65afd84e8c5b" + ], + "type": 3 + }, + "moonbasealpha": { + "threshold": 2, + "validators": [ + "0x521877064bd7ac7500d300f162c8c47c256a2f9c", + "0xbc1c70f58ae0459d4b8a013245420a893837d568", + "0x01e42c2c44af81dda1ac16fec76fea2a7a54a44c" + ], + "type": 3 + }, + "mumbai": { + "threshold": 2, + "validators": [ + "0xebc301013b6cd2548e347c28d2dc43ec20c068f2", + "0x315db9868fc8813b221b1694f8760ece39f45447", + "0x17517c98358c5937c5d9ee47ce1f5b4c2b7fc9f5" + ], + "type": 3 + }, + "optimismgoerli": { + "threshold": 2, + "validators": [ + "0x79e58546e2faca865c6732ad5f6c4951051c4d67", + "0x7bbfe1bb7146aad7df309c637987d856179ebbc1", + "0xf3d2fb4d53c2bb6a88cec040e0d87430fcee4e40" + ], + "type": 3 + }, + "polygonzkevmtestnet": { + "threshold": 2, + "validators": [ + "0x3f06b725bc9648917eb11c414e9f8d76fd959550", + "0x27bfc57679d9dd4ab2e870f5ed7ec0b339a0b636", + "0xd476548222f43206d0abaa30e46e28670aa7859c" + ], + "type": 3 + }, + "scrollsepolia": { + "threshold": 2, + "validators": [ + "0xbe18dbd758afb367180260b524e6d4bcd1cb6d05", + "0x9a11ed23ae962974018ab45bc133caabff7b3271", + "0x7867bea3c9761fe64e6d124b171f91fd5dd79644" + ], + "type": 3 + }, + "sepolia": { + "threshold": 2, + "validators": [ + "0xb22b65f202558adf86a8bb2847b76ae1036686a5", + "0x469f0940684d147defc44f3647146cb90dd0bc8e", + "0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83" + ], + "type": 3 + }, + "eclipsetestnet": { + "threshold": 1, + "validators": [ + "0xf344f34abca9a444545b5295066348a0ae22dda3" + ], + "type": 3 + } +} diff --git a/rust/sealevel/environments/testnet4/solanatestnet/core/program-ids.json b/rust/sealevel/environments/testnet4/solanatestnet/core/program-ids.json new file mode 100644 index 000000000..095ae366c --- /dev/null +++ b/rust/sealevel/environments/testnet4/solanatestnet/core/program-ids.json @@ -0,0 +1,8 @@ +{ + "mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR", + "validator_announce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3", + "multisig_ism_message_id": "4GHxwWyKB9exhKG4fdyU2hfLgfFzhHp2WcsSKc2uNR1k", + "igp_program_id": "5p7Hii6CJL4xGBYYTGEQmH9LnUSZteFJUu9AVLDExZX2", + "overhead_igp_account": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB", + "igp_account": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy" +} \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/program-ids.json b/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/program-ids.json new file mode 100644 index 000000000..2b614fb94 --- /dev/null +++ b/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/program-ids.json @@ -0,0 +1,10 @@ +{ + "solanatestnet": { + "hex": "0xb5eb96475b2d58f7b9a9ff2f60f46cf5ac1be244e557574971885337522b59c9", + "base58": "DF99ZiHj8aN4ETbKmpMoMsRTSbm7gtL5j6sKKLC5mkTS" + }, + "eclipsetestnet": { + "hex": "0x6dcd836e4ed228f42f3b2d6e2c2723b431b8e8b0bc9dded26bdff16ffaf818bd", + "base58": "8PdCNrJqkDfSMjg9BvXrDr3Hooo7WZ6cN7Qn9mHL1GZr" + } +} \ No newline at end of file diff --git a/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/token-config.json b/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/token-config.json new file mode 100644 index 000000000..f51cda645 --- /dev/null +++ b/rust/sealevel/environments/testnet4/warp-routes/eclipsetestnetsol/token-config.json @@ -0,0 +1,14 @@ +{ + "solanatestnet": { + "type": "native", + "decimals": 9, + "interchainGasPaymaster": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB" + }, + "eclipsetestnet": { + "type": "synthetic", + "decimals": 9, + "name": "Solana (solanatestnet)", + "symbol": "SOL", + "interchainGasPaymaster": "hBHAApi5ZoeCYHqDdCKkCzVKmBdwywdT3hMqe327eZB" + } +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-igp/src/instruction.rs b/rust/sealevel/programs/hyperlane-sealevel-igp/src/instruction.rs index 9381d0af2..a4fb51984 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-igp/src/instruction.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-igp/src/instruction.rs @@ -347,3 +347,56 @@ pub fn transfer_igp_account_ownership_instruction( }; Ok(instruction) } + +/// Gets an instruction to claim funds from an IGP to the beneficiary. +pub fn claim_instruction( + program_id: Pubkey, + igp: Pubkey, + beneficiary: Pubkey, +) -> Result { + let ixn = Instruction::Claim; + + // Accounts: + // 0. `[executable]` The system program. + // 1. `[writeable]` The IGP. + // 2. `[writeable]` The IGP beneficiary. + let accounts = vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(igp, false), + AccountMeta::new(beneficiary, false), + ]; + + let instruction = SolanaInstruction { + program_id, + data: ixn.try_to_vec()?, + accounts, + }; + + Ok(instruction) +} + +/// Gets an instruction to claim funds from an IGP to the beneficiary. +pub fn set_beneficiary_instruction( + program_id: Pubkey, + igp: Pubkey, + igp_owner: Pubkey, + new_beneficiary: Pubkey, +) -> Result { + let ixn = Instruction::SetIgpBeneficiary(new_beneficiary); + + // Accounts: + // 0. `[]` The IGP. + // 1. `[signer]` The owner of the IGP account. + let accounts = vec![ + AccountMeta::new(igp, false), + AccountMeta::new(igp_owner, true), + ]; + + let instruction = SolanaInstruction { + program_id, + data: ixn.try_to_vec()?, + accounts, + }; + + Ok(instruction) +} diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index b00e21079..b77b09340 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -50,7 +50,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '8ccfdb7-20240103-084118', + tag: '6360cc8-20240122-140607', }, blacklist: [ ...releaseCandidateHelloworldMatchingList, @@ -75,7 +75,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '8ccfdb7-20240103-084118', + tag: '6360cc8-20240122-140607', }, chains: validatorChainConfig(Contexts.Hyperlane), }, @@ -96,7 +96,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '8ccfdb7-20240103-084118', + tag: '6360cc8-20240122-140607', }, whitelist: [...releaseCandidateHelloworldMatchingList], gasPaymentEnforcement, @@ -109,7 +109,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '8ccfdb7-20240103-084118', + tag: '6360cc8-20240122-140607', }, chains: validatorChainConfig(Contexts.ReleaseCandidate), }, diff --git a/typescript/infra/config/environments/testnet4/chains.ts b/typescript/infra/config/environments/testnet4/chains.ts index 2c5fed600..ac479783c 100644 --- a/typescript/infra/config/environments/testnet4/chains.ts +++ b/typescript/infra/config/environments/testnet4/chains.ts @@ -31,7 +31,8 @@ export const ethereumTestnetConfigs: ChainMap = { // Blessed non-Ethereum chains. export const nonEthereumTestnetConfigs: ChainMap = { - // solanadevnet: chainMetadata.solanadevnet, + solanatestnet: chainMetadata.solanatestnet, + eclipsetestnet: chainMetadata.eclipsetestnet, }; export const testnetConfigs: ChainMap = { @@ -54,6 +55,6 @@ export const agentChainNames: AgentChainNames = { // Run validators for all chains. [Role.Validator]: supportedChainNames, // Only run relayers for Ethereum chains at the moment. - [Role.Relayer]: ethereumChainNames, + [Role.Relayer]: supportedChainNames, [Role.Scraper]: ethereumChainNames, }; diff --git a/typescript/infra/config/environments/testnet4/gas-oracle.ts b/typescript/infra/config/environments/testnet4/gas-oracle.ts index b173f4d3a..efa98e5f2 100644 --- a/typescript/infra/config/environments/testnet4/gas-oracle.ts +++ b/typescript/infra/config/environments/testnet4/gas-oracle.ts @@ -29,7 +29,8 @@ const gasPrices: ChainMap = { lineagoerli: ethers.utils.parseUnits('1', 'gwei'), polygonzkevmtestnet: ethers.utils.parseUnits('1', 'gwei'), chiado: ethers.utils.parseUnits('2', 'gwei'), - // solanadevnet: ethers.BigNumber.from('28'), + solanatestnet: ethers.BigNumber.from('28'), + eclipsetestnet: ethers.BigNumber.from('28'), }; // Used to categorize rarity of testnet tokens & approximate exchange rates. @@ -63,7 +64,8 @@ const chainTokenRarity: ChainMap = { lineagoerli: Rarity.Rare, polygonzkevmtestnet: Rarity.Common, chiado: Rarity.Common, - // solanadevnet: Rarity.Common, + solanatestnet: Rarity.Common, + eclipsetestnet: Rarity.Common, }; // Gets the "value" of a testnet chain diff --git a/typescript/infra/config/environments/testnet4/validators.ts b/typescript/infra/config/environments/testnet4/validators.ts index 23fb1a6f9..90677f9aa 100644 --- a/typescript/infra/config/environments/testnet4/validators.ts +++ b/typescript/infra/config/environments/testnet4/validators.ts @@ -306,22 +306,29 @@ export const validatorChainConfig = ( // 'proteustestnet', // ), // }, - // solanadevnet: { - // interval: 10, - // reorgPeriod: getReorgPeriod(chainMetadata.solanadevnet), - // validators: validatorsConfig( - // { - // [Contexts.Hyperlane]: [ - // '0xec0f73dbc5b1962a20f7dcbe07c98414025b0c43', - // '0x9c20a149dfa09ea9f77f5a7ca09ed44f9c025133', - // '0x967c5ecdf2625ae86580bd203b630abaaf85cd62', - // ], - // [Contexts.ReleaseCandidate]: [ - // '0x21b9eff4d1a6d3122596c7fb80315bf094b6e5c2', - // ], - // }, - // 'solanadevnet', - // ), - // }, + solanatestnet: { + interval: 1, + reorgPeriod: getReorgPeriod(chainMetadata.solanatestnet), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5'], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }, + 'solanatestnet', + ), + }, + eclipsetestnet: { + interval: 1, + reorgPeriod: getReorgPeriod(chainMetadata.eclipsetestnet), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xf344f34abca9a444545b5295066348a0ae22dda3'], + [Contexts.ReleaseCandidate]: [], + [Contexts.Neutron]: [], + }, + 'eclipsetestnet', + ), + }, }; }; diff --git a/typescript/sdk/src/consts/chainMetadata.ts b/typescript/sdk/src/consts/chainMetadata.ts index 00df3f733..5df2771b3 100644 --- a/typescript/sdk/src/consts/chainMetadata.ts +++ b/typescript/sdk/src/consts/chainMetadata.ts @@ -929,6 +929,33 @@ export const solanadevnet: ChainMetadata = { rpcUrls: [{ http: 'https://api.devnet.solana.com' }], }; +export const eclipsetestnet: ChainMetadata = { + blockExplorers: [ + { + apiUrl: 'https://testnet.dev2.eclipsenetwork.xyz', + family: ExplorerFamily.Other, + name: 'Eclipse Testnet Explorer', + url: 'https://explorer.dev.eclipsenetwork.xyz/?cluster=testnet', + }, + ], + blocks: { + confirmations: 1, + estimateBlockTime: 0.4, + reorgPeriod: 0, + }, + chainId: 239092742, + displayName: 'Eclipse Testnet', + domainId: 239092742, + isTestnet: true, + name: 'eclipsetestnet', + nativeToken: { + ...etherToken, + decimals: 9, + }, + protocol: ProtocolType.Sealevel, + rpcUrls: [{ http: 'https://testnet.dev2.eclipsenetwork.xyz' }], +}; + export const test1: ChainMetadata = { blockExplorers: [], blocks: { @@ -997,6 +1024,7 @@ export const chainMetadata: ChainMap = { bsctestnet, celo, chiado, + eclipsetestnet, ethereum, fuji, gnosis, diff --git a/typescript/sdk/src/consts/chains.ts b/typescript/sdk/src/consts/chains.ts index 3907ee8c9..7d065f700 100644 --- a/typescript/sdk/src/consts/chains.ts +++ b/typescript/sdk/src/consts/chains.ts @@ -35,6 +35,8 @@ export enum Chains { sepolia = 'sepolia', solana = 'solana', solanadevnet = 'solanadevnet', + solanatestnet = 'solanatestnet', + eclipsetestnet = 'eclipsetestnet', test1 = 'test1', test2 = 'test2', test3 = 'test3', @@ -87,6 +89,8 @@ export const Testnets: Array = [ Chains.scrollsepolia, Chains.sepolia, Chains.solanadevnet, + Chains.solanatestnet, + Chains.eclipsetestnet, ]; export const TestChains: Array = [ diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index fbd1215a4..cbc2b738a 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -99,6 +99,11 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + eclipsetestnet: { + threshold: 1, + validators: ['0xf344f34abca9a444545b5295066348a0ae22dda3'], + }, + ethereum: { threshold: 3, validators: [ @@ -281,4 +286,9 @@ export const defaultMultisigConfigs: ChainMap = { '0x967c5ecdf2625ae86580bd203b630abaaf85cd62', ], }, + + solanatestnet: { + threshold: 1, + validators: ['0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5'], + }, };