diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 180b9c072c..dcf73eb8ec 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -406,7 +406,7 @@ jobs: test -p ibc-integration-test --features clean-workers --no-fail-fast -- \ --nocapture --test-threads=1 clean_workers:: - interchain-security: + interchain-security-no-ica: runs-on: ubuntu-20.04 strategy: fail-fast: false @@ -415,6 +415,43 @@ jobs: - package: neutron command: neutrond account_prefix: neutron + steps: + - uses: actions/checkout@v3 + - uses: cachix/install-nix-action@v22 + with: + install_url: https://nixos-nix-install-tests.cachix.org/serve/vij683ly7sl95nnhb67bdjjfabclr85m/install + install_options: '--tarball-url-prefix https://nixos-nix-install-tests.cachix.org/serve' + extra_nix_config: | + experimental-features = nix-command flakes + - uses: cachix/cachix-action@v12 + with: + name: cosmos + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - uses: Swatinem/rust-cache@v1 + - uses: actions-rs/cargo@v1 + with: + command: test + args: -p ibc-integration-test --features interchain-security --no-fail-fast --no-run + - env: + RUST_LOG: info + RUST_BACKTRACE: 1 + NO_COLOR_LOG: 1 + CHAIN_COMMAND_PATHS: gaiad,${{ matrix.chain.command }} + ACCOUNT_PREFIXES: cosmos,${{ matrix.chain.account_prefix }} + run: | + nix shell .#gaia9 .#${{ matrix.chain.package }} -c cargo \ + test -p ibc-integration-test --features interchain-security --no-fail-fast -- \ + --nocapture --test-threads=1 interchain_security:: + + interchain-security-ica: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + chain: - package: stride-consumer command: strided account_prefix: stride @@ -446,7 +483,7 @@ jobs: ACCOUNT_PREFIXES: cosmos,${{ matrix.chain.account_prefix }} run: | nix shell .#gaia9 .#${{ matrix.chain.package }} -c cargo \ - test -p ibc-integration-test --features interchain-security --no-fail-fast -- \ + test -p ibc-integration-test --features interchain-security,ica --no-fail-fast -- \ --nocapture --test-threads=1 interchain_security:: model-based-test: diff --git a/tools/integration-test/src/tests/ica.rs b/tools/integration-test/src/tests/ica.rs index 982f78d712..dfcda16cea 100644 --- a/tools/integration-test/src/tests/ica.rs +++ b/tools/integration-test/src/tests/ica.rs @@ -10,24 +10,21 @@ use ibc_relayer::config::{ use ibc_relayer::event::IbcEventWithHeight; use ibc_relayer_types::applications::ics27_ica::msgs::send_tx::MsgSendTx; use ibc_relayer_types::applications::ics27_ica::packet_data::InterchainAccountPacketData; -use ibc_relayer_types::core::ics04_channel::version::Version; -use ibc_relayer_types::signer::Signer; -use ibc_relayer_types::{ - applications::{ - ics27_ica::{cosmos_tx::CosmosTx, msgs::register::MsgRegisterInterchainAccount}, - transfer::{msgs::send::MsgSend, Amount, Coin}, - }, - bigint::U256, - core::ics04_channel::channel::State, - events::IbcEvent, - timestamp::Timestamp, - tx_msg::Msg, +use ibc_relayer_types::applications::{ + ics27_ica::cosmos_tx::CosmosTx, + transfer::{msgs::send::MsgSend, Amount, Coin}, }; - -use ibc_test_framework::{ - ibc::denom::Denom, - prelude::*, - relayer::channel::{assert_eventually_channel_established, query_channel_end}, +use ibc_relayer_types::bigint::U256; +use ibc_relayer_types::core::ics04_channel::channel::State; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::timestamp::Timestamp; +use ibc_relayer_types::tx_msg::Msg; + +use ibc_test_framework::chain::ext::ica::register_interchain_account; +use ibc_test_framework::ibc::denom::Denom; +use ibc_test_framework::prelude::*; +use ibc_test_framework::relayer::channel::{ + assert_eventually_channel_established, query_channel_end, }; #[test] @@ -97,7 +94,8 @@ impl BinaryConnectionTest for IcaFilterTestAllow { ) -> Result<(), Error> { // Register an interchain account on behalf of // controller wallet `user1` where the counterparty chain is the interchain accounts host. - let (wallet, channel_id, port_id) = register_interchain_account(&chains, &connection)?; + let (wallet, channel_id, port_id) = + register_interchain_account(&chains.node_a, chains.handle_a(), &connection)?; // Check that the corresponding ICA channel is eventually established. let _counterparty_channel_id = assert_eventually_channel_established( @@ -230,7 +228,8 @@ impl BinaryConnectionTest for IcaFilterTestDeny { ) -> Result<(), Error> { // Register an interchain account on behalf of controller wallet `user1` // where the counterparty chain is the interchain accounts host. - let (_, channel_id, port_id) = register_interchain_account(&chains, &connection)?; + let (_, channel_id, port_id) = + register_interchain_account(&chains.node_a, chains.handle_a(), &connection)?; // Wait a bit, the relayer will refuse to complete the channel handshake // because the port is explicitly disallowed by the filter. @@ -247,49 +246,3 @@ impl BinaryConnectionTest for IcaFilterTestDeny { ) } } - -#[allow(clippy::type_complexity)] -fn register_interchain_account( - chains: &ConnectedChains, - connection: &ConnectedConnection, -) -> Result< - ( - MonoTagged, - TaggedChannelId, - TaggedPortId, - ), - Error, -> { - let wallet = chains.node_a.wallets().relayer().cloned(); - - let owner = chains.handle_a().get_signer()?; - - let version_str = format!("{{\"version\":\"ics27-1\",\"encoding\":\"proto3\",\"tx_type\":\"sdk_multi_msg\",\"controller_connection_id\":\"{}\",\"host_connection_id\":\"{}\"}}", connection.connection_id_a.0, connection.connection_id_b.0); - let msg = MsgRegisterInterchainAccount { - owner, - connection_id: connection.connection_id_a.0.clone(), - version: Version::new(version_str), - }; - - let msg_any = msg.to_any(); - - let tm = TrackedMsgs::new_static(vec![msg_any], "RegisterInterchainAccount"); - - let events = chains - .handle_a() - .send_messages_and_wait_commit(tm) - .map_err(Error::relayer)?; - - for event in events.iter() { - if let IbcEvent::OpenInitChannel(open_init) = &event.event { - let channel_id = open_init.channel_id.clone().ok_or(()).map_err(|_| Error::generic(eyre!("channel_id is empty in the event response after sending MsgRegisterInterchainAccount")))?; - return Ok(( - wallet, - TaggedChannelId::new(channel_id), - TaggedPortId::new(open_init.port_id.clone()), - )); - } - } - - Err(Error::generic(eyre!("could not retrieve an OpenInitChannel event resonse after sending MsgRegisterInterchainAccount"))) -} diff --git a/tools/integration-test/src/tests/interchain_security/ica_transfer.rs b/tools/integration-test/src/tests/interchain_security/ica_transfer.rs new file mode 100644 index 0000000000..f28732198c --- /dev/null +++ b/tools/integration-test/src/tests/interchain_security/ica_transfer.rs @@ -0,0 +1,188 @@ +//! The following tests are for the Interchain Security. +//! These tests require the first chain to be a Provider chain and +//! the second chain a Consumer chain. +use std::str::FromStr; + +use ibc_relayer::chain::tracking::TrackedMsgs; +use ibc_relayer::event::IbcEventWithHeight; +use ibc_relayer_types::applications::ics27_ica::cosmos_tx::CosmosTx; +use ibc_relayer_types::applications::ics27_ica::msgs::send_tx::MsgSendTx; +use ibc_relayer_types::applications::ics27_ica::packet_data::InterchainAccountPacketData; +use ibc_relayer_types::applications::transfer::msgs::send::MsgSend; +use ibc_relayer_types::applications::transfer::{Amount, Coin}; +use ibc_relayer_types::bigint::U256; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::timestamp::Timestamp; +use ibc_relayer_types::tx_msg::Msg; +use ibc_test_framework::chain::config::set_voting_period; +use ibc_test_framework::chain::ext::ica::register_interchain_account; +use ibc_test_framework::framework::binary::channel::run_binary_interchain_security_channel_test; +use ibc_test_framework::prelude::*; +use ibc_test_framework::relayer::channel::assert_eventually_channel_established; + +#[test] +fn test_ics_ica_transfer() -> Result<(), Error> { + run_binary_interchain_security_channel_test(&InterchainSecurityIcaTransferTest) +} + +struct InterchainSecurityIcaTransferTest; + +impl TestOverrides for InterchainSecurityIcaTransferTest { + fn modify_genesis_file(&self, genesis: &mut serde_json::Value) -> Result<(), Error> { + use serde_json::Value; + + // Allow MsgSend messages over ICA + let allow_messages = genesis + .get_mut("app_state") + .and_then(|app_state| app_state.get_mut("interchainaccounts")) + .and_then(|ica| ica.get_mut("host_genesis_state")) + .and_then(|state| state.get_mut("params")) + .and_then(|params| params.get_mut("allow_messages")) + .and_then(|allow_messages| allow_messages.as_array_mut()); + + if let Some(allow_messages) = allow_messages { + allow_messages.push(Value::String("/cosmos.bank.v1beta1.MsgSend".to_string())); + } else { + return Err(Error::generic(eyre!("failed to update genesis file"))); + } + + // Consumer chain doesn't have a gov key. + if genesis + .get_mut("app_state") + .and_then(|app_state| app_state.get("gov")) + .is_some() + { + set_voting_period(genesis, "10s")?; + } + Ok(()) + } + + // The `ccv_consumer_chain` must be `true` for the Consumer chain. + // The `trusting_period` must be strictly smaller than the `unbonding_period` + // specified in the Consumer chain proposal. The test framework uses 100s in + // the proposal. + fn modify_relayer_config(&self, config: &mut Config) { + config.mode.channels.enabled = true; + + for chain_config in config.chains.iter_mut() { + if chain_config.id == ChainId::from_string("ibcconsumer") { + chain_config.ccv_consumer_chain = true; + chain_config.trusting_period = Some(Duration::from_secs(99)); + } + } + } +} + +impl BinaryChannelTest for InterchainSecurityIcaTransferTest { + fn run( + &self, + _config: &TestConfig, + _relayer: RelayerDriver, + chains: ConnectedChains, + channel: ConnectedChannel, + ) -> Result<(), Error> { + let connection_b_to_a = channel.connection.clone().flip(); + let (wallet, channel_id, port_id) = + register_interchain_account(&chains.node_b, chains.handle_b(), &connection_b_to_a)?; + + // Check that the corresponding ICA channel is eventually established. + let _counterparty_channel_id = assert_eventually_channel_established( + chains.handle_b(), + chains.handle_a(), + &channel_id.as_ref(), + &port_id.as_ref(), + )?; + + // Query the controller chain for the address of the ICA wallet on the host chain. + let ica_address = chains.node_b.chain_driver().query_interchain_account( + &wallet.address(), + &channel.connection.connection_id_b.as_ref(), + )?; + + let stake_denom: MonoTagged = MonoTagged::new(Denom::base("stake")); + + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &ica_address.as_ref(), + &stake_denom.with_amount(0u64).as_ref(), + )?; + + // Send funds to the interchain account. + let ica_fund = 42000u64; + + chains.node_a.chain_driver().local_transfer_token( + &chains.node_a.wallets().user1(), + &ica_address.as_ref(), + &stake_denom.with_amount(ica_fund).as_ref(), + )?; + + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &ica_address.as_ref(), + &stake_denom.with_amount(ica_fund).as_ref(), + )?; + + let amount = 12345; + + let msg = MsgSend { + from_address: ica_address.to_string(), + to_address: chains.node_a.wallets().user2().address().to_string(), + amount: vec![Coin { + denom: stake_denom.to_string(), + amount: Amount(U256::from(amount)), + }], + }; + + let raw_msg = msg.to_any(); + + let cosmos_tx = CosmosTx { + messages: vec![raw_msg], + }; + + let raw_cosmos_tx = cosmos_tx.to_any(); + + let interchain_account_packet_data = InterchainAccountPacketData::new(raw_cosmos_tx.value); + + let signer = Signer::from_str(&wallet.address().to_string()).unwrap(); + + // Send funds from the ICA account to the `user2` account on the host chain on behalf + // of the `user1` account on the controller chain. + let ica_events = interchain_send_tx( + chains.handle_b(), + &signer, + &channel.connection.connection_id_b.0, + interchain_account_packet_data, + Timestamp::from_nanoseconds(120000000000).unwrap(), + )?; + + info!("ICA events from `CosmosTx`: {ica_events:#?}"); + + // Check that the ICA account's balance has been debited the sent amount. + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &ica_address.as_ref(), + &stake_denom.with_amount(ica_fund - amount).as_ref(), + )?; + Ok(()) + } +} + +fn interchain_send_tx( + chain: &ChainA, + from: &Signer, + connection: &ConnectionId, + msg: InterchainAccountPacketData, + relative_timeout: Timestamp, +) -> Result, Error> { + let msg = MsgSendTx { + owner: from.clone(), + connection_id: connection.clone(), + packet_data: msg, + relative_timeout, + }; + + let msg_any = msg.to_any(); + + let tm = TrackedMsgs::new_static(vec![msg_any], "SendTx"); + + chain + .send_messages_and_wait_commit(tm) + .map_err(Error::relayer) +} diff --git a/tools/integration-test/src/tests/interchain_security/mod.rs b/tools/integration-test/src/tests/interchain_security/mod.rs new file mode 100644 index 0000000000..8b0c57ae08 --- /dev/null +++ b/tools/integration-test/src/tests/interchain_security/mod.rs @@ -0,0 +1,3 @@ +#[cfg(any(doc, feature = "ica"))] +pub mod ica_transfer; +pub mod simple_transfer; diff --git a/tools/integration-test/src/tests/interchain_security.rs b/tools/integration-test/src/tests/interchain_security/simple_transfer.rs similarity index 95% rename from tools/integration-test/src/tests/interchain_security.rs rename to tools/integration-test/src/tests/interchain_security/simple_transfer.rs index bcc531743f..e418c97111 100644 --- a/tools/integration-test/src/tests/interchain_security.rs +++ b/tools/integration-test/src/tests/interchain_security/simple_transfer.rs @@ -1,5 +1,5 @@ //! The following tests are for the Interchain Security. -//! These tests require the first chain to be a Producer chain and +//! These tests require the first chain to be a Provider chain and //! the second chain a Consumer chain. use ibc_test_framework::chain::config::set_voting_period; use ibc_test_framework::framework::binary::channel::run_binary_interchain_security_channel_test; @@ -8,12 +8,12 @@ use ibc_test_framework::util::random::random_u128_range; #[test] fn test_ics_transfer() -> Result<(), Error> { - run_binary_interchain_security_channel_test(&InterchainSecurityTest) + run_binary_interchain_security_channel_test(&InterchainSecurityTransferTest) } -struct InterchainSecurityTest; +struct InterchainSecurityTransferTest; -impl TestOverrides for InterchainSecurityTest { +impl TestOverrides for InterchainSecurityTransferTest { fn modify_genesis_file(&self, genesis: &mut serde_json::Value) -> Result<(), Error> { // Consumer chain doesn't have a gov key. if genesis @@ -40,7 +40,7 @@ impl TestOverrides for InterchainSecurityTest { } } -impl BinaryChannelTest for InterchainSecurityTest { +impl BinaryChannelTest for InterchainSecurityTransferTest { fn run( &self, _config: &TestConfig, diff --git a/tools/test-framework/src/chain/cli/ica.rs b/tools/test-framework/src/chain/cli/ica.rs index 9dc0d6d3b0..5524543745 100644 --- a/tools/test-framework/src/chain/cli/ica.rs +++ b/tools/test-framework/src/chain/cli/ica.rs @@ -6,7 +6,7 @@ use crate::error::{handle_generic_error, Error}; /// Register a new interchain account controlled by the given account /// over the given connection. -pub fn register_interchain_account( +pub fn register_interchain_account_cli( chain_id: &str, command_path: &str, home_path: &str, diff --git a/tools/test-framework/src/chain/ext/ica.rs b/tools/test-framework/src/chain/ext/ica.rs index 6552e6f7a5..978e9649c7 100644 --- a/tools/test-framework/src/chain/ext/ica.rs +++ b/tools/test-framework/src/chain/ext/ica.rs @@ -1,12 +1,19 @@ -use crate::chain::cli::ica::{query_interchain_account, register_interchain_account}; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::chain::tracking::TrackedMsgs; +use ibc_relayer_types::applications::ics27_ica::msgs::register::MsgRegisterInterchainAccount; +use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_relayer_types::events::IbcEvent; +use ibc_relayer_types::tx_msg::Msg; + +use crate::chain::cli::ica::{query_interchain_account, register_interchain_account_cli}; use crate::chain::driver::ChainDriver; use crate::error::Error; -use crate::prelude::TaggedConnectionIdRef; +use crate::prelude::*; use crate::types::tagged::*; use crate::types::wallet::WalletAddress; pub trait InterchainAccountMethodsExt { - fn register_interchain_account( + fn register_interchain_account_cli( &self, from: &MonoTagged, connection_id: &TaggedConnectionIdRef, @@ -20,13 +27,13 @@ pub trait InterchainAccountMethodsExt { } impl<'a, Chain: Send> InterchainAccountMethodsExt for MonoTagged { - fn register_interchain_account( + fn register_interchain_account_cli( &self, from: &MonoTagged, connection_id: &TaggedConnectionIdRef, ) -> Result<(), Error> { let driver = *self.value(); - register_interchain_account( + register_interchain_account_cli( driver.chain_id.as_str(), &driver.command_path, &driver.home_path, @@ -54,3 +61,49 @@ impl<'a, Chain: Send> InterchainAccountMethodsExt for MonoTagged( + chain: &MonoTagged, + handle: &Chain, + connection: &ConnectedConnection, +) -> Result< + ( + MonoTagged, + TaggedChannelId, + TaggedPortId, + ), + Error, +> { + let wallet = chain.wallets().relayer().cloned(); + + let owner = handle.get_signer()?; + + let version_str = format!("{{\"version\":\"ics27-1\",\"encoding\":\"proto3\",\"tx_type\":\"sdk_multi_msg\",\"controller_connection_id\":\"{}\",\"host_connection_id\":\"{}\"}}", connection.connection_id_a.0, connection.connection_id_b.0); + let msg = MsgRegisterInterchainAccount { + owner, + connection_id: connection.connection_id_a.0.clone(), + version: Version::new(version_str), + }; + + let msg_any = msg.to_any(); + + let tm = TrackedMsgs::new_static(vec![msg_any], "RegisterInterchainAccount"); + + let events = handle + .send_messages_and_wait_commit(tm) + .map_err(Error::relayer)?; + + for event in events.iter() { + if let IbcEvent::OpenInitChannel(open_init) = &event.event { + let channel_id = open_init.channel_id.clone().ok_or(()).map_err(|_| Error::generic(eyre!("channel_id is empty in the event response after sending MsgRegisterInterchainAccount")))?; + return Ok(( + wallet, + TaggedChannelId::new(channel_id), + TaggedPortId::new(open_init.port_id.clone()), + )); + } + } + + Err(Error::generic(eyre!("could not retrieve an OpenInitChannel event resonse after sending MsgRegisterInterchainAccount"))) +}