diff --git a/.changelog/unreleased/testing/3866-test-ibc-gas-payment.md b/.changelog/unreleased/testing/3866-test-ibc-gas-payment.md new file mode 100644 index 0000000000..ff5b37e018 --- /dev/null +++ b/.changelog/unreleased/testing/3866-test-ibc-gas-payment.md @@ -0,0 +1,2 @@ +- Added test for gas payment with an IBC token. + ([\#3866](https://github.com/anoma/namada/pull/3866)) \ No newline at end of file diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index 5a4f302dbf..b1233f5916 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -2,6 +2,7 @@ "e2e::eth_bridge_tests::everything": 4, "e2e::ibc_tests::ibc_transfers": 414, "e2e::ibc_tests::pgf_over_ibc": 415, + "e2e::ibc_tests::fee_payment_with_ibc_token": 357, "e2e::ibc_tests::ibc_token_inflation": 840, "e2e::ibc_tests::ibc_rate_limit": 485, "e2e::ibc_tests::ibc_upgrade_client": 280, diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index 9b97fd2d95..9720f19bc8 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -1789,7 +1789,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about(wrap!( - "Query the substorage space of a specific enstablished \ + "Query the substorage space of a specific established \ address." )) .add_args::>() diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 3e03cac17e..bd44791c32 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -2265,7 +2265,7 @@ pub async fn build_become_validator( ); if !tx_args.force { return Err(Error::Other( - "The given address must be enstablished".to_string(), + "The given address must be established".to_string(), )); } }; diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 6972135863..0f1f17581d 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -29,6 +29,7 @@ pub enum TestWasms { TxProposalCode, TxProposalMaspRewards, TxProposalIbcTokenInflation, + TxProposalTokenGas, TxReadStorageKey, TxWriteStorageKey, VpAlwaysFalse, @@ -58,6 +59,7 @@ impl TestWasms { TestWasms::TxProposalIbcTokenInflation => { "tx_proposal_ibc_token_inflation.wasm" } + TestWasms::TxProposalTokenGas => "tx_proposal_token_gas.wasm", TestWasms::TxReadStorageKey => "tx_read_storage_key.wasm", TestWasms::TxWriteStorageKey => "tx_write.wasm", TestWasms::VpAlwaysFalse => "vp_always_false.wasm", diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index cf0f4c8747..63060b5a4a 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -40,8 +40,8 @@ use setup::constants::*; use sha2::{Digest, Sha256}; use crate::e2e::helpers::{ - epochs_per_year_from_min_duration, find_address, find_gaia_address, - get_actor_rpc, get_epoch, get_gaia_gov_address, + epoch_sleep, epochs_per_year_from_min_duration, find_address, + find_gaia_address, get_actor_rpc, get_epoch, get_gaia_gov_address, }; use crate::e2e::ledger_tests::{ start_namada_ledger_node_wait_wasm, write_json_file, @@ -234,6 +234,7 @@ fn ibc_transfers() -> Result<()> { &ibc_denom_on_namada, 50, ALBERT_KEY, + &[], )?; check_balance(&test, AA_VIEWING_KEY, &ibc_denom_on_namada, 50)?; check_balance(&test, AB_VIEWING_KEY, &ibc_denom_on_namada, 50)?; @@ -479,6 +480,7 @@ fn pgf_over_ibc() -> Result<()> { NAM, 100, ALBERT_KEY, + &[], )?; // Proposal on Namada @@ -488,8 +490,7 @@ fn pgf_over_ibc() -> Result<()> { let mut epoch = get_epoch(&test, &rpc).unwrap(); let delegated = epoch + PIPELINE_LEN; while epoch < delegated { - sleep(5); - epoch = get_epoch(&test, &rpc).unwrap(); + epoch = epoch_sleep(&test, &rpc, 120)?; } // funding proposal let continuous_receiver = find_gaia_address(&test_gaia, GAIA_RELAYER)?; @@ -504,16 +505,14 @@ fn pgf_over_ibc() -> Result<()> { let mut epoch = get_epoch(&test, &rpc).unwrap(); // Vote while epoch < start_epoch { - sleep(5); - epoch = get_epoch(&test, &rpc).unwrap(); + epoch = epoch_sleep(&test, &rpc, 120)?; } submit_votes(&test)?; // wait for the grace let grace_epoch = start_epoch + 6u64; while epoch < grace_epoch { - sleep(5); - epoch = get_epoch(&test, &rpc).unwrap(); + epoch = epoch_sleep(&test, &rpc, 120)?; } wait_for_packet_relay(&port_id_namada, &channel_id_namada, &test)?; @@ -526,6 +525,108 @@ fn pgf_over_ibc() -> Result<()> { Ok(()) } +// Test fee payment with an ibc token +// +// 1. Submit governance proposal to allow fee payment with the IBC token +// 2. Transfer some IBC tokens from gaia +// 3. Transparent transfer in Namada with ibc token gas payment +#[test] +fn fee_payment_with_ibc_token() -> Result<()> { + const PIPELINE_LEN: u64 = 2; + let update_genesis = + |mut genesis: templates::All, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(30); + genesis.parameters.ibc_params.default_mint_limit = + Amount::max_signed(); + genesis.parameters.gov_params.min_proposal_grace_epochs = 1; + genesis + .parameters + .ibc_params + .default_per_epoch_throughput_limit = Amount::max_signed(); + // Artificially increase the gas scale to allow for fee payment with + // the limited ibc tokens available + genesis.parameters.parameters.gas_scale = 10_000_000; + setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) + }; + let (ledger, gaia, test, test_gaia) = run_namada_gaia(update_genesis)?; + let _bg_ledger = ledger.background(); + let _bg_gaia = gaia.background(); + + // Proposal on Namada + // Delegate some token + delegate_token(&test)?; + let rpc = get_actor_rpc(&test, Who::Validator(0)); + let mut epoch = get_epoch(&test, &rpc).unwrap(); + let delegated = epoch + PIPELINE_LEN; + while epoch < delegated { + epoch = epoch_sleep(&test, &rpc, 120)?; + } + // ibc gas token proposal on Namada + let start_epoch = propose_gas_token(&test)?; + let mut epoch = get_epoch(&test, &rpc).unwrap(); + // Vote + while epoch < start_epoch { + epoch = epoch_sleep(&test, &rpc, 120)?; + } + submit_votes(&test)?; + + // Create an IBC channel while waiting the grace epoch + setup_hermes(&test, &test_gaia)?; + let (channel_id_namada, channel_id_gaia) = + create_channel_with_hermes(&test, &test_gaia)?; + let port_id_gaia = "transfer".parse().unwrap(); + let port_id_namada = "transfer"; + let ibc_token_on_namada = + format!("{port_id_namada}/{channel_id_namada}/{GAIA_COIN}"); + // Start relaying + let hermes = run_hermes(&test)?; + let _bg_hermes = hermes.background(); + + // wait for the grace + let grace_epoch = start_epoch + 4u64; + while epoch < grace_epoch { + epoch = epoch_sleep(&test, &rpc, 120)?; + } + + // Transfer 250 samoleans from Gaia to Namada + let namada_receiver = find_address(&test, ALBERT_KEY)?.to_string(); + transfer_from_gaia( + &test_gaia, + GAIA_USER, + &namada_receiver, + GAIA_COIN, + 250, + &port_id_gaia, + &channel_id_gaia, + None, + None, + )?; + wait_for_packet_relay(&port_id_gaia, &channel_id_gaia, &test)?; + + // Check the token on Namada + check_balance(&test, ALBERT_KEY, &ibc_token_on_namada, 250)?; + check_gaia_balance(&test_gaia, GAIA_USER, GAIA_COIN, 750)?; + + // Transparent transfer in Namada paying gas with samoleans + transfer_on_chain( + &test, + "transparent-transfer", + ALBERT, + CHRISTEL, + NAM, + 50, + ALBERT_KEY, + &["--gas-token", &ibc_token_on_namada, "--gas-limit", "250"], + )?; + check_balance(&test, ALBERT, NAM, 1_999_950)?; + check_balance(&test, CHRISTEL, NAM, 2_000_050)?; + check_balance(&test, ALBERT_KEY, &ibc_token_on_namada, 0)?; + check_balance(&test, "validator-0", &ibc_token_on_namada, 250)?; + + Ok(()) +} + /// IBC token inflation test /// - Propose the inflation of an IBC token received from Gaia /// - Shielding transfer of the token from Gaia @@ -560,20 +661,14 @@ fn ibc_token_inflation() -> Result<()> { let mut epoch = get_epoch(&test, &rpc).unwrap(); let delegated = epoch + PIPELINE_LEN; while epoch < delegated { - sleep(10); - #[allow(clippy::disallowed_methods)] - let new_epoch = get_epoch(&test, &rpc).unwrap_or_default(); - epoch = new_epoch; + epoch = epoch_sleep(&test, &rpc, 120)?; } // inflation proposal on Namada let start_epoch = propose_inflation(&test)?; let mut epoch = get_epoch(&test, &rpc).unwrap(); // Vote while epoch < start_epoch { - sleep(10); - #[allow(clippy::disallowed_methods)] - let new_epoch = get_epoch(&test, &rpc).unwrap_or_default(); - epoch = new_epoch; + epoch = epoch_sleep(&test, &rpc, 120)?; } submit_votes(&test)?; @@ -590,8 +685,7 @@ fn ibc_token_inflation() -> Result<()> { // wait for the grace let grace_epoch = start_epoch + 6u64; while epoch < grace_epoch { - sleep(5); - epoch = get_epoch(&test, &rpc).unwrap(); + epoch = epoch_sleep(&test, &rpc, 120)?; } // Check the target balance is zero before the inflation @@ -622,10 +716,7 @@ fn ibc_token_inflation() -> Result<()> { let mut epoch = get_epoch(&test, &rpc).unwrap(); let new_epoch = epoch + MASP_EPOCH_MULTIPLIER; while epoch < new_epoch { - sleep(10); - #[allow(clippy::disallowed_methods)] - let new_epoch = get_epoch(&test, &rpc).unwrap_or_default(); - epoch = new_epoch; + epoch = epoch_sleep(&test, &rpc, 120)?; } // Check balances @@ -726,8 +817,7 @@ fn ibc_rate_limit() -> Result<()> { let mut epoch = get_epoch(&test, &rpc).unwrap(); let next_epoch = epoch.next(); while epoch <= next_epoch { - sleep(5); - epoch = get_epoch(&test, &rpc).unwrap(); + epoch = epoch_sleep(&test, &rpc, 120)?; } // Transfer 1 NAM from Namada to Gaia @@ -770,8 +860,7 @@ fn ibc_rate_limit() -> Result<()> { let mut epoch = get_epoch(&test, &rpc).unwrap(); let next_epoch = epoch.next(); while epoch <= next_epoch { - sleep(5); - epoch = get_epoch(&test, &rpc).unwrap(); + epoch = epoch_sleep(&test, &rpc, 120)?; } // Transfer 1 NAM from Namada to Gaia will succeed in the new epoch @@ -1052,10 +1141,11 @@ fn transfer_on_chain( token: impl AsRef, amount: u64, signer: impl AsRef, + extra_args: &[&str], ) -> Result<()> { let rpc = get_actor_rpc(test, Who::Validator(0)); let amount = amount.to_string(); - let tx_args = apply_use_device(vec![ + let mut tx_args = apply_use_device(vec![ kind.as_ref(), "--source", sender.as_ref(), @@ -1070,6 +1160,7 @@ fn transfer_on_chain( "--node", &rpc, ]); + tx_args.extend_from_slice(extra_args); let mut client = run!(test, Bin::Client, tx_args, Some(120))?; client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); @@ -1367,6 +1458,50 @@ fn propose_upgrade_client( Ok(()) } +fn propose_gas_token(test: &Test) -> Result { + let albert = find_address(test, ALBERT)?; + let rpc = get_actor_rpc(test, Who::Validator(0)); + let epoch = get_epoch(test, &rpc)?; + let start_epoch = (epoch.0 + 3) / 3 * 3; + let proposal_json = serde_json::json!({ + "proposal": { + "content": { + "title": "IBC token gas", + "authors": "test@test.com", + "discussions-to": "www.github.com/anoma/aip/1", + "created": "2022-03-10T08:54:37Z", + "license": "MIT", + "abstract": "IBC token gas", + "motivation": "IBC token gas", + "details": "IBC token gas", + "requires": "2" + }, + "author": albert, + "voting_start_epoch": start_epoch, + "voting_end_epoch": start_epoch + 3_u64, + "activation_epoch": start_epoch + 4_u64, + }, + "data": TestWasms::TxProposalTokenGas.read_bytes() + }); + + let proposal_json_path = test.test_dir.path().join("proposal.json"); + write_json_file(proposal_json_path.as_path(), proposal_json); + + let submit_proposal_args = apply_use_device(vec![ + "init-proposal", + "--data-path", + proposal_json_path.to_str().unwrap(), + "--gas-limit", + "10000000", + "--node", + &rpc, + ]); + let mut client = run!(test, Bin::Client, submit_proposal_args, Some(100))?; + client.exp_string(TX_APPLIED_SUCCESS)?; + client.assert_success(); + Ok(start_epoch.into()) +} + fn wait_for_pass(test: &Test) -> Result<()> { let args = ["query", "gov", "proposal", "1"]; for _ in 0..10 { diff --git a/crates/tx/src/data/wrapper.rs b/crates/tx/src/data/wrapper.rs index eaaffbca84..1f030dd67f 100644 --- a/crates/tx/src/data/wrapper.rs +++ b/crates/tx/src/data/wrapper.rs @@ -190,8 +190,8 @@ impl WrapperTx { hasher } - /// Get the [`Amount`] of fees to be paid by the given wrapper. Returns - /// an error if the amount overflows + /// Get the [`DenominatedAmount`] of fees to be paid by the given wrapper. + /// Returns an error if the amount overflows pub fn get_tx_fee(&self) -> Result { self.fee .amount_per_gas_unit diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index 90e2f85c51..795c146a9e 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -3784,6 +3784,15 @@ dependencies = [ "rlsf", ] +[[package]] +name = "tx_proposal_token_gas" +version = "0.41.0" +dependencies = [ + "getrandom", + "namada_tx_prelude", + "rlsf", +] + [[package]] name = "tx_read_storage_key" version = "0.41.0" diff --git a/wasm_for_tests/Cargo.toml b/wasm_for_tests/Cargo.toml index e37543c2ef..13a9d70662 100644 --- a/wasm_for_tests/Cargo.toml +++ b/wasm_for_tests/Cargo.toml @@ -13,6 +13,7 @@ members = [ "tx_proposal_code", "tx_proposal_ibc_token_inflation", "tx_proposal_masp_reward", + "tx_proposal_token_gas", "tx_read_storage_key", "tx_write", "vp_always_false", diff --git a/wasm_for_tests/Makefile b/wasm_for_tests/Makefile index 4fd8f83485..5e92278698 100644 --- a/wasm_for_tests/Makefile +++ b/wasm_for_tests/Makefile @@ -16,6 +16,7 @@ wasms += tx_no_op_event wasms += tx_proposal_code wasms += tx_proposal_ibc_token_inflation wasms += tx_proposal_masp_reward +wasms += tx_proposal_token_gas wasms += tx_read_storage_key wasms += tx_write wasms += vp_always_false diff --git a/wasm_for_tests/tx_proposal_token_gas/Cargo.toml b/wasm_for_tests/tx_proposal_token_gas/Cargo.toml new file mode 100644 index 0000000000..3d027b6319 --- /dev/null +++ b/wasm_for_tests/tx_proposal_token_gas/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tx_proposal_token_gas" +description = "Wasm transaction used for testing." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +namada_tx_prelude.workspace = true +rlsf.workspace = true +getrandom.workspace = true + +[lib] +crate-type = ["cdylib"] diff --git a/wasm_for_tests/tx_proposal_token_gas/src/lib.rs b/wasm_for_tests/tx_proposal_token_gas/src/lib.rs new file mode 100644 index 0000000000..bbf087da34 --- /dev/null +++ b/wasm_for_tests/tx_proposal_token_gas/src/lib.rs @@ -0,0 +1,18 @@ +use std::collections::BTreeMap; + +use namada_tx_prelude::{ + parameters_storage::get_gas_cost_key, token::Amount, *, +}; + +#[transaction] +fn apply_tx(ctx: &mut Ctx, _tx_data: BatchedTx) -> TxResult { + let ibc_token = ibc::ibc_token("transfer/channel-0/samoleans"); + + let gas_cost_key = get_gas_cost_key(); + let mut minimum_gas_price: BTreeMap = + ctx.read(&gas_cost_key)?.unwrap_or_default(); + minimum_gas_price.insert(ibc_token, 1.into()); + ctx.write(&gas_cost_key, minimum_gas_price)?; + + Ok(()) +}