diff --git a/.changelog/unreleased/testing/3857-batch-events-tests.md b/.changelog/unreleased/testing/3857-batch-events-tests.md new file mode 100644 index 0000000000..293d6cb6ca --- /dev/null +++ b/.changelog/unreleased/testing/3857-batch-events-tests.md @@ -0,0 +1,2 @@ +- Added testing for batched tx events. + ([\#3857](https://github.com/anoma/namada/pull/3857)) \ No newline at end of file diff --git a/crates/events/src/extend.rs b/crates/events/src/extend.rs index ae631110cb..fdab393adc 100644 --- a/crates/events/src/extend.rs +++ b/crates/events/src/extend.rs @@ -542,20 +542,6 @@ impl EventAttributeEntry<'static> for MaspDataRefs { } } -/// Extend an [`Event`] with success data. -pub struct Success(pub bool); - -impl EventAttributeEntry<'static> for Success { - type Value = bool; - type ValueOwned = Self::Value; - - const KEY: &'static str = "success"; - - fn into_value(self) -> Self::Value { - self.0 - } -} - /// Extend an [`Event`] with a new domain. pub struct Domain(PhantomData); diff --git a/crates/node/src/shell/finalize_block.rs b/crates/node/src/shell/finalize_block.rs index 48165a8548..807b543624 100644 --- a/crates/node/src/shell/finalize_block.rs +++ b/crates/node/src/shell/finalize_block.rs @@ -1271,6 +1271,7 @@ mod test_finalize_block { use std::num::NonZeroU64; use std::str::FromStr; + use namada_apps_lib::wallet::defaults::albert_keypair; use namada_replay_protection as replay_protection; use namada_sdk::address; use namada_sdk::collections::{HashMap, HashSet}; @@ -1285,6 +1286,7 @@ mod test_finalize_block { }; use namada_sdk::eth_bridge::MinimumConfirmations; use namada_sdk::ethereum_events::{EthAddress, Uint as ethUint}; + use namada_sdk::events::extend::Log; use namada_sdk::events::Event; use namada_sdk::gas::VpGasMeter; use namada_sdk::governance::storage::keys::get_proposal_execution_key; @@ -1318,7 +1320,7 @@ mod test_finalize_block { use namada_sdk::tendermint::abci::types::{Misbehavior, MisbehaviorKind}; use namada_sdk::time::DurationSecs; use namada_sdk::token::{ - read_balance, update_balance, Amount, DenominatedAmount, + read_balance, read_denom, update_balance, Amount, DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES, }; use namada_sdk::tx::data::Fee; @@ -3471,7 +3473,6 @@ mod test_finalize_block { let (mut shell, _broadcaster, _, _) = setup(); let keypair = namada_apps_lib::wallet::defaults::bertha_keypair(); let mut out_of_gas_wrapper = { - let tx_code = TestWasms::TxNoOp.read_bytes(); let mut wrapper_tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { @@ -3487,21 +3488,11 @@ mod test_finalize_block { wrapper_tx.set_data(Data::new( "Encrypted transaction data".as_bytes().to_owned(), )); - wrapper_tx.set_code(Code::new(tx_code, None)); - wrapper_tx.add_section(Section::Authorization(Authorization::new( - wrapper_tx.sechashes(), - [(0, keypair.clone())].into_iter().collect(), - None, - ))); + wrapper_tx.add_code(TestWasms::TxNoOp.read_bytes(), None); + wrapper_tx.sign_wrapper(keypair.clone()); wrapper_tx }; - let mut wasm_path = top_level_directory(); - // Write a key to trigger the vp to validate the signature - wasm_path.push("wasm_for_tests/tx_write.wasm"); - let tx_code = std::fs::read(wasm_path) - .expect("Expected a file at given code path"); - let mut unsigned_wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { @@ -3517,26 +3508,20 @@ mod test_finalize_block { let mut failing_wrapper = unsigned_wrapper.clone(); - unsigned_wrapper.set_code(Code::new(tx_code, None)); let addr = Address::from(&keypair.to_public()); + // Write a key to trigger the vp to validate the signature let key = Key::from(addr.to_db_key()) .join(&Key::from("test".to_string().to_db_key())); - unsigned_wrapper.set_data(Data::new( - borsh::to_vec(&TxWriteData { + unsigned_wrapper + .add_code(TestWasms::TxWriteStorageKey.read_bytes(), None) + .add_data(TxWriteData { key, value: "test".as_bytes().to_owned(), - }) - .unwrap(), - )); + }); - let mut wasm_path = top_level_directory(); - wasm_path.push("wasm_for_tests/tx_fail.wasm"); - let tx_code = std::fs::read(wasm_path) - .expect("Expected a file at given code path"); - failing_wrapper.set_code(Code::new(tx_code, None)); - failing_wrapper.set_data(Data::new( - "Encrypted transaction data".as_bytes().to_owned(), - )); + failing_wrapper + .add_code(TestWasms::TxFail.read_bytes(), None) + .add_data("Encrypted transaction data"); let mut wrong_commitment_wrapper = failing_wrapper.clone(); let tx_code = TestWasms::TxInvalidData.read_bytes(); @@ -3915,6 +3900,86 @@ mod test_finalize_block { } } + // Test that paying fees with a whitelisted token which is not the native + // one is accepted + #[test] + fn test_fee_payment_whitelisted_token() { + let (mut shell, _, _, _) = setup(); + let btc = namada_sdk::address::testing::btc(); + let btc_denom = read_denom(&shell.state, &btc).unwrap().unwrap(); + let fee_amount: Amount = WRAPPER_GAS_LIMIT.into(); + + // Credit some tokens for fee payment + namada_sdk::token::credit_tokens( + &mut shell.state, + &btc, + &Address::from(&albert_keypair().to_public()), + fee_amount, + ) + .unwrap(); + let balance = read_balance( + &shell.state, + &btc, + &Address::from(&albert_keypair().to_public()), + ) + .unwrap(); + assert_eq!(balance, fee_amount.clone()); + + // Whitelist BTC for fee payment + let gas_cost_key = namada_sdk::parameters::storage::get_gas_cost_key(); + let mut gas_prices: BTreeMap = + shell.read_storage_key(&gas_cost_key).unwrap(); + gas_prices.insert(btc.clone(), 1.into()); + shell.shell.state.write(&gas_cost_key, gas_prices).unwrap(); + shell.commit(); + + // Submit tx + let mut tx = Tx::new(shell.chain_id.clone(), None); + tx.update_header(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount_per_gas_unit: DenominatedAmount::new( + 1.into(), + btc_denom, + ), + token: btc.clone(), + }, + albert_keypair().ref_to(), + WRAPPER_GAS_LIMIT.into(), + )))); + tx.add_code(TestWasms::TxNoOp.read_bytes(), None) + .add_data("Transaction data"); + tx.sign_wrapper(albert_keypair()); + + let processed_tx = ProcessedTx { + tx: tx.to_bytes().into(), + result: TxResult { + code: ResultCode::Ok.into(), + info: "".into(), + }, + }; + + let event = &shell + .finalize_block(FinalizeBlock { + txs: vec![processed_tx], + ..Default::default() + }) + .expect("Test failed")[0]; + + // Check fee payment + assert_eq!(*event.kind(), APPLIED_TX); + let code = event.read_attribute::().expect("Test failed"); + assert_eq!(code, ResultCode::Ok); + + // Check fee payer balance + let balance = read_balance( + &shell.state, + &btc, + &Address::from(&albert_keypair().to_public()), + ) + .unwrap(); + assert_eq!(balance, 0.into()); + } + // Test that the fees collected from a block are withdrew from the wrapper // signer and credited to the block proposer #[test] @@ -3939,10 +4004,6 @@ mod test_finalize_block { ) .unwrap(); - let mut wasm_path = top_level_directory(); - wasm_path.push("wasm_for_tests/tx_no_op.wasm"); - let tx_code = std::fs::read(wasm_path) - .expect("Expected a file at given code path"); let mut wrapper = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { @@ -3953,15 +4014,10 @@ mod test_finalize_block { 5_000_000.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); - wrapper.set_code(Code::new(tx_code, None)); - wrapper.set_data(Data::new("Transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, namada_apps_lib::wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper + .add_code(TestWasms::TxNoOp.read_bytes(), None) + .add_data("Transaction data"); + wrapper.sign_wrapper(albert_keypair()); let fee_amount = wrapper.header().wrapper().unwrap().get_tx_fee().unwrap(); let fee_amount = namada_sdk::token::denom_to_amount( @@ -6039,21 +6095,99 @@ mod test_finalize_block { batch.header.atomic = false; // append first inner tx to batch - batch.set_code(Code::new(TestWasms::TxNoOp.read_bytes(), None)); - batch.set_data(Data::new("bing".as_bytes().to_owned())); + batch + .add_code(TestWasms::TxNoOpEvent.read_bytes(), None) + .add_data("bing"); // append second inner tx to batch batch.push_default_inner_tx(); + batch + .add_code(TestWasms::TxNoOpEvent.read_bytes(), None) + .add_data("bong"); + + // sign the batch of txs + batch.sign_wrapper(sk); + + batch + }; + + let processed_txs = vec![ProcessedTx { + tx: batch_tx.to_bytes().into(), + result: TxResult { + code: ResultCode::Ok.into(), + info: "".into(), + }, + }]; + + let mut events = shell + .finalize_block(FinalizeBlock { + txs: processed_txs, + ..Default::default() + }) + .expect("Test failed"); + + // three top level events + assert_eq!(events.len(), 3); + + // tx events. Check that they are present and in the correct order + // TODO(namada#3856): right now we lose events ordering in a batch + // because of the BTreeSet we use in BatchedTxResult so we can only + // check the presence of the events but not the order + let mut unordered_events = vec![]; + let event = events.remove(0); + let msg = event.read_attribute::().unwrap(); + unordered_events.push(msg.as_str()); + + let event = events.remove(0); + let msg = event.read_attribute::().unwrap(); + unordered_events.push(msg.as_str()); + + assert!(unordered_events.contains(&"bing")); + assert!(unordered_events.contains(&"bong")); + + // multiple tx results (2) + let tx_event = events.remove(0); + let tx_results = tx_event.read_attribute::>().unwrap(); + assert_eq!(tx_results.len(), 2); + + // all txs should have succeeded + assert!(tx_results.are_results_successfull()); + } + + #[test] + fn test_multiple_identical_events_from_batch_tx_all_valid() { + const EVENT_MSG: &str = "bing"; + let (mut shell, _, _, _) = setup(); + + let sk = wallet::defaults::bertha_keypair(); + + let batch_tx = { + let mut batch = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount_per_gas_unit: DenominatedAmount::native( + 1.into(), + ), + token: shell.state.in_mem().native_token.clone(), + }, + sk.ref_to(), + WRAPPER_GAS_LIMIT.into(), + )))); + batch.header.chain_id = shell.chain_id.clone(); + batch.header.atomic = false; + + // append first inner tx to batch + batch + .add_code(TestWasms::TxNoOpEvent.read_bytes(), None) + .add_data(EVENT_MSG); - batch.set_code(Code::new(TestWasms::TxNoOp.read_bytes(), None)); - batch.set_data(Data::new("bong".as_bytes().to_owned())); + // append second inner tx to batch + batch.push_default_inner_tx(); + batch + .add_code(TestWasms::TxNoOpEvent.read_bytes(), None) + .add_data(EVENT_MSG); // sign the batch of txs - batch.sign_raw( - vec![sk.clone()], - vec![sk.ref_to()].into_iter().collect(), - None, - ); batch.sign_wrapper(sk); batch @@ -6074,12 +6208,25 @@ mod test_finalize_block { }) .expect("Test failed"); - // one top level event - assert_eq!(events.len(), 1); + // three top level events + assert_eq!(events.len(), 3); + + // tx events. Check that they are both present even if they are + // identical. This is important because some inner txs of a single batch + // may correctly emit identical events and we don't want them to + // overshadow each other which could deceive external tools like + // indexers let event = events.remove(0); + let msg = event.read_attribute::().unwrap(); + assert_eq!(&msg, EVENT_MSG); + + let event = events.remove(0); + let msg = event.read_attribute::().unwrap(); + assert_eq!(&msg, EVENT_MSG); // multiple tx results (2) - let tx_results = event.read_attribute::>().unwrap(); + let tx_event = events.remove(0); + let tx_results = tx_event.read_attribute::>().unwrap(); assert_eq!(tx_results.len(), 2); // all txs should have succeeded @@ -6108,25 +6255,16 @@ mod test_finalize_block { batch.header.atomic = false; // append first inner tx to batch (this one is valid) - batch.set_code(Code::new(TestWasms::TxNoOp.read_bytes(), None)); - batch.set_data(Data::new("bing".as_bytes().to_owned())); + batch + .add_code(TestWasms::TxNoOpEvent.read_bytes(), None) + .add_data("bing"); - // append second inner tx to batch (this one is invalid, because - // we pass the wrong data) + // append second inner tx to batch (this one is invalid) batch.push_default_inner_tx(); + batch + .add_code(TestWasms::TxFailEvent.read_bytes(), None) + .add_data("bong"); - batch.set_code(Code::new( - TestWasms::TxWriteStorageKey.read_bytes(), - None, - )); - batch.set_data(Data::new("bong".as_bytes().to_owned())); - - // sign the batch of txs - batch.sign_raw( - vec![sk.clone()], - vec![sk.ref_to()].into_iter().collect(), - None, - ); batch.sign_wrapper(sk); batch @@ -6147,12 +6285,18 @@ mod test_finalize_block { }) .expect("Test failed"); - // one top level event - assert_eq!(events.len(), 1); + // two top level events + assert_eq!(events.len(), 2); + + // tx events. Check the expected ones are present and in the correct + // order let event = events.remove(0); + let msg = event.read_attribute::().unwrap(); + assert_eq!(&msg, "bing"); // multiple tx results (2) - let tx_results = event.read_attribute::>().unwrap(); + let tx_event = events.remove(0); + let tx_results = tx_event.read_attribute::>().unwrap(); assert_eq!(tx_results.len(), 2); // check one succeeded and the other failed @@ -6160,6 +6304,75 @@ mod test_finalize_block { assert!(tx_results.are_any_err()); } + #[test] + fn test_multiple_events_from_atomic_batch_tx_one_valid_other_invalid() { + let (mut shell, _, _, _) = setup(); + + let sk = wallet::defaults::bertha_keypair(); + + let batch_tx = { + let mut batch = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount_per_gas_unit: DenominatedAmount::native( + 1.into(), + ), + token: shell.state.in_mem().native_token.clone(), + }, + sk.ref_to(), + WRAPPER_GAS_LIMIT.into(), + )))); + batch.header.chain_id = shell.chain_id.clone(); + batch.header.atomic = true; + + // append first inner tx to batch (this one is valid) + batch + .add_code(TestWasms::TxNoOpEvent.read_bytes(), None) + .add_data("bing"); + + // append second inner tx to batch (this one is invalid) + batch.push_default_inner_tx(); + batch + .add_code(TestWasms::TxFailEvent.read_bytes(), None) + .add_data("bong"); + + batch.sign_wrapper(sk); + + batch + }; + + let processed_txs = vec![ProcessedTx { + tx: batch_tx.to_bytes().into(), + result: TxResult { + code: ResultCode::Ok.into(), + info: "".into(), + }, + }]; + + let mut events = shell + .finalize_block(FinalizeBlock { + txs: processed_txs, + ..Default::default() + }) + .expect("Test failed"); + + // one top level event (no tx events, only tx result) + assert_eq!(events.len(), 1); + + // multiple tx results (2) + let tx_event = events.remove(0); + let tx_results = tx_event.read_attribute::>().unwrap(); + assert_eq!(tx_results.len(), 2); + + // check one succeeded and the other failed but the entire batch failed + assert!(tx_results.are_any_ok()); + assert!(tx_results.are_any_err()); + let result_code = tx_event + .read_attribute::() + .unwrap(); + assert_eq!(result_code, ResultCode::WasmRuntimeError); + } + /// DI indirection pub fn read_pos_params( storage: &S, diff --git a/crates/node/src/shell/mod.rs b/crates/node/src/shell/mod.rs index ea27241312..0cb72eed43 100644 --- a/crates/node/src/shell/mod.rs +++ b/crates/node/src/shell/mod.rs @@ -2052,6 +2052,7 @@ pub mod test_utils { #[cfg(test)] mod shell_tests { + use std::collections::BTreeMap; use std::fs::File; use eth_bridge::storage::eth_bridge_queries::is_bridge_comptime_enabled; @@ -2061,7 +2062,7 @@ mod shell_tests { use namada_sdk::token::read_denom; use namada_sdk::tx::data::protocol::{ProtocolTx, ProtocolTxType}; use namada_sdk::tx::data::Fee; - use namada_sdk::tx::{Authorization, Code, Data, Signed}; + use namada_sdk::tx::{Code, Data, Signed}; use namada_vote_ext::{ bridge_pool_roots, ethereum_events, ethereum_tx_data_variants, }; @@ -2073,7 +2074,7 @@ mod shell_tests { use crate::shell::token::DenominatedAmount; use crate::storage::{DbSnapshot, PersistentDB, SnapshotPath}; - const GAS_LIMIT_MULTIPLIER: u64 = 100_000; + const GAS_LIMIT: u64 = 100_000; /// Check that the shell broadcasts validator set updates, /// even when the Ethereum oracle is not running (e.g. @@ -2378,11 +2379,7 @@ mod shell_tests { // invalid tx type, it doesn't match the // tx type declared in the header tx.set_data(Data::new(ext.serialize_to_vec())); - tx.add_section(Section::Authorization(Authorization::new( - tx.sechashes(), - [(0, protocol_key)].into_iter().collect(), - None, - ))); + tx.sign_wrapper(protocol_key); tx } .to_bytes(); @@ -2451,13 +2448,7 @@ mod shell_tests { .set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); invalid_wrapper .set_data(Data::new("transaction data".as_bytes().to_owned())); - invalid_wrapper.add_section(Section::Authorization( - Authorization::new( - invalid_wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ), - )); + invalid_wrapper.sign_wrapper(keypair); // we mount a malleability attack to try and remove the fee let mut new_wrapper = @@ -2514,18 +2505,12 @@ mod shell_tests { token: shell.state.in_mem().native_token.clone(), }, wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper.sign_wrapper(wallet::defaults::albert_keypair()); // Write wrapper hash to storage let mut batch = namada_sdk::state::testing::TestState::batch(); @@ -2574,15 +2559,9 @@ mod shell_tests { token: shell.state.in_mem().native_token.clone(), }, wallet::defaults::bertha_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, wallet::defaults::bertha_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper.sign_wrapper(wallet::defaults::bertha_keypair()); let batch_hash = wrapper.raw_header_hash(); // Write batch hash in storage let batch_hash_key = replay_protection::current_key(&batch_hash); @@ -2690,11 +2669,7 @@ mod shell_tests { wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); let result = shell.mempool_validate( wrapper.to_bytes().as_ref(), @@ -2721,11 +2696,7 @@ mod shell_tests { wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); let result = shell.mempool_validate( wrapper.to_bytes().as_ref(), @@ -2753,18 +2724,12 @@ mod shell_tests { token: address::testing::apfel(), }, wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper.sign_wrapper(wallet::defaults::albert_keypair()); let result = shell.mempool_validate( wrapper.to_bytes().as_ref(), @@ -2773,6 +2738,65 @@ mod shell_tests { assert_eq!(result.code, ResultCode::FeeError.into()); } + // Check that a wrapper using a whitelisted non-native token for fee payment + // is accepted + #[test] + fn test_fee_whitelisted_non_native_token() { + let (mut shell, _recv, _, _) = test_utils::setup(); + let apfel_denom = read_denom(&shell.state, &address::testing::apfel()) + .expect("unable to read denomination from storage") + .expect("unable to find denomination of apfels"); + let fee_amount: token::Amount = GAS_LIMIT.into(); + + // Credit some tokens for fee payment + namada_sdk::token::credit_tokens( + &mut shell.state, + &address::testing::apfel(), + &Address::from(&wallet::defaults::albert_keypair().to_public()), + fee_amount, + ) + .unwrap(); + let balance = token::read_balance( + &shell.state, + &address::testing::apfel(), + &Address::from(&wallet::defaults::albert_keypair().to_public()), + ) + .unwrap(); + assert_eq!(balance, fee_amount.clone()); + + // Whitelist Apfel for fee payment + let gas_cost_key = namada_sdk::parameters::storage::get_gas_cost_key(); + let mut gas_prices: BTreeMap = + shell.read_storage_key(&gas_cost_key).unwrap(); + gas_prices.insert(address::testing::apfel(), 1.into()); + shell.shell.state.write(&gas_cost_key, gas_prices).unwrap(); + shell.commit(); + + // Submit tx + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount_per_gas_unit: DenominatedAmount::new( + 1.into(), + apfel_denom, + ), + token: address::testing::apfel(), + }, + wallet::defaults::albert_keypair().ref_to(), + GAS_LIMIT.into(), + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.sign_wrapper(wallet::defaults::albert_keypair()); + + let result = shell.mempool_validate( + wrapper.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, ResultCode::Ok.into()); + } + // Check that a wrapper setting a fee amount lower than the minimum required // is rejected #[test] @@ -2786,18 +2810,12 @@ mod shell_tests { token: shell.state.in_mem().native_token.clone(), }, wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper.sign_wrapper(wallet::defaults::albert_keypair()); let result = shell.mempool_validate( wrapper.to_bytes().as_ref(), @@ -2825,13 +2843,7 @@ mod shell_tests { wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper.sign_wrapper(wallet::defaults::albert_keypair()); let result = shell.mempool_validate( wrapper.to_bytes().as_ref(), @@ -2854,18 +2866,12 @@ mod shell_tests { token: shell.state.in_mem().native_token.clone(), }, wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper.sign_wrapper(wallet::defaults::albert_keypair()); let result = shell.mempool_validate( wrapper.to_bytes().as_ref(), @@ -2899,17 +2905,14 @@ mod shell_tests { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper .set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new(vec![0; size as usize])); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); + wrapper }; diff --git a/crates/node/src/shell/prepare_proposal.rs b/crates/node/src/shell/prepare_proposal.rs index f5271c1115..aaaa32418e 100644 --- a/crates/node/src/shell/prepare_proposal.rs +++ b/crates/node/src/shell/prepare_proposal.rs @@ -421,7 +421,7 @@ where // TODO(namada#3249): write tests for validator set update vote extensions in // prepare proposals mod test_prepare_proposal { - use std::collections::BTreeSet; + use std::collections::{BTreeMap, BTreeSet}; use namada_apps_lib::wallet; use namada_replay_protection as replay_protection; @@ -439,7 +439,7 @@ mod test_prepare_proposal { }; use namada_sdk::token::read_denom; use namada_sdk::tx::data::{Fee, TxType}; - use namada_sdk::tx::{Authorization, Code, Data, Section, Signed}; + use namada_sdk::tx::{Code, Data, Signed}; use namada_sdk::{address, governance, token}; use namada_vote_ext::{ethereum_events, ethereum_tx_data_variants}; @@ -465,7 +465,7 @@ mod test_prepare_proposal { assert!(rsp.code != 0.into(), "{}", rsp.log); } - const GAS_LIMIT_MULTIPLIER: u64 = 300_000; + const GAS_LIMIT: u64 = 300_000; /// Test that if a tx from the mempool is not a /// WrapperTx type, it is not included in the @@ -783,11 +783,7 @@ mod test_prepare_proposal { wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); // Write wrapper hash to storage let wrapper_unsigned_hash = wrapper.header_hash(); @@ -820,16 +816,12 @@ mod test_prepare_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); let req = RequestPrepareProposal { txs: vec![wrapper.to_bytes().into(); 2], @@ -860,11 +852,7 @@ mod test_prepare_proposal { wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); let inner_unsigned_hash = wrapper.raw_header_hash(); // Write inner hash to storage @@ -898,7 +886,7 @@ mod test_prepare_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); let tx_code = Code::new("wasm_code".as_bytes().to_owned(), None); @@ -906,11 +894,7 @@ mod test_prepare_proposal { let tx_data = Data::new("transaction data".as_bytes().to_owned()); wrapper.set_data(tx_data); let mut new_wrapper = wrapper.clone(); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); new_wrapper.update_header(TxType::Wrapper(Box::new(WrapperTx::new( Fee { @@ -918,13 +902,9 @@ mod test_prepare_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair_2.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); - new_wrapper.add_section(Section::Authorization(Authorization::new( - new_wrapper.sechashes(), - [(0, keypair_2)].into_iter().collect(), - None, - ))); + new_wrapper.sign_wrapper(keypair_2); let req = RequestPrepareProposal { txs: vec![wrapper.to_bytes().into(), new_wrapper.to_bytes().into()], @@ -953,11 +933,7 @@ mod test_prepare_proposal { wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper_tx .set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper_tx.add_section(Section::Authorization(Authorization::new( - wrapper_tx.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper_tx.sign_wrapper(keypair); #[allow(clippy::disallowed_methods)] let time = DateTimeUtc::now(); @@ -973,7 +949,6 @@ mod test_prepare_proposal { ..Default::default() }; let result = shell.prepare_proposal(req); - eprintln!("Proposal: {:?}", result.txs); assert_eq!(result.txs.len(), 0); } @@ -1000,11 +975,7 @@ mod test_prepare_proposal { wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper_tx .set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper_tx.add_section(Section::Authorization(Authorization::new( - wrapper_tx.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper_tx.sign_wrapper(keypair); let req = RequestPrepareProposal { txs: vec![wrapper_tx.to_bytes().into()], @@ -1013,7 +984,6 @@ mod test_prepare_proposal { ..Default::default() }; let result = shell.prepare_proposal(req); - eprintln!("Proposal: {:?}", result.txs); assert!(result.txs.is_empty()); } @@ -1038,11 +1008,7 @@ mod test_prepare_proposal { wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper_tx .set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper_tx.add_section(Section::Authorization(Authorization::new( - wrapper_tx.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper_tx.sign_wrapper(keypair); let req = RequestPrepareProposal { txs: vec![wrapper_tx.to_bytes().into()], @@ -1051,11 +1017,10 @@ mod test_prepare_proposal { ..Default::default() }; let result = shell.prepare_proposal(req); - eprintln!("Proposal: {:?}", result.txs); assert!(result.txs.is_empty()); } - // Check that a wrapper using a token not accepted byt the validator for fee + // Check that a wrapper using a token not accepted by the validator for fee // payment is not included in the block #[test] fn test_fee_non_accepted_token() { @@ -1087,7 +1052,7 @@ mod test_prepare_proposal { token: address::testing::btc(), }, namada_apps_lib::wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), ); let mut wrapper_tx = Tx::from_type(TxType::Wrapper(Box::new(wrapper))); @@ -1095,13 +1060,8 @@ mod test_prepare_proposal { wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper_tx .set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper_tx.add_section(Section::Authorization(Authorization::new( - wrapper_tx.sechashes(), - [(0, namada_apps_lib::wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper_tx + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); let req = RequestPrepareProposal { txs: vec![wrapper_tx.to_bytes().into()], @@ -1110,7 +1070,6 @@ mod test_prepare_proposal { ..Default::default() }; let result = shell.prepare_proposal(req); - eprintln!("Proposal: {:?}", result.txs); assert!(result.txs.is_empty()); } @@ -1133,7 +1092,7 @@ mod test_prepare_proposal { token: address::testing::apfel(), }, namada_apps_lib::wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), ); let mut wrapper_tx = Tx::from_type(TxType::Wrapper(Box::new(wrapper))); @@ -1141,13 +1100,8 @@ mod test_prepare_proposal { wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper_tx .set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper_tx.add_section(Section::Authorization(Authorization::new( - wrapper_tx.sechashes(), - [(0, namada_apps_lib::wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper_tx + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); let req = RequestPrepareProposal { txs: vec![wrapper_tx.to_bytes().into()], @@ -1156,10 +1110,74 @@ mod test_prepare_proposal { ..Default::default() }; let result = shell.prepare_proposal(req); - eprintln!("Proposal: {:?}", result.txs); assert!(result.txs.is_empty()); } + // Check that a wrapper using a whitelisted non-native token for fee payment + // is included in the block + #[test] + fn test_fee_whitelisted_non_native_token() { + let (mut shell, _recv, _, _) = test_utils::setup(); + + let apfel_denom = read_denom(&shell.state, &address::testing::apfel()) + .expect("unable to read denomination from storage") + .expect("unable to find denomination of apfels"); + let fee_amount: token::Amount = GAS_LIMIT.into(); + + // Credit some tokens for fee payment + namada_sdk::token::credit_tokens( + &mut shell.state, + &address::testing::apfel(), + &Address::from(&wallet::defaults::albert_keypair().to_public()), + fee_amount, + ) + .unwrap(); + let balance = token::read_balance( + &shell.state, + &address::testing::apfel(), + &Address::from(&wallet::defaults::albert_keypair().to_public()), + ) + .unwrap(); + assert_eq!(balance, fee_amount.clone()); + + // Whitelist Apfel for fee payment + let gas_cost_key = namada_sdk::parameters::storage::get_gas_cost_key(); + let mut gas_prices: BTreeMap = + shell.read_storage_key(&gas_cost_key).unwrap(); + gas_prices.insert(address::testing::apfel(), 1.into()); + shell.shell.state.write(&gas_cost_key, gas_prices).unwrap(); + shell.commit(); + + let wrapper = WrapperTx::new( + Fee { + amount_per_gas_unit: DenominatedAmount::new( + 1.into(), + apfel_denom, + ), + token: address::testing::apfel(), + }, + namada_apps_lib::wallet::defaults::albert_keypair().ref_to(), + GAS_LIMIT.into(), + ); + + let mut wrapper_tx = Tx::from_type(TxType::Wrapper(Box::new(wrapper))); + wrapper_tx.header.chain_id = shell.chain_id.clone(); + wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); + wrapper_tx + .set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper_tx + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); + + let req = RequestPrepareProposal { + txs: vec![wrapper_tx.to_bytes().into()], + max_tx_bytes: 0, + time: None, + ..Default::default() + }; + let result = shell.prepare_proposal(req); + assert_eq!(result.txs.first().unwrap(), &wrapper_tx.to_bytes()); + } + // Check that a wrapper setting a fee amount lower than the minimum accepted // by the validator is not included in the block #[test] @@ -1185,20 +1203,15 @@ mod test_prepare_proposal { token: shell.state.in_mem().native_token.clone(), }, namada_apps_lib::wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), ); let mut wrapper_tx = Tx::from_type(TxType::Wrapper(Box::new(wrapper))); wrapper_tx.header.chain_id = shell.chain_id.clone(); wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper_tx .set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper_tx.add_section(Section::Authorization(Authorization::new( - wrapper_tx.sechashes(), - [(0, namada_apps_lib::wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper_tx + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); let req = RequestPrepareProposal { txs: vec![wrapper_tx.to_bytes().into()], @@ -1207,7 +1220,6 @@ mod test_prepare_proposal { ..Default::default() }; let result = shell.prepare_proposal(req); - eprintln!("Proposal: {:?}", result.txs); assert!(result.txs.is_empty()); } @@ -1223,20 +1235,15 @@ mod test_prepare_proposal { token: shell.state.in_mem().native_token.clone(), }, namada_apps_lib::wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), ); let mut wrapper_tx = Tx::from_type(TxType::Wrapper(Box::new(wrapper))); wrapper_tx.header.chain_id = shell.chain_id.clone(); wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper_tx .set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper_tx.add_section(Section::Authorization(Authorization::new( - wrapper_tx.sechashes(), - [(0, namada_apps_lib::wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper_tx + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); let req = RequestPrepareProposal { txs: vec![wrapper_tx.to_bytes().into()], @@ -1245,7 +1252,6 @@ mod test_prepare_proposal { ..Default::default() }; let result = shell.prepare_proposal(req); - eprintln!("Proposal: {:?}", result.txs); assert!(result.txs.is_empty()); } @@ -1262,20 +1268,15 @@ mod test_prepare_proposal { token: shell.state.in_mem().native_token.clone(), }, namada_apps_lib::wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), ); let mut wrapper_tx = Tx::from_type(TxType::Wrapper(Box::new(wrapper))); wrapper_tx.header.chain_id = shell.chain_id.clone(); wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper_tx .set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper_tx.add_section(Section::Authorization(Authorization::new( - wrapper_tx.sechashes(), - [(0, namada_apps_lib::wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper_tx + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); let req = RequestPrepareProposal { txs: vec![wrapper_tx.to_bytes().into()], @@ -1284,7 +1285,6 @@ mod test_prepare_proposal { ..Default::default() }; let result = shell.prepare_proposal(req); - eprintln!("Proposal: {:?}", result.txs); assert!(result.txs.is_empty()); } @@ -1301,20 +1301,15 @@ mod test_prepare_proposal { token: shell.state.in_mem().native_token.clone(), }, namada_apps_lib::wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), ); let mut wrapper_tx = Tx::from_type(TxType::Wrapper(Box::new(wrapper))); wrapper_tx.header.chain_id = shell.chain_id.clone(); wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper_tx .set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper_tx.add_section(Section::Authorization(Authorization::new( - wrapper_tx.sechashes(), - [(0, namada_apps_lib::wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper_tx + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); let req = RequestPrepareProposal { txs: vec![wrapper_tx.to_bytes().into()], @@ -1323,7 +1318,6 @@ mod test_prepare_proposal { ..Default::default() }; let result = shell.prepare_proposal(req); - eprintln!("Proposal: {:?}", result.txs); assert!(result.txs.is_empty()); } diff --git a/crates/node/src/shell/process_proposal.rs b/crates/node/src/shell/process_proposal.rs index 1485b06f15..26fc9af0ed 100644 --- a/crates/node/src/shell/process_proposal.rs +++ b/crates/node/src/shell/process_proposal.rs @@ -570,6 +570,8 @@ where // process proposals #[cfg(test)] mod test_process_proposal { + use std::collections::BTreeMap; + use namada_apps_lib::wallet; use namada_replay_protection as replay_protection; use namada_sdk::address; @@ -580,7 +582,7 @@ mod test_process_proposal { use namada_sdk::state::StorageWrite; use namada_sdk::token::{read_denom, Amount, DenominatedAmount}; use namada_sdk::tx::data::Fee; - use namada_sdk::tx::{Authorization, Code, Data, Signed}; + use namada_sdk::tx::{Code, Data, Signed}; use namada_vote_ext::{ bridge_pool_roots, ethereum_events, validator_set_update, }; @@ -592,7 +594,7 @@ mod test_process_proposal { }; use crate::shims::abcipp_shim_types::shim::request::ProcessedTx; - const GAS_LIMIT_MULTIPLIER: u64 = 100_000; + const GAS_LIMIT: u64 = 100_000; /// Check that we reject a validator set update protocol tx /// if the bridge is not active. @@ -886,7 +888,7 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, public_key, - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); outer_tx.header.chain_id = shell.chain_id.clone(); outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); @@ -936,16 +938,12 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); outer_tx.header.chain_id = shell.chain_id.clone(); outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); - outer_tx.add_section(Section::Authorization(Authorization::new( - outer_tx.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + outer_tx.sign_wrapper(keypair); let mut new_tx = outer_tx.clone(); if let TxType::Wrapper(wrapper) = &mut new_tx.header.tx_type { // we mount a malleability attack to try and remove the fee @@ -1008,16 +1006,12 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); outer_tx.header.chain_id = shell.chain_id.clone(); outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); - outer_tx.add_section(Section::Authorization(Authorization::new( - outer_tx.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + outer_tx.sign_wrapper(keypair); let response = { let request = ProcessProposal { @@ -1072,16 +1066,12 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); outer_tx.header.chain_id = shell.chain_id.clone(); outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); - outer_tx.add_section(Section::Authorization(Authorization::new( - outer_tx.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + outer_tx.sign_wrapper(keypair); let response = { let request = ProcessProposal { @@ -1164,16 +1154,12 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); // Write wrapper hash to storage let mut batch = namada_sdk::state::testing::TestState::batch(); @@ -1232,16 +1218,12 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); // Run validation let request = ProcessProposal { @@ -1284,16 +1266,12 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); // Write inner hash to storage let mut batch = namada_sdk::state::testing::TestState::batch(); @@ -1343,17 +1321,13 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); let mut new_wrapper = wrapper.clone(); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); new_wrapper.update_header(TxType::Wrapper(Box::new(WrapperTx::new( Fee { @@ -1361,13 +1335,9 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair_2.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); - new_wrapper.add_section(Section::Authorization(Authorization::new( - new_wrapper.sechashes(), - [(0, keypair_2)].into_iter().collect(), - None, - ))); + new_wrapper.sign_wrapper(keypair_2); // Run validation let request = ProcessProposal { @@ -1395,17 +1365,13 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); let wrong_chain_id = ChainId("Wrong chain id".to_string()); wrapper.header.chain_id = wrong_chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); let protocol_tx = EthereumTxData::EthEventsVext({ @@ -1455,17 +1421,13 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.header.expiration = Some(DateTimeUtc::default()); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); // Run validation let request = ProcessProposal { @@ -1504,11 +1466,7 @@ mod test_process_proposal { wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); // Run validation let request = ProcessProposal { @@ -1544,11 +1502,7 @@ mod test_process_proposal { wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); // Run validation let request = ProcessProposal { @@ -1585,18 +1539,13 @@ mod test_process_proposal { token: address::testing::apfel(), }, namada_apps_lib::wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, namada_apps_lib::wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); // Run validation let request = ProcessProposal { @@ -1613,6 +1562,66 @@ mod test_process_proposal { } } + // Check that a wrapper using a whitelisted non-native token for fee payment + // is accepted + #[test] + fn test_fee_whitelisted_non_native_token() { + let (mut shell, _recv, _, _) = test_utils::setup(); + + let apfel_denom = read_denom(&shell.state, &address::testing::apfel()) + .expect("unable to read denomination from storage") + .expect("unable to find denomination of apfels"); + let fee_amount: token::Amount = GAS_LIMIT.into(); + + // Credit some tokens for fee payment + namada_sdk::token::credit_tokens( + &mut shell.state, + &address::testing::apfel(), + &Address::from(&wallet::defaults::albert_keypair().to_public()), + fee_amount, + ) + .unwrap(); + let balance = token::read_balance( + &shell.state, + &address::testing::apfel(), + &Address::from(&wallet::defaults::albert_keypair().to_public()), + ) + .unwrap(); + assert_eq!(balance, fee_amount.clone()); + + // Whitelist Apfel for fee payment + let gas_cost_key = namada_sdk::parameters::storage::get_gas_cost_key(); + let mut gas_prices: BTreeMap = + shell.read_storage_key(&gas_cost_key).unwrap(); + gas_prices.insert(address::testing::apfel(), 1.into()); + shell.shell.state.write(&gas_cost_key, gas_prices).unwrap(); + shell.commit(); + + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount_per_gas_unit: DenominatedAmount::new( + 1.into(), + apfel_denom, + ), + token: address::testing::apfel(), + }, + namada_apps_lib::wallet::defaults::albert_keypair().ref_to(), + GAS_LIMIT.into(), + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); + + // Run validation + let request = ProcessProposal { + txs: vec![wrapper.to_bytes()], + }; + assert!(shell.process_proposal(request).is_ok()); + } + // Check that a wrapper setting a fee amount lower than the minimum required // causes a block rejection #[test] @@ -1626,18 +1635,13 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, namada_apps_lib::wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, namada_apps_lib::wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); // Run validation let request = ProcessProposal { @@ -1674,13 +1678,8 @@ mod test_process_proposal { wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, namada_apps_lib::wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); // Run validation let request = ProcessProposal { @@ -1712,18 +1711,13 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, namada_apps_lib::wallet::defaults::albert_keypair().ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, namada_apps_lib::wallet::defaults::albert_keypair())] - .into_iter() - .collect(), - None, - ))); + wrapper + .sign_wrapper(namada_apps_lib::wallet::defaults::albert_keypair()); // Run validation let request = ProcessProposal { @@ -1766,17 +1760,13 @@ mod test_process_proposal { token: shell.state.in_mem().native_token.clone(), }, keypair.ref_to(), - GAS_LIMIT_MULTIPLIER.into(), + GAS_LIMIT.into(), )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper .set_code(Code::new("wasm_code".as_bytes().to_owned(), None)); wrapper.set_data(Data::new(vec![0; size as usize])); - wrapper.add_section(Section::Authorization(Authorization::new( - wrapper.sechashes(), - [(0, keypair)].into_iter().collect(), - None, - ))); + wrapper.sign_wrapper(keypair); wrapper }; diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index e519a8dd41..6972135863 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -19,8 +19,10 @@ pub const WASM_FOR_TESTS_DIR: &str = "wasm_for_tests"; #[derive(Debug, Clone, Copy, EnumIter)] pub enum TestWasms { TxFail, + TxFailEvent, TxMemoryLimit, TxNoOp, + TxNoOpEvent, TxInvalidData, TxInfiniteGuestGas, TxInfiniteHostGas, @@ -44,8 +46,10 @@ impl TestWasms { pub fn path(&self) -> PathBuf { let filename = match self { TestWasms::TxFail => "tx_fail.wasm", + TestWasms::TxFailEvent => "tx_fail_event.wasm", TestWasms::TxMemoryLimit => "tx_memory_limit.wasm", TestWasms::TxNoOp => "tx_no_op.wasm", + TestWasms::TxNoOpEvent => "tx_no_op_event.wasm", TestWasms::TxInvalidData => "tx_invalid_data.wasm", TestWasms::TxInfiniteGuestGas => "tx_infinite_guest_gas.wasm", TestWasms::TxInfiniteHostGas => "tx_infinite_host_gas.wasm", diff --git a/crates/tx_prelude/src/lib.rs b/crates/tx_prelude/src/lib.rs index edfb8a5a25..5e7cbd2edb 100644 --- a/crates/tx_prelude/src/lib.rs +++ b/crates/tx_prelude/src/lib.rs @@ -38,7 +38,10 @@ use namada_core::internal::HostEnvResult; use namada_core::key::common; use namada_core::storage::TxIndex; pub use namada_core::{address, encode, eth_bridge_pool, storage, *}; -use namada_events::{EmitEvents, Event, EventToEmit, EventType}; +pub use namada_events::extend::Log; +pub use namada_events::{ + EmitEvents, Event, EventLevel, EventToEmit, EventType, +}; pub use namada_governance::storage as gov_storage; pub use namada_macros::transaction; pub use namada_parameters::storage as parameters_storage; diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index 115e1cf71f..90e2f85c51 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -3693,6 +3693,15 @@ dependencies = [ "rlsf", ] +[[package]] +name = "tx_fail_event" +version = "0.41.0" +dependencies = [ + "getrandom", + "namada_tx_prelude", + "rlsf", +] + [[package]] name = "tx_infinite_guest_gas" version = "0.41.0" @@ -3738,6 +3747,15 @@ dependencies = [ "rlsf", ] +[[package]] +name = "tx_no_op_event" +version = "0.41.0" +dependencies = [ + "getrandom", + "namada_tx_prelude", + "rlsf", +] + [[package]] name = "tx_proposal_code" version = "0.41.0" diff --git a/wasm_for_tests/Cargo.toml b/wasm_for_tests/Cargo.toml index 34b65d16a5..e37543c2ef 100644 --- a/wasm_for_tests/Cargo.toml +++ b/wasm_for_tests/Cargo.toml @@ -3,11 +3,13 @@ resolver = "2" members = [ "tx_fail", + "tx_fail_event", "tx_infinite_guest_gas", "tx_infinite_host_gas", "tx_invalid_data", "tx_memory_limit", "tx_no_op", + "tx_no_op_event", "tx_proposal_code", "tx_proposal_ibc_token_inflation", "tx_proposal_masp_reward", diff --git a/wasm_for_tests/Makefile b/wasm_for_tests/Makefile index 93ba19f33b..4fd8f83485 100644 --- a/wasm_for_tests/Makefile +++ b/wasm_for_tests/Makefile @@ -6,11 +6,13 @@ nightly := $(shell cat ../rust-nightly-version) # All the wasms that can be built from this source, switched via Cargo features # Wasms can be added via the Cargo.toml `[features]` list. wasms := tx_fail +wasms += tx_fail_event wasms += tx_infinite_guest_gas wasms += tx_infinite_host_gas wasms += tx_invalid_data wasms += tx_memory_limit wasms += tx_no_op +wasms += tx_no_op_event wasms += tx_proposal_code wasms += tx_proposal_ibc_token_inflation wasms += tx_proposal_masp_reward diff --git a/wasm_for_tests/tx_fail_event/Cargo.toml b/wasm_for_tests/tx_fail_event/Cargo.toml new file mode 100644 index 0000000000..f42d5046e2 --- /dev/null +++ b/wasm_for_tests/tx_fail_event/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tx_fail_event" +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_fail_event/src/lib.rs b/wasm_for_tests/tx_fail_event/src/lib.rs new file mode 100644 index 0000000000..a67391322d --- /dev/null +++ b/wasm_for_tests/tx_fail_event/src/lib.rs @@ -0,0 +1,14 @@ +use namada_tx_prelude::*; + +#[transaction] +fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { + // Emit an event with the message contained in the transaction's data + let data = ctx.get_tx_data(&tx_data)?; + let data_str = String::try_from_slice(&data[..]) + .wrap_err("Failed to decode String tx data")?; + let mut event = Event::new(EventType::new("test"), EventLevel::Tx); + event.extend(Log(data_str)); + ctx.emit_event(event).wrap_err("Failed to emit event")?; + + Err(Error::SimpleMessage("failed tx")) +} diff --git a/wasm_for_tests/tx_no_op_event/Cargo.toml b/wasm_for_tests/tx_no_op_event/Cargo.toml new file mode 100644 index 0000000000..22aadd955e --- /dev/null +++ b/wasm_for_tests/tx_no_op_event/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tx_no_op_event" +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_no_op_event/src/lib.rs b/wasm_for_tests/tx_no_op_event/src/lib.rs new file mode 100644 index 0000000000..d6a2b5ea70 --- /dev/null +++ b/wasm_for_tests/tx_no_op_event/src/lib.rs @@ -0,0 +1,14 @@ +use namada_tx_prelude::*; + +#[transaction] +fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { + // Emit an event with the message contained in the transaction's data + let data = ctx.get_tx_data(&tx_data)?; + let data_str = String::try_from_slice(&data[..]) + .wrap_err("Failed to decode String tx data")?; + let mut event = Event::new(EventType::new("test"), EventLevel::Tx); + event.extend(Log(data_str)); + ctx.emit_event(event).wrap_err("Failed to emit event")?; + + Ok(()) +}