diff --git a/.changelog/unreleased/improvements/3297-split-transfer-tx.md b/.changelog/unreleased/improvements/3297-split-transfer-tx.md new file mode 100644 index 0000000000..a4a8b2fa4c --- /dev/null +++ b/.changelog/unreleased/improvements/3297-split-transfer-tx.md @@ -0,0 +1,6 @@ +- The transfer command has been split into: + - `transfer` (shielded transfer) + - `transparent-transfer` + - `shield` (from transparent to shielded) + - `unshield` (from shielded to transparent) + ([\#3297](https://github.com/anoma/namada/pull/3297)) \ No newline at end of file diff --git a/.github/workflows/scripts/hermes.txt b/.github/workflows/scripts/hermes.txt index 7588db229e..7258f5d5db 100644 --- a/.github/workflows/scripts/hermes.txt +++ b/.github/workflows/scripts/hermes.txt @@ -1 +1 @@ -1.8.2-namada-beta10-rc +1.8.2-namada-beta11-rc diff --git a/Cargo.lock b/Cargo.lock index 40787e4c1c..ce37252eb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5423,11 +5423,15 @@ dependencies = [ name = "namada_token" version = "0.38.1" dependencies = [ + "borsh 1.2.1", "namada_core", "namada_events", + "namada_macros", "namada_shielded_token", "namada_storage", "namada_trans_token", + "proptest", + "serde", ] [[package]] diff --git a/crates/apps/src/bin/namada/cli.rs b/crates/apps/src/bin/namada/cli.rs index 9b97f2e761..ac56cd02d3 100644 --- a/crates/apps/src/bin/namada/cli.rs +++ b/crates/apps/src/bin/namada/cli.rs @@ -45,7 +45,10 @@ fn handle_command(cmd: cli::cmds::Namada, raw_sub_cmd: String) -> Result<()> { } cli::cmds::Namada::Client(_) | cli::cmds::Namada::TxCustom(_) - | cli::cmds::Namada::TxTransfer(_) + | cli::cmds::Namada::TxTransparentTransfer(_) + | cli::cmds::Namada::TxShieldedTransfer(_) + | cli::cmds::Namada::TxShieldingTransfer(_) + | cli::cmds::Namada::TxUnshieldingTransfer(_) | cli::cmds::Namada::TxIbcTransfer(_) | cli::cmds::Namada::TxUpdateAccount(_) | cli::cmds::Namada::TxRevealPk(_) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index 88e00d6485..78284f2c99 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -57,7 +57,10 @@ pub mod cmds { // Inlined commands from the client. TxCustom(TxCustom), - TxTransfer(TxTransfer), + TxTransparentTransfer(TxTransparentTransfer), + TxShieldedTransfer(TxShieldedTransfer), + TxShieldingTransfer(TxShieldingTransfer), + TxUnshieldingTransfer(TxUnshieldingTransfer), TxIbcTransfer(TxIbcTransfer), TxUpdateAccount(TxUpdateAccount), TxInitProposal(TxInitProposal), @@ -74,7 +77,10 @@ pub mod cmds { .subcommand(EthBridgePool::def()) .subcommand(Ledger::def()) .subcommand(TxCustom::def()) - .subcommand(TxTransfer::def()) + .subcommand(TxTransparentTransfer::def()) + .subcommand(TxShieldedTransfer::def()) + .subcommand(TxShieldingTransfer::def()) + .subcommand(TxUnshieldingTransfer::def()) .subcommand(TxIbcTransfer::def()) .subcommand(TxUpdateAccount::def()) .subcommand(TxInitProposal::def()) @@ -91,7 +97,14 @@ pub mod cmds { let wallet = SubCmd::parse(matches).map(Self::Wallet); let ledger = SubCmd::parse(matches).map(Self::Ledger); let tx_custom = SubCmd::parse(matches).map(Self::TxCustom); - let tx_transfer = SubCmd::parse(matches).map(Self::TxTransfer); + let tx_transparent_transfer = + SubCmd::parse(matches).map(Self::TxTransparentTransfer); + let tx_shielded_transfer = + SubCmd::parse(matches).map(Self::TxShieldedTransfer); + let tx_shielding_transfer = + SubCmd::parse(matches).map(Self::TxShieldingTransfer); + let tx_unshielding_transfer = + SubCmd::parse(matches).map(Self::TxUnshieldingTransfer); let tx_ibc_transfer = SubCmd::parse(matches).map(Self::TxIbcTransfer); let tx_update_account = @@ -107,7 +120,10 @@ pub mod cmds { .or(wallet) .or(ledger) .or(tx_custom) - .or(tx_transfer) + .or(tx_transparent_transfer) + .or(tx_shielded_transfer) + .or(tx_shielding_transfer) + .or(tx_unshielding_transfer) .or(tx_ibc_transfer) .or(tx_update_account) .or(tx_init_proposal) @@ -218,7 +234,10 @@ pub mod cmds { app // Simple transactions .subcommand(TxCustom::def().display_order(1)) - .subcommand(TxTransfer::def().display_order(1)) + .subcommand(TxTransparentTransfer::def().display_order(1)) + .subcommand(TxShieldedTransfer::def().display_order(1)) + .subcommand(TxShieldingTransfer::def().display_order(1)) + .subcommand(TxUnshieldingTransfer::def().display_order(1)) .subcommand(TxIbcTransfer::def().display_order(1)) .subcommand(TxUpdateAccount::def().display_order(1)) .subcommand(TxInitAccount::def().display_order(1)) @@ -280,7 +299,14 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { use NamadaClientWithContext::*; let tx_custom = Self::parse_with_ctx(matches, TxCustom); - let tx_transfer = Self::parse_with_ctx(matches, TxTransfer); + let tx_transparent_transfer = + Self::parse_with_ctx(matches, TxTransparentTransfer); + let tx_shielded_transfer = + Self::parse_with_ctx(matches, TxShieldedTransfer); + let tx_shielding_transfer = + Self::parse_with_ctx(matches, TxShieldingTransfer); + let tx_unshielding_transfer = + Self::parse_with_ctx(matches, TxUnshieldingTransfer); let tx_ibc_transfer = Self::parse_with_ctx(matches, TxIbcTransfer); let tx_update_account = Self::parse_with_ctx(matches, TxUpdateAccount); @@ -356,7 +382,10 @@ pub mod cmds { let shielded_sync = Self::parse_with_ctx(matches, ShieldedSync); let utils = SubCmd::parse(matches).map(Self::WithoutContext); tx_custom - .or(tx_transfer) + .or(tx_transparent_transfer) + .or(tx_shielded_transfer) + .or(tx_shielding_transfer) + .or(tx_unshielding_transfer) .or(tx_ibc_transfer) .or(tx_update_account) .or(tx_init_account) @@ -443,7 +472,10 @@ pub mod cmds { pub enum NamadaClientWithContext { // Ledger cmds TxCustom(TxCustom), - TxTransfer(TxTransfer), + TxTransparentTransfer(TxTransparentTransfer), + TxShieldedTransfer(TxShieldedTransfer), + TxShieldingTransfer(TxShieldingTransfer), + TxUnshieldingTransfer(TxUnshieldingTransfer), TxIbcTransfer(TxIbcTransfer), QueryResult(QueryResult), TxUpdateAccount(TxUpdateAccount), @@ -1249,21 +1281,90 @@ pub mod cmds { } #[derive(Clone, Debug)] - pub struct TxTransfer(pub args::TxTransfer); + pub struct TxTransparentTransfer( + pub args::TxTransparentTransfer, + ); + + impl SubCmd for TxTransparentTransfer { + const CMD: &'static str = "transparent-transfer"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + TxTransparentTransfer(args::TxTransparentTransfer::parse( + matches, + )) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about(wrap!("Send a transparent transfer transaction.")) + .add_args::>() + } + } + + #[derive(Clone, Debug)] + pub struct TxShieldedTransfer( + pub args::TxShieldedTransfer, + ); - impl SubCmd for TxTransfer { + impl SubCmd for TxShieldedTransfer { const CMD: &'static str = "transfer"; fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| TxTransfer(args::TxTransfer::parse(matches))) + matches.subcommand_matches(Self::CMD).map(|matches| { + TxShieldedTransfer(args::TxShieldedTransfer::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about(wrap!("Send a shielded transfer transaction (from a shielded address to a shielded address).")) + .add_args::>() + } + } + + #[derive(Clone, Debug)] + pub struct TxShieldingTransfer( + pub args::TxShieldingTransfer, + ); + + impl SubCmd for TxShieldingTransfer { + const CMD: &'static str = "shield"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + TxShieldingTransfer(args::TxShieldingTransfer::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about(wrap!("Send a shielding transfer transaction (from a transparent address to a shielded address).")) + .add_args::>() + } + } + + #[derive(Clone, Debug)] + pub struct TxUnshieldingTransfer( + pub args::TxUnshieldingTransfer, + ); + + impl SubCmd for TxUnshieldingTransfer { + const CMD: &'static str = "unshield"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + TxUnshieldingTransfer(args::TxUnshieldingTransfer::parse( + matches, + )) + }) } fn def() -> App { App::new(Self::CMD) - .about(wrap!("Send a signed transfer transaction.")) - .add_args::>() + .about(wrap!("Send an unshielding transfer transaction (from a shielded address to a transparent address).")) + .add_args::>() } } @@ -2997,10 +3098,11 @@ 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_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, + 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, }; use namada_sdk::DEFAULT_GAS_LIMIT; @@ -3184,6 +3286,7 @@ pub mod args { pub const OWNER_OPT: ArgOpt = OWNER.opt(); pub const PATH: Arg = arg("path"); pub const PATH_OPT: ArgOpt = arg_opt("path"); + pub const PAYMENT_ADDRESS_TARGET: Arg = arg("target"); pub const PORT_ID: ArgDefault = arg_default( "port-id", DefaultFn(|| PortId::from_str("transfer").unwrap()), @@ -3234,12 +3337,14 @@ pub mod args { pub const SIGNATURES: ArgMulti = arg_multi("signatures"); pub const SOURCE: Arg = arg("source"); pub const SOURCE_OPT: ArgOpt = SOURCE.opt(); + pub const SOURCE_VALIDATOR: Arg = arg("source-validator"); + pub const SPENDING_KEY_SOURCE: Arg = arg("source"); pub const SPENDING_KEYS: ArgMulti = arg_multi("spending-keys"); pub const STEWARD: Arg = arg("steward"); - pub const SOURCE_VALIDATOR: Arg = arg("source-validator"); pub const STORAGE_KEY: Arg = arg("storage-key"); pub const SUSPEND_ACTION: ArgFlag = flag("suspend"); + pub const TARGET: Arg = arg("target"); pub const TEMPLATES_PATH: Arg = arg("templates-path"); pub const TIMEOUT_HEIGHT: ArgOpt = arg_opt("timeout-height"); pub const TIMEOUT_SEC_OFFSET: ArgOpt = arg_opt("timeout-sec-offset"); @@ -4221,19 +4326,21 @@ pub mod args { } } - impl CliToSdk> for TxTransfer { + impl CliToSdk> + for TxTransparentTransfer + { type Error = std::io::Error; fn to_sdk( self, ctx: &mut Context, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let tx = self.tx.to_sdk(ctx)?; let chain_ctx = ctx.borrow_mut_chain_or_exit(); - Ok(TxTransfer:: { + Ok(TxTransparentTransfer:: { tx, - source: chain_ctx.get_cached(&self.source), + source: chain_ctx.get(&self.source), target: chain_ctx.get(&self.target), token: chain_ctx.get(&self.token), amount: self.amount, @@ -4242,14 +4349,14 @@ pub mod args { } } - impl Args for TxTransfer { + impl Args for TxTransparentTransfer { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let source = TRANSFER_SOURCE.parse(matches); - let target = TRANSFER_TARGET.parse(matches); + let source = SOURCE.parse(matches); + let target = TARGET.parse(matches); let token = TOKEN.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); - let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); + let tx_code_path = PathBuf::from(TX_TRANSPARENT_TRANSFER_WASM); Self { tx, source, @@ -4262,15 +4369,193 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(TRANSFER_SOURCE.def().help(wrap!( + .arg(SOURCE.def().help(wrap!( "The source account address. The source's key may be used \ to produce the signature." ))) - .arg(TRANSFER_TARGET.def().help(wrap!( - "The target account address. The target's key may be used \ - to produce the signature." + .arg(TARGET.def().help(wrap!("The target account address."))) + .arg(TOKEN.def().help(wrap!("The token address."))) + .arg( + AMOUNT + .def() + .help(wrap!("The amount to transfer in decimal.")), + ) + } + } + + impl CliToSdk> for TxShieldedTransfer { + type Error = std::io::Error; + + fn to_sdk( + self, + ctx: &mut Context, + ) -> Result, Self::Error> { + let tx = self.tx.to_sdk(ctx)?; + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + + 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, + tx_code_path: self.tx_code_path.to_path_buf(), + }) + } + } + + impl Args for TxShieldedTransfer { + fn parse(matches: &ArgMatches) -> Self { + let tx = Tx::parse(matches); + let source = SPENDING_KEY_SOURCE.parse(matches); + 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, + source, + target, + token, + amount, + tx_code_path, + } + } + + fn def(app: App) -> App { + app.add_args::>() + .arg( + SPENDING_KEY_SOURCE + .def() + .help(wrap!("The source shielded spending key.")), + ) + .arg( + PAYMENT_ADDRESS_TARGET + .def() + .help(wrap!("The shielded target account address.")), + ) + .arg(TOKEN.def().help(wrap!("The token address."))) + .arg( + AMOUNT + .def() + .help(wrap!("The amount to transfer in decimal.")), + ) + } + } + + impl CliToSdk> for TxShieldingTransfer { + type Error = std::io::Error; + + fn to_sdk( + self, + ctx: &mut Context, + ) -> Result, Self::Error> { + let tx = self.tx.to_sdk(ctx)?; + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + + Ok(TxShieldingTransfer:: { + tx, + source: chain_ctx.get(&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(), + }) + } + } + + impl Args for TxShieldingTransfer { + fn parse(matches: &ArgMatches) -> Self { + let tx = Tx::parse(matches); + let source = SOURCE.parse(matches); + 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, + source, + target, + token, + amount, + tx_code_path, + } + } + + fn def(app: App) -> App { + app.add_args::>() + .arg(SOURCE.def().help(wrap!( + "The transparent source account address. The source's key \ + will be used to produce the signature." ))) - .arg(TOKEN.def().help(wrap!("The transfer token."))) + .arg( + PAYMENT_ADDRESS_TARGET + .def() + .help(wrap!("The target shielded account address.")), + ) + .arg(TOKEN.def().help(wrap!("The token address."))) + .arg( + AMOUNT + .def() + .help(wrap!("The amount to transfer in decimal.")), + ) + } + } + + impl CliToSdk> + for TxUnshieldingTransfer + { + type Error = std::io::Error; + + fn to_sdk( + self, + ctx: &mut Context, + ) -> Result, Self::Error> { + let tx = self.tx.to_sdk(ctx)?; + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + + Ok(TxUnshieldingTransfer:: { + tx, + 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(), + }) + } + } + + impl Args for TxUnshieldingTransfer { + fn parse(matches: &ArgMatches) -> Self { + let tx = Tx::parse(matches); + let source = SPENDING_KEY_SOURCE.parse(matches); + 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, + target, + token, + amount, + tx_code_path, + } + } + + fn def(app: App) -> App { + app.add_args::>() + .arg( + SPENDING_KEY_SOURCE + .def() + .help(wrap!("The source shielded spending key.")), + ) + .arg( + TARGET + .def() + .help(wrap!("The transparent target account address.")), + ) + .arg(TOKEN.def().help(wrap!("The token address."))) .arg( AMOUNT .def() @@ -6472,6 +6757,7 @@ pub mod args { type Data = PathBuf; type EthereumAddress = String; type Keypair = WalletKeypair; + type PaymentAddress = WalletPaymentAddr; type PublicKey = WalletPublicKey; type SpendingKey = WalletSpendingKey; type TendermintAddress = tendermint_rpc::Url; diff --git a/crates/apps_lib/src/cli/client.rs b/crates/apps_lib/src/cli/client.rs index cb9dc532ed..79103a9f3d 100644 --- a/crates/apps_lib/src/cli/client.rs +++ b/crates/apps_lib/src/cli/client.rs @@ -52,7 +52,7 @@ impl CliApi { ) } } - Sub::TxTransfer(TxTransfer(args)) => { + Sub::TxTransparentTransfer(TxTransparentTransfer(args)) => { let chain_ctx = ctx.borrow_mut_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); @@ -62,7 +62,43 @@ impl CliApi { client.wait_until_node_is_synced(&io).await?; let args = args.to_sdk(&mut ctx)?; let namada = ctx.to_sdk(client, io); - tx::submit_transfer(&namada, args).await?; + tx::submit_transparent_transfer(&namada, args).await?; + } + Sub::TxShieldedTransfer(TxShieldedTransfer(args)) => { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let ledger_address = + chain_ctx.get(&args.tx.ledger_address); + let client = client.unwrap_or_else(|| { + C::from_tendermint_address(&ledger_address) + }); + client.wait_until_node_is_synced(&io).await?; + let args = args.to_sdk(&mut ctx)?; + let namada = ctx.to_sdk(client, io); + tx::submit_shielded_transfer(&namada, args).await?; + } + Sub::TxShieldingTransfer(TxShieldingTransfer(args)) => { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let ledger_address = + chain_ctx.get(&args.tx.ledger_address); + let client = client.unwrap_or_else(|| { + C::from_tendermint_address(&ledger_address) + }); + client.wait_until_node_is_synced(&io).await?; + let args = args.to_sdk(&mut ctx)?; + let namada = ctx.to_sdk(client, io); + tx::submit_shielding_transfer(&namada, args).await?; + } + Sub::TxUnshieldingTransfer(TxUnshieldingTransfer(args)) => { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let ledger_address = + chain_ctx.get(&args.tx.ledger_address); + let client = client.unwrap_or_else(|| { + C::from_tendermint_address(&ledger_address) + }); + client.wait_until_node_is_synced(&io).await?; + let args = args.to_sdk(&mut ctx)?; + let namada = ctx.to_sdk(client, io); + tx::submit_unshielding_transfer(&namada, args).await?; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { let chain_ctx = ctx.borrow_mut_chain_or_exit(); diff --git a/crates/apps_lib/src/cli/context.rs b/crates/apps_lib/src/cli/context.rs index 2fad9721c5..2045b890e2 100644 --- a/crates/apps_lib/src/cli/context.rs +++ b/crates/apps_lib/src/cli/context.rs @@ -9,9 +9,9 @@ use color_eyre::eyre::Result; use namada::core::address::{Address, InternalAddress}; use namada::core::chain::ChainId; use namada::core::ethereum_events::EthAddress; -use namada::core::ibc::{is_ibc_denom, is_nft_trace}; use namada::core::key::*; use namada::core::masp::*; +use namada::ibc::{is_ibc_denom, is_nft_trace}; use namada::io::Io; use namada::ledger::ibc::storage::ibc_token; use namada_sdk::masp::fs::FsShieldedUtils; diff --git a/crates/apps_lib/src/client/tx.rs b/crates/apps_lib/src/client/tx.rs index f7a2a182c6..36b15a4898 100644 --- a/crates/apps_lib/src/client/tx.rs +++ b/crates/apps_lib/src/client/tx.rs @@ -739,18 +739,45 @@ pub async fn submit_init_validator( .await } -pub async fn submit_transfer( +pub async fn submit_transparent_transfer( namada: &impl Namada, - args: args::TxTransfer, + args: args::TxTransparentTransfer, ) -> Result<(), error::Error> { - for _ in 0..2 { - submit_reveal_aux( - namada, - args.tx.clone(), - &args.source.effective_address(), - ) - .await?; + submit_reveal_aux(namada, args.tx.clone(), &args.source).await?; + + let (mut tx, signing_data) = args.clone().build(namada).await?; + + if args.tx.dump_tx { + tx::dump_tx(namada.io(), &args.tx, tx); + } else { + sign(namada, &mut tx, &args.tx, signing_data).await?; + namada.submit(tx, &args.tx).await?; + } + + Ok(()) +} + +pub async fn submit_shielded_transfer( + namada: &impl Namada, + args: args::TxShieldedTransfer, +) -> Result<(), error::Error> { + let (mut tx, signing_data) = args.clone().build(namada).await?; + + if args.tx.dump_tx { + tx::dump_tx(namada.io(), &args.tx, tx); + } else { + sign(namada, &mut tx, &args.tx, signing_data).await?; + namada.submit(tx, &args.tx).await?; + } + Ok(()) +} +pub async fn submit_shielding_transfer( + namada: &impl Namada, + args: args::TxShieldingTransfer, +) -> Result<(), error::Error> { + // Repeat once if the tx fails on a crossover of an epoch + for _ in 0..2 { let (mut tx, signing_data, tx_epoch) = args.clone().build(namada).await?; @@ -760,22 +787,18 @@ pub async fn submit_transfer( } else { sign(namada, &mut tx, &args.tx, signing_data).await?; let cmt_hash = tx.first_commitments().unwrap().get_hash(); - let result = namada.submit(tx, &args.tx).await?; - match result { ProcessTxResponse::Applied(resp) if - // If a transaction is shielded - tx_epoch.is_some() && - // And it is rejected by a VP + // If a transaction is rejected by a VP matches!(resp.batch_result().get(&cmt_hash), Some(InnerTxResult::VpsRejected(_))) => { let submission_epoch = rpc::query_and_print_epoch(namada).await; // And its submission epoch doesn't match construction epoch - if tx_epoch.unwrap() != submission_epoch { + if tx_epoch != submission_epoch { // Then we probably straddled an epoch boundary. Let's retry... edisplay_line!(namada.io(), - "MASP transaction rejected and this may be due to the \ + "Shielding transaction rejected and this may be due to the \ epoch changing. Attempting to resubmit transaction.", ); continue; @@ -787,7 +810,21 @@ pub async fn submit_transfer( } } } + Ok(()) +} + +pub async fn submit_unshielding_transfer( + namada: &impl Namada, + args: args::TxUnshieldingTransfer, +) -> Result<(), error::Error> { + let (mut tx, signing_data) = args.clone().build(namada).await?; + if args.tx.dump_tx { + tx::dump_tx(namada.io(), &args.tx, tx); + } else { + sign(namada, &mut tx, &args.tx, signing_data).await?; + namada.submit(tx, &args.tx).await?; + } Ok(()) } diff --git a/crates/benches/host_env.rs b/crates/benches/host_env.rs index 8b13760964..1eee561163 100644 --- a/crates/benches/host_env.rs +++ b/crates/benches/host_env.rs @@ -3,29 +3,29 @@ use namada::core::account::AccountPublicKeysMap; use namada::core::address; use namada::core::collections::{HashMap, HashSet}; use namada::ledger::storage::DB; -use namada::token::{Amount, Transfer}; +use namada::token::{Amount, 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_TRANSFER_WASM, - TX_UPDATE_ACCOUNT_WASM, VP_USER_WASM, WASM_DIR, + BenchShell, TX_INIT_PROPOSAL_WASM, TX_REVEAL_PK_WASM, + TX_TRANSPARENT_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 = Transfer { + let transfer_data = TransparentTransfer { source: defaults::albert_address(), target: defaults::bertha_address(), token: address::testing::nam(), amount: Amount::native_whole(500).native_denominated(), - shielded: None, }; let tx = shell.generate_tx( - TX_TRANSFER_WASM, + TX_TRANSPARENT_TRANSFER_WASM, transfer_data, None, None, @@ -62,7 +62,7 @@ fn compile_wasm(c: &mut Criterion) { let mut txs: HashMap<&str, Vec> = HashMap::default(); for tx in [ - TX_TRANSFER_WASM, + TX_TRANSPARENT_TRANSFER_WASM, TX_INIT_PROPOSAL_WASM, TX_REVEAL_PK_WASM, TX_UPDATE_ACCOUNT_WASM, @@ -111,7 +111,7 @@ fn untrusted_wasm_validation(c: &mut Criterion) { let mut txs: HashMap<&str, Vec> = HashMap::default(); for tx in [ - TX_TRANSFER_WASM, + TX_TRANSPARENT_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 5f2b4455e4..298c37fcf5 100644 --- a/crates/benches/native_vps.rs +++ b/crates/benches/native_vps.rs @@ -55,14 +55,15 @@ 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, Transfer}; +use namada::token::{Amount, 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_TRANSFER_WASM, TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL_WASM, + TX_TRANSPARENT_TRANSFER_WASM, TX_UPDATE_STEWARD_COMMISSION, + TX_VOTE_PROPOSAL_WASM, }; use rand_core::OsRng; @@ -474,13 +475,12 @@ fn vp_multitoken(c: &mut Criterion) { generate_foreign_key_tx(&defaults::albert_keypair()); let transfer = shell.generate_tx( - TX_TRANSFER_WASM, - Transfer { + TX_TRANSPARENT_TRANSFER_WASM, + TransparentTransfer { source: defaults::albert_address(), target: defaults::bertha_address(), token: address::testing::nam(), amount: Amount::native_whole(1000).native_denominated(), - shielded: None, }, None, None, diff --git a/crates/benches/process_wrapper.rs b/crates/benches/process_wrapper.rs index 2d1c1241ba..9510ee3125 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, Transfer}; +use namada::token::{Amount, DenominatedAmount, TransparentTransfer}; use namada::tx::data::{Fee, WrapperTx}; use namada::tx::Authorization; use namada_apps_lib::wallet::defaults; -use namada_node::bench_utils::{BenchShell, TX_TRANSFER_WASM}; +use namada_node::bench_utils::{BenchShell, TX_TRANSPARENT_TRANSFER_WASM}; use namada_node::shell::process_proposal::ValidationMeta; fn process_tx(c: &mut Criterion) { @@ -18,13 +18,12 @@ fn process_tx(c: &mut Criterion) { BlockHeight(2); let mut batched_tx = shell.generate_tx( - TX_TRANSFER_WASM, - Transfer { + TX_TRANSPARENT_TRANSFER_WASM, + TransparentTransfer { source: defaults::albert_address(), target: defaults::bertha_address(), token: address::testing::nam(), amount: Amount::native_whole(1).native_denominated(), - shielded: None, }, None, None, diff --git a/crates/core/src/address.rs b/crates/core/src/address.rs index bdda98c56e..409622d747 100644 --- a/crates/core/src/address.rs +++ b/crates/core/src/address.rs @@ -9,6 +9,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; +use ibc::primitives::Signer; use namada_macros::BorshDeserializer; #[cfg(feature = "migrations")] use namada_migrations::*; @@ -16,7 +17,6 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use crate::ethereum_events::EthAddress; -use crate::ibc::primitives::Signer; use crate::ibc::IbcTokenHash; use crate::key::PublicKeyHash; use crate::{impl_display_and_from_str_via_format, key, string_encoding}; diff --git a/crates/core/src/ibc.rs b/crates/core/src/ibc.rs index 115ef5b017..fe29f61fe7 100644 --- a/crates/core/src/ibc.rs +++ b/crates/core/src/ibc.rs @@ -9,32 +9,8 @@ use namada_macros::BorshDeserializer; #[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; -use thiserror::Error; -use super::address::{Address, InternalAddress, HASH_LEN}; -use crate::ibc::apps::nft_transfer::context::{NftClassContext, NftContext}; -use crate::ibc::apps::nft_transfer::types::error::NftTransferError; -use crate::ibc::apps::nft_transfer::types::msgs::transfer::MsgTransfer as IbcMsgNftTransfer; -use crate::ibc::apps::nft_transfer::types::{ - ClassData, ClassId, ClassUri, PrefixedClassId, TokenData, TokenId, - TokenUri, TracePath as NftTracePath, -}; -use crate::ibc::apps::transfer::types::msgs::transfer::MsgTransfer as IbcMsgTransfer; -use crate::ibc::apps::transfer::types::{PrefixedDenom, TracePath}; -use crate::ibc::core::channel::types::msgs::{ - MsgAcknowledgement as IbcMsgAcknowledgement, - MsgRecvPacket as IbcMsgRecvPacket, MsgTimeout as IbcMsgTimeout, -}; -use crate::ibc::core::handler::types::msgs::MsgEnvelope; -use crate::ibc::primitives::proto::Protobuf; -use crate::token::Transfer; - -/// The event type defined in ibc-rs for receiving a token -pub const EVENT_TYPE_PACKET: &str = "fungible_token_packet"; -/// The event type defined in ibc-rs for receiving an NFT -pub const EVENT_TYPE_NFT_PACKET: &str = "non_fungible_token_packet"; -/// The escrow address for IBC transfer -pub const IBC_ESCROW_ADDRESS: Address = Address::Internal(InternalAddress::Ibc); +use super::address::HASH_LEN; /// IBC token hash derived from a denomination. #[derive( @@ -71,422 +47,3 @@ impl FromStr for IbcTokenHash { Ok(IbcTokenHash(output)) } } - -/// The different variants of an Ibc message -pub enum IbcMessage { - /// Ibc Envelop - Envelope(Box), - /// Ibc transaprent transfer - Transfer(MsgTransfer), - /// NFT transfer - NftTransfer(MsgNftTransfer), - /// Receiving a packet - RecvPacket(MsgRecvPacket), - /// Acknowledgement - AckPacket(MsgAcknowledgement), - /// Timeout - Timeout(MsgTimeout), -} - -/// IBC transfer message with `Transfer` -#[derive(Debug, Clone)] -pub struct MsgTransfer { - /// IBC transfer message - pub message: IbcMsgTransfer, - /// Shieleded transfer for MASP transaction - pub transfer: Option, -} - -impl BorshSerialize for MsgTransfer { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - let encoded_msg = self.message.clone().encode_vec(); - let members = (encoded_msg, self.transfer.clone()); - BorshSerialize::serialize(&members, writer) - } -} - -impl BorshDeserialize for MsgTransfer { - fn deserialize_reader( - reader: &mut R, - ) -> std::io::Result { - use std::io::{Error, ErrorKind}; - let (msg, transfer): (Vec, Option) = - BorshDeserialize::deserialize_reader(reader)?; - let message = IbcMsgTransfer::decode_vec(&msg) - .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; - Ok(Self { message, transfer }) - } -} - -/// IBC NFT transfer message with `Transfer` -#[derive(Debug, Clone)] -pub struct MsgNftTransfer { - /// IBC NFT transfer message - pub message: IbcMsgNftTransfer, - /// Shieleded transfer for MASP transaction - pub transfer: Option, -} - -impl BorshSerialize for MsgNftTransfer { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - let encoded_msg = self.message.clone().encode_vec(); - let members = (encoded_msg, self.transfer.clone()); - BorshSerialize::serialize(&members, writer) - } -} - -impl BorshDeserialize for MsgNftTransfer { - fn deserialize_reader( - reader: &mut R, - ) -> std::io::Result { - use std::io::{Error, ErrorKind}; - let (msg, transfer): (Vec, Option) = - BorshDeserialize::deserialize_reader(reader)?; - let message = IbcMsgNftTransfer::decode_vec(&msg) - .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; - Ok(Self { message, transfer }) - } -} - -/// IBC shielded transfer -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshDeserializer)] -pub struct IbcShieldedTransfer { - /// The IBC event type - pub transfer: Transfer, - /// The attributes of the IBC event - pub masp_tx: masp_primitives::transaction::Transaction, -} - -/// IBC receiving packet message with `Transfer` -#[derive(Debug, Clone)] -pub struct MsgRecvPacket { - /// IBC receiving packet message - pub message: IbcMsgRecvPacket, - /// Shieleded transfer for MASP transaction - pub transfer: Option, -} - -impl BorshSerialize for MsgRecvPacket { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - let encoded_msg = self.message.clone().encode_vec(); - let members = (encoded_msg, self.transfer.clone()); - BorshSerialize::serialize(&members, writer) - } -} - -impl BorshDeserialize for MsgRecvPacket { - fn deserialize_reader( - reader: &mut R, - ) -> std::io::Result { - use std::io::{Error, ErrorKind}; - let (msg, transfer): (Vec, Option) = - BorshDeserialize::deserialize_reader(reader)?; - let message = IbcMsgRecvPacket::decode_vec(&msg) - .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; - Ok(Self { message, transfer }) - } -} - -/// IBC acknowledgement message with `Transfer` for refunding to a shielded -/// address -#[derive(Debug, Clone)] -pub struct MsgAcknowledgement { - /// IBC acknowledgement message - pub message: IbcMsgAcknowledgement, - /// Shieleded transfer for MASP transaction - pub transfer: Option, -} - -impl BorshSerialize for MsgAcknowledgement { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - let encoded_msg = self.message.clone().encode_vec(); - let members = (encoded_msg, self.transfer.clone()); - BorshSerialize::serialize(&members, writer) - } -} - -impl BorshDeserialize for MsgAcknowledgement { - fn deserialize_reader( - reader: &mut R, - ) -> std::io::Result { - use std::io::{Error, ErrorKind}; - let (msg, transfer): (Vec, Option) = - BorshDeserialize::deserialize_reader(reader)?; - let message = IbcMsgAcknowledgement::decode_vec(&msg) - .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; - Ok(Self { message, transfer }) - } -} - -/// IBC timeout packet message with `Transfer` for refunding to a shielded -/// address -#[derive(Debug, Clone)] -pub struct MsgTimeout { - /// IBC timeout message - pub message: IbcMsgTimeout, - /// Shieleded transfer for MASP transaction - pub transfer: Option, -} - -impl BorshSerialize for MsgTimeout { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - let encoded_msg = self.message.clone().encode_vec(); - let members = (encoded_msg, self.transfer.clone()); - BorshSerialize::serialize(&members, writer) - } -} - -impl BorshDeserialize for MsgTimeout { - fn deserialize_reader( - reader: &mut R, - ) -> std::io::Result { - use std::io::{Error, ErrorKind}; - let (msg, transfer): (Vec, Option) = - BorshDeserialize::deserialize_reader(reader)?; - let message = IbcMsgTimeout::decode_vec(&msg) - .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; - Ok(Self { message, transfer }) - } -} - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("IBC transfer memo HEX decoding error: {0}")] - DecodingHex(data_encoding::DecodeError), - #[error("IBC transfer memo decoding error: {0}")] - DecodingShieldedTransfer(std::io::Error), -} - -/// Returns the trace path and the token string if the denom is an IBC -/// denom. -pub fn is_ibc_denom(denom: impl AsRef) -> Option<(TracePath, String)> { - let prefixed_denom = PrefixedDenom::from_str(denom.as_ref()).ok()?; - let base_denom = prefixed_denom.base_denom.to_string(); - if prefixed_denom.trace_path.is_empty() || base_denom.contains('/') { - // The denom is just a token or an NFT trace - return None; - } - // The base token isn't decoded because it could be non Namada token - Some((prefixed_denom.trace_path, base_denom)) -} - -/// Returns the trace path and the token string if the trace is an NFT one -pub fn is_nft_trace( - trace: impl AsRef, -) -> Option<(NftTracePath, String, String)> { - // The trace should be {port}/{channel}/.../{class_id}/{token_id} - if let Some((class_id, token_id)) = trace.as_ref().rsplit_once('/') { - let prefixed_class_id = PrefixedClassId::from_str(class_id).ok()?; - // The base token isn't decoded because it could be non Namada token - Some(( - prefixed_class_id.trace_path, - prefixed_class_id.base_class_id.to_string(), - token_id.to_string(), - )) - } else { - None - } -} - -/// NFT class -#[derive(Clone, Debug)] -pub struct NftClass { - /// NFT class ID - pub class_id: PrefixedClassId, - /// NFT class URI - pub class_uri: Option, - /// NFT class data - pub class_data: Option, -} - -impl BorshSerialize for NftClass { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - BorshSerialize::serialize(&self.class_id.to_string(), writer)?; - match &self.class_uri { - Some(uri) => { - BorshSerialize::serialize(&true, writer)?; - BorshSerialize::serialize(&uri.to_string(), writer)?; - } - None => BorshSerialize::serialize(&false, writer)?, - } - match &self.class_data { - Some(data) => { - BorshSerialize::serialize(&true, writer)?; - BorshSerialize::serialize(&data.to_string(), writer) - } - None => BorshSerialize::serialize(&false, writer), - } - } -} - -impl BorshDeserialize for NftClass { - fn deserialize_reader( - reader: &mut R, - ) -> std::io::Result { - use std::io::{Error, ErrorKind}; - let class_id: String = BorshDeserialize::deserialize_reader(reader)?; - let class_id = class_id.parse().map_err(|e: NftTransferError| { - Error::new(ErrorKind::InvalidData, e.to_string()) - })?; - - let is_uri: bool = BorshDeserialize::deserialize_reader(reader)?; - let class_uri = if is_uri { - let uri_str: String = BorshDeserialize::deserialize_reader(reader)?; - Some(uri_str.parse().map_err(|e: NftTransferError| { - Error::new(ErrorKind::InvalidData, e.to_string()) - })?) - } else { - None - }; - - let is_data: bool = BorshDeserialize::deserialize_reader(reader)?; - let class_data = if is_data { - let data_str: String = - BorshDeserialize::deserialize_reader(reader)?; - Some(data_str.parse().map_err(|e: NftTransferError| { - Error::new(ErrorKind::InvalidData, e.to_string()) - })?) - } else { - None - }; - - Ok(Self { - class_id, - class_uri, - class_data, - }) - } -} - -impl NftClassContext for NftClass { - fn get_id(&self) -> &ClassId { - &self.class_id.base_class_id - } - - fn get_uri(&self) -> Option<&ClassUri> { - self.class_uri.as_ref() - } - - fn get_data(&self) -> Option<&ClassData> { - self.class_data.as_ref() - } -} - -/// NFT metadata -#[derive(Clone, Debug)] -pub struct NftMetadata { - /// NFT class ID - pub class_id: PrefixedClassId, - /// NFT ID - pub token_id: TokenId, - /// NFT URI - pub token_uri: Option, - /// NFT data - pub token_data: Option, -} - -impl BorshSerialize for NftMetadata { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - BorshSerialize::serialize(&self.class_id.to_string(), writer)?; - BorshSerialize::serialize(&self.token_id.to_string(), writer)?; - match &self.token_uri { - Some(uri) => { - BorshSerialize::serialize(&true, writer)?; - BorshSerialize::serialize(&uri.to_string(), writer)?; - } - None => BorshSerialize::serialize(&false, writer)?, - } - match &self.token_data { - Some(data) => { - BorshSerialize::serialize(&true, writer)?; - BorshSerialize::serialize(&data.to_string(), writer) - } - None => BorshSerialize::serialize(&false, writer), - } - } -} - -impl BorshDeserialize for NftMetadata { - fn deserialize_reader( - reader: &mut R, - ) -> std::io::Result { - use std::io::{Error, ErrorKind}; - let class_id: String = BorshDeserialize::deserialize_reader(reader)?; - let class_id = class_id.parse().map_err(|e: NftTransferError| { - Error::new(ErrorKind::InvalidData, e.to_string()) - })?; - - let token_id: String = BorshDeserialize::deserialize_reader(reader)?; - let token_id = token_id.parse().map_err(|e: NftTransferError| { - Error::new(ErrorKind::InvalidData, e.to_string()) - })?; - - let is_uri: bool = BorshDeserialize::deserialize_reader(reader)?; - let token_uri = if is_uri { - let uri_str: String = BorshDeserialize::deserialize_reader(reader)?; - Some(uri_str.parse().map_err(|e: NftTransferError| { - Error::new(ErrorKind::InvalidData, e.to_string()) - })?) - } else { - None - }; - - let is_data: bool = BorshDeserialize::deserialize_reader(reader)?; - let token_data = if is_data { - let data_str: String = - BorshDeserialize::deserialize_reader(reader)?; - Some(data_str.parse().map_err(|e: NftTransferError| { - Error::new(ErrorKind::InvalidData, e.to_string()) - })?) - } else { - None - }; - - Ok(Self { - class_id, - token_id, - token_uri, - token_data, - }) - } -} - -impl NftContext for NftMetadata { - fn get_class_id(&self) -> &ClassId { - &self.class_id.base_class_id - } - - fn get_id(&self) -> &TokenId { - &self.token_id - } - - fn get_uri(&self) -> Option<&TokenUri> { - self.token_uri.as_ref() - } - - fn get_data(&self) -> Option<&TokenData> { - self.token_data.as_ref() - } -} diff --git a/crates/core/src/token.rs b/crates/core/src/token.rs index 616ec87221..69588b9518 100644 --- a/crates/core/src/token.rs +++ b/crates/core/src/token.rs @@ -7,17 +7,15 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::BASE32HEX_NOPAD; use ethabi::ethereum_types::U256; +use ibc::apps::transfer::types::Amount as IbcAmount; use namada_macros::BorshDeserializer; #[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::address::Address; use crate::arith::{self, checked, CheckedAdd, CheckedSub}; use crate::dec::{Dec, POS_DECIMAL_PRECISION}; -use crate::hash::Hash; -use crate::ibc::apps::transfer::types::Amount as IbcAmount; use crate::storage; use crate::storage::{DbKeySeg, KeySeg}; use crate::uint::{self, Uint, I256}; @@ -950,34 +948,6 @@ impl From for IbcAmount { } } -/// A simple bilateral token transfer -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshDeserializer, - BorshSchema, - Hash, - Eq, - PartialOrd, - Serialize, - Deserialize, -)] -pub struct Transfer { - /// 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, - /// Shielded transaction part - pub shielded: Option, -} - #[allow(missing_docs)] #[derive(Error, Debug)] pub enum AmountError { @@ -994,9 +964,6 @@ pub mod testing { use proptest::prelude::*; use super::*; - use crate::address::testing::{ - arb_established_address, arb_non_internal_address, - }; impl std::ops::Add for Amount { type Output = Self; @@ -1078,24 +1045,6 @@ pub mod testing { } } - prop_compose! { - /// Generate a transfer - pub fn arb_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(), - ) -> Transfer { - Transfer { - source, - target, - token, - amount, - shielded: None, - } - } - } - /// Generate an arbitrary token amount pub fn arb_amount() -> impl Strategy { any::().prop_map(|val| Amount::from_uint(val, 0).unwrap()) diff --git a/crates/encoding_spec/src/main.rs b/crates/encoding_spec/src/main.rs index ea5858ae3f..efb5c0c79c 100644 --- a/crates/encoding_spec/src/main.rs +++ b/crates/encoding_spec/src/main.rs @@ -23,14 +23,13 @@ use borsh::{schema, schema_container_of}; use itertools::Itertools; use lazy_static::lazy_static; use madato::types::TableRow; -use namada::account; use namada::core::address::Address; use namada::core::collections::HashSet; use namada::core::key::ed25519::{PublicKey, Signature}; use namada::core::storage::{self, Epoch}; -use namada::core::token; use namada::ledger::parameters::Parameters; use namada::tx::data::{pos, TxType, WrapperTx}; +use namada::{account, token}; /// This generator will write output into this `docs` file. const OUTPUT_PATH: &str = @@ -82,7 +81,14 @@ 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 token_transfer_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 update_account = schema_container_of::(); let pos_bond_schema = schema_container_of::(); let pos_withdraw_schema = schema_container_of::(); @@ -109,7 +115,10 @@ fn main() -> Result<(), Box> { definitions.extend(btree(&signature_schema)); definitions.extend(btree(&init_account_schema)); definitions.extend(btree(&init_validator_schema)); - definitions.extend(btree(&token_transfer_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(&update_account)); definitions.extend(btree(&pos_bond_schema)); definitions.extend(btree(&pos_withdraw_schema)); @@ -183,10 +192,10 @@ fn main() -> Result<(), Box> { tables.push(init_validator_table); let token_transfer_definition = definitions - .remove(token_transfer_schema.declaration()) + .remove(transparent_transfer_schema.declaration()) .unwrap(); let token_transfer_table = definition_to_table( - token_transfer_schema.declaration(), + transparent_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/actions.rs b/crates/ibc/src/actions.rs index 4676721c49..63696343a0 100644 --- a/crates/ibc/src/actions.rs +++ b/crates/ibc/src/actions.rs @@ -4,13 +4,12 @@ use std::cell::RefCell; use std::collections::BTreeSet; use std::rc::Rc; +use ibc::apps::transfer::types::msgs::transfer::MsgTransfer as IbcMsgTransfer; +use ibc::apps::transfer::types::packet::PacketData; +use ibc::apps::transfer::types::PrefixedCoin; +use ibc::core::channel::types::timeout::TimeoutHeight; use namada_core::address::Address; use namada_core::borsh::BorshSerializeExt; -use namada_core::ibc::apps::transfer::types::msgs::transfer::MsgTransfer as IbcMsgTransfer; -use namada_core::ibc::apps::transfer::types::packet::PacketData; -use namada_core::ibc::apps::transfer::types::PrefixedCoin; -use namada_core::ibc::core::channel::types::timeout::TimeoutHeight; -use namada_core::ibc::MsgTransfer; use namada_core::tendermint::Time as TmTime; use namada_core::token::Amount; use namada_events::EmitEvents; @@ -26,6 +25,7 @@ use token::DenominatedAmount; use crate::event::IbcEvent; use crate::{ storage as ibc_storage, IbcActions, IbcCommonContext, IbcStorageContext, + MsgTransfer, }; /// IBC protocol context diff --git a/crates/ibc/src/context/client.rs b/crates/ibc/src/context/client.rs index 702a1b5e4b..ab34d71832 100644 --- a/crates/ibc/src/context/client.rs +++ b/crates/ibc/src/context/client.rs @@ -1,17 +1,17 @@ //! AnyClientState and AnyConsensusState for IBC context +use ibc::clients::tendermint::client_state::ClientState as TmClientState; +use ibc::clients::tendermint::consensus_state::ConsensusState as TmConsensusState; +use ibc::clients::tendermint::types::{ + ClientState as TmClientStateType, ConsensusState as TmConsensusStateType, +}; +use ibc::core::client::types::error::ClientError; +use ibc::primitives::proto::Any; use ibc_derive::{IbcClientState, IbcConsensusState}; #[cfg(feature = "testing")] use ibc_testkit::testapp::ibc::clients::mock::client_state::MockClientState; #[cfg(feature = "testing")] use ibc_testkit::testapp::ibc::clients::mock::consensus_state::MockConsensusState; -use namada_core::ibc::clients::tendermint::client_state::ClientState as TmClientState; -use namada_core::ibc::clients::tendermint::consensus_state::ConsensusState as TmConsensusState; -use namada_core::ibc::clients::tendermint::types::{ - ClientState as TmClientStateType, ConsensusState as TmConsensusStateType, -}; -use namada_core::ibc::core::client::types::error::ClientError; -use namada_core::ibc::primitives::proto::Any; use prost::Message; use super::common::IbcCommonContext; diff --git a/crates/ibc/src/context/common.rs b/crates/ibc/src/context/common.rs index e536bb75ee..bc0e1d92b7 100644 --- a/crates/ibc/src/context/common.rs +++ b/crates/ibc/src/context/common.rs @@ -2,30 +2,27 @@ use core::time::Duration; -use namada_core::address::Address; -use namada_core::ibc::apps::nft_transfer::types::{PrefixedClassId, TokenId}; -use namada_core::ibc::clients::tendermint::consensus_state::ConsensusState as TmConsensusState; -use namada_core::ibc::clients::tendermint::types::ConsensusState as TmConsensusStateType; -use namada_core::ibc::core::channel::types::channel::ChannelEnd; -use namada_core::ibc::core::channel::types::commitment::{ +use ibc::apps::nft_transfer::types::{PrefixedClassId, TokenId}; +use ibc::clients::tendermint::consensus_state::ConsensusState as TmConsensusState; +use ibc::clients::tendermint::types::ConsensusState as TmConsensusStateType; +use ibc::core::channel::types::channel::ChannelEnd; +use ibc::core::channel::types::commitment::{ AcknowledgementCommitment, PacketCommitment, }; -use namada_core::ibc::core::channel::types::error::{ - ChannelError, PacketError, -}; -use namada_core::ibc::core::channel::types::packet::Receipt; -use namada_core::ibc::core::channel::types::timeout::TimeoutHeight; -use namada_core::ibc::core::client::types::error::ClientError; -use namada_core::ibc::core::client::types::Height; -use namada_core::ibc::core::connection::types::error::ConnectionError; -use namada_core::ibc::core::connection::types::ConnectionEnd; -use namada_core::ibc::core::handler::types::error::ContextError; -use namada_core::ibc::core::host::types::identifiers::{ +use ibc::core::channel::types::error::{ChannelError, PacketError}; +use ibc::core::channel::types::packet::Receipt; +use ibc::core::channel::types::timeout::TimeoutHeight; +use ibc::core::client::types::error::ClientError; +use ibc::core::client::types::Height; +use ibc::core::connection::types::error::ConnectionError; +use ibc::core::connection::types::ConnectionEnd; +use ibc::core::handler::types::error::ContextError; +use ibc::core::host::types::identifiers::{ ChannelId, ClientId, ConnectionId, PortId, Sequence, }; -use namada_core::ibc::primitives::proto::{Any, Protobuf}; -use namada_core::ibc::primitives::Timestamp; -use namada_core::ibc::{NftClass, NftMetadata}; +use ibc::primitives::proto::{Any, Protobuf}; +use ibc::primitives::Timestamp; +use namada_core::address::Address; use namada_core::storage::{BlockHeight, Key}; use namada_core::tendermint::Time as TmTime; use namada_core::time::DurationSecs; @@ -37,7 +34,7 @@ use sha2::Digest; use super::client::{AnyClientState, AnyConsensusState}; use super::storage::IbcStorageContext; -use crate::storage; +use crate::{storage, NftClass, NftMetadata}; /// Result of IBC common function call pub type Result = std::result::Result; diff --git a/crates/ibc/src/context/execution.rs b/crates/ibc/src/context/execution.rs index 955228edfe..c081d519f7 100644 --- a/crates/ibc/src/context/execution.rs +++ b/crates/ibc/src/context/execution.rs @@ -1,25 +1,23 @@ //! ExecutionContext implementation for IBC -use namada_core::ibc::core::channel::types::channel::ChannelEnd; -use namada_core::ibc::core::channel::types::commitment::{ +use ibc::core::channel::types::channel::ChannelEnd; +use ibc::core::channel::types::commitment::{ AcknowledgementCommitment, PacketCommitment, }; -use namada_core::ibc::core::channel::types::packet::Receipt; -use namada_core::ibc::core::client::context::ClientExecutionContext; -use namada_core::ibc::core::client::types::Height; -use namada_core::ibc::core::connection::types::ConnectionEnd; -use namada_core::ibc::core::handler::types::error::ContextError; -use namada_core::ibc::core::handler::types::events::IbcEvent; -use namada_core::ibc::core::host::types::identifiers::{ - ClientId, ConnectionId, Sequence, -}; -use namada_core::ibc::core::host::types::path::{ +use ibc::core::channel::types::packet::Receipt; +use ibc::core::client::context::ClientExecutionContext; +use ibc::core::client::types::Height; +use ibc::core::connection::types::ConnectionEnd; +use ibc::core::handler::types::error::ContextError; +use ibc::core::handler::types::events::IbcEvent; +use ibc::core::host::types::identifiers::{ClientId, ConnectionId, Sequence}; +use ibc::core::host::types::path::{ AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, ClientStatePath, CommitmentPath, ConnectionPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; -use namada_core::ibc::core::host::ExecutionContext; -use namada_core::ibc::primitives::Timestamp; +use ibc::core::host::ExecutionContext; +use ibc::primitives::Timestamp; use super::client::AnyClientState; use super::common::IbcCommonContext; diff --git a/crates/ibc/src/context/mod.rs b/crates/ibc/src/context/mod.rs index be3190813e..7bba1137b9 100644 --- a/crates/ibc/src/context/mod.rs +++ b/crates/ibc/src/context/mod.rs @@ -16,9 +16,9 @@ use std::fmt::Debug; use std::rc::Rc; use std::time::Duration; +use ibc::core::commitment_types::specs::ProofSpecs; +use ibc::core::host::types::identifiers::ChainId as IbcChainId; use namada_core::hash::Sha256Hasher; -use namada_core::ibc::core::commitment_types::specs::ProofSpecs; -use namada_core::ibc::core::host::types::identifiers::ChainId as IbcChainId; use namada_state::merkle_tree::ics23_specs::proof_specs; /// IBC context to handle IBC-related data diff --git a/crates/ibc/src/context/nft_transfer.rs b/crates/ibc/src/context/nft_transfer.rs index 2ac019bf6f..85d9aad44e 100644 --- a/crates/ibc/src/context/nft_transfer.rs +++ b/crates/ibc/src/context/nft_transfer.rs @@ -3,22 +3,21 @@ use std::cell::RefCell; use std::rc::Rc; -use namada_core::address::Address; -use namada_core::ibc::apps::nft_transfer::context::{ +use ibc::apps::nft_transfer::context::{ NftTransferExecutionContext, NftTransferValidationContext, }; -use namada_core::ibc::apps::nft_transfer::types::error::NftTransferError; -use namada_core::ibc::apps::nft_transfer::types::{ +use ibc::apps::nft_transfer::types::error::NftTransferError; +use ibc::apps::nft_transfer::types::{ ClassData, ClassUri, Memo, PrefixedClassId, TokenData, TokenId, TokenUri, PORT_ID_STR, }; -use namada_core::ibc::core::handler::types::error::ContextError; -use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; -use namada_core::ibc::{NftClass, NftMetadata, IBC_ESCROW_ADDRESS}; +use ibc::core::handler::types::error::ContextError; +use ibc::core::host::types::identifiers::{ChannelId, PortId}; +use namada_core::address::Address; use namada_core::token::Amount; use super::common::IbcCommonContext; -use crate::storage; +use crate::{storage, NftClass, NftMetadata, IBC_ESCROW_ADDRESS}; /// NFT transfer context to handle tokens #[derive(Debug)] diff --git a/crates/ibc/src/context/nft_transfer_mod.rs b/crates/ibc/src/context/nft_transfer_mod.rs index e8af60b523..1d94056425 100644 --- a/crates/ibc/src/context/nft_transfer_mod.rs +++ b/crates/ibc/src/context/nft_transfer_mod.rs @@ -4,8 +4,8 @@ use std::cell::RefCell; use std::fmt::Debug; use std::rc::Rc; -use namada_core::ibc::apps::nft_transfer::context::NftTransferValidationContext; -use namada_core::ibc::apps::nft_transfer::module::{ +use ibc::apps::nft_transfer::context::NftTransferValidationContext; +use ibc::apps::nft_transfer::module::{ on_acknowledgement_packet_execute, on_acknowledgement_packet_validate, on_chan_close_confirm_execute, on_chan_close_confirm_validate, on_chan_close_init_execute, on_chan_close_init_validate, @@ -16,21 +16,17 @@ use namada_core::ibc::apps::nft_transfer::module::{ on_recv_packet_execute, on_timeout_packet_execute, on_timeout_packet_validate, }; -use namada_core::ibc::apps::nft_transfer::types::error::NftTransferError; -use namada_core::ibc::apps::nft_transfer::types::MODULE_ID_STR; -use namada_core::ibc::core::channel::types::acknowledgement::Acknowledgement; -use namada_core::ibc::core::channel::types::channel::{Counterparty, Order}; -use namada_core::ibc::core::channel::types::error::{ - ChannelError, PacketError, -}; -use namada_core::ibc::core::channel::types::packet::Packet; -use namada_core::ibc::core::channel::types::Version; -use namada_core::ibc::core::host::types::identifiers::{ - ChannelId, ConnectionId, PortId, -}; -use namada_core::ibc::core::router::module::Module; -use namada_core::ibc::core::router::types::module::{ModuleExtras, ModuleId}; -use namada_core::ibc::primitives::Signer; +use ibc::apps::nft_transfer::types::error::NftTransferError; +use ibc::apps::nft_transfer::types::MODULE_ID_STR; +use ibc::core::channel::types::acknowledgement::Acknowledgement; +use ibc::core::channel::types::channel::{Counterparty, Order}; +use ibc::core::channel::types::error::{ChannelError, PacketError}; +use ibc::core::channel::types::packet::Packet; +use ibc::core::channel::types::Version; +use ibc::core::host::types::identifiers::{ChannelId, ConnectionId, PortId}; +use ibc::core::router::module::Module; +use ibc::core::router::types::module::{ModuleExtras, ModuleId}; +use ibc::primitives::Signer; use super::common::IbcCommonContext; use super::nft_transfer::NftTransferContext; @@ -332,10 +328,8 @@ fn into_packet_error(error: NftTransferError) -> PacketError { pub mod testing { use std::str::FromStr; - use namada_core::ibc::apps::nft_transfer::types::{ - ack_success_b64, PORT_ID_STR, - }; - use namada_core::ibc::core::channel::types::acknowledgement::AcknowledgementStatus; + use ibc::apps::nft_transfer::types::{ack_success_b64, PORT_ID_STR}; + use ibc::core::channel::types::acknowledgement::AcknowledgementStatus; use super::*; diff --git a/crates/ibc/src/context/router.rs b/crates/ibc/src/context/router.rs index 0833a6282a..61fc6e24d9 100644 --- a/crates/ibc/src/context/router.rs +++ b/crates/ibc/src/context/router.rs @@ -2,11 +2,11 @@ use std::rc::Rc; +use ibc::core::host::types::identifiers::PortId; +use ibc::core::router::module::Module; +use ibc::core::router::router::Router; +use ibc::core::router::types::module::ModuleId; use namada_core::collections::HashMap; -use namada_core::ibc::core::host::types::identifiers::PortId; -use namada_core::ibc::core::router::module::Module; -use namada_core::ibc::core::router::router::Router; -use namada_core::ibc::core::router::types::module::ModuleId; use super::super::ModuleWrapper; diff --git a/crates/ibc/src/context/token_transfer.rs b/crates/ibc/src/context/token_transfer.rs index 1fc9700f93..4d9b5e7b80 100644 --- a/crates/ibc/src/context/token_transfer.rs +++ b/crates/ibc/src/context/token_transfer.rs @@ -4,23 +4,20 @@ use std::cell::RefCell; use std::collections::BTreeSet; use std::rc::Rc; -use namada_core::address::{Address, InternalAddress}; -use namada_core::ibc::apps::transfer::context::{ +use ibc::apps::transfer::context::{ TokenTransferExecutionContext, TokenTransferValidationContext, }; -use namada_core::ibc::apps::transfer::types::error::TokenTransferError; -use namada_core::ibc::apps::transfer::types::{ - Memo, PrefixedCoin, PrefixedDenom, -}; -use namada_core::ibc::core::channel::types::error::ChannelError; -use namada_core::ibc::core::handler::types::error::ContextError; -use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; -use namada_core::ibc::IBC_ESCROW_ADDRESS; +use ibc::apps::transfer::types::error::TokenTransferError; +use ibc::apps::transfer::types::{Memo, PrefixedCoin, PrefixedDenom}; +use ibc::core::channel::types::error::ChannelError; +use ibc::core::handler::types::error::ContextError; +use ibc::core::host::types::identifiers::{ChannelId, PortId}; +use namada_core::address::{Address, InternalAddress}; use namada_core::uint::Uint; use namada_token::{read_denom, Amount, Denomination}; use super::common::IbcCommonContext; -use crate::storage; +use crate::{storage, IBC_ESCROW_ADDRESS}; /// Token transfer context to handle tokens #[derive(Debug)] diff --git a/crates/ibc/src/context/transfer_mod.rs b/crates/ibc/src/context/transfer_mod.rs index 4b88220213..9a1075e280 100644 --- a/crates/ibc/src/context/transfer_mod.rs +++ b/crates/ibc/src/context/transfer_mod.rs @@ -5,9 +5,8 @@ use std::collections::BTreeSet; use std::fmt::Debug; use std::rc::Rc; -use namada_core::address::Address; -use namada_core::ibc::apps::transfer::context::TokenTransferValidationContext; -use namada_core::ibc::apps::transfer::module::{ +use ibc::apps::transfer::context::TokenTransferValidationContext; +use ibc::apps::transfer::module::{ on_acknowledgement_packet_execute, on_acknowledgement_packet_validate, on_chan_close_confirm_execute, on_chan_close_confirm_validate, on_chan_close_init_execute, on_chan_close_init_validate, @@ -18,21 +17,18 @@ use namada_core::ibc::apps::transfer::module::{ on_recv_packet_execute, on_timeout_packet_execute, on_timeout_packet_validate, }; -use namada_core::ibc::apps::transfer::types::error::TokenTransferError; -use namada_core::ibc::apps::transfer::types::MODULE_ID_STR; -use namada_core::ibc::core::channel::types::acknowledgement::Acknowledgement; -use namada_core::ibc::core::channel::types::channel::{Counterparty, Order}; -use namada_core::ibc::core::channel::types::error::{ - ChannelError, PacketError, -}; -use namada_core::ibc::core::channel::types::packet::Packet; -use namada_core::ibc::core::channel::types::Version; -use namada_core::ibc::core::host::types::identifiers::{ - ChannelId, ConnectionId, PortId, -}; -use namada_core::ibc::core::router::module::Module; -use namada_core::ibc::core::router::types::module::{ModuleExtras, ModuleId}; -use namada_core::ibc::primitives::Signer; +use ibc::apps::transfer::types::error::TokenTransferError; +use ibc::apps::transfer::types::MODULE_ID_STR; +use ibc::core::channel::types::acknowledgement::Acknowledgement; +use ibc::core::channel::types::channel::{Counterparty, Order}; +use ibc::core::channel::types::error::{ChannelError, PacketError}; +use ibc::core::channel::types::packet::Packet; +use ibc::core::channel::types::Version; +use ibc::core::host::types::identifiers::{ChannelId, ConnectionId, PortId}; +use ibc::core::router::module::Module; +use ibc::core::router::types::module::{ModuleExtras, ModuleId}; +use ibc::primitives::Signer; +use namada_core::address::Address; use super::common::IbcCommonContext; use super::token_transfer::TokenTransferContext; @@ -351,10 +347,8 @@ fn into_packet_error(error: TokenTransferError) -> PacketError { pub mod testing { use std::str::FromStr; - use namada_core::ibc::apps::transfer::types::{ - ack_success_b64, PORT_ID_STR, - }; - use namada_core::ibc::core::channel::types::acknowledgement::AcknowledgementStatus; + use ibc::apps::transfer::types::{ack_success_b64, PORT_ID_STR}; + use ibc::core::channel::types::acknowledgement::AcknowledgementStatus; use super::*; diff --git a/crates/ibc/src/context/validation.rs b/crates/ibc/src/context/validation.rs index e063ccc772..5cb60fcd12 100644 --- a/crates/ibc/src/context/validation.rs +++ b/crates/ibc/src/context/validation.rs @@ -1,31 +1,31 @@ //! ValidationContext implementation for IBC -#[cfg(feature = "testing")] -use ibc_testkit::testapp::ibc::clients::mock::client_state::MockClientState; -use namada_core::ibc::clients::tendermint::client_state::ClientState as TmClientState; -use namada_core::ibc::core::channel::types::channel::ChannelEnd; -use namada_core::ibc::core::channel::types::commitment::{ +use ibc::clients::tendermint::client_state::ClientState as TmClientState; +use ibc::core::channel::types::channel::ChannelEnd; +use ibc::core::channel::types::commitment::{ AcknowledgementCommitment, PacketCommitment, }; -use namada_core::ibc::core::channel::types::packet::Receipt; -use namada_core::ibc::core::client::context::{ +use ibc::core::channel::types::packet::Receipt; +use ibc::core::client::context::{ ClientValidationContext, ExtClientValidationContext, }; -use namada_core::ibc::core::client::types::Height; -use namada_core::ibc::core::commitment_types::commitment::CommitmentPrefix; -use namada_core::ibc::core::commitment_types::specs::ProofSpecs; -use namada_core::ibc::core::connection::types::ConnectionEnd; -use namada_core::ibc::core::handler::types::error::ContextError; -use namada_core::ibc::core::host::types::identifiers::{ +use ibc::core::client::types::Height; +use ibc::core::commitment_types::commitment::CommitmentPrefix; +use ibc::core::commitment_types::specs::ProofSpecs; +use ibc::core::connection::types::ConnectionEnd; +use ibc::core::handler::types::error::ContextError; +use ibc::core::host::types::identifiers::{ ChainId, ClientId, ConnectionId, Sequence, }; -use namada_core::ibc::core::host::types::path::{ +use ibc::core::host::types::path::{ AckPath, ChannelEndPath, ClientConsensusStatePath, CommitmentPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; -use namada_core::ibc::core::host::ValidationContext; -use namada_core::ibc::cosmos_host::ValidateSelfClientContext; -use namada_core::ibc::primitives::{Signer, Timestamp}; +use ibc::core::host::ValidationContext; +use ibc::cosmos_host::ValidateSelfClientContext; +use ibc::primitives::{Signer, Timestamp}; +#[cfg(feature = "testing")] +use ibc_testkit::testapp::ibc::clients::mock::client_state::MockClientState; use super::client::{AnyClientState, AnyConsensusState}; use super::common::IbcCommonContext; diff --git a/crates/ibc/src/event.rs b/crates/ibc/src/event.rs index 5e19292686..5d340aee25 100644 --- a/crates/ibc/src/event.rs +++ b/crates/ibc/src/event.rs @@ -3,20 +3,20 @@ use std::cmp::Ordering; use std::str::FromStr; -use namada_core::borsh::*; -use namada_core::collections::HashMap; -use namada_core::ibc::core::channel::types::packet::Packet; -use namada_core::ibc::core::channel::types::timeout::TimeoutHeight as IbcTimeoutHeight; -use namada_core::ibc::core::client::types::events::{ +use ibc::core::channel::types::packet::Packet; +use ibc::core::channel::types::timeout::TimeoutHeight as IbcTimeoutHeight; +use ibc::core::client::types::events::{ CLIENT_ID_ATTRIBUTE_KEY, CONSENSUS_HEIGHTS_ATTRIBUTE_KEY, }; -use namada_core::ibc::core::client::types::{Height as IbcHeight, HeightError}; -use namada_core::ibc::core::handler::types::events::IbcEvent as RawIbcEvent; -use namada_core::ibc::core::host::types::identifiers::{ +use ibc::core::client::types::{Height as IbcHeight, HeightError}; +use ibc::core::handler::types::events::IbcEvent as RawIbcEvent; +use ibc::core::host::types::identifiers::{ ChannelId as IbcChannelId, ClientId as IbcClientId, ConnectionId as IbcConnectionId, PortId, Sequence, }; -use namada_core::ibc::primitives::Timestamp; +use ibc::primitives::Timestamp; +use namada_core::borsh::*; +use namada_core::collections::HashMap; use namada_core::tendermint::abci::Event as AbciEvent; use namada_events::extend::{ event_domain_of, AttributesMap, EventAttributeEntry, @@ -36,7 +36,7 @@ pub const TOKEN_EVENT_DESCRIPTOR: &str = IbcEvent::DOMAIN; pub mod types { //! IBC event types. - use namada_core::ibc::core::client::types::events::UPDATE_CLIENT_EVENT; + use ibc::core::client::types::events::UPDATE_CLIENT_EVENT; use namada_events::{event_type, EventType}; use super::IbcEvent; @@ -331,7 +331,7 @@ impl FromStr for TimeoutHeight { type Err = HeightError; fn from_str(s: &str) -> Result { - namada_core::ibc::core::client::types::Height::from_str(s).map_or_else( + ibc::core::client::types::Height::from_str(s).map_or_else( |err| match err { HeightError::ZeroHeight => { Ok(TimeoutHeight(IbcTimeoutHeight::Never)) diff --git a/crates/ibc/src/lib.rs b/crates/ibc/src/lib.rs index 65be208679..15a673c9c6 100644 --- a/crates/ibc/src/lib.rs +++ b/crates/ibc/src/lib.rs @@ -20,6 +20,8 @@ mod actions; pub mod context; pub mod event; +mod msg; +mod nft; pub mod parameters; pub mod storage; @@ -27,6 +29,7 @@ use std::cell::RefCell; use std::collections::BTreeSet; use std::fmt::Debug; use std::rc::Rc; +use std::str::FromStr; pub use actions::transfer_over_ibc; use borsh::BorshDeserialize; @@ -39,42 +42,52 @@ pub use context::token_transfer::TokenTransferContext; pub use context::transfer_mod::{ModuleWrapper, TransferModule}; use context::IbcContext; pub use context::ValidationParams; -use namada_core::address::Address; -use namada_core::ibc::apps::nft_transfer::handler::{ +use ibc::apps::nft_transfer::handler::{ send_nft_transfer_execute, send_nft_transfer_validate, }; -use namada_core::ibc::apps::nft_transfer::types::error::NftTransferError; -use namada_core::ibc::apps::nft_transfer::types::{ - is_receiver_chain_source as is_nft_receiver_chain_source, PrefixedClassId, - TokenId, TracePrefix as NftTracePrefix, +use ibc::apps::nft_transfer::types::error::NftTransferError; +use ibc::apps::nft_transfer::types::{ + ack_success_b64, is_receiver_chain_source as is_nft_receiver_chain_source, + PrefixedClassId, TokenId, TracePath as NftTracePath, + TracePrefix as NftTracePrefix, }; -use namada_core::ibc::apps::transfer::handler::{ +use ibc::apps::transfer::handler::{ send_transfer_execute, send_transfer_validate, }; -use namada_core::ibc::apps::transfer::types::error::TokenTransferError; -use namada_core::ibc::apps::transfer::types::{ - ack_success_b64, is_receiver_chain_source, TracePrefix, +use ibc::apps::transfer::types::error::TokenTransferError; +use ibc::apps::transfer::types::{ + is_receiver_chain_source, PrefixedDenom, TracePath, TracePrefix, }; -use namada_core::ibc::core::channel::types::acknowledgement::{ +use ibc::core::channel::types::acknowledgement::{ Acknowledgement, AcknowledgementStatus, }; -use namada_core::ibc::core::channel::types::commitment::compute_ack_commitment; -use namada_core::ibc::core::channel::types::msgs::{ +use ibc::core::channel::types::commitment::compute_ack_commitment; +use ibc::core::channel::types::msgs::{ MsgRecvPacket as IbcMsgRecvPacket, PacketMsg, }; -use namada_core::ibc::core::entrypoint::{execute, validate}; -use namada_core::ibc::core::handler::types::error::ContextError; -use namada_core::ibc::core::handler::types::events::Error as RawIbcEventError; -use namada_core::ibc::core::handler::types::msgs::MsgEnvelope; -use namada_core::ibc::core::host::types::error::IdentifierError; -use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; -use namada_core::ibc::core::router::types::error::RouterError; -use namada_core::ibc::primitives::proto::Any; -pub use namada_core::ibc::*; -use namada_token::Transfer; +use ibc::core::entrypoint::{execute, validate}; +use ibc::core::handler::types::error::ContextError; +use ibc::core::handler::types::events::Error as RawIbcEventError; +use ibc::core::handler::types::msgs::MsgEnvelope; +use ibc::core::host::types::error::IdentifierError; +use ibc::core::host::types::identifiers::{ChannelId, PortId}; +use ibc::core::router::types::error::RouterError; +use ibc::primitives::proto::Any; +pub use ibc::*; +pub use msg::*; +use namada_core::address::{self, Address}; +use namada_token::ShieldingTransfer; +pub use nft::*; use prost::Message; use thiserror::Error; +/// The event type defined in ibc-rs for receiving a token +pub const EVENT_TYPE_PACKET: &str = "fungible_token_packet"; +/// The event type defined in ibc-rs for receiving an NFT +pub const EVENT_TYPE_NFT_PACKET: &str = "non_fungible_token_packet"; +/// The escrow address for IBC transfer +pub const IBC_ESCROW_ADDRESS: Address = address::IBC; + #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { @@ -139,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) => { @@ -428,6 +441,37 @@ pub fn received_ibc_token( } } +/// Returns the trace path and the token string if the denom is an IBC +/// denom. +pub fn is_ibc_denom(denom: impl AsRef) -> Option<(TracePath, String)> { + let prefixed_denom = PrefixedDenom::from_str(denom.as_ref()).ok()?; + let base_denom = prefixed_denom.base_denom.to_string(); + if prefixed_denom.trace_path.is_empty() || base_denom.contains('/') { + // The denom is just a token or an NFT trace + return None; + } + // The base token isn't decoded because it could be non Namada token + Some((prefixed_denom.trace_path, base_denom)) +} + +/// Returns the trace path and the token string if the trace is an NFT one +pub fn is_nft_trace( + trace: impl AsRef, +) -> Option<(NftTracePath, String, String)> { + // The trace should be {port}/{channel}/.../{class_id}/{token_id} + if let Some((class_id, token_id)) = trace.as_ref().rsplit_once('/') { + let prefixed_class_id = PrefixedClassId::from_str(class_id).ok()?; + // The base token isn't decoded because it could be non Namada token + Some(( + prefixed_class_id.trace_path, + prefixed_class_id.base_class_id.to_string(), + token_id.to_string(), + )) + } else { + None + } +} + #[cfg(any(test, feature = "testing"))] /// Testing helpers ans strategies for IBC pub mod testing { diff --git a/crates/ibc/src/msg.rs b/crates/ibc/src/msg.rs new file mode 100644 index 0000000000..7502cf1e31 --- /dev/null +++ b/crates/ibc/src/msg.rs @@ -0,0 +1,193 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use ibc::apps::nft_transfer::types::msgs::transfer::MsgTransfer as IbcMsgNftTransfer; +use ibc::apps::transfer::types::msgs::transfer::MsgTransfer as IbcMsgTransfer; +use ibc::core::channel::types::msgs::{ + MsgAcknowledgement as IbcMsgAcknowledgement, + MsgRecvPacket as IbcMsgRecvPacket, MsgTimeout as IbcMsgTimeout, +}; +use ibc::core::handler::types::msgs::MsgEnvelope; +use ibc::primitives::proto::Protobuf; +use namada_token::ShieldingTransfer; + +/// The different variants of an Ibc message +pub enum IbcMessage { + /// Ibc Envelop + Envelope(Box), + /// Ibc transaprent transfer + Transfer(MsgTransfer), + /// NFT transfer + NftTransfer(MsgNftTransfer), + /// Receiving a packet + RecvPacket(MsgRecvPacket), + /// Acknowledgement + AckPacket(MsgAcknowledgement), + /// Timeout + Timeout(MsgTimeout), +} + +/// IBC transfer message with `Transfer` +#[derive(Debug, Clone)] +pub struct MsgTransfer { + /// IBC transfer message + pub message: IbcMsgTransfer, + /// Shieleded transfer for MASP transaction + pub transfer: Option, +} + +impl BorshSerialize for MsgTransfer { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + let encoded_msg = self.message.clone().encode_vec(); + let members = (encoded_msg, self.transfer.clone()); + BorshSerialize::serialize(&members, writer) + } +} + +impl BorshDeserialize for MsgTransfer { + fn deserialize_reader( + reader: &mut R, + ) -> std::io::Result { + use std::io::{Error, ErrorKind}; + let (msg, transfer): (Vec, Option) = + BorshDeserialize::deserialize_reader(reader)?; + let message = IbcMsgTransfer::decode_vec(&msg) + .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; + Ok(Self { message, transfer }) + } +} + +/// IBC NFT transfer message with `Transfer` +#[derive(Debug, Clone)] +pub struct MsgNftTransfer { + /// IBC NFT transfer message + pub message: IbcMsgNftTransfer, + /// Shieleded transfer for MASP transaction + pub transfer: Option, +} + +impl BorshSerialize for MsgNftTransfer { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + let encoded_msg = self.message.clone().encode_vec(); + let members = (encoded_msg, self.transfer.clone()); + BorshSerialize::serialize(&members, writer) + } +} + +impl BorshDeserialize for MsgNftTransfer { + fn deserialize_reader( + reader: &mut R, + ) -> std::io::Result { + use std::io::{Error, ErrorKind}; + let (msg, transfer): (Vec, Option) = + BorshDeserialize::deserialize_reader(reader)?; + let message = IbcMsgNftTransfer::decode_vec(&msg) + .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; + Ok(Self { message, transfer }) + } +} + +/// IBC receiving packet message with `Transfer` +#[derive(Debug, Clone)] +pub struct MsgRecvPacket { + /// IBC receiving packet message + pub message: IbcMsgRecvPacket, + /// Shieleded transfer for MASP transaction + pub transfer: Option, +} + +impl BorshSerialize for MsgRecvPacket { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + let encoded_msg = self.message.clone().encode_vec(); + let members = (encoded_msg, self.transfer.clone()); + BorshSerialize::serialize(&members, writer) + } +} + +impl BorshDeserialize for MsgRecvPacket { + fn deserialize_reader( + reader: &mut R, + ) -> std::io::Result { + use std::io::{Error, ErrorKind}; + let (msg, transfer): (Vec, Option) = + BorshDeserialize::deserialize_reader(reader)?; + let message = IbcMsgRecvPacket::decode_vec(&msg) + .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; + Ok(Self { message, transfer }) + } +} + +/// IBC acknowledgement message with `Transfer` for refunding to a shielded +/// address +#[derive(Debug, Clone)] +pub struct MsgAcknowledgement { + /// IBC acknowledgement message + pub message: IbcMsgAcknowledgement, + /// Shieleded transfer for MASP transaction + pub transfer: Option, +} + +impl BorshSerialize for MsgAcknowledgement { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + let encoded_msg = self.message.clone().encode_vec(); + let members = (encoded_msg, self.transfer.clone()); + BorshSerialize::serialize(&members, writer) + } +} + +impl BorshDeserialize for MsgAcknowledgement { + fn deserialize_reader( + reader: &mut R, + ) -> std::io::Result { + use std::io::{Error, ErrorKind}; + let (msg, transfer): (Vec, Option) = + BorshDeserialize::deserialize_reader(reader)?; + let message = IbcMsgAcknowledgement::decode_vec(&msg) + .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; + Ok(Self { message, transfer }) + } +} + +/// IBC timeout packet message with `Transfer` for refunding to a shielded +/// address +#[derive(Debug, Clone)] +pub struct MsgTimeout { + /// IBC timeout message + pub message: IbcMsgTimeout, + /// Shieleded transfer for MASP transaction + pub transfer: Option, +} + +impl BorshSerialize for MsgTimeout { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + let encoded_msg = self.message.clone().encode_vec(); + let members = (encoded_msg, self.transfer.clone()); + BorshSerialize::serialize(&members, writer) + } +} + +impl BorshDeserialize for MsgTimeout { + fn deserialize_reader( + reader: &mut R, + ) -> std::io::Result { + use std::io::{Error, ErrorKind}; + let (msg, transfer): (Vec, Option) = + BorshDeserialize::deserialize_reader(reader)?; + let message = IbcMsgTimeout::decode_vec(&msg) + .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; + Ok(Self { message, transfer }) + } +} diff --git a/crates/ibc/src/nft.rs b/crates/ibc/src/nft.rs new file mode 100644 index 0000000000..a1f05502a9 --- /dev/null +++ b/crates/ibc/src/nft.rs @@ -0,0 +1,193 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use ibc::apps::nft_transfer::context::{NftClassContext, NftContext}; +use ibc::apps::nft_transfer::types::error::NftTransferError; +use ibc::apps::nft_transfer::types::{ + ClassData, ClassId, ClassUri, PrefixedClassId, TokenData, TokenId, TokenUri, +}; + +/// NFT class +#[derive(Clone, Debug)] +pub struct NftClass { + /// NFT class ID + pub class_id: PrefixedClassId, + /// NFT class URI + pub class_uri: Option, + /// NFT class data + pub class_data: Option, +} + +impl BorshSerialize for NftClass { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + BorshSerialize::serialize(&self.class_id.to_string(), writer)?; + match &self.class_uri { + Some(uri) => { + BorshSerialize::serialize(&true, writer)?; + BorshSerialize::serialize(&uri.to_string(), writer)?; + } + None => BorshSerialize::serialize(&false, writer)?, + } + match &self.class_data { + Some(data) => { + BorshSerialize::serialize(&true, writer)?; + BorshSerialize::serialize(&data.to_string(), writer) + } + None => BorshSerialize::serialize(&false, writer), + } + } +} + +impl BorshDeserialize for NftClass { + fn deserialize_reader( + reader: &mut R, + ) -> std::io::Result { + use std::io::{Error, ErrorKind}; + let class_id: String = BorshDeserialize::deserialize_reader(reader)?; + let class_id = class_id.parse().map_err(|e: NftTransferError| { + Error::new(ErrorKind::InvalidData, e.to_string()) + })?; + + let is_uri: bool = BorshDeserialize::deserialize_reader(reader)?; + let class_uri = if is_uri { + let uri_str: String = BorshDeserialize::deserialize_reader(reader)?; + Some(uri_str.parse().map_err(|e: NftTransferError| { + Error::new(ErrorKind::InvalidData, e.to_string()) + })?) + } else { + None + }; + + let is_data: bool = BorshDeserialize::deserialize_reader(reader)?; + let class_data = if is_data { + let data_str: String = + BorshDeserialize::deserialize_reader(reader)?; + Some(data_str.parse().map_err(|e: NftTransferError| { + Error::new(ErrorKind::InvalidData, e.to_string()) + })?) + } else { + None + }; + + Ok(Self { + class_id, + class_uri, + class_data, + }) + } +} + +impl NftClassContext for NftClass { + fn get_id(&self) -> &ClassId { + &self.class_id.base_class_id + } + + fn get_uri(&self) -> Option<&ClassUri> { + self.class_uri.as_ref() + } + + fn get_data(&self) -> Option<&ClassData> { + self.class_data.as_ref() + } +} + +/// NFT metadata +#[derive(Clone, Debug)] +pub struct NftMetadata { + /// NFT class ID + pub class_id: PrefixedClassId, + /// NFT ID + pub token_id: TokenId, + /// NFT URI + pub token_uri: Option, + /// NFT data + pub token_data: Option, +} + +impl BorshSerialize for NftMetadata { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + BorshSerialize::serialize(&self.class_id.to_string(), writer)?; + BorshSerialize::serialize(&self.token_id.to_string(), writer)?; + match &self.token_uri { + Some(uri) => { + BorshSerialize::serialize(&true, writer)?; + BorshSerialize::serialize(&uri.to_string(), writer)?; + } + None => BorshSerialize::serialize(&false, writer)?, + } + match &self.token_data { + Some(data) => { + BorshSerialize::serialize(&true, writer)?; + BorshSerialize::serialize(&data.to_string(), writer) + } + None => BorshSerialize::serialize(&false, writer), + } + } +} + +impl BorshDeserialize for NftMetadata { + fn deserialize_reader( + reader: &mut R, + ) -> std::io::Result { + use std::io::{Error, ErrorKind}; + let class_id: String = BorshDeserialize::deserialize_reader(reader)?; + let class_id = class_id.parse().map_err(|e: NftTransferError| { + Error::new(ErrorKind::InvalidData, e.to_string()) + })?; + + let token_id: String = BorshDeserialize::deserialize_reader(reader)?; + let token_id = token_id.parse().map_err(|e: NftTransferError| { + Error::new(ErrorKind::InvalidData, e.to_string()) + })?; + + let is_uri: bool = BorshDeserialize::deserialize_reader(reader)?; + let token_uri = if is_uri { + let uri_str: String = BorshDeserialize::deserialize_reader(reader)?; + Some(uri_str.parse().map_err(|e: NftTransferError| { + Error::new(ErrorKind::InvalidData, e.to_string()) + })?) + } else { + None + }; + + let is_data: bool = BorshDeserialize::deserialize_reader(reader)?; + let token_data = if is_data { + let data_str: String = + BorshDeserialize::deserialize_reader(reader)?; + Some(data_str.parse().map_err(|e: NftTransferError| { + Error::new(ErrorKind::InvalidData, e.to_string()) + })?) + } else { + None + }; + + Ok(Self { + class_id, + token_id, + token_uri, + token_data, + }) + } +} + +impl NftContext for NftMetadata { + fn get_class_id(&self) -> &ClassId { + &self.class_id.base_class_id + } + + fn get_id(&self) -> &TokenId { + &self.token_id + } + + fn get_uri(&self) -> Option<&TokenUri> { + self.token_uri.as_ref() + } + + fn get_data(&self) -> Option<&TokenData> { + self.token_data.as_ref() + } +} diff --git a/crates/ibc/src/storage.rs b/crates/ibc/src/storage.rs index c9fa19839a..b187b2e41c 100644 --- a/crates/ibc/src/storage.rs +++ b/crates/ibc/src/storage.rs @@ -2,17 +2,17 @@ use std::str::FromStr; -use namada_core::address::{Address, InternalAddress, HASH_LEN, SHA_HASH_LEN}; -use namada_core::ibc::apps::nft_transfer::types::{PrefixedClassId, TokenId}; -use namada_core::ibc::core::client::types::Height; -use namada_core::ibc::core::host::types::identifiers::{ +use ibc::apps::nft_transfer::types::{PrefixedClassId, TokenId}; +use ibc::core::client::types::Height; +use ibc::core::host::types::identifiers::{ ChannelId, ClientId, ConnectionId, PortId, Sequence, }; -use namada_core::ibc::core::host::types::path::{ +use ibc::core::host::types::path::{ AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, ClientStatePath, CommitmentPath, ConnectionPath, Path, PortPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; +use namada_core::address::{Address, InternalAddress, HASH_LEN, SHA_HASH_LEN}; use namada_core::ibc::IbcTokenHash; use namada_core::storage::{DbKeySeg, Key, KeySeg}; use namada_core::token::Amount; diff --git a/crates/light_sdk/src/transaction/transfer.rs b/crates/light_sdk/src/transaction/transfer.rs index 3275447ac7..84475660ea 100644 --- a/crates/light_sdk/src/transaction/transfer.rs +++ b/crates/light_sdk/src/transaction/transfer.rs @@ -1,43 +1,98 @@ -use borsh_ext::BorshSerializeExt; use namada_sdk::address::Address; use namada_sdk::hash::Hash; use namada_sdk::key::common; use namada_sdk::token::DenominatedAmount; use namada_sdk::tx::data::GasLimit; -use namada_sdk::tx::{Authorization, Tx, TxError}; +use namada_sdk::tx::{ + Authorization, Tx, TxError, TX_SHIELDED_TRANSFER_WASM, + TX_SHIELDING_TRANSFER_WASM, TX_TRANSPARENT_TRANSFER_WASM, + TX_UNSHIELDING_TRANSFER_WASM, +}; use super::{attach_fee, attach_fee_signature, GlobalArgs}; use crate::transaction; -const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; - /// A transfer transaction #[derive(Debug, Clone)] pub struct Transfer(Tx); impl Transfer { - /// Build a raw Transfer transaction from the given parameters - pub fn new( + /// Build a transparent transfer transaction from the given parameters + pub fn transparent( source: Address, target: Address, token: Address, amount: DenominatedAmount, - // TODO(namada#2596): handle masp here - shielded: Option, args: GlobalArgs, ) -> Self { - let init_proposal = namada_sdk::token::Transfer { + let data = namada_sdk::token::TransparentTransfer { + source, + target, + token, + amount, + }; + + Self(transaction::build_tx( + args, + data, + TX_TRANSPARENT_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, + shielded_section_hash: Hash, + args: GlobalArgs, + ) -> Self { + let data = namada_sdk::token::ShieldingTransfer { source, + token, + amount, + shielded_section_hash, + }; + + Self(transaction::build_tx( + args, + data, + TX_SHIELDING_TRANSFER_WASM.to_string(), + )) + } + + /// Build an unshielding transfer transaction from the given parameters + pub fn unshielding( + target: Address, + token: Address, + amount: DenominatedAmount, + shielded_section_hash: Hash, + args: GlobalArgs, + ) -> Self { + let data = namada_sdk::token::UnshieldingTransfer { target, token, amount, - shielded, + shielded_section_hash, }; Self(transaction::build_tx( args, - init_proposal.serialize_to_vec(), - TX_TRANSFER_WASM.to_string(), + data, + TX_UNSHIELDING_TRANSFER_WASM.to_string(), )) } diff --git a/crates/namada/src/ledger/native_vp/ibc/mod.rs b/crates/namada/src/ledger/native_vp/ibc/mod.rs index a0e1f41bbd..485b4ea81c 100644 --- a/crates/namada/src/ledger/native_vp/ibc/mod.rs +++ b/crates/namada/src/ledger/native_vp/ibc/mod.rs @@ -419,7 +419,6 @@ mod tests { use crate::core::address::testing::{ established_address_1, established_address_2, nam, }; - use crate::core::ibc::{MsgNftTransfer, MsgTransfer}; use crate::core::storage::Epoch; use crate::ibc::apps::nft_transfer::types::events::{ RecvEvent as NftRecvEvent, TokenTraceEvent, @@ -499,7 +498,7 @@ mod tests { next_sequence_recv_key, next_sequence_send_key, nft_class_key, nft_metadata_key, receipt_key, }; - use crate::ibc::{NftClass, NftMetadata}; + use crate::ibc::{MsgNftTransfer, MsgTransfer, NftClass, NftMetadata}; use crate::key::testing::keypair_1; use crate::ledger::gas::VpGasMeter; use crate::ledger::parameters::storage::{ diff --git a/crates/namada/src/ledger/protocol/mod.rs b/crates/namada/src/ledger/protocol/mod.rs index 3b481da6a6..b19098e18d 100644 --- a/crates/namada/src/ledger/protocol/mod.rs +++ b/crates/namada/src/ledger/protocol/mod.rs @@ -7,13 +7,11 @@ use borsh_ext::BorshSerializeExt; use eyre::{eyre, WrapErr}; use namada_core::booleans::BoolResultUnitExt; use namada_core::hash::Hash; -use namada_core::storage::Key; use namada_events::extend::{ ComposeEvent, Height as HeightAttr, TxHash as TxHashAttr, }; use namada_events::EventLevel; use namada_gas::TxGasMeter; -use namada_sdk::tx::TX_TRANSFER_WASM; use namada_state::StorageWrite; use namada_token::event::{TokenEvent, TokenOperation, UserAccount}; use namada_tx::data::protocol::{ProtocolTx, ProtocolTxType}; @@ -396,22 +394,6 @@ where Ok(extended_tx_result) } -/// Load the wasm hash for a transfer from storage. -/// -/// # Panics -/// If the transaction hash is not found in storage -pub fn get_transfer_hash_from_storage(storage: &S) -> Hash -where - S: StorageRead, -{ - let transfer_code_name_key = - Key::wasm_code_name(TX_TRANSFER_WASM.to_string()); - storage - .read(&transfer_code_name_key) - .expect("Could not read the storage") - .expect("Expected tx transfer hash in storage") -} - /// Performs the required operation on a wrapper transaction: /// - replay protection /// - fee payment diff --git a/crates/node/src/bench_utils.rs b/crates/node/src/bench_utils.rs index e49c1d184b..ba4074517b 100644 --- a/crates/node/src/bench_utils.rs +++ b/crates/node/src/bench_utils.rs @@ -26,7 +26,6 @@ use namada::core::masp::{ }; use namada::core::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; use namada::core::time::DateTimeUtc; -use namada::core::token::{Amount, DenominatedAmount, Transfer}; use namada::events::extend::{ComposeEvent, MaspTxBatchRefs, MaspTxBlockIndex}; use namada::events::Event; use namada::governance::storage::proposal::ProposalType; @@ -76,6 +75,10 @@ use namada::ledger::queries::{ }; use namada::masp::MaspTxRefs; use namada::state::StorageRead; +use namada::token::{ + Amount, DenominatedAmount, ShieldedTransfer, ShieldingTransfer, + UnshieldingTransfer, +}; use namada::tx::data::pos::Bond; use namada::tx::data::{ BatchResults, BatchedTxResult, Fee, TxResult, VpsResult, @@ -91,7 +94,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, ShieldedTransfer, ShieldedUtils, + self, ContextSyncStatus, ShieldedContext, ShieldedUtils, }; pub use namada_sdk::tx::{ TX_BECOME_VALIDATOR_WASM, TX_BOND_WASM, TX_BRIDGE_POOL_WASM, @@ -101,10 +104,11 @@ 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_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, + 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, }; use namada_sdk::wallet::Wallet; use namada_sdk::{Namada, NamadaImpl}; @@ -1085,43 +1089,61 @@ impl BenchShieldedCtx { ) .unwrap() .map( - |ShieldedTransfer { + |masp::ShieldedTransfer { builder: _, masp_tx, metadata: _, epoch: _, }| masp_tx, - ); + ) + .expect("MASP must have shielded part"); let mut hasher = Sha256::new(); - let shielded_section_hash = shielded.clone().map(|transaction| { - namada::core::hash::Hash( - Section::MaspTx(transaction) - .hash(&mut hasher) - .finalize_reset() - .into(), + let shielded_section_hash = namada::core::hash::Hash( + Section::MaspTx(shielded.clone()) + .hash(&mut hasher) + .finalize_reset() + .into(), + ); + let tx = if source.effective_address() == MASP + && target.effective_address() == MASP + { + namada.client().generate_tx( + TX_SHIELDED_TRANSFER_WASM, + ShieldedTransfer { + section_hash: shielded_section_hash, + }, + Some(shielded), + None, + vec![&defaults::albert_keypair()], ) - }); - - let tx = namada.client().generate_tx( - TX_TRANSFER_WASM, - Transfer { - source: source.effective_address(), - target: target.effective_address(), - token: address::testing::nam(), - amount: if source.effective_address().eq(&MASP) - && target.effective_address().eq(&MASP) - { - DenominatedAmount::native(0.into()) - } else { - DenominatedAmount::native(amount) + } 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, }, - shielded: shielded_section_hash, - }, - shielded, - None, - vec![&defaults::albert_keypair()], - ); + Some(shielded), + None, + vec![&defaults::albert_keypair()], + ) + } 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, + }, + Some(shielded), + None, + vec![&defaults::albert_keypair()], + ) + }; let NamadaImpl { client, wallet, @@ -1180,12 +1202,13 @@ impl BenchShieldedCtx { timeout_timestamp_on_b: timeout_timestamp, }; - let transfer = - Transfer::deserialize(&mut tx.tx.data(&tx.cmt).unwrap().as_slice()) - .unwrap(); + let transfer = ShieldingTransfer::deserialize( + &mut tx.tx.data(&tx.cmt).unwrap().as_slice(), + ) + .unwrap(); let masp_tx = tx .tx - .get_section(&transfer.shielded.unwrap()) + .get_section(&transfer.shielded_section_hash) .unwrap() .masp_tx() .unwrap(); diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index c408e74085..6a3244a914 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -46,6 +46,7 @@ testing = [ "namada_ibc/testing", "namada_proof_of_stake/testing", "namada_storage/testing", + "namada_token/testing", "namada_tx/testing", "async-client", "proptest", @@ -159,6 +160,7 @@ namada_proof_of_stake = { path = "../proof_of_stake", default-features = false, namada_state = { path = "../state", features = ["testing"] } namada_storage = { path = "../storage", features = ["testing"] } namada_test_utils = { path = "../test_utils" } +namada_token = { path = "../token", features = ["testing"] } namada_tx = { path = "../tx", features = ["testing"]} namada_vote_ext = {path = "../vote_ext"} diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index 91b028a8e0..7998a93785 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -59,10 +59,12 @@ pub trait NamadaTypes: Clone + std::fmt::Debug { + From; /// Represents the address of an Ethereum endpoint type EthereumAddress: Clone + std::fmt::Debug; - /// Represents a viewing key + /// Represents a shielded viewing key type ViewingKey: Clone + std::fmt::Debug; - /// Represents a spending key + /// Represents a shielded spending key type SpendingKey: Clone + std::fmt::Debug; + /// Represents a shielded payment address + type PaymentAddress: Clone + std::fmt::Debug; /// Represents the owner of a balance type BalanceOwner: Clone + std::fmt::Debug; /// Represents a public key @@ -101,6 +103,7 @@ impl NamadaTypes for SdkTypes { type Data = Vec; type EthereumAddress = (); type Keypair = namada_core::key::common::SecretKey; + type PaymentAddress = namada_core::masp::PaymentAddress; type PublicKey = namada_core::key::common::PublicKey; type SpendingKey = namada_core::masp::ExtendedSpendingKey; type TendermintAddress = tendermint_rpc::Url; @@ -226,15 +229,15 @@ impl From for InputAmount { } } -/// Transfer transaction arguments +/// Transparent transfer transaction arguments #[derive(Clone, Debug)] -pub struct TxTransfer { +pub struct TxTransparentTransfer { /// Common tx arguments pub tx: Tx, /// Transfer source address - pub source: C::TransferSource, + pub source: C::Address, /// Transfer target address - pub target: C::TransferTarget, + pub target: C::Address, /// Transferred token address pub token: C::Address, /// Transferred token amount @@ -243,26 +246,26 @@ pub struct TxTransfer { pub tx_code_path: PathBuf, } -impl TxBuilder for TxTransfer { +impl TxBuilder for TxTransparentTransfer { fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx, { - TxTransfer { + TxTransparentTransfer { tx: func(self.tx), ..self } } } -impl TxTransfer { +impl TxTransparentTransfer { /// Transfer source address - pub fn source(self, source: C::TransferSource) -> Self { + pub fn source(self, source: C::Address) -> Self { Self { source, ..self } } /// Transfer target address - pub fn receiver(self, target: C::TransferTarget) -> Self { + pub fn receiver(self, target: C::Address) -> Self { Self { target, ..self } } @@ -285,14 +288,94 @@ impl TxTransfer { } } -impl TxTransfer { +impl TxTransparentTransfer { /// Build a transaction from this builder pub async fn build( &mut self, context: &impl Namada, - ) -> crate::error::Result<(namada_tx::Tx, SigningTxData, Option)> - { - tx::build_transfer(context, self).await + ) -> crate::error::Result<(namada_tx::Tx, SigningTxData)> { + tx::build_transparent_transfer(context, self).await + } +} + +/// Shielded transfer transaction arguments +#[derive(Clone, Debug)] +pub struct TxShieldedTransfer { + /// Common tx arguments + pub tx: Tx, + /// Transfer source spending key + pub source: C::SpendingKey, + /// Transfer target address + pub target: C::PaymentAddress, + /// Transferred token address + pub token: C::Address, + /// Transferred token amount + pub amount: InputAmount, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxShieldedTransfer { + /// Build a transaction from this builder + pub async fn build( + &mut self, + context: &impl Namada, + ) -> crate::error::Result<(namada_tx::Tx, SigningTxData)> { + tx::build_shielded_transfer(context, self).await + } +} + +/// 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, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxShieldingTransfer { + /// Build a transaction from this builder + pub async fn build( + &mut self, + context: &impl Namada, + ) -> crate::error::Result<(namada_tx::Tx, SigningTxData, Epoch)> { + tx::build_shielding_transfer(context, self).await + } +} + +/// 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 target address + pub target: C::Address, + /// Transferred token address + pub token: C::Address, + /// Transferred token amount + pub amount: InputAmount, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxUnshieldingTransfer { + /// Build a transaction from this builder + pub async fn build( + &mut self, + context: &impl Namada, + ) -> crate::error::Result<(namada_tx::Tx, SigningTxData)> { + tx::build_unshielding_transfer(context, self).await } } diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 2d6c060c4d..7832b23d33 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -21,7 +21,7 @@ pub use { bip39, masp_primitives, masp_proofs, namada_account as account, namada_gas as gas, namada_governance as governance, namada_proof_of_stake as proof_of_stake, namada_state as state, - namada_storage as storage, zeroize, + namada_storage as storage, namada_token as token, zeroize, }; pub mod eth_bridge; @@ -51,36 +51,35 @@ use std::path::PathBuf; use std::str::FromStr; use args::{InputAmount, SdkTypes}; +use io::Io; +use masp::{ShieldedContext, ShieldedUtils}; use namada_core::address::Address; use namada_core::collections::HashSet; use namada_core::dec::Dec; use namada_core::ethereum_events::EthAddress; use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_core::key::*; -use namada_core::masp::{TransferSource, TransferTarget}; +use namada_core::masp::{ExtendedSpendingKey, PaymentAddress, TransferSource}; use namada_tx::data::wrapper::GasLimit; use namada_tx::Tx; +use rpc::{denominate_amount, format_denominated_amount, query_native_token}; +use signing::SigningTxData; +use token::{DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - -use crate::io::Io; -use crate::masp::{ShieldedContext, ShieldedUtils}; -use crate::rpc::{ - denominate_amount, format_denominated_amount, query_native_token, -}; -use crate::signing::SigningTxData; -use crate::token::{DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}; -use crate::tx::{ +use tx::{ ProcessTxResponse, TX_BECOME_VALIDATOR_WASM, TX_BOND_WASM, TX_BRIDGE_POOL_WASM, TX_CHANGE_COMMISSION_WASM, 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_TRANSFER_WASM, - TX_UNBOND_WASM, TX_UNJAIL_VALIDATOR_WASM, TX_UPDATE_ACCOUNT_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, }; -use crate::wallet::{Wallet, WalletIo, WalletStorage}; +use wallet::{Wallet, WalletIo, WalletStorage}; /// Default gas-limit pub const DEFAULT_GAS_LIMIT: u64 = 25_000; @@ -172,20 +171,78 @@ pub trait Namada: Sized + MaybeSync + MaybeSend { } } - /// Make a TxTransfer builder from the given minimum set of arguments - fn new_transfer( + /// Make a TxTransparentTransfer builder from the given minimum set of + /// arguments + fn new_transparent_transfer( &self, - source: TransferSource, - target: TransferTarget, + source: Address, + target: Address, + token: Address, + amount: InputAmount, + ) -> args::TxTransparentTransfer { + args::TxTransparentTransfer { + source, + target, + token, + amount, + tx_code_path: PathBuf::from(TX_TRANSPARENT_TRANSFER_WASM), + tx: self.tx_builder(), + } + } + + /// Make a TxShieldedTransfer builder from the given minimum set of + /// arguments + fn new_shielded_transfer( + &self, + source: ExtendedSpendingKey, + target: PaymentAddress, token: Address, amount: InputAmount, - ) -> args::TxTransfer { - args::TxTransfer { + ) -> args::TxShieldedTransfer { + args::TxShieldedTransfer { source, target, token, amount, - tx_code_path: PathBuf::from(TX_TRANSFER_WASM), + tx_code_path: PathBuf::from(TX_SHIELDED_TRANSFER_WASM), + tx: self.tx_builder(), + } + } + + /// Make a TxShieldingTransfer builder from the given minimum set of + /// arguments + fn new_shielding_transfer( + &self, + source: Address, + target: PaymentAddress, + token: Address, + amount: InputAmount, + ) -> args::TxShieldingTransfer { + args::TxShieldingTransfer { + source, + target, + token, + amount, + tx_code_path: PathBuf::from(TX_SHIELDING_TRANSFER_WASM), + tx: self.tx_builder(), + } + } + + /// Make a TxUnshieldingTransfer builder from the given minimum set of + /// arguments + fn new_unshielding_transfer( + &self, + source: ExtendedSpendingKey, + target: Address, + token: Address, + amount: InputAmount, + ) -> args::TxUnshieldingTransfer { + args::TxUnshieldingTransfer { + source, + target, + token, + amount, + tx_code_path: PathBuf::from(TX_UNSHIELDING_TRANSFER_WASM), tx: self.tx_builder(), } } @@ -807,17 +864,21 @@ 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; - use namada_core::token::testing::{arb_denominated_amount, arb_transfer}; - use namada_core::token::Transfer; use namada_governance::storage::proposal::testing::{ arb_init_proposal, arb_vote_proposal, }; 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_tx::data::pgf::UpdateStewardCommission; use namada_tx::data::pos::{ BecomeValidator, Bond, CommissionChange, ConsensusKeyChange, @@ -870,8 +931,10 @@ pub mod testing { UpdateAccount(UpdateAccount), VoteProposal(VoteProposalData), Withdraw(Withdraw), - Transfer(Transfer), - MaspTransfer(Transfer, (StoredBuildParams, String)), + TransparentTransfer(TransparentTransfer), + ShieldedTransfer(ShieldedTransfer, (StoredBuildParams, String)), + ShieldingTransfer(ShieldingTransfer, (StoredBuildParams, String)), + UnshieldingTransfer(UnshieldingTransfer, (StoredBuildParams, String)), Bond(Bond), Redelegation(Redelegation), UpdateStewardCommission(UpdateStewardCommission), @@ -1027,17 +1090,17 @@ pub mod testing { prop_compose! { /// Generate an arbitrary transfer transaction - pub fn arb_transfer_tx()( + pub fn arb_transparent_transfer_tx()( mut header in arb_header(), wrapper in arb_wrapper_tx(), - transfer in arb_transfer(), + transfer in arb_transparent_transfer(), 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_TRANSFER_WASM.to_owned())); - (tx, TxData::Transfer(transfer)) + tx.add_code_from_hash(code_hash, Some(TX_TRANSPARENT_TRANSFER_WASM.to_owned())); + (tx, TxData::TransparentTransfer(transfer)) } } @@ -1059,56 +1122,62 @@ pub mod testing { Shielded, // Shielding transaction Shielding, - // Deshielding transaction - Deshielding, + // Unshielding transaction + Unshielding, } prop_compose! { /// Generate an arbitrary transfer transaction - pub fn arb_masp_transfer_tx()(transfer in arb_transfer())( + pub fn arb_masp_transfer_tx()(transfer in arb_transparent_transfer())( 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::Deshielding), arb_deshielding_transfer(encode_address(&transfer.target), 1)), + (Just(MaspTxType::Unshielding), arb_deshielding_transfer(encode_address(&transfer.target), 1)), ], - mut transfer in Just(transfer), + transfer in Just(transfer), ) -> (Tx, TxData) { header.tx_type = TxType::Wrapper(Box::new(wrapper)); let mut tx = Tx { header, sections: vec![] }; - match masp_tx_type { + let shielded_section_hash = tx.add_masp_tx_section(shielded_transfer.masp_tx).1; + let build_param_bytes = + data_encoding::HEXLOWER.encode(&build_params.serialize_to_vec()); + let tx_data = match masp_tx_type { MaspTxType::Shielded => { - transfer.source = MASP; - transfer.target = MASP; - transfer.amount = token::Amount::zero().into(); + tx.add_code_from_hash(code_hash, Some(TX_SHIELDED_TRANSFER_WASM.to_owned())); + let data = ShieldedTransfer { section_hash: shielded_section_hash }; + tx.add_data(data.clone()); + TxData::ShieldedTransfer(data, (build_params, build_param_bytes)) }, MaspTxType::Shielding => { - transfer.target = MASP; // Set the transparent amount and token let (decoded, value) = asset_types.iter().next().unwrap(); - transfer.amount = DenominatedAmount::new( + let token = decoded.token.clone(); + let amount = DenominatedAmount::new( token::Amount::from_masp_denominated(*value, decoded.position), decoded.denom, ); - transfer.token = decoded.token.clone(); + 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_data(data.clone()); + TxData::ShieldingTransfer(data, (build_params, build_param_bytes)) }, - MaspTxType::Deshielding => { - transfer.source = MASP; + MaspTxType::Unshielding => { // Set the transparent amount and token let (decoded, value) = asset_types.iter().next().unwrap(); - transfer.amount = DenominatedAmount::new( + let token = decoded.token.clone(); + let amount = DenominatedAmount::new( token::Amount::from_masp_denominated(*value, decoded.position), decoded.denom, ); - transfer.token = decoded.token.clone(); + 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_data(data.clone()); + TxData::UnshieldingTransfer(data, (build_params, build_param_bytes)) }, - } - let masp_tx_hash = tx.add_masp_tx_section(shielded_transfer.masp_tx).1; - transfer.shielded = Some(masp_tx_hash); - tx.add_data(transfer.clone()); - tx.add_code_from_hash(code_hash, Some(TX_TRANSFER_WASM.to_owned())); + }; tx.add_masp_builder(MaspBuilder { asset_types: asset_types.into_keys().collect(), // Store how the Info objects map to Descriptors/Outputs @@ -1116,11 +1185,9 @@ pub mod testing { // Store the data that was used to construct the Transaction builder: shielded_transfer.builder, // Link the Builder to the Transaction by hash code - target: masp_tx_hash, + target: shielded_section_hash, }); - let build_param_bytes = - data_encoding::HEXLOWER.encode(&build_params.serialize_to_vec()); - (tx, TxData::MaspTransfer(transfer, (build_params, build_param_bytes))) + (tx, tx_data) } } @@ -1499,7 +1566,7 @@ pub mod testing { /// Generate an arbitrary tx pub fn arb_tx() -> impl Strategy { prop_oneof![ - arb_transfer_tx(), + arb_transparent_transfer_tx(), arb_masp_transfer_tx(), arb_bond_tx(), arb_unbond_tx(), diff --git a/crates/sdk/src/signing.rs b/crates/sdk/src/signing.rs index f6c5e4f840..a6e1ef5f44 100644 --- a/crates/sdk/src/signing.rs +++ b/crates/sdk/src/signing.rs @@ -17,15 +17,13 @@ use namada_core::collections::{HashMap, HashSet}; use namada_core::key::*; use namada_core::masp::{AssetData, ExtendedViewingKey, PaymentAddress}; use namada_core::sign::SignatureIndex; -use namada_core::token; -use namada_core::token::Transfer; -// use namada_core::storage::Key; use namada_core::token::{Amount, DenominatedAmount}; use namada_governance::storage::proposal::{ InitProposalData, ProposalType, VoteProposalData, }; use namada_governance::storage::vote::ProposalVote; use namada_parameters::storage as parameter_storage; +use namada_token as token; use namada_token::storage_key::balance_key; use namada_tx::data::pgf::UpdateStewardCommission; use namada_tx::data::pos::BecomeValidator; @@ -50,10 +48,11 @@ 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_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, + 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, }; pub use crate::wallet::store::AddressVpType; use crate::wallet::{Wallet, WalletIo}; @@ -703,25 +702,61 @@ fn format_outputs(output: &mut Vec) { } } +enum TokenTransfer<'a> { + Transparent(&'a token::TransparentTransfer), + Shielded, + Shielding(&'a token::ShieldingTransfer), + Unshielding(&'a token::UnshieldingTransfer), +} + +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 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 token_and_amount(&self) -> Option<(&Address, DenominatedAmount)> { + match self { + TokenTransfer::Transparent(transfer) => { + Some((&transfer.token, transfer.amount)) + } + TokenTransfer::Shielded => None, + TokenTransfer::Shielding(transfer) => { + Some((&transfer.token, transfer.amount)) + } + TokenTransfer::Unshielding(transfer) => { + Some((&transfer.token, transfer.amount)) + } + } + } +} + /// Adds a Ledger output for the sender and destination for transparent and MASP /// transactions -pub async fn make_ledger_masp_endpoints( +async fn make_ledger_token_transfer_endpoints( tokens: &HashMap, output: &mut Vec, - transfer: &Transfer, + transfer: TokenTransfer<'_>, builder: Option<&MaspBuilder>, assets: &HashMap, ) { - if transfer.source != MASP { - output.push(format!("Sender : {}", transfer.source)); - if transfer.target == MASP { - make_ledger_amount_addr( - tokens, - output, - transfer.amount, - &transfer.token, - "Sending ", - ); + 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 "); } } else if let Some(builder) = builder { for sapling_input in builder.builder.sapling_inputs() { @@ -738,14 +773,14 @@ pub async fn make_ledger_masp_endpoints( .await; } } - if transfer.target != MASP { - output.push(format!("Destination : {}", transfer.target)); - if transfer.source == MASP { + 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, - transfer.amount, - &transfer.token, + amount, + token, "Receiving ", ); } @@ -764,15 +799,6 @@ pub async fn make_ledger_masp_endpoints( .await; } } - if transfer.source != MASP && transfer.target != MASP { - make_ledger_amount_addr( - tokens, - output, - transfer.amount, - &transfer.token, - "", - ); - } } /// Convert decimal numbers into the format used by Ledger. Specifically remove @@ -1248,8 +1274,37 @@ pub async fn to_ledger_vector( HEXLOWER.encode(&extra_code_hash.0) )]); } - } else if code_sec.tag == Some(TX_TRANSFER_WASM.to_string()) { - let transfer = Transfer::try_from_slice( + } 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()))?, ) @@ -1258,43 +1313,134 @@ pub async fn to_ledger_vector( })?; // To facilitate lookups of MASP AssetTypes let mut asset_types = HashMap::new(); - let builder = if let Some(shielded_hash) = transfer.shielded { - tx.sections.iter().find_map(|x| match x { - Section::MaspBuilder(builder) - if builder.target == shielded_hash => - { - for decoded in &builder.asset_types { - match decoded.encode() { - Err(_) => None, - Ok(asset) => { - asset_types.insert(asset, decoded.clone()); - Some(builder) - } - }?; - } - Some(builder) + 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) + } + }?; } - _ => None, - }) - } else { - None - }; + Some(builder) + } + _ => None, + }); - tv.name = "Transfer_0".to_string(); + 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( + &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 = "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.output.push("Type : Transfer".to_string()); - make_ledger_masp_endpoints( + tv.output.push("Type : UnshieldingTransfer".to_string()); + make_ledger_token_transfer_endpoints( &tokens, &mut tv.output, - &transfer, + TokenTransfer::Unshielding(&transfer), builder, &asset_types, ) .await; - make_ledger_masp_endpoints( + make_ledger_token_transfer_endpoints( &tokens, &mut tv.output_expert, - &transfer, + TokenTransfer::Unshielding(&transfer), builder, &asset_types, ) diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 5dc90cec70..afa2e5a5cf 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -35,14 +35,13 @@ use namada_core::ibc::core::channel::types::timeout::TimeoutHeight; use namada_core::ibc::core::client::types::Height as IbcHeight; use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_core::ibc::primitives::Timestamp as IbcTimestamp; -use namada_core::ibc::{is_nft_trace, MsgNftTransfer, MsgTransfer}; use namada_core::key::{self, *}; use namada_core::masp::{ AssetData, PaymentAddress, TransferSource, TransferTarget, }; +use namada_core::storage; use namada_core::storage::Epoch; use namada_core::time::DateTimeUtc; -use namada_core::{storage, token}; use namada_governance::cli::onchain::{ DefaultProposal, OnChainProposal, PgfFundingProposal, PgfStewardProposal, }; @@ -52,10 +51,12 @@ use namada_governance::storage::proposal::{ }; use namada_governance::storage::vote::ProposalVote; use namada_ibc::storage::{channel_key, ibc_token}; +use namada_ibc::{is_nft_trace, MsgNftTransfer, MsgTransfer}; use namada_proof_of_stake::parameters::{ PosParams, MAX_VALIDATOR_METADATA_LEN, }; use namada_proof_of_stake::types::{CommissionPair, ValidatorState}; +use namada_token as token; use namada_token::storage_key::balance_key; use namada_token::DenominatedAmount; use namada_tx::data::pgf::UpdateStewardCommission; @@ -101,8 +102,14 @@ pub const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; 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"; -/// Transfer transaction WASM path -pub const TX_TRANSFER_WASM: &str = "tx_transfer.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"; /// IBC transaction WASM path pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; /// User validity predicate WASM path @@ -2560,14 +2567,13 @@ 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::Transfer { - source: source.clone(), + let transfer = token::ShieldingTransfer { // The token will be escrowed to IBC address - target: Address::Internal(InternalAddress::Ibc), + source: source.clone(), token: args.token.clone(), amount: validated_amount, // Link the Transfer to the MASP Transaction by hash code - shielded: Some(masp_tx_hash), + shielded_section_hash: masp_tx_hash, }; tx.add_masp_builder(MaspBuilder { asset_types, @@ -2820,62 +2826,54 @@ pub fn build_batch( Ok((batched_tx, signing_data)) } -/// Submit an ordinary transfer -pub async fn build_transfer( +/// Build a transparent transfer +pub async fn build_transparent_transfer( context: &N, - args: &mut args::TxTransfer, -) -> Result<(Tx, SigningTxData, Option)> { - let default_signer = Some(args.source.effective_address()); + args: &mut args::TxTransparentTransfer, +) -> Result<(Tx, SigningTxData)> { + let source = &args.source; + let target = &args.target; + + let default_signer = Some(source.clone()); let signing_data = signing::aux_signing_data( context, &args.tx, - Some(args.source.effective_address()), + Some(source.clone()), default_signer, ) .await?; + // Transparent fee payment let (fee_amount, updated_balance) = - if let TransferSource::ExtendedSpendingKey(_) = args.source { - // MASP fee payment - (validate_fee(context, &args.tx).await?, None) - } else { - // Transparent fee payment - validate_transparent_fee(context, &args.tx, &signing_data.fee_payer) - .await - .map(|(fee_amount, updated_balance)| { - (fee_amount, Some(updated_balance)) - })? - }; - - // TODO(namada#2596): need multiple source/targets here for masp fee payment - // targets or leave the fees on the MASP balance - let source = args.source.effective_address(); - let target = args.target.effective_address(); + 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?; - // validate the amount given + // Validate the amount given let validated_amount = validate_amount(context, args.amount, &args.token, args.tx.force) .await?; - // If source is transparent check the balance (MASP balance is checked when - // constructing the shielded part) + // Check the balance of the source if let Some(updated_balance) = updated_balance { - let check_balance = if updated_balance.source == source + 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)) + CheckBalance::Query(balance_key(&args.token, source)) }; check_balance_too_low_err( &args.token, - &source, + source, validated_amount.amount(), check_balance, args.tx.force, @@ -2884,60 +2882,184 @@ pub async fn build_transfer( .await?; } - let masp_addr = MASP; + // Construct the corresponding transparent Transfer object + let transfer = token::TransparentTransfer { + source: source.clone(), + target: target.clone(), + token: args.token.clone(), + amount: validated_amount, + }; - // If the transaction is shielded, redact the amount and token - // types by setting the transparent value to 0 and token type to a constant. - // This has no side-effect because transaction is to self. - let (transparent_amount, transparent_token) = - if source == masp_addr && target == masp_addr { - // TODO(namada#1677): Refactor me, we shouldn't rely on any specific - // token here. - (token::Amount::zero().into(), context.native_token()) - } else { - (validated_amount, args.token.clone()) - }; + let tx = build_pow_flag( + context, + &args.tx, + args.tx_code_path.clone(), + transfer, + do_nothing, + fee_amount, + &signing_data.fee_payer, + ) + .await?; + Ok((tx, signing_data)) +} + +/// Build a shielded transfer +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?; + + // 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?; // TODO(namada#2597): this function should also take another arg as the fees // token and amount let shielded_parts = construct_shielded_parts( context, - &args.source, - &args.target, + &TransferSource::ExtendedSpendingKey(args.source), + &TransferTarget::PaymentAddress(args.target), &args.token, validated_amount, !(args.tx.dry_run || args.tx.dry_run_wrapper), ) - .await?; - let shielded_tx_epoch = shielded_parts.as_ref().map(|trans| trans.0.epoch); + .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; - // Construct the corresponding transparent Transfer object - let transfer = token::Transfer { - source: source.clone(), - target: target.clone(), - token: transparent_token.clone(), - amount: transparent_amount, - // Link the Transfer to the MASP Transaction by hash code - shielded: None, + 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.section_hash = 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 tx = build_pow_flag( + context, + &args.tx, + args.tx_code_path.clone(), + data, + add_shielded_parts, + fee_amount, + &signing_data.fee_payer, + ) + .await?; + Ok((tx, signing_data)) +} - let add_shielded = |tx: &mut Tx, transfer: &mut token::Transfer| { - // Add the MASP Transaction and its Builder to facilitate validation - if let Some(( - ShieldedTransfer { - builder, - masp_tx, - metadata, - epoch: _, - }, - asset_types, - )) = shielded_parts +/// Build a shielding transfer +pub async fn build_shielding_transfer( + context: &N, + args: &mut args::TxShieldingTransfer, +) -> Result<(Tx, SigningTxData, Epoch)> { + 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?; + + // 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)) + })?; + + // Validate the amount given + let validated_amount = + validate_amount(context, args.amount, &args.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 == args.token { - // Add a MASP Transaction section to the Tx and get the tx hash - let masp_tx_hash = tx.add_masp_tx_section(masp_tx).1; - transfer.shielded = Some(masp_tx_hash); + CheckBalance::Balance(updated_balance.post_balance) + } else { + CheckBalance::Query(balance_key(&args.token, source)) + }; - tracing::debug!("Transfer data {:?}", transfer); + check_balance_too_low_err( + &args.token, + source, + validated_amount.amount(), + check_balance, + args.tx.force, + context, + ) + .await?; + } + + let shielded_parts = construct_shielded_parts( + context, + &TransferSource::Address(source.clone()), + &TransferTarget::PaymentAddress(args.target), + &args.token, + validated_amount, + !(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, @@ -2946,17 +3068,28 @@ pub async fn build_transfer( // Store the data that was used to construct the Transaction builder, // Link the Builder to the Transaction by hash code - target: masp_tx_hash, + target: shielded_section_hash, }); + + data.shielded_section_hash = shielded_section_hash; + tracing::debug!("Transfer data {data:?}"); + Ok(()) }; - 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 tx = build_pow_flag( context, &args.tx, args.tx_code_path.clone(), - transfer, - add_shielded, + data, + add_shielded_parts, fee_amount, &signing_data.fee_payer, ) @@ -2964,6 +3097,91 @@ pub async fn build_transfer( Ok((tx, signing_data, shielded_tx_epoch)) } +/// Build an unshielding transfer +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?; + + // 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?; + + // 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, + !(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 + builder, + // Link the Builder to the Transaction by hash code + target: shielded_section_hash, + }); + + data.shielded_section_hash = 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 tx = build_pow_flag( + context, + &args.tx, + args.tx_code_path.clone(), + data, + add_shielded_parts, + fee_amount, + &signing_data.fee_payer, + ) + .await?; + Ok((tx, signing_data)) +} + // Construct the shielded part of the transaction, if any async fn construct_shielded_parts( context: &N, @@ -2993,7 +3211,7 @@ async fn construct_shielded_parts( Err(Build(builder::Error::InsufficientFunds(_))) => { return Err(TxSubmitError::NegativeBalanceAfterTransfer( Box::new(source.effective_address()), - amount.amount().to_string_native(), + amount.to_string(), Box::new(token.clone()), ) .into()); @@ -3265,10 +3483,10 @@ pub async fn build_custom( } /// Generate IBC shielded transfer -pub async fn gen_ibc_shielded_transfer( +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) @@ -3323,12 +3541,11 @@ pub async fn gen_ibc_shielded_transfer( if let Some(shielded_transfer) = shielded_transfer { let masp_tx_hash = Section::MaspTx(shielded_transfer.masp_tx.clone()).get_hash(); - let transfer = token::Transfer { + let transfer = token::ShieldingTransfer { source: source.clone(), - target: MASP, token: token.clone(), amount: validated_amount, - shielded: Some(masp_tx_hash), + shielded_section_hash: masp_tx_hash, }; Ok(Some((transfer, shielded_transfer.masp_tx))) } else { diff --git a/crates/sdk/src/wallet/mod.rs b/crates/sdk/src/wallet/mod.rs index 01db64feb8..a664c0fbe4 100644 --- a/crates/sdk/src/wallet/mod.rs +++ b/crates/sdk/src/wallet/mod.rs @@ -16,11 +16,11 @@ use bip39::{Language, Mnemonic, MnemonicType, Seed}; use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::address::Address; use namada_core::collections::{HashMap, HashSet}; -use namada_core::ibc::is_ibc_denom; use namada_core::key::*; use namada_core::masp::{ ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, }; +use namada_ibc::is_ibc_denom; pub use pre_genesis::gen_key_to_store; use rand::CryptoRng; use rand_core::RngCore; diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 02236e6f0b..f7d8678876 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -151,7 +151,15 @@ fn run_ledger_ibc() -> Result<()> { // Transfer 50000 received over IBC on Chain B let token = format!("{port_id_b}/{channel_id_b}/nam"); - transfer_on_chain(&test_b, BERTHA, ALBERT, token, 50000, BERTHA_KEY)?; + transfer_on_chain( + &test_b, + "transparent-transfer", + BERTHA, + ALBERT, + token, + 50000, + BERTHA_KEY, + )?; check_balances_after_non_ibc(&port_id_b, &channel_id_b, &test_b)?; // Transfer 50000 back from the origin-specific account on Chain B to Chain @@ -234,7 +242,15 @@ fn run_ledger_ibc_with_hermes() -> Result<()> { // Transfer 50000 received over IBC on Chain B let token = format!("{port_id_b}/{channel_id_b}/nam"); - transfer_on_chain(&test_b, BERTHA, ALBERT, token, 50000, BERTHA_KEY)?; + transfer_on_chain( + &test_b, + "transparent-transfer", + BERTHA, + ALBERT, + token, + 50000, + BERTHA_KEY, + )?; check_balances_after_non_ibc(&port_id_b, &channel_id_b, &test_b)?; // Transfer 50000 back from the origin-specific account on Chain B to Chain @@ -263,6 +279,7 @@ fn run_ledger_ibc_with_hermes() -> Result<()> { // Send a token to the shielded address on Chain A transfer_on_chain( &test_a, + "shield", ALBERT, AA_PAYMENT_ADDRESS, BTC, @@ -466,6 +483,7 @@ fn ibc_namada_gaia() -> Result<()> { // Shielded transfer on Namada transfer_on_chain( &test, + "transfer", A_SPENDING_KEY, AB_PAYMENT_ADDRESS, &ibc_denom, @@ -536,6 +554,7 @@ fn pgf_over_ibc_with_hermes() -> Result<()> { // Transfer to PGF account transfer_on_chain( &test_a, + "transparent-transfer", ALBERT, PGF_ADDRESS.to_string(), NAM, @@ -1656,6 +1675,7 @@ fn try_invalid_transfers( fn transfer_on_chain( test: &Test, + kind: impl AsRef, sender: impl AsRef, receiver: impl AsRef, token: impl AsRef, @@ -1665,7 +1685,7 @@ fn transfer_on_chain( std::env::set_var(ENV_VAR_CHAIN_ID, test.net.chain_id.to_string()); let rpc = get_actor_rpc(test, Who::Validator(0)); let tx_args = [ - "transfer", + kind.as_ref(), "--source", sender.as_ref(), "--target", diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index e0a69364e9..792d700029 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -168,7 +168,7 @@ fn test_node_connectivity_and_consensus() -> Result<()> { // 3. Submit a valid token transfer tx let tx_args = [ - "transfer", + "transparent-transfer", "--source", BERTHA, "--target", @@ -852,7 +852,7 @@ fn pos_init_validator() -> Result<()> { // 3. Submit a delegation to the new validator First, transfer some tokens // to the validator's key for fees: let tx_args = vec![ - "transfer", + "transparent-transfer", "--source", BERTHA, "--target", @@ -892,7 +892,7 @@ fn pos_init_validator() -> Result<()> { // 4. Transfer some NAM to the new validator let validator_stake_str = &validator_stake.to_string_native(); let tx_args = vec![ - "transfer", + "transparent-transfer", "--source", BERTHA, "--target", @@ -994,7 +994,7 @@ fn ledger_many_txs_in_a_block() -> Result<()> { // A token transfer tx args let tx_args = Arc::new(vec![ - "transfer", + "transparent-transfer", "--source", BERTHA, "--target", @@ -1223,7 +1223,7 @@ fn double_signing_gets_slashed() -> Result<()> { // 5. Submit a valid token transfer tx to validator 0 let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); let tx_args = [ - "transfer", + "transparent-transfer", "--source", BERTHA, "--target", @@ -2264,7 +2264,7 @@ fn rollback() -> Result<()> { // send a few transactions let txs_args = vec![vec![ - "transfer", + "transparent-transfer", "--source", BERTHA, "--target", diff --git a/crates/tests/src/integration/ledger_tests.rs b/crates/tests/src/integration/ledger_tests.rs index 23e8acf5a5..214ab32a1d 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_TRANSFER_WASM, VP_USER_WASM}; +use namada_sdk::tx::{TX_TRANSPARENT_TRANSFER_WASM, VP_USER_WASM}; use namada_test_utils::TestWasms; use test_log::test; @@ -59,7 +59,7 @@ fn ledger_txs_and_queries() -> Result<()> { let validator_one_rpc = "http://127.0.0.1:26567"; let (node, _services) = setup::setup()?; - let transfer = token::Transfer { + let transfer = token::TransparentTransfer { source: defaults::bertha_address(), target: defaults::albert_address(), token: node.native_token(), @@ -67,7 +67,6 @@ fn ledger_txs_and_queries() -> Result<()> { token::Amount::native_whole(10), token::NATIVE_MAX_DECIMAL_PLACES.into(), ), - shielded: None, } .serialize_to_vec(); let tx_data_path = node.test_dir.path().join("tx.data"); @@ -80,7 +79,7 @@ fn ledger_txs_and_queries() -> Result<()> { let txs_args = vec![ // 2. Submit a token transfer tx (from an established account) vec![ - "transfer", + "transparent-transfer", "--source", BERTHA, "--target", @@ -96,7 +95,7 @@ fn ledger_txs_and_queries() -> Result<()> { ], // Submit a token transfer tx (from an ed25519 implicit account) vec![ - "transfer", + "transparent-transfer", "--source", DAEWON, "--target", @@ -112,7 +111,7 @@ fn ledger_txs_and_queries() -> Result<()> { ], // Submit a token transfer tx (from a secp256k1 implicit account) vec![ - "transfer", + "transparent-transfer", "--source", ESTER, "--target", @@ -141,7 +140,7 @@ fn ledger_txs_and_queries() -> Result<()> { vec![ "tx", "--code-path", - TX_TRANSFER_WASM, + TX_TRANSPARENT_TRANSFER_WASM, "--data-path", &tx_data_path, "--owner", @@ -358,7 +357,7 @@ fn invalid_transactions() -> Result<()> { // 2. Submit an invalid transaction (trying to transfer tokens should fail // in the user's VP due to the wrong signer) let tx_args = vec![ - "transfer", + "transparent-transfer", "--source", BERTHA, "--target", @@ -387,7 +386,7 @@ fn invalid_transactions() -> Result<()> { let daewon_lower = DAEWON.to_lowercase(); let tx_args = vec![ - "transfer", + "transparent-transfer", "--source", DAEWON, "--signing-keys", @@ -976,7 +975,7 @@ fn proposal_submission() -> Result<()> { // vp and verify that the transaction succeeds, i.e. the non allowlisted // vp can still run let transfer = vec![ - "transfer", + "transparent-transfer", "--source", BERTHA, "--target", @@ -1387,7 +1386,7 @@ fn implicit_account_reveal_pk() -> Result<()> { // A token transfer tx Box::new(|source| { [ - "transfer", + "transparent-transfer", "--source", source, "--target", @@ -1444,7 +1443,7 @@ fn implicit_account_reveal_pk() -> Result<()> { let tx_args = tx_args(&key_alias); // 2b. Send some funds to the implicit account. let credit_args = vec![ - "transfer", + "transparent-transfer", "--source", BERTHA, "--target", @@ -1620,7 +1619,7 @@ fn enforce_fee_payment() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "transparent-transfer", "--source", ALBERT_KEY, "--target", @@ -1656,7 +1655,7 @@ fn enforce_fee_payment() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "transparent-transfer", "--source", ALBERT_KEY, "--target", diff --git a/crates/tests/src/integration/masp.rs b/crates/tests/src/integration/masp.rs index 4912e3624a..ab534b0114 100644 --- a/crates/tests/src/integration/masp.rs +++ b/crates/tests/src/integration/masp.rs @@ -43,7 +43,7 @@ fn masp_incentives() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "shield", "--source", ALBERT, "--target", @@ -259,7 +259,7 @@ fn masp_incentives() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "shield", "--source", ALBERT, "--target", @@ -397,7 +397,7 @@ fn masp_incentives() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "unshield", "--source", B_SPENDING_KEY, "--target", @@ -507,7 +507,7 @@ fn masp_incentives() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "unshield", "--source", A_SPENDING_KEY, "--target", @@ -677,7 +677,7 @@ fn masp_incentives() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "unshield", "--source", B_SPENDING_KEY, "--target", @@ -708,7 +708,7 @@ fn masp_incentives() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "unshield", "--source", A_SPENDING_KEY, "--target", @@ -824,7 +824,7 @@ fn spend_unconverted_asset_type() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "shield", "--source", ALBERT, "--target", @@ -845,7 +845,7 @@ fn spend_unconverted_asset_type() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "shield", "--source", ALBERT, "--target", @@ -993,7 +993,7 @@ fn masp_txs_and_queries() -> Result<()> { // 1. Attempt to spend 15 BTC at SK(A) to Bertha ( vec![ - "transfer", + "unshield", "--source", A_SPENDING_KEY, "--target", @@ -1012,7 +1012,7 @@ fn masp_txs_and_queries() -> Result<()> { // 2. Send 20 BTC from Albert to PA(A) ( vec![ - "transfer", + "shield", "--source", ALBERT, "--target", @@ -1163,7 +1163,7 @@ fn masp_txs_and_queries() -> Result<()> { // 11. Send 20 BTC from SK(B) to Bertha ( vec![ - "transfer", + "unshield", "--source", B_SPENDING_KEY, "--target", @@ -1184,7 +1184,10 @@ fn masp_txs_and_queries() -> Result<()> { for (tx_args, tx_result) in &txs_args { node.assert_success(); // there is no need to dry run balance queries - let dry_run_args = if tx_args[0] == "transfer" { + let dry_run_args = if tx_args[0] == "transfer" + || tx_args[0] == "shield" + || tx_args[0] == "unshield" + { // We ensure transfers don't cross epoch boundaries. node.next_epoch(); vec![true, false] @@ -1198,7 +1201,7 @@ fn masp_txs_and_queries() -> Result<()> { Bin::Client, vec!["shielded-sync", "--node", validator_one_rpc], )?; - let tx_args = if dry_run && tx_args[0] == "transfer" { + let tx_args = if dry_run { [tx_args.clone(), vec!["--dry-run"]].concat() } else { tx_args.clone() @@ -1314,7 +1317,7 @@ fn multiple_unfetched_txs_same_block() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "shield", "--source", ALBERT_KEY, "--target", @@ -1333,7 +1336,7 @@ fn multiple_unfetched_txs_same_block() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "shield", "--source", ALBERT_KEY, "--target", @@ -1352,7 +1355,7 @@ fn multiple_unfetched_txs_same_block() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "shield", "--source", ALBERT_KEY, "--target", @@ -1548,7 +1551,7 @@ fn cross_epoch_unshield() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "shield", "--source", ALBERT, "--target", @@ -1583,7 +1586,7 @@ fn cross_epoch_unshield() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "unshield", "--source", A_SPENDING_KEY, "--target", @@ -1689,7 +1692,7 @@ fn dynamic_assets() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "shield", "--source", ALBERT, "--target", @@ -1823,7 +1826,7 @@ fn dynamic_assets() -> Result<()> { &node, Bin::Client, vec![ - "transfer", + "shield", "--source", ALBERT, "--target", diff --git a/crates/token/Cargo.toml b/crates/token/Cargo.toml index b06ab6086d..69c663680b 100644 --- a/crates/token/Cargo.toml +++ b/crates/token/Cargo.toml @@ -15,14 +15,21 @@ version.workspace = true [features] default = [] multicore = ["namada_shielded_token/multicore"] -testing = ["namada_core/testing"] +testing = ["namada_core/testing", "proptest"] [dependencies] namada_core = { path = "../core" } namada_events = { path = "../events", default-features = false } +namada_macros = { path = "../macros" } namada_shielded_token = { path = "../shielded_token" } namada_storage = { path = "../storage" } namada_trans_token = { path = "../trans_token" } +borsh.workspace = true +proptest = { workspace = true, optional = true } +serde.workspace = true + [dev-dependencies] namada_core = { path = "../core", features = ["testing"] } + +proptest.workspace = true \ No newline at end of file diff --git a/crates/token/src/lib.rs b/crates/token/src/lib.rs index 2ce15898a5..fb3b58f357 100644 --- a/crates/token/src/lib.rs +++ b/crates/token/src/lib.rs @@ -18,8 +18,12 @@ clippy::print_stderr )] +use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use namada_core::hash::Hash; +use namada_macros::BorshDeserializer; pub use namada_shielded_token::*; pub use namada_trans_token::*; +use serde::{Deserialize, Serialize}; /// Token storage keys pub mod storage_key { @@ -28,8 +32,6 @@ pub mod storage_key { } use namada_core::address::Address; -#[cfg(any(test, feature = "testing"))] -pub use namada_core::token::testing; use namada_events::EmitEvents; use namada_storage::{Result, StorageRead, StorageWrite}; @@ -64,3 +66,134 @@ where } Ok(()) } + +/// Arguments for a transparent token transfer +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, + Hash, + Eq, + PartialOrd, + 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, +} + +/// 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, +} + +/// Arguments for a shielding transfer (from a transparent token to a shielded +/// token) +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + BorshSchema, + Hash, + Eq, + PartialOrd, + Serialize, + Deserialize, +)] +pub struct ShieldingTransfer { + /// 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"))] +/// Testing helpers and strategies for tokens +pub mod testing { + use namada_core::address::testing::{ + arb_established_address, arb_non_internal_address, + }; + use namada_core::address::Address; + pub use namada_core::token::*; + pub use namada_trans_token::testing::*; + use proptest::prelude::*; + + use super::TransparentTransfer; + + prop_compose! { + /// Generate a transparent transfer + pub 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 { + source, + target, + token, + amount, + } + } + } +} diff --git a/crates/tx_prelude/src/lib.rs b/crates/tx_prelude/src/lib.rs index eb0f6a3be9..3b737c8157 100644 --- a/crates/tx_prelude/src/lib.rs +++ b/crates/tx_prelude/src/lib.rs @@ -37,7 +37,7 @@ use namada_core::storage::TxIndex; pub use namada_core::storage::{ self, BlockHash, BlockHeight, Epoch, Header, BLOCK_HASH_LENGTH, }; -pub use namada_core::{encode, eth_bridge_pool, *}; +pub use namada_core::{address, encode, eth_bridge_pool, *}; use namada_events::{EmitEvents, Event, EventToEmit, EventType}; pub use namada_governance::storage as gov_storage; pub use namada_macros::transaction; diff --git a/crates/tx_prelude/src/token.rs b/crates/tx_prelude/src/token.rs index b30954aa1c..ab9ecaf571 100644 --- a/crates/tx_prelude/src/token.rs +++ b/crates/tx_prelude/src/token.rs @@ -5,13 +5,14 @@ use namada_events::{EmitEvents, EventLevel}; #[cfg(any(test, feature = "testing"))] pub use namada_token::testing; pub use namada_token::{ - storage_key, utils, Amount, DenominatedAmount, Transfer, + storage_key, utils, Amount, DenominatedAmount, ShieldedTransfer, + ShieldingTransfer, TransparentTransfer, UnshieldingTransfer, }; use namada_tx_env::TxEnv; use crate::{Ctx, TxResult}; -/// A token transfer that can be used in a transaction. +/// A transparent token transfer that can be used in a transaction. pub fn transfer( ctx: &mut Ctx, src: &Address, diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 99bb9183e2..006b77e01b 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -83,55 +83,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anstream" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" - -[[package]] -name = "anstyle-parse" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] - [[package]] name = "anyhow" version = "1.0.75" @@ -839,46 +790,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "clap" -version = "4.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.52", -] - -[[package]] -name = "clap_lex" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" - [[package]] name = "clru" version = "0.5.0" @@ -936,12 +847,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "colorchoice" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" - [[package]] name = "concat-idents" version = "1.1.5" @@ -3382,12 +3287,6 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" -[[package]] -name = "is_terminal_polyfill" -version = "1.70.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" - [[package]] name = "itertools" version = "0.11.0" @@ -4239,11 +4138,15 @@ dependencies = [ name = "namada_token" version = "0.38.1" dependencies = [ + "borsh 1.4.0", "namada_core", "namada_events", + "namada_macros", "namada_shielded_token", "namada_storage", "namada_trans_token", + "proptest", + "serde", ] [[package]] @@ -5716,9 +5619,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -5773,9 +5676,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -6986,7 +6889,25 @@ dependencies = [ ] [[package]] -name = "tx_transfer" +name = "tx_shielded_transfer" +version = "0.38.1" +dependencies = [ + "getrandom 0.2.15", + "namada_tx_prelude", + "rlsf", +] + +[[package]] +name = "tx_shielding_transfer" +version = "0.38.1" +dependencies = [ + "getrandom 0.2.15", + "namada_tx_prelude", + "rlsf", +] + +[[package]] +name = "tx_transparent_transfer" version = "0.38.1" dependencies = [ "getrandom 0.2.15", @@ -7020,6 +6941,15 @@ dependencies = [ "rlsf", ] +[[package]] +name = "tx_unshielding_transfer" +version = "0.38.1" +dependencies = [ + "getrandom 0.2.15", + "namada_tx_prelude", + "rlsf", +] + [[package]] name = "tx_update_account" version = "0.38.1" @@ -7202,12 +7132,6 @@ dependencies = [ "serde", ] -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - [[package]] name = "uuid" version = "0.8.2" @@ -7646,15 +7570,14 @@ dependencies = [ [[package]] name = "webc" -version = "6.0.0-alpha9" +version = "6.0.0-rc1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b4e8dd987046eede4348d660404ff990412631b7d493f9e547adcf2862cd5" +checksum = "c1fc686c7b43c9bc630a499f6ae1f0a4c4bd656576a53ae8a147b0cc9bc983ad" dependencies = [ "anyhow", "base64 0.21.7", "bytes", "cfg-if", - "clap", "document-features", "flate2", "indexmap 1.9.3", diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 54cf93dadc..c39334600b 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -16,12 +16,15 @@ members = [ "tx_reactivate_validator", "tx_redelegate", "tx_resign_steward", - "tx_transfer", + "tx_reveal_pk", + "tx_shielded_transfer", + "tx_shielding_transfer", + "tx_transparent_transfer", "tx_unbond", + "tx_unjail_validator", + "tx_unshielding_transfer", "tx_update_account", - "tx_reveal_pk", "tx_update_steward_commission", - "tx_unjail_validator", "tx_vote_proposal", "tx_withdraw", "vp_implicit", diff --git a/wasm/tx_ibc/src/lib.rs b/wasm/tx_ibc/src/lib.rs index a91ef16f91..e131a90c9a 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.and_then(|transfer| transfer.shielded) + transfer.map(|transfer| transfer.shielded_section_hash) { let shielded = tx_data .tx diff --git a/wasm/tx_transfer/Cargo.toml b/wasm/tx_shielded_transfer/Cargo.toml similarity index 92% rename from wasm/tx_transfer/Cargo.toml rename to wasm/tx_shielded_transfer/Cargo.toml index 868a5e6538..8398b69750 100644 --- a/wasm/tx_transfer/Cargo.toml +++ b/wasm/tx_shielded_transfer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tx_transfer" +name = "tx_shielded_transfer" description = "WASM transaction to transfer tokens" authors.workspace = true edition.workspace = true diff --git a/wasm/tx_shielded_transfer/src/lib.rs b/wasm/tx_shielded_transfer/src/lib.rs new file mode 100644 index 0000000000..cc9e70a638 --- /dev/null +++ b/wasm/tx_shielded_transfer/src/lib.rs @@ -0,0 +1,29 @@ +//! 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 new file mode 100644 index 0000000000..54dca625ec --- /dev/null +++ b/wasm/tx_shielding_transfer/Cargo.toml @@ -0,0 +1,18 @@ +[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 new file mode 100644 index 0000000000..389942686b --- /dev/null +++ b/wasm/tx_shielding_transfer/src/lib.rs @@ -0,0 +1,39 @@ +//! 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_transfer/src/lib.rs b/wasm/tx_transfer/src/lib.rs deleted file mode 100644 index 62e7adebac..0000000000 --- a/wasm/tx_transfer/src/lib.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! A tx for token transfer. -//! This tx uses `token::Transfer` 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 transfer = token::Transfer::try_from_slice(&data[..]) - .wrap_err("Failed to decode token::Transfer 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")?; - - if let Some(masp_section_ref) = transfer.shielded { - 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 new file mode 100644 index 0000000000..7408b86688 --- /dev/null +++ b/wasm/tx_transparent_transfer/Cargo.toml @@ -0,0 +1,18 @@ +[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 new file mode 100644 index 0000000000..76aacc3891 --- /dev/null +++ b/wasm/tx_transparent_transfer/src/lib.rs @@ -0,0 +1,22 @@ +//! 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 new file mode 100644 index 0000000000..03d9aa125d --- /dev/null +++ b/wasm/tx_unshielding_transfer/Cargo.toml @@ -0,0 +1,18 @@ +[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 new file mode 100644 index 0000000000..79bdac0757 --- /dev/null +++ b/wasm/tx_unshielding_transfer/src/lib.rs @@ -0,0 +1,39 @@ +//! 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(()) +} diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index 330d229b52..34884148da 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -4188,11 +4188,15 @@ dependencies = [ name = "namada_token" version = "0.38.1" dependencies = [ + "borsh 1.2.1", "namada_core", "namada_events", + "namada_macros", "namada_shielded_token", "namada_storage", "namada_trans_token", + "proptest", + "serde", ] [[package]]