diff --git a/.changelog/unreleased/improvements/4939-znam-ibc-shielding.md b/.changelog/unreleased/improvements/4939-znam-ibc-shielding.md new file mode 100644 index 00000000000..f40c15fdc1e --- /dev/null +++ b/.changelog/unreleased/improvements/4939-znam-ibc-shielding.md @@ -0,0 +1,4 @@ +- Allow IBC deposits into payment addresses (without generating MASP + transactions), and IBC withdraws from payment addresses (to automatically + get refunded, if an IBC unshielding transfer fails on the counter-party). + ([\#4939](https://github.com/namada-net/namada/pull/4939)) \ No newline at end of file diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index d965f0bdc55..eb5717fc9c6 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -1,5 +1,6 @@ { "e2e::eth_bridge_tests::everything": 4, + "e2e::ibc_tests::ibc_to_and_from_payment_addrs": 840, "e2e::ibc_tests::frontend_sus_fee": 350, "e2e::ibc_tests::ibc_transfers": 414, "e2e::ibc_tests::ibc_nft_transfers": 224, diff --git a/Cargo.lock b/Cargo.lock index ef51643cb77..90a39aa560d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5796,6 +5796,7 @@ dependencies = [ "ethabi", "ethbridge-structs", "eyre", + "group", "ibc", "ics23", "impl-num-traits", @@ -6316,7 +6317,6 @@ dependencies = [ "eyre", "flume", "futures", - "group", "itertools 0.14.0", "lazy_static", "leanbridgetree", diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index 32ec3e07b05..4ef66678053 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -3623,7 +3623,6 @@ pub mod args { arg_opt("output-folder-path"); pub const OSMOSIS_POOL_HOP: ArgMulti = arg_multi("pool-hop"); - pub const OVERFLOW_OPT: ArgOpt = arg_opt("overflow-addr"); pub const OWNER: Arg = arg("owner"); pub const OWNER_OPT: ArgOpt = OWNER.opt(); pub const PATH: Arg = arg("path"); @@ -5311,18 +5310,15 @@ pub mod args { Either::Left(r) => Either::Left(chain_ctx.get(&r)), Either::Right(r) => Either::Right(chain_ctx.get(&r)), }; - let overflow = self.overflow.map(|r| chain_ctx.get(&r)); Ok(TxOsmosisSwap { transfer: self.transfer.to_sdk(ctx)?, output_denom: self.output_denom, recipient, - overflow, slippage: self.slippage, local_recovery_addr: self.local_recovery_addr, route: self.route, osmosis_lcd_rpc: self.osmosis_lcd_rpc, osmosis_sqs_rpc: self.osmosis_sqs_rpc, - frontend_sus_fee: None, }) } } @@ -5336,7 +5332,6 @@ pub mod args { let maybe_trans_recipient = TARGET_OPT.parse(matches); let maybe_shielded_recipient = PAYMENT_ADDRESS_TARGET_OPT.parse(matches); - let maybe_overflow = OVERFLOW_OPT.parse(matches); let slippage_percent = SLIPPAGE.parse(matches); if slippage_percent.is_some_and(|percent| { let zero = Dec::zero(); @@ -5369,13 +5364,11 @@ pub mod args { } else { Either::Right(maybe_shielded_recipient.unwrap()) }, - overflow: maybe_overflow, slippage, local_recovery_addr, route, osmosis_lcd_rpc, osmosis_sqs_rpc, - frontend_sus_fee: None, } } @@ -5409,7 +5402,6 @@ pub mod args { .arg( TARGET_OPT .def() - .conflicts_with(OVERFLOW_OPT.name) .conflicts_with(PAYMENT_ADDRESS_TARGET_OPT.name) .help(wrap!( "Transparent Namada address that shall receive \ @@ -5425,15 +5417,6 @@ pub mod args { minimum amount of tokens swapped on Osmosis." )), ) - .arg(OVERFLOW_OPT.def().help(wrap!( - "Transparent address that receives the amount of target \ - asset exceeding the minimum trade amount. Only \ - applicable when shielding assets that have been swapped \ - on Osmosis. This address should not be linkable to any \ - of the user's personal accounts, to maximize the privacy \ - of the trade. If unspecified, a disposable address is \ - generated." - ))) .arg(SLIPPAGE.def().help(wrap!( "Slippage percentage, as a number between 0 and 100. \ Represents the maximum acceptable deviation from the \ diff --git a/crates/benches/native_vps.rs b/crates/benches/native_vps.rs index 3c0c78a7711..9badc66949f 100644 --- a/crates/benches/native_vps.rs +++ b/crates/benches/native_vps.rs @@ -41,12 +41,12 @@ use namada_apps_lib::masp_primitives::merkle_tree::CommitmentTree; use namada_apps_lib::masp_primitives::transaction::Transaction; use namada_apps_lib::masp_proofs::sapling::SaplingVerificationContextInner; use namada_apps_lib::proof_of_stake::KeySeg; -use namada_apps_lib::state::{Epoch, StorageRead, StorageWrite, TxIndex}; +use namada_apps_lib::state::{Epoch, StorageRead, StorageWrite}; use namada_apps_lib::token::masp::{ PVKs, partial_deauthorize, preload_verifying_keys, }; use namada_apps_lib::token::{Amount, Transfer}; -use namada_apps_lib::tx::{BatchedTx, Code, Section, Tx}; +use namada_apps_lib::tx::{BatchedTx, Code, IndexedTx, Section, Tx}; use namada_apps_lib::validation::{ EthBridgeNutVp, EthBridgePoolVp, EthBridgeVp, GovernanceVp, IbcVp, IbcVpContext, MaspVp, MultitokenVp, ParametersVp, PgfVp, PosVp, @@ -222,12 +222,13 @@ fn governance(c: &mut Criterion) { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); + let indexed_tx = IndexedTx::default(); let ctx = Ctx::new( &Address::Internal(InternalAddress::Governance), &shell.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -294,13 +295,14 @@ fn governance(c: &mut Criterion) { // .write_log // .verifiers_and_changed_keys(&verifiers_from_tx); +// let indexed_tx = IndexedTx::default(); // let slash_fund = SlashFundVp { // ctx: Ctx::new( // &Address::Internal(InternalAddress::SlashFund), // &shell.state.storage, // &shell.state.write_log, // &tx, -// &TxIndex(0), +// &indexed_tx, // // VpGasMeter::new_from_tx_meter(&TxGasMeter::new( // u64::MAX.into(), )), @@ -451,12 +453,13 @@ fn ibc(c: &mut Criterion) { &TxGasMeter::new(u64::MAX, 1), )); let shell_read = shielded_ctx.shell.read(); + let indexed_tx = IndexedTx::default(); let ibc = IbcVp::new(Ctx::new( &Address::Internal(InternalAddress::Ibc), &shell_read.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -519,12 +522,13 @@ fn vp_multitoken(c: &mut Criterion) { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); + let indexed_tx = IndexedTx::default(); let ctx = Ctx::new( &Address::Internal(InternalAddress::Multitoken), &shell.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -644,12 +648,13 @@ fn masp(c: &mut Criterion) { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); + let indexed_tx = IndexedTx::default(); let ctx = Ctx::new( &Address::Internal(InternalAddress::Masp), &shell_read.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1271,12 +1276,13 @@ fn pgf(c: &mut Criterion) { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); + let indexed_tx = IndexedTx::default(); let ctx = Ctx::new( &Address::Internal(InternalAddress::Pgf), &shell.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1349,12 +1355,13 @@ fn eth_bridge_nut(c: &mut Criterion) { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); + let indexed_tx = IndexedTx::default(); let ctx = Ctx::new( &vp_address, &shell.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1423,12 +1430,13 @@ fn eth_bridge(c: &mut Criterion) { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); + let indexed_tx = IndexedTx::default(); let ctx = Ctx::new( &vp_address, &shell.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1522,12 +1530,13 @@ fn eth_bridge_pool(c: &mut Criterion) { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); + let indexed_tx = IndexedTx::default(); let ctx = Ctx::new( &vp_address, &shell.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1596,12 +1605,13 @@ fn parameters(c: &mut Criterion) { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); + let indexed_tx = IndexedTx::default(); let ctx = Ctx::new( &vp_address, &shell.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1673,12 +1683,13 @@ fn pos(c: &mut Criterion) { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); + let indexed_tx = IndexedTx::default(); let ctx = Ctx::new( &vp_address, &shell.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1727,12 +1738,13 @@ fn ibc_vp_validate_action(c: &mut Criterion) { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); + let indexed_tx = IndexedTx::default(); let ibc = IbcVp::new(Ctx::new( &Address::Internal(InternalAddress::Ibc), &shell_read.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1745,17 +1757,20 @@ fn ibc_vp_validate_action(c: &mut Criterion) { let exec_ctx = IbcVpContext::new(ibc.ctx.pre()); let ctx = Rc::new(RefCell::new(exec_ctx)); - let mut actions = - IbcActions::<_, parameters::Store<_>, token::Store<()>>::new( - ctx.clone(), - verifiers.clone(), - ); + let mut actions = IbcActions::< + _, + parameters::Store<_>, + token::Store<_>, + token::ShieldedStore<_>, + >::new(ctx.clone(), verifiers.clone()); actions.set_validation_params(ibc.validation_params().unwrap()); - let module = create_transfer_middlewares::<_, parameters::Store<_>>( - ctx.clone(), - verifiers, - ); + let module = create_transfer_middlewares::< + _, + parameters::Store<_>, + token::Store<_>, + token::ShieldedStore<_>, + >(ctx.clone(), verifiers); actions.add_transfer_module(module); let module = NftTransferModule::<_, token::Store<()>>::new(ctx); actions.add_transfer_module(module); @@ -1791,12 +1806,13 @@ fn ibc_vp_execute_action(c: &mut Criterion) { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); + let indexed_tx = IndexedTx::default(); let ibc = IbcVp::new(Ctx::new( &Address::Internal(InternalAddress::Ibc), &shell_read.state, &signed_tx.tx, &signed_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1810,17 +1826,20 @@ fn ibc_vp_execute_action(c: &mut Criterion) { let exec_ctx = IbcVpContext::new(ibc.ctx.pre()); let ctx = Rc::new(RefCell::new(exec_ctx)); - let mut actions = - IbcActions::<_, parameters::Store<_>, token::Store<()>>::new( - ctx.clone(), - verifiers.clone(), - ); + let mut actions = IbcActions::< + _, + parameters::Store<_>, + token::Store<_>, + token::ShieldedStore<_>, + >::new(ctx.clone(), verifiers.clone()); actions.set_validation_params(ibc.validation_params().unwrap()); - let module = create_transfer_middlewares::<_, parameters::Store<_>>( - ctx.clone(), - verifiers, - ); + let module = create_transfer_middlewares::< + _, + parameters::Store<_>, + token::Store<_>, + token::ShieldedStore<_>, + >(ctx.clone(), verifiers); actions.add_transfer_module(module); let module = NftTransferModule::<_, token::Store<()>>::new(ctx); actions.add_transfer_module(module); diff --git a/crates/benches/process_wrapper.rs b/crates/benches/process_wrapper.rs index 2d5b1636387..664f28bdfa3 100644 --- a/crates/benches/process_wrapper.rs +++ b/crates/benches/process_wrapper.rs @@ -1,6 +1,5 @@ use criterion::{Criterion, criterion_group, criterion_main}; use namada_apps_lib::key::RefTo; -use namada_apps_lib::state::TxIndex; use namada_apps_lib::time::DateTimeUtc; use namada_apps_lib::token::{Amount, DenominatedAmount, Transfer}; use namada_apps_lib::tx::Authorization; @@ -90,7 +89,7 @@ fn process_tx(c: &mut Criterion) { shell .check_proposal_tx( &wrapper, - &TxIndex::default(), + Default::default(), validation_meta, temp_state, datetime, diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 9d3ce842959..811493e16a6 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -46,6 +46,7 @@ ed25519-consensus.workspace = true ethabi.workspace = true ethbridge-structs.workspace = true eyre.workspace = true +group.workspace = true ibc.workspace = true ics23.workspace = true impl-num-traits.workspace = true diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index 1861f66fccb..fca4d4f1757 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -2,12 +2,14 @@ use std::collections::BTreeMap; use std::fmt::Display; +use std::io::{Read, Write}; use std::num::ParseIntError; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXUPPER; use masp_primitives::asset_type::AssetType; -use masp_primitives::sapling::ViewingKey; +use masp_primitives::sapling::{Diversifier, Note, ViewingKey}; use masp_primitives::transaction::TransparentAddress; pub use masp_primitives::transaction::{ Transaction as MaspTransaction, TxId as TxIdInner, @@ -554,6 +556,39 @@ impl PaymentAddress { // hex of the first 40 chars of the hash format!("{:.width$X}", hasher.finalize(), width = HASH_HEX_LEN) } + + /// Encode a payment address in compatibility mode (i.e. with the legacy + /// Bech32 encoding) + pub fn encode_compat(&self) -> String { + use crate::string_encoding::Format; + + bech32::encode::(Self::HRP, self.to_bytes().as_ref()) + .unwrap_or_else(|_| { + panic!( + "The human-readable part {} should never cause a failure", + Self::HRP + ) + }) + } + + /// Create a note owned by this payment address + /// + /// ## Caution + /// + /// The value of `rseed` must be unique between each note appended + /// to the commitment tree, in order to avoid nullifier collisions, + /// when generating notes deterministically. + pub fn create_note( + &self, + asset_type: AssetType, + value: u64, + rseed: [u8; 32], + ) -> Option { + use masp_primitives::sapling::Rseed; + + self.0 + .create_note(asset_type, value, Rseed::AfterZip212(rseed)) + } } impl From for masp_primitives::sapling::PaymentAddress { @@ -953,11 +988,273 @@ impl FromStr for MaspValue { } } +/// Compact representation of a [`Note`]. +#[derive(Debug, Clone, Copy, BorshSerialize, BorshDeserialize)] +#[allow(missing_docs)] +pub struct CompactNote { + pub asset_type: AssetType, + pub value: u64, + pub diversifier: Diversifier, + #[borsh( + serialize_with = "compact_note_pk_d_borsh::serialize", + deserialize_with = "compact_note_pk_d_borsh::deserialize" + )] + pub pk_d: masp_primitives::jubjub::SubgroupPoint, + pub rseed: masp_primitives::sapling::Rseed, +} + +mod compact_note_pk_d_borsh { + #![allow(missing_docs)] + + use group::GroupEncoding; + + use super::*; + + pub fn serialize( + pk_d: &masp_primitives::jubjub::SubgroupPoint, + writer: &mut W, + ) -> std::io::Result<()> { + BorshSerialize::serialize(&pk_d.to_bytes(), writer) + } + + pub fn deserialize( + reader: &mut R, + ) -> std::io::Result { + masp_primitives::jubjub::SubgroupPoint::from_bytes( + &<[u8; 32] as BorshDeserialize>::deserialize_reader(reader)?, + ) + .into_option() + .ok_or_else(|| std::io::Error::other("Invalid pk_d in CompactNote")) + } +} + +impl serde::Serialize for CompactNote { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + let encoded = HEXUPPER.encode(&self.serialize_to_vec()); + serde::Serialize::serialize(&encoded, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for CompactNote { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + + let encoded: String = serde::Deserialize::deserialize(deserializer)?; + + let borsh_bytes = + HEXUPPER.decode(encoded.as_bytes()).map_err(Error::custom)?; + + ::try_from_slice(&borsh_bytes) + .map_err(Error::custom) + } +} + +impl CompactNote { + /// Create a compact version of a [`Note`]. + pub fn new( + note: Note, + pa: masp_primitives::sapling::PaymentAddress, + ) -> Option { + let g_d = pa.g_d()?; + let pk_d = *pa.pk_d(); + + if g_d != note.g_d || pk_d != note.pk_d { + return None; + } + + let diversifier = *pa.diversifier(); + let Note { + asset_type, + value, + pk_d, + rseed, + .. + } = note; + + Some(Self { + asset_type, + value, + diversifier, + pk_d, + rseed, + }) + } + + /// Convert this [`CompactNote`] back into a [`Note`]. + pub fn into_note(self) -> Option { + let g_d = self.diversifier.g_d()?; + + let Self { + asset_type, + value, + pk_d, + rseed, + .. + } = self; + + Some(Note { + asset_type, + value, + g_d, + pk_d, + rseed, + }) + } + + /// Extract a dummy [`MaspTransaction`], from unencrypted notes minted + /// by the protocol. + #[allow(clippy::result_large_err)] + pub fn extract_dummy_tx( + notes: &[Self], + ) -> std::io::Result { + use group::GroupEncoding; + use masp_primitives::consensus::{BranchId, MainNetwork}; + use masp_primitives::sapling::note_encryption::sapling_note_encryption; + use masp_primitives::transaction::components::{ + I128Sum, OutputDescription, sapling, transparent, + }; + use masp_primitives::transaction::{TransactionData, TxVersion}; + + // dummy signature + let sig = [0u8; 64]; + + let (transparent_inputs, shielded_outputs, sapling_value_balance) = + notes.iter().try_fold( + (vec![], vec![], I128Sum::zero()), + |(mut t, mut s, mut bal), cnote| { + const GROTH_PROOF_BYTES: usize = 48 + 96 + 48; + + let payment_addr = + masp_primitives::sapling::PaymentAddress::from_parts( + cnote.diversifier, + cnote.pk_d, + ) + .ok_or_else(|| std::io::Error::other("invalid payment address in CompactNote"))?; + let note = cnote.into_note().unwrap(); + + let note_ciphertexts = sapling_note_encryption::( + None, + note, + payment_addr, + masp_primitives::memo::MemoBytes::empty(), + ); + + t.push(transparent::TxIn { + asset_type: note.asset_type, + value: note.value, + address: TAddrData::Addr(IBC).taddress(), + transparent_sig: (), + }); + + s.push(OutputDescription { + cv: Default::default(), + cmu: note.cmu(), + ephemeral_key: note_ciphertexts.epk().to_bytes().into(), + enc_ciphertext: note_ciphertexts.encrypt_note_plaintext(), + out_ciphertext: [0; 80], + zkproof: [0u8; GROTH_PROOF_BYTES], + }); + + #[allow(clippy::arithmetic_side_effects)] + { + bal += I128Sum::from_pair(note.asset_type, -i128::from(note.value)); + } + + Ok::<_, std::io::Error>((t, s, bal)) + }, + )?; + + let data = TransactionData::from_parts( + TxVersion::MASPv5, + BranchId::MASP, + // bogus values for these two heights + 0, + 0.into(), + // transparent bundle - here we have transparent inputs + Some(transparent::Bundle { + vin: transparent_inputs, + vout: vec![], + authorization: transparent::Authorized, + }), + // sapling bundle - here we have shielded outputs + Some(sapling::Bundle { + shielded_spends: vec![], + shielded_converts: vec![], + shielded_outputs, + value_balance: sapling_value_balance, + authorization: sapling::Authorized { + binding_sig: + masp_primitives::sapling::redjubjub::Signature::read( + &mut sig.as_slice(), + ) + .expect("reading the sig shouldn't fail"), + }, + }), + ); + + data.freeze() + } +} + #[cfg(test)] mod test { use super::*; use crate::address; + #[test] + fn test_trial_decrypt_protocol_shielding() { + use masp_primitives::consensus::MainNetwork; + use masp_primitives::sapling::note_encryption::{ + PreparedIncomingViewingKey, try_sapling_note_decryption, + }; + use masp_primitives::transaction::components::OutputDescription; + use masp_primitives::transaction::{Authorization, Authorized}; + + type Proof = OutputDescription< + < + ::SaplingAuth + as masp_primitives::transaction::components::sapling::Authorization + >::Proof + >; + + let sk = ExtendedSpendingKey::from( + masp_primitives::zip32::ExtendedSpendingKey::master(&[0_u8]), + ); + let vk = sk.to_viewing_key().as_viewing_key(); + let (_, pa) = sk.0.default_address(); + + let cnote = CompactNote { + asset_type: AssetType::new(b"test").unwrap(), + value: 1234u64, + diversifier: *pa.diversifier(), + pk_d: *pa.pk_d(), + rseed: masp_primitives::sapling::Rseed::AfterZip212([0xff; 32]), + }; + + let dummy_tx = CompactNote::extract_dummy_tx(&[cnote]).unwrap(); + + for so in dummy_tx.sapling_bundle().unwrap().shielded_outputs.iter() { + assert!( + try_sapling_note_decryption::<_, Proof>( + &MainNetwork, + 1.into(), + &PreparedIncomingViewingKey::new(&vk.ivk()), + so, + ) + .is_some() + ); + } + } + #[test] fn test_extended_spending_key_serialize() { let sk = ExtendedSpendingKey::from( diff --git a/crates/core/src/storage.rs b/crates/core/src/storage.rs index 2ac9959df1f..41621d43da8 100644 --- a/crates/core/src/storage.rs +++ b/crates/core/src/storage.rs @@ -55,7 +55,7 @@ pub enum Error { pub type Result = std::result::Result; /// The length of the transaction index -pub const TX_INDEX_LENGTH: usize = 4; +pub const TX_INDEX_LENGTH: usize = 8 + 4 + 1 + 4; /// The length of the epoch type pub const EPOCH_TYPE_LENGTH: usize = 8; diff --git a/crates/core/src/token.rs b/crates/core/src/token.rs index de30d386a20..074d6d26ca9 100644 --- a/crates/core/src/token.rs +++ b/crates/core/src/token.rs @@ -1064,12 +1064,9 @@ impl From for IbcAmount { } } -impl TryFrom for Amount { - type Error = AmountParseError; - - fn try_from(amount: IbcAmount) -> Result { - let uint = Uint(primitive_types::U256::from(amount).0); - Self::from_uint(uint, 0) +impl From for Amount { + fn from(amount: IbcAmount) -> Self { + Uint(primitive_types::U256::from(amount).0).into() } } diff --git a/crates/ethereum_bridge/src/vp/bridge_pool_vp.rs b/crates/ethereum_bridge/src/vp/bridge_pool_vp.rs index ffe10c8fde9..0e21829a043 100644 --- a/crates/ethereum_bridge/src/vp/bridge_pool_vp.rs +++ b/crates/ethereum_bridge/src/vp/bridge_pool_vp.rs @@ -636,10 +636,10 @@ mod test_bridge_pool_vp { use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_state::testing::TestState; use namada_state::write_log::WriteLog; - use namada_state::{StateRead, StorageWrite, TxIndex}; + use namada_state::{StateRead, StorageWrite}; use namada_trans_token::storage_key::balance_key; - use namada_tx::Tx; use namada_tx::data::TxType; + use namada_tx::{IndexedTx, Tx}; use namada_vm::WasmCacheRwAccess; use namada_vm::wasm::VpCache; use namada_vm::wasm::run::VpEvalWasm; @@ -913,6 +913,7 @@ mod test_bridge_pool_vp { gas_meter: &'ctx RefCell, keys_changed: &'ctx BTreeSet, verifiers: &'ctx BTreeSet
, + indexed_tx: &'ctx IndexedTx, ) -> Ctx<'ctx, TestState> { let batched_tx = tx.batch_ref_first_tx().unwrap(); Ctx::new( @@ -920,7 +921,7 @@ mod test_bridge_pool_vp { state, batched_tx.tx, batched_tx.cmt, - &TxIndex(0), + indexed_tx, gas_meter, keys_changed, verifiers, @@ -1004,7 +1005,15 @@ mod test_bridge_pool_vp { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); - let ctx = setup_ctx(&tx, &state, &gas_meter, &keys_changed, &verifiers); + let indexed_tx = IndexedTx::default(); + let ctx = setup_ctx( + &tx, + &state, + &gas_meter, + &keys_changed, + &verifiers, + &indexed_tx, + ); let mut tx = Tx::new(state.in_mem().chain_id.clone(), None); tx.add_data(transfer); @@ -1360,7 +1369,15 @@ mod test_bridge_pool_vp { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); - let ctx = setup_ctx(&tx, &state, &gas_meter, &keys_changed, &verifiers); + let indexed_tx = IndexedTx::default(); + let ctx = setup_ctx( + &tx, + &state, + &gas_meter, + &keys_changed, + &verifiers, + &indexed_tx, + ); let mut tx = Tx::new(state.in_mem().chain_id.clone(), None); tx.add_data(transfer); @@ -1418,7 +1435,15 @@ mod test_bridge_pool_vp { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); - let ctx = setup_ctx(&tx, &state, &gas_meter, &keys_changed, &verifiers); + let indexed_tx = IndexedTx::default(); + let ctx = setup_ctx( + &tx, + &state, + &gas_meter, + &keys_changed, + &verifiers, + &indexed_tx, + ); let mut tx = Tx::new(state.in_mem().chain_id.clone(), None); tx.add_data(transfer); @@ -1497,7 +1522,15 @@ mod test_bridge_pool_vp { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); - let ctx = setup_ctx(&tx, &state, &gas_meter, &keys_changed, &verifiers); + let indexed_tx = IndexedTx::default(); + let ctx = setup_ctx( + &tx, + &state, + &gas_meter, + &keys_changed, + &verifiers, + &indexed_tx, + ); let mut tx = Tx::new(state.in_mem().chain_id.clone(), None); tx.add_data(transfer); @@ -1571,7 +1604,15 @@ mod test_bridge_pool_vp { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); - let ctx = setup_ctx(&tx, &state, &gas_meter, &keys_changed, &verifiers); + let indexed_tx = IndexedTx::default(); + let ctx = setup_ctx( + &tx, + &state, + &gas_meter, + &keys_changed, + &verifiers, + &indexed_tx, + ); let mut tx = Tx::new(state.in_mem().chain_id.clone(), None); tx.add_data(transfer); @@ -1662,7 +1703,15 @@ mod test_bridge_pool_vp { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); - let ctx = setup_ctx(&tx, &state, &gas_meter, &keys_changed, &verifiers); + let indexed_tx = IndexedTx::default(); + let ctx = setup_ctx( + &tx, + &state, + &gas_meter, + &keys_changed, + &verifiers, + &indexed_tx, + ); let mut tx = Tx::new(state.in_mem().chain_id.clone(), None); tx.add_data(transfer); @@ -1739,7 +1788,15 @@ mod test_bridge_pool_vp { let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(u64::MAX, 1), )); - let ctx = setup_ctx(&tx, &state, &gas_meter, &keys_changed, &verifiers); + let indexed_tx = IndexedTx::default(); + let ctx = setup_ctx( + &tx, + &state, + &gas_meter, + &keys_changed, + &verifiers, + &indexed_tx, + ); let mut tx = Tx::from_type(TxType::Raw); tx.push_default_inner_tx(); diff --git a/crates/ethereum_bridge/src/vp/eth_bridge_vp.rs b/crates/ethereum_bridge/src/vp/eth_bridge_vp.rs index eff83cac5b4..b564dcafdbc 100644 --- a/crates/ethereum_bridge/src/vp/eth_bridge_vp.rs +++ b/crates/ethereum_bridge/src/vp/eth_bridge_vp.rs @@ -158,10 +158,10 @@ mod tests { use namada_core::ethereum_events::EthAddress; use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_state::testing::TestState; - use namada_state::{StateRead, StorageWrite, TxIndex}; + use namada_state::{StateRead, StorageWrite}; use namada_trans_token::storage_key::{balance_key, minted_balance_key}; use namada_tx::data::TxType; - use namada_tx::{Tx, TxCommitments}; + use namada_tx::{IndexedTx, Tx, TxCommitments}; use namada_vm::WasmCacheRwAccess; use namada_vm::wasm::VpCache; use namada_vm::wasm::run::VpEvalWasm; @@ -238,13 +238,14 @@ mod tests { gas_meter: &'ctx RefCell, keys_changed: &'ctx BTreeSet, verifiers: &'ctx BTreeSet
, + indexed_tx: &'ctx IndexedTx, ) -> Ctx<'ctx, TestState> { Ctx::new( &crate::ADDRESS, state, tx, cmt, - &TxIndex(0), + indexed_tx, gas_meter, keys_changed, verifiers, @@ -384,6 +385,7 @@ mod tests { &TxGasMeter::new(u64::MAX, 1), )); let batched_tx = tx.batch_ref_first_tx().unwrap(); + let indexed_tx = IndexedTx::default(); let ctx = setup_ctx( batched_tx.tx, batched_tx.cmt, @@ -391,6 +393,7 @@ mod tests { &gas_meter, &keys_changed, &verifiers, + &indexed_tx, ); let res = EthBridge::validate_tx( @@ -441,6 +444,7 @@ mod tests { &TxGasMeter::new(u64::MAX, 1), )); let batched_tx = tx.batch_ref_first_tx().unwrap(); + let indexed_tx = IndexedTx::default(); let ctx = setup_ctx( batched_tx.tx, batched_tx.cmt, @@ -448,6 +452,7 @@ mod tests { &gas_meter, &keys_changed, &verifiers, + &indexed_tx, ); let res = EthBridge::validate_tx( @@ -501,6 +506,7 @@ mod tests { &TxGasMeter::new(u64::MAX, 1), )); let batched_tx = tx.batch_ref_first_tx().unwrap(); + let indexed_tx = IndexedTx::default(); let ctx = setup_ctx( batched_tx.tx, batched_tx.cmt, @@ -508,6 +514,7 @@ mod tests { &gas_meter, &keys_changed, &verifiers, + &indexed_tx, ); let res = EthBridge::validate_tx( diff --git a/crates/ethereum_bridge/src/vp/nut_vp.rs b/crates/ethereum_bridge/src/vp/nut_vp.rs index 40f637acf68..a7fd99fce01 100644 --- a/crates/ethereum_bridge/src/vp/nut_vp.rs +++ b/crates/ethereum_bridge/src/vp/nut_vp.rs @@ -107,7 +107,6 @@ mod test_nuts { use namada_core::address::testing::arb_non_internal_address; use namada_core::borsh::BorshSerializeExt; use namada_core::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; - use namada_core::storage::TxIndex; use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_state::testing::TestState; use namada_state::{StateRead, StorageWrite}; @@ -186,12 +185,13 @@ mod test_nuts { &TxGasMeter::new(u64::MAX, 1), )); let batched_tx = tx.batch_ref_first_tx().unwrap(); + let indexed_tx = Default::default(); let ctx = Ctx::new( &Address::Internal(InternalAddress::Nut(DAI_ERC20_ETH_ADDRESS)), &state, batched_tx.tx, batched_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, diff --git a/crates/governance/src/vp/mod.rs b/crates/governance/src/vp/mod.rs index 896edb95584..5206dc8f8ed 100644 --- a/crates/governance/src/vp/mod.rs +++ b/crates/governance/src/vp/mod.rs @@ -1185,13 +1185,13 @@ mod test { use namada_state::testing::TestState; use namada_state::{ BlockHeight, Epoch, FullAccessState, Key, Sha256Hasher, State, - StateRead, StorageRead, TxIndex, + StateRead, StorageRead, }; use namada_token as token; use namada_token::storage_key::balance_key; use namada_tx::action::{Action, GovAction, Write}; use namada_tx::data::TxType; - use namada_tx::{Authorization, Code, Data, Section, Tx}; + use namada_tx::{Authorization, Code, Data, IndexedTx, Section, Tx}; use namada_vm::wasm::VpCache; use namada_vm::wasm::run::VpEvalWasm; use namada_vm::{WasmCacheRwAccess, wasm}; @@ -1255,7 +1255,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -1280,7 +1280,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1499,7 +1499,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -1549,7 +1549,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1604,7 +1604,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -1654,7 +1654,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1710,7 +1710,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -1760,7 +1760,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1816,7 +1816,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -1866,7 +1866,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1903,7 +1903,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -1953,7 +1953,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1990,7 +1990,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -2040,7 +2040,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2095,7 +2095,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -2145,7 +2145,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2200,7 +2200,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -2250,7 +2250,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2287,7 +2287,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -2337,7 +2337,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2393,7 +2393,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2429,7 +2429,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -2479,7 +2479,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2535,7 +2535,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2571,7 +2571,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -2621,7 +2621,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2677,7 +2677,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2713,7 +2713,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -2763,7 +2763,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2836,7 +2836,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2872,7 +2872,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -2922,7 +2922,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2995,7 +2995,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -3031,7 +3031,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -3081,7 +3081,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -3154,7 +3154,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -3185,7 +3185,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -3241,7 +3241,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -3276,7 +3276,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -3332,7 +3332,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -3362,7 +3362,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -3418,7 +3418,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -3453,7 +3453,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -3509,7 +3509,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, diff --git a/crates/governance/src/vp/pgf.rs b/crates/governance/src/vp/pgf.rs index 43a75e6d7ff..4fdaa9478b0 100644 --- a/crates/governance/src/vp/pgf.rs +++ b/crates/governance/src/vp/pgf.rs @@ -284,10 +284,10 @@ mod test { use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; use namada_proof_of_stake::test_utils::get_dummy_genesis_validator; use namada_state::testing::TestState; - use namada_state::{BlockHeight, Epoch, State, StateRead, TxIndex}; + use namada_state::{BlockHeight, Epoch, State, StateRead}; use namada_token::storage_key::balance_key; use namada_tx::data::TxType; - use namada_tx::{Authorization, Code, Data, Section, Tx}; + use namada_tx::{Authorization, Code, Data, IndexedTx, Section, Tx}; use namada_vm::WasmCacheRwAccess; use namada_vm::wasm::run::VpEvalWasm; use namada_vm::wasm::{self, VpCache}; @@ -354,7 +354,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -410,7 +410,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -441,7 +441,7 @@ mod test { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::vp_cache(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let signer = keypair_1(); let signer_address = Address::from(&signer.clone().ref_to()); @@ -497,7 +497,7 @@ mod test { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, diff --git a/crates/ibc/src/actions.rs b/crates/ibc/src/actions.rs index 1e35c43e02f..d484acde9f4 100644 --- a/crates/ibc/src/actions.rs +++ b/crates/ibc/src/actions.rs @@ -14,18 +14,14 @@ use ibc::core::channel::types::timeout::{TimeoutHeight, TimeoutTimestamp}; use ibc::primitives::IntoTimestamp; use namada_core::address::Address; use namada_core::borsh::{BorshSerialize, BorshSerializeExt}; -use namada_core::chain::ChainId; use namada_core::ibc::PGFIbcTarget; +use namada_core::masp_primitives::asset_type::AssetType; use namada_core::tendermint::Time as TmTime; use namada_core::token::Amount; -use namada_events::EmitEvents; -use namada_state::{ - BlockHeader, BlockHeight, Epoch, Epochs, Key, Result, ResultExt, State, - StorageRead, StorageWrite, TxIndex, -}; -use namada_systems::{parameters, trans_token}; +use namada_events::{EmitEvents, Event}; +use namada_state::{Result, ResultExt, State}; +use namada_systems::{parameters, shielded_token, trans_token}; -use crate::event::IbcEvent; use crate::{ IbcActions, IbcCommonContext, IbcStorageContext, MsgTransfer, storage as ibc_storage, @@ -39,82 +35,6 @@ pub struct IbcProtocolContext<'a, S, Token> { _marker: PhantomData, } -impl StorageRead for IbcProtocolContext<'_, S, Token> -where - S: State, -{ - type PrefixIter<'iter> - = ::PrefixIter<'iter> - where - Self: 'iter; - - fn read_bytes(&self, key: &Key) -> Result>> { - self.state.read_bytes(key) - } - - fn has_key(&self, key: &Key) -> Result { - self.state.has_key(key) - } - - fn iter_prefix<'iter>( - &'iter self, - prefix: &Key, - ) -> Result> { - self.state.iter_prefix(prefix) - } - - fn iter_next<'iter>( - &'iter self, - iter: &mut Self::PrefixIter<'iter>, - ) -> Result)>> { - self.state.iter_next(iter) - } - - fn get_chain_id(&self) -> Result { - self.state.get_chain_id() - } - - fn get_block_height(&self) -> Result { - self.state.get_block_height() - } - - fn get_block_header( - &self, - height: BlockHeight, - ) -> Result> { - StorageRead::get_block_header(self.state, height) - } - - fn get_block_epoch(&self) -> Result { - self.state.get_block_epoch() - } - - fn get_pred_epochs(&self) -> Result { - self.state.get_pred_epochs() - } - - fn get_tx_index(&self) -> Result { - self.state.get_tx_index() - } - - fn get_native_token(&self) -> Result
{ - self.state.get_native_token() - } -} - -impl StorageWrite for IbcProtocolContext<'_, S, Token> -where - S: State, -{ - fn write_bytes(&mut self, key: &Key, val: impl AsRef<[u8]>) -> Result<()> { - self.state.write_bytes(key, val) - } - - fn delete(&mut self, key: &Key) -> Result<()> { - self.state.delete(key) - } -} - impl IbcStorageContext for IbcProtocolContext<'_, S, Token> where S: State + EmitEvents, @@ -123,17 +43,17 @@ where + trans_token::Write + trans_token::Events, { - type Storage = Self; + type Storage = S; fn storage(&self) -> &Self::Storage { - self + self.state } fn storage_mut(&mut self) -> &mut Self::Storage { - self + self.state } - fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<()> { + fn emit_event(&mut self, event: Event) -> Result<()> { // There's no gas cost for protocol, we can ignore result self.state.write_log_mut().emit_event(event); Ok(()) @@ -179,6 +99,15 @@ where fn log_string(&self, message: String) { tracing::trace!(message); } + + fn has_conversion(&self, asset_type: &AssetType) -> Result { + Ok(self + .state + .in_mem() + .conversion_state + .assets + .contains_key(asset_type)) + } } impl IbcCommonContext for IbcProtocolContext<'_, S, Token> @@ -192,7 +121,7 @@ where } /// Transfer tokens over IBC -pub fn transfer_over_ibc<'a, S, Params, Token, Transfer>( +pub fn transfer_over_ibc<'a, S, Params, Token, ShieldedToken, Transfer>( state: &'a mut S, token: &Address, source: &Address, @@ -200,13 +129,12 @@ pub fn transfer_over_ibc<'a, S, Params, Token, Transfer>( ) -> Result<()> where S: 'a + State + EmitEvents, - Params: parameters::Read< - as IbcStorageContext>::Storage, - >, + Params: parameters::Read, Token: trans_token::Keys + trans_token::Write + trans_token::Events + Debug, + ShieldedToken: shielded_token::Write + Debug, Transfer: BorshSerialize + BorshDeserialize, { let token = PrefixedCoin { @@ -221,9 +149,10 @@ where }; let ctx = IbcProtocolContext { state, - _marker: PhantomData, + _marker: PhantomData::, }; - let min_duration = Params::epoch_duration_parameter(&ctx)?.min_duration; + let min_duration = + Params::epoch_duration_parameter(ctx.state)?.min_duration; #[allow(clippy::arithmetic_side_effects)] let timeout_timestamp = ctx .state @@ -254,7 +183,7 @@ where // Use an empty verifiers set placeholder for validation, this is only // needed in txs and not protocol let verifiers = Rc::new(RefCell::new(BTreeSet::
::new())); - let mut actions = IbcActions::<_, Params, Token>::new( + let mut actions = IbcActions::<_, Params, Token, ShieldedToken>::new( Rc::new(RefCell::new(ctx)), verifiers, ); diff --git a/crates/ibc/src/context/execution.rs b/crates/ibc/src/context/execution.rs index dfceff91a7b..5a528968a39 100644 --- a/crates/ibc/src/context/execution.rs +++ b/crates/ibc/src/context/execution.rs @@ -253,10 +253,12 @@ where } fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), HostError> { - let event = event.try_into().expect("The event should be converted"); + let event: crate::event::IbcEvent = + event.try_into().expect("The event should be converted"); + self.inner .borrow_mut() - .emit_ibc_event(event) + .emit_event(event.into()) .map_err(HostError::from) } diff --git a/crates/ibc/src/context/middlewares.rs b/crates/ibc/src/context/middlewares.rs index d563071bded..17d302415dc 100644 --- a/crates/ibc/src/context/middlewares.rs +++ b/crates/ibc/src/context/middlewares.rs @@ -6,7 +6,6 @@ pub mod shielded_recv; use std::cell::RefCell; use std::collections::BTreeSet; use std::fmt::Debug; -use std::marker::PhantomData; use std::rc::Rc; use ibc::core::host::types::identifiers::PortId; @@ -22,30 +21,42 @@ use crate::context::transfer_mod::TransferModule; use crate::{IbcCommonContext, IbcStorageContext}; /// The stack of middlewares of the transfer module. -pub type TransferMiddlewares = - OverflowReceiveMiddleware>; +pub type TransferMiddlewares = + OverflowReceiveMiddleware< + ShieldedRecvModule, + >; /// Create a new instance of [`TransferMiddlewares`] -pub fn create_transfer_middlewares( +pub fn create_transfer_middlewares( ctx: Rc>, verifiers: Rc>>, -) -> TransferMiddlewares +) -> TransferMiddlewares where C: IbcCommonContext + Debug, - Params: namada_systems::parameters::Read<::Storage>, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, { OverflowReceiveMiddleware::wrap(ShieldedRecvModule { next: PacketForwardMiddleware::wrap(PfmTransferModule { transfer_module: TransferModule::new(ctx, verifiers), - _phantom: PhantomData, }), }) } -impl crate::ModuleWrapper for TransferMiddlewares +impl crate::ModuleWrapper + for TransferMiddlewares where C: IbcCommonContext + Debug, - Params: namada_systems::parameters::Read<::Storage>, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, { fn as_module(&self) -> &dyn Module { self diff --git a/crates/ibc/src/context/middlewares/pfm_mod.rs b/crates/ibc/src/context/middlewares/pfm_mod.rs index 8d29fa0ca60..580afeec4b0 100644 --- a/crates/ibc/src/context/middlewares/pfm_mod.rs +++ b/crates/ibc/src/context/middlewares/pfm_mod.rs @@ -2,7 +2,6 @@ //! [`TransferModule`]. use std::fmt::{Debug, Formatter}; -use std::marker::PhantomData; use ibc::apps::transfer::context::TokenTransferExecutionContext; use ibc::apps::transfer::handler::{ @@ -37,23 +36,29 @@ use namada_state::{StorageRead, StorageWrite}; use crate::context::IbcContext; use crate::context::transfer_mod::TransferModule; use crate::storage::inflight_packet_key; -use crate::{Error, IbcCommonContext, IbcStorageContext, TokenTransferContext}; +use crate::{ + Error, IbcAccountId, IbcCommonContext, IbcStorageContext, + TokenTransferContext, +}; /// A wrapper around an IBC transfer module necessary to /// build execution contexts. This allows us to implement /// packet forward middleware on this struct. -pub struct PfmTransferModule +pub struct PfmTransferModule where C: IbcCommonContext + Debug, { /// The main module - pub transfer_module: TransferModule, - #[allow(missing_docs)] - pub _phantom: PhantomData, + pub transfer_module: TransferModule, } -impl Debug - for PfmTransferModule +impl Debug + for PfmTransferModule +where + C: IbcCommonContext + Debug, + Params: Debug, + Token: Debug, + ShieldedToken: Debug, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct(stringify!(PfmTransferModule)) @@ -63,16 +68,29 @@ impl Debug } from_middleware! { - impl Module for PfmTransferModule + impl Module for PfmTransferModule where C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, } -impl MiddlewareModule for PfmTransferModule +impl MiddlewareModule + for PfmTransferModule where C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, { - type NextMiddleware = TransferModule; + type NextMiddleware = TransferModule; fn next_middleware(&self) -> &Self::NextMiddleware { &self.transfer_module @@ -83,10 +101,13 @@ where } } -impl PfmContext for PfmTransferModule +impl PfmContext + for PfmTransferModule where C: IbcCommonContext + Debug, Params: namada_systems::parameters::Read<::Storage>, + Token: namada_systems::trans_token::Read<::Storage>, + ShieldedToken: namada_systems::shielded_token::Write<::Storage>, { type Error = crate::Error; @@ -106,10 +127,11 @@ where let mut ctx = IbcContext::::new( self.transfer_module.ctx.inner.clone(), ); - let mut token_transfer_ctx = TokenTransferContext::new( - self.transfer_module.ctx.inner.clone(), - Default::default(), - ); + let mut token_transfer_ctx = + TokenTransferContext::<_, Params, Token, ShieldedToken>::new( + self.transfer_module.ctx.inner.clone(), + Default::default(), + ); self.transfer_module.ctx.insert_verifier(&MULTITOKEN); @@ -125,10 +147,11 @@ where data: PacketData, ) -> Result<(), Self::Error> { tracing::debug!(?packet, ?data, "PFM receive_refund_execute"); - let mut token_transfer_ctx = TokenTransferContext::new( - self.transfer_module.ctx.inner.clone(), - self.transfer_module.ctx.verifiers.clone(), - ); + let mut token_transfer_ctx = + TokenTransferContext::<_, Params, Token, ShieldedToken>::new( + self.transfer_module.ctx.inner.clone(), + self.transfer_module.ctx.verifiers.clone(), + ); self.transfer_module.ctx.insert_verifier(&MULTITOKEN); refund_packet_token_execute(&mut token_transfer_ctx, packet, &data) .map_err(Error::TokenTransfer) @@ -146,10 +169,11 @@ where packet", ); - let mut token_transfer_ctx = TokenTransferContext::new( - self.transfer_module.ctx.inner.clone(), - self.transfer_module.ctx.verifiers.clone(), - ); + let mut token_transfer_ctx = + TokenTransferContext::<_, Params, Token, ShieldedToken>::new( + self.transfer_module.ctx.inner.clone(), + self.transfer_module.ctx.verifiers.clone(), + ); self.transfer_module.ctx.insert_verifier(&MULTITOKEN); @@ -169,7 +193,7 @@ where token_transfer_ctx .escrow_coins_execute( - &IBC_ADDRESS, + &IbcAccountId::Transparent(IBC_ADDRESS), &msg.refund_port_id, &msg.refund_channel_id, &coin, @@ -187,7 +211,11 @@ where }; token_transfer_ctx - .burn_coins_execute(&IBC_ADDRESS, &coin, &String::new().into()) + .burn_coins_execute( + &IbcAccountId::Transparent(IBC_ADDRESS), + &coin, + &String::new().into(), + ) .map_err(|e| Error::TokenTransfer(e.into())) } } diff --git a/crates/ibc/src/context/middlewares/shielded_recv.rs b/crates/ibc/src/context/middlewares/shielded_recv.rs index 2d3423ea7d6..0ad69a896ac 100644 --- a/crates/ibc/src/context/middlewares/shielded_recv.rs +++ b/crates/ibc/src/context/middlewares/shielded_recv.rs @@ -37,25 +37,41 @@ use serde_json::{Map, Value}; use crate::context::middlewares::pfm_mod::PfmTransferModule; use crate::msg::{NamadaMemo, OsmosisSwapMemoData}; -use crate::{Error, IbcCommonContext, IbcStorageContext, TokenTransferContext}; +use crate::{ + Error, IbcAccountId, IbcCommonContext, IbcStorageContext, + TokenTransferContext, +}; /// A middleware for handling IBC pockets received /// after a shielded swap. The minimum amount will /// be shielded and the rest placed in an overflow /// account. -pub struct ShieldedRecvModule +pub struct ShieldedRecvModule where C: IbcCommonContext + Debug, - Params: namada_systems::parameters::Read<::Storage>, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, { /// The next middleware module - pub next: PacketForwardMiddleware>, + pub next: PacketForwardMiddleware< + PfmTransferModule, + >, } -impl ShieldedRecvModule +impl + ShieldedRecvModule where C: IbcCommonContext + Debug, - Params: namada_systems::parameters::Read<::Storage>, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, { fn insert_verifier(&self, address: Address) { self.next @@ -76,10 +92,16 @@ where } } -impl Debug for ShieldedRecvModule +impl Debug + for ShieldedRecvModule where C: IbcCommonContext + Debug, - Params: namada_systems::parameters::Read<::Storage>, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct(stringify!(ShieldedRecvModule)) @@ -89,18 +111,32 @@ where } from_middleware! { - impl Module for ShieldedRecvModule + impl Module + for ShieldedRecvModule where C: IbcCommonContext + Debug, - Params: namada_systems::parameters::Read<::Storage>, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, } -impl MiddlewareModule for ShieldedRecvModule +impl MiddlewareModule + for ShieldedRecvModule where C: IbcCommonContext + Debug, - Params: namada_systems::parameters::Read<::Storage>, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, { - type NextMiddleware = PacketForwardMiddleware>; + type NextMiddleware = PacketForwardMiddleware< + PfmTransferModule, + >; fn next_middleware(&self) -> &Self::NextMiddleware { &self.next @@ -195,10 +231,16 @@ impl ibc_middleware_overflow_receive::PacketMetadata } } -impl OverflowRecvContext for ShieldedRecvModule +impl OverflowRecvContext + for ShieldedRecvModule where C: IbcCommonContext + Debug, - Params: namada_systems::parameters::Read<::Storage>, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, { type Error = Error; type PacketMetadata = NamadaMemo; @@ -211,9 +253,14 @@ where let ctx = self.get_ctx(); let verifiers = self.get_verifiers(); let mut token_transfer_context = - TokenTransferContext::new(ctx, verifiers); + TokenTransferContext::<_, Params, Token, ShieldedToken>::new( + ctx, verifiers, + ); token_transfer_context - .mint_coins_execute(receiver, coin) + .mint_coins_execute( + &IbcAccountId::Transparent(receiver.clone()), + coin, + ) .map_err(|e| Error::TokenTransfer(TokenTransferError::Host(e))) } @@ -227,9 +274,16 @@ where let ctx = self.get_ctx(); let verifiers = self.get_verifiers(); let mut token_transfer_context = - TokenTransferContext::new(ctx, verifiers); + TokenTransferContext::<_, Params, Token, ShieldedToken>::new( + ctx, verifiers, + ); token_transfer_context - .unescrow_coins_execute(receiver, port, channel, coin) + .unescrow_coins_execute( + &IbcAccountId::Transparent(receiver.clone()), + port, + channel, + coin, + ) .map_err(|e| Error::TokenTransfer(TokenTransferError::Host(e))) } } diff --git a/crates/ibc/src/context/storage.rs b/crates/ibc/src/context/storage.rs index 2339f17a961..0fbcf134931 100644 --- a/crates/ibc/src/context/storage.rs +++ b/crates/ibc/src/context/storage.rs @@ -2,11 +2,11 @@ pub use ics23::ProofSpec; use namada_core::address::Address; +use namada_core::masp_primitives::asset_type::AssetType; use namada_core::token::Amount; +use namada_events::Event; use namada_state::{Result, StorageRead, StorageWrite}; -use crate::event::IbcEvent; - /// IBC context trait to be implemented in integration that can read and write pub trait IbcStorageContext { /// Storage read/write type @@ -18,8 +18,11 @@ pub trait IbcStorageContext { /// Read/write storage access fn storage_mut(&mut self) -> &mut Self::Storage; - /// Emit an IBC event - fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<()>; + /// Check if a conversion exists + fn has_conversion(&self, asset_type: &AssetType) -> Result; + + /// Emit an event + fn emit_event(&mut self, event: Event) -> Result<()>; /// Transfer token fn transfer_token( diff --git a/crates/ibc/src/context/token_transfer.rs b/crates/ibc/src/context/token_transfer.rs index 1f273c84866..083106ddc7c 100644 --- a/crates/ibc/src/context/token_transfer.rs +++ b/crates/ibc/src/context/token_transfer.rs @@ -1,7 +1,9 @@ //! IBC token transfer context +use std::borrow::Cow; use std::cell::RefCell; use std::collections::BTreeSet; +use std::marker::PhantomData; use std::rc::Rc; use ibc::apps::transfer::context::{ @@ -12,24 +14,31 @@ use ibc::core::host::types::error::HostError; use ibc::core::host::types::identifiers::{ChannelId, PortId}; use ibc::core::primitives::Signer; use namada_core::address::{Address, InternalAddress, MASP}; -use namada_core::token::Amount; +use namada_core::arith::{CheckedAdd, checked}; +use namada_core::masp::{AssetData, CompactNote, PaymentAddress}; +use namada_core::token::{Amount, MaspDigitPos}; use namada_core::uint::Uint; +use namada_tx::event::{MaspEvent, MaspEventKind, MaspTxRef}; use super::common::IbcCommonContext; -use crate::{IBC_ESCROW_ADDRESS, trace}; +use crate::context::storage::IbcStorageContext; +use crate::storage::{load_shielding_counter, write_shielding_counter}; +use crate::{IBC_ESCROW_ADDRESS, IbcAccountId, trace}; /// Token transfer context to handle tokens #[derive(Debug)] -pub struct TokenTransferContext +pub struct TokenTransferContext where C: IbcCommonContext, { pub(crate) inner: Rc>, pub(crate) verifiers: Rc>>, - is_shielded: bool, + has_masp_tx: bool, + _marker: PhantomData<(Params, Token, ShieldedToken)>, } -impl TokenTransferContext +impl + TokenTransferContext where C: IbcCommonContext, { @@ -41,7 +50,8 @@ where Self { inner, verifiers, - is_shielded: false, + has_masp_tx: false, + _marker: PhantomData, } } @@ -52,7 +62,7 @@ where /// Set to enable a shielded transfer pub fn enable_shielded_transfer(&mut self) { - self.is_shielded = true; + self.has_masp_tx = true; } fn validate_sent_coin(&self, coin: &PrefixedCoin) -> Result<(), HostError> { @@ -130,7 +140,10 @@ where .ok_or_else(|| HostError::Other { description: "The per-epoch deposit overflowed".to_string(), })?; - self.inner.borrow_mut().store_deposit(token, added_deposit) + self.inner + .borrow_mut() + .store_deposit(token, added_deposit)?; + Ok(()) } /// Add the amount to the per-epoch withdraw of the token @@ -170,34 +183,252 @@ where &ibc_denom, ) } + + fn validate_masp_withdraw( + &self, + from_account: &IbcAccountId, + ) -> Result<(), HostError> { + if from_account.is_shielded() && !self.has_masp_tx { + return Err(HostError::Other { + description: format!( + "Set refund address {from_account} without including an \ + IBC unshielding MASP transaction" + ), + }); + } + Ok(()) + } + + #[inline] + fn maybe_store_masp_note_commitments( + &self, + to_account: &IbcAccountId, + token: &Address, + amount: &Amount, + ) -> Result<(), HostError> + where + Params: + namada_systems::parameters::Read<::Storage>, + Token: namada_systems::trans_token::Read<::Storage>, + ShieldedToken: namada_systems::shielded_token::Write< + ::Storage, + >, + { + if let IbcAccountId::Shielded(owner_pa) = to_account { + self.store_masp_note_commitments(owner_pa, token, amount)?; + } + Ok(()) + } + + fn store_masp_note_commitments( + &self, + owner_pa: &PaymentAddress, + token: &Address, + amount: &Amount, + ) -> Result<(), HostError> + where + Params: + namada_systems::parameters::Read<::Storage>, + Token: namada_systems::trans_token::Read<::Storage>, + ShieldedToken: namada_systems::shielded_token::Write< + ::Storage, + >, + { + use namada_events::extend::ComposeEvent; + use namada_state::StorageRead; + use namada_tx::event::ProtocolIbcShielding; + + if amount.is_zero() { + return Ok(()); + } + + let mut notes = vec![]; + let mut note_commitments = vec![]; + + let mut next_shielding_counter = load_shielding_counter( + self.inner.borrow().storage(), + ) + .map_err(|err| HostError::Other { + description: format!( + "Failed to load IBC shielding counter from storage: {err}" + ), + })?; + + let denom = Token::read_denom(self.inner.borrow().storage(), token) + .map_err(|err| HostError::Other { + description: format!( + "Failed to read token denom of {token}: {err}" + ), + })? + .ok_or_else(|| HostError::Other { + description: format!("No token denom in storage for {token}"), + })?; + + let epoched_asset = { + use namada_state::StorageRead; + + let masp_epoch = { + let current_epoch = + self.inner.borrow().storage().get_block_epoch()?; + Params::masp_epoch( + self.inner.borrow().storage(), + current_epoch, + )? + }; + let asset = AssetData { + token: token.clone(), + denom, + // NB: assume there are conversions for all + // other digit positions + position: MaspDigitPos::Zero, + epoch: Some(masp_epoch), + }; + let asset_type = asset.encode().map_err(|_| HostError::Other { + description: format!( + "Failed to create asset type of IBC shielding: {asset:?}" + ), + })?; + + // NB: attribute an epoch to the asset so that + // it can earn rewards, assuming it has conversions + // in the conversion state at the current masp epoch + self.inner + .borrow() + .has_conversion(&asset_type)? + .then_some(masp_epoch) + }; + + for (digit, note_value) in MaspDigitPos::iter() + .zip(amount.iter_words()) + .filter(|(_, word)| *word != 0u64) + { + let asset = AssetData { + token: token.clone(), + denom, + position: digit, + epoch: epoched_asset, + }; + let asset_type = asset.encode().map_err(|_| HostError::Other { + description: format!( + "Failed to create asset type of IBC shielding: {asset:?}" + ), + })?; + + let rseed = namada_core::hash::Hash::sha256(format!( + "Namada IBC shielding {next_shielding_counter}" + )) + .0; + + checked!(next_shielding_counter += 1).map_err(|_err| { + HostError::Other { + description: "Arithmetic overflow in IBC shielding \ + counter increment" + .to_string(), + } + })?; + + let note = owner_pa + .create_note(asset_type, note_value, rseed) + .ok_or_else(|| HostError::Other { + description: format!( + "Invalid payment address used to mint note: {owner_pa}" + ), + })?; + + note_commitments.push(note.commitment()); + notes.push( + CompactNote::new(note, (*owner_pa).into()) + .expect("The payment address has already been validated"), + ); + } + + write_shielding_counter( + self.inner.borrow_mut().storage_mut(), + next_shielding_counter, + ) + .map_err(|err| HostError::Other { + description: format!( + "Failed to write IBC shielding counter to storage: {err}" + ), + })?; + + let tx_index = self + .inner + .borrow() + .storage() + .get_tx_index() + .map_err(|err| HostError::Other { + description: format!("Failed to read tx index: {err}"), + })? + .into(); + self.inner.borrow_mut().emit_event( + MaspEvent { + tx_index, + kind: MaspEventKind::Transfer, + data: MaspTxRef::Unencrypted(notes), + } + .with(ProtocolIbcShielding) + .into(), + )?; + + // update the undated asset balance + if epoched_asset.is_none() { + let current_amount = ShieldedToken::read_undated_balance( + self.inner.borrow().storage(), + token, + ) + .map_err(|err| HostError::Other { + description: format!( + "Failed to read undated asset balance of {token}: {err}" + ), + })?; + + ShieldedToken::write_undated_balance( + self.inner.borrow_mut().storage_mut(), + token, + current_amount.checked_add(*amount).ok_or_else(|| { + HostError::Other { + description: format!( + "Arithmetic overflow in IBC shielding undated \ + asset balance increment of {token}", + ), + } + })?, + ) + .map_err(|err| HostError::Other { + description: format!( + "Failed to write undated asset balance of {token}: {err}" + ), + })?; + } + + ShieldedToken::update_commitment_tree( + self.inner.borrow_mut().storage_mut(), + note_commitments, + ) + .map_err(HostError::from) + } } -impl TokenTransferValidationContext for TokenTransferContext +impl TokenTransferValidationContext + for TokenTransferContext where C: IbcCommonContext, { - type AccountId = Address; + type AccountId = IbcAccountId; fn sender_account( &self, signer: &Signer, ) -> Result { - Address::decode(signer.as_ref()).map_err(|e| HostError::Other { - description: format!( - "Decoding the signer failed: {signer}, error {e}" - ), - }) + signer.as_ref().parse() } fn receiver_account( &self, signer: &Signer, ) -> Result { - Address::try_from(signer).map_err(|e| HostError::Other { - description: format!( - "Decoding the signer failed: {signer}, error {e}" - ), - }) + signer.as_ref().parse() } fn get_port(&self) -> Result { @@ -263,9 +494,13 @@ where } } -impl TokenTransferExecutionContext for TokenTransferContext +impl TokenTransferExecutionContext + for TokenTransferContext where C: IbcCommonContext, + Params: namada_systems::parameters::Read<::Storage>, + ShieldedToken: namada_systems::shielded_token::Write<::Storage>, + Token: namada_systems::trans_token::Read<::Storage>, { fn escrow_coins_execute( &mut self, @@ -277,6 +512,7 @@ where ) -> Result<(), HostError> { let (ibc_token, amount) = self.get_token_amount(coin)?; + self.validate_masp_withdraw(from_account)?; self.add_withdraw(&ibc_token, amount)?; // A transfer of NUT tokens must be verified by their VP @@ -286,16 +522,16 @@ where self.insert_verifier(&ibc_token); } - let from_account = if self.is_shielded { - &MASP + let from_account = if self.has_masp_tx { + Cow::Owned(MASP) } else { - from_account + from_account.to_address() }; self.inner .borrow_mut() .transfer_token( - from_account, + &from_account, &IBC_ESCROW_ADDRESS, &ibc_token, amount, @@ -313,10 +549,18 @@ where let (ibc_token, amount) = self.get_token_amount(coin)?; self.add_deposit(&ibc_token, amount)?; + self.maybe_store_masp_note_commitments( + to_account, &ibc_token, &amount, + )?; self.inner .borrow_mut() - .transfer_token(&IBC_ESCROW_ADDRESS, to_account, &ibc_token, amount) + .transfer_token( + &IBC_ESCROW_ADDRESS, + &to_account.to_address(), + &ibc_token, + amount, + ) .map_err(HostError::from) } @@ -330,6 +574,7 @@ where self.update_mint_amount(&ibc_token, amount, true)?; self.add_deposit(&ibc_token, amount)?; + self.maybe_store_masp_note_commitments(account, &ibc_token, &amount)?; // A transfer of NUT tokens must be verified by their VP if ibc_token.is_internal() @@ -338,13 +583,15 @@ where self.insert_verifier(&ibc_token); } + let account = account.to_address(); + // Store the IBC denom with the token hash to be able to retrieve it // later - self.maybe_store_ibc_denom(account, coin)?; + self.maybe_store_ibc_denom(&account, coin)?; self.inner .borrow_mut() - .mint_token(account, &ibc_token, amount) + .mint_token(&account, &ibc_token, amount) .map_err(HostError::from) } @@ -356,6 +603,7 @@ where ) -> Result<(), HostError> { let (ibc_token, amount) = self.get_token_amount(coin)?; + self.validate_masp_withdraw(account)?; self.update_mint_amount(&ibc_token, amount, false)?; self.add_withdraw(&ibc_token, amount)?; @@ -366,12 +614,16 @@ where self.insert_verifier(&ibc_token); } - let account = if self.is_shielded { &MASP } else { account }; + let account = if self.has_masp_tx { + Cow::Owned(MASP) + } else { + account.to_address() + }; // The burn is "unminting" from the minted balance self.inner .borrow_mut() - .burn_token(account, &ibc_token, amount) + .burn_token(&account, &ibc_token, amount) .map_err(HostError::from) } } diff --git a/crates/ibc/src/context/transfer_mod.rs b/crates/ibc/src/context/transfer_mod.rs index 03c81e5353f..251616bc75b 100644 --- a/crates/ibc/src/context/transfer_mod.rs +++ b/crates/ibc/src/context/transfer_mod.rs @@ -30,8 +30,9 @@ use ibc::core::router::types::module::{ModuleExtras, ModuleId}; use ibc::primitives::Signer; use namada_core::address::Address; -use super::common::IbcCommonContext; use super::token_transfer::TokenTransferContext; +use crate::context::common::IbcCommonContext; +use crate::context::storage::IbcStorageContext; /// IBC module wrapper for getting the reference of the module pub trait ModuleWrapper: Module { @@ -50,15 +51,16 @@ pub trait ModuleWrapper: Module { /// IBC module for token transfer #[derive(Debug)] -pub struct TransferModule +pub struct TransferModule where C: IbcCommonContext, { /// IBC actions - pub ctx: TokenTransferContext, + pub ctx: TokenTransferContext, } -impl TransferModule +impl + TransferModule where C: IbcCommonContext, { @@ -73,9 +75,16 @@ where } } -impl ModuleWrapper for TransferModule +impl ModuleWrapper + for TransferModule where C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, { fn as_module(&self) -> &dyn Module { self @@ -94,9 +103,16 @@ where } } -impl Module for TransferModule +impl Module + for TransferModule where C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage> + + Debug, + Token: namada_systems::trans_token::Read<::Storage> + + Debug, + ShieldedToken: namada_systems::shielded_token::Write<::Storage> + + Debug, { #[allow(clippy::too_many_arguments)] fn on_chan_open_init_validate( diff --git a/crates/ibc/src/lib.rs b/crates/ibc/src/lib.rs index 9172a7688b2..139f9d79f2b 100644 --- a/crates/ibc/src/lib.rs +++ b/crates/ibc/src/lib.rs @@ -28,8 +28,10 @@ pub mod storage; pub mod trace; pub mod vp; +use std::borrow::Cow; use std::cell::RefCell; use std::collections::BTreeSet; +use std::fmt; use std::fmt::Debug; use std::marker::PhantomData; use std::rc::Rc; @@ -69,8 +71,10 @@ use ibc::apps::transfer::types::{ use ibc::core::channel::types::acknowledgement::AcknowledgementStatus; use ibc::core::channel::types::commitment::compute_ack_commitment; use ibc::core::channel::types::msgs::{ - MsgRecvPacket as IbcMsgRecvPacket, PacketMsg, + MsgAcknowledgement as IbcMsgAcknowledgement, + MsgRecvPacket as IbcMsgRecvPacket, MsgTimeout as IbcMsgTimeout, PacketMsg, }; +use ibc::core::channel::types::packet::Packet; use ibc::core::channel::types::timeout::{TimeoutHeight, TimeoutTimestamp}; use ibc::core::entrypoint::{execute, validate}; use ibc::core::handler::types::error::HandlerError; @@ -92,7 +96,7 @@ use namada_core::ibc::core::channel::types::commitment::{ AcknowledgementCommitment, PacketCommitment, compute_packet_commitment, }; pub use namada_core::ibc::*; -use namada_core::masp::{TAddrData, addr_taddr, ibc_taddr}; +use namada_core::masp::{PaymentAddress, TAddrData, addr_taddr, ibc_taddr}; use namada_core::masp_primitives::transaction::components::ValueSum; use namada_core::token::Amount; use namada_events::EmitEvents; @@ -101,7 +105,6 @@ use namada_state::{ State, StorageHasher, StorageRead, StorageWrite, WlState, }; use namada_systems::ibc::ChangedBalances; -use namada_systems::trans_token; pub use nft::*; use prost::Message; use thiserror::Error; @@ -178,12 +181,7 @@ impl TryFrom for IbcTransferInfo { let packet_data = serde_json::to_vec(&message.packet_data) .map_err(StorageError::new)?; let ibc_traces = vec![message.packet_data.token.denom.to_string()]; - let amount = message - .packet_data - .token - .amount - .try_into() - .into_storage_result()?; + let amount: Amount = message.packet_data.token.amount.into(); let receiver = message.packet_data.receiver.to_string(); Ok(Self { src_port_id: message.port_id_on_a, @@ -229,6 +227,41 @@ impl TryFrom for IbcTransferInfo { } } +enum MaspPacketBalanceChange { + Recv, + Timeout, + AckFailure, +} + +impl MaspPacketBalanceChange { + fn apply_msg( + &self, + storage: &S, + accum: ChangedBalances, + packet: &Packet, + ibc_traces: Vec, + amount: Amount, + keys_changed: &BTreeSet, + ) -> StorageResult + where + S: StorageRead, + { + match self { + Self::Recv => apply_recv_msg( + storage, + accum, + packet, + ibc_traces, + amount, + keys_changed, + ), + Self::Timeout | Self::AckFailure => { + apply_refund_msg(accum, packet, ibc_traces, amount) + } + } + } +} + /// IBC storage `Keys/Read/Write` implementation #[derive(Debug)] pub struct Store(PhantomData); @@ -239,20 +272,14 @@ where { fn try_extract_masp_tx_from_envelope( tx_data: &[u8], - ) -> StorageResult> { - let msg = decode_message::(tx_data) - .into_storage_result() - .ok(); - let tx = if let Some(IbcMessage::Envelope(envelope)) = msg { - Some(extract_masp_tx_from_envelope(&envelope).ok_or_else(|| { - StorageError::new_const( - "Missing MASP transaction in IBC message", - ) - })?) + ) -> Option { + let msg = decode_message::(tx_data).ok()?; + + if let IbcMessage::Envelope(envelope) = msg { + extract_masp_tx_from_envelope(&envelope) } else { None - }; - Ok(tx) + } } fn apply_ibc_packet( @@ -296,26 +323,53 @@ where )?; } // This event is emitted on the receiver - Some(IbcMessage::Envelope(envelope)) => { - if let MsgEnvelope::Packet(PacketMsg::Recv(msg)) = *envelope { - if msg.packet.port_id_on_b.as_str() == PORT_ID_STR { - let packet_data = serde_json::from_slice::( - &msg.packet.data, - ) - .map_err(StorageError::new)?; + Some(IbcMessage::Envelope(envelope)) => 'handle_envelope: { + if let MsgEnvelope::Packet(packet_msg) = *envelope { + let (packet, packet_kind) = match packet_msg { + PacketMsg::Recv(IbcMsgRecvPacket { + packet, .. + }) => (packet, MaspPacketBalanceChange::Recv), + PacketMsg::Timeout(IbcMsgTimeout { + packet, .. + }) => (packet, MaspPacketBalanceChange::Timeout), + PacketMsg::Ack(IbcMsgAcknowledgement { + packet, + acknowledgement, + .. + }) => { + let success_ack_commitment = compute_ack_commitment( + &AcknowledgementStatus::success( + ack_success_b64(), + ) + .into(), + ); + let ack_commitment = + compute_ack_commitment(&acknowledgement); + + if ack_commitment == success_ack_commitment { + break 'handle_envelope; + } + + (packet, MaspPacketBalanceChange::AckFailure) + } + _ => { + break 'handle_envelope; + } + }; + + if packet.port_id_on_b.as_str() == PORT_ID_STR { + let packet_data = + serde_json::from_slice::(&packet.data) + .map_err(StorageError::new)?; let receiver = packet_data.receiver.to_string(); let addr = TAddrData::Ibc(receiver.clone()); accum.decoder.insert(ibc_taddr(receiver), addr); let ibc_denom = packet_data.token.denom.to_string(); - let amount = packet_data - .token - .amount - .try_into() - .into_storage_result()?; - accum = apply_recv_msg( + let amount: Amount = packet_data.token.amount.into(); + accum = packet_kind.apply_msg( storage, accum, - &msg, + &packet, vec![ibc_denom], amount, keys_changed, @@ -323,7 +377,7 @@ where } else { let packet_data = serde_json::from_slice::( - &msg.packet.data, + &packet.data, ) .map_err(StorageError::new)?; let receiver = packet_data.receiver.to_string(); @@ -340,10 +394,10 @@ where ) }) .collect(); - accum = apply_recv_msg( + accum = packet_kind.apply_msg( storage, accum, - &msg, + &packet, ibc_traces, Amount::from_u64(1), keys_changed, @@ -356,6 +410,77 @@ where } } +/// Account id for IBC transfers. +#[derive(Debug)] +pub enum IbcAccountId { + /// Transparent account. + Transparent(Address), + /// Shielded account. + Shielded(PaymentAddress), +} + +impl IbcAccountId { + /// Check whether this is a transparent address. + pub const fn is_transparent(&self) -> bool { + matches!(self, Self::Transparent(_)) + } + + /// Check whether this is a shielded address. + pub const fn is_shielded(&self) -> bool { + matches!(self, Self::Shielded(_)) + } + + /// Convert this [`IbcAccountId`] to a transparent address. + pub fn to_address(&self) -> Cow<'_, Address> { + match self { + Self::Transparent(addr) => Cow::Borrowed(addr), + Self::Shielded(_) => Cow::Owned(address::MASP), + } + } +} + +impl FromStr for IbcAccountId { + type Err = HostError; + + fn from_str(s: &str) -> Result { + match () { + // assume we have a transparent addr + _ if s.starts_with('t') => { + Ok(IbcAccountId::Transparent(s.parse().map_err(|err| { + HostError::Other { + description: format!( + "Failed to parse IBC transparent address {s:?}: \ + {err}" + ), + } + })?)) + } + // assume we have a shielded addr + _ if s.starts_with('z') => { + Ok(IbcAccountId::Shielded(s.parse().map_err(|err| { + HostError::Other { + description: format!( + "Failed to parse IBC shielded address {s:?}: {err}" + ), + } + })?)) + } + _ => Err(HostError::Other { + description: format!("Unknown IBC address {s:?}"), + }), + } + } +} + +impl fmt::Display for IbcAccountId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Transparent(addr) => write!(f, "{addr}"), + Self::Shielded(addr) => write!(f, "{addr}"), + } + } +} + fn check_ibc_transfer( storage: &S, ibc_transfer: &IbcTransferInfo, @@ -410,21 +535,19 @@ where // Check that the packet receipt key has been changed fn check_packet_receiving( - msg: &IbcMsgRecvPacket, + packet: &Packet, keys_changed: &BTreeSet, ) -> StorageResult<()> { let receipt_key = storage::receipt_key( - &msg.packet.port_id_on_b, - &msg.packet.chan_id_on_b, - msg.packet.seq_on_a, + &packet.port_id_on_b, + &packet.chan_id_on_b, + packet.seq_on_a, ); if !keys_changed.contains(&receipt_key) { return Err(StorageError::new_alloc(format!( "The packet has not been received: Port ID {}, Channel ID {}, \ Sequence {}", - msg.packet.port_id_on_b, - msg.packet.chan_id_on_b, - msg.packet.seq_on_a, + packet.port_id_on_b, packet.chan_id_on_b, packet.seq_on_a, ))); } Ok(()) @@ -514,7 +637,7 @@ where fn apply_recv_msg( storage: &S, mut accum: ChangedBalances, - msg: &IbcMsgRecvPacket, + packet: &Packet, ibc_traces: Vec, amount: Amount, keys_changed: &BTreeSet, @@ -523,14 +646,14 @@ where S: StorageRead, { // First check that the packet receipt is reflecteed in the state changes - check_packet_receiving(msg, keys_changed)?; + check_packet_receiving(packet, keys_changed)?; // If the transfer was a failure, then enable funds to // be withdrawn from the IBC internal address if is_receiving_success( storage, - &msg.packet.port_id_on_b, - &msg.packet.chan_id_on_b, - msg.packet.seq_on_a, + &packet.port_id_on_b, + &packet.chan_id_on_b, + packet.seq_on_a, )? { for ibc_trace in ibc_traces { // Only artificially increase the IBC internal address pre-balance @@ -539,16 +662,16 @@ where // received. if !is_receiver_chain_source_str( &ibc_trace, - &msg.packet.port_id_on_a, - &msg.packet.chan_id_on_a, + &packet.port_id_on_a, + &packet.chan_id_on_a, ) { // Get the received token let token = received_ibc_token( ibc_trace, - &msg.packet.port_id_on_a, - &msg.packet.chan_id_on_a, - &msg.packet.port_id_on_b, - &msg.packet.chan_id_on_b, + &packet.port_id_on_a, + &packet.chan_id_on_a, + &packet.port_id_on_b, + &packet.chan_id_on_b, ) .into_storage_result()?; let delta = ValueSum::from_pair(token.clone(), amount); @@ -572,6 +695,49 @@ where Ok(accum) } +// Apply the given refund message (originating from an ACK error, or TIMEOUT) +// to the changed balances structure +fn apply_refund_msg( + mut accum: ChangedBalances, + packet: &Packet, + ibc_traces: Vec, + amount: Amount, +) -> StorageResult { + for ibc_trace in ibc_traces { + // Only artificially increase the IBC internal address pre-balance + // if sending involves burning. We do not do this in the escrow + // case since the pre-balance already accounts for the amount being + // sent. + if !is_sender_chain_source( + &ibc_trace, + &packet.port_id_on_a, + &packet.chan_id_on_a, + ) { + // Get the sent token + let token = trace::convert_to_address(ibc_trace) + .map_err(|e| Error::Trace(format!("Invalid base token: {e}"))) + .into_storage_result()?; + let delta = ValueSum::from_pair(token.clone(), amount); + // Enable funds to be taken from the IBC internal + // address and be deposited elsewhere + // Required for the IBC internal Address to release + // funds + let ibc_taddr = addr_taddr(address::IBC); + let pre_entry = accum + .pre + .get(&ibc_taddr) + .cloned() + .unwrap_or(ValueSum::zero()); + accum.pre.insert( + ibc_taddr, + checked!(pre_entry + &delta).map_err(StorageError::new)?, + ); + } + } + + Ok(accum) +} + /// Internal transfer data extracted from the wrapping IBC transaction #[derive(Debug)] pub struct InternalData { @@ -585,21 +751,24 @@ pub struct InternalData { /// IBC actions to handle IBC operations #[derive(Debug)] -pub struct IbcActions<'a, C, Params, Token> +pub struct IbcActions<'a, C, Params, Token, ShieldedToken> where C: IbcCommonContext, { ctx: IbcContext, router: IbcRouter<'a>, verifiers: Rc>>, - _marker: PhantomData, + _marker: PhantomData<(Token, ShieldedToken)>, } -impl<'a, C, Params, Token> IbcActions<'a, C, Params, Token> +impl<'a, C, Params, Token, ShieldedToken> + IbcActions<'a, C, Params, Token, ShieldedToken> where C: IbcCommonContext, Params: namada_systems::parameters::Read, - Token: trans_token::Keys, + Token: namada_systems::trans_token::Keys + + namada_systems::trans_token::Read, + ShieldedToken: namada_systems::shielded_token::Write, { /// Make new IBC actions pub fn new( @@ -632,24 +801,25 @@ where let message = decode_message::(tx_data)?; let result = match message { IbcMessage::Transfer(msg) => { - let mut token_transfer_ctx = TokenTransferContext::new( + let mut token_transfer_ctx = TokenTransferContext::< + _, + Params, + Token, + ShieldedToken, + >::new( self.ctx.inner.clone(), self.verifiers.clone(), ); // Add the source to the set of verifiers self.verifiers.borrow_mut().insert( - Address::from_str(msg.message.packet_data.sender.as_ref()) - .map_err(|_| { - Error::TokenTransfer( - HostError::Other { - description: format!( - "Cannot convert the sender address {}", - msg.message.packet_data.sender - ), - } - .into(), - ) - })?, + msg.message + .packet_data + .sender + .as_ref() + .parse::() + .map_err(|err| Error::TokenTransfer(err.into()))? + .to_address() + .into_owned(), ); if msg.transfer.is_some() { token_transfer_ctx.enable_shielded_transfer(); @@ -679,18 +849,14 @@ where } // Add the source to the set of verifiers self.verifiers.borrow_mut().insert( - Address::from_str(msg.message.packet_data.sender.as_ref()) - .map_err(|_| { - Error::NftTransfer( - HostError::Other { - description: format!( - "Cannot convert the sender address {}", - msg.message.packet_data.sender - ), - } - .into(), - ) - })?, + msg.message + .packet_data + .sender + .as_ref() + .parse::() + .map_err(|err| Error::TokenTransfer(err.into()))? + .to_address() + .into_owned(), ); // Record the tokens credited/debited in this NFT transfer let tokens = msg @@ -725,12 +891,12 @@ where if let Some(verifier) = get_envelope_verifier(envelope.as_ref()) { self.verifiers.borrow_mut().insert( - Address::from_str(verifier.as_ref()).map_err(|_| { - Error::Other(format!( - "Cannot convert the address {}", - verifier, - )) - })?, + verifier + .as_ref() + .parse::() + .map_err(|err| Error::TokenTransfer(err.into()))? + .to_address() + .into_owned(), ); } execute(&mut self.ctx, &mut self.router, *envelope.clone()) @@ -819,7 +985,12 @@ where let message = decode_message::(tx_data)?; let result = match message { IbcMessage::Transfer(msg) => { - let mut token_transfer_ctx = TokenTransferContext::new( + let mut token_transfer_ctx = TokenTransferContext::< + _, + Params, + Token, + ShieldedToken, + >::new( self.ctx.inner.clone(), verifiers.clone(), ); diff --git a/crates/ibc/src/storage.rs b/crates/ibc/src/storage.rs index f609796f7cb..bce88276dc1 100644 --- a/crates/ibc/src/storage.rs +++ b/crates/ibc/src/storage.rs @@ -15,6 +15,7 @@ use ibc::core::host::types::path::{ }; use ibc_middleware_packet_forward::InFlightPacketKey; use namada_core::address::{Address, InternalAddress}; +use namada_core::arith::checked; use namada_core::storage::{DbKeySeg, Key, KeySeg}; use namada_core::token::Amount; use namada_events::EmitEvents; @@ -677,3 +678,41 @@ pub fn inflight_packet_key(inflight_packet_key: &InFlightPacketKey) -> Key { .with_segment(inflight_packet_key.channel.to_string()) .with_segment(inflight_packet_key.sequence.to_string()) } + +/// Get a MASP shieldings counter key. +/// +/// The value stored under this key is used to compute +/// deterministic rseeds for MASP notes. +pub fn masp_shielding_counter_key() -> Key { + const MASP_SUBKEY: &str = "masp"; + const SHIELDING_COUNTER_SUBKEY: &str = "shielding_counter"; + + middlewares_prefix() + .with_segment(MASP_SUBKEY.to_string()) + .with_segment(SHIELDING_COUNTER_SUBKEY.to_string()) +} + +/// Load the IBC shielding counter in storage. +pub fn load_shielding_counter(storage: &S) -> Result { + let key = masp_shielding_counter_key(); + Ok(storage.read::(&key)?.unwrap_or_default()) +} + +/// Write the IBC shielding counter to storage. +pub fn write_shielding_counter( + storage: &mut S, + counter: u64, +) -> Result<()> { + storage.write(&masp_shielding_counter_key(), counter) +} + +/// Increment the IBC shielding counter in storage, +/// and return its previous value. +pub fn fetch_and_increment_shielding_counter( + storage: &mut S, +) -> Result { + let current_counter = load_shielding_counter(storage)?; + let next_counter = checked!(current_counter + 1).map_err(Error::new)?; + write_shielding_counter(storage, next_counter)?; + Ok(current_counter) +} diff --git a/crates/ibc/src/vp/context.rs b/crates/ibc/src/vp/context.rs index 8d46da0b8c7..0ae93235612 100644 --- a/crates/ibc/src/vp/context.rs +++ b/crates/ibc/src/vp/context.rs @@ -7,6 +7,7 @@ use namada_core::address::Address; use namada_core::arith::checked; use namada_core::chain::{BlockHeader, BlockHeight, ChainId, Epoch, Epochs}; use namada_core::collections::{HashMap, HashSet}; +use namada_core::masp_primitives::asset_type::AssetType; use namada_core::storage::{Key, TxIndex}; use namada_events::Event; use namada_gas::MEMORY_ACCESS_GAS_PER_BYTE; @@ -17,7 +18,6 @@ use namada_systems::trans_token::{self as token, Amount}; use namada_vp::VpEnv; use namada_vp::native_vp::{CtxPreStorageRead, VpEvaluator}; -use crate::event::IbcEvent; use crate::storage::{self, is_ibc_key}; use crate::{IbcCommonContext, IbcStorageContext}; @@ -163,7 +163,7 @@ where self.ctx.get_block_epoch() } - fn get_tx_index(&self) -> Result { + fn get_tx_index(&self) -> Result<(BlockHeight, TxIndex, Option)> { self.ctx.get_tx_index() } @@ -225,8 +225,8 @@ where &mut self.storage } - fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<()> { - self.storage.event.insert(event.into()); + fn emit_event(&mut self, event: Event) -> Result<()> { + self.storage.event.insert(event); Ok(()) } @@ -268,6 +268,18 @@ where fn log_string(&self, message: String) { tracing::debug!("{message} in the pseudo execution for IBC VP"); } + + fn has_conversion(&self, asset_type: &AssetType) -> Result { + Ok(self + .storage + .ctx + .ctx + .state + .in_mem() + .conversion_state + .assets + .contains_key(asset_type)) + } } impl<'view, 'a, S, CA, EVAL, Token> IbcCommonContext @@ -355,7 +367,7 @@ where self.ctx.get_block_epoch() } - fn get_tx_index(&self) -> Result { + fn get_tx_index(&self) -> Result<(BlockHeight, TxIndex, Option)> { self.ctx.get_tx_index() } @@ -404,7 +416,7 @@ where self } - fn emit_ibc_event(&mut self, _event: IbcEvent) -> Result<()> { + fn emit_event(&mut self, _event: Event) -> Result<()> { unimplemented!("Validation doesn't emit an event") } @@ -444,6 +456,17 @@ where fn log_string(&self, message: String) { tracing::debug!("{message} for validation in IBC VP"); } + + fn has_conversion(&self, asset_type: &AssetType) -> Result { + Ok(self + .ctx + .ctx + .state + .in_mem() + .conversion_state + .assets + .contains_key(asset_type)) + } } impl<'a, S, CA, EVAL> IbcCommonContext diff --git a/crates/ibc/src/vp/mod.rs b/crates/ibc/src/vp/mod.rs index 81e0baa9e98..55104f63149 100644 --- a/crates/ibc/src/vp/mod.rs +++ b/crates/ibc/src/vp/mod.rs @@ -25,8 +25,9 @@ use namada_gas::{IBC_ACTION_EXECUTE_GAS, IBC_ACTION_VALIDATE_GAS}; use namada_state::write_log::StorageModification; use namada_state::{Error, Result, StateRead}; use namada_systems::trans_token::{self as token, Amount}; -use namada_systems::{governance, parameters, proof_of_stake}; +use namada_systems::{governance, parameters, proof_of_stake, shielded_token}; use namada_tx::BatchedTxRef; +use namada_tx::event::MaspEvent; use namada_vp::VpEnv; use namada_vp::native_vp::{Ctx, CtxPreStorageRead, NativeVp, VpEvaluator}; use thiserror::Error; @@ -84,6 +85,9 @@ pub struct Ibc< ParamsPseudo, Gov, Token, + TokenPseudo, + ShieldedToken, + ShieldedTokenPseudo, PoS, Transfer, > where @@ -92,12 +96,16 @@ pub struct Ibc< /// Context to interact with the host structures. pub ctx: Ctx<'ctx, S, CA, EVAL>, /// Generic types for DI + #[allow(clippy::type_complexity)] pub _marker: PhantomData<( Params, ParamsPre, ParamsPseudo, Gov, Token, + TokenPseudo, + ShieldedToken, + ShieldedTokenPseudo, PoS, Transfer, )>, @@ -114,6 +122,9 @@ impl< ParamsPseudo, Gov, Token, + TokenPseudo, + ShieldedToken, + ShieldedTokenPseudo, PoS, Transfer, > NativeVp<'view> @@ -127,6 +138,9 @@ impl< ParamsPseudo, Gov, Token, + TokenPseudo, + ShieldedToken, + ShieldedTokenPseudo, PoS, Transfer, > @@ -135,14 +149,23 @@ where EVAL: 'static + VpEvaluator<'ctx, S, CA, EVAL> + Debug, CA: 'static + Clone + Debug, Gov: governance::Read>, - Params: parameters::Read>, + Params: + parameters::Read> + Debug, ParamsPre: parameters::Keys - + parameters::Read>, - ParamsPseudo: - parameters::Read>, + + parameters::Read> + + Debug, + ParamsPseudo: parameters::Read> + + Debug, Token: token::Keys + + token::Write> + + Debug, + TokenPseudo: token::Keys + token::Write> + Debug, + ShieldedToken: shielded_token::Write> + + Debug, + ShieldedTokenPseudo: shielded_token::Write> + + Debug, PoS: proof_of_stake::Read>, Transfer: BorshDeserialize, { @@ -196,6 +219,9 @@ impl< ParamsPseudo, Gov, Token, + TokenPseudo, + ShieldedToken, + ShieldedTokenPseudo, PoS, Transfer, > @@ -209,6 +235,9 @@ impl< ParamsPseudo, Gov, Token, + TokenPseudo, + ShieldedToken, + ShieldedTokenPseudo, PoS, Transfer, > @@ -216,14 +245,23 @@ where S: 'static + StateRead, EVAL: 'static + VpEvaluator<'ctx, S, CA, EVAL> + Debug, CA: 'static + Clone + Debug, - Params: parameters::Read>, + Params: + parameters::Read> + Debug, ParamsPre: parameters::Keys - + parameters::Read>, - ParamsPseudo: - parameters::Read>, + + parameters::Read> + + Debug, + ParamsPseudo: parameters::Read> + + Debug, Token: token::Keys + + token::Write> + + Debug, + TokenPseudo: token::Keys + token::Write> + Debug, + ShieldedToken: shielded_token::Write> + + Debug, + ShieldedTokenPseudo: shielded_token::Write> + + Debug, PoS: proof_of_stake::Read>, Transfer: BorshDeserialize, { @@ -241,7 +279,7 @@ where keys_changed: &BTreeSet, ) -> Result<()> { let exec_ctx = - PseudoExecutionContext::<'_, '_, S, CA, EVAL, Token>::new( + PseudoExecutionContext::<'_, '_, S, CA, EVAL, TokenPseudo>::new( self.ctx.pre(), ); let ctx = Rc::new(RefCell::new(exec_ctx)); @@ -249,16 +287,20 @@ where // needed in actual txs to addresses whose VPs should be triggered let verifiers = Rc::new(RefCell::new(BTreeSet::
::new())); - let mut actions = IbcActions::<_, ParamsPseudo, Token>::new( - ctx.clone(), - verifiers.clone(), - ); - let module = create_transfer_middlewares::<_, ParamsPseudo>( - ctx.clone(), - verifiers, - ); + let mut actions = IbcActions::< + _, + ParamsPseudo, + TokenPseudo, + ShieldedTokenPseudo, + >::new(ctx.clone(), verifiers.clone()); + let module = create_transfer_middlewares::< + _, + ParamsPseudo, + TokenPseudo, + ShieldedTokenPseudo, + >(ctx.clone(), verifiers); actions.add_transfer_module(module); - let module = NftTransferModule::<_, Token>::new(ctx.clone()); + let module = NftTransferModule::<_, TokenPseudo>::new(ctx.clone()); actions.add_transfer_module(module); // Charge gas for the expensive execution self.ctx.charge_gas(IBC_ACTION_EXECUTE_GAS.into())?; @@ -285,8 +327,21 @@ where .ctx .state .write_log() + // events originating from ibc nft/token transfers .get_events_of::() .cloned() + .chain( + self.ctx + .state + .write_log() + // events originating from masp shielding transfers over ibc + .get_events_of::() + .filter(|masp_event| { + masp_event + .has_attribute::() + }) + .cloned(), + ) .map(|mut event| { // NB: these attributes are attached to wasm txs // by the protocol. @@ -316,12 +371,17 @@ where // needed in actual txs to addresses whose VPs should be triggered let verifiers = Rc::new(RefCell::new(BTreeSet::
::new())); - let mut actions = - IbcActions::<_, Params, Token>::new(ctx.clone(), verifiers.clone()); + let mut actions = IbcActions::<_, Params, Token, ShieldedToken>::new( + ctx.clone(), + verifiers.clone(), + ); actions.set_validation_params(self.validation_params()?); let module = - create_transfer_middlewares::<_, Params>(ctx.clone(), verifiers); + create_transfer_middlewares::<_, Params, Token, ShieldedToken>( + ctx.clone(), + verifiers, + ); actions.add_transfer_module(module); let module = NftTransferModule::<_, Token>::new(ctx); actions.add_transfer_module(module); @@ -423,7 +483,7 @@ where let tokens: BTreeSet<&Address> = keys_changed .iter() .filter_map(|k| { - Token::is_any_token_balance_key(k).map(|[key, _]| key) + TokenPseudo::is_any_token_balance_key(k).map(|[key, _]| key) }) .collect(); for token in tokens { @@ -528,7 +588,6 @@ mod tests { use namada_core::chain::testing::get_dummy_header; use namada_core::chain::{BlockHeight, Epoch}; use namada_core::key::testing::keypair_1; - use namada_core::storage::TxIndex; use namada_core::tendermint::time::Time as TmTime; use namada_core::time::DurationSecs; use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; @@ -541,7 +600,7 @@ mod tests { use namada_token::Transfer; use namada_token::storage_key::balance_key; use namada_tx::data::TxType; - use namada_tx::{Authorization, Code, Data, Section, Tx}; + use namada_tx::{Authorization, Code, Data, IndexedTx, Section, Tx}; use namada_vm::wasm::VpCache; use namada_vm::wasm::run::VpEvalWasm; use namada_vm::{WasmCacheRwAccess, wasm}; @@ -657,6 +716,15 @@ mod tests { CtxPreStorageRead<'ctx, 'ctx, TestState, VpCache, Eval>, >, namada_token::Store< + VpValidationContext<'ctx, 'ctx, TestState, VpCache, Eval>, + >, + namada_token::Store< + PseudoExecutionStorage<'ctx, 'ctx, TestState, VpCache, Eval>, + >, + namada_token::ShieldedStore< + VpValidationContext<'ctx, 'ctx, TestState, VpCache, Eval>, + >, + namada_token::ShieldedStore< PseudoExecutionStorage<'ctx, 'ctx, TestState, VpCache, Eval>, >, namada_proof_of_stake::Store< @@ -1066,7 +1134,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1092,7 +1160,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1146,7 +1214,7 @@ mod tests { signer: "account0".to_string().into(), }; - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1169,7 +1237,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1273,7 +1341,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1296,7 +1364,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1380,7 +1448,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1406,7 +1474,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1478,7 +1546,7 @@ mod tests { keys_changed.insert(conn_counter_key); // No event - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1501,7 +1569,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1601,7 +1669,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1623,7 +1691,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1709,7 +1777,7 @@ mod tests { .emit_event::(event.try_into().unwrap()); let tx_code = vec![]; - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let mut outer_tx = Tx::from_type(TxType::Raw); @@ -1734,7 +1802,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1805,7 +1873,7 @@ mod tests { .emit_event::(event.try_into().unwrap()); let tx_code = vec![]; - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let mut outer_tx = Tx::from_type(TxType::Raw); @@ -1830,7 +1898,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1928,7 +1996,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1954,7 +2022,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2051,7 +2119,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -2077,7 +2145,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2159,7 +2227,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -2185,7 +2253,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2265,7 +2333,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -2288,7 +2356,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2419,7 +2487,7 @@ mod tests { .write_log_mut() .emit_event::(message_event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let tx_data = MsgTransfer:: { message: msg, @@ -2445,7 +2513,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2627,7 +2695,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -2650,7 +2718,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2783,7 +2851,7 @@ mod tests { }, }); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -2806,7 +2874,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -2942,7 +3010,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -2965,7 +3033,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -3101,7 +3169,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -3124,7 +3192,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -3277,7 +3345,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let tx_data = MsgNftTransfer:: { message: msg, @@ -3303,7 +3371,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -3508,7 +3576,7 @@ mod tests { .write_log_mut() .emit_event::(event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -3531,7 +3599,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -3666,7 +3734,7 @@ mod tests { .write_log_mut() .emit_event::(message_event.try_into().unwrap()); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_code = vec![]; let tx_data = MsgTransfer:: { message: msg, @@ -3692,7 +3760,7 @@ mod tests { &state, batched_tx.tx, batched_tx.cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index 2314eb0d4b9..078d391d0c1 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -280,11 +280,12 @@ impl BenchShellInner { batched_tx: &BatchedTxRef<'_>, ) -> BTreeSet
{ let gas_meter = RefCell::new(TxGasMeter::new(u64::MAX, 1)); + let indexed_tx = IndexedTx::default(); run::tx( &mut self.inner.state, &gas_meter, None, - &TxIndex(0), + &indexed_tx, batched_tx.tx, batched_tx.cmt, &mut self.inner.vp_wasm_cache, diff --git a/crates/node/src/dry_run_tx.rs b/crates/node/src/dry_run_tx.rs index bc2cd898e04..10548aa7fde 100644 --- a/crates/node/src/dry_run_tx.rs +++ b/crates/node/src/dry_run_tx.rs @@ -53,7 +53,7 @@ where &tx, &wrapper, &request.data, - &TxIndex::default(), + TxIndex::default(), height, &tx_gas_meter, &mut shell_params, diff --git a/crates/node/src/protocol.rs b/crates/node/src/protocol.rs index 7865ea4078f..e2f9ef7d697 100644 --- a/crates/node/src/protocol.rs +++ b/crates/node/src/protocol.rs @@ -263,7 +263,11 @@ where let batched_tx_result = apply_wasm_tx( wrapper_hash, &tx.batch_ref_tx(cmt), - &tx_index, + &IndexedTx { + block_height: height, + block_index: tx_index, + batch_index: Some(0), + }, ShellParams { tx_gas_meter, state, @@ -325,7 +329,7 @@ where tx, wrapper, tx_bytes, - &tx_index, + tx_index, height, tx_gas_meter, &mut shell_params, @@ -376,11 +380,23 @@ where .collect::>() .into_iter(); + let mut indexed_tx = IndexedTx { + block_height: height, + block_index: tx_index, + batch_index: None, + }; + for cmt in inner_txs { + indexed_tx.batch_index = tx + .header + .batch + .get_index_of(cmt) + .map(|idx| TxIndex::must_from_usize(idx).into()); + match apply_wasm_tx( wrapper_hash, &tx.batch_ref_tx(cmt), - &tx_index, + &indexed_tx, ShellParams { tx_gas_meter, state, @@ -417,17 +433,7 @@ where compute_inner_tx_hash(wrapper_hash, Either::Right(cmt)); batched_tx_result.events.insert( MaspEvent { - tx_index: IndexedTx { - block_height: height, - block_index: tx_index, - batch_index: tx - .header - .batch - .get_index_of(cmt) - .map(|idx| { - TxIndex::must_from_usize(idx).into() - }), - }, + tx_index: indexed_tx, kind: MaspEventKind::Transfer, data: masp_ref, } @@ -489,7 +495,7 @@ pub(crate) fn apply_wrapper_tx( tx: &Tx, wrapper: &WrapperTx, tx_bytes: &[u8], - tx_index: &TxIndex, + tx_index: TxIndex, height: BlockHeight, tx_gas_meter: &RefCell, shell_params: &mut ShellParams<'_, S, D, H, CA>, @@ -517,9 +523,14 @@ where // Charge or check fees, propagate any errors to prevent committing invalid // data let payment_result = match block_proposer { - Some(block_proposer) => { - transfer_fee(shell_params, block_proposer, tx, wrapper, tx_index)? - } + Some(block_proposer) => transfer_fee( + shell_params, + block_proposer, + tx, + wrapper, + tx_index, + height, + )?, None => check_fees(shell_params, tx, wrapper, dry_run)?, }; @@ -582,7 +593,8 @@ pub fn transfer_fee( block_proposer: &Address, tx: &Tx, wrapper: &WrapperTx, - tx_index: &TxIndex, + tx_index: TxIndex, + height: BlockHeight, ) -> Result> where S: 'static @@ -633,7 +645,16 @@ where } else { // See if the first inner transaction of the batch pays the fees // with a masp unshield - match try_masp_fee_payment(shell_params, tx, tx_index, false) { + match try_masp_fee_payment( + shell_params, + tx, + &IndexedTx { + block_height: height, + block_index: tx_index, + batch_index: Some(0), + }, + false, + ) { Ok(valid_batched_tx_result) => { #[cfg(not(fuzzing))] let balance = token::read_balance( @@ -789,7 +810,7 @@ fn try_masp_fee_payment( tx_wasm_cache, }: &mut ShellParams<'_, S, D, H, CA>, tx: &Tx, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, dry_run: bool, ) -> std::result::Result where @@ -832,7 +853,7 @@ where match apply_wasm_tx( Some(&tx.header_hash()), &first_tx, - tx_index, + indexed_tx, ShellParams { tx_gas_meter: &masp_gas_meter, state: *state, @@ -1002,7 +1023,7 @@ where let valid_batched_tx_result = try_masp_fee_payment( shell_params, tx, - &TxIndex::default(), + &Default::default(), dry_run, )?; let balance = token::read_balance( @@ -1037,7 +1058,7 @@ where fn apply_wasm_tx( wrapper_hash: Option<&Hash>, batched_tx: &BatchedTxRef<'_>, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, shell_params: ShellParams<'_, S, D, H, CA>, gas_meter_kind: GasMeterKind, dry_run: bool, @@ -1058,7 +1079,7 @@ where let verifiers = execute_tx( wrapper_hash, batched_tx, - tx_index, + indexed_tx, state, tx_gas_meter, vp_wasm_cache, @@ -1069,7 +1090,7 @@ where let vps_result = check_vps(CheckVps { batched_tx, - tx_index, + indexed_tx, state, tx_gas_meter: &mut tx_gas_meter.borrow_mut(), verifiers_from_tx: &verifiers, @@ -1179,7 +1200,7 @@ where fn execute_tx( wrapper_hash: Option<&Hash>, batched_tx: &BatchedTxRef<'_>, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, state: &mut S, tx_gas_meter: &RefCell, vp_wasm_cache: &mut VpCache, @@ -1197,7 +1218,7 @@ where state, tx_gas_meter, wrapper_hash, - tx_index, + indexed_tx, batched_tx.tx, batched_tx.cmt, vp_wasm_cache, @@ -1219,7 +1240,7 @@ where CA: 'static + WasmCacheAccess + Sync, { batched_tx: &'a BatchedTxRef<'a>, - tx_index: &'a TxIndex, + indexed_tx: &'a IndexedTx, state: &'a S, tx_gas_meter: &'a mut TxGasMeter, verifiers_from_tx: &'a BTreeSet
, @@ -1232,7 +1253,7 @@ where fn check_vps( CheckVps { batched_tx: tx, - tx_index, + indexed_tx, state, tx_gas_meter, verifiers_from_tx, @@ -1253,7 +1274,7 @@ where verifiers, keys_changed, tx, - tx_index, + indexed_tx, state, tx_gas_meter, vp_wasm_cache, @@ -1275,7 +1296,7 @@ fn execute_vps( verifiers: BTreeSet
, keys_changed: BTreeSet, batched_tx: &BatchedTxRef<'_>, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, state: &S, tx_gas_meter: &TxGasMeter, vp_wasm_cache: &mut VpCache, @@ -1309,7 +1330,7 @@ where wasm::run::vp( vp_code_hash, batched_tx, - tx_index, + indexed_tx, addr, state, &gas_meter, @@ -1335,7 +1356,7 @@ where state, batched_tx.tx, batched_tx.cmt, - tx_index, + indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1770,7 +1791,7 @@ mod tests { verifiers, changed_keys, &batched_tx, - &TxIndex::default(), + &IndexedTx::default(), &state, &gas_meter, &mut vp_cache, @@ -1816,7 +1837,7 @@ mod tests { wasm::run::vp( code_hash, &batched_tx, - &TxIndex::default(), + &IndexedTx::default(), &addr, &state, &RefCell::new(VpGasMeter::new_from_tx_meter( @@ -1847,7 +1868,7 @@ mod tests { wasm::run::vp( code_hash, &batched_tx, - &TxIndex::default(), + &IndexedTx::default(), &addr, &state, &RefCell::new(VpGasMeter::new_from_tx_meter( diff --git a/crates/node/src/shell/finalize_block.rs b/crates/node/src/shell/finalize_block.rs index 2854d05daf0..39f8796ca1b 100644 --- a/crates/node/src/shell/finalize_block.rs +++ b/crates/node/src/shell/finalize_block.rs @@ -1198,6 +1198,7 @@ where _, parameters::Store<_>, token::Store<_>, + token::ShieldedStore<_>, token::Transfer, >(state, token, source, target) }, @@ -1254,6 +1255,7 @@ where _, parameters::Store<_>, token::Store<_>, + token::ShieldedStore<_>, token::Transfer, >(state, token, source, target) }, @@ -5690,12 +5692,13 @@ mod test_finalize_block { let keys_changed = BTreeSet::from([min_confirmations_key()]); let verifiers = BTreeSet::default(); let batched_tx = tx.batch_ref_first_tx().unwrap(); + let indexed_tx = namada_sdk::tx::IndexedTx::default(); let ctx = namada_vp::native_vp::Ctx::<_, _, VpEvalWasm<_, _, _>>::new( shell.mode.get_validator_address().expect("Test failed"), shell.state.read_only(), batched_tx.tx, batched_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, diff --git a/crates/node/src/shell/prepare_proposal.rs b/crates/node/src/shell/prepare_proposal.rs index 5461021deb6..45342e1dafa 100644 --- a/crates/node/src/shell/prepare_proposal.rs +++ b/crates/node/src/shell/prepare_proposal.rs @@ -8,6 +8,7 @@ use namada_sdk::key::tm_raw_hash_to_string; use namada_sdk::parameters::get_gas_scale; use namada_sdk::proof_of_stake::storage::find_validator_by_raw_hash; use namada_sdk::state::{DB, DBIter, StorageHasher, TempWlState, TxIndex}; +use namada_sdk::storage::BlockHeight; use namada_sdk::token::{Amount, DenominatedAmount}; use namada_sdk::tx::Tx; use namada_sdk::tx::data::WrapperTx; @@ -132,7 +133,8 @@ where .filter_map(|(tx_index, tx_bytes)| { let result = validate_wrapper_bytes( tx_bytes, - &TxIndex::must_from_usize(tx_index), + TxIndex::must_from_usize(tx_index), + self.get_current_decision_height(), block_time, block_proposer, proposer_local_config, @@ -276,7 +278,8 @@ where #[allow(clippy::too_many_arguments)] fn validate_wrapper_bytes( tx_bytes: &[u8], - tx_index: &TxIndex, + tx_index: TxIndex, + height: BlockHeight, block_time: Option, block_proposer: &Address, proposer_local_config: Option<&ValidatorLocalConfig>, @@ -317,6 +320,7 @@ where &wrapper, &tx, tx_index, + height, block_proposer, proposer_local_config, &mut ShellParams::new( @@ -334,7 +338,8 @@ where fn prepare_proposal_fee_check( wrapper: &WrapperTx, tx: &Tx, - tx_index: &TxIndex, + tx_index: TxIndex, + height: BlockHeight, proposer: &Address, proposer_local_config: Option<&ValidatorLocalConfig>, shell_params: &mut ShellParams<'_, TempWlState<'static, D, H>, D, H, CA>, @@ -352,8 +357,15 @@ where super::fee_data_check(wrapper, minimum_gas_price, shell_params)?; - protocol::transfer_fee(shell_params, proposer, tx, wrapper, tx_index) - .map_or_else(|e| Err(Error::TxApply(e)), |_| Ok(())) + protocol::transfer_fee( + shell_params, + proposer, + tx, + wrapper, + tx_index, + height, + ) + .map_or_else(|e| Err(Error::TxApply(e)), |_| Ok(())) } fn compute_min_gas_price( @@ -433,9 +445,7 @@ mod test_prepare_proposal { }; use namada_sdk::proof_of_stake::types::WeightedValidator; use namada_sdk::state::collections::lazy_map::{NestedSubKey, SubKey}; - use namada_sdk::storage::{ - BlockHeight, InnerEthEventsQueue, StorageRead, StorageWrite, - }; + use namada_sdk::storage::{InnerEthEventsQueue, StorageRead, StorageWrite}; use namada_sdk::token::read_denom; use namada_sdk::tx::data::{Fee, TxType}; use namada_sdk::tx::{Code, Data, Signed}; diff --git a/crates/node/src/shell/process_proposal.rs b/crates/node/src/shell/process_proposal.rs index 915b8daf9e0..792334f72b2 100644 --- a/crates/node/src/shell/process_proposal.rs +++ b/crates/node/src/shell/process_proposal.rs @@ -142,7 +142,7 @@ where .map(|(tx_index, tx_bytes)| { let result = self.check_proposal_tx( tx_bytes, - &TxIndex::must_from_usize(tx_index), + TxIndex::must_from_usize(tx_index), &mut metadata, &mut temp_state, block_time, @@ -194,7 +194,7 @@ where pub fn check_proposal_tx( &self, tx_bytes: &[u8], - tx_index: &TxIndex, + tx_index: TxIndex, metadata: &mut ValidationMeta, temp_state: &mut TempWlState<'static, D, H>, block_time: DateTimeUtc, @@ -534,6 +534,7 @@ where &wrapper, &tx, tx_index, + self.get_current_decision_height(), block_proposer, &mut ShellParams::new( &RefCell::new(tx_gas_meter), @@ -567,7 +568,8 @@ where fn process_proposal_fee_check( wrapper: &WrapperTx, tx: &Tx, - tx_index: &TxIndex, + tx_index: TxIndex, + height: BlockHeight, proposer: &Address, shell_params: &mut ShellParams<'_, TempWlState<'static, D, H>, D, H, CA>, ) -> ShellResult<()> @@ -586,8 +588,15 @@ where fee_data_check(wrapper, minimum_gas_price, shell_params)?; - protocol::transfer_fee(shell_params, proposer, tx, wrapper, tx_index) - .map_or_else(|e| Err(Error::TxApply(e)), |_| Ok(())) + protocol::transfer_fee( + shell_params, + proposer, + tx, + wrapper, + tx_index, + height, + ) + .map_or_else(|e| Err(Error::TxApply(e)), |_| Ok(())) } /// We test the failure cases of [`process_proposal`]. The happy flows diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index d70e035bc58..826bc00119b 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -9,7 +9,7 @@ use std::time::Duration as StdDuration; use either::Either; use masp_primitives::transaction::components::sapling::builder::BuildParams; use masp_primitives::zip32::PseudoExtendedKey; -use namada_core::address::{Address, MASP}; +use namada_core::address::Address; use namada_core::chain::{BlockHeight, ChainId, Epoch}; use namada_core::collections::HashMap; use namada_core::dec::Dec; @@ -17,7 +17,6 @@ use namada_core::ethereum_events::EthAddress; use namada_core::keccak::KeccakHash; use namada_core::key::{SchemeType, common}; use namada_core::masp::{DiversifierIndex, MaspEpoch, PaymentAddress}; -use namada_core::string_encoding::StringEncoded; use namada_core::time::DateTimeUtc; use namada_core::token::Amount; use namada_core::{storage, token}; @@ -25,7 +24,6 @@ use namada_governance::cli::onchain::{ DefaultProposal, PgfFundingProposal, PgfStewardProposal, }; use namada_ibc::IbcShieldingData; -use namada_io::{Io, display_line}; use namada_token::masp::utils::RetryStrategy; use namada_tx::Memo; use namada_tx::data::GasLimit; @@ -40,7 +38,7 @@ use crate::rpc::{ get_registry_from_xcs_osmosis_contract, osmosis_denom_from_namada_denom, query_ibc_denom, query_osmosis_route_and_min_out, }; -use crate::signing::{SigningData, gen_disposable_signing_key}; +use crate::signing::SigningData; use crate::wallet::{DatedSpendingKey, DatedViewingKey}; use crate::{Namada, rpc, tx}; @@ -522,12 +520,6 @@ pub struct TxOsmosisSwap { pub output_denom: String, /// Address of the recipient on Namada pub recipient: Either, - /// Address to receive funds exceeding the minimum amount, - /// in case of IBC shieldings - /// - /// If unspecified, a disposable address is generated to - /// receive funds with - pub overflow: Option, /// Constraints on the osmosis swap pub slippage: Option, /// Recovery address (on Osmosis) in case of failure @@ -538,10 +530,6 @@ pub struct TxOsmosisSwap { pub osmosis_lcd_rpc: Option, /// REST rpc endpoint to Osmosis SQS pub osmosis_sqs_rpc: Option, - /// The optional data for the frontend sustainability fee - /// NOTE: if the swap is shielded (from MASP to MASP), no sustainability - /// fee should be taken - pub frontend_sus_fee: Option<(C::PaymentAddress, Dec)>, } impl TxOsmosisSwap { @@ -605,11 +593,9 @@ impl TxOsmosisSwap { slippage, local_recovery_addr, route: fixed_route, - overflow, osmosis_lcd_rpc, osmosis_sqs_rpc, output_denom: namada_output_denom, - frontend_sus_fee, } = self; let osmosis_lcd_rpc = osmosis_lcd_rpc @@ -617,23 +603,6 @@ impl TxOsmosisSwap { let osmosis_sqs_rpc = osmosis_sqs_rpc .map_or(Cow::Borrowed(OSMOSIS_SQS_SERVER), Cow::Owned); - let recipient = recipient.map_either( - |addr| addr, - |payment_addr| async move { - let overflow_receiver = if let Some(overflow) = overflow { - overflow - } else { - let addr = (&gen_disposable_signing_key(ctx).await).into(); - display_line!( - ctx.io(), - "Sending unshielded funds to disposable address {addr}", - ); - addr - }; - (payment_addr, overflow_receiver) - }, - ); - // validate `local_recovery_addr` and the contract addr if !bech32::decode(&local_recovery_addr) .is_ok_and(|(hrp, data)| hrp.as_str() == "osmo" && data.len() == 20) @@ -659,18 +628,27 @@ impl TxOsmosisSwap { let namada_input_denom = query_ibc_denom(ctx, transfer.token.to_string(), None).await; + let (receiver, swap_into_znam) = recipient.either( + |transparent_recipient| { + (transparent_recipient.encode_compat(), false) + }, + |shielded_recipient| (shielded_recipient.encode_compat(), true), + ); + let (osmosis_input_denom, _) = osmosis_denom_from_namada_denom( &osmosis_lcd_rpc, ®istry_xcs_addr, &namada_input_denom, + swap_into_znam, ) .await?; - let (osmosis_output_denom, namada_output_addr) = + let (osmosis_output_denom, _namada_output_addr) = osmosis_denom_from_namada_denom( &osmosis_lcd_rpc, ®istry_xcs_addr, &namada_output_denom, + swap_into_znam, ) .await?; @@ -691,59 +669,6 @@ impl TxOsmosisSwap { return Err(Error::Other("Swap has been cancelled".to_owned())); } - let (receiver, final_memo) = match recipient { - Either::Left(transparent_recipient) => { - (transparent_recipient.encode_compat(), None) - } - Either::Right(fut) => { - let (payment_addr, overflow_receiver) = fut.await; - - let amount_to_shield = trade_min_output_amount; - let shielding_tx = tx::gen_ibc_shielding_transfer( - ctx, - GenIbcShieldingTransfer { - query: Query { - ledger_address: transfer.tx.ledger_address.clone(), - }, - output_folder: None, - target: payment_addr, - asset: IbcShieldingTransferAsset::Address( - namada_output_addr, - ), - amount: InputAmount::Validated( - token::DenominatedAmount::new( - amount_to_shield, - 0u8.into(), - ), - ), - expiration: transfer.tx.expiration.clone(), - frontend_sus_fee, - }, - ) - .await? - .ok_or_else(|| { - Error::Other( - "Failed to generate IBC shielding transfer".to_owned(), - ) - })?; - - let memo = assert_json_obj( - serde_json::to_value(&NamadaMemo { - namada: NamadaMemoData::OsmosisSwap { - shielding_data: StringEncoded::new( - IbcShieldingData(shielding_tx), - ), - shielded_amount: amount_to_shield, - overflow_receiver, - }, - }) - .unwrap(), - ); - - (MASP.encode_compat(), Some(memo)) - } - }; - let cosmwasm_memo = Memo { wasm: Wasm { contract: transfer.receiver.clone(), @@ -753,12 +678,12 @@ impl TxOsmosisSwap { slippage: Slippage::MinOutputAmount( trade_min_output_amount, ), - final_memo, receiver, on_failed_delivery: LocalRecoveryAddr { local_recovery_addr, }, route, + final_memo: None, }, }, }, @@ -3229,8 +3154,8 @@ pub struct GenIbcShieldingTransfer { /// The optional data for the frontend sustainability fee (the target and /// the amount, the token must be the same as the one involved in the /// shielding transaction since ics-20 only supports a single asset) - /// NOTE: if the shielding operation is part of a swap, and this is - /// shielded (from MASP to MASP), no sustainability fee should be taken + // NOTE: if the shielding operation is part of a swap, and this is + // shielded (from MASP to MASP), no sustainability fee should be taken pub frontend_sus_fee: Option<(C::PaymentAddress, Dec)>, } diff --git a/crates/sdk/src/masp.rs b/crates/sdk/src/masp.rs index 7677bd9a870..2588cd72138 100644 --- a/crates/sdk/src/masp.rs +++ b/crates/sdk/src/masp.rs @@ -11,7 +11,7 @@ use masp_primitives::transaction::Transaction; use masp_primitives::transaction::components::I128Sum; use namada_core::address::Address; use namada_core::chain::BlockHeight; -use namada_core::masp::MaspEpoch; +use namada_core::masp::{CompactNote, MaspEpoch}; use namada_core::time::DurationSecs; use namada_core::token::{Denomination, MaspDigitPos}; use namada_events::extend::ReadFromEventAttributes; @@ -35,7 +35,7 @@ use crate::{MaybeSend, MaybeSync, token}; /// Extract the relevant shield portions from a [`Tx`] MASP section or an IBC /// message, if any. #[allow(clippy::result_large_err)] -fn extract_masp_tx( +pub fn extract_masp_tx( tx: &Tx, masp_ref: &MaspTxRef, ) -> Result { @@ -90,6 +90,13 @@ fn extract_masp_tx( )) } } + MaspTxRef::Unencrypted(notes) => CompactNote::extract_dummy_tx(notes) + .map_err(|err| { + Error::Other(format!( + "Failed to build dummy MASP tx from unencrypted note \ + outputs: {err}" + )) + }), } } diff --git a/crates/sdk/src/rpc.rs b/crates/sdk/src/rpc.rs index 924eee24d10..d9d687349a9 100644 --- a/crates/sdk/src/rpc.rs +++ b/crates/sdk/src/rpc.rs @@ -1639,6 +1639,7 @@ pub async fn osmosis_denom_from_namada_denom( lcd_rpc_addr: &str, registry_contract_addr: &str, namada_denom: &str, + swap_into_znam: bool, ) -> Result<(String, Address), Error> { async fn fetch_contract_data( contract_addr: &str, @@ -1701,7 +1702,7 @@ pub async fn osmosis_denom_from_namada_denom( let namada_chain_name = fetch_contract_data( registry_contract_addr, lcd_rpc_addr, - &chain_name_req("tnam"), + &chain_name_req(if swap_into_znam { "znam" } else { "tnam" }), ) .await?; let osmosis_chain_name = fetch_contract_data( diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 93f643562da..31012c9fc39 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -9,6 +9,7 @@ use std::time::Duration; use borsh::BorshSerialize; use data::{Fee, GasLimit}; use masp_primitives::asset_type::AssetType; +use masp_primitives::sapling::Diversifier; use masp_primitives::transaction::Transaction as MaspTransaction; use masp_primitives::transaction::builder::Builder; use masp_primitives::transaction::components::I128Sum; @@ -21,7 +22,9 @@ use masp_primitives::transaction::components::sapling::fees::{ use masp_primitives::transaction::components::transparent::fees::{ InputView as TransparentInputView, OutputView as TransparentOutputView, }; -use masp_primitives::zip32::PseudoExtendedKey; +use masp_primitives::zip32::{ + ExtendedKey, PseudoExtendedKey, Scope as Zip32Scope, +}; use namada_account::{InitAccount, UpdateAccount}; use namada_core::address::{Address, IBC, MASP}; use namada_core::arith::checked; @@ -2798,9 +2801,6 @@ pub async fn build_ibc_transfer( )); } - let refund_target = - get_refund_target(context, &args.source, &args.refund_target).await?; - let (mut signing_data, wrap_args, updated_balance) = derive_build_data( context, args.tx @@ -3038,12 +3038,6 @@ pub async fn build_ibc_transfer( let ibc_denom = rpc::query_ibc_denom(context, &args.token.to_string(), Some(&source)) .await; - // The refund target should be given or created if the source is shielded. - // Otherwise, the refund target should be None. - assert!( - (args.source.spending_key().is_some() && refund_target.is_some()) - || (args.source.address().is_some() && refund_target.is_none()) - ); // The memo is either IbcShieldingData or just a memo let memo = args .ibc_shielding_data @@ -3051,15 +3045,11 @@ pub async fn build_ibc_transfer( .map_or(args.ibc_memo.clone(), |shielding_data| { Some(shielding_data.clone().into()) }); - // If the refund address is given, set the refund address. It is used only - // when refunding and won't affect the actual transfer because the actual - // source will be the MASP address and the MASP transaction is generated by - // the shielded source address. - let sender = refund_target - .map(|t| t.to_string()) - .unwrap_or(source.to_string()) - .into(); let data = if args.port_id == PortId::transfer() { + let refund_target = + get_ibc_refund_target(&args.source, args.refund_target.as_ref())?; + let sender = refund_target.to_string().into(); + let token = PrefixedCoin { denom: ibc_denom .parse() @@ -3084,6 +3074,23 @@ pub async fn build_ibc_transfer( } else if let Some((trace_path, base_class_id, token_id)) = is_nft_trace(&ibc_denom) { + let refund_transparent_addr = + if matches!(&args.source, TransferSource::ExtendedKey(_)) { + Some(TransferTarget::Address( + gen_ibc_transparent_refund_target(context).await?, + )) + } else { + None + }; + + let refund_target = get_ibc_refund_target( + &args.source, + args.refund_target + .as_ref() + .or(refund_transparent_addr.as_ref()), + )?; + let sender = refund_target.to_string().into(); + let class_id = PrefixedClassId { trace_path, base_class_id: base_class_id.parse().map_err(|_| { @@ -4732,60 +4739,94 @@ async fn target_exists_or_err( .await } -/// Returns the given refund target address if the given address is valid for -/// the IBC shielded transfer. Returns an error if the address is a payment -/// address or given for non-shielded transfer. -async fn get_refund_target( +/// Generate a transparent IBC refund target +/// +/// This is the legacy method of getting a refund target +/// for MASP unshielding transfers. Currently required by +/// NFT transfers. +async fn gen_ibc_transparent_refund_target( context: &impl Namada, +) -> Result
{ + // Generate a new transparent address if it doesn't exist + let mut rng = OsRng; + let mut wallet = context.wallet_mut().await; + let mut alias = format!("{IBC_REFUND_ALIAS_PREFIX}-{}", rng.next_u64()); + while wallet.find_address(&alias).is_some() { + alias = format!("{IBC_REFUND_ALIAS_PREFIX}-{}", rng.next_u64()); + } + wallet + .gen_store_secret_key( + SchemeType::Ed25519, + Some(alias.clone()), + false, + None, + &mut rng, + ) + .ok_or_else(|| { + Error::Other("Adding a new refund address failed".to_string()) + })?; + wallet + .save() + .map_err(|e| Error::Other(format!("Saving wallet error: {e}")))?; + let addr = wallet.find_address(alias).ok_or_else(|| { + Error::Other("Finding the reund address failed".to_string()) + })?; + Ok(addr.into_owned()) +} + +/// Returns a valid IBC refund target +/// +/// Generates a random payment address if the source is a spending key, +/// or takes the transparent source as a refund target. If the refund +/// target is overriden, an error may be emitted, in case the target +/// is a foreign chain account. +fn get_ibc_refund_target( source: &TransferSource, - refund_target: &Option, -) -> Result> { - match (source, refund_target) { - (_, Some(TransferTarget::PaymentAddress(pa))) => { - Err(Error::Other(format!( - "Supporting only a transparent address as a refund target: {}", - pa, - ))) - } + override_refund_target: Option<&TransferTarget>, +) -> Result { + match (source, override_refund_target) { ( - TransferSource::ExtendedKey(_), - Some(TransferTarget::Address(addr)), - ) => Ok(Some(addr.clone())), - (TransferSource::ExtendedKey(_), None) => { - // Generate a new transparent address if it doesn't exist - let mut rng = OsRng; - let mut wallet = context.wallet_mut().await; - let mut alias = - format!("{IBC_REFUND_ALIAS_PREFIX}-{}", rng.next_u64()); - while wallet.find_address(&alias).is_some() { - alias = format!("{IBC_REFUND_ALIAS_PREFIX}-{}", rng.next_u64()); - } - wallet - .gen_store_secret_key( - SchemeType::Ed25519, - Some(alias.clone()), - false, - None, - &mut rng, - ) - .ok_or_else(|| { - Error::Other( - "Adding a new refund address failed".to_string(), - ) - })?; - wallet.save().map_err(|e| { - Error::Other(format!("Saving wallet error: {e}")) - })?; - let addr = wallet.find_address(alias).ok_or_else(|| { - Error::Other("Finding the reund address failed".to_string()) - })?; - Ok(Some(addr.into_owned())) + TransferSource::Address(taddr), + Some(TransferTarget::PaymentAddress(pa)), + ) => Err(Error::Other(format!( + "Can't specify payment address {pa} as an IBC refund target when \ + the transfer source {taddr} is a transparent address" + ))), + ( + _, + Some( + target @ (TransferTarget::Address(_) + | TransferTarget::PaymentAddress(_)), + ), + ) => { + // Take the transfer target verbatim if the refund address is not + // IBC, or if we're not attempting to withdraw from the MASP without + // adding a spending key as a transfer source + Ok(target.clone()) } - (_, Some(_)) => Err(Error::Other( - "Refund target can't be specified for non-shielded transfer" + (_, Some(TransferTarget::Ibc(_))) => Err(Error::Other( + "Can't specify foreign chain address as an IBC refund target" .to_string(), )), - (_, None) => Ok(None), + (TransferSource::ExtendedKey(esk), None) => { + let ivk = esk + .to_viewing_key() + .to_diversifiable_full_viewing_key() + .to_ivk(Zip32Scope::External); + + // Generate a random payment address + let mut diversifier = Diversifier([0u8; 11]); + loop { + OsRng.fill_bytes(&mut diversifier.0); + + if let Some(pa) = ivk.to_payment_address(diversifier) { + break Ok(TransferTarget::PaymentAddress(pa.into())); + } + } + } + (TransferSource::Address(addr), None) => { + Ok(TransferTarget::Address(addr.clone())) + } } } diff --git a/crates/sdk/src/validation.rs b/crates/sdk/src/validation.rs index f4237424967..c021bad5bc3 100644 --- a/crates/sdk/src/validation.rs +++ b/crates/sdk/src/validation.rs @@ -33,7 +33,10 @@ pub type IbcVp<'a, S, CA> = ibc::vp::Ibc< ParamsPreStore<'a, S, CA>, ParamsIbcPseudoStore<'a, S, CA>, GovPreStore<'a, S, CA>, + TokenIbcVpStore<'a, S, CA>, TokenStoreForIbcExec<'a, S, CA>, + ShieldedTokenIbcVpStore<'a, S, CA>, + ShieldedTokenStoreForIbcExec<'a, S, CA>, PosPreStore<'a, S, CA>, token::Transfer, >; @@ -119,6 +122,11 @@ pub type TokenPreStore<'a, S, CA> = pub type IbcPostStore<'a, S, CA> = ibc::Store, Eval>>; +/// Token store impl over the native prior context +pub type TokenIbcVpStore<'a, S, CA> = token::Store< + ibc::vp::context::VpValidationContext<'a, 'a, S, VpCache, Eval>, +>; + /// Token store impl over IBC pseudo-execution storage pub type TokenStoreForIbcExec<'a, S, CA> = token::Store< ibc::vp::context::PseudoExecutionStorage< @@ -130,6 +138,22 @@ pub type TokenStoreForIbcExec<'a, S, CA> = token::Store< >, >; +/// Shielded token store impl over the native prior context +pub type ShieldedTokenIbcVpStore<'a, S, CA> = token::ShieldedStore< + ibc::vp::context::VpValidationContext<'a, 'a, S, VpCache, Eval>, +>; + +/// Shielded token store impl over IBC pseudo-execution storage +pub type ShieldedTokenStoreForIbcExec<'a, S, CA> = token::ShieldedStore< + ibc::vp::context::PseudoExecutionStorage< + 'a, + 'a, + S, + VpCache, + Eval, + >, +>; + /// Parameters store implementation over the native prior context pub type ParamsIbcVpStore<'a, S, CA> = parameters::Store< ibc::vp::context::VpValidationContext<'a, 'a, S, VpCache, Eval>, diff --git a/crates/shielded_token/Cargo.toml b/crates/shielded_token/Cargo.toml index 8d8d0cb653b..1e6cef33835 100644 --- a/crates/shielded_token/Cargo.toml +++ b/crates/shielded_token/Cargo.toml @@ -61,7 +61,6 @@ bridgetree.workspace = true eyre.workspace = true flume = { workspace = true, optional = true } futures.workspace = true -group.workspace = true itertools.workspace = true lazy_static.workspace = true linkme = { workspace = true, optional = true } diff --git a/crates/shielded_token/src/lib.rs b/crates/shielded_token/src/lib.rs index 02b3dc572da..f9258b5b333 100644 --- a/crates/shielded_token/src/lib.rs +++ b/crates/shielded_token/src/lib.rs @@ -29,21 +29,75 @@ pub mod validation; #[cfg(feature = "masp-validation")] pub mod vp; +use std::marker::PhantomData; use std::str::FromStr; use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; pub use namada_core::dec::Dec; pub use namada_core::masp::{MaspEpoch, MaspTransaction, MaspTxId, MaspValue}; +use namada_core::masp_primitives::merkle_tree::CommitmentTree; +use namada_core::masp_primitives::sapling::Node as SaplingNode; pub use namada_state::{ ConversionLeaf, ConversionState, Error, Key, OptionExt, Result, ResultExt, StorageRead, StorageWrite, WithConversionState, }; +use namada_systems::shielded_token as shielded_token_sys; use serde::{Deserialize, Serialize}; pub use storage::*; #[cfg(feature = "masp")] pub use crate::masp::shielded_wallet::ShieldedWallet; +/// Shielded token storage `Read/Write` implementation +#[derive(Debug)] +pub struct ShieldedStore(PhantomData); + +impl shielded_token_sys::Read for ShieldedStore { + fn read_commitment_tree( + storage: &S, + ) -> Result> { + use crate::storage_key::masp_commitment_tree_key; + + let tree_key = masp_commitment_tree_key(); + let commitment_tree: CommitmentTree = storage + .read(&tree_key)? + .unwrap_or_else(CommitmentTree::empty); + + Ok(commitment_tree) + } + + #[inline] + fn read_undated_balance( + storage: &S, + token_address: &namada_core::address::Address, + ) -> Result { + read_undated_balance(storage, token_address) + } +} + +impl shielded_token_sys::Write + for ShieldedStore +{ + fn write_commitment_tree( + storage: &mut S, + commitment_tree: CommitmentTree, + ) -> Result<()> { + use crate::storage_key::masp_commitment_tree_key; + + let tree_key = masp_commitment_tree_key(); + storage.write(&tree_key, commitment_tree) + } + + #[inline] + fn write_undated_balance( + storage: &mut S, + token_address: &namada_core::address::Address, + balance: namada_core::token::Amount, + ) -> Result<()> { + write_undated_balance(storage, token_address, balance) + } +} + /// Token parameters for each kind of asset held on chain #[derive( Clone, diff --git a/crates/shielded_token/src/masp/shielded_wallet.rs b/crates/shielded_token/src/masp/shielded_wallet.rs index 618595db34c..61c135eacad 100644 --- a/crates/shielded_token/src/masp/shielded_wallet.rs +++ b/crates/shielded_token/src/masp/shielded_wallet.rs @@ -2,7 +2,6 @@ use std::collections::{BTreeMap, BTreeSet, btree_map}; use std::fmt; -use std::io::{Read, Write}; use eyre::{ContextCompat, WrapErr, eyre}; use masp_primitives::asset_type::AssetType; @@ -14,9 +13,7 @@ use masp_primitives::convert::AllowedConversion; use masp_primitives::ff::PrimeField; use masp_primitives::memo::MemoBytes; use masp_primitives::merkle_tree::MerklePath; -use masp_primitives::sapling::{ - Diversifier, Node, Note, Nullifier, ViewingKey, -}; +use masp_primitives::sapling::{Node, Note, Nullifier, ViewingKey}; use masp_primitives::transaction::builder::Builder; use masp_primitives::transaction::components::sapling::builder::BuildParams; use masp_primitives::transaction::components::{ @@ -31,6 +28,7 @@ use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::chain::BlockHeight; use namada_core::collections::{HashMap, HashSet}; use namada_core::control_flow; +pub use namada_core::masp::CompactNote; use namada_core::masp::{ AssetData, MaspEpoch, TransferSource, TransferTarget, encode_asset_type, }; @@ -180,108 +178,6 @@ impl fmt::Display for NotePosition { } } -/// Compact representation of a [`Note`]. -#[derive(Debug, Clone, Copy)] -#[allow(missing_docs)] -pub struct CompactNote { - pub asset_type: AssetType, - pub value: u64, - pub diversifier: Diversifier, - pub pk_d: masp_primitives::jubjub::SubgroupPoint, - pub rseed: masp_primitives::sapling::Rseed, -} - -impl BorshSerialize for CompactNote { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - use group::GroupEncoding; - - BorshSerialize::serialize(&self.asset_type, writer)?; - BorshSerialize::serialize(&self.value, writer)?; - BorshSerialize::serialize(&self.diversifier, writer)?; - BorshSerialize::serialize(&self.pk_d.to_bytes(), writer)?; - BorshSerialize::serialize(&self.rseed, writer) - } -} - -impl BorshDeserialize for CompactNote { - fn deserialize_reader(reader: &mut R) -> std::io::Result { - use group::GroupEncoding; - - let asset_type = - ::deserialize_reader(reader)?; - let value = ::deserialize_reader(reader)?; - let diversifier = - ::deserialize_reader(reader)?; - let pk_d = masp_primitives::jubjub::SubgroupPoint::from_bytes( - &<[u8; 32] as BorshDeserialize>::deserialize_reader(reader)?, - ) - .into_option() - .ok_or_else(|| std::io::Error::other("Invalid pk_d in CompactNote"))?; - let rseed = ::deserialize_reader(reader)?; - - Ok(Self { - asset_type, - value, - diversifier, - pk_d, - rseed, - }) - } -} - -impl CompactNote { - /// Create a compact version of a [`Note`]. - pub fn new( - note: Note, - pa: masp_primitives::sapling::PaymentAddress, - ) -> Option { - let g_d = pa.g_d()?; - let pk_d = *pa.pk_d(); - - if g_d != note.g_d || pk_d != note.pk_d { - return None; - } - - let diversifier = *pa.diversifier(); - let Note { - asset_type, - value, - pk_d, - rseed, - .. - } = note; - - Some(Self { - asset_type, - value, - diversifier, - pk_d, - rseed, - }) - } - - /// Convert this [`CompactNote`] back into a [`Note`]. - pub fn into_note(self) -> Option { - let g_d = self.diversifier.g_d()?; - - let Self { - asset_type, - value, - pk_d, - rseed, - .. - } = self; - - Some(Note { - asset_type, - value, - g_d, - pk_d, - rseed, - }) - } -} - /// Represents the current state of the shielded pool from the perspective of /// the chosen viewing keys. #[derive(BorshSerialize, BorshDeserialize, Debug, Default)] diff --git a/crates/shielded_token/src/storage.rs b/crates/shielded_token/src/storage.rs index c79bf20dd97..e53f8cc323c 100644 --- a/crates/shielded_token/src/storage.rs +++ b/crates/shielded_token/src/storage.rs @@ -93,6 +93,19 @@ where Ok(undated_balance) } +/// Write the undated balance of the given token in the MASP. +pub fn write_undated_balance( + storage: &mut S, + token_address: &Address, + balance: token::Amount, +) -> Result<()> +where + S: StorageWrite, +{ + let undated_balance_key = masp_undated_balance_key(token_address); + storage.write(&undated_balance_key, balance) +} + /// Read the masp token map. pub fn read_token_map(storage: &S) -> Result where diff --git a/crates/shielded_token/src/vp.rs b/crates/shielded_token/src/vp.rs index ede932c1540..656079650db 100644 --- a/crates/shielded_token/src/vp.rs +++ b/crates/shielded_token/src/vp.rs @@ -1,5 +1,6 @@ //! MASP native VP +use std::borrow::Cow; use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet}; use std::marker::PhantomData; @@ -8,7 +9,9 @@ use borsh::BorshDeserialize; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::CommitmentTree; use masp_primitives::sapling::Node; -use masp_primitives::transaction::components::transparent::Authorization; +use masp_primitives::transaction::components::transparent::{ + Authorization, Authorized as AuthorizedTransp, +}; use masp_primitives::transaction::components::{ I128Sum, TxIn, TxOut, ValueSum, }; @@ -17,7 +20,9 @@ use namada_core::address::{self, Address}; use namada_core::arith::{CheckedAdd, CheckedSub, checked}; use namada_core::booleans::BoolResultUnitExt; use namada_core::collections::HashSet; -use namada_core::masp::{MaspEpoch, TAddrData, addr_taddr, encode_asset_type}; +use namada_core::masp::{ + CompactNote, MaspEpoch, TAddrData, addr_taddr, encode_asset_type, +}; use namada_core::storage::Key; use namada_core::token; use namada_core::token::{Amount, MaspDigitPos}; @@ -27,6 +32,9 @@ use namada_state::{ }; use namada_systems::{governance, ibc, parameters, trans_token}; use namada_tx::BatchedTxRef; +use namada_tx::event::{ + MaspTxRef, ProtocolIbcShielding, masp_types as masp_event_types, +}; use namada_vp_env::{Error, Result, VpEnv}; use crate::storage_key::{ @@ -44,6 +52,96 @@ pub struct MaspVp<'ctx, CTX, Params, Gov, Ibc, TransToken, Transfer> { PhantomData<(&'ctx CTX, Params, Gov, Ibc, TransToken, Transfer)>, } +/// Source of the MASP data we are validating. +enum MaspSource { + /// Updates to the state of the MASP resulting from a user + /// built shielded transaction. + UserTx(Box), + /// Updates to the state of the MASP resulting from changes + /// done by the protocol (e.g. IBC memoless shieldings). + Protocol(Vec), +} + +impl MaspSource { + fn sapling_value_balance(&self) -> I128Sum { + match self { + Self::UserTx(shielded_tx) => shielded_tx.sapling_value_balance(), + Self::Protocol(notes) => + { + #[allow(clippy::arithmetic_side_effects)] + notes.iter().fold(I128Sum::zero(), |accum, note| { + accum + + I128Sum::from_pair( + note.asset_type, + -i128::from(note.value), + ) + }) + } + } + } + + fn shielded_outputs( + &self, + ) -> impl Iterator { + match self { + Self::UserTx(shielded_tx) => itertools::Either::Left( + shielded_tx.sapling_bundle().into_iter().flat_map(|bundle| { + bundle.shielded_outputs.iter().map(|so| so.cmu) + }), + ), + Self::Protocol(notes) => { + itertools::Either::Right(notes.iter().filter_map(|cnote| { + cnote.into_note().map(|note| note.cmu()) + })) + } + } + } + + fn as_user_tx(&self) -> Option<&Transaction> { + if let Self::UserTx(shielded_tx) = self { + Some(shielded_tx) + } else { + None + } + } + + fn transparent_inputs( + &self, + ) -> impl Iterator>> { + match self { + Self::UserTx(shielded_tx) => itertools::Either::Left( + shielded_tx.transparent_bundle().into_iter().flat_map( + |transp_bundle| transp_bundle.vin.iter().map(Cow::Borrowed), + ), + ), + Self::Protocol(notes) => { + itertools::Either::Right(notes.iter().filter_map(|cnote| { + cnote.into_note().map(|note| { + Cow::Owned(TxIn { + asset_type: note.asset_type, + value: note.value, + address: addr_taddr(address::IBC), + transparent_sig: (), + }) + }) + })) + } + } + } + + fn transparent_outputs(&self) -> impl Iterator { + match self { + Self::UserTx(shielded_tx) => itertools::Either::Left( + shielded_tx + .transparent_bundle() + .into_iter() + .flat_map(|transp_bundle| transp_bundle.vout.iter()), + ), + Self::Protocol(_) => itertools::Either::Right(std::iter::empty()), + } + } +} + // Balances changed by a transaction #[derive(Debug, Clone)] struct ChangedBalances { @@ -128,14 +226,15 @@ where fn valid_nullifiers_reveal( ctx: &'ctx CTX, keys_changed: &BTreeSet, - transaction: &Transaction, + masp_source: &MaspSource, ) -> Result<()> { // Support set to check that a nullifier was not revealed more // than once in the same tx let mut revealed_nullifiers = HashSet::new(); - for description in transaction - .sapling_bundle() + for description in masp_source + .as_user_tx() + .and_then(|transaction| transaction.sapling_bundle()) .map_or(&vec![], |bundle| &bundle.shielded_spends) { let nullifier_key = masp_nullifier_key(&description.nullifier); @@ -223,7 +322,7 @@ where // the tree and anchor in storage fn valid_note_commitment_update( ctx: &'ctx CTX, - transaction: &Transaction, + masp_source: &MaspSource, ) -> Result<()> { // Check that the merkle tree in storage has been correctly updated with // the output descriptions cmu @@ -237,15 +336,10 @@ where // Based on the output descriptions of the transaction, update the // previous tree in storage - for description in transaction - .sapling_bundle() - .map_or(&vec![], |bundle| &bundle.shielded_outputs) - { - previous_tree - .append(Node::from_scalar(description.cmu)) - .map_err(|()| { - Error::new_const("Failed to update the commitment tree") - })?; + for cmu in masp_source.shielded_outputs() { + previous_tree.append(Node::from_scalar(cmu)).map_err(|()| { + Error::new_const("Failed to update the commitment tree") + })?; } // Check that the updated previous tree matches the actual post tree. // This verifies that all and only the necessary notes have been @@ -264,8 +358,12 @@ where // Check that the spend descriptions anchors of a transaction are valid fn valid_spend_descriptions_anchor( ctx: &'ctx CTX, - transaction: &Transaction, + masp_source: &MaspSource, ) -> Result<()> { + let MaspSource::UserTx(transaction) = masp_source else { + return Ok(()); + }; + for description in transaction .sapling_bundle() .map_or(&vec![], |bundle| &bundle.shielded_spends) @@ -288,8 +386,12 @@ where // Check that the convert descriptions anchors of a transaction are valid fn valid_convert_descriptions_anchor( ctx: &'ctx CTX, - transaction: &Transaction, + masp_source: &MaspSource, ) -> Result<()> { + let MaspSource::UserTx(transaction) = masp_source else { + return Ok(()); + }; + if let Some(bundle) = transaction.sapling_bundle() { if !bundle.shielded_converts.is_empty() { let anchor_key = masp_convert_anchor_key(); @@ -435,37 +537,70 @@ where .data(batched_tx.cmt) .ok_or_err_msg("No transaction data")?; let actions = ctx.read_actions()?; - // Try to get the Transaction object from the tx first (IBC) and from - // the actions afterwards - let shielded_tx = if let Some(tx) = - Ibc::try_extract_masp_tx_from_envelope::(&tx_data)? + // Try to get the Transaction object from the tx first (IBC), from + // the actions or from unencrypted protocol generated notes + let masp_source = if let Some(tx) = + Ibc::try_extract_masp_tx_from_envelope::(&tx_data) { - tx + if ctx + .get_events(&masp_event_types::TRANSFER)? + .into_iter() + .any(|masp_event| { + masp_event.has_attribute::() + }) + { + return Err(Error::new_const( + "Attempted to mint IBC tokens twice through the MASP", + )); + } + MaspSource::UserTx(Box::new(tx)) } else { - let masp_section_ref = - namada_tx::action::get_masp_section_ref(&actions) - .map_err(Error::new_const)? - .ok_or_else(|| { - Error::new_const( - "Missing MASP section reference in action", - ) - })?; - - batched_tx - .tx - .get_masp_section(&masp_section_ref) - .cloned() - .ok_or_else(|| { - Error::new_const("Missing MASP section in transaction") - })? + match namada_tx::action::get_masp_section_ref(&actions) + .map_err(Error::new_const)? + { + Some(masp_section_ref) => MaspSource::UserTx(Box::new( + batched_tx + .tx + .get_masp_section(&masp_section_ref) + .cloned() + .ok_or_else(|| { + Error::new_const( + "Missing MASP section in transaction", + ) + })?, + )), + None => { + let notes = ctx + .get_events(&masp_event_types::TRANSFER)? + .into_iter() + .find_map(|masp_event| { + if let MaspTxRef::Unencrypted(notes) = + masp_event.read_attribute::().ok()? + { + Some(notes) + } else { + None + } + }) + .ok_or_else(|| { + Error::new_const( + "Missing MASP unencrypted note event in \ + transaction", + ) + })?; + MaspSource::Protocol(notes) + } + } }; - if u64::from(ctx.get_block_height()?) - > u64::from(shielded_tx.expiry_height()) - { - let error = Error::new_const("MASP transaction is expired"); - tracing::debug!("{error}"); - return Err(error); + if let MaspSource::UserTx(shielded_tx) = &masp_source { + if u64::from(ctx.get_block_height()?) + > u64::from(shielded_tx.expiry_height()) + { + let error = Error::new_const("MASP transaction is expired"); + tracing::debug!("{error}"); + return Err(error); + } } // Check the validity of the keys and get the transfer data @@ -489,7 +624,7 @@ where .unwrap_or(&zero), &changed_balances.undated_pre, &changed_balances.undated_post, - &shielded_tx.sapling_value_balance(), + &masp_source.sapling_value_balance(), masp_epoch, &changed_balances.undated_tokens, conversion_state, @@ -506,15 +641,15 @@ where // nullifier is being revealed by the tx // 4. The transaction must correctly update the note commitment tree // in storage with the new output descriptions - Self::valid_spend_descriptions_anchor(ctx, &shielded_tx)?; - Self::valid_convert_descriptions_anchor(ctx, &shielded_tx)?; - Self::valid_nullifiers_reveal(ctx, keys_changed, &shielded_tx)?; - Self::valid_note_commitment_update(ctx, &shielded_tx)?; + Self::valid_spend_descriptions_anchor(ctx, &masp_source)?; + Self::valid_convert_descriptions_anchor(ctx, &masp_source)?; + Self::valid_nullifiers_reveal(ctx, keys_changed, &masp_source)?; + Self::valid_note_commitment_update(ctx, &masp_source)?; // Checks on the transparent bundle, if present let mut changed_bals_minus_txn = changed_balances.clone(); validate_transparent_bundle( - &shielded_tx, + &masp_source, &mut changed_bals_minus_txn, masp_epoch, conversion_state, @@ -575,7 +710,10 @@ where // Transactions inside this Tx. We achieve this by not allowing // the IBC to be in the transparent output of any of the // Transaction(s). - if let Some(transp_bundle) = shielded_tx.transparent_bundle() { + if let Some(transp_bundle) = masp_source + .as_user_tx() + .and_then(|shielded_tx| shielded_tx.transparent_bundle()) + { for vout in transp_bundle.vout.iter() { if let Some(TAddrData::Ibc(_)) = changed_bals_minus_txn.decoder.get(&vout.address) @@ -635,7 +773,11 @@ where } // Verify the proofs - verify_shielded_tx(&shielded_tx, |gas| ctx.charge_gas(gas)) + if let Some(shielded_tx) = masp_source.as_user_tx() { + verify_shielded_tx(shielded_tx, |gas| ctx.charge_gas(gas))?; + } + + Ok(()) } } @@ -812,36 +954,34 @@ fn validate_transparent_output( // than the initial balances and that the transparent outputs are not more than // the final balances. Also ensure that the sapling value balance is exactly 0. fn validate_transparent_bundle( - shielded_tx: &Transaction, + masp_source: &MaspSource, changed_balances: &mut ChangedBalances, epoch: MaspEpoch, conversion_state: &ConversionState, authorizers: &mut BTreeSet, ) -> Result<()> { // The Sapling value balance adds to the transparent tx pool - let mut transparent_tx_pool = shielded_tx.sapling_value_balance(); - - if let Some(transp_bundle) = shielded_tx.transparent_bundle() { - for vin in transp_bundle.vin.iter() { - validate_transparent_input( - vin, - changed_balances, - &mut transparent_tx_pool, - epoch, - conversion_state, - authorizers, - )?; - } + let mut transparent_tx_pool = masp_source.sapling_value_balance(); + + for vin in masp_source.transparent_inputs() { + validate_transparent_input( + &vin, + changed_balances, + &mut transparent_tx_pool, + epoch, + conversion_state, + authorizers, + )?; + } - for out in transp_bundle.vout.iter() { - validate_transparent_output( - out, - changed_balances, - &mut transparent_tx_pool, - epoch, - conversion_state, - )?; - } + for out in masp_source.transparent_outputs() { + validate_transparent_output( + out, + changed_balances, + &mut transparent_tx_pool, + epoch, + conversion_state, + )?; } // Ensure that the shielded transaction exactly balances @@ -958,11 +1098,11 @@ mod shielded_token_tests { use namada_core::address::testing::nam; use namada_core::borsh::BorshSerializeExt; use namada_gas::{GasMeterKind, TxGasMeter, VpGasMeter}; + use namada_state::StateRead; use namada_state::testing::{TestState, arb_account_storage_key, arb_key}; - use namada_state::{StateRead, TxIndex}; use namada_trans_token::Amount; use namada_trans_token::storage_key::balance_key; - use namada_tx::{BatchedTx, Tx}; + use namada_tx::{BatchedTx, IndexedTx, Tx}; use namada_vm::WasmCacheRwAccess; use namada_vm::wasm::VpCache; use namada_vm::wasm::compilation_cache::common::testing::vp_cache; @@ -1014,7 +1154,7 @@ mod shielded_token_tests { state.db_write(&src_key, amount.serialize_to_vec()).unwrap(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let mut tx = Tx::from_type(namada_tx::data::TxType::Raw); tx.push_default_inner_tx(); let BatchedTx { tx, cmt } = tx.batch_first_tx(); @@ -1039,7 +1179,7 @@ mod shielded_token_tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1069,7 +1209,7 @@ mod shielded_token_tests { let keys_changed = BTreeSet::from([src_key.clone()]); let verifiers = Default::default(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let mut tx = Tx::from_type(namada_tx::data::TxType::Raw); tx.push_default_inner_tx(); let BatchedTx { tx, cmt } = tx.batch_first_tx(); @@ -1089,7 +1229,7 @@ mod shielded_token_tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1118,7 +1258,7 @@ mod shielded_token_tests { namada_parameters::init_test_storage(&mut state).unwrap(); let verifiers = Default::default(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let mut tx = Tx::from_type(namada_tx::data::TxType::Raw); tx.push_default_inner_tx(); let BatchedTx { tx, cmt } = tx.batch_first_tx(); @@ -1139,7 +1279,7 @@ mod shielded_token_tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, diff --git a/crates/state/src/in_memory.rs b/crates/state/src/in_memory.rs index 64237553f88..07964a1dbd5 100644 --- a/crates/state/src/in_memory.rs +++ b/crates/state/src/in_memory.rs @@ -18,8 +18,9 @@ use namada_storage::tx_queue::ExpiredTxsQueue; use namada_storage::types::CommitOnlyData; use namada_storage::{ BlockHeader, BlockHeight, BlockResults, EPOCH_TYPE_LENGTH, Epoch, Epochs, - EthEventsQueue, Key, KeySeg, StorageHasher, TxIndex, + EthEventsQueue, Key, KeySeg, StorageHasher, }; +use namada_tx::IndexedTx; use crate::Result; @@ -58,7 +59,7 @@ where /// this is reset back to `None`. pub update_epoch_blocks_delay: Option, /// The shielded transaction index - pub tx_index: TxIndex, + pub indexed_tx: IndexedTx, /// The currently saved conversion state pub conversion_state: ConversionState, /// Queue of expired transactions that need to be retransmitted. @@ -157,7 +158,7 @@ where "Privacy is a function of liberty.", ), update_epoch_blocks_delay: None, - tx_index: TxIndex::default(), + indexed_tx: IndexedTx::default(), conversion_state: ConversionState::default(), expired_txs_queue: ExpiredTxsQueue::default(), native_token, @@ -334,3 +335,173 @@ where .unwrap_or_default() } } + +pub mod virtual_storage { + //! Access to in-memory state through [`TEMP_STORAGE`]. + + use namada_core::address::TEMP_STORAGE; + use namada_core::arith::checked; + use namada_core::hints; + use namada_core::storage::{DbKeySeg, KeySeg}; + use namada_gas::{Gas, MEMORY_ACCESS_GAS_PER_BYTE}; + use namada_storage::conversion_state::AssetType; + + use super::InMemory; + use crate::{Error, Result, ResultExt}; + + const IN_MEMORY_SEGMENT: &str = "in-memory"; + const CONVERSION_STATE_SEGMENT: &str = "conversion-state"; + const HAS_CONVERSION_SEGMENT: &str = "has-conversion"; + + /// Check if the given storage key corresponds to an + /// in-memory virtual storage key. + pub fn is_in_mem_key(key: &namada_storage::Key) -> bool { + matches!( + &key.segments[..], + [ + DbKeySeg::AddressSeg(TEMP_STORAGE), + DbKeySeg::StringSeg(seg), + .. + ] + if seg == IN_MEMORY_SEGMENT + ) + } + + /// Read a value from the in-memory virtual storage. + pub fn read_from_in_mem( + key: &namada_storage::Key, + in_mem: &InMemory, + ) -> Result<(Vec, Gas)> + where + H: namada_storage::StorageHasher, + { + let invalid_key = || { + Error::new_alloc(format!( + "Invalid in-memory virtual storage key {key}" + )) + }; + + if hints::unlikely(!is_in_mem_key(key)) { + return Err(invalid_key()); + } + + // NOTE: it's only safe to skip the first two segments because + // we went through the `is_in_mem_key` predicate + match &key.segments[2..] { + // handle a request to the `has_conversion_key` key space + [ + DbKeySeg::StringSeg(conversion_seg), + DbKeySeg::StringSeg(has_conv_seg), + DbKeySeg::StringSeg(asset_type_hash), + ] if conversion_seg == CONVERSION_STATE_SEGMENT + && has_conv_seg == HAS_CONVERSION_SEGMENT => + { + let asset_type: AssetType = asset_type_hash.parse().wrap_err( + "Invalid AssetType hash in has_conversion virtual storage \ + request", + )?; + + let value = vec![ + in_mem + .conversion_state + .assets + .contains_key(&asset_type) + .into(), + ]; + + let gas = checked!(key.len() + value.len())? as u64; + + Ok((value, checked!(gas * MEMORY_ACCESS_GAS_PER_BYTE)?.into())) + } + // invalid request + _ => Err(invalid_key()), + } + } + + /// Key used to query the conversion state of a given [`AssetType`]. + pub fn has_conversion_key(asset_type: &AssetType) -> namada_storage::Key { + namada_storage::Key::from(TEMP_STORAGE.to_db_key()) + .with_segment(IN_MEMORY_SEGMENT.to_string()) + .with_segment(CONVERSION_STATE_SEGMENT.to_string()) + .with_segment(HAS_CONVERSION_SEGMENT.to_string()) + .with_segment(asset_type.to_string()) + } + + #[cfg(test)] + mod tests { + use std::collections::BTreeMap; + + use namada_core::address; + use namada_core::borsh::BorshSerializeExt; + use namada_core::chain::ChainId; + use namada_core::hash::Sha256Hasher; + use namada_core::masp_primitives::merkle_tree::FrozenCommitmentTree; + use namada_core::masp_primitives::transaction::components::amount::I128Sum; + use namada_core::token::{MaspDigitPos, NATIVE_MAX_DECIMAL_PLACES}; + use namada_storage::conversion_state::{ + ConversionLeaf, ConversionState, + }; + + use super::*; + + #[test] + fn test_is_in_mem_key() { + let asset = AssetType::new(b"bing bong").unwrap(); + assert!(is_in_mem_key(&has_conversion_key(&asset))); + assert!(is_in_mem_key(&namada_storage::Key { + segments: vec![ + DbKeySeg::AddressSeg(TEMP_STORAGE), + DbKeySeg::StringSeg(IN_MEMORY_SEGMENT.to_string()), + DbKeySeg::StringSeg("whatever".to_string()), + ], + })); + assert!(!is_in_mem_key(&namada_storage::Key { + segments: vec![ + DbKeySeg::AddressSeg(namada_core::address::IBC), + DbKeySeg::StringSeg(IN_MEMORY_SEGMENT.to_string()), + DbKeySeg::StringSeg("whatever".to_string()), + ], + })); + } + + #[test] + fn test_in_mem_virtual_storage_requests() { + let asset1 = AssetType::new(b"1").unwrap(); + let asset2 = AssetType::new(b"2").unwrap(); + + let mut in_mem = InMemory::::new( + ChainId("namada".into()), + address::testing::nam(), + None, + ); + in_mem.conversion_state = { + let tree = FrozenCommitmentTree::new(&[]); + let leaf = ConversionLeaf { + token: address::testing::nam(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + digit_pos: MaspDigitPos::Zero, + epoch: Default::default(), + conversion: I128Sum::zero().into(), + leaf_pos: 0, + }; + let assets = BTreeMap::from([(asset1, leaf)]); + ConversionState { + #[allow(deprecated)] + current_precision: None, + tree, + assets, + } + }; + + let (value, _gas) = + read_from_in_mem(&has_conversion_key(&asset1), &in_mem) + .unwrap(); + assert_eq!(value, true.serialize_to_vec()); + + let (value, _gas) = + read_from_in_mem(&has_conversion_key(&asset2), &in_mem) + .unwrap(); + assert_eq!(value, false.serialize_to_vec()); + } + } +} diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 6bcc9abf46a..b0878eda332 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -27,6 +27,7 @@ use std::iter::Peekable; pub use in_memory::{ BlockStorage, InMemory, LastBlock, ProcessProposalCachedResult, + virtual_storage as in_mem_virtual_storage, }; use namada_core::address::Address; use namada_core::arith::checked; @@ -334,11 +335,23 @@ macro_rules! impl_storage_read { fn get_tx_index( &self, - ) -> std::result::Result { + ) -> std::result::Result< + (BlockHeight, storage::TxIndex, Option), + namada_storage::Error, + > { self.charge_gas( namada_gas::STORAGE_ACCESS_GAS_PER_BYTE.into(), ).into_storage_result()?; - Ok(self.in_mem().tx_index) + let namada_tx::IndexedTx { + block_height, + block_index, + batch_index, + } = self.in_mem().indexed_tx; + Ok(( + block_height, + block_index, + batch_index, + )) } fn get_native_token(&self) -> namada_storage::Result
{ @@ -642,7 +655,7 @@ pub mod testing { "Test address generator seed", ), update_epoch_blocks_delay: None, - tx_index: TxIndex::default(), + indexed_tx: namada_tx::IndexedTx::default(), conversion_state: ConversionState::default(), expired_txs_queue: ExpiredTxsQueue::default(), native_token: address::testing::nam(), diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 4ad58590328..c0f88905f81 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -110,7 +110,7 @@ pub trait StorageRead { fn get_pred_epochs(&self) -> Result; /// Get the transaction index. - fn get_tx_index(&self) -> Result; + fn get_tx_index(&self) -> Result<(BlockHeight, TxIndex, Option)>; /// Get the native token address fn get_native_token(&self) -> Result
; @@ -476,8 +476,8 @@ pub mod testing { Ok(self.pred_epochs.clone()) } - fn get_tx_index(&self) -> Result { - Ok(TxIndex::default()) + fn get_tx_index(&self) -> Result<(BlockHeight, TxIndex, Option)> { + Ok(Default::default()) } fn get_native_token(&self) -> Result
{ diff --git a/crates/systems/src/ibc.rs b/crates/systems/src/ibc.rs index b492513b9e4..6aee6df0a3a 100644 --- a/crates/systems/src/ibc.rs +++ b/crates/systems/src/ibc.rs @@ -15,7 +15,7 @@ pub trait Read { /// Extract MASP transaction from IBC envelope fn try_extract_masp_tx_from_envelope( tx_data: &[u8], - ) -> Result>; + ) -> Option; /// Apply relevant IBC packets to the changed balances structure fn apply_ibc_packet( diff --git a/crates/systems/src/parameters.rs b/crates/systems/src/parameters.rs index a8fd89e4098..6a015972511 100644 --- a/crates/systems/src/parameters.rs +++ b/crates/systems/src/parameters.rs @@ -1,6 +1,7 @@ //! Parameters abstract interfaces -use namada_core::chain::BlockHeight; +use namada_core::chain::{BlockHeight, Epoch}; +use namada_core::masp::MaspEpoch; pub use namada_core::parameters::*; use namada_core::storage; use namada_core::time::DurationSecs; @@ -37,6 +38,15 @@ pub trait Read { last_block_height: BlockHeight, num_blocks_to_read: u64, ) -> Result; + + /// Read the current MASP epoch + fn masp_epoch(storage: &S, current_epoch: Epoch) -> Result { + MaspEpoch::try_from_epoch( + current_epoch, + Self::masp_epoch_multiplier(storage)?, + ) + .map_err(namada_storage::Error::SimpleMessage) + } } /// Abstract parameters storage write interface diff --git a/crates/systems/src/shielded_token.rs b/crates/systems/src/shielded_token.rs index 27f7e3fa922..1f5358776e0 100644 --- a/crates/systems/src/shielded_token.rs +++ b/crates/systems/src/shielded_token.rs @@ -1 +1,52 @@ //! Shielded token abstract interfaces + +use namada_core::address::Address; +use namada_core::masp_primitives::merkle_tree::CommitmentTree; +use namada_core::masp_primitives::sapling::Node; +use namada_core::token; +pub use namada_storage::{Error, Result}; + +/// Abstract shielded token storage read interface +pub trait Read { + /// Read the commitment tree from storage. + fn read_commitment_tree(storage: &S) -> Result>; + + /// Read the undated balance of the given token in the MASP. + fn read_undated_balance( + storage: &S, + token_address: &Address, + ) -> Result; +} + +/// Abstract shielded token storage write interface +pub trait Write: Read { + /// Write a commitment tree to storage. + fn write_commitment_tree( + storage: &mut S, + commitment_tree: CommitmentTree, + ) -> Result<()>; + + /// Write the undated balance of the given token in the MASP. + fn write_undated_balance( + storage: &mut S, + token_address: &Address, + balance: token::Amount, + ) -> Result<()>; + + /// Update the commitment tree in storage. + fn update_commitment_tree(storage: &mut S, commitments: I) -> Result<()> + where + I: IntoIterator, + { + let mut commitment_tree = Self::read_commitment_tree(storage)?; + + for cmu in commitments { + // Add cmu to the merkle tree + commitment_tree.append(cmu).map_err(|_| { + Error::SimpleMessage("Note commitment tree is full") + })?; + } + + Self::write_commitment_tree(storage, commitment_tree) + } +} diff --git a/crates/tests/src/e2e/helpers.rs b/crates/tests/src/e2e/helpers.rs index 8eda638bfb8..d929fb4cf57 100644 --- a/crates/tests/src/e2e/helpers.rs +++ b/crates/tests/src/e2e/helpers.rs @@ -18,6 +18,7 @@ use eyre::eyre; use namada_apps_lib::cli::context::ENV_VAR_CHAIN_ID; use namada_apps_lib::config::utils::convert_tm_addr_to_socket_addr; use namada_apps_lib::config::{Config, TendermintMode}; +use namada_core::masp::PaymentAddress; use namada_core::token::NATIVE_MAX_DECIMAL_PLACES; use namada_sdk::address::Address; use namada_sdk::chain::Epoch; @@ -128,6 +129,35 @@ pub fn find_address(test: &Test, alias: impl AsRef) -> Result
{ Ok(address) } +/// Find the address of an account by its alias from the wallet +#[allow(dead_code)] +pub fn find_payment_address( + test: &Test, + alias: impl AsRef, +) -> Result { + let mut find = run!( + test, + Bin::Wallet, + &["find", "--addr", "--alias", alias.as_ref()], + Some(10) + )?; + find.exp_string("Found payment address:")?; + let (unread, matched) = find.exp_regex("\".*\": .*")?; + let address_str = strip_trailing_newline(&matched) + .trim() + .rsplit_once(' ') + .unwrap() + .1; + let address = PaymentAddress::from_str(address_str).map_err(|e| { + eyre!(format!( + "Address: {} parsed from {}, Error: {}\n\nOutput: {}", + address_str, matched, e, unread + )) + })?; + println!("Found {}", address); + Ok(address) +} + /// Find the balance of specific token for an account. #[allow(dead_code)] pub fn find_balance( diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 2e5873b4dd6..5cf47747c3a 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -53,8 +53,8 @@ use sha2::{Digest, Sha256}; use crate::e2e::helpers::{ epoch_sleep, epochs_per_year_from_min_duration, find_address, - find_cosmos_address, get_actor_rpc, get_cosmos_gov_address, - get_cosmos_rpc_address, get_epoch, + find_cosmos_address, find_payment_address, get_actor_rpc, + get_cosmos_gov_address, get_cosmos_rpc_address, get_epoch, }; use crate::e2e::ledger_tests::{ start_namada_ledger_node_wait_wasm, write_json_file, @@ -76,6 +76,287 @@ const CW721_WASM: &str = "cw721_base.wasm"; const ICS721_WASM: &str = "ics721_base.wasm"; const NFT_ID: &str = "test_nft"; +#[test] +fn ibc_to_and_from_payment_addrs() -> Result<()> { + const PIPELINE_LEN: u64 = 2; + const MASP_EPOCH_MULTIPLIER: u64 = 1; + + let update_genesis = + |mut genesis: templates::All, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(60); + genesis.parameters.gov_params.min_proposal_grace_epochs = 3; + genesis.parameters.ibc_params.default_mint_limit = + Amount::max_signed(); + genesis.parameters.pos_params.pipeline_len = PIPELINE_LEN; + genesis.parameters.parameters.masp_epoch_multiplier = + MASP_EPOCH_MULTIPLIER; + genesis + .parameters + .ibc_params + .default_per_epoch_throughput_limit = Amount::max_signed(); + setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) + }; + let (ledger, gaia, test, test_gaia) = + run_namada_cosmos(CosmosChainType::Gaia(None), update_genesis)?; + let _bg_ledger = ledger.background(); + let _bg_gaia = gaia.background(); + + // Delegate tokens on Namada + delegate_token(&test)?; + let rpc = get_actor_rpc(&test, Who::Validator(0)); + let mut epoch = get_epoch(&test, &rpc).unwrap(); + let delegated_epoch = epoch + PIPELINE_LEN; + + // Create channel between Namada and Gaia + let hermes_dir = setup_hermes(&test, &test_gaia)?; + let port_id_namada = FT_PORT_ID.parse().unwrap(); + let port_id_gaia = FT_PORT_ID.parse().unwrap(); + let (channel_id_namada, channel_id_gaia) = create_channel_with_hermes( + &hermes_dir, + &test, + &test_gaia, + &port_id_namada, + &port_id_gaia, + )?; + + // Start relaying + let hermes = run_hermes(&hermes_dir)?; + let bg_hermes = hermes.background(); + + // Shielding transfer 200 samoleans from Gaia to Namada + let albert_payment_addr = find_payment_address(&test, AA_PAYMENT_ADDRESS)?; + transfer_from_cosmos( + &test_gaia, + COSMOS_USER, + albert_payment_addr.to_string(), + COSMOS_COIN, + 200, + &port_id_gaia, + &channel_id_gaia, + None, + None, + )?; + wait_for_packet_relay( + &hermes_dir, + &port_id_gaia, + &channel_id_gaia, + &test_gaia, + )?; + let ibc_denom_on_namada = + format!("{port_id_namada}/{channel_id_namada}/{COSMOS_COIN}"); + check_shielded_balance(&test, AA_VIEWING_KEY, &ibc_denom_on_namada, 200)?; + check_cosmos_balance(&test_gaia, COSMOS_USER, COSMOS_COIN, 800)?; + + // Unshielding transfer 100 samoleans from Namada to Gaia + let gaia_receiver = find_cosmos_address(&test_gaia, COSMOS_USER)?; + transfer( + &test, + A_SPENDING_KEY, + &gaia_receiver, + &ibc_denom_on_namada, + 100, + Some(BERTHA_KEY), + &port_id_namada, + &channel_id_namada, + None, + None, + None, + None, + false, + None, + None, + )?; + wait_for_packet_relay( + &hermes_dir, + &port_id_namada, + &channel_id_namada, + &test, + )?; + check_shielded_balance(&test, AA_VIEWING_KEY, &ibc_denom_on_namada, 100)?; + check_cosmos_balance(&test_gaia, COSMOS_USER, COSMOS_COIN, 900)?; + + // Get refunded by unshielding to an invalid address on Gaia + transfer( + &test, + A_SPENDING_KEY, + "invalid_receiver", + &ibc_denom_on_namada, + 100, + Some(BERTHA_KEY), + &port_id_namada, + &channel_id_namada, + None, + None, + None, + None, + false, + None, + None, + )?; + wait_for_packet_relay( + &hermes_dir, + &port_id_namada, + &channel_id_namada, + &test, + )?; + // The balance should not have changed + check_shielded_balance(&test, AA_VIEWING_KEY, &ibc_denom_on_namada, 100)?; + check_cosmos_balance(&test_gaia, COSMOS_USER, COSMOS_COIN, 900)?; + + // Attempt to mint tokens twice, by including both a payment + // address and a shielding transfer. This should fail. + let shielding_data = { + let shielding_data_path = gen_ibc_shielding_data( + &test, + AA_PAYMENT_ADDRESS, + COSMOS_COIN, + 100, + &port_id_namada, + &channel_id_namada, + None, + )?; + String::from_utf8(std::fs::read(shielding_data_path)?)? + }; + transfer_from_cosmos( + &test_gaia, + COSMOS_USER, + albert_payment_addr.to_string(), + COSMOS_COIN, + 100, + &port_id_gaia, + &channel_id_gaia, + // NB: stuff the shielding data onto a `Right` + // variant, to avoid using the MASP transparent + // address as a receiver, and use the payment + // address instead + Some(Either::Right(shielding_data)), + // NB: add a timeout, to get refunded on Gaia + Some(Duration::from_secs(10)), + )?; + + // Check that the VP rejects the received packet + let mut hermes = bg_hermes.foreground(); + hermes.exp_string("Attempted to mint IBC tokens twice through the MASP")?; + let bg_hermes = hermes.background(); + wait_for_packet_relay( + &hermes_dir, + &port_id_gaia, + &channel_id_gaia, + &test_gaia, + )?; + check_shielded_balance(&test, AA_VIEWING_KEY, &ibc_denom_on_namada, 100)?; + check_cosmos_balance(&test_gaia, COSMOS_USER, COSMOS_COIN, 900)?; + + // Now let's activate rewards for samoleans on Namada + { + // Wait for tokens to be delegated, in case + // they haven't yet + epoch = get_epoch(&test, &rpc).unwrap(); + while epoch < delegated_epoch { + epoch = epoch_sleep(&test, &rpc, 120)?; + } + + // Launch inflation proposal on Namada + let start_epoch = propose_inflation(&test)?; + + // Vote + epoch = get_epoch(&test, &rpc).unwrap(); + while epoch < start_epoch { + epoch = epoch_sleep(&test, &rpc, 120)?; + } + submit_votes(&test)?; + + // Wait for grace epoch + let grace_epoch = start_epoch + 6u64 /* grace epoch offset */; + epoch = get_epoch(&test, &rpc).unwrap(); + while epoch < grace_epoch { + epoch = epoch_sleep(&test, &rpc, 120)?; + } + + // Wait the next masp epoch to update the conversion state + let new_masp_epoch = grace_epoch + MASP_EPOCH_MULTIPLIER; + epoch = get_epoch(&test, &rpc).unwrap(); + while epoch < new_masp_epoch { + epoch = epoch_sleep(&test, &rpc, 120)?; + } + } + + // Attempt to mint tokens twice, again, but this time, + // with rewards activated + let shielding_data = { + let shielding_data_path = gen_ibc_shielding_data( + &test, + AA_PAYMENT_ADDRESS, + COSMOS_COIN, + 100, + &port_id_namada, + &channel_id_namada, + None, + )?; + String::from_utf8(std::fs::read(shielding_data_path)?)? + }; + transfer_from_cosmos( + &test_gaia, + COSMOS_USER, + albert_payment_addr.to_string(), + COSMOS_COIN, + 100, + &port_id_gaia, + &channel_id_gaia, + Some(Either::Right(shielding_data)), + Some(Duration::from_secs(10)), + )?; + + // Check that the VP rejects the received packet + let mut hermes = bg_hermes.foreground(); + hermes.exp_string("Attempted to mint IBC tokens twice through the MASP")?; + let _bg_hermes = hermes.background(); + wait_for_packet_relay( + &hermes_dir, + &port_id_gaia, + &channel_id_gaia, + &test_gaia, + )?; + check_shielded_balance(&test, AA_VIEWING_KEY, &ibc_denom_on_namada, 100)?; + check_cosmos_balance(&test_gaia, COSMOS_USER, COSMOS_COIN, 900)?; + + // Shielding transfer 100 samoleans from Gaia to Namada (with rewards + // active) + let albert_payment_addr = find_payment_address(&test, AA_PAYMENT_ADDRESS)?; + transfer_from_cosmos( + &test_gaia, + COSMOS_USER, + albert_payment_addr.to_string(), + COSMOS_COIN, + 100, + &port_id_gaia, + &channel_id_gaia, + None, + None, + )?; + wait_for_packet_relay( + &hermes_dir, + &port_id_gaia, + &channel_id_gaia, + &test_gaia, + )?; + let ibc_denom_on_namada = + format!("{port_id_namada}/{channel_id_namada}/{COSMOS_COIN}"); + check_shielded_balance(&test, AA_VIEWING_KEY, &ibc_denom_on_namada, 200)?; + check_cosmos_balance(&test_gaia, COSMOS_USER, COSMOS_COIN, 800)?; + + // Check the balance of the minted NAM rewards + epoch = get_epoch(&test, &rpc).unwrap(); + let new_masp_epoch = epoch + MASP_EPOCH_MULTIPLIER; + while epoch < new_masp_epoch { + epoch = epoch_sleep(&test, &rpc, 120)?; + } + check_inflated_balance(&test, AA_VIEWING_KEY)?; + + Ok(()) +} + /// IBC transfer tests: /// 1. Transparent transfers /// - Namada -> Gaia -> Namada @@ -3306,8 +3587,14 @@ fn transfer_from_cosmos( // If the receiver is a payment address we want to mask it to the more // general MASP internal address to improve on privacy let receiver = match PaymentAddress::from_str(receiver.as_ref()) { - Ok(_) => MASP.to_string(), - Err(_) => receiver.as_ref().to_string(), + // assume we have IBC shielding data if the memo is read from the + // filesystem. in this we override the receiver, setting it to + // the MASP transparent addr. + Ok(_) if memo.as_ref().is_some_and(|memo| memo.is_left()) => { + MASP.to_string() + } + // otherwise, we take whatever receiver is specified in the call args. + _ => receiver.as_ref().to_string(), }; let mut args = vec![ "tx", @@ -3346,13 +3633,14 @@ fn transfer_from_cosmos( args.push(&memo); } - let timeout_nanosec = timeout_sec - .as_ref() - .map(|d| d.as_nanos().to_string()) - .unwrap_or_default(); - if timeout_sec.is_some() { + let timeout_nanosec; + if let Some(d) = timeout_sec { + timeout_nanosec = d.as_nanos().to_string(); + args.push("--packet-timeout-timestamp"); args.push(&timeout_nanosec); + args.push("--packet-timeout-height"); + args.push("0-0"); } let mut gaia = run_cosmos_cmd(test, args, Some(40))?; @@ -4385,7 +4673,7 @@ fn osmosis_xcs() -> Result<()> { { "operation": "set", "chain_name": "namada", - "prefix": "tnam" + "prefix": "znam" }, { "operation": "set", @@ -4616,66 +4904,72 @@ fn osmosis_xcs() -> Result<()> { "transfer/{channel_from_osmosis_to_gaia}/{COSMOS_COIN}" )); - // Transparently swap samoleans with nam - let mut cmd = run!( - &test_namada, - Bin::Client, - [ - "osmosis-swap", - "--osmosis-lcd", - "http://localhost:1317", - "--source", - BERTHA, - "--token", - NAM, - "--amount", - "0.000064", - "--channel-id", - channel_from_namada_to_osmosis.as_ref(), - "--output-denom", - &output_denom_on_namada, - "--local-recovery-addr", - &osmosis_jones, - "--swap-contract", - &crosschain_swaps_addr, - "--minimum-amount", - "1", - "--target", - BERTHA, - "--pool-hop", - &format!("1:{output_denom_on_osmosis}"), - "--node", - &rpc_namada, - ], - Some(80), - )?; + // NOTE: This cfg is only used to disable compiling this code. + // A separate contract needs to be deployed for transparent + // swaps, so we won't test it here. + #[cfg(target_family = "wasm")] + { + // Transparently swap samoleans with nam + let mut cmd = run!( + &test_namada, + Bin::Client, + [ + "osmosis-swap", + "--osmosis-lcd", + "http://localhost:1317", + "--source", + BERTHA, + "--token", + NAM, + "--amount", + "0.000064", + "--channel-id", + channel_from_namada_to_osmosis.as_ref(), + "--output-denom", + &output_denom_on_namada, + "--local-recovery-addr", + &osmosis_jones, + "--swap-contract", + &crosschain_swaps_addr, + "--minimum-amount", + "1", + "--target", + BERTHA, + "--pool-hop", + &format!("1:{output_denom_on_osmosis}"), + "--node", + &rpc_namada, + ], + Some(80), + )?; - // confirm trade - cmd.send_line("y")?; - cmd.assert_success(); + // confirm trade + cmd.send_line("y")?; + cmd.assert_success(); - wait_for_packet_relay( - &hermes_namada_osmosis, - &PortId::transfer(), - &channel_from_osmosis_to_namada, - &test_osmosis, - )?; - wait_for_packet_relay( - &hermes_gaia_namada, - &PortId::transfer(), - &channel_from_gaia_to_namada, - &test_namada, - )?; + wait_for_packet_relay( + &hermes_namada_osmosis, + &PortId::transfer(), + &channel_from_osmosis_to_namada, + &test_osmosis, + )?; + wait_for_packet_relay( + &hermes_gaia_namada, + &PortId::transfer(), + &channel_from_gaia_to_namada, + &test_namada, + )?; - // Check that the swap worked - // 39 is derived from the uniswap formula: - // floor( 100 - (100*100/(100 + 64)) ) - check_balance( - &test_namada, - BERTHA, - format!("transfer/{channel_from_namada_to_gaia}/{COSMOS_COIN}"), - 39, - )?; + // Check that the swap worked + // 39 is derived from the uniswap formula: + // floor( 100 - (100*100/(100 + 64)) ) + check_balance( + &test_namada, + BERTHA, + format!("transfer/{channel_from_namada_to_gaia}/{COSMOS_COIN}"), + 39, + )?; + } // Perform a shielded swap of samoleans and nam let mut cmd = run!( @@ -4686,7 +4980,7 @@ fn osmosis_xcs() -> Result<()> { "--osmosis-lcd", "http://localhost:1317", "--source", - AA_VIEWING_KEY, + A_SPENDING_KEY, "--token", NAM, "--amount", @@ -4703,8 +4997,6 @@ fn osmosis_xcs() -> Result<()> { "10", "--target-pa", AA_PAYMENT_ADDRESS, - "--overflow-addr", - ALBERT, "--pool-hop", &format!("1:{output_denom_on_osmosis}"), "--gas-payer", @@ -4734,17 +5026,13 @@ fn osmosis_xcs() -> Result<()> { &test_namada, )?; - // Check that the minimum amount got shielded + // Check the shielded balance. check_shielded_balance( &test_namada, AA_VIEWING_KEY, &output_denom_on_namada, - 10, + 35, )?; - // 5 is derived from the uniswap formula: - // floor( 61 - ( 164 * 61 / (164 + 56) ) ) minus - // the minimum amount (10) which was shielded - check_balance(&test_namada, ALBERT, &output_denom_on_namada, 5)?; Ok(()) } diff --git a/crates/tests/src/native_vp/mod.rs b/crates/tests/src/native_vp/mod.rs index 3fc7f93f863..c75bfa587fc 100644 --- a/crates/tests/src/native_vp/mod.rs +++ b/crates/tests/src/native_vp/mod.rs @@ -66,7 +66,7 @@ impl TestNativeVpEnv { &self.tx_env.state, &self.tx_env.batched_tx.tx, &self.tx_env.batched_tx.cmt, - &self.tx_env.tx_index, + &self.tx_env.indexed_tx, gas_meter, &self.keys_changed, &self.verifiers, @@ -86,7 +86,7 @@ impl TestNativeVpEnv { &self.tx_env.state, &self.tx_env.batched_tx.tx, &self.tx_env.batched_tx.cmt, - &self.tx_env.tx_index, + &self.tx_env.indexed_tx, gas_meter, &self.keys_changed, &self.verifiers, diff --git a/crates/tests/src/vm_host_env/ibc.rs b/crates/tests/src/vm_host_env/ibc.rs index 534c6bdc316..e1e5cbdb3c3 100644 --- a/crates/tests/src/vm_host_env/ibc.rs +++ b/crates/tests/src/vm_host_env/ibc.rs @@ -68,7 +68,7 @@ use namada_sdk::proof_of_stake::OwnedPosParams; use namada_sdk::proof_of_stake::test_utils::get_dummy_genesis_validator; use namada_sdk::state::StateRead; use namada_sdk::state::testing::TestState; -use namada_sdk::storage::{self, BlockHeight, Epoch, Key, TxIndex}; +use namada_sdk::storage::{self, BlockHeight, Epoch, Key}; use namada_sdk::tendermint::time::Time as TmTime; use namada_sdk::time::DurationSecs; use namada_sdk::tx::BatchedTxRef; @@ -123,12 +123,13 @@ pub fn validate_ibc_vp_from_tx<'a>( let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(10_000_000_000, 1), )); + let indexed_tx = namada_sdk::tx::IndexedTx::default(); let ctx = Ctx::new( &ADDRESS, &tx_env.state, batched_tx.tx, batched_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -163,12 +164,13 @@ pub fn validate_multitoken_vp_from_tx<'a>( let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( &TxGasMeter::new(10_000_000_000, 1), )); + let indexed_tx = namada_sdk::tx::IndexedTx::default(); let ctx = Ctx::<_, _, VpEvalWasm<_, _, _>>::new( &ADDRESS, &tx_env.state, batched_tx.tx, batched_tx.cmt, - &TxIndex(0), + &indexed_tx, &gas_meter, &keys_changed, &verifiers, diff --git a/crates/tests/src/vm_host_env/tx.rs b/crates/tests/src/vm_host_env/tx.rs index 36430728e8f..ab65e3c9a04 100644 --- a/crates/tests/src/vm_host_env/tx.rs +++ b/crates/tests/src/vm_host_env/tx.rs @@ -9,8 +9,8 @@ use namada_sdk::hash::Hash; use namada_sdk::parameters::{self, EpochDuration}; use namada_sdk::state::prefix_iter::PrefixIterators; use namada_sdk::state::testing::TestState; +use namada_sdk::storage::Key; use namada_sdk::storage::mockdb::MockDB; -use namada_sdk::storage::{Key, TxIndex}; use namada_sdk::time::DurationSecs; pub use namada_sdk::tx::data::TxType; pub use namada_sdk::tx::*; @@ -55,7 +55,7 @@ pub struct TestTxEnv { pub verifiers: BTreeSet
, pub gas_meter: RefCell>, pub sentinel: RefCell, - pub tx_index: TxIndex, + pub indexed_tx: IndexedTx, pub result_buffer: Option>, pub yielded_value: Option>, pub vp_wasm_cache: VpCache, @@ -90,7 +90,7 @@ impl Default for TestTxEnv { 1, ))), sentinel: RefCell::new(TxSentinel::default()), - tx_index: TxIndex::default(), + indexed_tx: IndexedTx::default(), verifiers: BTreeSet::default(), result_buffer: None, yielded_value: None, @@ -243,7 +243,7 @@ impl TestTxEnv { &mut self.state, &gas_meter, None, - &self.tx_index, + &self.indexed_tx, &self.batched_tx.tx, &self.batched_tx.cmt, &mut self.vp_wasm_cache, @@ -370,7 +370,7 @@ mod native_tx_host_env { sentinel, result_buffer, yielded_value, - tx_index, + indexed_tx, vp_wasm_cache, vp_cache_dir: _, tx_wasm_cache, @@ -387,7 +387,7 @@ mod native_tx_host_env { sentinel, &batched_tx.tx, &batched_tx.cmt, - tx_index, + indexed_tx, result_buffer, yielded_value, vp_wasm_cache, @@ -408,7 +408,7 @@ mod native_tx_host_env { #[unsafe(no_mangle)] extern "C-unwind" fn extern_fn_name( $($arg: $type),* ) -> $ret { with(|TestTxEnv { - tx_index, + indexed_tx, state, iterators, verifiers, @@ -432,7 +432,7 @@ mod native_tx_host_env { sentinel, &batched_tx.tx, &batched_tx.cmt, - tx_index, + indexed_tx, result_buffer, yielded_value, vp_wasm_cache, @@ -460,7 +460,7 @@ mod native_tx_host_env { sentinel, result_buffer, yielded_value, - tx_index, + indexed_tx, vp_wasm_cache, vp_cache_dir: _, tx_wasm_cache, @@ -477,7 +477,7 @@ mod native_tx_host_env { sentinel, &batched_tx.tx, &batched_tx.cmt, - tx_index, + indexed_tx, result_buffer, yielded_value, vp_wasm_cache, @@ -780,7 +780,7 @@ mod tests { sentinel, result_buffer, yielded_value, - tx_index, + indexed_tx, vp_wasm_cache, vp_cache_dir: _, tx_wasm_cache, @@ -797,7 +797,7 @@ mod tests { sentinel, &batched_tx.tx, &batched_tx.cmt, - tx_index, + indexed_tx, result_buffer, yielded_value, wasmer_store.clone(), diff --git a/crates/tests/src/vm_host_env/vp.rs b/crates/tests/src/vm_host_env/vp.rs index 58a59e7e406..8edbeeec1f3 100644 --- a/crates/tests/src/vm_host_env/vp.rs +++ b/crates/tests/src/vm_host_env/vp.rs @@ -6,9 +6,9 @@ use namada_sdk::gas::{TxGasMeter, VpGasMeter}; use namada_sdk::state::mockdb::MockDB; use namada_sdk::state::prefix_iter::PrefixIterators; use namada_sdk::state::testing::TestState; -use namada_sdk::storage::{self, Key, TxIndex}; -use namada_sdk::tx::Tx; +use namada_sdk::storage::{self, Key}; use namada_sdk::tx::data::TxType; +use namada_sdk::tx::{IndexedTx, Tx}; use namada_tx_prelude::BatchedTx; use namada_vm::WasmCacheRwAccess; use namada_vm::host_env::gas_meter::GasMeter; @@ -47,7 +47,7 @@ pub struct TestVpEnv { pub iterators: PrefixIterators<'static, MockDB>, pub gas_meter: RefCell>, pub batched_tx: BatchedTx, - pub tx_index: TxIndex, + pub indexed_tx: IndexedTx, pub keys_changed: BTreeSet, pub verifiers: BTreeSet
, pub eval_runner: native_vp_host_env::VpEval, @@ -80,7 +80,7 @@ impl Default for TestVpEnv { )), )), batched_tx, - tx_index: TxIndex::default(), + indexed_tx: IndexedTx::default(), keys_changed: BTreeSet::default(), verifiers: BTreeSet::default(), eval_runner, @@ -237,7 +237,7 @@ mod native_vp_host_env { iterators, gas_meter, batched_tx, - tx_index, + indexed_tx, keys_changed, verifiers, eval_runner, @@ -254,7 +254,7 @@ mod native_vp_host_env { gas_meter, &batched_tx.tx, &batched_tx.cmt, - tx_index, + indexed_tx, verifiers, result_buffer, yielded_value, @@ -282,7 +282,7 @@ mod native_vp_host_env { iterators, gas_meter, batched_tx, - tx_index, + indexed_tx, keys_changed, verifiers, eval_runner, @@ -299,7 +299,7 @@ mod native_vp_host_env { gas_meter, &batched_tx.tx, &batched_tx.cmt, - tx_index, + indexed_tx, verifiers, result_buffer, yielded_value, diff --git a/crates/trans_token/src/vp.rs b/crates/trans_token/src/vp.rs index 36eb7a0f33c..45b2282f5c7 100644 --- a/crates/trans_token/src/vp.rs +++ b/crates/trans_token/src/vp.rs @@ -358,10 +358,12 @@ mod tests { use namada_ibc::trace::ibc_token; use namada_parameters::storage::get_native_token_transferable_key; use namada_state::testing::TestState; - use namada_state::{StateRead, StorageWrite, TxIndex}; + use namada_state::{StateRead, StorageWrite}; use namada_tx::action::Write; use namada_tx::data::TxType; - use namada_tx::{Authorization, BatchedTx, Code, Data, Section, Tx}; + use namada_tx::{ + Authorization, BatchedTx, Code, Data, IndexedTx, Section, Tx, + }; use namada_vm::WasmCacheRwAccess; use namada_vm::wasm::VpCache; use namada_vm::wasm::compilation_cache::common::testing::vp_cache; @@ -447,7 +449,7 @@ mod tests { let dest = established_address_2(); let keys_changed = transfer(&mut state, &src, &dest); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -463,7 +465,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -497,7 +499,7 @@ mod tests { .write(&dest_key, amount.serialize_to_vec()) .expect("write failed"); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -511,7 +513,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -564,7 +566,7 @@ mod tests { .expect("write failed"); keys_changed.insert(minter_key); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -584,7 +586,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -635,7 +637,7 @@ mod tests { .expect("write failed"); keys_changed.insert(minter_key); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -651,7 +653,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -697,7 +699,7 @@ mod tests { // no minter is set - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -711,7 +713,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -764,7 +766,7 @@ mod tests { .expect("write failed"); keys_changed.insert(minter_key); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -780,7 +782,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -813,7 +815,7 @@ mod tests { keys_changed.insert(minter_key); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -829,7 +831,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -865,7 +867,7 @@ mod tests { keys_changed.insert(key); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -879,7 +881,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -909,7 +911,7 @@ mod tests { let key = get_native_token_transferable_key(); state.write(&key, false).unwrap(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -924,7 +926,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -961,7 +963,7 @@ mod tests { let key = get_native_token_transferable_key(); state.write(&key, false).unwrap(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -977,7 +979,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1012,7 +1014,7 @@ mod tests { let key = get_native_token_transferable_key(); state.write(&key, false).unwrap(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -1027,7 +1029,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1062,7 +1064,7 @@ mod tests { let key = get_native_token_transferable_key(); state.write(&key, false).unwrap(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -1078,7 +1080,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1118,7 +1120,7 @@ mod tests { let key = get_native_token_transferable_key(); state.write(&key, false).unwrap(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -1134,7 +1136,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1165,7 +1167,7 @@ mod tests { let mut keys_changed = transfer(&mut state, &src1, &dest1); keys_changed.append(&mut transfer(&mut state, &src2, &dest2)); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let BatchedTx { tx, cmt } = dummy_tx(&state); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter(&TxGasMeter::new( @@ -1187,7 +1189,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &verifiers, @@ -1219,7 +1221,7 @@ mod tests { &state, &tx, &cmt, - &tx_index, + &indexed_tx, &gas_meter, &keys_changed, &parties, diff --git a/crates/tx/src/event.rs b/crates/tx/src/event.rs index 4240076b4d3..dbacab6bd4b 100644 --- a/crates/tx/src/event.rs +++ b/crates/tx/src/event.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use namada_core::borsh::{BorshDeserialize, BorshSerialize}; use namada_core::ibc::IbcTxDataHash; -use namada_core::masp::MaspTxId; +use namada_core::masp::{CompactNote, MaspTxId}; use namada_events::extend::{ ComposeEvent, EventAttributeEntry, Height, Log, TxHash, }; @@ -153,14 +153,15 @@ impl From for EventType { } } -/// A type representing the possible reference to some MASP data, either a masp -/// section or ibc tx data +/// A type representing the possible reference to some MASP data #[derive(Clone, Serialize, Deserialize)] pub enum MaspTxRef { /// Reference to a MASP section MaspSection(MaspTxId), /// Reference to an ibc tx data section IbcData(IbcTxDataHash), + /// Unencrypted notes generated by the protocol + Unencrypted(Vec), } impl Display for MaspTxRef { @@ -177,10 +178,6 @@ impl FromStr for MaspTxRef { } } -/// A list of MASP tx references -#[derive(Default, Clone, Serialize, Deserialize)] -pub struct MaspTxRefs(pub Vec<(IndexedTx, MaspTxRef)>); - /// MASP transaction event pub struct MaspEvent { /// The indexed transaction that generated this event @@ -225,3 +222,38 @@ impl EventAttributeEntry<'static> for IndexedTx { self } } + +/// Marker event attribute used to signal a MASP note +/// was created by the protocol via IBC. +#[derive(Debug)] +pub struct ProtocolIbcShielding; + +impl Display for ProtocolIbcShielding { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) + } +} + +impl FromStr for ProtocolIbcShielding { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + if !s.is_empty() { + return Err( + "ProtocolIbcShielding is just a marker, no value expected" + ); + } + Ok(Self) + } +} + +impl EventAttributeEntry<'static> for ProtocolIbcShielding { + type Value = Self; + type ValueOwned = Self; + + const KEY: &'static str = "protocol-ibc-shielding"; + + fn into_value(self) -> Self::Value { + self + } +} diff --git a/crates/tx/src/types.rs b/crates/tx/src/types.rs index 7a3539642f5..c355ea5aa7a 100644 --- a/crates/tx/src/types.rs +++ b/crates/tx/src/types.rs @@ -1056,6 +1056,22 @@ impl FromStr for IndexedTx { } } +impl From<(BlockHeight, TxIndex, Option)> for IndexedTx { + fn from( + (block_height, block_index, batch_index): ( + BlockHeight, + TxIndex, + Option, + ), + ) -> Self { + Self { + block_height, + block_index, + batch_index, + } + } +} + /// Inclusive range of [`IndexedTx`] entries. pub struct IndexedTxRange { lo: IndexedTx, diff --git a/crates/tx_prelude/src/ibc.rs b/crates/tx_prelude/src/ibc.rs index 0b9185c3a4b..1ccd84cc5e5 100644 --- a/crates/tx_prelude/src/ibc.rs +++ b/crates/tx_prelude/src/ibc.rs @@ -5,7 +5,9 @@ use std::collections::BTreeSet; use std::rc::Rc; use namada_core::address::Address; +use namada_core::masp_primitives::asset_type::AssetType; use namada_core::token::Amount; +use namada_events::Event; use namada_ibc::context::middlewares::create_transfer_middlewares; pub use namada_ibc::event::{IbcEvent, IbcEventType}; pub use namada_ibc::storage::{ @@ -18,6 +20,7 @@ pub use namada_ibc::{ IbcActions, IbcCommonContext, IbcStorageContext, NftTransferModule, ProofSpec, TransferModule, }; +use namada_state::in_mem_virtual_storage; use namada_tx_env::TxEnv; use crate::{Ctx, Result, parameters, token}; @@ -27,14 +30,22 @@ use crate::{Ctx, Result, parameters, token}; /// execution. pub fn ibc_actions( ctx: &mut Ctx, -) -> IbcActions<'_, Ctx, crate::parameters::Store, token::Store> { +) -> IbcActions< + '_, + Ctx, + crate::parameters::Store, + token::Store, + token::ShieldedStore, +> { let ctx = Rc::new(RefCell::new(ctx.clone())); let verifiers = Rc::new(RefCell::new(BTreeSet::
::new())); let mut actions = IbcActions::new(ctx.clone(), verifiers.clone()); - let module = create_transfer_middlewares::<_, parameters::Store>( - ctx.clone(), - verifiers, - ); + let module = create_transfer_middlewares::< + _, + parameters::Store, + token::Store, + token::ShieldedStore, + >(ctx.clone(), verifiers); actions.add_transfer_module(module); let module = NftTransferModule::>::new(ctx); actions.add_transfer_module(module); @@ -56,7 +67,13 @@ impl IbcStorageContext for Ctx { super::log_string(message); } - fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<()> { + fn has_conversion(&self, asset_type: &AssetType) -> Result { + Ok(self + .read_temp(&in_mem_virtual_storage::has_conversion_key(asset_type))? + .unwrap_or_default()) + } + + fn emit_event(&mut self, event: Event) -> Result<()> { ::emit_event(self, event) } diff --git a/crates/tx_prelude/src/lib.rs b/crates/tx_prelude/src/lib.rs index e4e26dc1f35..4fc63315f57 100644 --- a/crates/tx_prelude/src/lib.rs +++ b/crates/tx_prelude/src/lib.rs @@ -50,7 +50,9 @@ pub use namada_state::{ collections, iter_prefix, iter_prefix_bytes, }; use namada_token::MaspTransaction; -pub use namada_tx::{BatchedTx, Section, Tx, action, data as transaction}; +pub use namada_tx::{ + BatchedTx, IndexedTx, Section, Tx, action, data as transaction, +}; pub use namada_tx_env::TxEnv; use namada_vm_env::tx::*; use namada_vm_env::{read_from_buffer, read_key_val_bytes_from_buffer}; @@ -240,9 +242,19 @@ impl StorageRead for Ctx { )) } - fn get_tx_index(&self) -> Result { - let tx_index = unsafe { namada_tx_get_tx_index() }; - Ok(TxIndex(tx_index)) + fn get_tx_index(&self) -> Result<(BlockHeight, TxIndex, Option)> { + // NOTE: the conversion to i64 is infallible + let read_result = unsafe { namada_tx_get_tx_index() } as i64; + let bytes = read_from_buffer(read_result, namada_tx_result_buffer) + .ok_or(Error::SimpleMessage( + "Missing result from `namada_tx_get_tx_index` call", + ))?; + let IndexedTx { + block_height, + block_index, + batch_index, + } = namada_core::decode(bytes).expect("Cannot decode tx index"); + Ok((block_height, block_index, batch_index)) } } diff --git a/crates/tx_prelude/src/token.rs b/crates/tx_prelude/src/token.rs index 1827f10fb4c..b870bea6744 100644 --- a/crates/tx_prelude/src/token.rs +++ b/crates/tx_prelude/src/token.rs @@ -7,8 +7,8 @@ use namada_token::TransparentTransfersRef; pub use namada_token::testing; pub use namada_token::tx::apply_shielded_transfer; pub use namada_token::{ - Amount, DenominatedAmount, Denomination, MaspDigitPos, Store, Transfer, - storage_key, utils, validate_transfer_in_out, + Amount, DenominatedAmount, Denomination, MaspDigitPos, ShieldedStore, + Store, Transfer, storage_key, utils, validate_transfer_in_out, }; use namada_tx::BatchedTx; use namada_tx_env::Address; diff --git a/crates/vm/src/host_env.rs b/crates/vm/src/host_env.rs index ead41019254..724597e0e46 100644 --- a/crates/vm/src/host_env.rs +++ b/crates/vm/src/host_env.rs @@ -19,7 +19,7 @@ use namada_core::collections::HashSet; use namada_core::decode; use namada_core::hash::Hash; use namada_core::internal::{HostEnvResult, KeyVal}; -use namada_core::storage::{Key, TX_INDEX_LENGTH, TxIndex}; +use namada_core::storage::{Key, TX_INDEX_LENGTH}; use namada_events::{Event, EventTypeBuilder}; use namada_gas::{ self as gas, Gas, GasMetering, MEMORY_ACCESS_GAS_PER_BYTE, TxGasMeter, @@ -29,7 +29,7 @@ use namada_state::prefix_iter::{PrefixIteratorId, PrefixIterators}; use namada_state::write_log::{self, WriteLog}; use namada_state::{ DB, DBIter, InMemory, OptionExt, ResultExt, State, StateRead, - StorageHasher, StorageRead, StorageWrite, + StorageHasher, StorageRead, StorageWrite, in_mem_virtual_storage, }; pub use namada_state::{Error, Result}; use namada_token::MaspTransaction; @@ -38,7 +38,7 @@ use namada_token::storage_key::{ is_any_token_parameter_key, }; use namada_tx::data::{InnerTxId, TxSentinel}; -use namada_tx::{BatchedTx, BatchedTxRef, Tx, TxCommitments}; +use namada_tx::{BatchedTx, BatchedTxRef, IndexedTx, Tx, TxCommitments}; use namada_vp::vp_host_fns; use thiserror::Error; @@ -133,7 +133,7 @@ where pub cmt: HostRef, /// The transaction index is used to identify a shielded transaction's /// parent - pub tx_index: HostRef, + pub indexed_tx: HostRef, /// The verifiers whose validity predicates should be triggered. pub verifiers: HostRef>, /// Cache for 2-step reads from host environment. @@ -178,7 +178,7 @@ where wrapper_hash: &Hash, tx: &Tx, cmt: &TxCommitments, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, verifiers: &mut BTreeSet
, result_buffer: &mut Option>, yielded_value: &mut Option>, @@ -194,7 +194,7 @@ where let wrapper_hash = unsafe { RoHostRef::new(wrapper_hash) }; let tx = unsafe { RoHostRef::new(tx) }; let cmt = unsafe { RoHostRef::new(cmt) }; - let tx_index = unsafe { RoHostRef::new(tx_index) }; + let indexed_tx = unsafe { RoHostRef::new(indexed_tx) }; let verifiers = unsafe { RwHostRef::new(verifiers) }; let result_buffer = unsafe { RwHostRef::new(result_buffer) }; let yielded_value = unsafe { RwHostRef::new(yielded_value) }; @@ -212,7 +212,7 @@ where wrapper_hash, tx, cmt, - tx_index, + indexed_tx, verifiers, result_buffer, yielded_value, @@ -305,7 +305,7 @@ where wrapper_hash: self.wrapper_hash, tx: self.tx, cmt: self.cmt, - tx_index: self.tx_index, + indexed_tx: self.indexed_tx, verifiers: self.verifiers, result_buffer: self.result_buffer, yielded_value: self.yielded_value, @@ -358,7 +358,7 @@ where pub cmt: HostRef, /// The transaction index is used to identify a shielded transaction's /// parent - pub tx_index: HostRef, + pub indexed_tx: HostRef, /// The runner of the [`vp_eval`] function pub eval_runner: HostRef, /// Cache for 2-step reads from host environment. @@ -427,7 +427,7 @@ where gas_meter: &RefCell>, tx: &Tx, cmt: &TxCommitments, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, iterators: &mut PrefixIterators<'static, D>, verifiers: &BTreeSet
, result_buffer: &mut Option>, @@ -444,7 +444,7 @@ where gas_meter, tx, cmt, - tx_index, + indexed_tx, iterators, verifiers, result_buffer, @@ -508,7 +508,7 @@ where gas_meter: &RefCell>, tx: &Tx, cmt: &TxCommitments, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, iterators: &mut PrefixIterators<'static, D>, verifiers: &BTreeSet
, result_buffer: &mut Option>, @@ -523,7 +523,7 @@ where let in_mem = unsafe { RoHostRef::new(in_mem) }; let tx = unsafe { RoHostRef::new(tx) }; let cmt = unsafe { RoHostRef::new(cmt) }; - let tx_index = unsafe { RoHostRef::new(tx_index) }; + let indexed_tx = unsafe { RoHostRef::new(indexed_tx) }; let iterators = unsafe { RwHostRef::new(iterators) }; let gas_meter = unsafe { RoHostRef::new(gas_meter) }; let verifiers = unsafe { RoHostRef::new(verifiers) }; @@ -542,7 +542,7 @@ where gas_meter, tx, cmt, - tx_index, + indexed_tx, eval_runner, result_buffer, yielded_value, @@ -592,7 +592,7 @@ where gas_meter: self.gas_meter, tx: self.tx, cmt: self.cmt, - tx_index: self.tx_index, + indexed_tx: self.indexed_tx, eval_runner: self.eval_runner, result_buffer: self.result_buffer, yielded_value: self.yielded_value, @@ -734,17 +734,27 @@ where let key = Key::parse(key)?; - let write_log = unsafe { env.ctx.write_log.get() }; - let (log_val, gas) = write_log.read_temp(&key).into_storage_result()?; + let (val, gas) = if in_mem_virtual_storage::is_in_mem_key(&key) { + let in_mem = unsafe { env.ctx.in_mem.get() }; + let (val, gas) = + in_mem_virtual_storage::read_from_in_mem(&key, in_mem)?; + (Some(val), gas) + } else { + let write_log = unsafe { env.ctx.write_log.get() }; + let (val, gas) = write_log.read_temp(&key).into_storage_result()?; + (val.cloned(), gas) + }; + consume_tx_gas::(env, gas)?; - match log_val { + + match val { Some(value) => { let len: i64 = value .len() .try_into() .map_err(TxRuntimeError::NumConversionError)?; let result_buffer = unsafe { env.ctx.result_buffer.get_mut() }; - result_buffer.replace(value.clone()); + result_buffer.replace(value); Ok(len) } None => Ok(HostEnvResult::Fail.to_i64()), @@ -1636,8 +1646,18 @@ where .expect("Consts mul that cannot overflow") .into(), )?; - let tx_index = unsafe { env.ctx.tx_index.get() }; - Ok(tx_index.0) + + let indexed_tx = unsafe { env.ctx.indexed_tx.get() }; + + let value = indexed_tx.serialize_to_vec(); + let len: u32 = value + .len() + .try_into() + .map_err(TxRuntimeError::NumConversionError)?; + let result_buffer = unsafe { env.ctx.result_buffer.get_mut() }; + result_buffer.replace(value); + + Ok(len) } /// Getting the block height function exposed to the wasm VM VP @@ -1654,9 +1674,18 @@ where CA: WasmCacheAccess, { let gas_meter = env.ctx.gas_meter(); - let tx_index = unsafe { env.ctx.tx_index.get() }; - let tx_idx = vp_host_fns::get_tx_index(gas_meter, tx_index)?; - Ok(tx_idx.0) + let indexed_tx = unsafe { env.ctx.indexed_tx.get() }; + let indexed_tx = vp_host_fns::get_tx_index(gas_meter, indexed_tx)?; + + let value = indexed_tx.serialize_to_vec(); + let len: u32 = value + .len() + .try_into() + .map_err(TxRuntimeError::NumConversionError)?; + let result_buffer = unsafe { env.ctx.result_buffer.get_mut() }; + result_buffer.replace(value); + + Ok(len) } /// Getting the block epoch function exposed to the wasm VM Tx @@ -2419,7 +2448,7 @@ pub mod testing { sentinel: &RefCell, tx: &Tx, cmt: &TxCommitments, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, result_buffer: &mut Option>, yielded_value: &mut Option>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, @@ -2442,7 +2471,7 @@ pub mod testing { &Hash::zero(), tx, cmt, - tx_index, + indexed_tx, verifiers, result_buffer, yielded_value, @@ -2463,7 +2492,7 @@ pub mod testing { sentinel: &RefCell, tx: &Tx, cmt: &TxCommitments, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, result_buffer: &mut Option>, yielded_value: &mut Option>, store: Rc>, @@ -2493,7 +2522,7 @@ pub mod testing { &Hash::zero(), tx, cmt, - tx_index, + indexed_tx, verifiers, result_buffer, yielded_value, @@ -2516,7 +2545,7 @@ pub mod testing { gas_meter: &RefCell>, tx: &Tx, cmt: &TxCommitments, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, verifiers: &BTreeSet
, result_buffer: &mut Option>, yielded_value: &mut Option>, @@ -2539,7 +2568,7 @@ pub mod testing { gas_meter, tx, cmt, - tx_index, + indexed_tx, iterators, verifiers, result_buffer, diff --git a/crates/vm/src/wasm/run.rs b/crates/vm/src/wasm/run.rs index 6367c48f7bc..791a7914144 100644 --- a/crates/vm/src/wasm/run.rs +++ b/crates/vm/src/wasm/run.rs @@ -12,14 +12,16 @@ use borsh::BorshDeserialize; use namada_core::address::Address; use namada_core::hash::{Error as TxHashError, Hash}; use namada_core::internal::HostEnvResult; -use namada_core::storage::{Key, TxIndex}; +use namada_core::storage::Key; use namada_core::validity_predicate::VpError; pub use namada_gas::GasMeterKind; use namada_gas::{GasMetering, TxGasMeter, VpGasMeter, WASM_MEMORY_PAGE_GAS}; use namada_state::prefix_iter::PrefixIterators; use namada_state::{DB, DBIter, State, StateRead, StorageHasher, StorageRead}; use namada_tx::data::{TxSentinel, TxType}; -use namada_tx::{BatchedTxRef, Commitment, Section, Tx, TxCommitments}; +use namada_tx::{ + BatchedTxRef, Commitment, IndexedTx, Section, Tx, TxCommitments, +}; use namada_vp::vp_host_fns; use parity_wasm::elements::Instruction::*; use parity_wasm::elements::{self, SignExtInstruction}; @@ -151,7 +153,7 @@ pub fn tx( state: &mut S, gas_meter: &RefCell, wrapper_hash: Option<&Hash>, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, tx: &Tx, cmt: &TxCommitments, vp_wasm_cache: &mut VpCache, @@ -252,7 +254,7 @@ where wrapper_hash, tx, cmt, - tx_index, + indexed_tx, &mut verifiers, &mut result_buffer, &mut yielded_value, @@ -404,7 +406,7 @@ fn extract_tx_error( pub fn vp( vp_code_hash: Hash, batched_tx: &BatchedTxRef<'_>, - tx_index: &TxIndex, + indexed_tx: &IndexedTx, address: &Address, state: &S, gas_meter: &RefCell, @@ -456,7 +458,7 @@ where &wasm_gas_meter, tx, cmt, - tx_index, + indexed_tx, &mut iterators, verifiers, &mut result_buffer, @@ -717,7 +719,7 @@ where &wasm_gas_meter, native_ctx.tx, native_ctx.cmt, - native_ctx.tx_index, + native_ctx.indexed_tx, &mut iterators, native_ctx.verifiers, &mut result_buffer, @@ -1594,7 +1596,7 @@ mod tests { fn test_tx_memory_limiter_in_guest() { let mut state = TestState::default(); let gas_meter = RefCell::new(TxGasMeter::new(TX_GAS_LIMIT, GAS_SCALE)); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); // This code will allocate memory of the given size let tx_code = TestWasms::TxMemoryLimit.read_bytes(); @@ -1624,7 +1626,7 @@ mod tests { &mut state, &gas_meter, None, - &tx_index, + &indexed_tx, batched_tx.tx, batched_tx.cmt, &mut vp_cache, @@ -1645,7 +1647,7 @@ mod tests { &mut state, &gas_meter, None, - &tx_index, + &indexed_tx, batched_tx.tx, batched_tx.cmt, &mut vp_cache, @@ -1670,7 +1672,7 @@ mod tests { )); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); // This code will call `eval` with the other VP below let vp_eval = TestWasms::VpEval.read_bytes(); @@ -1717,7 +1719,7 @@ mod tests { vp( code_hash, &outer_tx.batch_ref_first_tx().unwrap(), - &tx_index, + &indexed_tx, &addr, &state, &gas_meter, @@ -1751,7 +1753,7 @@ mod tests { vp( code_hash, &outer_tx.batch_ref_first_tx().unwrap(), - &tx_index, + &indexed_tx, &addr, &state, &gas_meter, @@ -1776,7 +1778,7 @@ mod tests { )); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); // This code will allocate memory of the given size let vp_code = TestWasms::VpMemoryLimit.read_bytes(); @@ -1803,7 +1805,7 @@ mod tests { let result = vp( code_hash, &outer_tx.batch_ref_first_tx().unwrap(), - &tx_index, + &indexed_tx, &addr, &state, &gas_meter, @@ -1824,7 +1826,7 @@ mod tests { let error = vp( code_hash, &outer_tx.batch_ref_first_tx().unwrap(), - &tx_index, + &indexed_tx, &addr, &state, &gas_meter, @@ -1845,7 +1847,7 @@ mod tests { fn test_tx_memory_limiter_in_host_input() { let mut state = TestState::default(); let gas_meter = RefCell::new(TxGasMeter::new(TX_GAS_LIMIT, GAS_SCALE)); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_no_op = TestWasms::TxNoOp.read_bytes(); // store the wasm code @@ -1878,7 +1880,7 @@ mod tests { &mut state, &gas_meter, None, - &tx_index, + &indexed_tx, batched_tx.tx, batched_tx.cmt, &mut vp_cache, @@ -1907,7 +1909,7 @@ mod tests { )); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // store the wasm code @@ -1934,7 +1936,7 @@ mod tests { let result = vp( code_hash, &outer_tx.batch_ref_first_tx().unwrap(), - &tx_index, + &indexed_tx, &addr, &state, &gas_meter, @@ -1961,7 +1963,7 @@ mod tests { fn test_tx_memory_limiter_in_host_env() { let mut state = TestState::default(); let gas_meter = RefCell::new(TxGasMeter::new(TX_GAS_LIMIT, GAS_SCALE)); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_read_key = TestWasms::TxReadStorageKey.read_bytes(); // store the wasm code @@ -1999,7 +2001,7 @@ mod tests { &mut state, &gas_meter, None, - &tx_index, + &indexed_tx, batched_tx.tx, batched_tx.cmt, &mut vp_cache, @@ -2024,7 +2026,7 @@ mod tests { )); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let vp_read_key = TestWasms::VpReadStorageKey.read_bytes(); // store the wasm code @@ -2056,7 +2058,7 @@ mod tests { let error = vp( code_hash, &outer_tx.batch_ref_first_tx().unwrap(), - &tx_index, + &indexed_tx, &addr, &state, &gas_meter, @@ -2084,7 +2086,7 @@ mod tests { )); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); // This code will call `eval` with the other VP below let vp_eval = TestWasms::VpEval.read_bytes(); @@ -2135,7 +2137,7 @@ mod tests { vp( code_hash, &outer_tx.batch_ref_first_tx().unwrap(), - &tx_index, + &indexed_tx, &addr, &state, &gas_meter, @@ -2225,7 +2227,7 @@ mod tests { let mut state = TestState::default(); let gas_meter = RefCell::new(TxGasMeter::new(OUT_OF_GAS_LIMIT, GAS_SCALE)); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); // This code will charge gas in a host function indefinetely let tx_code = TestWasms::TxInfiniteGuestGas.read_bytes(); @@ -2249,7 +2251,7 @@ mod tests { &mut state, &gas_meter, None, - &tx_index, + &indexed_tx, batched_tx.tx, batched_tx.cmt, &mut vp_cache, @@ -2268,7 +2270,7 @@ mod tests { let mut state = TestState::default(); let gas_meter = RefCell::new(TxGasMeter::new(OUT_OF_GAS_LIMIT, GAS_SCALE)); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); // This code will charge gas in a host function indefinetely let tx_code = TestWasms::TxInfiniteHostGas.read_bytes(); @@ -2292,7 +2294,7 @@ mod tests { &mut state, &gas_meter, None, - &tx_index, + &indexed_tx, batched_tx.tx, batched_tx.cmt, &mut vp_cache, @@ -2308,7 +2310,7 @@ mod tests { #[test] fn test_vp_out_of_gas_in_guest() { let mut state = TestState::default(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let addr = state.in_mem_mut().address_gen.generate_address("rng seed"); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( @@ -2335,7 +2337,7 @@ mod tests { let result = vp( code_hash, &outer_tx.batch_ref_first_tx().unwrap(), - &tx_index, + &indexed_tx, &addr, &state, &gas_meter, @@ -2354,7 +2356,7 @@ mod tests { #[test] fn test_vp_out_of_gas_in_host() { let mut state = TestState::default(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let addr = state.in_mem_mut().address_gen.generate_address("rng seed"); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( @@ -2381,7 +2383,7 @@ mod tests { let result = vp( code_hash, &outer_tx.batch_ref_first_tx().unwrap(), - &tx_index, + &indexed_tx, &addr, &state, &gas_meter, @@ -2539,7 +2541,7 @@ mod tests { ) -> Result<()> { let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.push_default_inner_tx(); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let mut state = TestState::default(); let addr = state.in_mem_mut().address_gen.generate_address("rng seed"); let gas_meter = RefCell::new(VpGasMeter::new_from_tx_meter( @@ -2558,7 +2560,7 @@ mod tests { vp( code_hash, &outer_tx.batch_ref_first_tx().unwrap(), - &tx_index, + &indexed_tx, &addr, &state, &gas_meter, @@ -2584,7 +2586,7 @@ mod tests { vp_cache: &mut VpCache, ) -> Result> { let tx_data = vec![]; - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let mut state = TestState::default(); let gas_meter = RefCell::new(TxGasMeter::new(TX_GAS_LIMIT, GAS_SCALE)); @@ -2608,7 +2610,7 @@ mod tests { &mut state, &gas_meter, None, - &tx_index, + &indexed_tx, batched_tx.tx, batched_tx.cmt, vp_cache, @@ -2625,7 +2627,7 @@ mod tests { fn test_tx_alloc() { let mut state = TestState::default(); let gas_meter = RefCell::new(TxGasMeter::new(TX_GAS_LIMIT, GAS_SCALE)); - let tx_index = TxIndex::default(); + let indexed_tx = IndexedTx::default(); let tx_write = TestWasms::TxWriteStorageKey.read_bytes(); // store the wasm code @@ -2664,7 +2666,7 @@ mod tests { &mut state, &gas_meter, None, - &tx_index, + &indexed_tx, batched_tx.tx, batched_tx.cmt, &mut vp_cache, diff --git a/crates/vp/src/native_vp.rs b/crates/vp/src/native_vp.rs index 7bf42209101..536af2791c9 100644 --- a/crates/vp/src/native_vp.rs +++ b/crates/vp/src/native_vp.rs @@ -12,7 +12,7 @@ use namada_core::borsh::BorshDeserialize; use namada_core::chain::{ChainId, Epochs}; use namada_gas::{Gas, GasMeterKind, GasMetering, VpGasMeter}; use namada_state::{ConversionState, ReadConversionState}; -use namada_tx::{BatchedTxRef, Tx, TxCommitments}; +use namada_tx::{BatchedTxRef, IndexedTx, Tx, TxCommitments}; use super::vp_host_fns; use crate::state::prefix_iter::PrefixIterators; @@ -58,7 +58,7 @@ where pub cmt: &'a TxCommitments, /// The transaction index is used to obtain the shielded transaction's /// parent - pub tx_index: &'a TxIndex, + pub indexed_tx: &'a IndexedTx, /// The storage keys that have been changed. Used for calls to `eval`. pub keys_changed: &'a BTreeSet, /// The verifiers whose validity predicates should be triggered. Used for @@ -124,7 +124,7 @@ where state: &'a S, tx: &'a Tx, cmt: &'a TxCommitments, - tx_index: &'a TxIndex, + indexed_tx: &'a IndexedTx, gas_meter: &'a RefCell, keys_changed: &'a BTreeSet, verifiers: &'a BTreeSet
, @@ -138,7 +138,7 @@ where gas_meter, tx, cmt, - tx_index, + indexed_tx, keys_changed, verifiers, vp_wasm_cache, @@ -229,8 +229,14 @@ where self.ctx.get_block_epoch() } - fn get_tx_index(&self) -> Result { - self.ctx.get_tx_index().into_storage_result() + fn get_tx_index(&self) -> Result<(BlockHeight, TxIndex, Option)> { + self.ctx.get_tx_index().into_storage_result().map( + |IndexedTx { + block_height, + block_index, + batch_index, + }| (block_height, block_index, batch_index), + ) } fn get_native_token(&self) -> Result
{ @@ -307,8 +313,14 @@ where self.ctx.get_block_epoch() } - fn get_tx_index(&self) -> Result { - self.ctx.get_tx_index().into_storage_result() + fn get_tx_index(&self) -> Result<(BlockHeight, TxIndex, Option)> { + self.ctx.get_tx_index().into_storage_result().map( + |IndexedTx { + block_height, + block_index, + batch_index, + }| (block_height, block_index, batch_index), + ) } fn get_native_token(&self) -> Result
{ @@ -378,8 +390,8 @@ where .into_storage_result() } - fn get_tx_index(&self) -> Result { - vp_host_fns::get_tx_index(self.gas_meter, self.tx_index) + fn get_tx_index(&self) -> Result { + vp_host_fns::get_tx_index(self.gas_meter, self.indexed_tx) .into_storage_result() } diff --git a/crates/vp/src/vp_host_fns.rs b/crates/vp/src/vp_host_fns.rs index 0ce9690d2d2..559d6973788 100644 --- a/crates/vp/src/vp_host_fns.rs +++ b/crates/vp/src/vp_host_fns.rs @@ -7,10 +7,11 @@ use namada_core::address::{Address, ESTABLISHED_ADDRESS_BYTES_LEN}; use namada_core::arith::checked; use namada_core::chain::{BlockHeader, BlockHeight, ChainId, Epoch, Epochs}; use namada_core::hash::{HASH_LENGTH, Hash}; -use namada_core::storage::{Key, TX_INDEX_LENGTH, TxIndex}; +use namada_core::storage::{Key, TX_INDEX_LENGTH}; use namada_events::{Event, EventTypeBuilder}; use namada_gas::{self as gas, Gas, GasMetering, MEMORY_ACCESS_GAS_PER_BYTE}; -use namada_tx::{BatchedTxRef, Section}; +use namada_state::in_mem_virtual_storage; +use namada_tx::{BatchedTxRef, IndexedTx, Section}; use thiserror::Error; use crate::state::write_log::WriteLog; @@ -130,10 +131,17 @@ pub fn read_temp( where S: StateRead + Debug, { - let (log_val, gas) = - state.write_log().read_temp(key).into_storage_result()?; + let (val, gas) = if in_mem_virtual_storage::is_in_mem_key(key) { + let (val, gas) = + in_mem_virtual_storage::read_from_in_mem(key, state.in_mem())?; + (Some(val), gas) + } else { + let (val, gas) = + state.write_log().read_temp(key).into_storage_result()?; + (val.cloned(), gas) + }; add_gas(gas_meter, gas)?; - Ok(log_val.cloned()) + Ok(val) } /// Storage `has_key` in prior state (before tx execution). It will try to read @@ -275,12 +283,11 @@ where Ok(epoch) } -/// Getting the block epoch. The epoch is that of the block to which the -/// current transaction is being applied. +/// Getting the transaction index. pub fn get_tx_index( gas_meter: &RefCell, - tx_index: &TxIndex, -) -> Result { + indexed_tx: &IndexedTx, +) -> Result { add_gas( gas_meter, (TX_INDEX_LENGTH as u64) @@ -288,7 +295,7 @@ pub fn get_tx_index( .expect("Consts mul that cannot overflow") .into(), )?; - Ok(*tx_index) + Ok(*indexed_tx) } /// Getting the native token's address. diff --git a/crates/vp_env/src/lib.rs b/crates/vp_env/src/lib.rs index c7eb0695a4c..3c473d9247e 100644 --- a/crates/vp_env/src/lib.rs +++ b/crates/vp_env/src/lib.rs @@ -27,8 +27,8 @@ pub use namada_core::chain::{BlockHeader, BlockHeight, Epoch, Epochs}; use namada_core::hash::Hash; use namada_events::{Event, EventType}; use namada_gas::Gas; -pub use namada_storage::{Error, Key, Result, StorageRead, TxIndex}; -use namada_tx::BatchedTxRef; +pub use namada_storage::{Error, Key, Result, StorageRead}; +use namada_tx::{BatchedTxRef, IndexedTx}; /// Validity predicate's environment is available for native VPs and WASM VPs pub trait VpEnv<'view> @@ -83,7 +83,7 @@ where fn get_block_epoch(&self) -> Result; /// Get the shielded transaction index. - fn get_tx_index(&self) -> Result; + fn get_tx_index(&self) -> Result; /// Get the address of the native token. fn get_native_token(&self) -> Result
; diff --git a/crates/vp_prelude/src/lib.rs b/crates/vp_prelude/src/lib.rs index cbfb25ee74e..ab8ddb49012 100644 --- a/crates/vp_prelude/src/lib.rs +++ b/crates/vp_prelude/src/lib.rs @@ -47,7 +47,7 @@ pub use namada_macros::validity_predicate; pub use namada_storage::{ Error, OptionExt, ResultExt, StorageRead, iter_prefix, iter_prefix_bytes, }; -pub use namada_tx::{BatchedTx, Section, Tx}; +pub use namada_tx::{BatchedTx, IndexedTx, Section, Tx}; use namada_vm_env::vp::*; use namada_vm_env::{read_from_buffer, read_key_val_bytes_from_buffer}; pub use namada_vp_env::{VpEnv, collection_validation}; @@ -341,8 +341,14 @@ impl<'view> VpEnv<'view> for Ctx { get_pred_epochs() } - fn get_tx_index(&self) -> Result { - get_tx_index() + fn get_tx_index(&self) -> Result { + get_tx_index().map(|(block_height, block_index, batch_index)| { + IndexedTx { + block_height, + block_index, + batch_index, + } + }) } fn get_native_token(&self) -> Result { @@ -490,7 +496,9 @@ impl StorageRead for CtxPreStorageRead<'_> { get_pred_epochs() } - fn get_tx_index(&self) -> Result { + fn get_tx_index( + &self, + ) -> Result<(BlockHeight, TxIndex, Option), Error> { get_tx_index() } @@ -563,7 +571,9 @@ impl StorageRead for CtxPostStorageRead<'_> { get_pred_epochs() } - fn get_tx_index(&self) -> Result { + fn get_tx_index( + &self, + ) -> Result<(BlockHeight, TxIndex, Option), Error> { get_tx_index() } @@ -624,8 +634,20 @@ fn get_block_epoch() -> Result { Ok(Epoch(unsafe { namada_vp_get_block_epoch() })) } -fn get_tx_index() -> Result { - Ok(TxIndex(unsafe { namada_vp_get_tx_index() })) +fn get_tx_index() -> Result<(BlockHeight, TxIndex, Option), Error> { + // NOTE: the conversion to i64 is infallible + let read_result = unsafe { namada_vp_get_tx_index() } as i64; + let bytes = read_from_buffer(read_result, namada_vp_result_buffer).ok_or( + Error::SimpleMessage( + "Missing result from `namada_vp_get_tx_index` call", + ), + )?; + let IndexedTx { + block_height, + block_index, + batch_index, + } = namada_core::decode(bytes).expect("Cannot decode tx index"); + Ok((block_height, block_index, batch_index)) } fn get_pred_epochs() -> Result { diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 73ada894dc2..67fcf4c8f79 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -4639,6 +4639,7 @@ dependencies = [ "ethabi", "ethbridge-structs", "eyre", + "group", "ibc", "ics23", "impl-num-traits", @@ -4951,7 +4952,6 @@ dependencies = [ "eyre", "flume", "futures", - "group", "itertools 0.14.0", "lazy_static", "leanbridgetree", diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index fa76ccab6f4..2c22ac8e72f 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -2270,6 +2270,7 @@ dependencies = [ "ethabi", "ethbridge-structs", "eyre", + "group", "ibc", "ics23", "impl-num-traits", @@ -2459,7 +2460,6 @@ dependencies = [ "borsh", "eyre", "futures", - "group", "itertools 0.14.0", "lazy_static", "leanbridgetree",