diff --git a/.changelog/unreleased/features/3356-vectorize-transfers.md b/.changelog/unreleased/features/3356-vectorize-transfers.md new file mode 100644 index 0000000000..3b7f8e66da --- /dev/null +++ b/.changelog/unreleased/features/3356-vectorize-transfers.md @@ -0,0 +1,2 @@ +- Reworked transparent and masp transfers to allow for multiple sources, targets, + tokens and amounts. ([\#3356](https://github.com/anoma/namada/pull/3356)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/3446-generalize-vectorized-transfers.md b/.changelog/unreleased/improvements/3446-generalize-vectorized-transfers.md new file mode 100644 index 0000000000..c81570de73 --- /dev/null +++ b/.changelog/unreleased/improvements/3446-generalize-vectorized-transfers.md @@ -0,0 +1,2 @@ +- Combined the various Transfer formats into one general one. + ([\#3446](https://github.com/anoma/namada/pull/3446)) \ No newline at end of file diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index eadf78063a..bc4da08739 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -3099,11 +3099,10 @@ pub mod args { TX_CHANGE_METADATA_WASM, TX_CLAIM_REWARDS_WASM, TX_DEACTIVATE_VALIDATOR_WASM, TX_IBC_WASM, TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL, TX_REACTIVATE_VALIDATOR_WASM, TX_REDELEGATE_WASM, - TX_RESIGN_STEWARD, TX_REVEAL_PK, TX_SHIELDED_TRANSFER_WASM, - TX_SHIELDING_TRANSFER_WASM, TX_TRANSPARENT_TRANSFER_WASM, - TX_UNBOND_WASM, TX_UNJAIL_VALIDATOR_WASM, TX_UNSHIELDING_TRANSFER_WASM, - TX_UPDATE_ACCOUNT_WASM, TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL, - TX_WITHDRAW_WASM, VP_USER_WASM, + TX_RESIGN_STEWARD, TX_REVEAL_PK, TX_TRANSFER_WASM, TX_UNBOND_WASM, + TX_UNJAIL_VALIDATOR_WASM, TX_UPDATE_ACCOUNT_WASM, + TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, + VP_USER_WASM, }; use namada_sdk::DEFAULT_GAS_LIMIT; @@ -4338,14 +4337,21 @@ pub mod args { ctx: &mut Context, ) -> Result, Self::Error> { let tx = self.tx.to_sdk(ctx)?; + let mut data = vec![]; let chain_ctx = ctx.borrow_mut_chain_or_exit(); + for transfer_data in self.data { + data.push(TxTransparentTransferData { + source: chain_ctx.get(&transfer_data.source), + target: chain_ctx.get(&transfer_data.target), + token: chain_ctx.get(&transfer_data.token), + amount: transfer_data.amount, + }); + } + Ok(TxTransparentTransfer:: { tx, - source: chain_ctx.get(&self.source), - target: chain_ctx.get(&self.target), - token: chain_ctx.get(&self.token), - amount: self.amount, + data, tx_code_path: self.tx_code_path.to_path_buf(), }) } @@ -4358,13 +4364,17 @@ pub mod args { let target = TARGET.parse(matches); let token = TOKEN.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); - let tx_code_path = PathBuf::from(TX_TRANSPARENT_TRANSFER_WASM); - Self { - tx, + let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); + let data = vec![TxTransparentTransferData { source, target, token, amount, + }]; + + Self { + tx, + data, tx_code_path, } } @@ -4393,14 +4403,21 @@ pub mod args { ctx: &mut Context, ) -> Result, Self::Error> { let tx = self.tx.to_sdk(ctx)?; + let mut data = vec![]; let chain_ctx = ctx.borrow_mut_chain_or_exit(); + for transfer_data in self.data { + data.push(TxShieldedTransferData { + source: chain_ctx.get_cached(&transfer_data.source), + target: chain_ctx.get(&transfer_data.target), + token: chain_ctx.get(&transfer_data.token), + amount: transfer_data.amount, + }); + } + Ok(TxShieldedTransfer:: { tx, - source: chain_ctx.get_cached(&self.source), - target: chain_ctx.get(&self.target), - token: chain_ctx.get(&self.token), - amount: self.amount, + data, tx_code_path: self.tx_code_path.to_path_buf(), }) } @@ -4413,13 +4430,17 @@ pub mod args { let target = PAYMENT_ADDRESS_TARGET.parse(matches); let token = TOKEN.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); - let tx_code_path = PathBuf::from(TX_SHIELDED_TRANSFER_WASM); - Self { - tx, + let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); + let data = vec![TxShieldedTransferData { source, target, token, amount, + }]; + + Self { + tx, + data, tx_code_path, } } @@ -4453,14 +4474,21 @@ pub mod args { ctx: &mut Context, ) -> Result, Self::Error> { let tx = self.tx.to_sdk(ctx)?; + let mut data = vec![]; let chain_ctx = ctx.borrow_mut_chain_or_exit(); + for transfer_data in self.data { + data.push(TxShieldingTransferData { + source: chain_ctx.get(&transfer_data.source), + token: chain_ctx.get(&transfer_data.token), + amount: transfer_data.amount, + }); + } + Ok(TxShieldingTransfer:: { tx, - source: chain_ctx.get(&self.source), + data, target: chain_ctx.get(&self.target), - token: chain_ctx.get(&self.token), - amount: self.amount, tx_code_path: self.tx_code_path.to_path_buf(), }) } @@ -4473,13 +4501,17 @@ pub mod args { let target = PAYMENT_ADDRESS_TARGET.parse(matches); let token = TOKEN.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); - let tx_code_path = PathBuf::from(TX_SHIELDING_TRANSFER_WASM); - Self { - tx, + let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); + let data = vec![TxShieldingTransferData { source, - target, token, amount, + }]; + + Self { + tx, + data, + target, tx_code_path, } } @@ -4514,14 +4546,21 @@ pub mod args { ctx: &mut Context, ) -> Result, Self::Error> { let tx = self.tx.to_sdk(ctx)?; + let mut data = vec![]; let chain_ctx = ctx.borrow_mut_chain_or_exit(); + for transfer_data in self.data { + data.push(TxUnshieldingTransferData { + target: chain_ctx.get(&transfer_data.target), + token: chain_ctx.get(&transfer_data.token), + amount: transfer_data.amount, + }); + } + Ok(TxUnshieldingTransfer:: { tx, + data, source: chain_ctx.get_cached(&self.source), - target: chain_ctx.get(&self.target), - token: chain_ctx.get(&self.token), - amount: self.amount, tx_code_path: self.tx_code_path.to_path_buf(), }) } @@ -4534,13 +4573,17 @@ pub mod args { let target = TARGET.parse(matches); let token = TOKEN.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); - let tx_code_path = PathBuf::from(TX_UNSHIELDING_TRANSFER_WASM); - Self { - tx, - source, + let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); + let data = vec![TxUnshieldingTransferData { target, token, amount, + }]; + + Self { + tx, + source, + data, tx_code_path, } } diff --git a/crates/apps_lib/src/client/tx.rs b/crates/apps_lib/src/client/tx.rs index 80e6e52e09..12424f5b5c 100644 --- a/crates/apps_lib/src/client/tx.rs +++ b/crates/apps_lib/src/client/tx.rs @@ -743,7 +743,26 @@ pub async fn submit_transparent_transfer( namada: &impl Namada, args: args::TxTransparentTransfer, ) -> Result<(), error::Error> { - submit_reveal_aux(namada, args.tx.clone(), &args.source).await?; + if args.data.len() > 1 { + // TODO(namada#3379): Vectorized transfers are not yet supported in the + // CLI + return Err(error::Error::Other( + "Unexpected vectorized transparent transfer".to_string(), + )); + } + + submit_reveal_aux( + namada, + args.tx.clone(), + &args + .data + .first() + .ok_or_else(|| { + error::Error::Other("Missing transfer data".to_string()) + })? + .source, + ) + .await?; let (mut tx, signing_data) = args.clone().build(namada).await?; diff --git a/crates/benches/host_env.rs b/crates/benches/host_env.rs index 1eee561163..3d944a245b 100644 --- a/crates/benches/host_env.rs +++ b/crates/benches/host_env.rs @@ -3,29 +3,28 @@ use namada::core::account::AccountPublicKeysMap; use namada::core::address; use namada::core::collections::{HashMap, HashSet}; use namada::ledger::storage::DB; -use namada::token::{Amount, TransparentTransfer}; +use namada::token::{Amount, Transfer, TransparentTransfer}; use namada::tx::Authorization; use namada::vm::wasm::TxCache; use namada_apps_lib::wallet::defaults; use namada_apps_lib::wasm_loader; use namada_node::bench_utils::{ - BenchShell, TX_INIT_PROPOSAL_WASM, TX_REVEAL_PK_WASM, - TX_TRANSPARENT_TRANSFER_WASM, TX_UPDATE_ACCOUNT_WASM, VP_USER_WASM, - WASM_DIR, + BenchShell, TX_INIT_PROPOSAL_WASM, TX_REVEAL_PK_WASM, TX_TRANSFER_WASM, + TX_UPDATE_ACCOUNT_WASM, VP_USER_WASM, WASM_DIR, }; // Benchmarks the validation of a single signature on a single `Section` of a // transaction fn tx_section_signature_validation(c: &mut Criterion) { let shell = BenchShell::default(); - let transfer_data = TransparentTransfer { + let transfer_data = Transfer::transparent(vec![TransparentTransfer { source: defaults::albert_address(), target: defaults::bertha_address(), token: address::testing::nam(), amount: Amount::native_whole(500).native_denominated(), - }; + }]); let tx = shell.generate_tx( - TX_TRANSPARENT_TRANSFER_WASM, + TX_TRANSFER_WASM, transfer_data, None, None, @@ -62,7 +61,7 @@ fn compile_wasm(c: &mut Criterion) { let mut txs: HashMap<&str, Vec> = HashMap::default(); for tx in [ - TX_TRANSPARENT_TRANSFER_WASM, + TX_TRANSFER_WASM, TX_INIT_PROPOSAL_WASM, TX_REVEAL_PK_WASM, TX_UPDATE_ACCOUNT_WASM, @@ -111,7 +110,7 @@ fn untrusted_wasm_validation(c: &mut Criterion) { let mut txs: HashMap<&str, Vec> = HashMap::default(); for tx in [ - TX_TRANSPARENT_TRANSFER_WASM, + TX_TRANSFER_WASM, TX_INIT_PROPOSAL_WASM, TX_REVEAL_PK_WASM, TX_UPDATE_ACCOUNT_WASM, diff --git a/crates/benches/native_vps.rs b/crates/benches/native_vps.rs index 298c37fcf5..8670583f4a 100644 --- a/crates/benches/native_vps.rs +++ b/crates/benches/native_vps.rs @@ -55,15 +55,14 @@ use namada::sdk::masp_primitives::merkle_tree::CommitmentTree; use namada::sdk::masp_primitives::transaction::Transaction; use namada::sdk::masp_proofs::sapling::SaplingVerificationContextInner; use namada::state::{Epoch, StorageRead, StorageWrite, TxIndex}; -use namada::token::{Amount, TransparentTransfer}; +use namada::token::{Amount, Transfer, TransparentTransfer}; use namada::tx::{BatchedTx, Code, Section, Tx}; use namada_apps_lib::wallet::defaults; use namada_node::bench_utils::{ generate_foreign_key_tx, BenchShell, BenchShieldedCtx, ALBERT_PAYMENT_ADDRESS, ALBERT_SPENDING_KEY, BERTHA_PAYMENT_ADDRESS, TX_BRIDGE_POOL_WASM, TX_IBC_WASM, TX_INIT_PROPOSAL_WASM, TX_RESIGN_STEWARD, - TX_TRANSPARENT_TRANSFER_WASM, TX_UPDATE_STEWARD_COMMISSION, - TX_VOTE_PROPOSAL_WASM, + TX_TRANSFER_WASM, TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL_WASM, }; use rand_core::OsRng; @@ -475,13 +474,13 @@ fn vp_multitoken(c: &mut Criterion) { generate_foreign_key_tx(&defaults::albert_keypair()); let transfer = shell.generate_tx( - TX_TRANSPARENT_TRANSFER_WASM, - TransparentTransfer { + TX_TRANSFER_WASM, + Transfer::transparent(vec![TransparentTransfer { source: defaults::albert_address(), target: defaults::bertha_address(), token: address::testing::nam(), amount: Amount::native_whole(1000).native_denominated(), - }, + }]), None, None, vec![&defaults::albert_keypair()], diff --git a/crates/benches/process_wrapper.rs b/crates/benches/process_wrapper.rs index 9510ee3125..19d449474e 100644 --- a/crates/benches/process_wrapper.rs +++ b/crates/benches/process_wrapper.rs @@ -3,11 +3,11 @@ use namada::core::address; use namada::core::key::RefTo; use namada::core::storage::BlockHeight; use namada::core::time::DateTimeUtc; -use namada::token::{Amount, DenominatedAmount, TransparentTransfer}; +use namada::token::{Amount, DenominatedAmount, Transfer, TransparentTransfer}; use namada::tx::data::{Fee, WrapperTx}; use namada::tx::Authorization; use namada_apps_lib::wallet::defaults; -use namada_node::bench_utils::{BenchShell, TX_TRANSPARENT_TRANSFER_WASM}; +use namada_node::bench_utils::{BenchShell, TX_TRANSFER_WASM}; use namada_node::shell::process_proposal::ValidationMeta; fn process_tx(c: &mut Criterion) { @@ -18,13 +18,13 @@ fn process_tx(c: &mut Criterion) { BlockHeight(2); let mut batched_tx = shell.generate_tx( - TX_TRANSPARENT_TRANSFER_WASM, - TransparentTransfer { + TX_TRANSFER_WASM, + Transfer::transparent(vec![TransparentTransfer { source: defaults::albert_address(), target: defaults::bertha_address(), token: address::testing::nam(), amount: Amount::native_whole(1).native_denominated(), - }, + }]), None, None, vec![&defaults::albert_keypair()], diff --git a/crates/encoding_spec/src/main.rs b/crates/encoding_spec/src/main.rs index efb5c0c79c..b0a3bdb858 100644 --- a/crates/encoding_spec/src/main.rs +++ b/crates/encoding_spec/src/main.rs @@ -81,14 +81,7 @@ fn main() -> Result<(), Box> { let signature_schema = schema_container_of::(); let init_account_schema = schema_container_of::(); let init_validator_schema = schema_container_of::(); - let transparent_transfer_schema = - schema_container_of::(); - let shielded_transfer_schema = - schema_container_of::(); - let shielding_transfer_schema = - schema_container_of::(); - let unshielding_transfer_schema = - schema_container_of::(); + let transfer_schema = schema_container_of::(); let update_account = schema_container_of::(); let pos_bond_schema = schema_container_of::(); let pos_withdraw_schema = schema_container_of::(); @@ -115,10 +108,7 @@ fn main() -> Result<(), Box> { definitions.extend(btree(&signature_schema)); definitions.extend(btree(&init_account_schema)); definitions.extend(btree(&init_validator_schema)); - definitions.extend(btree(&transparent_transfer_schema)); - definitions.extend(btree(&shielded_transfer_schema)); - definitions.extend(btree(&shielding_transfer_schema)); - definitions.extend(btree(&unshielding_transfer_schema)); + definitions.extend(btree(&transfer_schema)); definitions.extend(btree(&update_account)); definitions.extend(btree(&pos_bond_schema)); definitions.extend(btree(&pos_withdraw_schema)); @@ -191,11 +181,10 @@ fn main() -> Result<(), Box> { ).with_rust_doc_link("https://dev.namada.net/master/rustdoc/namada/types/transaction/struct.InitValidator.html"); tables.push(init_validator_table); - let token_transfer_definition = definitions - .remove(transparent_transfer_schema.declaration()) - .unwrap(); + let token_transfer_definition = + definitions.remove(transfer_schema.declaration()).unwrap(); let token_transfer_table = definition_to_table( - transparent_transfer_schema.declaration(), + transfer_schema.declaration(), token_transfer_definition, ).with_rust_doc_link("https://dev.namada.net/master/rustdoc/namada/types/token/struct.Transfer.html"); tables.push(token_transfer_table); diff --git a/crates/ibc/src/lib.rs b/crates/ibc/src/lib.rs index 15a673c9c6..b58b9041ef 100644 --- a/crates/ibc/src/lib.rs +++ b/crates/ibc/src/lib.rs @@ -76,7 +76,7 @@ use ibc::primitives::proto::Any; pub use ibc::*; pub use msg::*; use namada_core::address::{self, Address}; -use namada_token::ShieldingTransfer; +use namada_token::Transfer; pub use nft::*; use prost::Message; use thiserror::Error; @@ -152,7 +152,7 @@ where pub fn execute( &mut self, tx_data: &[u8], - ) -> Result, Error> { + ) -> Result, Error> { let message = decode_message(tx_data)?; match &message { IbcMessage::Transfer(msg) => { diff --git a/crates/ibc/src/msg.rs b/crates/ibc/src/msg.rs index 7502cf1e31..714584675a 100644 --- a/crates/ibc/src/msg.rs +++ b/crates/ibc/src/msg.rs @@ -7,7 +7,7 @@ use ibc::core::channel::types::msgs::{ }; use ibc::core::handler::types::msgs::MsgEnvelope; use ibc::primitives::proto::Protobuf; -use namada_token::ShieldingTransfer; +use namada_token::Transfer; /// The different variants of an Ibc message pub enum IbcMessage { @@ -31,7 +31,7 @@ pub struct MsgTransfer { /// IBC transfer message pub message: IbcMsgTransfer, /// Shieleded transfer for MASP transaction - pub transfer: Option, + pub transfer: Option, } impl BorshSerialize for MsgTransfer { @@ -50,7 +50,7 @@ impl BorshDeserialize for MsgTransfer { reader: &mut R, ) -> std::io::Result { use std::io::{Error, ErrorKind}; - let (msg, transfer): (Vec, Option) = + let (msg, transfer): (Vec, Option) = BorshDeserialize::deserialize_reader(reader)?; let message = IbcMsgTransfer::decode_vec(&msg) .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; @@ -64,7 +64,7 @@ pub struct MsgNftTransfer { /// IBC NFT transfer message pub message: IbcMsgNftTransfer, /// Shieleded transfer for MASP transaction - pub transfer: Option, + pub transfer: Option, } impl BorshSerialize for MsgNftTransfer { @@ -83,7 +83,7 @@ impl BorshDeserialize for MsgNftTransfer { reader: &mut R, ) -> std::io::Result { use std::io::{Error, ErrorKind}; - let (msg, transfer): (Vec, Option) = + let (msg, transfer): (Vec, Option) = BorshDeserialize::deserialize_reader(reader)?; let message = IbcMsgNftTransfer::decode_vec(&msg) .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; @@ -97,7 +97,7 @@ pub struct MsgRecvPacket { /// IBC receiving packet message pub message: IbcMsgRecvPacket, /// Shieleded transfer for MASP transaction - pub transfer: Option, + pub transfer: Option, } impl BorshSerialize for MsgRecvPacket { @@ -116,7 +116,7 @@ impl BorshDeserialize for MsgRecvPacket { reader: &mut R, ) -> std::io::Result { use std::io::{Error, ErrorKind}; - let (msg, transfer): (Vec, Option) = + let (msg, transfer): (Vec, Option) = BorshDeserialize::deserialize_reader(reader)?; let message = IbcMsgRecvPacket::decode_vec(&msg) .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; @@ -131,7 +131,7 @@ pub struct MsgAcknowledgement { /// IBC acknowledgement message pub message: IbcMsgAcknowledgement, /// Shieleded transfer for MASP transaction - pub transfer: Option, + pub transfer: Option, } impl BorshSerialize for MsgAcknowledgement { @@ -150,7 +150,7 @@ impl BorshDeserialize for MsgAcknowledgement { reader: &mut R, ) -> std::io::Result { use std::io::{Error, ErrorKind}; - let (msg, transfer): (Vec, Option) = + let (msg, transfer): (Vec, Option) = BorshDeserialize::deserialize_reader(reader)?; let message = IbcMsgAcknowledgement::decode_vec(&msg) .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; @@ -165,7 +165,7 @@ pub struct MsgTimeout { /// IBC timeout message pub message: IbcMsgTimeout, /// Shieleded transfer for MASP transaction - pub transfer: Option, + pub transfer: Option, } impl BorshSerialize for MsgTimeout { @@ -184,7 +184,7 @@ impl BorshDeserialize for MsgTimeout { reader: &mut R, ) -> std::io::Result { use std::io::{Error, ErrorKind}; - let (msg, transfer): (Vec, Option) = + let (msg, transfer): (Vec, Option) = BorshDeserialize::deserialize_reader(reader)?; let message = IbcMsgTimeout::decode_vec(&msg) .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; diff --git a/crates/light_sdk/src/transaction/transfer.rs b/crates/light_sdk/src/transaction/transfer.rs index 84475660ea..93ad473d08 100644 --- a/crates/light_sdk/src/transaction/transfer.rs +++ b/crates/light_sdk/src/transaction/transfer.rs @@ -1,99 +1,72 @@ use namada_sdk::address::Address; use namada_sdk::hash::Hash; use namada_sdk::key::common; -use namada_sdk::token::DenominatedAmount; +use namada_sdk::token::transaction::Transaction; +use namada_sdk::token::TransparentTransfer; +pub use namada_sdk::token::{DenominatedAmount, Transfer}; use namada_sdk::tx::data::GasLimit; -use namada_sdk::tx::{ - Authorization, Tx, TxError, TX_SHIELDED_TRANSFER_WASM, - TX_SHIELDING_TRANSFER_WASM, TX_TRANSPARENT_TRANSFER_WASM, - TX_UNSHIELDING_TRANSFER_WASM, -}; +use namada_sdk::tx::{Authorization, Tx, TxError, TX_TRANSFER_WASM}; use super::{attach_fee, attach_fee_signature, GlobalArgs}; use crate::transaction; /// A transfer transaction #[derive(Debug, Clone)] -pub struct Transfer(Tx); +pub struct TransferBuilder(Tx); -impl Transfer { +impl TransferBuilder { /// Build a transparent transfer transaction from the given parameters pub fn transparent( - source: Address, - target: Address, - token: Address, - amount: DenominatedAmount, + transfers: Vec, args: GlobalArgs, ) -> Self { - let data = namada_sdk::token::TransparentTransfer { - source, - target, - token, - amount, + let data = namada_sdk::token::Transfer { + transparent: transfers, + shielded_section_hash: None, }; Self(transaction::build_tx( args, data, - TX_TRANSPARENT_TRANSFER_WASM.to_string(), + TX_TRANSFER_WASM.to_string(), )) } /// Build a shielded transfer transaction from the given parameters - pub fn shielded(shielded_section_hash: Hash, args: GlobalArgs) -> Self { - let data = namada_sdk::token::ShieldedTransfer { - section_hash: shielded_section_hash, - }; - - Self(transaction::build_tx( - args, - data, - TX_SHIELDED_TRANSFER_WASM.to_string(), - )) - } - - /// Build a shielding transfer transaction from the given parameters - pub fn shielding( - source: Address, - token: Address, - amount: DenominatedAmount, + pub fn shielded( shielded_section_hash: Hash, + transaction: Transaction, args: GlobalArgs, ) -> Self { - let data = namada_sdk::token::ShieldingTransfer { - source, - token, - amount, - shielded_section_hash, + let data = namada_sdk::token::Transfer { + transparent: vec![], + shielded_section_hash: Some(shielded_section_hash), }; - Self(transaction::build_tx( - args, - data, - TX_SHIELDING_TRANSFER_WASM.to_string(), - )) + let mut tx = + transaction::build_tx(args, data, TX_TRANSFER_WASM.to_string()); + tx.add_masp_tx_section(transaction); + + Self(tx) } - /// Build an unshielding transfer transaction from the given parameters - pub fn unshielding( - target: Address, - token: Address, - amount: DenominatedAmount, + /// Build a MASP transfer transaction from the given parameters + pub fn masp( + transfers: Vec, shielded_section_hash: Hash, + transaction: Transaction, args: GlobalArgs, ) -> Self { - let data = namada_sdk::token::UnshieldingTransfer { - target, - token, - amount, - shielded_section_hash, + let data = namada_sdk::token::Transfer { + transparent: transfers, + shielded_section_hash: Some(shielded_section_hash), }; - Self(transaction::build_tx( - args, - data, - TX_UNSHIELDING_TRANSFER_WASM.to_string(), - )) + let mut tx = + transaction::build_tx(args, data, TX_TRANSFER_WASM.to_string()); + tx.add_masp_tx_section(transaction); + + Self(tx) } /// Get the bytes to sign for the given transaction diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index ef80036715..116b9facea 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -75,10 +75,7 @@ use namada::ledger::queries::{ }; use namada::masp::MaspTxRefs; use namada::state::StorageRead; -use namada::token::{ - Amount, DenominatedAmount, ShieldedTransfer, ShieldingTransfer, - UnshieldingTransfer, -}; +use namada::token::{Amount, DenominatedAmount, Transfer, TransparentTransfer}; use namada::tx::data::pos::Bond; use namada::tx::data::{ BatchResults, BatchedTxResult, Fee, TxResult, VpsResult, @@ -94,7 +91,7 @@ use namada_apps_lib::cli::context::FromContext; use namada_apps_lib::cli::Context; use namada_apps_lib::wallet::{defaults, CliWalletUtils}; use namada_sdk::masp::{ - self, ContextSyncStatus, ShieldedContext, ShieldedUtils, + self, ContextSyncStatus, MaspTransferData, ShieldedContext, ShieldedUtils, }; pub use namada_sdk::tx::{ TX_BECOME_VALIDATOR_WASM, TX_BOND_WASM, TX_BRIDGE_POOL_WASM, @@ -104,11 +101,10 @@ pub use namada_sdk::tx::{ TX_CLAIM_REWARDS_WASM, TX_DEACTIVATE_VALIDATOR_WASM, TX_IBC_WASM, TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL as TX_INIT_PROPOSAL_WASM, TX_REACTIVATE_VALIDATOR_WASM, TX_REDELEGATE_WASM, TX_RESIGN_STEWARD, - TX_REVEAL_PK as TX_REVEAL_PK_WASM, TX_SHIELDED_TRANSFER_WASM, - TX_SHIELDING_TRANSFER_WASM, TX_TRANSPARENT_TRANSFER_WASM, TX_UNBOND_WASM, - TX_UNJAIL_VALIDATOR_WASM, TX_UNSHIELDING_TRANSFER_WASM, - TX_UPDATE_ACCOUNT_WASM, TX_UPDATE_STEWARD_COMMISSION, - TX_VOTE_PROPOSAL as TX_VOTE_PROPOSAL_WASM, TX_WITHDRAW_WASM, VP_USER_WASM, + TX_REVEAL_PK as TX_REVEAL_PK_WASM, TX_TRANSFER_WASM, TX_UNBOND_WASM, + TX_UNJAIL_VALIDATOR_WASM, TX_UPDATE_ACCOUNT_WASM, + TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL as TX_VOTE_PROPOSAL_WASM, + TX_WITHDRAW_WASM, VP_USER_WASM, }; use namada_sdk::wallet::Wallet; use namada_sdk::{Namada, NamadaImpl}; @@ -1080,14 +1076,17 @@ impl BenchShieldedCtx { StdIo, native_token, ); + let masp_transfer_data = MaspTransferData { + source: source.clone(), + target: target.clone(), + token: address::testing::nam(), + amount: denominated_amount, + }; let shielded = async_runtime .block_on( ShieldedContext::::gen_shielded_transfer( &namada, - &source, - &target, - &address::testing::nam(), - denominated_amount, + vec![masp_transfer_data], true, ), ) @@ -1113,9 +1112,10 @@ impl BenchShieldedCtx { && target.effective_address() == MASP { namada.client().generate_tx( - TX_SHIELDED_TRANSFER_WASM, - ShieldedTransfer { - section_hash: shielded_section_hash, + TX_TRANSFER_WASM, + Transfer { + transparent: vec![], + shielded_section_hash: Some(shielded_section_hash), }, Some(shielded), None, @@ -1123,12 +1123,15 @@ impl BenchShieldedCtx { ) } else if target.effective_address() == MASP { namada.client().generate_tx( - TX_SHIELDING_TRANSFER_WASM, - ShieldingTransfer { - source: source.effective_address(), - token: address::testing::nam(), - amount: DenominatedAmount::native(amount), - shielded_section_hash, + TX_TRANSFER_WASM, + Transfer { + transparent: vec![TransparentTransfer { + source: source.effective_address(), + target: MASP, + token: address::testing::nam(), + amount: DenominatedAmount::native(amount), + }], + shielded_section_hash: Some(shielded_section_hash), }, Some(shielded), None, @@ -1136,12 +1139,15 @@ impl BenchShieldedCtx { ) } else { namada.client().generate_tx( - TX_UNSHIELDING_TRANSFER_WASM, - UnshieldingTransfer { - target: target.effective_address(), - token: address::testing::nam(), - amount: DenominatedAmount::native(amount), - shielded_section_hash, + TX_TRANSFER_WASM, + Transfer { + transparent: vec![TransparentTransfer { + source: MASP, + target: target.effective_address(), + token: address::testing::nam(), + amount: DenominatedAmount::native(amount), + }], + shielded_section_hash: Some(shielded_section_hash), }, Some(shielded), None, @@ -1206,13 +1212,20 @@ impl BenchShieldedCtx { timeout_timestamp_on_b: timeout_timestamp, }; - let transfer = ShieldingTransfer::deserialize( - &mut tx.tx.data(&tx.cmt).unwrap().as_slice(), - ) - .unwrap(); + let vectorized_transfer = + Transfer::deserialize(&mut tx.tx.data(&tx.cmt).unwrap().as_slice()) + .unwrap(); + let transfer = Transfer { + transparent: vec![ + vectorized_transfer.transparent.first().unwrap().to_owned(), + ], + shielded_section_hash: Some( + vectorized_transfer.shielded_section_hash.unwrap(), + ), + }; let masp_tx = tx .tx - .get_section(&transfer.shielded_section_hash) + .get_section(&transfer.shielded_section_hash.unwrap()) .unwrap() .masp_tx() .unwrap(); diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index 8db6155e6f..306024a609 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -229,11 +229,9 @@ impl From for InputAmount { } } -/// Transparent transfer transaction arguments +/// Transparent transfer-specific arguments #[derive(Clone, Debug)] -pub struct TxTransparentTransfer { - /// Common tx arguments - pub tx: Tx, +pub struct TxTransparentTransferData { /// Transfer source address pub source: C::Address, /// Transfer target address @@ -242,6 +240,15 @@ pub struct TxTransparentTransfer { pub token: C::Address, /// Transferred token amount pub amount: InputAmount, +} + +/// Transparent transfer transaction arguments +#[derive(Clone, Debug)] +pub struct TxTransparentTransfer { + /// Common tx arguments + pub tx: Tx, + /// The transfer specific data + pub data: Vec>, /// Path to the TX WASM code file pub tx_code_path: PathBuf, } @@ -258,7 +265,7 @@ impl TxBuilder for TxTransparentTransfer { } } -impl TxTransparentTransfer { +impl TxTransparentTransferData { /// Transfer source address pub fn source(self, source: C::Address) -> Self { Self { source, ..self } @@ -278,7 +285,9 @@ impl TxTransparentTransfer { pub fn amount(self, amount: InputAmount) -> Self { Self { amount, ..self } } +} +impl TxTransparentTransfer { /// Path to the TX WASM code file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { Self { @@ -298,11 +307,9 @@ impl TxTransparentTransfer { } } -/// Shielded transfer transaction arguments +/// Shielded transfer-specific arguments #[derive(Clone, Debug)] -pub struct TxShieldedTransfer { - /// Common tx arguments - pub tx: Tx, +pub struct TxShieldedTransferData { /// Transfer source spending key pub source: C::SpendingKey, /// Transfer target address @@ -311,6 +318,15 @@ pub struct TxShieldedTransfer { pub token: C::Address, /// Transferred token amount pub amount: InputAmount, +} + +/// Shielded transfer transaction arguments +#[derive(Clone, Debug)] +pub struct TxShieldedTransfer { + /// Common tx arguments + pub tx: Tx, + /// Transfer-specific data + pub data: Vec>, /// Path to the TX WASM code file pub tx_code_path: PathBuf, } @@ -325,19 +341,26 @@ impl TxShieldedTransfer { } } +/// Shielding transfer-specific arguments +#[derive(Clone, Debug)] +pub struct TxShieldingTransferData { + /// Transfer source spending key + pub source: C::Address, + /// Transferred token address + pub token: C::Address, + /// Transferred token amount + pub amount: InputAmount, +} + /// Shielding transfer transaction arguments #[derive(Clone, Debug)] pub struct TxShieldingTransfer { /// Common tx arguments pub tx: Tx, - /// Transfer source address - pub source: C::Address, /// Transfer target address pub target: C::PaymentAddress, - /// Transferred token address - pub token: C::Address, - /// Transferred token amount - pub amount: InputAmount, + /// Transfer-specific data + pub data: Vec>, /// Path to the TX WASM code file pub tx_code_path: PathBuf, } @@ -352,19 +375,26 @@ impl TxShieldingTransfer { } } -/// Unshielding transfer transaction arguments +/// Unshielding transfer-specific arguments #[derive(Clone, Debug)] -pub struct TxUnshieldingTransfer { - /// Common tx arguments - pub tx: Tx, - /// Transfer source spending key - pub source: C::SpendingKey, +pub struct TxUnshieldingTransferData { /// Transfer target address pub target: C::Address, /// Transferred token address pub token: C::Address, /// Transferred token amount pub amount: InputAmount, +} + +/// Unshielding transfer transaction arguments +#[derive(Clone, Debug)] +pub struct TxUnshieldingTransfer { + /// Common tx arguments + pub tx: Tx, + /// Transfer source spending key + pub source: C::SpendingKey, + /// Transfer-specific data + pub data: Vec>, /// Path to the TX WASM code file pub tx_code_path: PathBuf, } diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 7832b23d33..62764a1a90 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -72,10 +72,8 @@ use tx::{ TX_CHANGE_CONSENSUS_KEY_WASM, TX_CHANGE_METADATA_WASM, TX_CLAIM_REWARDS_WASM, TX_DEACTIVATE_VALIDATOR_WASM, TX_IBC_WASM, TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL, TX_REACTIVATE_VALIDATOR_WASM, - TX_REDELEGATE_WASM, TX_RESIGN_STEWARD, TX_REVEAL_PK, - TX_SHIELDED_TRANSFER_WASM, TX_SHIELDING_TRANSFER_WASM, - TX_TRANSPARENT_TRANSFER_WASM, TX_UNBOND_WASM, TX_UNJAIL_VALIDATOR_WASM, - TX_UNSHIELDING_TRANSFER_WASM, TX_UPDATE_ACCOUNT_WASM, + TX_REDELEGATE_WASM, TX_RESIGN_STEWARD, TX_REVEAL_PK, TX_TRANSFER_WASM, + TX_UNBOND_WASM, TX_UNJAIL_VALIDATOR_WASM, TX_UPDATE_ACCOUNT_WASM, TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, VP_USER_WASM, }; @@ -175,17 +173,11 @@ pub trait Namada: Sized + MaybeSync + MaybeSend { /// arguments fn new_transparent_transfer( &self, - source: Address, - target: Address, - token: Address, - amount: InputAmount, + data: Vec, ) -> args::TxTransparentTransfer { args::TxTransparentTransfer { - source, - target, - token, - amount, - tx_code_path: PathBuf::from(TX_TRANSPARENT_TRANSFER_WASM), + data, + tx_code_path: PathBuf::from(TX_TRANSFER_WASM), tx: self.tx_builder(), } } @@ -194,17 +186,11 @@ pub trait Namada: Sized + MaybeSync + MaybeSend { /// arguments fn new_shielded_transfer( &self, - source: ExtendedSpendingKey, - target: PaymentAddress, - token: Address, - amount: InputAmount, + data: Vec, ) -> args::TxShieldedTransfer { args::TxShieldedTransfer { - source, - target, - token, - amount, - tx_code_path: PathBuf::from(TX_SHIELDED_TRANSFER_WASM), + data, + tx_code_path: PathBuf::from(TX_TRANSFER_WASM), tx: self.tx_builder(), } } @@ -213,17 +199,13 @@ pub trait Namada: Sized + MaybeSync + MaybeSend { /// arguments fn new_shielding_transfer( &self, - source: Address, target: PaymentAddress, - token: Address, - amount: InputAmount, + data: Vec, ) -> args::TxShieldingTransfer { args::TxShieldingTransfer { - source, + data, target, - token, - amount, - tx_code_path: PathBuf::from(TX_SHIELDING_TRANSFER_WASM), + tx_code_path: PathBuf::from(TX_TRANSFER_WASM), tx: self.tx_builder(), } } @@ -233,16 +215,12 @@ pub trait Namada: Sized + MaybeSync + MaybeSend { fn new_unshielding_transfer( &self, source: ExtendedSpendingKey, - target: Address, - token: Address, - amount: InputAmount, + data: Vec, ) -> args::TxUnshieldingTransfer { args::TxUnshieldingTransfer { source, - target, - token, - amount, - tx_code_path: PathBuf::from(TX_UNSHIELDING_TRANSFER_WASM), + data, + tx_code_path: PathBuf::from(TX_TRANSFER_WASM), tx: self.tx_builder(), } } @@ -864,6 +842,7 @@ pub mod testing { use namada_core::address::testing::{ arb_established_address, arb_non_internal_address, }; + use namada_core::address::MASP; use namada_core::eth_bridge_pool::PendingTransfer; use namada_core::hash::testing::arb_hash; use namada_core::key::testing::arb_common_keypair; @@ -872,13 +851,8 @@ pub mod testing { }; use namada_governance::{InitProposalData, VoteProposalData}; use namada_ibc::testing::arb_ibc_any; - use namada_token::testing::{ - arb_denominated_amount, arb_transparent_transfer, - }; - use namada_token::{ - ShieldedTransfer, ShieldingTransfer, TransparentTransfer, - UnshieldingTransfer, - }; + use namada_token::testing::arb_denominated_amount; + use namada_token::Transfer; use namada_tx::data::pgf::UpdateStewardCommission; use namada_tx::data::pos::{ BecomeValidator, Bond, CommissionChange, ConsensusKeyChange, @@ -890,6 +864,8 @@ pub mod testing { use prost::Message; use ripemd::Digest as RipemdDigest; use sha2::Digest; + use token::testing::arb_vectorized_transparent_transfer; + use token::TransparentTransfer; use super::*; use crate::account::tests::{arb_init_account, arb_update_account}; @@ -931,10 +907,8 @@ pub mod testing { UpdateAccount(UpdateAccount), VoteProposal(VoteProposalData), Withdraw(Withdraw), - TransparentTransfer(TransparentTransfer), - ShieldedTransfer(ShieldedTransfer, (StoredBuildParams, String)), - ShieldingTransfer(ShieldingTransfer, (StoredBuildParams, String)), - UnshieldingTransfer(UnshieldingTransfer, (StoredBuildParams, String)), + TransparentTransfer(Transfer), + MaspTransfer(Transfer, (StoredBuildParams, String)), Bond(Bond), Redelegation(Redelegation), UpdateStewardCommission(UpdateStewardCommission), @@ -1093,13 +1067,13 @@ pub mod testing { pub fn arb_transparent_transfer_tx()( mut header in arb_header(), wrapper in arb_wrapper_tx(), - transfer in arb_transparent_transfer(), + transfer in arb_vectorized_transparent_transfer(10), code_hash in arb_hash(), ) -> (Tx, TxData) { header.tx_type = TxType::Wrapper(Box::new(wrapper)); let mut tx = Tx { header, sections: vec![] }; tx.add_data(transfer.clone()); - tx.add_code_from_hash(code_hash, Some(TX_TRANSPARENT_TRANSFER_WASM.to_owned())); + tx.add_code_from_hash(code_hash, Some(TX_TRANSFER_WASM.to_owned())); (tx, TxData::TransparentTransfer(transfer)) } } @@ -1127,17 +1101,17 @@ pub mod testing { } prop_compose! { - /// Generate an arbitrary transfer transaction - pub fn arb_masp_transfer_tx()(transfer in arb_transparent_transfer())( + /// Generate an arbitrary masp transfer transaction + pub fn arb_masp_transfer_tx()(transfers in arb_vectorized_transparent_transfer(5))( mut header in arb_header(), wrapper in arb_wrapper_tx(), code_hash in arb_hash(), (masp_tx_type, (shielded_transfer, asset_types, build_params)) in prop_oneof![ (Just(MaspTxType::Shielded), arb_shielded_transfer(0..MAX_ASSETS)), - (Just(MaspTxType::Shielding), arb_shielding_transfer(encode_address(&transfer.source), 1)), - (Just(MaspTxType::Unshielding), arb_deshielding_transfer(encode_address(&transfer.target), 1)), + (Just(MaspTxType::Shielding), arb_shielding_transfer(encode_address(&transfers.transparent.first().unwrap().source), 1)), + (Just(MaspTxType::Unshielding), arb_deshielding_transfer(encode_address(&transfers.transparent.first().unwrap().target), 1)), ], - transfer in Just(transfer), + transfers in Just(transfers), ) -> (Tx, TxData) { header.tx_type = TxType::Wrapper(Box::new(wrapper)); let mut tx = Tx { header, sections: vec![] }; @@ -1146,10 +1120,13 @@ pub mod testing { data_encoding::HEXLOWER.encode(&build_params.serialize_to_vec()); let tx_data = match masp_tx_type { MaspTxType::Shielded => { - tx.add_code_from_hash(code_hash, Some(TX_SHIELDED_TRANSFER_WASM.to_owned())); - let data = ShieldedTransfer { section_hash: shielded_section_hash }; + tx.add_code_from_hash(code_hash, Some(TX_TRANSFER_WASM.to_owned())); + let data = Transfer { + transparent: vec![], + shielded_section_hash: Some(shielded_section_hash), + }; tx.add_data(data.clone()); - TxData::ShieldedTransfer(data, (build_params, build_param_bytes)) + TxData::MaspTransfer(data, (build_params, build_param_bytes)) }, MaspTxType::Shielding => { // Set the transparent amount and token @@ -1159,10 +1136,21 @@ pub mod testing { token::Amount::from_masp_denominated(*value, decoded.position), decoded.denom, ); - tx.add_code_from_hash(code_hash, Some(TX_SHIELDING_TRANSFER_WASM.to_owned())); - let data = ShieldingTransfer {source: transfer.source, token, amount, shielded_section_hash }; + tx.add_code_from_hash(code_hash, Some(TX_TRANSFER_WASM.to_owned())); + let transparent = transfers.transparent.into_iter().map(|transfer| + TransparentTransfer{ + source: transfer.source, + token: token.clone(), + amount, + target: MASP, + } + ).collect(); + let data = Transfer{ + transparent, + shielded_section_hash: Some(shielded_section_hash), + }; tx.add_data(data.clone()); - TxData::ShieldingTransfer(data, (build_params, build_param_bytes)) + TxData::MaspTransfer(data, (build_params, build_param_bytes)) }, MaspTxType::Unshielding => { // Set the transparent amount and token @@ -1172,10 +1160,18 @@ pub mod testing { token::Amount::from_masp_denominated(*value, decoded.position), decoded.denom, ); - tx.add_code_from_hash(code_hash, Some(TX_UNSHIELDING_TRANSFER_WASM.to_owned())); - let data = UnshieldingTransfer {target: transfer.target, token, amount, shielded_section_hash }; + tx.add_code_from_hash(code_hash, Some(TX_TRANSFER_WASM.to_owned())); + let transparent = transfers.transparent.into_iter().map(|transfer| + TransparentTransfer{ + target: transfer.target, + token: token.clone(), + amount, + source: MASP, + } + ).collect(); + let data = Transfer{transparent, shielded_section_hash: Some(shielded_section_hash) }; tx.add_data(data.clone()); - TxData::UnshieldingTransfer(data, (build_params, build_param_bytes)) + TxData::MaspTransfer(data, (build_params, build_param_bytes)) }, }; tx.add_masp_builder(MaspBuilder { diff --git a/crates/sdk/src/masp.rs b/crates/sdk/src/masp.rs index 516a7ad611..bed3d1cf52 100644 --- a/crates/sdk/src/masp.rs +++ b/crates/sdk/src/masp.rs @@ -121,6 +121,16 @@ pub struct ShieldedTransfer { pub epoch: MaspEpoch, } +/// The data for a single masp transfer +#[allow(missing_docs)] +#[derive(Debug)] +pub struct MaspTransferData { + pub source: TransferSource, + pub target: TransferTarget, + pub token: Address, + pub amount: token::DenominatedAmount, +} + /// Shielded pool data for a token #[allow(missing_docs)] #[derive(Debug, BorshSerialize, BorshDeserialize, BorshDeserializer)] @@ -134,11 +144,17 @@ pub struct MaspTokenRewardData { } /// A return type for gen_shielded_transfer +#[allow(clippy::large_enum_variant)] #[derive(Error, Debug)] pub enum TransferErr { /// Build error for masp errors - #[error("{0}")] - Build(#[from] builder::Error), + #[error("{error}")] + Build { + /// The error + error: builder::Error, + /// The optional associated transfer data for logging purposes + data: Option, + }, /// errors #[error("{0}")] General(#[from] Error), @@ -1515,36 +1531,9 @@ impl ShieldedContext { /// amounts and signatures specified by the containing Transfer object. pub async fn gen_shielded_transfer( context: &impl Namada, - source: &TransferSource, - target: &TransferTarget, - token: &Address, - amount: token::DenominatedAmount, + data: Vec, update_ctx: bool, ) -> Result, TransferErr> { - // No shielded components are needed when neither source nor destination - // are shielded - - let spending_key = source.spending_key(); - let payment_address = target.payment_address(); - // No shielded components are needed when neither source nor - // destination are shielded - if spending_key.is_none() && payment_address.is_none() { - return Ok(None); - } - // We want to fund our transaction solely from supplied spending key - let spending_key = spending_key.map(|x| x.into()); - { - // Load the current shielded context given the spending key we - // possess - let mut shielded = context.shielded_mut().await; - let _ = shielded.load().await; - } - // Determine epoch in which to submit potential shielded transaction - let epoch = rpc::query_masp_epoch(context.client()).await?; - // Context required for storing which notes are in the source's - // possession - let memo = MemoBytes::empty(); - // Try to get a seed from env var, if any. #[allow(unused_mut)] let mut rng = StdRng::from_rng(OsRng).unwrap(); @@ -1567,7 +1556,6 @@ impl ShieldedContext { rng }; - // Now we build up the transaction within this object // TODO: if the user requested the default expiration, there might be a // small discrepancy between the datetime we calculate here and the one // we set for the transaction. This should be small enough to not cause @@ -1621,235 +1609,309 @@ impl ShieldedContext { // use from the masp crate to specify the expiration better expiration_height.into(), ); + // Determine epoch in which to submit potential shielded transaction + let epoch = rpc::query_masp_epoch(context.client()).await?; - // Convert transaction amount into MASP types - let Some(denom) = query_denom(context.client(), token).await else { - return Err(TransferErr::General(Error::from( - QueryError::General(format!("denomination for token {token}")), - ))); - }; - let (asset_types, masp_amount) = { - let mut shielded = context.shielded_mut().await; - // Do the actual conversion to an asset type - let amount = shielded - .convert_amount( - context.client(), - epoch, - token, - denom, - amount.amount(), - ) - .await?; - // Make sure to save any decodings of the asset types used so that - // balance queries involving them are successful - let _ = shielded.save().await; - amount - }; - - // If there are shielded inputs - if let Some(sk) = spending_key { - // Locate unspent notes that can help us meet the transaction amount - let (_, unspent_notes, used_convs) = context - .shielded_mut() - .await - .collect_unspent_notes( - context, - &to_viewing_key(&sk).vk, - I128Sum::from_sum(masp_amount), - epoch, - ) - .await?; - // Commit the notes found to our transaction - for (diversifier, note, merkle_path) in unspent_notes { - builder - .add_sapling_spend(sk, diversifier, note, merkle_path) - .map_err(builder::Error::SaplingBuild)?; - } - // Commit the conversion notes used during summation - for (conv, wit, value) in used_convs.values() { - if value.is_positive() { - builder - .add_sapling_convert( - conv.clone(), - *value as u64, - wit.clone(), - ) - .map_err(builder::Error::SaplingBuild)?; - } + let mut is_context_loaded = false; + for MaspTransferData { + source, + target, + token, + amount, + } in data + { + let spending_key = source.spending_key(); + let payment_address = target.payment_address(); + // No shielded components are needed when neither source nor + // destination are shielded + if spending_key.is_none() && payment_address.is_none() { + return Ok(None); } - } else { - // We add a dummy UTXO to our transaction, but only the source of - // the parent Transfer object is used to validate fund - // availability - let source_enc = source - .address() - .ok_or_else(|| { - Error::Other( - "source address should be transparent".to_string(), - ) - })? - .serialize_to_vec(); - - let hash = ripemd::Ripemd160::digest(sha2::Sha256::digest( - source_enc.as_ref(), - )); - let script = TransparentAddress(hash.into()); - for (digit, asset_type) in - MaspDigitPos::iter().zip(asset_types.iter()) + // We want to fund our transaction solely from supplied spending key + let spending_key = spending_key.map(|x| x.into()); { - let amount_part = digit.denominate(&amount.amount()); - // Skip adding an input if its value is 0 - if amount_part != 0 { - builder - .add_transparent_input(TxOut { - asset_type: *asset_type, - value: amount_part, - address: script, - }) - .map_err(builder::Error::TransparentBuild)?; + if !is_context_loaded { + // Load the current shielded context (at most once) given + // the spending key we possess + let mut shielded = context.shielded_mut().await; + let _ = shielded.load().await; + is_context_loaded = true; } } - } + // Context required for storing which notes are in the source's + // possession + let memo = MemoBytes::empty(); - // Anotate the asset type in the value balance with its decoding in - // order to facilitate cross-epoch computations - let value_balance = builder.value_balance(); - let value_balance = context - .shielded_mut() - .await - .decode_sum(context.client(), value_balance) - .await; + // Now we build up the transaction within this object - // If we are sending to a transparent output, then we will need to embed - // the transparent target address into the shielded transaction so that - // it can be signed - let transparent_target_hash = if payment_address.is_none() { - let target_enc = target - .address() - .ok_or_else(|| { - Error::Other( - "target address should be transparent".to_string(), + // Convert transaction amount into MASP types + let Some(denom) = query_denom(context.client(), &token).await + else { + return Err(TransferErr::General(Error::from( + QueryError::General(format!( + "denomination for token {token}" + )), + ))); + }; + let (asset_types, masp_amount) = { + let mut shielded = context.shielded_mut().await; + // Do the actual conversion to an asset type + let amount = shielded + .convert_amount( + context.client(), + epoch, + &token, + denom, + amount.amount(), ) - })? - .serialize_to_vec(); - Some(ripemd::Ripemd160::digest(sha2::Sha256::digest( - target_enc.as_ref(), - ))) - } else { - None - }; - // This indicates how many more assets need to be sent to the receiver - // in order to satisfy the requested transfer amount. - let mut rem_amount = amount.amount().raw_amount().0; - // If we are sending to a shielded address, we may need the outgoing - // viewing key in the following computations. - let ovk_opt = spending_key.map(|x| x.expsk.ovk); - - // Now handle the outputs of this transaction - // Loop through the value balance components and see which - // ones can be given to the receiver - for ((asset_type, decoded), val) in value_balance.components() { - let rem_amount = &mut rem_amount[decoded.position as usize]; - // Only asset types with the correct token can contribute. But - // there must be a demonstrated need for it. - if decoded.token == *token - && decoded.denom == denom - && decoded.epoch.map_or(true, |vbal_epoch| vbal_epoch <= epoch) - && *rem_amount > 0 - { - let val = u128::try_from(*val).expect( - "value balance in absence of output descriptors should be \ - non-negative", - ); - // We want to take at most the remaining quota for the - // current denomination to the receiver - let contr = std::cmp::min(*rem_amount as u128, val) as u64; - // Make transaction output tied to the current token, - // denomination, and epoch. - if let Some(pa) = payment_address { - // If there is a shielded output - builder - .add_sapling_output( - ovk_opt, - pa.into(), - *asset_type, - contr, - memo.clone(), - ) - .map_err(builder::Error::SaplingBuild)?; - } else { - // If there is a transparent output - let hash = transparent_target_hash - .expect( - "transparent target hash should have been \ - computed already", - ) - .into(); + .await?; + // Make sure to save any decodings of the asset types used so + // that balance queries involving them are + // successful + let _ = shielded.save().await; + amount + }; + + // If there are shielded inputs + if let Some(sk) = spending_key { + // Locate unspent notes that can help us meet the transaction + // amount + let (_, unspent_notes, used_convs) = context + .shielded_mut() + .await + .collect_unspent_notes( + context, + &to_viewing_key(&sk).vk, + I128Sum::from_sum(masp_amount), + epoch, + ) + .await?; + // Commit the notes found to our transaction + for (diversifier, note, merkle_path) in unspent_notes { builder - .add_transparent_output( - &TransparentAddress(hash), - *asset_type, - contr, + .add_sapling_spend(sk, diversifier, note, merkle_path) + .map_err(|e| TransferErr::Build { + error: builder::Error::SaplingBuild(e), + data: None, + })?; + } + // Commit the conversion notes used during summation + for (conv, wit, value) in used_convs.values() { + if value.is_positive() { + builder + .add_sapling_convert( + conv.clone(), + *value as u64, + wit.clone(), + ) + .map_err(|e| TransferErr::Build { + error: builder::Error::SaplingBuild(e), + data: None, + })?; + } + } + } else { + // We add a dummy UTXO to our transaction, but only the source + // of the parent Transfer object is used to + // validate fund availability + let source_enc = source + .address() + .ok_or_else(|| { + Error::Other( + "source address should be transparent".to_string(), ) - .map_err(builder::Error::TransparentBuild)?; + })? + .serialize_to_vec(); + + let hash = ripemd::Ripemd160::digest(sha2::Sha256::digest( + source_enc.as_ref(), + )); + let script = TransparentAddress(hash.into()); + for (digit, asset_type) in + MaspDigitPos::iter().zip(asset_types.iter()) + { + let amount_part = digit.denominate(&amount.amount()); + // Skip adding an input if its value is 0 + if amount_part != 0 { + builder + .add_transparent_input(TxOut { + asset_type: *asset_type, + value: amount_part, + address: script, + }) + .map_err(|e| TransferErr::Build { + error: builder::Error::TransparentBuild(e), + data: None, + })?; + } } - // Lower what is required of the remaining contribution - *rem_amount -= contr; } - } - // Nothing must remain to be included in output - if rem_amount != [0; 4] { - // Convert the shortfall into a I128Sum - let mut shortfall = I128Sum::zero(); - for (asset_type, val) in asset_types.iter().zip(rem_amount) { - shortfall += I128Sum::from_pair(*asset_type, val.into()); - } - // Return an insufficient ffunds error - return Result::Err(TransferErr::from( - builder::Error::InsufficientFunds(shortfall), - )); - } + // Anotate the asset type in the value balance with its decoding in + // order to facilitate cross-epoch computations + let value_balance = builder.value_balance(); + let value_balance = context + .shielded_mut() + .await + .decode_sum(context.client(), value_balance) + .await; - // Now add outputs representing the change from this payment - if let Some(sk) = spending_key { - // Represents the amount of inputs we are short by - let mut additional = I128Sum::zero(); - for (asset_type, amt) in builder.value_balance().components() { - match amt.cmp(&0) { - Ordering::Greater => { - // Send the change in this asset type back to the sender + // If we are sending to a transparent output, then we will need to + // embed the transparent target address into the + // shielded transaction so that it can be signed + let transparent_target_hash = if payment_address.is_none() { + let target_enc = target + .address() + .ok_or_else(|| { + Error::Other( + "target address should be transparent".to_string(), + ) + })? + .serialize_to_vec(); + Some(ripemd::Ripemd160::digest(sha2::Sha256::digest( + target_enc.as_ref(), + ))) + } else { + None + }; + // This indicates how many more assets need to be sent to the + // receiver in order to satisfy the requested transfer + // amount. + let mut rem_amount = amount.amount().raw_amount().0; + // If we are sending to a shielded address, we may need the outgoing + // viewing key in the following computations. + let ovk_opt = spending_key.map(|x| x.expsk.ovk); + + // Now handle the outputs of this transaction + // Loop through the value balance components and see which + // ones can be given to the receiver + for ((asset_type, decoded), val) in value_balance.components() { + let rem_amount = &mut rem_amount[decoded.position as usize]; + // Only asset types with the correct token can contribute. But + // there must be a demonstrated need for it. + if decoded.token == token + && decoded.denom == denom + && decoded + .epoch + .map_or(true, |vbal_epoch| vbal_epoch <= epoch) + && *rem_amount > 0 + { + let val = u128::try_from(*val).expect( + "value balance in absence of output descriptors \ + should be non-negative", + ); + // We want to take at most the remaining quota for the + // current denomination to the receiver + let contr = std::cmp::min(*rem_amount as u128, val) as u64; + // Make transaction output tied to the current token, + // denomination, and epoch. + if let Some(pa) = payment_address { + // If there is a shielded output builder .add_sapling_output( - Some(sk.expsk.ovk), - sk.default_address().1, + ovk_opt, + pa.into(), *asset_type, - *amt as u64, + contr, memo.clone(), ) - .map_err(builder::Error::SaplingBuild)?; + .map_err(|e| TransferErr::Build { + error: builder::Error::SaplingBuild(e), + data: None, + })?; + } else { + // If there is a transparent output + let hash = transparent_target_hash + .expect( + "transparent target hash should have been \ + computed already", + ) + .into(); + builder + .add_transparent_output( + &TransparentAddress(hash), + *asset_type, + contr, + ) + .map_err(|e| TransferErr::Build { + error: builder::Error::TransparentBuild(e), + data: None, + })?; } - Ordering::Less => { - // Record how much of the current asset type we are - // short by - additional += - I128Sum::from_nonnegative(*asset_type, -*amt) - .map_err(|()| { + // Lower what is required of the remaining contribution + *rem_amount -= contr; + } + } + + // Nothing must remain to be included in output + if rem_amount != [0; 4] { + // Convert the shortfall into a I128Sum + let mut shortfall = I128Sum::zero(); + for (asset_type, val) in asset_types.iter().zip(rem_amount) { + shortfall += I128Sum::from_pair(*asset_type, val.into()); + } + // Return an insufficient funds error + return Result::Err(TransferErr::Build { + error: builder::Error::InsufficientFunds(shortfall), + data: Some(MaspTransferData { + source, + target, + token, + amount, + }), + }); + } + + // Now add outputs representing the change from this payment + if let Some(sk) = spending_key { + // Represents the amount of inputs we are short by + let mut additional = I128Sum::zero(); + for (asset_type, amt) in builder.value_balance().components() { + match amt.cmp(&0) { + Ordering::Greater => { + // Send the change in this asset type back to the + // sender + builder + .add_sapling_output( + Some(sk.expsk.ovk), + sk.default_address().1, + *asset_type, + *amt as u64, + memo.clone(), + ) + .map_err(|e| TransferErr::Build { + error: builder::Error::SaplingBuild(e), + data: None, + })?; + } + Ordering::Less => { + // Record how much of the current asset type we are + // short by + additional += + I128Sum::from_nonnegative(*asset_type, -*amt) + .map_err(|()| { Error::Other(format!( "from non negative conversion: {}", line!() )) })?; + } + Ordering::Equal => {} } - Ordering::Equal => {} } - } - // If we are short by a non-zero amount, then we have insufficient - // funds - if !additional.is_zero() { - return Err(TransferErr::from( - builder::Error::InsufficientFunds(additional), - )); + // If we are short by a non-zero amount, then we have + // insufficient funds + if !additional.is_zero() { + return Result::Err(TransferErr::Build { + error: builder::Error::InsufficientFunds(additional), + data: Some(MaspTransferData { + source, + target, + token, + amount, + }), + }); + } } } @@ -1859,12 +1921,14 @@ impl ShieldedContext { let prover = context.shielded().await.utils.local_tx_prover(); #[cfg(feature = "testing")] let prover = testing::MockTxProver(std::sync::Mutex::new(OsRng)); - let (masp_tx, metadata) = builder.build( - &prover, - &FeeRule::non_standard(U64Sum::zero()), - &mut rng, - &mut RngBuildParams::new(OsRng), - )?; + let (masp_tx, metadata) = builder + .build( + &prover, + &FeeRule::non_standard(U64Sum::zero()), + &mut rng, + &mut RngBuildParams::new(OsRng), + ) + .map_err(|error| TransferErr::Build { error, data: None })?; if update_ctx { // Cache the generated transfer diff --git a/crates/sdk/src/signing.rs b/crates/sdk/src/signing.rs index a6e1ef5f44..03a427234c 100644 --- a/crates/sdk/src/signing.rs +++ b/crates/sdk/src/signing.rs @@ -48,11 +48,10 @@ use crate::tx::{ TX_CHANGE_METADATA_WASM, TX_CLAIM_REWARDS_WASM, TX_DEACTIVATE_VALIDATOR_WASM, TX_IBC_WASM, TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL, TX_REACTIVATE_VALIDATOR_WASM, TX_REDELEGATE_WASM, - TX_RESIGN_STEWARD, TX_REVEAL_PK, TX_SHIELDED_TRANSFER_WASM, - TX_SHIELDING_TRANSFER_WASM, TX_TRANSPARENT_TRANSFER_WASM, TX_UNBOND_WASM, - TX_UNJAIL_VALIDATOR_WASM, TX_UNSHIELDING_TRANSFER_WASM, - TX_UPDATE_ACCOUNT_WASM, TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL, - TX_WITHDRAW_WASM, VP_USER_WASM, + TX_RESIGN_STEWARD, TX_REVEAL_PK, TX_TRANSFER_WASM, TX_UNBOND_WASM, + TX_UNJAIL_VALIDATOR_WASM, TX_UPDATE_ACCOUNT_WASM, + TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, + VP_USER_WASM, }; pub use crate::wallet::store::AddressVpType; use crate::wallet::{Wallet, WalletIo}; @@ -702,61 +701,101 @@ fn format_outputs(output: &mut Vec) { } } -enum TokenTransfer<'a> { - Transparent(&'a token::TransparentTransfer), - Shielded, - Shielding(&'a token::ShieldingTransfer), - Unshielding(&'a token::UnshieldingTransfer), +enum TransferSide<'a> { + Source(&'a Address), + Target(&'a Address), } +struct TokenTransfer<'a>(&'a token::Transfer); + impl TokenTransfer<'_> { - fn source(&self) -> Option<&Address> { - match self { - TokenTransfer::Transparent(transfer) => Some(&transfer.source), - TokenTransfer::Shielded => None, - TokenTransfer::Shielding(transfer) => Some(&transfer.source), - TokenTransfer::Unshielding(_) => None, - } + fn sources(&self) -> Vec<&Address> { + self.0 + .transparent + .iter() + .map(|transfer| &transfer.source) + .collect() } - fn target(&self) -> Option<&Address> { - match self { - TokenTransfer::Transparent(transfer) => Some(&transfer.target), - TokenTransfer::Shielded => None, - TokenTransfer::Shielding(_) => None, - TokenTransfer::Unshielding(transfer) => Some(&transfer.target), - } + fn targets(&self) -> Vec<&Address> { + self.0 + .transparent + .iter() + .map(|transfer| &transfer.target) + .collect() } - fn token_and_amount(&self) -> Option<(&Address, DenominatedAmount)> { - match self { - TokenTransfer::Transparent(transfer) => { - Some((&transfer.token, transfer.amount)) + fn tokens_and_amounts( + &self, + address: TransferSide<'_>, + ) -> Result, Error> { + let mut map: HashMap<&Address, DenominatedAmount> = HashMap::new(); + + match address { + TransferSide::Source(source) => { + for transfer in &self.0.transparent { + if source == &transfer.source { + Self::update_token_amount_map( + &mut map, + &transfer.token, + transfer.amount, + )?; + } + } } - TokenTransfer::Shielded => None, - TokenTransfer::Shielding(transfer) => { - Some((&transfer.token, transfer.amount)) + TransferSide::Target(target) => { + for transfer in &self.0.transparent { + if target == &transfer.target { + Self::update_token_amount_map( + &mut map, + &transfer.token, + transfer.amount, + )?; + } + } } - TokenTransfer::Unshielding(transfer) => { - Some((&transfer.token, transfer.amount)) + } + Ok(map) + } + + fn update_token_amount_map<'a>( + map: &mut HashMap<&'a Address, DenominatedAmount>, + token: &'a Address, + amount: DenominatedAmount, + ) -> Result<(), Error> { + match map.get_mut(token) { + Some(prev_amount) => { + *prev_amount = checked!(prev_amount + amount)?; + } + None => { + map.insert(token, amount); } } + + Ok(()) } } -/// Adds a Ledger output for the sender and destination for transparent and MASP -/// transactions +/// Adds a Ledger output for the senders and destinations for transparent and +/// MASP transactions async fn make_ledger_token_transfer_endpoints( tokens: &HashMap, output: &mut Vec, transfer: TokenTransfer<'_>, builder: Option<&MaspBuilder>, assets: &HashMap, -) { - if let Some(source) = transfer.source() { - output.push(format!("Sender : {}", source)); - if let Some((token, amount)) = transfer.token_and_amount() { - make_ledger_amount_addr(tokens, output, amount, token, "Sending "); +) -> Result<(), Error> { + let sources = transfer.sources(); + if !sources.is_empty() { + for source in transfer.sources() { + output.push(format!("Sender : {}", source)); + for (token, amount) in + transfer.tokens_and_amounts(TransferSide::Source(source))? + { + make_ledger_amount_addr( + tokens, output, amount, token, "Sending ", + ); + } } } else if let Some(builder) = builder { for sapling_input in builder.builder.sapling_inputs() { @@ -773,16 +812,21 @@ async fn make_ledger_token_transfer_endpoints( .await; } } - if let Some(target) = transfer.target() { - output.push(format!("Destination : {}", target)); - if let Some((token, amount)) = transfer.token_and_amount() { - make_ledger_amount_addr( - tokens, - output, - amount, - token, - "Receiving ", - ); + let targets = transfer.targets(); + if !targets.is_empty() { + for target in targets { + output.push(format!("Destination : {}", target)); + for (token, amount) in + transfer.tokens_and_amounts(TransferSide::Target(target))? + { + make_ledger_amount_addr( + tokens, + output, + amount, + token, + "Receiving ", + ); + } } } else if let Some(builder) = builder { for sapling_output in builder.builder.sapling_outputs() { @@ -799,6 +843,8 @@ async fn make_ledger_token_transfer_endpoints( .await; } } + + Ok(()) } /// Convert decimal numbers into the format used by Ledger. Specifically remove @@ -1274,84 +1320,8 @@ pub async fn to_ledger_vector( HEXLOWER.encode(&extra_code_hash.0) )]); } - } else if code_sec.tag == Some(TX_TRANSPARENT_TRANSFER_WASM.to_string()) - { - let transfer = token::TransparentTransfer::try_from_slice( - &tx.data(cmt) - .ok_or_else(|| Error::Other("Invalid Data".to_string()))?, - ) - .map_err(|err| { - Error::from(EncodingError::Conversion(err.to_string())) - })?; - - tv.name = "Transfer_0".to_string(); - - tv.output.push("Type : TransparentTransfer".to_string()); - make_ledger_token_transfer_endpoints( - &tokens, - &mut tv.output, - TokenTransfer::Transparent(&transfer), - None, - &HashMap::default(), - ) - .await; - make_ledger_token_transfer_endpoints( - &tokens, - &mut tv.output_expert, - TokenTransfer::Transparent(&transfer), - None, - &HashMap::default(), - ) - .await; - } else if code_sec.tag == Some(TX_SHIELDED_TRANSFER_WASM.to_string()) { - let transfer = token::ShieldedTransfer::try_from_slice( - &tx.data(cmt) - .ok_or_else(|| Error::Other("Invalid Data".to_string()))?, - ) - .map_err(|err| { - Error::from(EncodingError::Conversion(err.to_string())) - })?; - // To facilitate lookups of MASP AssetTypes - let mut asset_types = HashMap::new(); - let builder = tx.sections.iter().find_map(|x| match x { - Section::MaspBuilder(builder) - if builder.target == transfer.section_hash => - { - for decoded in &builder.asset_types { - match decoded.encode() { - Err(_) => None, - Ok(asset) => { - asset_types.insert(asset, decoded.clone()); - Some(builder) - } - }?; - } - Some(builder) - } - _ => None, - }); - - tv.name = "ShieldedTransfer_0".to_string(); - - tv.output.push("Type : ShieldedTransfer".to_string()); - make_ledger_token_transfer_endpoints( - &tokens, - &mut tv.output, - TokenTransfer::Shielded, - builder, - &asset_types, - ) - .await; - make_ledger_token_transfer_endpoints( - &tokens, - &mut tv.output_expert, - TokenTransfer::Shielded, - builder, - &asset_types, - ) - .await; - } else if code_sec.tag == Some(TX_SHIELDING_TRANSFER_WASM.to_string()) { - let transfer = token::ShieldingTransfer::try_from_slice( + } else if code_sec.tag == Some(TX_TRANSFER_WASM.to_string()) { + let transfer = token::Transfer::try_from_slice( &tx.data(cmt) .ok_or_else(|| Error::Other("Invalid Data".to_string()))?, ) @@ -1362,7 +1332,8 @@ pub async fn to_ledger_vector( let mut asset_types = HashMap::new(); let builder = tx.sections.iter().find_map(|x| match x { Section::MaspBuilder(builder) - if builder.target == transfer.shielded_section_hash => + if Some(builder.target) + == transfer.shielded_section_hash => { for decoded in &builder.asset_types { match decoded.encode() { @@ -1378,73 +1349,25 @@ pub async fn to_ledger_vector( _ => None, }); - tv.name = "ShieldingTransfer_0".to_string(); - - tv.output.push("Type : ShieldingTransfer".to_string()); - make_ledger_token_transfer_endpoints( - &tokens, - &mut tv.output, - TokenTransfer::Shielding(&transfer), - builder, - &asset_types, - ) - .await; - make_ledger_token_transfer_endpoints( - &tokens, - &mut tv.output_expert, - TokenTransfer::Shielding(&transfer), - builder, - &asset_types, - ) - .await; - } else if code_sec.tag == Some(TX_UNSHIELDING_TRANSFER_WASM.to_string()) - { - let transfer = token::UnshieldingTransfer::try_from_slice( - &tx.data(cmt) - .ok_or_else(|| Error::Other("Invalid Data".to_string()))?, - ) - .map_err(|err| { - Error::from(EncodingError::Conversion(err.to_string())) - })?; - // To facilitate lookups of MASP AssetTypes - let mut asset_types = HashMap::new(); - let builder = tx.sections.iter().find_map(|x| match x { - Section::MaspBuilder(builder) - if builder.target == transfer.shielded_section_hash => - { - for decoded in &builder.asset_types { - match decoded.encode() { - Err(_) => None, - Ok(asset) => { - asset_types.insert(asset, decoded.clone()); - Some(builder) - } - }?; - } - Some(builder) - } - _ => None, - }); - - tv.name = "UnshieldingTransfer_0".to_string(); + tv.name = "Transfer_0".to_string(); - tv.output.push("Type : UnshieldingTransfer".to_string()); + tv.output.push("Type : Transfer".to_string()); make_ledger_token_transfer_endpoints( &tokens, &mut tv.output, - TokenTransfer::Unshielding(&transfer), + TokenTransfer(&transfer), builder, &asset_types, ) - .await; + .await?; make_ledger_token_transfer_endpoints( &tokens, &mut tv.output_expert, - TokenTransfer::Unshielding(&transfer), + TokenTransfer(&transfer), builder, &asset_types, ) - .await; + .await?; } else if code_sec.tag == Some(TX_IBC_WASM.to_string()) { let any_msg = Any::decode( tx.data(cmt) diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index b9daabeca2..e851ec9de5 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -65,12 +65,17 @@ use namada_tx::data::{pos, BatchedTxResult, ResultCode, TxResult}; pub use namada_tx::{Authorization, *}; use num_traits::Zero; use rand_core::{OsRng, RngCore}; +use token::TransparentTransfer; +use crate::args::{ + TxShieldedTransferData, TxShieldingTransferData, TxTransparentTransferData, + TxUnshieldingTransferData, +}; use crate::control_flow::time; use crate::error::{EncodingError, Error, QueryError, Result, TxSubmitError}; use crate::io::Io; use crate::masp::TransferErr::Build; -use crate::masp::{ShieldedContext, ShieldedTransfer}; +use crate::masp::{MaspTransferData, ShieldedContext, ShieldedTransfer}; use crate::queries::Client; use crate::rpc::{ self, get_validator_stake, query_wasm_code_hash, validate_amount, @@ -103,13 +108,7 @@ pub const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; /// Update validity predicate WASM path pub const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; /// Transparent transfer transaction WASM path -pub const TX_TRANSPARENT_TRANSFER_WASM: &str = "tx_transparent_transfer.wasm"; -/// Shielded transfer transaction WASM path -pub const TX_SHIELDED_TRANSFER_WASM: &str = "tx_shielded_transfer.wasm"; -/// Shielding transfer transaction WASM path -pub const TX_SHIELDING_TRANSFER_WASM: &str = "tx_shielding_transfer.wasm"; -/// Unshielding transfer transaction WASM path -pub const TX_UNSHIELDING_TRANSFER_WASM: &str = "tx_unshielding_transfer.wasm"; +pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; /// IBC transaction WASM path pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; /// User validity predicate WASM path @@ -2513,15 +2512,19 @@ pub async fn build_ibc_transfer( query_wasm_code_hash(context, args.tx_code_path.to_str().unwrap()) .await .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; + let masp_transfer_data = MaspTransferData { + source: args.source.clone(), + target: TransferTarget::Address(Address::Internal( + InternalAddress::Ibc, + )), + token: args.token.clone(), + amount: validated_amount, + }; // For transfer from a spending key let shielded_parts = construct_shielded_parts( context, - &args.source, - // The token will be escrowed to IBC address - &TransferTarget::Address(Address::Internal(InternalAddress::Ibc)), - &args.token, - validated_amount, + vec![masp_transfer_data], !(args.tx.dry_run || args.tx.dry_run_wrapper), ) .await?; @@ -2567,13 +2570,16 @@ pub async fn build_ibc_transfer( let transfer = shielded_parts.map(|(shielded_transfer, asset_types)| { let masp_tx_hash = tx.add_masp_tx_section(shielded_transfer.masp_tx.clone()).1; - let transfer = token::ShieldingTransfer { - // The token will be escrowed to IBC address - source: source.clone(), - token: args.token.clone(), - amount: validated_amount, + let transfer = token::Transfer { + transparent: vec![TransparentTransfer { + // The token will be escrowed to IBC address + source: source.clone(), + target: MASP, + token: args.token.clone(), + amount: validated_amount, + }], // Link the Transfer to the MASP Transaction by hash code - shielded_section_hash: masp_tx_hash, + shielded_section_hash: Some(masp_tx_hash), }; tx.add_masp_builder(MaspBuilder { asset_types, @@ -2831,63 +2837,95 @@ pub async fn build_transparent_transfer( context: &N, args: &mut args::TxTransparentTransfer, ) -> Result<(Tx, SigningTxData)> { - let source = &args.source; - let target = &args.target; + let mut transfers = vec![]; + + // Evaluate signer and fees + let (signing_data, fee_amount, updated_balance) = { + let source = if args.data.len() == 1 { + // If only one transfer take its source as the signer + args.data + .first() + .map(|transfer_data| transfer_data.source.clone()) + } else { + // Otherwise the caller is required to pass the public keys in the + // argument + None + }; - let default_signer = Some(source.clone()); - let signing_data = signing::aux_signing_data( - context, - &args.tx, - Some(source.clone()), - default_signer, - ) - .await?; + let signing_data = signing::aux_signing_data( + context, + &args.tx, + source.clone(), + source, + ) + .await?; - // Transparent fee payment - let (fee_amount, updated_balance) = - validate_transparent_fee(context, &args.tx, &signing_data.fee_payer) - .await - .map(|(fee_amount, updated_balance)| { - (fee_amount, Some(updated_balance)) - })?; + // Transparent fee payment + let (fee_amount, updated_balance) = validate_transparent_fee( + context, + &args.tx, + &signing_data.fee_payer, + ) + .await + .map(|(fee_amount, updated_balance)| { + (fee_amount, Some(updated_balance)) + })?; - // Check that the source address exists on chain - source_exists_or_err(source.clone(), args.tx.force, context).await?; - // Check that the target address exists on chain - target_exists_or_err(target.clone(), args.tx.force, context).await?; + (signing_data, fee_amount, updated_balance) + }; - // Validate the amount given - let validated_amount = - validate_amount(context, args.amount, &args.token, args.tx.force) + for TxTransparentTransferData { + source, + target, + token, + amount, + } in &args.data + { + // Check that the source address exists on chain + source_exists_or_err(source.clone(), args.tx.force, context).await?; + // Check that the target address exists on chain + target_exists_or_err(target.clone(), args.tx.force, context).await?; + + // Validate the amount given + let validated_amount = + validate_amount(context, amount.to_owned(), token, args.tx.force) + .await?; + + // Check the balance of the source + if let Some(updated_balance) = &updated_balance { + let check_balance = if &updated_balance.source == source + && &updated_balance.token == token + { + CheckBalance::Balance(updated_balance.post_balance) + } else { + CheckBalance::Query(balance_key(token, source)) + }; + + check_balance_too_low_err( + token, + source, + validated_amount.amount(), + check_balance, + args.tx.force, + context, + ) .await?; + } - // Check the balance of the source - if let Some(updated_balance) = updated_balance { - let check_balance = if &updated_balance.source == source - && updated_balance.token == args.token - { - CheckBalance::Balance(updated_balance.post_balance) - } else { - CheckBalance::Query(balance_key(&args.token, source)) + // Construct the corresponding transparent Transfer object + let transfer_data = token::TransparentTransfer { + source: source.to_owned(), + target: target.to_owned(), + token: token.to_owned(), + amount: validated_amount, }; - check_balance_too_low_err( - &args.token, - source, - validated_amount.amount(), - check_balance, - args.tx.force, - context, - ) - .await?; + transfers.push(transfer_data); } - // Construct the corresponding transparent Transfer object - let transfer = token::TransparentTransfer { - source: source.clone(), - target: target.clone(), - token: args.token.clone(), - amount: validated_amount, + let transfer = token::Transfer { + transparent: transfers, + shielded_section_hash: None, }; let tx = build_pow_flag( @@ -2908,69 +2946,77 @@ pub async fn build_shielded_transfer( context: &N, args: &mut args::TxShieldedTransfer, ) -> Result<(Tx, SigningTxData)> { - let default_signer = Some(MASP); - let signing_data = signing::aux_signing_data( - context, - &args.tx, - Some(MASP), - default_signer, - ) - .await?; + let signing_data = + signing::aux_signing_data(context, &args.tx, Some(MASP), Some(MASP)) + .await?; // Shielded fee payment let fee_amount = validate_fee(context, &args.tx).await?; - // Validate the amount given - let validated_amount = - validate_amount(context, args.amount, &args.token, args.tx.force) - .await?; + let mut transfer_data = vec![]; + for TxShieldedTransferData { + source, + target, + token, + amount, + } in &args.data + { + // Validate the amount given + let validated_amount = + validate_amount(context, amount.to_owned(), token, args.tx.force) + .await?; + + transfer_data.push(MaspTransferData { + source: TransferSource::ExtendedSpendingKey(source.to_owned()), + target: TransferTarget::PaymentAddress(target.to_owned()), + token: token.to_owned(), + amount: validated_amount, + }); + } // TODO(namada#2597): this function should also take another arg as the fees // token and amount let shielded_parts = construct_shielded_parts( context, - &TransferSource::ExtendedSpendingKey(args.source), - &TransferTarget::PaymentAddress(args.target), - &args.token, - validated_amount, + transfer_data, !(args.tx.dry_run || args.tx.dry_run_wrapper), ) .await? .expect("Shielded transfer must have shielded parts"); - let add_shielded_parts = - |tx: &mut Tx, data: &mut token::ShieldedTransfer| { - // Add the MASP Transaction and its Builder to facilitate validation - let ( - ShieldedTransfer { - builder, - masp_tx, - metadata, - epoch: _, - }, - asset_types, - ) = shielded_parts; - // Add a MASP Transaction section to the Tx and get the tx hash - let section_hash = tx.add_masp_tx_section(masp_tx).1; - - tx.add_masp_builder(MaspBuilder { - asset_types, - // Store how the Info objects map to Descriptors/Outputs - metadata, - // Store the data that was used to construct the Transaction + let add_shielded_parts = |tx: &mut Tx, data: &mut token::Transfer| { + // Add the MASP Transaction and its Builder to facilitate validation + let ( + ShieldedTransfer { builder, - // Link the Builder to the Transaction by hash code - target: section_hash, - }); + masp_tx, + metadata, + epoch: _, + }, + asset_types, + ) = shielded_parts; + // Add a MASP Transaction section to the Tx and get the tx hash + let section_hash = tx.add_masp_tx_section(masp_tx).1; - data.section_hash = section_hash; - tracing::debug!("Transfer data {data:?}"); - Ok(()) - }; + tx.add_masp_builder(MaspBuilder { + asset_types, + // Store how the Info objects map to Descriptors/Outputs + metadata, + // Store the data that was used to construct the Transaction + builder, + // Link the Builder to the Transaction by hash code + target: section_hash, + }); + + data.shielded_section_hash = Some(section_hash); + tracing::debug!("Transfer data {data:?}"); + Ok(()) + }; // Construct the tx data with a placeholder shielded section hash - let data = token::ShieldedTransfer { - section_hash: Hash::zero(), + let data = token::Transfer { + transparent: vec![], + shielded_section_hash: None, }; let tx = build_pow_flag( context, @@ -2990,15 +3036,19 @@ pub async fn build_shielding_transfer( context: &N, args: &mut args::TxShieldingTransfer, ) -> Result<(Tx, SigningTxData, MaspEpoch)> { - let source = &args.source; - let default_signer = Some(source.clone()); - let signing_data = signing::aux_signing_data( - context, - &args.tx, - Some(source.clone()), - default_signer, - ) - .await?; + let source = if args.data.len() == 1 { + // If only one transfer take its source as the signer + args.data + .first() + .map(|transfer_data| transfer_data.source.clone()) + } else { + // Otherwise the caller is required to pass the public keys in the + // argument + None + }; + let signing_data = + signing::aux_signing_data(context, &args.tx, source.clone(), source) + .await?; // Transparent fee payment let (fee_amount, updated_balance) = @@ -3008,80 +3058,97 @@ pub async fn build_shielding_transfer( (fee_amount, Some(updated_balance)) })?; - // Validate the amount given - let validated_amount = - validate_amount(context, args.amount, &args.token, args.tx.force) + let mut transfer_data = vec![]; + let mut data = vec![]; + for TxShieldingTransferData { + source, + token, + amount, + } in &args.data + { + // Validate the amount given + let validated_amount = + validate_amount(context, amount.to_owned(), token, args.tx.force) + .await?; + + // Check the balance of the source + if let Some(updated_balance) = &updated_balance { + let check_balance = if &updated_balance.source == source + && &updated_balance.token == token + { + CheckBalance::Balance(updated_balance.post_balance) + } else { + CheckBalance::Query(balance_key(token, source)) + }; + + check_balance_too_low_err( + token, + source, + validated_amount.amount(), + check_balance, + args.tx.force, + context, + ) .await?; + } - // Check the balance of the source - if let Some(updated_balance) = updated_balance { - let check_balance = if &updated_balance.source == source - && updated_balance.token == args.token - { - CheckBalance::Balance(updated_balance.post_balance) - } else { - CheckBalance::Query(balance_key(&args.token, source)) - }; + transfer_data.push(MaspTransferData { + source: TransferSource::Address(source.to_owned()), + target: TransferTarget::PaymentAddress(args.target), + token: token.to_owned(), + amount: validated_amount, + }); - check_balance_too_low_err( - &args.token, - source, - validated_amount.amount(), - check_balance, - args.tx.force, - context, - ) - .await?; + data.push(token::TransparentTransfer { + source: source.to_owned(), + target: MASP, + token: token.to_owned(), + amount: validated_amount, + }); } let shielded_parts = construct_shielded_parts( context, - &TransferSource::Address(source.clone()), - &TransferTarget::PaymentAddress(args.target), - &args.token, - validated_amount, + transfer_data, !(args.tx.dry_run || args.tx.dry_run_wrapper), ) .await? .expect("Shielding transfer must have shielded parts"); let shielded_tx_epoch = shielded_parts.0.epoch; - let add_shielded_parts = - |tx: &mut Tx, data: &mut token::ShieldingTransfer| { - // Add the MASP Transaction and its Builder to facilitate validation - let ( - ShieldedTransfer { - builder, - masp_tx, - metadata, - epoch: _, - }, - asset_types, - ) = shielded_parts; - // Add a MASP Transaction section to the Tx and get the tx hash - let shielded_section_hash = tx.add_masp_tx_section(masp_tx).1; - - tx.add_masp_builder(MaspBuilder { - asset_types, - // Store how the Info objects map to Descriptors/Outputs - metadata, - // Store the data that was used to construct the Transaction + let add_shielded_parts = |tx: &mut Tx, data: &mut token::Transfer| { + // Add the MASP Transaction and its Builder to facilitate validation + let ( + ShieldedTransfer { builder, - // Link the Builder to the Transaction by hash code - target: shielded_section_hash, - }); + masp_tx, + metadata, + epoch: _, + }, + asset_types, + ) = shielded_parts; + // Add a MASP Transaction section to the Tx and get the tx hash + let shielded_section_hash = tx.add_masp_tx_section(masp_tx).1; - data.shielded_section_hash = shielded_section_hash; - tracing::debug!("Transfer data {data:?}"); - Ok(()) - }; + tx.add_masp_builder(MaspBuilder { + asset_types, + // Store how the Info objects map to Descriptors/Outputs + metadata, + // Store the data that was used to construct the Transaction + builder, + // Link the Builder to the Transaction by hash code + target: shielded_section_hash, + }); + + data.shielded_section_hash = Some(shielded_section_hash); + tracing::debug!("Transfer data {data:?}"); + Ok(()) + }; // Construct the tx data with a placeholder shielded section hash - let data = token::ShieldingTransfer { - source: source.clone(), - token: args.token.clone(), - amount: validated_amount, - shielded_section_hash: Hash::zero(), + let data = token::Transfer { + transparent: data, + shielded_section_hash: None, }; let tx = build_pow_flag( @@ -3102,73 +3169,86 @@ pub async fn build_unshielding_transfer( context: &N, args: &mut args::TxUnshieldingTransfer, ) -> Result<(Tx, SigningTxData)> { - let default_signer = Some(MASP); - let signing_data = signing::aux_signing_data( - context, - &args.tx, - Some(MASP), - default_signer, - ) - .await?; + let signing_data = + signing::aux_signing_data(context, &args.tx, Some(MASP), Some(MASP)) + .await?; // Shielded fee payment let fee_amount = validate_fee(context, &args.tx).await?; - // Validate the amount given - let validated_amount = - validate_amount(context, args.amount, &args.token, args.tx.force) - .await?; + let mut transfer_data = vec![]; + let mut data = vec![]; + for TxUnshieldingTransferData { + target, + token, + amount, + } in &args.data + { + // Validate the amount given + let validated_amount = + validate_amount(context, amount.to_owned(), token, args.tx.force) + .await?; + + transfer_data.push(MaspTransferData { + source: TransferSource::ExtendedSpendingKey(args.source), + target: TransferTarget::Address(target.to_owned()), + token: token.to_owned(), + amount: validated_amount, + }); + + data.push(token::TransparentTransfer { + source: MASP, + target: target.to_owned(), + token: token.to_owned(), + amount: validated_amount, + }); + } // TODO(namada#2597): this function should also take another arg as the fees // token and amount let shielded_parts = construct_shielded_parts( context, - &TransferSource::ExtendedSpendingKey(args.source), - &TransferTarget::Address(args.target.clone()), - &args.token, - validated_amount, + transfer_data, !(args.tx.dry_run || args.tx.dry_run_wrapper), ) .await? .expect("Shielding transfer must have shielded parts"); - let add_shielded_parts = - |tx: &mut Tx, data: &mut token::UnshieldingTransfer| { - // Add the MASP Transaction and its Builder to facilitate validation - let ( - ShieldedTransfer { - builder, - masp_tx, - metadata, - epoch: _, - }, - asset_types, - ) = shielded_parts; - // Add a MASP Transaction section to the Tx and get the tx hash - let shielded_section_hash = tx.add_masp_tx_section(masp_tx).1; - - tx.add_masp_builder(MaspBuilder { - asset_types, - // Store how the Info objects map to Descriptors/Outputs - metadata, - // Store the data that was used to construct the Transaction + let add_shielded_parts = |tx: &mut Tx, data: &mut token::Transfer| { + // Add the MASP Transaction and its Builder to facilitate validation + let ( + ShieldedTransfer { builder, - // Link the Builder to the Transaction by hash code - target: shielded_section_hash, - }); + masp_tx, + metadata, + epoch: _, + }, + asset_types, + ) = shielded_parts; + // Add a MASP Transaction section to the Tx and get the tx hash + let shielded_section_hash = tx.add_masp_tx_section(masp_tx).1; - data.shielded_section_hash = shielded_section_hash; - tracing::debug!("Transfer data {data:?}"); - Ok(()) - }; + tx.add_masp_builder(MaspBuilder { + asset_types, + // Store how the Info objects map to Descriptors/Outputs + metadata, + // Store the data that was used to construct the Transaction + builder, + // Link the Builder to the Transaction by hash code + target: shielded_section_hash, + }); + + data.shielded_section_hash = Some(shielded_section_hash); + tracing::debug!("Transfer data {data:?}"); + Ok(()) + }; // Construct the tx data with a placeholder shielded section hash - let data = token::UnshieldingTransfer { - target: args.target.clone(), - token: args.token.clone(), - amount: validated_amount, - shielded_section_hash: Hash::zero(), + let data = token::Transfer { + transparent: data, + shielded_section_hash: None, }; + let tx = build_pow_flag( context, &args.tx, @@ -3185,10 +3265,7 @@ pub async fn build_unshielding_transfer( // Construct the shielded part of the transaction, if any async fn construct_shielded_parts( context: &N, - source: &TransferSource, - target: &TransferTarget, - token: &Address, - amount: token::DenominatedAmount, + data: Vec, update_ctx: bool, ) -> Result)>> { // Precompute asset types to increase chances of success in decoding @@ -3201,14 +3278,23 @@ async fn construct_shielded_parts( .await; let stx_result = ShieldedContext::::gen_shielded_transfer( - context, source, target, token, amount, update_ctx, + context, data, update_ctx, ) .await; let shielded_parts = match stx_result { Ok(Some(stx)) => stx, Ok(None) => return Ok(None), - Err(Build(builder::Error::InsufficientFunds(_))) => { + Err(Build { + error: builder::Error::InsufficientFunds(_), + data, + }) => { + let MaspTransferData { + source, + token, + amount, + .. + } = data.unwrap(); return Err(TxSubmitError::NegativeBalanceAfterTransfer( Box::new(source.effective_address()), amount.to_string(), @@ -3486,7 +3572,7 @@ pub async fn build_custom( pub async fn gen_ibc_shielding_transfer( context: &N, args: args::GenIbcShieldedTransfer, -) -> Result> { +) -> Result> { let source = Address::Internal(InternalAddress::Ibc); let (src_port_id, src_channel_id) = get_ibc_src_port_channel(context, &args.port_id, &args.channel_id) @@ -3526,13 +3612,16 @@ pub async fn gen_ibc_shielding_transfer( .precompute_asset_types(context.client(), tokens) .await; + let masp_transfer_data = MaspTransferData { + source: TransferSource::Address(source.clone()), + target: args.target, + token: token.clone(), + amount: validated_amount, + }; let shielded_transfer = ShieldedContext::::gen_shielded_transfer( context, - &TransferSource::Address(source.clone()), - &args.target, - &token, - validated_amount, + vec![masp_transfer_data], true, ) .await @@ -3541,11 +3630,14 @@ pub async fn gen_ibc_shielding_transfer( if let Some(shielded_transfer) = shielded_transfer { let masp_tx_hash = Section::MaspTx(shielded_transfer.masp_tx.clone()).get_hash(); - let transfer = token::ShieldingTransfer { - source: source.clone(), - token: token.clone(), - amount: validated_amount, - shielded_section_hash: masp_tx_hash, + let transfer = token::Transfer { + transparent: vec![TransparentTransfer { + source: source.clone(), + target: MASP, + token: token.clone(), + amount: validated_amount, + }], + shielded_section_hash: Some(masp_tx_hash), }; Ok(Some((transfer, shielded_transfer.masp_tx))) } else { diff --git a/crates/tests/src/integration/ledger_tests.rs b/crates/tests/src/integration/ledger_tests.rs index 214ab32a1d..1e2423c1cb 100644 --- a/crates/tests/src/integration/ledger_tests.rs +++ b/crates/tests/src/integration/ledger_tests.rs @@ -19,7 +19,7 @@ use namada_node::shell::testing::node::NodeResults; use namada_node::shell::testing::utils::{Bin, CapturedOutput}; use namada_sdk::migrations; use namada_sdk::queries::RPC; -use namada_sdk::tx::{TX_TRANSPARENT_TRANSFER_WASM, VP_USER_WASM}; +use namada_sdk::tx::{TX_TRANSFER_WASM, VP_USER_WASM}; use namada_test_utils::TestWasms; use test_log::test; @@ -59,16 +59,17 @@ fn ledger_txs_and_queries() -> Result<()> { let validator_one_rpc = "http://127.0.0.1:26567"; let (node, _services) = setup::setup()?; - let transfer = token::TransparentTransfer { - source: defaults::bertha_address(), - target: defaults::albert_address(), - token: node.native_token(), - amount: token::DenominatedAmount::new( - token::Amount::native_whole(10), - token::NATIVE_MAX_DECIMAL_PLACES.into(), - ), - } - .serialize_to_vec(); + let transfer = + token::Transfer::transparent(vec![token::TransparentTransfer { + source: defaults::bertha_address(), + target: defaults::albert_address(), + token: node.native_token(), + amount: token::DenominatedAmount::new( + token::Amount::native_whole(10), + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ), + }]) + .serialize_to_vec(); let tx_data_path = node.test_dir.path().join("tx.data"); std::fs::write(&tx_data_path, transfer).unwrap(); let tx_data_path = tx_data_path.to_string_lossy(); @@ -140,7 +141,7 @@ fn ledger_txs_and_queries() -> Result<()> { vec![ "tx", "--code-path", - TX_TRANSPARENT_TRANSFER_WASM, + TX_TRANSFER_WASM, "--data-path", &tx_data_path, "--owner", diff --git a/crates/token/src/lib.rs b/crates/token/src/lib.rs index c14d93f430..c7a5ac7d7d 100644 --- a/crates/token/src/lib.rs +++ b/crates/token/src/lib.rs @@ -67,7 +67,7 @@ where Ok(()) } -/// Arguments for a transparent token transfer +/// Arguments for a multi-party transparent token transfer #[derive( Debug, Clone, @@ -82,39 +82,24 @@ where Serialize, Deserialize, )] -pub struct TransparentTransfer { - /// Source address will spend the tokens - pub source: Address, - /// Target address will receive the tokens - pub target: Address, - /// Token's address - pub token: Address, - /// The amount of tokens - pub amount: DenominatedAmount, +pub struct Transfer { + /// Transfer-specific data + pub transparent: Vec, + /// Hash of tx section that contains the MASP transaction + pub shielded_section_hash: Option, } -/// Arguments for a shielded token transfer -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshDeserializer, - BorshSchema, - Hash, - Eq, - PartialOrd, - Serialize, - Deserialize, -)] -pub struct ShieldedTransfer { - /// Hash of tx section that contains the MASP transaction - pub section_hash: Hash, +impl Transfer { + /// Make a transparent transfer + pub fn transparent(data: Vec) -> Self { + Self { + transparent: data, + shielded_section_hash: None, + } + } } -/// Arguments for a shielding transfer (from a transparent token to a shielded -/// token) +/// Arguments for a transparent token transfer #[derive( Debug, Clone, @@ -129,42 +114,15 @@ pub struct ShieldedTransfer { Serialize, Deserialize, )] -pub struct ShieldingTransfer { +pub struct TransparentTransfer { /// Source address will spend the tokens pub source: Address, - /// Token's address - pub token: Address, - /// The amount of tokens - pub amount: DenominatedAmount, - /// Hash of tx section that contains the MASP transaction - pub shielded_section_hash: Hash, -} - -/// Arguments for an unshielding transfer (from a shielded token to a -/// transparent token) -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshDeserializer, - BorshSchema, - Hash, - Eq, - PartialOrd, - Serialize, - Deserialize, -)] -pub struct UnshieldingTransfer { /// Target address will receive the tokens pub target: Address, /// Token's address pub token: Address, /// The amount of tokens pub amount: DenominatedAmount, - /// Hash of tx section that contains the MASP transaction - pub shielded_section_hash: Hash, } #[cfg(any(test, feature = "testing"))] @@ -178,16 +136,16 @@ pub mod testing { pub use namada_trans_token::testing::*; use proptest::prelude::*; - use super::TransparentTransfer; + use super::{Transfer, TransparentTransfer}; prop_compose! { /// Generate a transparent transfer - pub fn arb_transparent_transfer()( + fn arb_transparent_transfer()( source in arb_non_internal_address(), target in arb_non_internal_address(), token in arb_established_address().prop_map(Address::Established), amount in arb_denominated_amount(), - ) -> TransparentTransfer { + ) -> TransparentTransfer{ TransparentTransfer { source, target, @@ -196,4 +154,15 @@ pub mod testing { } } } + + /// Generate a vectorized transparent transfer + pub fn arb_vectorized_transparent_transfer( + number_of_txs: usize, + ) -> impl Strategy { + proptest::collection::vec(arb_transparent_transfer(), 0..number_of_txs) + .prop_map(|data| Transfer { + transparent: data, + shielded_section_hash: None, + }) + } } diff --git a/crates/tx_prelude/src/token.rs b/crates/tx_prelude/src/token.rs index ab9ecaf571..99af630dbb 100644 --- a/crates/tx_prelude/src/token.rs +++ b/crates/tx_prelude/src/token.rs @@ -5,8 +5,7 @@ use namada_events::{EmitEvents, EventLevel}; #[cfg(any(test, feature = "testing"))] pub use namada_token::testing; pub use namada_token::{ - storage_key, utils, Amount, DenominatedAmount, ShieldedTransfer, - ShieldingTransfer, TransparentTransfer, UnshieldingTransfer, + storage_key, utils, Amount, DenominatedAmount, Transfer, }; use namada_tx_env::TxEnv; diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 7c491ea3d2..bc3953440f 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -6889,25 +6889,7 @@ dependencies = [ ] [[package]] -name = "tx_shielded_transfer" -version = "0.39.0" -dependencies = [ - "getrandom 0.2.15", - "namada_tx_prelude", - "rlsf", -] - -[[package]] -name = "tx_shielding_transfer" -version = "0.39.0" -dependencies = [ - "getrandom 0.2.15", - "namada_tx_prelude", - "rlsf", -] - -[[package]] -name = "tx_transparent_transfer" +name = "tx_transfer" version = "0.39.0" dependencies = [ "getrandom 0.2.15", @@ -6941,15 +6923,6 @@ dependencies = [ "rlsf", ] -[[package]] -name = "tx_unshielding_transfer" -version = "0.39.0" -dependencies = [ - "getrandom 0.2.15", - "namada_tx_prelude", - "rlsf", -] - [[package]] name = "tx_update_account" version = "0.39.0" diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index b0212b5def..f9b0737140 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -17,12 +17,9 @@ members = [ "tx_redelegate", "tx_resign_steward", "tx_reveal_pk", - "tx_shielded_transfer", - "tx_shielding_transfer", - "tx_transparent_transfer", + "tx_transfer", "tx_unbond", "tx_unjail_validator", - "tx_unshielding_transfer", "tx_update_account", "tx_update_steward_commission", "tx_vote_proposal", diff --git a/wasm/checksums.json b/wasm/checksums.json index ae9470452a..f73d7a0fd7 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,29 +1,26 @@ { - "tx_become_validator.wasm": "tx_become_validator.9499cd491944c2a1eca4544d529c2ac133b504c4ac10b121d30d092b13fb486f.wasm", - "tx_bond.wasm": "tx_bond.732f85dd8ef096a1e358296287f2457f362663e0f23f68333fa209d8d97ae494.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.e940f9695742c8967811115bc28f9eb4458d500a8522657174997d4c9d946198.wasm", - "tx_change_consensus_key.wasm": "tx_change_consensus_key.b5bf392325e332f19901eb9237cb32766cbee0e64c9cc47d65dd62882f716337.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.243491629bc622b6ca7a0e9179b635f1d269b58a3597121fab2e3d135749b016.wasm", - "tx_change_validator_metadata.wasm": "tx_change_validator_metadata.0e7d7586c52c29c51256020058a93665203af00da985371a9df1702f208d9cd1.wasm", - "tx_claim_rewards.wasm": "tx_claim_rewards.ae5b42e465e46c68c5f185321a34dceeffa8e56b0994ea680b6cb70a8880aed5.wasm", - "tx_deactivate_validator.wasm": "tx_deactivate_validator.fe46741d2440a2a5b9e01ece72a0938e6e1727ce97a2753951aac101fc667d88.wasm", - "tx_ibc.wasm": "tx_ibc.c630d1b1ca76a6c877d0d0fb7c91ebeed99f263cec1c9cd37ce9fce1513ba347.wasm", - "tx_init_account.wasm": "tx_init_account.afe7d7be1b9781f5fa66855b8195bc2bea897c9ee20d729d0728145716da5305.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.10bfbf2c4d87db3d1ae3c319f140db85f1ae44ea54c92aa3ece4b9bf8dbdefa9.wasm", - "tx_reactivate_validator.wasm": "tx_reactivate_validator.a71e116b6ab4ac37940dd32361e922ed43e8d0b9bb9129f4fd0e89dae8f3c902.wasm", - "tx_redelegate.wasm": "tx_redelegate.bcd3f7332de530df6407a6d6cb77046d43904609bea467065028c45f50e775ef.wasm", - "tx_resign_steward.wasm": "tx_resign_steward.d60c11ddfef084421ed46444f1c0165125e29ac2305cc8d82b6cbc579a03803d.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.9cc8f2fec826ddf606b1f0244ce485f63a8928adf18174f55a656db6423cbf73.wasm", - "tx_shielded_transfer.wasm": "tx_shielded_transfer.f77c6fc4bcf121977e00a4ebc24b5c60fbb1a182062b51ec96df21701cee5ffc.wasm", - "tx_shielding_transfer.wasm": "tx_shielding_transfer.0bcb41dbc0bceafccc65bc5262510a40ee0c0450c0c08303b676b37de6514647.wasm", - "tx_transparent_transfer.wasm": "tx_transparent_transfer.cf989d4d445340e7c98e446f414a4c7c9d353638a34b40198b2b63a05fbbf69a.wasm", - "tx_unbond.wasm": "tx_unbond.16130834f9cbb300e50ce5405b3531618c733156430fa6788a086fb303eb61c9.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.088c40fa51dc5cbab896e5464daa71bb205e999ba2aa24e8775a63c2ebf10084.wasm", - "tx_unshielding_transfer.wasm": "tx_unshielding_transfer.7a7e3e048ff135642a6694437cc6e0a4c7c6943e9c149a4f81363f063fe28dbf.wasm", - "tx_update_account.wasm": "tx_update_account.bcd9bc9f531015719cfe1009af1c5bcfd6567cea55f4cec053bd45c96a2143df.wasm", - "tx_update_steward_commission.wasm": "tx_update_steward_commission.ee6739d438764e8aaaa91cace5ca7fa70b06a4cd4d021f3764fa28d94bd0d202.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.45cda2bb54d6101206297be8e97a437806a24fb47fe7f91b61f05dbe8314bb53.wasm", - "tx_withdraw.wasm": "tx_withdraw.bca31434c620eee98fc1394a9dfb071033e87830152f0585ec82cc9c68ad310a.wasm", - "vp_implicit.wasm": "vp_implicit.1d6894f6b285a65561cf5ff0599deb5b5e7ac6227a074bf571cba28f63b4813f.wasm", - "vp_user.wasm": "vp_user.3e7c5f520227e3b49b8a66218ef9ce18976400d70a6b36e0878638ed9912e147.wasm" + "tx_become_validator.wasm": "tx_become_validator.76e5ddf2b1ec805df627d220a472cf691e54fd56206910deb44a84e881e6f872.wasm", + "tx_bond.wasm": "tx_bond.277546629805e35f60fe1e4f208d36f488906c75a008217dd301b9e11fb17de5.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.b853589ea716d1865b6dba2788c70e120b8ce9711b17895593cd2c1b57bd2b57.wasm", + "tx_change_consensus_key.wasm": "tx_change_consensus_key.edf0b19a0d851d75dcd39c498c352f30157b7de7fcfeb189d111ceddc10ec930.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.c62598cd97bebec856a967228e78df455cd620818e16ad2617de22e1073c4b05.wasm", + "tx_change_validator_metadata.wasm": "tx_change_validator_metadata.38d2819cdb4fe80a832c163c5b316c9e74c564deba9732ed7452cb2b3bef42c2.wasm", + "tx_claim_rewards.wasm": "tx_claim_rewards.1fd1118484a918947a005442b5dcac77556c538533982ccbcba9d7b02de533b0.wasm", + "tx_deactivate_validator.wasm": "tx_deactivate_validator.9dacf4d4aef2bc4d5ee8fce78f73544783376a974de686adc87ff0957e5a46fb.wasm", + "tx_ibc.wasm": "tx_ibc.dc05de488be5664844c93fad966000190c2b0847d4b5c20834c165a56ee716a7.wasm", + "tx_init_account.wasm": "tx_init_account.d2f3a2c059e0f92d683471df83afb4cd7e143e4a0428893cf7e6155382d8270d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.04cb9b3468e6ffc7604231acb1668ff92e0c28a06354c7ad26698fb549a7aab5.wasm", + "tx_reactivate_validator.wasm": "tx_reactivate_validator.0c52fcf238c1b9be9455891744f5f495b4c78c26bbe20fad5eddbb28b6176792.wasm", + "tx_redelegate.wasm": "tx_redelegate.c584955237fb27964fc94c1aaa2c04eb9190894f6c24b4634e0b07cee511cce8.wasm", + "tx_resign_steward.wasm": "tx_resign_steward.c3f8a4465aad3fb674dbd80c2c994e1881a25315215b615dcd570885271c481b.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.79455aadb09effe70b407a88dcb50552344e32d9b9e30897b7e0e24ce34e76a2.wasm", + "tx_transfer.wasm": "tx_transfer.a3fd5a7966b2a56790b7e8ca3578e492e8ae2ef03dadd090ec0e82f521f5c493.wasm", + "tx_unbond.wasm": "tx_unbond.3d69f98b7832de1d63cdff4e2dec3eb8ba6da44104af00ed0607f040c300d52c.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.d736868736feccea85d7bb8a5bd5cc67c19315521f608838ad60befbfc9e7738.wasm", + "tx_update_account.wasm": "tx_update_account.3daaa7d67b1545a161a65efb99fa930a09f3ecdc87e3f0a6e965d49756912b6c.wasm", + "tx_update_steward_commission.wasm": "tx_update_steward_commission.a62fcce86de9723ceff0f838d106be6f912d06f48cf10a3163dbb9ef53620c96.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.724a81d2cc2a0e491381d98d22753f46003f781f09f968bff29ad57bddd6f5f8.wasm", + "tx_withdraw.wasm": "tx_withdraw.1c6766e0b777b961700c0c6542dcf88bef7650924ba7b11c20a0c7caeae65f50.wasm", + "vp_implicit.wasm": "vp_implicit.13cdf1daf217ae900e7ac2838d66a584a4aa8cd627e10ad99786acc3aedc3e30.wasm", + "vp_user.wasm": "vp_user.f1336b2653d225c95a15d7be9971eff708196386eb83373bcc13855c0379da85.wasm" } \ No newline at end of file diff --git a/wasm/tx_ibc/src/lib.rs b/wasm/tx_ibc/src/lib.rs index e131a90c9a..fec8318ca9 100644 --- a/wasm/tx_ibc/src/lib.rs +++ b/wasm/tx_ibc/src/lib.rs @@ -13,7 +13,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { ibc::ibc_actions(ctx).execute(&data).into_storage_result()?; if let Some(masp_section_ref) = - transfer.map(|transfer| transfer.shielded_section_hash) + transfer.and_then(|transfer| transfer.shielded_section_hash) { let shielded = tx_data .tx diff --git a/wasm/tx_shielded_transfer/src/lib.rs b/wasm/tx_shielded_transfer/src/lib.rs deleted file mode 100644 index cc9e70a638..0000000000 --- a/wasm/tx_shielded_transfer/src/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! A tx for shielded token transfer. - -use namada_tx_prelude::action::{Action, MaspAction, Write}; -use namada_tx_prelude::*; - -#[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { - let data = ctx.get_tx_data(&tx_data)?; - let transfer = token::ShieldedTransfer::try_from_slice(&data[..]) - .wrap_err("Failed to decode token::ShieldedTransfer tx data")?; - debug_log!("apply_tx called with transfer: {:#?}", transfer); - - let masp_section_ref = transfer.section_hash; - let shielded = tx_data - .tx - .get_section(&masp_section_ref) - .and_then(|x| x.as_ref().masp_tx()) - .ok_or_err_msg("Unable to find required shielded section in tx data") - .map_err(|err| { - ctx.set_commitment_sentinel(); - err - })?; - token::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 { masp_section_ref }))?; - Ok(()) -} diff --git a/wasm/tx_shielding_transfer/Cargo.toml b/wasm/tx_shielding_transfer/Cargo.toml deleted file mode 100644 index 54dca625ec..0000000000 --- a/wasm/tx_shielding_transfer/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "tx_shielding_transfer" -description = "WASM transaction to transfer tokens" -authors.workspace = true -edition.workspace = true -license.workspace = true -version.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -namada_tx_prelude.workspace = true - -rlsf.workspace = true -getrandom.workspace = true - -[lib] -crate-type = ["cdylib"] diff --git a/wasm/tx_shielding_transfer/src/lib.rs b/wasm/tx_shielding_transfer/src/lib.rs deleted file mode 100644 index 389942686b..0000000000 --- a/wasm/tx_shielding_transfer/src/lib.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! A tx for shielding token transfer. - -use namada_tx_prelude::action::{Action, MaspAction, Write}; -use namada_tx_prelude::*; - -#[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { - let data = ctx.get_tx_data(&tx_data)?; - let transfer = token::ShieldingTransfer::try_from_slice(&data[..]) - .wrap_err("Failed to decode token::ShieldingTransfer tx data")?; - debug_log!("apply_tx called with transfer: {:#?}", transfer); - - token::transfer( - ctx, - &transfer.source, - &address::MASP, - &transfer.token, - transfer.amount.amount(), - ) - .wrap_err("Token transfer failed")?; - - let masp_section_ref = transfer.shielded_section_hash; - let shielded = tx_data - .tx - .get_section(&masp_section_ref) - .and_then(|x| x.as_ref().masp_tx()) - .ok_or_err_msg("Unable to find required shielded section in tx data") - .map_err(|err| { - ctx.set_commitment_sentinel(); - err - })?; - token::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 { masp_section_ref }))?; - - Ok(()) -} diff --git a/wasm/tx_shielded_transfer/Cargo.toml b/wasm/tx_transfer/Cargo.toml similarity index 92% rename from wasm/tx_shielded_transfer/Cargo.toml rename to wasm/tx_transfer/Cargo.toml index 8398b69750..868a5e6538 100644 --- a/wasm/tx_shielded_transfer/Cargo.toml +++ b/wasm/tx_transfer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tx_shielded_transfer" +name = "tx_transfer" description = "WASM transaction to transfer tokens" authors.workspace = true edition.workspace = true diff --git a/wasm/tx_transfer/src/lib.rs b/wasm/tx_transfer/src/lib.rs new file mode 100644 index 0000000000..b11e841574 --- /dev/null +++ b/wasm/tx_transfer/src/lib.rs @@ -0,0 +1,46 @@ +//! A tx for transparent token transfer. +//! This tx uses `token::TransparentTransfer` wrapped inside `SignedTxData` +//! as its input as declared in `namada` crate. + +use namada_tx_prelude::action::{Action, MaspAction, Write}; +use namada_tx_prelude::*; + +#[transaction] +fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { + let data = ctx.get_tx_data(&tx_data)?; + let transfers = token::Transfer::try_from_slice(&data[..]) + .wrap_err("Failed to decode token::TransparentTransfer tx data")?; + debug_log!("apply_tx called with transfer: {:#?}", transfers); + + for transfer in transfers.transparent { + token::transfer( + ctx, + &transfer.source, + &transfer.target, + &transfer.token, + transfer.amount.amount(), + ) + .wrap_err("Token transfer failed")?; + } + + if let Some(masp_section_ref) = transfers.shielded_section_hash { + let shielded = tx_data + .tx + .get_section(&masp_section_ref) + .and_then(|x| x.as_ref().masp_tx()) + .ok_or_err_msg( + "Unable to find required shielded section in tx data", + ) + .map_err(|err| { + ctx.set_commitment_sentinel(); + err + })?; + token::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 { masp_section_ref }))?; + } + + Ok(()) +} diff --git a/wasm/tx_transparent_transfer/Cargo.toml b/wasm/tx_transparent_transfer/Cargo.toml deleted file mode 100644 index 7408b86688..0000000000 --- a/wasm/tx_transparent_transfer/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "tx_transparent_transfer" -description = "WASM transaction to transfer tokens" -authors.workspace = true -edition.workspace = true -license.workspace = true -version.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -namada_tx_prelude.workspace = true - -rlsf.workspace = true -getrandom.workspace = true - -[lib] -crate-type = ["cdylib"] diff --git a/wasm/tx_transparent_transfer/src/lib.rs b/wasm/tx_transparent_transfer/src/lib.rs deleted file mode 100644 index 76aacc3891..0000000000 --- a/wasm/tx_transparent_transfer/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! A tx for transparent token transfer. -//! This tx uses `token::TransparentTransfer` wrapped inside `SignedTxData` -//! as its input as declared in `namada` crate. - -use namada_tx_prelude::*; - -#[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { - let data = ctx.get_tx_data(&tx_data)?; - let transfer = token::TransparentTransfer::try_from_slice(&data[..]) - .wrap_err("Failed to decode token::TransparentTransfer tx data")?; - debug_log!("apply_tx called with transfer: {:#?}", transfer); - - token::transfer( - ctx, - &transfer.source, - &transfer.target, - &transfer.token, - transfer.amount.amount(), - ) - .wrap_err("Token transfer failed") -} diff --git a/wasm/tx_unshielding_transfer/Cargo.toml b/wasm/tx_unshielding_transfer/Cargo.toml deleted file mode 100644 index 03d9aa125d..0000000000 --- a/wasm/tx_unshielding_transfer/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "tx_unshielding_transfer" -description = "WASM transaction to transfer tokens" -authors.workspace = true -edition.workspace = true -license.workspace = true -version.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -namada_tx_prelude.workspace = true - -rlsf.workspace = true -getrandom.workspace = true - -[lib] -crate-type = ["cdylib"] diff --git a/wasm/tx_unshielding_transfer/src/lib.rs b/wasm/tx_unshielding_transfer/src/lib.rs deleted file mode 100644 index 79bdac0757..0000000000 --- a/wasm/tx_unshielding_transfer/src/lib.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! A tx for unshielding token transfer. - -use namada_tx_prelude::action::{Action, MaspAction, Write}; -use namada_tx_prelude::*; - -#[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: BatchedTx) -> TxResult { - let data = ctx.get_tx_data(&tx_data)?; - let transfer = token::UnshieldingTransfer::try_from_slice(&data[..]) - .wrap_err("Failed to decode token::UnshieldingTransfer tx data")?; - debug_log!("apply_tx called with transfer: {:#?}", transfer); - - token::transfer( - ctx, - &address::MASP, - &transfer.target, - &transfer.token, - transfer.amount.amount(), - ) - .wrap_err("Token transfer failed")?; - - let masp_section_ref = transfer.shielded_section_hash; - let shielded = tx_data - .tx - .get_section(&masp_section_ref) - .and_then(|x| x.as_ref().masp_tx()) - .ok_or_err_msg("Unable to find required shielded section in tx data") - .map_err(|err| { - ctx.set_commitment_sentinel(); - err - })?; - token::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 { masp_section_ref }))?; - - Ok(()) -}