diff --git a/Cargo.lock b/Cargo.lock index cb3c570072..3689d1c641 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5536,8 +5536,11 @@ dependencies = [ "namada_macros", "namada_migrations", "namada_shielded_token", + "namada_storage", "namada_systems", "namada_trans_token", + "namada_tx", + "namada_tx_env", "proptest", "serde", ] diff --git a/crates/core/src/masp.rs b/crates/core/src/masp.rs index 1ae82ee00a..037a3588ce 100644 --- a/crates/core/src/masp.rs +++ b/crates/core/src/masp.rs @@ -10,7 +10,9 @@ use borsh_ext::BorshSerializeExt; use masp_primitives::asset_type::AssetType; use masp_primitives::sapling::ViewingKey; use masp_primitives::transaction::TransparentAddress; -pub use masp_primitives::transaction::TxId as TxIdInner; +pub use masp_primitives::transaction::{ + Transaction as MaspTransaction, TxId as TxIdInner, +}; use namada_macros::BorshDeserializer; #[cfg(feature = "migrations")] use namada_migrations::*; diff --git a/crates/shielded_token/src/lib.rs b/crates/shielded_token/src/lib.rs index e337d7faad..6719c4dd46 100644 --- a/crates/shielded_token/src/lib.rs +++ b/crates/shielded_token/src/lib.rs @@ -29,10 +29,11 @@ pub mod vp; use std::str::FromStr; -pub use masp_primitives::transaction::Transaction as MaspTransaction; use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; pub use namada_core::dec::Dec; -pub use namada_core::masp::{MaspEpoch, MaspTxId, MaspTxRefs, MaspValue}; +pub use namada_core::masp::{ + MaspEpoch, MaspTransaction, MaspTxId, MaspTxRefs, MaspValue, +}; pub use namada_state::{ ConversionLeaf, ConversionState, Error, Key, OptionExt, Result, ResultExt, StorageRead, StorageWrite, WithConversionState, diff --git a/crates/token/Cargo.toml b/crates/token/Cargo.toml index 827da0c9e7..86aa87f012 100644 --- a/crates/token/Cargo.toml +++ b/crates/token/Cargo.toml @@ -30,8 +30,11 @@ namada_events = { path = "../events", default-features = false } namada_macros = { path = "../macros" } namada_migrations = { path = "../migrations", optional = true } namada_shielded_token = { path = "../shielded_token" } +namada_storage = { path = "../storage" } namada_systems = { path = "../systems" } namada_trans_token = { path = "../trans_token" } +namada_tx = { path = "../tx" } +namada_tx_env = { path = "../tx_env" } arbitrary = { workspace = true, optional = true } borsh.workspace = true diff --git a/crates/token/src/tx.rs b/crates/token/src/tx.rs index fc02f965b6..3f9a10e701 100644 --- a/crates/token/src/tx.rs +++ b/crates/token/src/tx.rs @@ -1,3 +1,176 @@ //! Token transaction -pub use namada_trans_token::tx::transfer as transparent_transfer; +use std::collections::{BTreeMap, BTreeSet}; + +use namada_core::collections::HashSet; +use namada_core::masp; +use namada_events::{EmitEvents, EventLevel}; +use namada_shielded_token::{utils, MaspTxId}; +use namada_storage::{Error, OptionExt, ResultExt}; +use namada_trans_token::event::{TokenEvent, TokenOperation}; +pub use namada_trans_token::tx::transfer; +use namada_trans_token::UserAccount; +use namada_tx::action::{self, Action, MaspAction}; +use namada_tx::BatchedTx; +use namada_tx_env::{Address, Result, TxEnv}; + +use crate::{Transfer, TransparentTransfersRef}; + +/// Transparent and shielded token transfers that can be used in a transaction. +pub fn multi_transfer( + env: &mut ENV, + transfers: Transfer, + tx_data: &BatchedTx, +) -> Result<()> +where + ENV: TxEnv + EmitEvents + action::Write, +{ + // Effect the transparent multi transfer(s) + let debited_accounts = + if let Some(transparent) = transfers.transparent_part() { + apply_transparent_transfers(env, transparent) + .wrap_err("Transparent token transfer failed")? + } else { + HashSet::new() + }; + + // Apply the shielded transfer if there is a link to one + if let Some(masp_section_ref) = transfers.shielded_section_hash { + apply_shielded_transfer( + env, + masp_section_ref, + debited_accounts, + tx_data, + ) + .wrap_err("Shielded token transfer failed")?; + } + Ok(()) +} + +/// Transfer tokens from `sources` to `targets` and submit a transfer event. +/// Returns an `Err` if any source has insufficient balance or if the transfer +/// to any destination would overflow (This can only happen if the total supply +/// doesn't fit in `token::Amount`). Returns a set of debited accounts. +pub fn apply_transparent_transfers( + env: &mut ENV, + transfers: TransparentTransfersRef<'_>, +) -> Result> +where + ENV: TxEnv + EmitEvents, +{ + let sources = transfers.sources(); + let targets = transfers.targets(); + let debited_accounts = + namada_trans_token::multi_transfer(env, &sources, &targets)?; + + let mut evt_sources = BTreeMap::new(); + let mut evt_targets = BTreeMap::new(); + let mut post_balances = BTreeMap::new(); + + for ((src, token), amount) in sources { + // The tx must be authorized by the source address + env.insert_verifier(&src)?; + if token.is_internal() { + // Established address tokens do not have VPs themselves, their + // validation is handled by the `Multitoken` internal address, + // but internal token addresses have to verify + // the transfer + env.insert_verifier(&token)?; + } + evt_sources.insert( + (UserAccount::Internal(src.clone()), token.clone()), + amount.into(), + ); + post_balances.insert( + (UserAccount::Internal(src.clone()), token.clone()), + crate::read_balance(env, &token, &src)?.into(), + ); + } + + for ((target, token), amount) in targets { + if token.is_internal() { + // Established address tokens do not have VPs themselves, their + // validation is handled by the `Multitoken` internal address, + // but internal token addresses have to verify + // the transfer + env.insert_verifier(&token)?; + } + evt_targets.insert( + (UserAccount::Internal(target.clone()), token.clone()), + amount.into(), + ); + post_balances.insert( + (UserAccount::Internal(target.clone()), token.clone()), + crate::read_balance(env, &token, &target)?.into(), + ); + } + + env.emit(TokenEvent { + descriptor: "transfer-from-wasm".into(), + level: EventLevel::Tx, + operation: TokenOperation::Transfer { + sources: evt_sources, + targets: evt_targets, + post_balances, + }, + }); + + Ok(debited_accounts) +} + +/// Apply a shielded transfer +pub fn apply_shielded_transfer( + env: &mut ENV, + masp_section_ref: MaspTxId, + debited_accounts: HashSet
, + tx_data: &BatchedTx, +) -> Result<()> +where + ENV: TxEnv + EmitEvents + action::Write, +{ + let shielded = tx_data + .tx + .get_masp_section(&masp_section_ref) + .cloned() + .ok_or_err_msg("Unable to find required shielded section in tx data") + .map_err(|err| { + env.set_commitment_sentinel(); + err + })?; + utils::handle_masp_tx(env, &shielded) + .wrap_err("Encountered error while handling MASP transaction")?; + ENV::update_masp_note_commitment_tree(&shielded) + .wrap_err("Failed to update the MASP commitment tree")?; + + env.push_action(Action::Masp(MaspAction::MaspSectionRef( + masp_section_ref, + )))?; + // Extract the debited accounts for the masp part of the transfer and + // push the relative actions + let vin_addresses = + shielded + .transparent_bundle() + .map_or_else(Default::default, |bndl| { + bndl.vin + .iter() + .map(|vin| vin.address) + .collect::>() + }); + let masp_authorizers: Vec<_> = debited_accounts + .into_iter() + .filter(|account| { + vin_addresses.contains(&masp::addr_taddr(account.clone())) + }) + .collect(); + if masp_authorizers.len() != vin_addresses.len() { + return Err(Error::SimpleMessage( + "Transfer transaction does not debit all the expected accounts", + )); + } + + for authorizer in masp_authorizers { + env.push_action(Action::Masp(MaspAction::MaspAuthorizer(authorizer)))?; + } + + Ok(()) +} diff --git a/crates/tx_env/src/lib.rs b/crates/tx_env/src/lib.rs index 7eb99caa85..2bbb513f02 100644 --- a/crates/tx_env/src/lib.rs +++ b/crates/tx_env/src/lib.rs @@ -22,6 +22,7 @@ pub use namada_core::address::Address; pub use namada_core::borsh::{ BorshDeserialize, BorshSerialize, BorshSerializeExt, }; +pub use namada_core::masp::MaspTransaction; pub use namada_core::storage; pub use namada_events::{Event, EventToEmit, EventType}; pub use namada_storage::{Result, ResultExt, StorageRead, StorageWrite}; @@ -103,4 +104,9 @@ pub trait TxEnv: StorageRead + StorageWrite { /// Set the sentinel for an invalid section commitment fn set_commitment_sentinel(&mut self); + + /// Update the masp note commitment tree in storage with the new notes + fn update_masp_note_commitment_tree( + transaction: &MaspTransaction, + ) -> Result; } diff --git a/crates/tx_prelude/src/ibc.rs b/crates/tx_prelude/src/ibc.rs index 94f4aef0bc..283dc0f723 100644 --- a/crates/tx_prelude/src/ibc.rs +++ b/crates/tx_prelude/src/ibc.rs @@ -63,7 +63,7 @@ impl IbcStorageContext for Ctx { token: &Address, amount: Amount, ) -> Result<()> { - token::tx::transparent_transfer(self, src, dest, token, amount) + token::transfer(self, src, dest, token, amount) } fn mint_token( diff --git a/crates/tx_prelude/src/lib.rs b/crates/tx_prelude/src/lib.rs index 6297857ce8..29e5b58357 100644 --- a/crates/tx_prelude/src/lib.rs +++ b/crates/tx_prelude/src/lib.rs @@ -404,6 +404,12 @@ impl TxEnv for Ctx { fn set_commitment_sentinel(&mut self) { unsafe { namada_tx_set_commitment_sentinel() } } + + fn update_masp_note_commitment_tree( + transaction: &MaspTransaction, + ) -> Result { + update_masp_note_commitment_tree(transaction) + } } impl namada_tx::action::Read for Ctx { diff --git a/crates/tx_prelude/src/token.rs b/crates/tx_prelude/src/token.rs index a7c0bffda5..89d42e9964 100644 --- a/crates/tx_prelude/src/token.rs +++ b/crates/tx_prelude/src/token.rs @@ -1,171 +1,11 @@ //! Shielded and transparent tokens related functions -use std::collections::{BTreeMap, BTreeSet}; - -use namada_core::address::Address; -use namada_core::collections::HashSet; -use namada_core::masp::addr_taddr; -use namada_events::extend::UserAccount; -use namada_events::{EmitEvents, EventLevel}; -use namada_state::{Error, OptionExt, ResultExt}; -use namada_token::event::{TokenEvent, TokenOperation}; #[cfg(any(test, feature = "testing"))] pub use namada_token::testing; +pub use namada_token::tx::{ + apply_shielded_transfer, apply_transparent_transfers, multi_transfer, + transfer, +}; pub use namada_token::{ - storage_key, transfer, tx, utils, Amount, DenominatedAmount, Store, - Transfer, + storage_key, utils, Amount, DenominatedAmount, Store, Transfer, }; -use namada_token::{MaspTxId, TransparentTransfersRef}; -use namada_tx::action::{Action, MaspAction, Write}; -use namada_tx::BatchedTx; -use namada_tx_env::TxEnv; - -use crate::{update_masp_note_commitment_tree, Ctx, Result}; - -/// Transparent and shielded token transfers that can be used in a transaction. -pub fn multi_transfer( - ctx: &mut Ctx, - transfers: Transfer, - tx_data: &BatchedTx, -) -> Result<()> { - // Effect the transparent multi transfer(s) - let debited_accounts = - if let Some(transparent) = transfers.transparent_part() { - apply_transparent_transfers(ctx, transparent) - .wrap_err("Transparent token transfer failed")? - } else { - HashSet::new() - }; - - // Apply the shielded transfer if there is a link to one - if let Some(masp_section_ref) = transfers.shielded_section_hash { - apply_shielded_transfer( - ctx, - masp_section_ref, - debited_accounts, - tx_data, - ) - .wrap_err("Shielded token transfer failed")?; - } - Ok(()) -} - -/// Transfer tokens from `sources` to `targets` and submit a transfer event. -/// Returns an `Err` if any source has insufficient balance or if the transfer -/// to any destination would overflow (This can only happen if the total supply -/// doesn't fit in `token::Amount`). Returns a set of debited accounts. -pub fn apply_transparent_transfers( - ctx: &mut Ctx, - transfers: TransparentTransfersRef<'_>, -) -> Result> { - let sources = transfers.sources(); - let targets = transfers.targets(); - let debited_accounts = - namada_token::multi_transfer(ctx, &sources, &targets)?; - - let mut evt_sources = BTreeMap::new(); - let mut evt_targets = BTreeMap::new(); - let mut post_balances = BTreeMap::new(); - - for ((src, token), amount) in sources { - // The tx must be authorized by the source address - ctx.insert_verifier(&src)?; - if token.is_internal() { - // Established address tokens do not have VPs themselves, their - // validation is handled by the `Multitoken` internal address, - // but internal token addresses have to verify - // the transfer - ctx.insert_verifier(&token)?; - } - evt_sources.insert( - (UserAccount::Internal(src.clone()), token.clone()), - amount.into(), - ); - post_balances.insert( - (UserAccount::Internal(src.clone()), token.clone()), - namada_token::read_balance(ctx, &token, &src)?.into(), - ); - } - - for ((target, token), amount) in targets { - if token.is_internal() { - // Established address tokens do not have VPs themselves, their - // validation is handled by the `Multitoken` internal address, - // but internal token addresses have to verify - // the transfer - ctx.insert_verifier(&token)?; - } - evt_targets.insert( - (UserAccount::Internal(target.clone()), token.clone()), - amount.into(), - ); - post_balances.insert( - (UserAccount::Internal(target.clone()), token.clone()), - namada_token::read_balance(ctx, &token, &target)?.into(), - ); - } - - ctx.emit(TokenEvent { - descriptor: "transfer-from-wasm".into(), - level: EventLevel::Tx, - operation: TokenOperation::Transfer { - sources: evt_sources, - targets: evt_targets, - post_balances, - }, - }); - - Ok(debited_accounts) -} - -/// Apply a shielded transfer -pub fn apply_shielded_transfer( - ctx: &mut Ctx, - masp_section_ref: MaspTxId, - debited_accounts: HashSet
, - tx_data: &BatchedTx, -) -> Result<()> { - let shielded = tx_data - .tx - .get_masp_section(&masp_section_ref) - .cloned() - .ok_or_err_msg("Unable to find required shielded section in tx data") - .map_err(|err| { - ctx.set_commitment_sentinel(); - err - })?; - utils::handle_masp_tx(ctx, &shielded) - .wrap_err("Encountered error while handling MASP transaction")?; - update_masp_note_commitment_tree(&shielded) - .wrap_err("Failed to update the MASP commitment tree")?; - - ctx.push_action(Action::Masp(MaspAction::MaspSectionRef( - masp_section_ref, - )))?; - // Extract the debited accounts for the masp part of the transfer and - // push the relative actions - let vin_addresses = - shielded - .transparent_bundle() - .map_or_else(Default::default, |bndl| { - bndl.vin - .iter() - .map(|vin| vin.address) - .collect::>() - }); - let masp_authorizers: Vec<_> = debited_accounts - .into_iter() - .filter(|account| vin_addresses.contains(&addr_taddr(account.clone()))) - .collect(); - if masp_authorizers.len() != vin_addresses.len() { - return Err(Error::SimpleMessage( - "Transfer transaction does not debit all the expected accounts", - )); - } - - for authorizer in masp_authorizers { - ctx.push_action(Action::Masp(MaspAction::MaspAuthorizer(authorizer)))?; - } - - Ok(()) -} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index c7c031653d..a1645274a8 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -4096,8 +4096,11 @@ dependencies = [ "namada_events", "namada_macros", "namada_shielded_token", + "namada_storage", "namada_systems", "namada_trans_token", + "namada_tx", + "namada_tx_env", "proptest", "serde", ] diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index 49f2e76b4f..5bcd2417cf 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -2261,8 +2261,11 @@ dependencies = [ "namada_events", "namada_macros", "namada_shielded_token", + "namada_storage", "namada_systems", "namada_trans_token", + "namada_tx", + "namada_tx_env", "serde", ]