From fe6c3eb53bf972b204110d3cd34db2719f96bd25 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 19 Sep 2023 08:36:26 +0200 Subject: [PATCH 01/14] Combined construction of signing data with transaction construction. --- apps/src/lib/client/tx.rs | 314 ++++++++------------------------------ shared/src/sdk/tx.rs | 278 ++++++++++++++++++++++++--------- 2 files changed, 270 insertions(+), 322 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index c8c42b190b..e00583e72a 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -146,25 +146,13 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let default_signer = Some(args.owner.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.owner.clone()), - default_signer, - ) - .await?; + submit_reveal_aux::<_, IO>(client, ctx, args.tx.clone(), &args.owner).await?; - submit_reveal_aux::<_, IO>(client, ctx, args.tx.clone(), &args.owner) - .await?; - - let (mut tx, _epoch) = tx::build_custom::<_, _, _, IO>( + let (mut tx, signing_data, _epoch) = tx::build_custom::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, args.clone(), - &signing_data.fee_payer, ) .await?; @@ -191,22 +179,11 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let default_signer = Some(args.addr.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.addr.clone()), - default_signer, - ) - .await?; - - let (mut tx, _epoch) = tx::build_update_account::<_, _, _, IO>( + let (mut tx, signing_data, _epoch) = tx::build_update_account::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, args.clone(), - signing_data.fee_payer.clone(), ) .await?; @@ -233,21 +210,11 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - None, - None, - ) - .await?; - - let (mut tx, _epoch) = tx::build_init_account::<_, _, _, IO>( + let (mut tx, signing_data, _epoch) = tx::build_init_account::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, args.clone(), - &signing_data.fee_payer, ) .await?; @@ -714,16 +681,6 @@ pub async fn submit_transfer< args: args::TxTransfer, ) -> Result<(), error::Error> { for _ in 0..2 { - let default_signer = Some(args.source.effective_address()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.source.effective_address()), - default_signer, - ) - .await?; - submit_reveal_aux::<_, IO>( client, &mut ctx, @@ -732,13 +689,11 @@ pub async fn submit_transfer< ) .await?; - let arg = args.clone(); - let (mut tx, tx_epoch) = tx::build_transfer::<_, _, _, IO>( + let (mut tx, signing_data, tx_epoch) = tx::build_transfer::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, - arg, - signing_data.fee_payer.clone(), + args.clone(), ) .await?; signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) @@ -795,25 +750,13 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let default_signer = Some(args.source.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.source.clone()), - default_signer, - ) - .await?; - - submit_reveal_aux::<_, IO>(client, &mut ctx, args.tx.clone(), &args.source) - .await?; + submit_reveal_aux::<_, IO>(client, &mut ctx, args.tx.clone(), &args.source).await?; - let (mut tx, _epoch) = tx::build_ibc_transfer::<_, _, _, IO>( + let (mut tx, signing_data, _epoch) = tx::build_ibc_transfer::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, args.clone(), - signing_data.fee_payer.clone(), ) .await?; signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) @@ -842,7 +785,7 @@ where let current_epoch = rpc::query_and_print_epoch::<_, IO>(client).await; let governance_parameters = rpc::query_governance_parameters(client).await; - let ((mut tx_builder, _fee_unshield_epoch), signing_data) = if args + let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args .is_offline { let proposal = OfflineProposal::try_from(args.proposal_data.as_ref()) @@ -889,16 +832,6 @@ where .validate(&governance_parameters, current_epoch, args.tx.force) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(proposal.proposal.author.clone()), - default_signer, - ) - .await?; - submit_reveal_aux::<_, IO>( client, &mut ctx, @@ -907,18 +840,14 @@ where ) .await?; - ( - tx::build_pgf_funding_proposal::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - proposal, - &signing_data.fee_payer.clone(), - ) - .await?, - signing_data, + tx::build_pgf_funding_proposal::<_, _, _, IO>( + client, + &mut ctx.wallet, + &mut ctx.shielded, + args.clone(), + proposal, ) + .await? } else if args.is_pgf_stewards { let proposal = PgfStewardProposal::try_from( args.proposal_data.as_ref(), @@ -941,16 +870,6 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(proposal.proposal.author.clone()), - default_signer, - ) - .await?; - submit_reveal_aux::<_, IO>( client, &mut ctx, @@ -959,18 +878,14 @@ where ) .await?; - ( - tx::build_pgf_stewards_proposal::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - proposal, - signing_data.fee_payer.clone(), - ) - .await?, - signing_data, + tx::build_pgf_stewards_proposal::<_, _, _, IO>( + client, + &mut ctx.wallet, + &mut ctx.shielded, + args.clone(), + proposal, ) + .await? } else { let proposal = DefaultProposal::try_from(args.proposal_data.as_ref()) .map_err(|e| { @@ -991,16 +906,6 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(proposal.proposal.author.clone()), - default_signer, - ) - .await?; - submit_reveal_aux::<_, IO>( client, &mut ctx, @@ -1009,18 +914,14 @@ where ) .await?; - ( - tx::build_default_proposal::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - proposal, - signing_data.fee_payer.clone(), - ) - .await?, - signing_data, + tx::build_default_proposal::<_, _, _, IO>( + client, + &mut ctx.wallet, + &mut ctx.shielded, + args.clone(), + proposal, ) + .await? }; signing::generate_test_vector::<_, _, IO>( client, @@ -1059,19 +960,17 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let current_epoch = rpc::query_and_print_epoch::<_, IO>(client).await; - - let default_signer = Some(args.voter.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.voter.clone()), - default_signer.clone(), - ) - .await?; - - let (mut tx_builder, _fee_unshield_epoch) = if args.is_offline { + let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline { + let default_signer = Some(args.voter.clone()); + let signing_data = aux_signing_data::<_, IO>( + client, + &mut ctx.wallet, + &args.tx, + Some(args.voter.clone()), + default_signer.clone(), + ) + .await?; + let proposal_vote = ProposalVote::try_from(args.vote) .map_err(|_| error::TxError::InvalidProposalVote)?; @@ -1113,15 +1012,15 @@ where display_line!(IO, "Proposal vote serialized to: {}", output_file_path); return Ok(()); } else { + let current_epoch = rpc::query_and_print_epoch::(client).await; tx::build_vote_proposal::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, args.clone(), current_epoch, - signing_data.fee_payer.clone(), ) - .await? + .await? }; signing::generate_test_vector::<_, _, IO>( client, @@ -1269,25 +1168,13 @@ where C::Error: std::fmt::Display, { let default_address = args.source.clone().unwrap_or(args.validator.clone()); - let default_signer = Some(default_address.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(default_address.clone()), - default_signer, - ) - .await?; - - submit_reveal_aux::<_, IO>(client, ctx, args.tx.clone(), &default_address) - .await?; + submit_reveal_aux::<_, IO>(client, ctx, args.tx.clone(), &default_address).await?; - let (mut tx, _fee_unshield_epoch) = tx::build_bond::<_, _, _, IO>( + let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_bond::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, args.clone(), - signing_data.fee_payer.clone(), ) .await?; signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) @@ -1297,9 +1184,8 @@ where tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx).await?; } Ok(()) @@ -1314,24 +1200,12 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let default_address = args.source.clone().unwrap_or(args.validator.clone()); - let default_signer = Some(default_address.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(default_address), - default_signer, - ) - .await?; - - let (mut tx, _fee_unshield_epoch, latest_withdrawal_pre) = + let (mut tx, signing_data, _fee_unshield_epoch, latest_withdrawal_pre) = tx::build_unbond::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, args.clone(), - signing_data.fee_payer.clone(), ) .await?; signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) @@ -1361,23 +1235,11 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let default_address = args.source.clone().unwrap_or(args.validator.clone()); - let default_signer = Some(default_address.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(default_address), - default_signer, - ) - .await?; - - let (mut tx, _fee_unshield_epoch) = tx::build_withdraw::<_, _, _, IO>( + let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_withdraw::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, args.clone(), - signing_data.fee_payer.clone(), ) .await?; signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) @@ -1403,27 +1265,14 @@ pub async fn submit_validator_commission_change( where C: namada::ledger::queries::Client + Sync, { - let default_signer = Some(args.validator.clone()); - let signing_data = aux_signing_data::<_, IO>( + let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_validator_commission_change::<_, _, _, IO>( client, &mut ctx.wallet, - &args.tx, - Some(args.validator.clone()), - default_signer, + &mut ctx.shielded, + args.clone(), ) .await?; - - let (mut tx, _fee_unshield_epoch) = - tx::build_validator_commission_change::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); @@ -1448,27 +1297,14 @@ pub async fn submit_unjail_validator< where C::Error: std::fmt::Display, { - let default_signer = Some(args.validator.clone()); - let signing_data = aux_signing_data::<_, IO>( + let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_unjail_validator::<_, _, _, IO>( client, &mut ctx.wallet, - &args.tx, - Some(args.validator.clone()), - default_signer, + &mut ctx.shielded, + args.clone(), ) .await?; - - let (mut tx, _fee_unshield_epoch) = - tx::build_unjail_validator::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); @@ -1494,26 +1330,14 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let default_signer = Some(args.steward.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( + let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_update_steward_commission::<_, _, _, IO>( client, &mut ctx.wallet, - &args.tx, - Some(args.steward.clone()), - default_signer, + &mut ctx.shielded, + args.clone(), ) .await?; - let (mut tx, _fee_unshield_epoch) = - tx::build_update_steward_commission::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - &signing_data.fee_payer, - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) .await?; @@ -1537,26 +1361,14 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let default_signer = Some(args.steward.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( + let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_resign_steward::<_, _, _, IO>( client, &mut ctx.wallet, - &args.tx, - Some(args.steward.clone()), - default_signer, + &mut ctx.shielded, + args.clone(), ) .await?; - let (mut tx, _fee_unshield_epoch) = - tx::build_resign_steward::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - &signing_data.fee_payer, - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) .await?; diff --git a/shared/src/sdk/tx.rs b/shared/src/sdk/tx.rs index 9d7fe0cfe4..5885ebda44 100644 --- a/shared/src/sdk/tx.rs +++ b/shared/src/sdk/tx.rs @@ -42,6 +42,7 @@ use crate::ibc::core::timestamp::Timestamp as IbcTimestamp; use crate::ibc::core::Msg; use crate::ibc::Height as IbcHeight; use crate::ledger::ibc::storage::ibc_denom_key; +use crate::sdk::signing::SigningTxData; use crate::proto::{MaspBuilder, Tx}; use crate::sdk::args::{self, InputAmount}; use crate::sdk::error::{EncodingError, Error, QueryError, Result, TxError}; @@ -530,8 +531,17 @@ pub async fn build_validator_commission_change< rate, tx_code_path, }: args::CommissionRateChange, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(validator.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx_args, + Some(validator.clone()), + default_signer, + ) + .await?; + let epoch = rpc::query_epoch(client).await?; let params: PosParams = rpc::get_pos_params(client).await?; @@ -605,10 +615,10 @@ pub async fn build_validator_commission_change< tx_code_path, data, do_nothing, - &fee_payer, + &signing_data.fee_payer, None, ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Craft transaction to update a steward commission @@ -627,8 +637,17 @@ pub async fn build_update_steward_commission< commission, tx_code_path, }: args::UpdateStewardCommission, - gas_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(steward.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx_args, + Some(steward.clone()), + default_signer, + ) + .await?; + if !rpc::is_steward(client, &steward).await && !tx_args.force { edisplay_line!(IO, "The given address {} is not a steward.", &steward); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); @@ -660,10 +679,10 @@ pub async fn build_update_steward_commission< tx_code_path, data, do_nothing, - gas_payer, + &signing_data.fee_payer, None, ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Craft transaction to resign as a steward @@ -681,8 +700,17 @@ pub async fn build_resign_steward< steward, tx_code_path, }: args::ResignSteward, - gas_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(steward.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx_args, + Some(steward.clone()), + default_signer, + ) + .await?; + if !rpc::is_steward(client, &steward).await && !tx_args.force { edisplay_line!(IO, "The given address {} is not a steward.", &steward); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); @@ -696,10 +724,10 @@ pub async fn build_resign_steward< tx_code_path, steward, do_nothing, - gas_payer, + &signing_data.fee_payer, None, ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit transaction to unjail a jailed validator @@ -717,8 +745,17 @@ pub async fn build_unjail_validator< validator, tx_code_path, }: args::TxUnjailValidator, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(validator.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx_args, + Some(validator.clone()), + default_signer, + ) + .await?; + if !rpc::is_validator(client, &validator).await? { edisplay_line!( IO, @@ -803,10 +840,10 @@ pub async fn build_unjail_validator< tx_code_path, validator, do_nothing, - &fee_payer, + &signing_data.fee_payer, None, ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit transaction to withdraw an unbond @@ -825,8 +862,18 @@ pub async fn build_withdraw< source, tx_code_path, }: args::Withdraw, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_address = source.clone().unwrap_or(validator.clone()); + let default_signer = Some(default_address.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx_args, + Some(default_address), + default_signer, + ) + .await?; + let epoch = rpc::query_epoch(client).await?; let validator = known_validator_or_err::<_, IO>( @@ -879,10 +926,10 @@ pub async fn build_withdraw< tx_code_path, data, do_nothing, - &fee_payer, + &signing_data.fee_payer, None, ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit a transaction to unbond @@ -902,8 +949,18 @@ pub async fn build_unbond< source, tx_code_path, }: args::Unbond, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option, Option<(Epoch, token::Amount)>)> { +) -> Result<(Tx, SigningTxData, Option, Option<(Epoch, token::Amount)>)> { + let default_address = source.clone().unwrap_or(validator.clone()); + let default_signer = Some(default_address.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx_args, + Some(default_address), + default_signer, + ) + .await?; + let source = source.clone(); // Check the source's current bond amount let bond_source = source.clone().unwrap_or_else(|| validator.clone()); @@ -969,11 +1026,11 @@ pub async fn build_unbond< tx_code_path, data, do_nothing, - &fee_payer, + &signing_data.fee_payer, None, ) .await?; - Ok((tx, epoch, latest_withdrawal_pre)) + Ok((tx, signing_data, epoch, latest_withdrawal_pre)) } /// Query the unbonds post-tx @@ -1062,14 +1119,21 @@ pub async fn build_bond< native_token, tx_code_path, }: args::Bond, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { - let validator = known_validator_or_err::<_, IO>( - validator.clone(), - tx_args.force, +) -> Result<(Tx, SigningTxData, Option)> { + let default_address = source.clone().unwrap_or(validator.clone()); + let default_signer = Some(default_address.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( client, + wallet, + &tx_args, + Some(default_address.clone()), + default_signer, ) - .await?; + .await?; + + let validator = + known_validator_or_err::<_, IO>(validator.clone(), tx_args.force, client) + .await?; // Check that the source address exists on chain let source = match source.clone() { @@ -1115,10 +1179,10 @@ pub async fn build_bond< tx_code_path, data, do_nothing, - &fee_payer, + &signing_data.fee_payer, tx_source_balance, ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a default proposal governance @@ -1141,8 +1205,17 @@ pub async fn build_default_proposal< tx_code_path, }: args::InitProposal, proposal: DefaultProposal, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(proposal.proposal.author.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx, + Some(proposal.proposal.author.clone()), + default_signer, + ) + .await?; + let init_proposal_data = InitProposalData::try_from(proposal.clone()) .map_err(|e| TxError::InvalidProposal(e.to_string()))?; @@ -1168,10 +1241,10 @@ pub async fn build_default_proposal< tx_code_path, init_proposal_data, push_data, - &fee_payer, + &signing_data.fee_payer, None, // TODO: need to pay the fee to submit a proposal ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a proposal vote @@ -1194,8 +1267,17 @@ pub async fn build_vote_proposal< tx_code_path, }: args::VoteProposal, epoch: Epoch, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(voter.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx, + Some(voter.clone()), + default_signer.clone(), + ) + .await?; + let proposal_vote = ProposalVote::try_from(vote) .map_err(|_| TxError::InvalidProposalVote)?; @@ -1255,10 +1337,10 @@ pub async fn build_vote_proposal< tx_code_path, data, do_nothing, - &fee_payer, + &signing_data.fee_payer, None, ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a pgf funding proposal governance @@ -1281,8 +1363,17 @@ pub async fn build_pgf_funding_proposal< tx_code_path, }: args::InitProposal, proposal: PgfFundingProposal, - fee_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(proposal.proposal.author.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx, + Some(proposal.proposal.author.clone()), + default_signer, + ) + .await?; + let init_proposal_data = InitProposalData::try_from(proposal.clone()) .map_err(|e| TxError::InvalidProposal(e.to_string()))?; @@ -1300,10 +1391,10 @@ pub async fn build_pgf_funding_proposal< tx_code_path, init_proposal_data, add_section, - fee_payer, + &signing_data.fee_payer, None, // TODO: need to pay the fee to submit a proposal ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a pgf funding proposal governance @@ -1326,8 +1417,17 @@ pub async fn build_pgf_stewards_proposal< tx_code_path, }: args::InitProposal, proposal: PgfStewardProposal, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(proposal.proposal.author.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx, + Some(proposal.proposal.author.clone()), + default_signer, + ) + .await?; + let init_proposal_data = InitProposalData::try_from(proposal.clone()) .map_err(|e| TxError::InvalidProposal(e.to_string()))?; @@ -1346,10 +1446,10 @@ pub async fn build_pgf_stewards_proposal< tx_code_path, init_proposal_data, add_section, - &fee_payer, + &signing_data.fee_payer, None, // TODO: need to pay the fee to submit a proposal ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit an IBC transfer @@ -1363,8 +1463,16 @@ pub async fn build_ibc_transfer< wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::TxIbcTransfer, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(args.source.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &args.tx, + Some(args.source.clone()), + default_signer, + ) + .await?; // Check that the source address exists on chain let source = source_exists_or_err::<_, IO>( args.source.clone(), @@ -1486,12 +1594,12 @@ pub async fn build_ibc_transfer< shielded, &args.tx, &mut tx, - fee_payer, + signing_data.fee_payer.clone(), tx_source_balance, ) .await?; - Ok((tx, epoch)) + Ok((tx, signing_data, epoch)) } /// Abstraction for helping build transactions @@ -1663,8 +1771,17 @@ pub async fn build_transfer< wallet: &mut Wallet, shielded: &mut ShieldedContext, mut args: args::TxTransfer, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(args.source.effective_address()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &args.tx, + Some(args.source.effective_address()), + default_signer, + ) + .await?; + let source = args.source.effective_address(); let target = args.target.effective_address(); let token = args.token.clone(); @@ -1796,7 +1913,7 @@ pub async fn build_transfer< args.tx_code_path, transfer, add_shielded, - &fee_payer, + &signing_data.fee_payer, tx_source_balance, ) .await?; @@ -1819,7 +1936,7 @@ pub async fn build_transfer< (None, Some(_transfer_unshield_epoch)) => shielded_tx_epoch, (None, None) => None, }; - Ok((tx, masp_epoch)) + Ok((tx, signing_data, masp_epoch)) } /// Submit a transaction to initialize an account @@ -1839,10 +1956,11 @@ pub async fn build_init_account< public_keys, threshold, }: args::TxInitAccount, - fee_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { - let vp_code_hash = - query_wasm_code_hash_buf::<_, IO>(client, &vp_code_path).await?; +) -> Result<(Tx, SigningTxData, Option)> { + let signing_data = + signing::aux_signing_data::<_, _, IO>(client, wallet, &tx_args, None, None).await?; + + let vp_code_hash = query_wasm_code_hash_buf::<_, IO>(client, &vp_code_path).await?; let threshold = match threshold { Some(threshold) => threshold, @@ -1875,10 +1993,10 @@ pub async fn build_init_account< tx_code_path, data, add_code_hash, - fee_payer, + &signing_data.fee_payer, None, ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit a transaction to update a VP @@ -1899,8 +2017,17 @@ pub async fn build_update_account< public_keys, threshold, }: args::TxUpdateAccount, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(addr.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx_args, + Some(addr.clone()), + default_signer, + ) + .await?; + let addr = if let Some(account) = rpc::get_account_info(client, &addr).await? { account.address @@ -1945,10 +2072,10 @@ pub async fn build_update_account< tx_code_path, data, add_code_hash, - &fee_payer, + &signing_data.fee_payer, None, ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit a custom transaction @@ -1966,10 +2093,19 @@ pub async fn build_custom< code_path, data_path, serialized_tx, - owner: _, + owner, }: args::TxCustom, - fee_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(owner.clone()); + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + &tx_args, + Some(owner.clone()), + default_signer, + ) + .await?; + let mut tx = if let Some(serialized_tx) = serialized_tx { Tx::deserialize(serialized_tx.as_ref()).map_err(|_| { Error::Other("Invalid tx deserialization.".to_string()) @@ -1994,12 +2130,12 @@ pub async fn build_custom< shielded, &tx_args, &mut tx, - fee_payer.clone(), + signing_data.fee_payer.clone(), None, ) .await?; - Ok((tx, epoch)) + Ok((tx, signing_data, epoch)) } async fn expect_dry_broadcast< From 16425d50944fd5405381677e51dd1892d02dd61b Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 20 Sep 2023 13:26:07 +0200 Subject: [PATCH 02/14] Created builders and constructors for each type of transaction. --- apps/src/lib/cli.rs | 8 +- apps/src/lib/cli/client.rs | 54 +- apps/src/lib/client/rpc.rs | 28 +- apps/src/lib/client/tx.rs | 462 +++------- benches/lib.rs | 15 +- shared/src/ledger/eth_bridge/bridge_pool.rs | 65 +- shared/src/ledger/eth_bridge/validator_set.rs | 4 +- shared/src/ledger/mod.rs | 410 +++++++++ shared/src/sdk/args.rs | 770 +++++++++++++++- shared/src/sdk/masp.rs | 50 +- shared/src/sdk/rpc.rs | 26 +- shared/src/sdk/signing.rs | 169 ++-- shared/src/sdk/tx.rs | 870 +++++++----------- shared/src/types/io.rs | 4 +- tests/src/e2e/ledger_tests.rs | 6 +- tests/src/integration/masp.rs | 10 +- 16 files changed, 1827 insertions(+), 1124 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 135ff1e3c5..531027a102 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -15,7 +15,7 @@ pub mod wallet; use clap::{ArgGroup, ArgMatches, ColorChoice}; use color_eyre::eyre::Result; -use namada::types::io::DefaultIo; +use namada::types::io::StdIo; use utils::*; pub use utils::{safe_exit, Cmd}; @@ -3525,8 +3525,8 @@ pub mod args { target, token, amount, - native_token: (), tx_code_path, + native_token: (), } } @@ -3881,8 +3881,8 @@ pub mod args { validator, amount, source, - native_token: (), tx_code_path, + native_token: (), } } @@ -5799,7 +5799,7 @@ pub fn namada_relayer_cli() -> Result { cmds::EthBridgePool::WithContext(sub_cmd), ) => { let global_args = args::Global::parse(&matches); - let context = Context::new::(global_args)?; + let context = Context::new::(global_args)?; Ok(NamadaRelayer::EthBridgePoolWithCtx(Box::new(( sub_cmd, context, )))) diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 1a7d9f534a..5af2aaa2b2 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -1,9 +1,11 @@ use color_eyre::eyre::{eyre, Report, Result}; -use namada::ledger::eth_bridge::bridge_pool; + use namada::sdk::tx::dump_tx; -use namada::sdk::{signing, tx as sdk_tx}; +use namada::sdk::signing; use namada::types::control_flow::ProceedOrElse; use namada::types::io::Io; +use namada::ledger::NamadaImpl; +use namada::ledger::Namada; use crate::cli; use crate::cli::api::{CliApi, CliClient}; @@ -256,58 +258,30 @@ impl CliApi { let args = args.to_sdk(&mut ctx); let tx_args = args.tx.clone(); - let default_signer = Some(args.sender.clone()); - let signing_data = tx::aux_signing_data::<_, IO>( + let mut namada = NamadaImpl::new( &client, &mut ctx.wallet, - &args.tx, - Some(args.sender.clone()), - default_signer, - ) - .await?; + &mut ctx.shielded, + ); - let (mut tx, _epoch) = - bridge_pool::build_bridge_pool_tx::<_, _, _, IO>( - &client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; + let (mut tx, signing_data, _epoch) = + args.clone().build(&mut namada).await?; - signing::generate_test_vector::<_, _, IO>( - &client, - &mut ctx.wallet, - &tx, - ) - .await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { dump_tx::(&args.tx, tx); } else { - tx::submit_reveal_aux::<_, IO>( - &client, - &mut ctx, + tx::submit_reveal_aux( + &mut namada, tx_args.clone(), &args.sender, ) .await?; - signing::sign_tx( - &mut ctx.wallet, - &tx_args, - &mut tx, - signing_data, - )?; + namada.sign(&mut tx, &tx_args, signing_data)?; - sdk_tx::process_tx::<_, _, IO>( - &client, - &mut ctx.wallet, - &tx_args, - tx, - ) - .await?; + namada.submit(tx, &tx_args).await?; } } Sub::TxUnjailValidator(TxUnjailValidator(mut args)) => { diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d750ccc759..72d6514c35 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -173,7 +173,7 @@ pub async fn query_transfers< // Realize the rewards that would have been attained upon the // transaction's reception let amt = shielded - .compute_exchanged_amount::<_, IO>( + .compute_exchanged_amount( client, amt, epoch, @@ -224,7 +224,7 @@ pub async fn query_transfers< IO, " {}{} {}", sign, - format_denominated_amount::<_, IO>( + format_denominated_amount( client, asset, change.into(), @@ -252,7 +252,7 @@ pub async fn query_transfers< IO, " {}{} {}", sign, - format_denominated_amount::<_, IO>( + format_denominated_amount( client, &token_addr, val.into(), @@ -358,7 +358,7 @@ pub async fn query_transparent_balance< .await { Ok(balance) => { - let balance = format_denominated_amount::<_, IO>( + let balance = format_denominated_amount( client, &token, balance, ) .await; @@ -380,7 +380,7 @@ pub async fn query_transparent_balance< for (token_alias, token) in tokens { let balance = get_token_balance(client, &token, &owner).await; if !balance.is_zero() { - let balance = format_denominated_amount::<_, IO>( + let balance = format_denominated_amount( client, &token, balance, ) .await; @@ -518,7 +518,7 @@ pub async fn query_pinned_balance< token_alias ); } else { - let formatted = format_denominated_amount::<_, IO>( + let formatted = format_denominated_amount( client, token, total_balance.into(), @@ -552,7 +552,7 @@ pub async fn query_pinned_balance< ); found_any = true; } - let formatted = format_denominated_amount::<_, IO>( + let formatted = format_denominated_amount( client, token_addr, (*value).into(), @@ -599,7 +599,7 @@ async fn print_balances( owner.clone(), format!( ": {}, owned by {}", - format_denominated_amount::<_, IO>(client, tok, balance) + format_denominated_amount(client, tok, balance) .await, wallet.lookup_alias(owner) ), @@ -785,7 +785,7 @@ pub async fn query_shielded_balance< IO, "{}: {}", token_alias, - format_denominated_amount::<_, IO>( + format_denominated_amount( client, &token, token::Amount::from(total_balance) @@ -853,7 +853,7 @@ pub async fn query_shielded_balance< .map(|a| a.to_string()) .unwrap_or_else(|| token.to_string()); display_line!(IO, "Shielded Token {}:", alias); - let formatted = format_denominated_amount::<_, IO>( + let formatted = format_denominated_amount( client, &token, token_balance.into(), @@ -904,7 +904,7 @@ pub async fn query_shielded_balance< if !val.is_zero() { found_any = true; } - let formatted = format_denominated_amount::<_, IO>( + let formatted = format_denominated_amount( client, address, (*val).into(), @@ -975,7 +975,7 @@ pub async fn print_decoded_balance< IO, "{} : {}", wallet.lookup_alias(token_addr), - format_denominated_amount::<_, IO>( + format_denominated_amount( client, token_addr, (*amount).into() @@ -1009,7 +1009,7 @@ pub async fn print_decoded_balance_with_epoch< "{} | {} : {}", alias, epoch, - format_denominated_amount::<_, IO>(client, token_addr, asset_value) + format_denominated_amount(client, token_addr, asset_value) .await, ); } @@ -2195,7 +2195,7 @@ pub async fn query_wasm_code_hash< client: &C, code_path: impl AsRef, ) -> Result { - rpc::query_wasm_code_hash::<_, IO>(client, code_path).await + rpc::query_wasm_code_hash(client, code_path).await } /// Query a storage value and decode it with [`BorshDeserialize`]. diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index e00583e72a..4b2aa9b865 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -36,39 +36,33 @@ use crate::node::ledger::tendermint_node; use crate::wallet::{ gen_validator_keys, read_and_confirm_encryption_password, CliWalletUtils, }; +use namada::ledger::NamadaImpl; +use namada::ledger::Namada; +use namada::types::io::StdIo; /// Wrapper around `signing::aux_signing_data` that stores the optional /// disposable address to the wallet -pub async fn aux_signing_data< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn aux_signing_data<'a>( + context: &mut impl Namada<'a, WalletUtils = CliWalletUtils>, args: &args::Tx, owner: Option
, default_signer: Option
, ) -> Result { - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - args, - owner, - default_signer, - ) - .await?; + let signing_data = + signing::aux_signing_data(context, args, owner, default_signer) + .await?; if args.disposable_signing_key { if !(args.dry_run || args.dry_run_wrapper) { // Store the generated signing key to wallet in case of need - crate::wallet::save(wallet).map_err(|_| { + crate::wallet::save(context.wallet).map_err(|_| { error::Error::Other( "Failed to save disposable address to wallet".to_string(), ) })?; } else { display_line!( - IO, + StdIo, "Transaction dry run. The disposable address will not be \ saved to wallet." ) @@ -79,12 +73,8 @@ pub async fn aux_signing_data< } // Build a transaction to reveal the signer of the given transaction. -pub async fn submit_reveal_aux< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - ctx: &mut Context, +pub async fn submit_reveal_aux<'a>( + context: &mut impl Namada<'a>, args: args::Tx, address: &Address, ) -> Result<(), error::Error> { @@ -93,44 +83,28 @@ pub async fn submit_reveal_aux< } if let Address::Implicit(ImplicitAddress(pkh)) = address { - let key = ctx + let key = context .wallet .find_key_by_pkh(pkh, args.clone().password) .map_err(|e| error::Error::Other(e.to_string()))?; let public_key = key.ref_to(); - if tx::is_reveal_pk_needed::(client, address, args.force).await? { - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args, - None, - None, - ) - .await?; - - let (mut tx, _epoch) = tx::build_reveal_pk::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, + if tx::is_reveal_pk_needed(context.client, address, args.force).await? { + println!( + "Submitting a tx to reveal the public key for address {address}..." + ); + let (mut tx, signing_data, _epoch) = tx::build_reveal_pk( + context, &args, - address, &public_key, - &signing_data.fee_payer, ) .await?; - signing::generate_test_vector::<_, _, IO>( - client, - &mut ctx.wallet, - &tx, - ) - .await?; + signing::generate_test_vector(context, &tx).await?; - signing::sign_tx(&mut ctx.wallet, &args, &mut tx, signing_data)?; + context.sign(&mut tx, &args, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args, tx) - .await?; + context.submit(tx, &args).await?; } } @@ -146,25 +120,19 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - submit_reveal_aux::<_, IO>(client, ctx, args.tx.clone(), &args.owner).await?; - - let (mut tx, signing_data, _epoch) = tx::build_custom::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + submit_reveal_aux(&mut namada, args.tx.clone(), &args.owner).await?; + + let (mut tx, signing_data, _epoch) = args.build(&mut namada).await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data)?; + namada.submit(tx, &args.tx).await?; } Ok(()) @@ -179,23 +147,17 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, signing_data, _epoch) = tx::build_update_account::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let (mut tx, signing_data, _epoch) = args.build(&mut namada).await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data)?; + namada.submit(tx, &args.tx).await?; } Ok(()) @@ -210,23 +172,18 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, signing_data, _epoch) = tx::build_init_account::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; - - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let (mut tx, signing_data, _epoch) = tx::build_init_account(&mut namada, &args) .await?; + signing::generate_test_vector(&mut namada, &tx).await?; + if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data)?; + namada.submit(tx, &args.tx).await?; } Ok(()) @@ -438,19 +395,13 @@ where tx.add_code_from_hash(tx_code_hash).add_data(data); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - &mut ctx.wallet, - &tx_args, - None, - None, - ) - .await?; + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let signing_data = + aux_signing_data(&mut namada, &tx_args, None, None).await?; - tx::prepare_tx::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, + tx::prepare_tx( + &mut namada, &tx_args, &mut tx, signing_data.fee_payer.clone(), @@ -458,18 +409,14 @@ where ) .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector(&mut namada, &tx).await?; if tx_args.dump_tx { tx::dump_tx::(&tx_args, tx); } else { - signing::sign_tx(&mut ctx.wallet, &tx_args, &mut tx, signing_data)?; + namada.sign(&mut tx, &tx_args, signing_data)?; - let result = - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &tx_args, tx) - .await? - .initialized_accounts(); + let result = namada.submit(tx, &tx_args).await?.initialized_accounts(); if !tx_args.dry_run { let (validator_address_alias, validator_address) = match &result[..] @@ -681,36 +628,25 @@ pub async fn submit_transfer< args: args::TxTransfer, ) -> Result<(), error::Error> { for _ in 0..2 { - submit_reveal_aux::<_, IO>( - client, - &mut ctx, + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + + submit_reveal_aux( + &mut namada, args.tx.clone(), &args.source.effective_address(), ) .await?; - let (mut tx, signing_data, tx_epoch) = tx::build_transfer::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + let (mut tx, signing_data, tx_epoch) = args.clone().build(&mut namada).await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); break; } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - let result = tx::process_tx::<_, _, IO>( - client, - &mut ctx.wallet, - &args.tx, - tx, - ) - .await?; + namada.sign(&mut tx, &args.tx, signing_data)?; + let result = namada.submit(tx, &args.tx).await?; let submission_epoch = rpc::query_and_print_epoch::<_, IO>(client).await; @@ -750,24 +686,17 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - submit_reveal_aux::<_, IO>(client, &mut ctx, args.tx.clone(), &args.source).await?; - - let (mut tx, signing_data, _epoch) = tx::build_ibc_transfer::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + submit_reveal_aux(&mut namada, args.tx.clone(), &args.source).await?; + let (mut tx, signing_data, _epoch) = args.build(&mut namada).await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data)?; + namada.submit(tx, &args.tx).await?; } Ok(()) @@ -784,7 +713,8 @@ where { let current_epoch = rpc::query_and_print_epoch::<_, IO>(client).await; let governance_parameters = rpc::query_governance_parameters(client).await; - + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args .is_offline { @@ -798,9 +728,8 @@ where .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; let default_signer = Some(proposal.author.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, + let signing_data = aux_signing_data( + &mut namada, &args.tx, Some(proposal.author.clone()), default_signer, @@ -832,19 +761,16 @@ where .validate(&governance_parameters, current_epoch, args.tx.force) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - submit_reveal_aux::<_, IO>( - client, - &mut ctx, + submit_reveal_aux( + &mut namada, args.tx.clone(), &proposal.proposal.author, ) .await?; - tx::build_pgf_funding_proposal::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), + tx::build_pgf_funding_proposal( + &mut namada, + &args, proposal, ) .await? @@ -870,19 +796,16 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - submit_reveal_aux::<_, IO>( - client, - &mut ctx, + submit_reveal_aux( + &mut namada, args.tx.clone(), &proposal.proposal.author, ) .await?; - tx::build_pgf_stewards_proposal::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), + tx::build_pgf_stewards_proposal( + &mut namada, + &args, proposal, ) .await? @@ -906,46 +829,27 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - submit_reveal_aux::<_, IO>( - client, - &mut ctx, + submit_reveal_aux( + &mut namada, args.tx.clone(), &proposal.proposal.author, ) .await?; - tx::build_default_proposal::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), + tx::build_default_proposal( + &mut namada, + &args, proposal, ) .await? }; - signing::generate_test_vector::<_, _, IO>( - client, - &mut ctx.wallet, - &tx_builder, - ) - .await?; + signing::generate_test_vector(&mut namada, &tx_builder).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx_builder); } else { - signing::sign_tx( - &mut ctx.wallet, - &args.tx, - &mut tx_builder, - signing_data, - )?; - tx::process_tx::<_, _, IO>( - client, - &mut ctx.wallet, - &args.tx, - tx_builder, - ) - .await?; + namada.sign(&mut tx_builder, &args.tx, signing_data)?; + namada.submit(tx_builder, &args.tx).await?; } Ok(()) @@ -960,11 +864,12 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline { let default_signer = Some(args.voter.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, + let signing_data = aux_signing_data( + &mut namada, &args.tx, Some(args.voter.clone()), default_signer.clone(), @@ -1012,39 +917,15 @@ where display_line!(IO, "Proposal vote serialized to: {}", output_file_path); return Ok(()); } else { - let current_epoch = rpc::query_and_print_epoch::(client).await; - tx::build_vote_proposal::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - current_epoch, - ) - .await? + args.build(&mut namada).await? }; - signing::generate_test_vector::<_, _, IO>( - client, - &mut ctx.wallet, - &tx_builder, - ) - .await?; + signing::generate_test_vector(&mut namada, &tx_builder).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx_builder); } else { - signing::sign_tx( - &mut ctx.wallet, - &args.tx, - &mut tx_builder, - signing_data, - )?; - tx::process_tx::<_, _, IO>( - client, - &mut ctx.wallet, - &args.tx, - tx_builder, - ) - .await?; + namada.sign(&mut tx_builder, &args.tx, signing_data)?; + namada.submit(tx_builder, &args.tx).await?; } Ok(()) @@ -1069,11 +950,11 @@ where edisplay_line!(IO, "Couldn't decode the transaction."); safe_exit(1) }; - + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let default_signer = Some(owner.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, + let signing_data = aux_signing_data( + &mut namada, &tx_args, Some(owner.clone()), default_signer, @@ -1147,13 +1028,9 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - submit_reveal_aux::<_, IO>( - client, - ctx, - args.tx, - &(&args.public_key).into(), - ) - .await?; + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + submit_reveal_aux(&mut namada, args.tx, &(&args.public_key).into()).await?; Ok(()) } @@ -1167,25 +1044,20 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let default_address = args.source.clone().unwrap_or(args.validator.clone()); - submit_reveal_aux::<_, IO>(client, ctx, args.tx.clone(), &default_address).await?; + submit_reveal_aux(&mut namada, args.tx.clone(), &default_address).await?; - let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_bond::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + let (mut tx, signing_data, _fee_unshield_epoch) = args.build(&mut namada).await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx).await?; + namada.submit(tx, &args.tx).await?; } Ok(()) @@ -1200,24 +1072,18 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _fee_unshield_epoch, latest_withdrawal_pre) = - tx::build_unbond::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + args.build(&mut namada).await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.submit(tx, &args.tx).await?; tx::query_unbonds::<_, IO>(client, args.clone(), latest_withdrawal_pre) .await?; @@ -1235,23 +1101,18 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_withdraw::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let (mut tx, signing_data, _fee_unshield_epoch) = + args.build(&mut namada).await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.submit(tx, &args.tx).await?; } Ok(()) @@ -1265,22 +1126,18 @@ pub async fn submit_validator_commission_change( where C: namada::ledger::queries::Client + Sync, { - let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_validator_commission_change::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx).await?; + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let (mut tx, signing_data, _fee_unshield_epoch) = + args.build(&mut namada).await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.submit(tx, &args.tx).await?; } Ok(()) @@ -1297,22 +1154,18 @@ pub async fn submit_unjail_validator< where C::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_unjail_validator::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx).await?; + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let (mut tx, signing_data, _fee_unshield_epoch) = + args.build(&mut namada).await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.submit(tx, &args.tx).await?; } Ok(()) @@ -1330,23 +1183,18 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_update_steward_commission::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let (mut tx, signing_data, _fee_unshield_epoch) = + args.build(&mut namada).await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data)?; + namada.submit(tx, &args.tx).await?; } Ok(()) @@ -1361,23 +1209,17 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, signing_data, _fee_unshield_epoch) = tx::build_resign_steward::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - ) - .await?; + let mut namada = + NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let (mut tx, signing_data, _epoch) = args.build(&mut namada).await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data)?; + namada.submit(tx, &args.tx).await?; } Ok(()) diff --git a/benches/lib.rs b/benches/lib.rs index 47645abdf4..b420d24a43 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -80,7 +80,7 @@ use namada::tendermint::Hash; use namada::tendermint_rpc::{self}; use namada::types::address::InternalAddress; use namada::types::chain::ChainId; -use namada::types::io::DefaultIo; +use namada::types::io::StdIo; use namada::types::masp::{ ExtendedViewingKey, PaymentAddress, TransferSource, TransferTarget, }; @@ -104,6 +104,7 @@ use namada_test_utils::tx_data::TxWriteData; use rand_core::OsRng; use sha2::{Digest, Sha256}; use tempfile::TempDir; +use namada::ledger::NamadaImpl; pub const WASM_DIR: &str = "../wasm"; pub const TX_BOND_WASM: &str = "tx_bond.wasm"; @@ -682,7 +683,7 @@ impl Default for BenchShieldedCtx { let mut shell = BenchShell::default(); let mut ctx = - Context::new::(namada_apps::cli::args::Global { + Context::new::(namada_apps::cli::args::Global { chain_id: None, base_dir: shell.tempdir.as_ref().canonicalize().unwrap(), wasm_dir: Some(WASM_DIR.into()), @@ -803,11 +804,13 @@ impl BenchShieldedCtx { &[], )) .unwrap(); + let mut namada = NamadaImpl::new( + &self.shell, + &mut self.wallet, + &mut self.shielded, + ); let shielded = async_runtime - .block_on( - self.shielded - .gen_shielded_transfer::<_, DefaultIo>(&self.shell, args), - ) + .block_on(ShieldedContext::::gen_shielded_transfer(&mut namada, &args)) .unwrap() .map( |ShieldedTransfer { diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index b9573cab97..38cfcd51cb 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -9,7 +9,6 @@ use borsh::BorshSerialize; use ethbridge_bridge_contract::Bridge; use ethers::providers::Middleware; use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; -use namada_core::types::key::common; use namada_core::types::storage::Epoch; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; @@ -23,10 +22,8 @@ use crate::ledger::queries::{ use crate::proto::Tx; use crate::sdk::args; use crate::sdk::error::Error; -use crate::sdk::masp::{ShieldedContext, ShieldedUtils}; use crate::sdk::rpc::{query_wasm_code_hash, validate_amount}; use crate::sdk::tx::prepare_tx; -use crate::sdk::wallet::{Wallet, WalletUtils}; use crate::types::address::Address; use crate::types::control_flow::time::{Duration, Instant}; use crate::types::control_flow::{ @@ -41,17 +38,14 @@ use crate::types::keccak::KeccakHash; use crate::types::token::{Amount, DenominatedAmount}; use crate::types::voting_power::FractionalVotingPower; use crate::{display, display_line}; +use crate::ledger::Namada; +use crate::sdk::signing::aux_signing_data; +use crate::sdk::signing::SigningTxData; + /// Craft a transaction that adds a transfer to the Ethereum bridge pool. -pub async fn build_bridge_pool_tx< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_bridge_pool_tx<'a>( + context: &mut impl Namada<'a>, args::EthereumBridgePool { tx: tx_args, nut, @@ -64,11 +58,18 @@ pub async fn build_bridge_pool_tx< fee_token, code_path, }: args::EthereumBridgePool, - wrapper_fee_payer: common::PublicKey, -) -> Result<(Tx, Option), Error> { +) -> Result<(Tx, SigningTxData, Option), Error> { + let default_signer = Some(sender.clone()); + let signing_data = aux_signing_data( + context, + &tx_args, + Some(sender.clone()), + default_signer, + ) + .await?; let fee_payer = fee_payer.unwrap_or_else(|| sender.clone()); - let DenominatedAmount { amount, .. } = validate_amount::<_, IO>( - client, + let DenominatedAmount { amount, .. } = validate_amount( + context.client, amount, &wrapped_erc20s::token(&asset), tx_args.force, @@ -77,7 +78,7 @@ pub async fn build_bridge_pool_tx< .map_err(|e| Error::Other(format!("Failed to validate amount. {}", e)))?; let DenominatedAmount { amount: fee_amount, .. - } = validate_amount::<_, IO>(client, fee_amount, &fee_token, tx_args.force) + } = validate_amount(context.client, fee_amount, &fee_token, tx_args.force) .await .map_err(|e| { Error::Other(format!( @@ -105,7 +106,7 @@ pub async fn build_bridge_pool_tx< }; let tx_code_hash = - query_wasm_code_hash::<_, IO>(client, code_path.to_str().unwrap()) + query_wasm_code_hash(context.client, code_path.to_str().unwrap()) .await .unwrap(); @@ -115,18 +116,16 @@ pub async fn build_bridge_pool_tx< // TODO(namada#1800): validate the tx on the client side - let epoch = prepare_tx::( - client, - wallet, - shielded, + let epoch = prepare_tx( + context, &tx_args, &mut tx, - wrapper_fee_payer, + signing_data.fee_payer.clone(), None, ) .await?; - Ok((tx, epoch)) + Ok((tx, signing_data, epoch)) } /// A json serializable representation of the Ethereum @@ -913,7 +912,7 @@ mod recommendations { use super::*; use crate::types::control_flow::ProceedOrElse; - use crate::types::io::DefaultIo; + use crate::types::io::StdIo; /// An established user address for testing & development pub fn bertha_address() -> Address { @@ -1019,7 +1018,7 @@ mod recommendations { signed_pool: &mut signed_pool, expected_eligible: &mut expected, }); - let eligible = generate_eligible::( + let eligible = generate_eligible::( &table, &in_progress, signed_pool, @@ -1114,7 +1113,7 @@ mod recommendations { let profitable = vec![transfer(100_000); 17]; let hash = profitable[0].keccak256().to_string(); let expected = vec![hash; 17]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations::( process_transfers(profitable), &Default::default(), Uint::from_u64(800_000), @@ -1133,7 +1132,7 @@ mod recommendations { let hash = transfers[0].keccak256().to_string(); transfers.push(transfer(0)); let expected: Vec<_> = vec![hash; 17]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations::( process_transfers(transfers), &Default::default(), Uint::from_u64(800_000), @@ -1151,7 +1150,7 @@ mod recommendations { let transfers = vec![transfer(75_000); 4]; let hash = transfers[0].keccak256().to_string(); let expected = vec![hash; 2]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations::( process_transfers(transfers), &Default::default(), Uint::from_u64(50_000), @@ -1173,7 +1172,7 @@ mod recommendations { .map(|t| t.keccak256().to_string()) .take(5) .collect(); - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations::( process_transfers(transfers), &Default::default(), Uint::from_u64(150_000), @@ -1192,7 +1191,7 @@ mod recommendations { let hash = transfers[0].keccak256().to_string(); let expected = vec![hash; 4]; transfers.extend([transfer(17_500), transfer(17_500)]); - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations::( process_transfers(transfers), &Default::default(), Uint::from_u64(150_000), @@ -1208,7 +1207,7 @@ mod recommendations { #[test] fn test_wholly_infeasible() { let transfers = vec![transfer(75_000); 4]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations::( process_transfers(transfers), &Default::default(), Uint::from_u64(300_000), @@ -1289,7 +1288,7 @@ mod recommendations { const VALIDATOR_GAS_FEE: Uint = Uint::from_u64(100_000); - let recommended_batch = generate_recommendations::( + let recommended_batch = generate_recommendations::( eligible, &conversion_table, // gas spent by validator signature checks diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 4ae08dd598..be99e130f8 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -26,7 +26,7 @@ use crate::types::control_flow::{ self, install_shutdown_signal, Halt, TryHalt, }; use crate::types::ethereum_events::EthAddress; -use crate::types::io::{DefaultIo, Io}; +use crate::types::io::{Io, StdIo}; use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; use crate::{display_line, edisplay_line}; @@ -513,7 +513,7 @@ where time::sleep(sleep_for).await; let is_synchronizing = - eth_sync_or::<_, _, _, DefaultIo>(&*eth_client, || ()) + eth_sync_or::<_, _, _, StdIo>(&*eth_client, || ()) .await .is_break(); if is_synchronizing { diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 04b5809bc2..505699d707 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -17,3 +17,413 @@ pub mod vp_host_fns; pub use namada_core::ledger::{ gas, parameters, replay_protection, storage_api, tx_env, vp_env, }; + +use crate::sdk::wallet::{Wallet, WalletUtils}; +use crate::sdk::masp::{ShieldedContext, ShieldedUtils}; +use crate::types::masp::{TransferSource, TransferTarget}; +use crate::types::address::Address; +use crate::sdk::args::{self, InputAmount}; +use crate::sdk::tx::{ + TX_TRANSFER_WASM, TX_REVEAL_PK, TX_BOND_WASM, TX_UNBOND_WASM, TX_IBC_WASM, + TX_INIT_PROPOSAL, TX_UPDATE_ACCOUNT_WASM, TX_VOTE_PROPOSAL, VP_USER_WASM, + TX_CHANGE_COMMISSION_WASM, TX_INIT_VALIDATOR_WASM, TX_UNJAIL_VALIDATOR_WASM, + TX_WITHDRAW_WASM, TX_BRIDGE_POOL_WASM, TX_RESIGN_STEWARD, + TX_UPDATE_STEWARD_COMMISSION, self, +}; +use std::path::PathBuf; +use crate::types::transaction::GasLimit; +use crate::sdk::signing::{SigningTxData, self}; +use crate::proto::Tx; +use crate::types::key::*; +use crate::types::token; +use crate::sdk::tx::ProcessTxResponse; +use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; +use crate::types::token::NATIVE_MAX_DECIMAL_PLACES; +use std::str::FromStr; +use std::ops::{Deref, DerefMut}; +use namada_core::types::dec::Dec; +use namada_core::types::ethereum_events::EthAddress; + +/// Encapsulates a Namada session to enable splitting borrows of its parts +pub struct NamadaStruct<'a, C, U, V> where + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, + V: ShieldedUtils, +{ + /// Used to send and receive messages from the ledger + pub client: &'a C, + /// Stores the addresses and keys required for ledger interactions + pub wallet: &'a mut Wallet, + /// Stores the current state of the shielded pool + pub shielded: &'a mut ShieldedContext, +} + +#[async_trait::async_trait(?Send)] +/// An interface for high-level interaction with the Namada SDK +pub trait Namada<'a> : DerefMut> { + /// A client with async request dispatcher method + type Client: 'a + crate::ledger::queries::Client + Sync; + /// Captures the interactive parts of the wallet's functioning + type WalletUtils: 'a + WalletUtils; + /// Abstracts platform specific details away from the logic of shielded pool + /// operations. + type ShieldedUtils: 'a + ShieldedUtils; + + /// Make a tx builder using no arguments + fn tx_builder(&mut self) -> args::Tx { + args::Tx { + dry_run: false, + dry_run_wrapper: false, + dump_tx: false, + output_folder: None, + force: false, + broadcast_only: false, + ledger_address: (), + initialized_account_alias: None, + wallet_alias_force: false, + fee_amount: None, + wrapper_fee_payer: None, + fee_token: self.wallet.find_address(args::NAM).expect("NAM not in wallet").clone(), + fee_unshield: None, + gas_limit: GasLimit::from(20_000), + expiration: None, + disposable_signing_key: false, + chain_id: None, + signing_keys: vec![], + signatures: vec![], + tx_reveal_code_path: PathBuf::from(TX_REVEAL_PK), + verification_key: None, + password: None, + } + } + + /// Make a TxTransfer builder from the given minimum set of arguments + fn new_transfer( + &mut self, + source: TransferSource, + target: TransferTarget, + token: Address, + amount: InputAmount, + ) -> args::TxTransfer { + args::TxTransfer { + source, + target, + token, + amount, + tx_code_path: PathBuf::from(TX_TRANSFER_WASM), + tx: self.tx_builder(), + native_token: self.wallet.find_address(args::NAM).expect("NAM not in wallet").clone(), + } + } + + /// Make a RevealPK builder from the given minimum set of arguments + fn new_reveal_pk( + &mut self, + public_key: common::PublicKey, + ) -> args::RevealPk { + args::RevealPk { + public_key, + tx: self.tx_builder(), + } + } + + /// Make a Bond builder from the given minimum set of arguments + fn new_bond( + &mut self, + validator: Address, + amount: token::Amount, + ) -> args::Bond { + args::Bond { + validator, + amount, + source: None, + tx: self.tx_builder(), + native_token: self.wallet.find_address(args::NAM).expect("NAM not in wallet").clone(), + tx_code_path: PathBuf::from(TX_BOND_WASM), + } + } + + /// Make a Unbond builder from the given minimum set of arguments + fn new_unbond( + &mut self, + validator: Address, + amount: token::Amount, + ) -> args::Unbond { + args::Unbond { + validator, + amount, + source: None, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_UNBOND_WASM), + } + } + + /// Make a TxIbcTransfer builder from the given minimum set of arguments + fn new_ibc_transfer( + &mut self, + source: Address, + receiver: String, + token: Address, + amount: InputAmount, + channel_id: ChannelId, + ) -> args::TxIbcTransfer { + args::TxIbcTransfer { + source, + receiver, + token, + amount, + channel_id, + port_id: PortId::from_str("transfer").unwrap(), + timeout_height: None, + timeout_sec_offset: None, + memo: None, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_IBC_WASM), + } + } + + /// Make a InitProposal builder from the given minimum set of arguments + fn new_init_proposal( + &mut self, + proposal_data: Vec, + ) -> args::InitProposal { + args::InitProposal { + proposal_data, + native_token: self.wallet.find_address(args::NAM).expect("NAM not in wallet").clone(), + is_offline: false, + is_pgf_stewards: false, + is_pgf_funding: false, + tx_code_path: PathBuf::from(TX_INIT_PROPOSAL), + tx: self.tx_builder(), + } + } + + /// Make a TxUpdateAccount builder from the given minimum set of arguments + fn new_update_account( + &mut self, + addr: Address, + ) -> args::TxUpdateAccount { + args::TxUpdateAccount { + addr, + vp_code_path: None, + public_keys: vec![], + threshold: None, + tx_code_path: PathBuf::from(TX_UPDATE_ACCOUNT_WASM), + tx: self.tx_builder(), + } + } + + /// Make a VoteProposal builder from the given minimum set of arguments + fn new_vote_prposal( + &mut self, + vote: String, + voter: Address, + ) -> args::VoteProposal { + args::VoteProposal { + vote, + voter, + proposal_id: None, + is_offline: false, + proposal_data: None, + tx_code_path: PathBuf::from(TX_VOTE_PROPOSAL), + tx: self.tx_builder(), + } + } + + /// Make a CommissionRateChange builder from the given minimum set of arguments + fn new_change_commission_rate( + &mut self, + rate: Dec, + validator: Address, + ) -> args::CommissionRateChange { + args::CommissionRateChange { + rate, + validator, + tx_code_path: PathBuf::from(TX_CHANGE_COMMISSION_WASM), + tx: self.tx_builder(), + } + } + + /// Make a TxInitValidator builder from the given minimum set of arguments + fn new_init_validator( + &mut self, + commission_rate: Dec, + max_commission_rate_change: Dec, + ) -> args::TxInitValidator { + args::TxInitValidator { + commission_rate, + max_commission_rate_change, + scheme: SchemeType::Ed25519, + account_keys: vec![], + threshold: None, + consensus_key: None, + eth_cold_key: None, + eth_hot_key: None, + protocol_key: None, + validator_vp_code_path: PathBuf::from(VP_USER_WASM), + unsafe_dont_encrypt: false, + tx_code_path: PathBuf::from(TX_INIT_VALIDATOR_WASM), + tx: self.tx_builder(), + } + } + + /// Make a TxUnjailValidator builder from the given minimum set of arguments + fn new_unjail_validator( + &mut self, + validator: Address, + ) -> args::TxUnjailValidator { + args::TxUnjailValidator { + validator, + tx_code_path: PathBuf::from(TX_UNJAIL_VALIDATOR_WASM), + tx: self.tx_builder(), + } + } + + /// Make a Withdraw builder from the given minimum set of arguments + fn new_withdraw( + &mut self, + validator: Address, + ) -> args::Withdraw { + args::Withdraw { + validator, + source: None, + tx_code_path: PathBuf::from(TX_WITHDRAW_WASM), + tx: self.tx_builder(), + } + } + + /// Make a Withdraw builder from the given minimum set of arguments + fn new_add_erc20_transfer( + &mut self, + sender: Address, + recipient: EthAddress, + asset: EthAddress, + amount: InputAmount, + ) -> args::EthereumBridgePool { + args::EthereumBridgePool { + sender, + recipient, + asset, + amount, + fee_amount: InputAmount::Unvalidated(token::DenominatedAmount { + amount: token::Amount::default(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }), + fee_payer: None, + fee_token: self.wallet.find_address(args::NAM).expect("NAM not in wallet").clone(), + nut: false, + code_path: PathBuf::from(TX_BRIDGE_POOL_WASM), + tx: self.tx_builder(), + } + } + + /// Make a ResignSteward builder from the given minimum set of arguments + fn new_resign_steward(&mut self, steward: Address) -> args::ResignSteward { + args::ResignSteward { + steward, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_RESIGN_STEWARD), + } + } + + /// Make a UpdateStewardCommission builder from the given minimum set of + /// arguments + fn new_update_steward_rewards( + &mut self, + steward: Address, + commission: Vec, + ) -> args::UpdateStewardCommission { + args::UpdateStewardCommission { + steward, + commission, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_UPDATE_STEWARD_COMMISSION), + } + } + + /// Make a TxCustom builder from the given minimum set of arguments + fn new_custom( + &mut self, + owner: Address, + ) -> args::TxCustom { + args::TxCustom { + owner, + tx: self.tx_builder(), + code_path: None, + data_path: None, + serialized_tx: None, + } + } + + /// Sign the given transaction using the given signing data + fn sign( + &mut self, + tx: &mut Tx, + args: &args::Tx, + signing_data: SigningTxData, + ) -> crate::sdk::error::Result<()> { + signing::sign_tx(self.wallet, args, tx, signing_data) + } + + /// Process the given transaction using the given flags + async fn submit( + &mut self, + tx: Tx, + args: &args::Tx, + ) -> crate::sdk::error::Result { + tx::process_tx(self.client, self.wallet, args, tx).await + } +} + +/// Provides convenience methods for common Namada interactions +pub struct NamadaImpl<'a, C, U, V>(NamadaStruct<'a, C, U, V>) where + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, + V: ShieldedUtils; + +impl<'a, C, U, V> NamadaImpl<'a, C, U, V> where + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, + V: ShieldedUtils, +{ + /// Construct a new Namada context + pub fn new( + client: &'a C, + wallet: &'a mut Wallet, + shielded: &'a mut ShieldedContext, + ) -> Self { + Self(NamadaStruct { client, wallet, shielded }) + } +} + +impl<'a, C, U, V> Deref for NamadaImpl<'a, C, U, V> where + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, + V: ShieldedUtils, +{ + type Target = NamadaStruct<'a, C, U, V>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, C, U, V> DerefMut for NamadaImpl<'a, C, U, V> where + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, + V: ShieldedUtils, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a, C, U, V> Namada<'a> for NamadaImpl<'a, C, U, V> where + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, + V: ShieldedUtils, +{ + type Client = C; + type WalletUtils = U; + type ShieldedUtils = V; +} diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs index b765dece5a..6e1381b18f 100644 --- a/shared/src/sdk/args.rs +++ b/shared/src/sdk/args.rs @@ -19,6 +19,16 @@ use crate::types::masp::MaspValue; use crate::types::storage::Epoch; use crate::types::transaction::GasLimit; use crate::types::{storage, token}; +use crate::ledger::Namada; +use crate::sdk::signing::SigningTxData; +use crate::sdk::{tx, rpc}; +use crate::ledger::eth_bridge::bridge_pool; +use namada_core::ledger::governance::cli::onchain::{ + DefaultProposal, PgfFundingProposal, PgfStewardProposal, +}; + +/// The Namada token +pub const NAM: &str = "NAM"; /// [`Duration`](StdDuration) wrapper that provides a /// method to parse a value from a string. @@ -124,6 +134,52 @@ pub struct TxCustom { pub owner: C::Address, } +impl TxBuilder for TxCustom { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + TxCustom { tx: func(self.tx), ..self } + } +} + +impl TxCustom { + /// Path to the tx WASM code file + pub fn code_path(self, code_path: PathBuf) -> Self { + Self { code_path: Some(code_path), ..self } + } + /// Path to the data file + pub fn data_path(self, data_path: C::Data) -> Self { + Self { data_path: Some(data_path), ..self } + } + /// Path to the serialized transaction + pub fn serialized_tx(self, serialized_tx: C::Data) -> Self { + Self { serialized_tx: Some(serialized_tx), ..self } + } + /// The address that correspond to the signatures/signing-keys + pub fn owner(self, owner: C::Address) -> Self { + Self { owner, ..self } + } +} + +impl TxCustom { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_custom(context, self).await + } +} + +/// An amount read in by the cli +#[derive(Copy, Clone, Debug)] +pub enum InputAmount { + /// An amount whose representation has been validated + /// against the allowed representation in storage + Validated(token::DenominatedAmount), + /// The parsed amount read in from the cli. It has + /// not yet been validated against the allowed + /// representation in storage. + Unvalidated(token::DenominatedAmount), +} + /// Transfer transaction arguments #[derive(Clone, Debug)] pub struct TxTransfer { @@ -142,16 +198,47 @@ pub struct TxTransfer { /// Path to the TX WASM code file pub tx_code_path: PathBuf, } -/// An amount read in by the cli -#[derive(Copy, Clone, Debug)] -pub enum InputAmount { - /// An amount whose representation has been validated - /// against the allowed representation in storage - Validated(token::DenominatedAmount), - /// The parsed amount read in from the cli. It has - /// not yet been validated against the allowed - /// representation in storage. - Unvalidated(token::DenominatedAmount), + +impl TxBuilder for TxTransfer { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + TxTransfer { tx: func(self.tx), ..self } + } +} + +impl TxTransfer { + /// Transfer source address + pub fn source(self, source: C::TransferSource) -> Self { + Self { source, ..self } + } + /// Transfer target address + pub fn receiver(self, target: C::TransferTarget) -> Self { + Self { target, ..self } + } + /// Transferred token address + pub fn token(self, token: C::Address) -> Self { + Self { token, ..self } + } + /// Transferred token amount + pub fn amount(self, amount: InputAmount) -> Self { + Self { amount, ..self } + } + /// Native token address + pub fn native_token(self, native_token: C::NativeAddress) -> Self { + Self { native_token, ..self } + } + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } +} + +impl TxTransfer { + /// Build a transaction from this builder + pub async fn build<'a>(self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_transfer(context, self).await + } } /// IBC transfer transaction arguments @@ -163,7 +250,7 @@ pub struct TxIbcTransfer { pub source: C::Address, /// Transfer target address pub receiver: String, - /// Transferred token addres s + /// Transferred token address pub token: C::Address, /// Transferred token amount pub amount: InputAmount, @@ -181,6 +268,65 @@ pub struct TxIbcTransfer { pub tx_code_path: PathBuf, } +impl TxBuilder for TxIbcTransfer { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + TxIbcTransfer { tx: func(self.tx), ..self } + } +} + +impl TxIbcTransfer { + /// Transfer source address + pub fn source(self, source: C::Address) -> Self { + Self { source, ..self } + } + /// Transfer target address + pub fn receiver(self, receiver: String) -> Self { + Self { receiver, ..self } + } + /// Transferred token address + pub fn token(self, token: C::Address) -> Self { + Self { token, ..self } + } + /// Transferred token amount + pub fn amount(self, amount: InputAmount) -> Self { + Self { amount, ..self } + } + /// Port ID + pub fn port_id(self, port_id: PortId) -> Self { + Self { port_id, ..self } + } + /// Channel ID + pub fn channel_id(self, channel_id: ChannelId) -> Self { + Self { channel_id, ..self } + } + /// Timeout height of the destination chain + pub fn timeout_height(self, timeout_height: u64) -> Self { + Self { timeout_height: Some(timeout_height), ..self } + } + /// Timeout timestamp offset + pub fn timeout_sec_offset(self, timeout_sec_offset: u64) -> Self { + Self { timeout_sec_offset: Some(timeout_sec_offset), ..self } + } + /// Memo + pub fn memo(self, memo: String) -> Self { + Self { memo: Some(memo), ..self } + } + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } +} + +impl TxIbcTransfer { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_ibc_transfer(context, self).await + } +} + + /// Transaction to initialize create a new proposal #[derive(Clone, Debug)] pub struct InitProposal { @@ -200,6 +346,121 @@ pub struct InitProposal { pub tx_code_path: PathBuf, } +impl TxBuilder for InitProposal { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + InitProposal { tx: func(self.tx), ..self } + } +} + +impl InitProposal { + /// The proposal data + pub fn proposal_data(self, proposal_data: C::Data) -> Self { + Self { proposal_data, ..self } + } + /// Native token address + pub fn native_token(self, native_token: C::NativeAddress) -> Self { + Self { native_token, ..self } + } + /// Flag if proposal should be run offline + pub fn is_offline(self, is_offline: bool) -> Self { + Self { is_offline, ..self } + } + /// Flag if proposal is of type Pgf stewards + pub fn is_pgf_stewards(self, is_pgf_stewards: bool) -> Self { + Self { is_pgf_stewards, ..self } + } + /// Flag if proposal is of type Pgf funding + pub fn is_pgf_funding(self, is_pgf_funding: bool) -> Self { + Self { is_pgf_funding, ..self } + } + /// Path to the tx WASM file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } +} + +impl InitProposal { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + let current_epoch = rpc::query_epoch(context.client).await?; + let governance_parameters = rpc::query_governance_parameters(context.client).await; + + if self.is_pgf_funding { + let proposal = + PgfFundingProposal::try_from(self.proposal_data.as_ref()) + .map_err(|e| { + crate::sdk::error::TxError::FailedGovernaneProposalDeserialize( + e.to_string(), + ) + })? + .validate(&governance_parameters, current_epoch, self.tx.force) + .map_err(|e| crate::sdk::error::TxError::InvalidProposal(e.to_string()))?; + + tx::build_pgf_funding_proposal( + context, + self, + proposal, + ) + .await + } else if self.is_pgf_stewards { + let proposal = PgfStewardProposal::try_from( + self.proposal_data.as_ref(), + ) + .map_err(|e| { + crate::sdk::error::TxError::FailedGovernaneProposalDeserialize(e.to_string()) + })?; + let author_balance = rpc::get_token_balance( + context.client, + context.wallet.find_address(NAM).expect("NAM not in wallet"), + &proposal.proposal.author, + ) + .await?; + let proposal = proposal + .validate( + &governance_parameters, + current_epoch, + author_balance, + self.tx.force, + ) + .map_err(|e| crate::sdk::error::TxError::InvalidProposal(e.to_string()))?; + + tx::build_pgf_stewards_proposal( + context, + self, + proposal, + ) + .await + } else { + let proposal = DefaultProposal::try_from(self.proposal_data.as_ref()) + .map_err(|e| { + crate::sdk::error::TxError::FailedGovernaneProposalDeserialize(e.to_string()) + })?; + let author_balance = rpc::get_token_balance( + context.client, + context.wallet.find_address(NAM).expect("NAM not in wallet"), + &proposal.proposal.author, + ) + .await?; + let proposal = proposal + .validate( + &governance_parameters, + current_epoch, + author_balance, + self.tx.force, + ) + .map_err(|e| crate::sdk::error::TxError::InvalidProposal(e.to_string()))?; + tx::build_default_proposal( + context, + self, + proposal, + ) + .await + } + } +} + /// Transaction to vote on a proposal #[derive(Clone, Debug)] pub struct VoteProposal { @@ -219,6 +480,49 @@ pub struct VoteProposal { pub tx_code_path: PathBuf, } +impl TxBuilder for VoteProposal { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + VoteProposal { tx: func(self.tx), ..self } + } +} + +impl VoteProposal { + /// Proposal id + pub fn proposal_id(self, proposal_id: u64) -> Self { + Self { proposal_id: Some(proposal_id), ..self } + } + /// The vote + pub fn vote(self, vote: String) -> Self { + Self { vote, ..self } + } + /// The address of the voter + pub fn voter(self, voter: C::Address) -> Self { + Self { voter, ..self } + } + /// Flag if proposal vote should be run offline + pub fn is_offline(self, is_offline: bool) -> Self { + Self { is_offline, ..self } + } + /// The proposal file path + pub fn proposal_data(self, proposal_data: C::Data) -> Self { + Self { proposal_data: Some(proposal_data), ..self } + } + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } +} + +impl VoteProposal { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + let current_epoch = rpc::query_epoch(context.client).await?; + tx::build_vote_proposal(context, self, current_epoch).await + } +} + /// Transaction to initialize a new account #[derive(Clone, Debug)] pub struct TxInitAccount { @@ -282,6 +586,44 @@ pub struct TxUpdateAccount { pub threshold: Option, } +impl TxBuilder for TxUpdateAccount { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + TxUpdateAccount { tx: func(self.tx), ..self } + } +} + +impl TxUpdateAccount { + /// Path to the VP WASM code file + pub fn vp_code_path(self, vp_code_path: PathBuf) -> Self { + Self { vp_code_path: Some(vp_code_path), ..self } + } + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } + /// Address of the account whose VP is to be updated + pub fn addr(self, addr: C::Address) -> Self { + Self { addr, ..self } + } + /// Public keys + pub fn public_keys(self, public_keys: Vec) -> Self { + Self { public_keys, ..self } + } + /// The account threshold + pub fn threshold(self, threshold: u8) -> Self { + Self { threshold: Some(threshold), ..self } + } +} + +impl TxUpdateAccount { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_update_account(context, self).await + } +} + /// Bond arguments #[derive(Clone, Debug)] pub struct Bond { @@ -300,6 +642,45 @@ pub struct Bond { pub tx_code_path: PathBuf, } +impl TxBuilder for Bond { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + Bond { tx: func(self.tx), ..self } + } +} + +impl Bond { + /// Validator address + pub fn validator(self, validator: C::Address) -> Self { + Self { validator, ..self } + } + /// Amount of tokens to stake in a bond + pub fn amount(self, amount: token::Amount) -> Self { + Self { amount, ..self } + } + /// Source address for delegations. For self-bonds, the validator is + /// also the source. + pub fn source(self, source: C::Address) -> Self { + Self { source: Some(source), ..self } + } + /// Native token address + pub fn native_token(self, native_token: C::NativeAddress) -> Self { + Self { native_token, ..self } + } + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } +} + +impl Bond { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_bond(context, self).await + } +} + /// Unbond arguments #[derive(Clone, Debug)] pub struct Unbond { @@ -316,6 +697,41 @@ pub struct Unbond { pub tx_code_path: PathBuf, } +impl Unbond { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option, Option<(Epoch, token::Amount)>)> + { + tx::build_unbond(context, self).await + } +} + +impl TxBuilder for Unbond { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + Unbond { tx: func(self.tx), ..self } + } +} + +impl Unbond { + /// Validator address + pub fn validator(self, validator: C::Address) -> Self { + Self { validator, ..self } + } + /// Amount of tokens to unbond from a bond + pub fn amount(self, amount: token::Amount) -> Self { + Self { amount, ..self } + } + /// Source address for unbonding from delegations. For unbonding from + /// self-bonds, the validator is also the source + pub fn source(self, source: C::Address) -> Self { + Self { source: Some(source), ..self } + } + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } +} + /// Reveal public key #[derive(Clone, Debug)] pub struct RevealPk { @@ -325,6 +741,32 @@ pub struct RevealPk { pub public_key: C::PublicKey, } +impl TxBuilder for RevealPk { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + RevealPk { tx: func(self.tx), ..self } + } +} + +impl RevealPk { + /// A public key to be revealed on-chain + pub fn public_key(self, public_key: C::PublicKey) -> Self { + Self { public_key, ..self } + } +} + +impl RevealPk { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_reveal_pk( + context, + &self.tx, + &self.public_key, + ).await + } +} + /// Query proposal #[derive(Clone, Debug)] pub struct QueryProposal { @@ -362,6 +804,37 @@ pub struct Withdraw { pub tx_code_path: PathBuf, } +impl TxBuilder for Withdraw { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + Withdraw { tx: func(self.tx), ..self } + } +} + +impl Withdraw { + /// Validator address + pub fn validator(self, validator: C::Address) -> Self { + Self { validator, ..self } + } + /// Source address for withdrawing from delegations. For withdrawing + /// from self-bonds, the validator is also the source + pub fn source(self, source: C::Address) -> Self { + Self { source: Some(source), ..self } + } + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } +} + +impl Withdraw { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_withdraw(context, self).await + } +} + /// Query asset conversions #[derive(Clone, Debug)] pub struct QueryConversions { @@ -452,6 +925,37 @@ pub struct CommissionRateChange { pub tx_code_path: PathBuf, } +impl TxBuilder for CommissionRateChange { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + CommissionRateChange { tx: func(self.tx), ..self } + } +} + + +impl CommissionRateChange { + /// Validator address (should be self) + pub fn validator(self, validator: C::Address) -> Self { + Self { validator, ..self } + } + /// Value to which the tx changes the commission rate + pub fn rate(self, rate: Dec) -> Self { + Self { rate, ..self } + } + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } +} + +impl CommissionRateChange { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_validator_commission_change(context, self).await + } +} + #[derive(Clone, Debug)] /// Commission rate change args pub struct UpdateStewardCommission { @@ -465,6 +969,36 @@ pub struct UpdateStewardCommission { pub tx_code_path: PathBuf, } +impl TxBuilder for UpdateStewardCommission { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + UpdateStewardCommission { tx: func(self.tx), ..self } + } +} + +impl UpdateStewardCommission { + /// Steward address + pub fn steward(self, steward: C::Address) -> Self { + Self { steward, ..self } + } + /// Value to which the tx changes the commission rate + pub fn commission(self, commission: C::Data) -> Self { + Self { commission, ..self } + } + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } +} + +impl UpdateStewardCommission { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_update_steward_commission(context, self).await + } +} + #[derive(Clone, Debug)] /// Commission rate change args pub struct ResignSteward { @@ -476,6 +1010,32 @@ pub struct ResignSteward { pub tx_code_path: PathBuf, } +impl TxBuilder for ResignSteward { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + ResignSteward { tx: func(self.tx), ..self } + } +} + +impl ResignSteward { + /// Validator address + pub fn steward(self, steward: C::Address) -> Self { + Self { steward, ..self } + } + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } +} + +impl ResignSteward { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_resign_steward(context, self).await + } +} + #[derive(Clone, Debug)] /// Re-activate a jailed validator args pub struct TxUnjailValidator { @@ -487,6 +1047,32 @@ pub struct TxUnjailValidator { pub tx_code_path: PathBuf, } +impl TxBuilder for TxUnjailValidator { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + TxUnjailValidator { tx: func(self.tx), ..self } + } +} + +impl TxUnjailValidator { + /// Validator address (should be self) + pub fn validator(self, validator: C::Address) -> Self { + Self { validator, ..self } + } + /// Path to the TX WASM code file + pub fn tc_code_path(self, tx_code_path: PathBuf) -> Self { + Self { tx_code_path, ..self } + } +} + +impl TxUnjailValidator { + /// Build a transaction from this builder + pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_unjail_validator(context, self).await + } +} + #[derive(Clone, Debug)] /// Sign a transaction offline pub struct SignTx { @@ -597,6 +1183,109 @@ pub struct Tx { pub password: Option>, } +/// Builder functions for Tx +pub trait TxBuilder : Sized { + /// Apply the given function to the Tx inside self + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx; + /// Simulate applying the transaction + fn dry_run(self, dry_run: bool) -> Self { + self.tx(|x| Tx { dry_run, ..x }) + } + /// Simulate applying both the wrapper and inner transactions + fn dry_run_wrapper(self, dry_run_wrapper: bool) -> Self { + self.tx(|x| Tx { dry_run_wrapper, ..x }) + } + /// Dump the transaction bytes to file + fn dump_tx(self, dump_tx: bool) -> Self { + self.tx(|x| Tx { dump_tx, ..x }) + } + /// The output directory path to where serialize the data + fn output_folder(self, output_folder: PathBuf) -> Self { + self.tx(|x| Tx { output_folder: Some(output_folder), ..x }) + } + /// Submit the transaction even if it doesn't pass client checks + fn force(self, force: bool) -> Self { + self.tx(|x| Tx { force, ..x }) + } + /// Do not wait for the transaction to be added to the blockchain + fn broadcast_only(self, broadcast_only: bool) -> Self { + self.tx(|x| Tx { broadcast_only, ..x }) + } + /// The address of the ledger node as host:port + fn ledger_address(self, ledger_address: C::TendermintAddress) -> Self { + self.tx(|x| Tx { ledger_address, ..x }) + } + /// If any new account is initialized by the tx, use the given alias to + /// save it in the wallet. + fn initialized_account_alias(self, initialized_account_alias: String) -> Self { + self.tx(|x| Tx { initialized_account_alias: Some(initialized_account_alias), ..x }) + } + /// Whether to force overwrite the above alias, if it is provided, in the + /// wallet. + fn wallet_alias_force(self, wallet_alias_force: bool) -> Self { + self.tx(|x| Tx { wallet_alias_force, ..x }) + } + /// The amount being payed (for gas unit) to include the transaction + fn fee_amount(self, fee_amount: InputAmount) -> Self { + self.tx(|x| Tx { fee_amount: Some(fee_amount), ..x }) + } + /// The fee payer signing key + fn wrapper_fee_payer(self, wrapper_fee_payer: C::Keypair) -> Self { + self.tx(|x| Tx { wrapper_fee_payer: Some(wrapper_fee_payer), ..x }) + } + /// The token in which the fee is being paid + fn fee_token(self, fee_token: C::Address) -> Self { + self.tx(|x| Tx { fee_token, ..x }) + } + /// The optional spending key for fee unshielding + fn fee_unshield(self, fee_unshield: C::TransferSource) -> Self { + self.tx(|x| Tx { fee_unshield: Some(fee_unshield), ..x }) + } + /// The max amount of gas used to process tx + fn gas_limit(self, gas_limit: GasLimit) -> Self { + self.tx(|x| Tx { gas_limit, ..x }) + } + /// The optional expiration of the transaction + fn expiration(self, expiration: DateTimeUtc) -> Self { + self.tx(|x| Tx { expiration: Some(expiration), ..x }) + } + /// Generate an ephimeral signing key to be used only once to sign a + /// wrapper tx + fn disposable_signing_key(self, disposable_signing_key: bool) -> Self { + self.tx(|x| Tx { disposable_signing_key, ..x }) + } + /// The chain id for which the transaction is intended + fn chain_id(self, chain_id: ChainId) -> Self { + self.tx(|x| Tx { chain_id: Some(chain_id), ..x }) + } + /// Sign the tx with the key for the given alias from your wallet + fn signing_keys(self, signing_keys: Vec) -> Self { + self.tx(|x| Tx { signing_keys, ..x }) + } + /// List of signatures to attach to the transaction + fn signatures(self, signatures: Vec) -> Self { + self.tx(|x| Tx { signatures, ..x }) + } + /// Path to the TX WASM code file to reveal PK + fn tx_reveal_code_path(self, tx_reveal_code_path: PathBuf) -> Self { + self.tx(|x| Tx { tx_reveal_code_path, ..x }) + } + /// Sign the tx with the public key for the given alias from your wallet + fn verification_key(self, verification_key: C::PublicKey) -> Self { + self.tx(|x| Tx { verification_key: Some(verification_key), ..x }) + } + /// Password to decrypt key + fn password(self, password: Zeroizing) -> Self { + self.tx(|x| Tx { password: Some(password), ..x }) + } +} + +impl TxBuilder for Tx { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + func(self) + } +} + /// MASP add key or address arguments #[derive(Clone, Debug)] pub struct MaspAddrKeyAdd { @@ -775,6 +1464,65 @@ pub struct EthereumBridgePool { pub code_path: PathBuf, } +impl TxBuilder for EthereumBridgePool { + fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + EthereumBridgePool { tx: func(self.tx), ..self } + } +} + +impl EthereumBridgePool { + /// Whether the transfer is for a NUT. + /// + /// By default, we add wrapped ERC20s onto the + /// Bridge pool. + pub fn nut(self, nut: bool) -> Self { + Self { nut, ..self } + } + /// The type of token + pub fn asset(self, asset: EthAddress) -> Self { + Self { asset, ..self } + } + /// The recipient address + pub fn recipient(self, recipient: EthAddress) -> Self { + Self { recipient, ..self } + } + /// The sender of the transfer + pub fn sender(self, sender: C::Address) -> Self { + Self { sender, ..self } + } + /// The amount to be transferred + pub fn amount(self, amount: InputAmount) -> Self { + Self { amount, ..self } + } + /// The amount of gas fees + pub fn fee_amount(self, fee_amount: InputAmount) -> Self { + Self { fee_amount, ..self } + } + /// The account of fee payer. + /// + /// If unset, it is the same as the sender. + pub fn fee_payer(self, fee_payer: C::Address) -> Self { + Self { fee_payer: Some(fee_payer), ..self } + } + /// The token in which the gas is being paid + pub fn fee_token(self, fee_token: C::Address) -> Self { + Self { fee_token, ..self } + } + /// Path to the tx WASM code file + pub fn code_path(self, code_path: PathBuf) -> Self { + Self { code_path, ..self } + } +} + +impl EthereumBridgePool { + /// Build a transaction from this builder + pub async fn build<'a>(self, context: &mut impl Namada<'a>) -> + crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + bridge_pool::build_bridge_pool_tx(context, self).await + } +} + /// Bridge pool proof arguments. #[derive(Debug, Clone)] pub struct BridgePoolProof { diff --git a/shared/src/sdk/masp.rs b/shared/src/sdk/masp.rs index 739f941b9a..6b44dd018d 100644 --- a/shared/src/sdk/masp.rs +++ b/shared/src/sdk/masp.rs @@ -68,7 +68,7 @@ use crate::sdk::{args, rpc}; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; use crate::types::address::{masp, Address}; -use crate::types::io::Io; +use crate::types::io::{Io, StdIo}; use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use crate::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; use crate::types::token; @@ -77,6 +77,7 @@ use crate::types::token::{ }; use crate::types::transaction::{EllipticCurve, PairingEngine, WrapperTx}; use crate::{display_line, edisplay_line}; +use crate::ledger::Namada; /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. @@ -1035,7 +1036,7 @@ impl ShieldedContext { if let Some(balance) = self.compute_shielded_balance(client, vk).await? { let exchanged_amount = self - .compute_exchanged_amount::<_, IO>( + .compute_exchanged_amount( client, balance, target_epoch, @@ -1058,7 +1059,7 @@ impl ShieldedContext { /// the trace amount that could not be converted is moved from input to /// output. #[allow(clippy::too_many_arguments)] - async fn apply_conversion( + async fn apply_conversion( &mut self, client: &C, conv: AllowedConversion, @@ -1080,7 +1081,7 @@ impl ShieldedContext { let threshold = -conv[&masp_asset]; if threshold == 0 { edisplay_line!( - IO, + StdIo, "Asset threshold of selected conversion for asset type {} is \ 0, this is a bug, please report it.", masp_asset @@ -1110,7 +1111,7 @@ impl ShieldedContext { /// note of the conversions that were used. Note that this function does /// not assume that allowed conversions from the ledger are expressed in /// terms of the latest asset types. - pub async fn compute_exchanged_amount( + pub async fn compute_exchanged_amount( &mut self, client: &C, mut input: MaspAmount, @@ -1149,14 +1150,14 @@ impl ShieldedContext { (conversions.get_mut(&asset_type), at_target_asset_type) { display_line!( - IO, + StdIo, "converting current asset type to latest asset type..." ); // Not at the target asset type, not at the latest asset // type. Apply conversion to get from // current asset type to the latest // asset type. - self.apply_conversion::<_, IO>( + self.apply_conversion( client, conv.clone(), (asset_epoch, token_addr.clone(), denom), @@ -1171,14 +1172,14 @@ impl ShieldedContext { at_target_asset_type, ) { display_line!( - IO, + StdIo, "converting latest asset type to target asset type..." ); // Not at the target asset type, yet at the latest asset // type. Apply inverse conversion to get // from latest asset type to the target // asset type. - self.apply_conversion::<_, IO>( + self.apply_conversion( client, conv.clone(), (asset_epoch, token_addr.clone(), denom), @@ -1213,7 +1214,7 @@ impl ShieldedContext { /// of the specified asset type. Return the total value accumulated plus /// notes and the corresponding diversifiers/merkle paths that were used to /// achieve the total value. - pub async fn collect_unspent_notes( + pub async fn collect_unspent_notes( &mut self, client: &C, vk: &ViewingKey, @@ -1259,7 +1260,7 @@ impl ShieldedContext { })?; let input = self.decode_all_amounts(client, pre_contr).await; let (contr, proposed_convs) = self - .compute_exchanged_amount::<_, IO>( + .compute_exchanged_amount( client, input, target_epoch, @@ -1413,7 +1414,7 @@ impl ShieldedContext { display_line!(IO, "Decoded pinned balance: {:?}", amount); // Finally, exchange the balance to the transaction's epoch let computed_amount = self - .compute_exchanged_amount::<_, IO>( + .compute_exchanged_amount( client, amount, ep, @@ -1483,10 +1484,9 @@ impl ShieldedContext { /// understood that transparent account changes are effected only by the /// amounts and signatures specified by the containing Transfer object. #[cfg(feature = "masp-tx-gen")] - pub async fn gen_shielded_transfer( - &mut self, - client: &C, - args: args::TxTransfer, + pub async fn gen_shielded_transfer<'a>( + context: &mut impl Namada<'a>, + args: &args::TxTransfer, ) -> Result, TransferErr> { // No shielded components are needed when neither source nor destination // are shielded @@ -1507,12 +1507,13 @@ impl ShieldedContext { let spending_key = spending_key.map(|x| x.into()); let spending_keys: Vec<_> = spending_key.into_iter().collect(); // Load the current shielded context given the spending key we possess - let _ = self.load().await; - self.fetch(client, &spending_keys, &[]).await?; + let _ = context.shielded.load().await; + let context = &mut **context; + context.shielded.fetch(context.client, &spending_keys, &[]).await?; // Save the update state so that future fetches can be short-circuited - let _ = self.save().await; + let _ = context.shielded.save().await; // Determine epoch in which to submit potential shielded transaction - let epoch = rpc::query_epoch(client).await?; + let epoch = rpc::query_epoch(context.client).await?; // Context required for storing which notes are in the source's // possesion let memo = MemoBytes::empty(); @@ -1552,9 +1553,10 @@ impl ShieldedContext { // If there are shielded inputs if let Some(sk) = spending_key { // Locate unspent notes that can help us meet the transaction amount - let (_, unspent_notes, used_convs) = self - .collect_unspent_notes::<_, IO>( - client, + let (_, unspent_notes, used_convs) = context + .shielded + .collect_unspent_notes( + context.client, &to_viewing_key(&sk).vk, I128Sum::from_sum(amount), epoch, @@ -1743,7 +1745,7 @@ impl ShieldedContext { let build_transfer = || -> Result> { let (masp_tx, metadata) = builder.build( - &self.utils.local_tx_prover(), + &context.shielded.utils.local_tx_prover(), &FeeRule::non_standard(U64Sum::zero()), )?; Ok(ShieldedTransfer { diff --git a/shared/src/sdk/rpc.rs b/shared/src/sdk/rpc.rs index 58609bed42..cb0e55835a 100644 --- a/shared/src/sdk/rpc.rs +++ b/shared/src/sdk/rpc.rs @@ -38,7 +38,7 @@ use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; use crate::types::control_flow::{time, Halt, TryHalt}; use crate::types::hash::Hash; -use crate::types::io::Io; +use crate::types::io::{Io, StdIo}; use crate::types::key::common; use crate::types::storage::{BlockHeight, BlockResults, Epoch, PrefixValue}; use crate::types::{storage, token}; @@ -239,7 +239,6 @@ pub async fn query_conversion( /// Query a wasm code hash pub async fn query_wasm_code_hash< C: crate::ledger::queries::Client + Sync, - IO: Io, >( client: &C, code_path: impl AsRef, @@ -252,7 +251,7 @@ pub async fn query_wasm_code_hash< Some(hash) => Ok(Hash::try_from(&hash[..]).expect("Invalid code hash")), None => { edisplay_line!( - IO, + StdIo, "The corresponding wasm code of the code path {} doesn't \ exist on chain.", code_path.as_ref(), @@ -789,7 +788,6 @@ pub async fn get_public_key_at( /// Query a validator's unbonds for a given epoch pub async fn query_and_print_unbonds< C: crate::ledger::queries::Client + Sync, - IO: Io, >( client: &C, source: &Address, @@ -811,17 +809,17 @@ pub async fn query_and_print_unbonds< } if total_withdrawable != token::Amount::default() { display_line!( - IO, + StdIo, "Total withdrawable now: {}.", total_withdrawable.to_string_native() ); } if !not_yet_withdrawable.is_empty() { - display_line!(IO, "Current epoch: {current_epoch}.") + display_line!(StdIo, "Current epoch: {current_epoch}.") } for (withdraw_epoch, amount) in not_yet_withdrawable { display_line!( - IO, + StdIo, "Amount {} withdrawable starting from epoch {withdraw_epoch}.", amount.to_string_native() ); @@ -939,7 +937,6 @@ pub async fn enriched_bonds_and_unbonds< /// Get the correct representation of the amount given the token type. pub async fn validate_amount< C: crate::ledger::queries::Client + Sync, - IO: Io, >( client: &C, amount: InputAmount, @@ -958,14 +955,14 @@ pub async fn validate_amount< None => { if force { display_line!( - IO, + StdIo, "No denomination found for token: {token}, but --force \ was passed. Defaulting to the provided denomination." ); Ok(input_amount.denom) } else { display_line!( - IO, + StdIo, "No denomination found for token: {token}, the input \ arguments could not be parsed." ); @@ -977,7 +974,7 @@ pub async fn validate_amount< }?; if denom < input_amount.denom && !force { display_line!( - IO, + StdIo, "The input amount contained a higher precision than allowed by \ {token}." ); @@ -988,7 +985,7 @@ pub async fn validate_amount< } else { input_amount.increase_precision(denom).map_err(|_err| { display_line!( - IO, + StdIo, "The amount provided requires more the 256 bits to represent." ); Error::from(QueryError::General( @@ -1065,7 +1062,6 @@ where /// correctly as a string. pub async fn format_denominated_amount< C: crate::ledger::queries::Client + Sync, - IO: Io, >( client: &C, token: &Address, @@ -1075,12 +1071,12 @@ pub async fn format_denominated_amount< RPC.vp().token().denomination(client, token).await, ) .unwrap_or_else(|t| { - display_line!(IO, "Error in querying for denomination: {t}"); + display_line!(StdIo, "Error in querying for denomination: {t}"); None }) .unwrap_or_else(|| { display_line!( - IO, + StdIo, "No denomination found for token: {token}, defaulting to zero \ decimal places" ); diff --git a/shared/src/sdk/signing.rs b/shared/src/sdk/signing.rs index 042be03a63..3afb990422 100644 --- a/shared/src/sdk/signing.rs +++ b/shared/src/sdk/signing.rs @@ -24,14 +24,13 @@ use sha2::Digest; use zeroize::Zeroizing; use crate::display_line; +use super::masp::{ShieldedContext, ShieldedTransfer}; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc_proto::google::protobuf::Any; use crate::ledger::parameters::storage as parameter_storage; use crate::proto::{MaspBuilder, Section, Tx}; use crate::sdk::error::{EncodingError, Error, TxError}; -use crate::sdk::masp::{ - make_asset_type, ShieldedContext, ShieldedTransfer, ShieldedUtils, -}; +use crate::sdk::masp::make_asset_type; use crate::sdk::rpc::{ format_denominated_amount, query_wasm_code_hash, validate_amount, }; @@ -55,6 +54,8 @@ use crate::types::transaction::governance::{ }; use crate::types::transaction::pos::InitValidator; use crate::types::transaction::Fee; +use crate::ledger::Namada; +use crate::sdk::args::SdkTypes; #[cfg(feature = "std")] /// Env. var specifying where to store signing test vectors @@ -85,7 +86,6 @@ pub struct SigningTxData { pub async fn find_pk< C: crate::ledger::queries::Client + Sync, U: WalletUtils, - IO: Io, >( client: &C, wallet: &mut Wallet, @@ -95,7 +95,7 @@ pub async fn find_pk< match addr { Address::Established(_) => { display_line!( - IO, + StdIo, "Looking-up public key of {} from the ledger...", addr.encode() ); @@ -153,14 +153,9 @@ pub fn find_key_by_pk( /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, an `Error` is returned. -pub async fn tx_signers< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - args: &args::Tx, +pub async fn tx_signers<'a>( + context: &mut impl Namada<'a>, + args: &args::Tx, default: Option
, ) -> Result, Error> { let signer = if !&args.signing_keys.is_empty() { @@ -179,7 +174,7 @@ pub async fn tx_signers< Some(signer) if signer == masp() => Ok(vec![masp_tx_key().ref_to()]), Some(signer) => Ok(vec![ - find_pk::(client, wallet, &signer, args.password.clone()) + find_pk(context.client, context.wallet, &signer, args.password.clone()) .await?, ]), None => other_err( @@ -242,27 +237,21 @@ pub fn sign_tx( /// Return the necessary data regarding an account to be able to generate a /// multisignature section -pub async fn aux_signing_data< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - args: &args::Tx, +pub async fn aux_signing_data<'a>( + context: &mut impl Namada<'a>, + args: &args::Tx, owner: Option
, default_signer: Option
, ) -> Result { let public_keys = if owner.is_some() || args.wrapper_fee_payer.is_none() { - tx_signers::(client, wallet, args, default_signer.clone()) - .await? + tx_signers(context, args, default_signer.clone()).await? } else { vec![] }; let (account_public_keys_map, threshold) = match &owner { Some(owner @ Address::Established(_)) => { - let account = rpc::get_account_info::(client, owner).await?; + let account = rpc::get_account_info(context.client, owner).await?; if let Some(account) = account { (Some(account.public_keys_map), account.threshold) } else { @@ -282,7 +271,7 @@ pub async fn aux_signing_data< }; let fee_payer = if args.disposable_signing_key { - wallet.generate_disposable_signing_key().to_public() + context.wallet.generate_disposable_signing_key().to_public() } else { match &args.wrapper_fee_payer { Some(keypair) => keypair.to_public(), @@ -322,15 +311,10 @@ pub struct TxSourcePostBalance { /// wrapper and its payload which is needed for monitoring its /// progress on chain. #[allow(clippy::too_many_arguments)] -pub async fn wrap_tx< - C: crate::sdk::queries::Client + Sync, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - shielded: &mut ShieldedContext, +pub async fn wrap_tx<'a, N: Namada<'a>>( + context: &mut N, tx: &mut Tx, - args: &args::Tx, + args: &args::Tx, tx_source_balance: Option, epoch: Epoch, fee_payer: common::PublicKey, @@ -339,9 +323,9 @@ pub async fn wrap_tx< // Validate fee amount and token let gas_cost_key = parameter_storage::get_gas_cost_key(); let minimum_fee = match rpc::query_storage_value::< - C, + _, BTreeMap, - >(client, &gas_cost_key) + >(context.client, &gas_cost_key) .await .and_then(|map| { map.get(&args.fee_token) @@ -364,14 +348,10 @@ pub async fn wrap_tx< }; let fee_amount = match args.fee_amount { Some(amount) => { - let validated_fee_amount = validate_amount::<_, IO>( - client, - amount, - &args.fee_token, - args.force, - ) - .await - .expect("Expected to be able to validate fee"); + let validated_fee_amount = + validate_amount(context.client, amount, &args.fee_token, args.force) + .await + .expect("Expected to be able to validate fee"); let amount = Amount::from_uint(validated_fee_amount.amount, 0).unwrap(); @@ -381,7 +361,7 @@ pub async fn wrap_tx< } else if !args.force { // Update the fee amount if it's not enough display_line!( - IO, + StdIo, "The provided gas price {} is less than the minimum \ amount required {}, changing it to match the minimum", amount.to_string_native(), @@ -405,7 +385,7 @@ pub async fn wrap_tx< let balance_key = token::balance_key(&args.fee_token, &fee_payer_address); - rpc::query_storage_value::(client, &balance_key) + rpc::query_storage_value::<_, token::Amount>(context.client, &balance_key) .await .unwrap_or_default() } @@ -441,8 +421,7 @@ pub async fn wrap_tx< tx_code_path: PathBuf::new(), }; - match shielded - .gen_shielded_transfer::<_, IO>(client, transfer_args) + match ShieldedContext::::gen_shielded_transfer(context, &transfer_args) .await { Ok(Some(ShieldedTransfer { @@ -471,8 +450,8 @@ pub async fn wrap_tx< let descriptions_limit_key= parameter_storage::get_fee_unshielding_descriptions_limit_key(); let descriptions_limit = - rpc::query_storage_value::( - client, + rpc::query_storage_value::<_, u64>( + context.client, &descriptions_limit_key, ) .await @@ -519,15 +498,15 @@ pub async fn wrap_tx< } else { let token_addr = args.fee_token.clone(); if !args.force { - let fee_amount = format_denominated_amount::<_, IO>( - client, + let fee_amount = format_denominated_amount( + context.client, &token_addr, total_fee, ) .await; - let balance = format_denominated_amount::<_, IO>( - client, + let balance = format_denominated_amount( + context.client, &token_addr, updated_balance, ) @@ -546,7 +525,7 @@ pub async fn wrap_tx< _ => { if args.fee_unshield.is_some() { display_line!( - IO, + StdIo, "Enough transparent balance to pay fees: the fee \ unshielding spending key will be ignored" ); @@ -616,7 +595,6 @@ fn make_ledger_amount_addr( /// type async fn make_ledger_amount_asset< C: crate::ledger::queries::Client + Sync, - IO: Io, >( client: &C, tokens: &HashMap, @@ -629,7 +607,7 @@ async fn make_ledger_amount_asset< if let Some((token, _, _epoch)) = assets.get(token) { // If the AssetType can be decoded, then at least display Addressees let formatted_amt = - format_denominated_amount::<_, IO>(client, token, amount.into()) + format_denominated_amount(client, token, amount.into()) .await; if let Some(token) = tokens.get(token) { output @@ -716,7 +694,6 @@ fn format_outputs(output: &mut Vec) { /// transactions pub async fn make_ledger_masp_endpoints< C: crate::ledger::queries::Client + Sync, - IO: Io, >( client: &C, tokens: &HashMap, @@ -740,7 +717,7 @@ pub async fn make_ledger_masp_endpoints< for sapling_input in builder.builder.sapling_inputs() { let vk = ExtendedViewingKey::from(*sapling_input.key()); output.push(format!("Sender : {}", vk)); - make_ledger_amount_asset::<_, IO>( + make_ledger_amount_asset( client, tokens, output, @@ -767,7 +744,7 @@ pub async fn make_ledger_masp_endpoints< for sapling_output in builder.builder.sapling_outputs() { let pa = PaymentAddress::from(sapling_output.address()); output.push(format!("Destination : {}", pa)); - make_ledger_amount_asset::<_, IO>( + make_ledger_amount_asset( client, tokens, output, @@ -792,13 +769,8 @@ pub async fn make_ledger_masp_endpoints< /// Internal method used to generate transaction test vectors #[cfg(feature = "std")] -pub async fn generate_test_vector< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn generate_test_vector<'a>( + context: &mut impl Namada<'a>, tx: &Tx, ) -> Result<(), Error> { use std::env; @@ -810,8 +782,7 @@ pub async fn generate_test_vector< // Contract the large data blobs in the transaction tx.wallet_filter(); // Convert the transaction to Ledger format - let decoding = - to_ledger_vector::<_, _, IO>(client, wallet, &tx).await?; + let decoding = to_ledger_vector(context, &tx).await?; let output = serde_json::to_string(&decoding) .map_err(|e| Error::from(EncodingError::Serde(e.to_string())))?; // Record the transaction at the identified path @@ -849,46 +820,36 @@ pub async fn generate_test_vector< /// Converts the given transaction to the form that is displayed on the Ledger /// device -pub async fn to_ledger_vector< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn to_ledger_vector<'a>( + context: &mut impl Namada<'a>, tx: &Tx, ) -> Result { let init_account_hash = - query_wasm_code_hash::<_, IO>(client, TX_INIT_ACCOUNT_WASM).await?; + query_wasm_code_hash(context.client, TX_INIT_ACCOUNT_WASM).await?; let init_validator_hash = - query_wasm_code_hash::<_, IO>(client, TX_INIT_VALIDATOR_WASM).await?; + query_wasm_code_hash(context.client, TX_INIT_VALIDATOR_WASM).await?; let init_proposal_hash = - query_wasm_code_hash::<_, IO>(client, TX_INIT_PROPOSAL).await?; + query_wasm_code_hash(context.client, TX_INIT_PROPOSAL).await?; let vote_proposal_hash = - query_wasm_code_hash::<_, IO>(client, TX_VOTE_PROPOSAL).await?; - let reveal_pk_hash = - query_wasm_code_hash::<_, IO>(client, TX_REVEAL_PK).await?; + query_wasm_code_hash(context.client, TX_VOTE_PROPOSAL).await?; + let reveal_pk_hash = query_wasm_code_hash(context.client, TX_REVEAL_PK).await?; let update_account_hash = - query_wasm_code_hash::<_, IO>(client, TX_UPDATE_ACCOUNT_WASM).await?; - let transfer_hash = - query_wasm_code_hash::<_, IO>(client, TX_TRANSFER_WASM).await?; - let ibc_hash = query_wasm_code_hash::<_, IO>(client, TX_IBC_WASM).await?; - let bond_hash = query_wasm_code_hash::<_, IO>(client, TX_BOND_WASM).await?; - let unbond_hash = - query_wasm_code_hash::<_, IO>(client, TX_UNBOND_WASM).await?; - let withdraw_hash = - query_wasm_code_hash::<_, IO>(client, TX_WITHDRAW_WASM).await?; + query_wasm_code_hash(context.client, TX_UPDATE_ACCOUNT_WASM).await?; + let transfer_hash = query_wasm_code_hash(context.client, TX_TRANSFER_WASM).await?; + let ibc_hash = query_wasm_code_hash(context.client, TX_IBC_WASM).await?; + let bond_hash = query_wasm_code_hash(context.client, TX_BOND_WASM).await?; + let unbond_hash = query_wasm_code_hash(context.client, TX_UNBOND_WASM).await?; + let withdraw_hash = query_wasm_code_hash(context.client, TX_WITHDRAW_WASM).await?; let change_commission_hash = - query_wasm_code_hash::<_, IO>(client, TX_CHANGE_COMMISSION_WASM) - .await?; - let user_hash = query_wasm_code_hash::<_, IO>(client, VP_USER_WASM).await?; + query_wasm_code_hash(context.client, TX_CHANGE_COMMISSION_WASM).await?; + let user_hash = query_wasm_code_hash(context.client, VP_USER_WASM).await?; // To facilitate lookups of human-readable token names - let tokens: HashMap = wallet + let tokens: HashMap = context.wallet .get_addresses_with_vp_type(AddressVpType::Token) .into_iter() .map(|addr| { - let alias = match wallet.find_alias(&addr) { + let alias = match context.wallet.find_alias(&addr) { Some(alias) => alias.to_string(), None => addr.to_string(), }; @@ -1174,8 +1135,8 @@ pub async fn to_ledger_vector< tv.name = "Transfer 0".to_string(); tv.output.push("Type : Transfer".to_string()); - make_ledger_masp_endpoints::<_, IO>( - client, + make_ledger_masp_endpoints( + context.client, &tokens, &mut tv.output, &transfer, @@ -1183,8 +1144,8 @@ pub async fn to_ledger_vector< &asset_types, ) .await; - make_ledger_masp_endpoints::<_, IO>( - client, + make_ledger_masp_endpoints( + context.client, &tokens, &mut tv.output_expert, &transfer, @@ -1356,14 +1317,14 @@ pub async fn to_ledger_vector< if let Some(wrapper) = tx.header.wrapper() { let gas_token = wrapper.fee.token.clone(); - let gas_limit = format_denominated_amount::<_, IO>( - client, + let gas_limit = format_denominated_amount( + context.client, &gas_token, Amount::from(wrapper.gas_limit), ) .await; - let fee_amount_per_gas_unit = format_denominated_amount::<_, IO>( - client, + let fee_amount_per_gas_unit = format_denominated_amount( + context.client, &gas_token, wrapper.fee.amount_per_gas_unit, ) diff --git a/shared/src/sdk/tx.rs b/shared/src/sdk/tx.rs index 5885ebda44..2ca4879bfe 100644 --- a/shared/src/sdk/tx.rs +++ b/shared/src/sdk/tx.rs @@ -43,21 +43,21 @@ use crate::ibc::core::Msg; use crate::ibc::Height as IbcHeight; use crate::ledger::ibc::storage::ibc_denom_key; use crate::sdk::signing::SigningTxData; -use crate::proto::{MaspBuilder, Tx}; -use crate::sdk::args::{self, InputAmount}; -use crate::sdk::error::{EncodingError, Error, QueryError, Result, TxError}; use crate::sdk::masp::TransferErr::Build; -use crate::sdk::masp::{ShieldedContext, ShieldedTransfer, ShieldedUtils}; +use crate::sdk::masp::{ShieldedContext, ShieldedTransfer}; use crate::sdk::rpc::{ - self, format_denominated_amount, query_wasm_code_hash, validate_amount, - TxBroadcastData, TxResponse, + self, format_denominated_amount, validate_amount, TxBroadcastData, + TxResponse, query_wasm_code_hash }; -use crate::sdk::signing::{self, TxSourcePostBalance}; use crate::sdk::wallet::{Wallet, WalletUtils}; +use crate::proto::{MaspBuilder, Tx}; +use crate::sdk::args::{self, InputAmount}; +use crate::sdk::error::{EncodingError, Error, QueryError, Result, TxError}; +use crate::sdk::signing::{self, TxSourcePostBalance}; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::tendermint_rpc::error::Error as RpcError; use crate::types::control_flow::{time, ProceedOrElse}; -use crate::types::io::Io; +use crate::types::io::{Io, StdIo}; use crate::types::key::*; use crate::types::masp::TransferTarget; use crate::types::storage::Epoch; @@ -66,11 +66,14 @@ use crate::types::transaction::account::{InitAccount, UpdateAccount}; use crate::types::transaction::{pos, TxType}; use crate::types::{storage, token}; use crate::{display_line, edisplay_line, vm}; +use crate::ledger::Namada; /// Initialize account transaction WASM pub const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; /// Initialize validator transaction WASM path pub const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; +/// Unjail validator transaction WASM path +pub const TX_UNJAIL_VALIDATOR_WASM: &str = "tx_unjail_validator.wasm"; /// Initialize proposal transaction WASM path pub const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; /// Vote transaction WASM path @@ -91,9 +94,16 @@ pub const TX_BOND_WASM: &str = "tx_bond.wasm"; pub const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; /// Withdraw WASM path pub const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; +/// Bridge pool WASM path +pub const TX_BRIDGE_POOL_WASM: &str = "tx_bridge_pool.wasm"; /// Change commission WASM path pub const TX_CHANGE_COMMISSION_WASM: &str = "tx_change_validator_commission.wasm"; +/// Resign steward WASM path +pub const TX_RESIGN_STEWARD: &str = "tx_resign_steward.wasm"; +/// Update steward commission WASM path +pub const TX_UPDATE_STEWARD_COMMISSION: &str = + "tx_update_steward_commission.wasm"; /// Default timeout in seconds for requests to the `/accepted` /// and `/applied` ABCI query endpoints. @@ -148,26 +158,18 @@ pub fn dump_tx(args: &args::Tx, tx: Tx) { /// Prepare a transaction for signing and submission by adding a wrapper header /// to it. #[allow(clippy::too_many_arguments)] -pub async fn prepare_tx< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - _wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn prepare_tx<'a>( + context: &mut impl Namada<'a>, args: &args::Tx, tx: &mut Tx, fee_payer: common::PublicKey, tx_source_balance: Option, ) -> Result> { if !args.dry_run { - let epoch = rpc::query_epoch(client).await?; + let epoch = rpc::query_epoch(context.client).await?; - signing::wrap_tx::<_, _, IO>( - client, - shielded, + signing::wrap_tx( + context, tx, args, tx_source_balance, @@ -185,7 +187,6 @@ pub async fn prepare_tx< pub async fn process_tx< C: crate::sdk::queries::Client + Sync, U: WalletUtils, - IO: Io, >( client: &C, wallet: &mut Wallet, @@ -203,7 +204,7 @@ pub async fn process_tx< // println!("HTTP request body: {}", request_body); if args.dry_run || args.dry_run_wrapper { - expect_dry_broadcast::<_, IO>(TxBroadcastData::DryRun(tx), client).await + expect_dry_broadcast::<_, StdIo>(TxBroadcastData::DryRun(tx), client).await } else { // We use this to determine when the wrapper tx makes it on-chain let wrapper_hash = tx.header_hash().to_string(); @@ -223,13 +224,13 @@ pub async fn process_tx< // of masp epoch Either broadcast or submit transaction and // collect result into sum type if args.broadcast_only { - broadcast_tx::<_, IO>(client, &to_broadcast) + broadcast_tx::<_, StdIo>(client, &to_broadcast) .await .map(ProcessTxResponse::Broadcast) } else { - match submit_tx::<_, IO>(client, to_broadcast).await { + match submit_tx::<_, StdIo>(client, to_broadcast).await { Ok(x) => { - save_initialized_accounts::( + save_initialized_accounts::( wallet, args, x.initialized_accounts.clone(), @@ -265,37 +266,25 @@ pub async fn has_revealed_pk( } /// Submit transaction to reveal the given public key -pub async fn build_reveal_pk< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_reveal_pk<'a>( + context: &mut impl Namada<'a>, args: &args::Tx, - address: &Address, public_key: &common::PublicKey, - fee_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { - display_line!( - IO, - "Submitting a tx to reveal the public key for address {address}..." - ); +) -> Result<(Tx, SigningTxData, Option)> { + let signing_data = + signing::aux_signing_data(context, args, None, None) + .await?; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, + build( + context, args, args.tx_reveal_code_path.clone(), public_key, do_nothing, - fee_payer, + &signing_data.fee_payer, None, ) - .await + .await.map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Broadcast a transaction to be included in the blockchain and checks that @@ -516,51 +505,43 @@ pub async fn save_initialized_accounts( } /// Submit validator comission rate change -pub async fn build_validator_commission_change< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_validator_commission_change<'a>( + context: &mut impl Namada<'a>, args::CommissionRateChange { tx: tx_args, validator, rate, tx_code_path, - }: args::CommissionRateChange, + }: &args::CommissionRateChange, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(validator.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx_args, + let signing_data = signing::aux_signing_data( + context, + tx_args, Some(validator.clone()), default_signer, ) .await?; - let epoch = rpc::query_epoch(client).await?; + let epoch = rpc::query_epoch(context.client).await?; - let params: PosParams = rpc::get_pos_params(client).await?; + let params: PosParams = rpc::get_pos_params(context.client).await?; let validator = validator.clone(); - if rpc::is_validator(client, &validator).await? { - if rate < Dec::zero() || rate > Dec::one() { + if rpc::is_validator(context.client, &validator).await? { + if *rate < Dec::zero() || *rate > Dec::one() { edisplay_line!( - IO, + StdIo, "Invalid new commission rate, received {}", rate ); - return Err(Error::from(TxError::InvalidCommissionRate(rate))); + return Err(Error::from(TxError::InvalidCommissionRate(*rate))); } let pipeline_epoch_minus_one = epoch + params.pipeline_len - 1; match rpc::query_commission_rate( - client, + context.client, &validator, Some(pipeline_epoch_minus_one), ) @@ -574,27 +555,27 @@ pub async fn build_validator_commission_change< > max_commission_change_per_epoch { edisplay_line!( - IO, + StdIo, "New rate is too large of a change with respect to \ the predecessor epoch in which the rate will take \ effect." ); if !tx_args.force { return Err(Error::from( - TxError::InvalidCommissionRate(rate), + TxError::InvalidCommissionRate(*rate), )); } } } None => { - edisplay_line!(IO, "Error retrieving from storage"); + edisplay_line!(StdIo, "Error retrieving from storage"); if !tx_args.force { return Err(Error::from(TxError::Retrieval)); } } } } else { - edisplay_line!(IO, "The given address {validator} is not a validator."); + edisplay_line!(StdIo, "The given address {validator} is not a validator."); if !tx_args.force { return Err(Error::from(TxError::InvalidValidatorAddress( validator, @@ -604,15 +585,13 @@ pub async fn build_validator_commission_change< let data = pos::CommissionChange { validator: validator.clone(), - new_rate: rate, + new_rate: *rate, }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, do_nothing, &signing_data.fee_payer, @@ -622,34 +601,26 @@ pub async fn build_validator_commission_change< } /// Craft transaction to update a steward commission -pub async fn build_update_steward_commission< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_update_steward_commission<'a>( + context: &mut impl Namada<'a>, args::UpdateStewardCommission { tx: tx_args, steward, commission, tx_code_path, - }: args::UpdateStewardCommission, + }: &args::UpdateStewardCommission, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(steward.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx_args, + let signing_data = signing::aux_signing_data( + context, + tx_args, Some(steward.clone()), default_signer, ) .await?; - if !rpc::is_steward(client, &steward).await && !tx_args.force { - edisplay_line!(IO, "The given address {} is not a steward.", &steward); + if !rpc::is_steward(context.client, steward).await && !tx_args.force { + edisplay_line!(StdIo, "The given address {} is not a steward.", &steward); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); }; @@ -658,7 +629,7 @@ pub async fn build_update_steward_commission< if !commission.is_valid() && !tx_args.force { edisplay_line!( - IO, + StdIo, "The sum of all percentage must not be greater than 1." ); return Err(Error::from(TxError::InvalidStewardCommission( @@ -671,12 +642,10 @@ pub async fn build_update_steward_commission< commission: commission.reward_distribution, }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, do_nothing, &signing_data.fee_payer, @@ -686,43 +655,33 @@ pub async fn build_update_steward_commission< } /// Craft transaction to resign as a steward -pub async fn build_resign_steward< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_resign_steward<'a>( + context: &mut impl Namada<'a>, args::ResignSteward { tx: tx_args, steward, tx_code_path, - }: args::ResignSteward, + }: &args::ResignSteward, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(steward.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx_args, + let signing_data = signing::aux_signing_data( + context, + tx_args, Some(steward.clone()), default_signer, ) .await?; - if !rpc::is_steward(client, &steward).await && !tx_args.force { - edisplay_line!(IO, "The given address {} is not a steward.", &steward); + if !rpc::is_steward(context.client, steward).await && !tx_args.force { + edisplay_line!(StdIo, "The given address {} is not a steward.", &steward); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, - steward, + build( + context, + tx_args, + tx_code_path.clone(), + steward.clone(), do_nothing, &signing_data.fee_payer, None, @@ -731,34 +690,26 @@ pub async fn build_resign_steward< } /// Submit transaction to unjail a jailed validator -pub async fn build_unjail_validator< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_unjail_validator<'a>( + context: &mut impl Namada<'a>, args::TxUnjailValidator { tx: tx_args, validator, tx_code_path, - }: args::TxUnjailValidator, + }: &args::TxUnjailValidator, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(validator.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx_args, + let signing_data = signing::aux_signing_data( + context, + tx_args, Some(validator.clone()), default_signer, ) .await?; - if !rpc::is_validator(client, &validator).await? { + if !rpc::is_validator(context.client, validator).await? { edisplay_line!( - IO, + StdIo, "The given address {} is not a validator.", &validator ); @@ -769,12 +720,12 @@ pub async fn build_unjail_validator< } } - let params: PosParams = rpc::get_pos_params(client).await?; - let current_epoch = rpc::query_epoch(client).await?; + let params: PosParams = rpc::get_pos_params(context.client).await?; + let current_epoch = rpc::query_epoch(context.client).await?; let pipeline_epoch = current_epoch + params.pipeline_len; let validator_state_at_pipeline = - rpc::get_validator_state(client, &validator, Some(pipeline_epoch)) + rpc::get_validator_state(context.client, validator, Some(pipeline_epoch)) .await? .ok_or_else(|| { Error::from(TxError::Other( @@ -783,7 +734,7 @@ pub async fn build_unjail_validator< })?; if validator_state_at_pipeline != ValidatorState::Jailed { edisplay_line!( - IO, + StdIo, "The given validator address {} is not jailed at the pipeline \ epoch when it would be restored to one of the validator sets.", &validator @@ -796,9 +747,9 @@ pub async fn build_unjail_validator< } let last_slash_epoch_key = - crate::ledger::pos::validator_last_slash_key(&validator); + crate::ledger::pos::validator_last_slash_key(validator); let last_slash_epoch = - rpc::query_storage_value::(client, &last_slash_epoch_key) + rpc::query_storage_value::<_, Epoch>(context.client, &last_slash_epoch_key) .await; match last_slash_epoch { Ok(last_slash_epoch) => { @@ -806,7 +757,7 @@ pub async fn build_unjail_validator< last_slash_epoch + params.slash_processing_epoch_offset(); if current_epoch < eligible_epoch { edisplay_line!( - IO, + StdIo, "The given validator address {} is currently frozen and \ not yet eligible to be unjailed.", &validator @@ -832,13 +783,11 @@ pub async fn build_unjail_validator< Err(err) => return Err(err), } - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, - validator, + build( + context, + tx_args, + tx_code_path.clone(), + validator.clone(), do_nothing, &signing_data.fee_payer, None, @@ -847,48 +796,37 @@ pub async fn build_unjail_validator< } /// Submit transaction to withdraw an unbond -pub async fn build_withdraw< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_withdraw<'a>( + context: &mut impl Namada<'a>, args::Withdraw { tx: tx_args, validator, source, tx_code_path, - }: args::Withdraw, + }: &args::Withdraw, ) -> Result<(Tx, SigningTxData, Option)> { let default_address = source.clone().unwrap_or(validator.clone()); let default_signer = Some(default_address.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx_args, + let signing_data = signing::aux_signing_data( + context, + tx_args, Some(default_address), default_signer, ) .await?; - let epoch = rpc::query_epoch(client).await?; + let epoch = rpc::query_epoch(context.client).await?; - let validator = known_validator_or_err::<_, IO>( - validator.clone(), - tx_args.force, - client, - ) - .await?; + let validator = + known_validator_or_err(validator.clone(), tx_args.force, context.client) + .await?; let source = source.clone(); // Check the source's current unbond amount let bond_source = source.clone().unwrap_or_else(|| validator.clone()); let tokens = rpc::query_withdrawable_tokens( - client, + context.client, &bond_source, &validator, Some(epoch), @@ -897,33 +835,30 @@ pub async fn build_withdraw< if tokens.is_zero() { edisplay_line!( - IO, + StdIo, "There are no unbonded bonds ready to withdraw in the current \ epoch {}.", epoch ); - rpc::query_and_print_unbonds::<_, IO>(client, &bond_source, &validator) - .await?; + rpc::query_and_print_unbonds(context.client, &bond_source, &validator).await?; if !tx_args.force { return Err(Error::from(TxError::NoUnbondReady(epoch))); } } else { display_line!( - IO, + StdIo, "Found {} tokens that can be withdrawn.", tokens.to_string_native() ); - display_line!(IO, "Submitting transaction to withdraw them..."); + display_line!(StdIo, "Submitting transaction to withdraw them..."); } let data = pos::Withdraw { validator, source }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, do_nothing, &signing_data.fee_payer, @@ -933,29 +868,21 @@ pub async fn build_withdraw< } /// Submit a transaction to unbond -pub async fn build_unbond< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_unbond<'a>( + context: &mut impl Namada<'a>, args::Unbond { tx: tx_args, validator, amount, source, tx_code_path, - }: args::Unbond, + }: &args::Unbond, ) -> Result<(Tx, SigningTxData, Option, Option<(Epoch, token::Amount)>)> { let default_address = source.clone().unwrap_or(validator.clone()); let default_signer = Some(default_address.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx_args, + let signing_data = signing::aux_signing_data( + context, + tx_args, Some(default_address), default_signer, ) @@ -966,24 +893,20 @@ pub async fn build_unbond< let bond_source = source.clone().unwrap_or_else(|| validator.clone()); if !tx_args.force { - known_validator_or_err::<_, IO>( - validator.clone(), - tx_args.force, - client, - ) - .await?; + known_validator_or_err(validator.clone(), tx_args.force, context.client) + .await?; let bond_amount = - rpc::query_bond(client, &bond_source, &validator, None).await?; + rpc::query_bond(context.client, &bond_source, validator, None).await?; display_line!( - IO, + StdIo, "Bond amount available for unbonding: {} NAM", bond_amount.to_string_native() ); - if amount > bond_amount { + if *amount > bond_amount { edisplay_line!( - IO, + StdIo, "The total bonds of the source {} is lower than the amount to \ be unbonded. Amount to unbond is {} and the total bonds is \ {}.", @@ -1003,7 +926,7 @@ pub async fn build_unbond< // Query the unbonds before submitting the tx let unbonds = - rpc::query_unbond_with_slashing(client, &bond_source, &validator) + rpc::query_unbond_with_slashing(context.client, &bond_source, validator) .await?; let mut withdrawable = BTreeMap::::new(); for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { @@ -1014,16 +937,14 @@ pub async fn build_unbond< let data = pos::Unbond { validator: validator.clone(), - amount, + amount: *amount, source: source.clone(), }; - let (tx, epoch) = build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + let (tx, epoch) = build( + context, + tx_args, + tx_code_path.clone(), data, do_nothing, &signing_data.fee_payer, @@ -1102,15 +1023,8 @@ pub async fn query_unbonds( } /// Submit a transaction to bond -pub async fn build_bond< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_bond<'a>( + context: &mut impl Namada<'a>, args::Bond { tx: tx_args, validator, @@ -1118,65 +1032,60 @@ pub async fn build_bond< source, native_token, tx_code_path, - }: args::Bond, + }: &args::Bond, ) -> Result<(Tx, SigningTxData, Option)> { let default_address = source.clone().unwrap_or(validator.clone()); let default_signer = Some(default_address.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx_args, + let signing_data = signing::aux_signing_data( + context, + tx_args, Some(default_address.clone()), default_signer, ) .await?; let validator = - known_validator_or_err::<_, IO>(validator.clone(), tx_args.force, client) + known_validator_or_err(validator.clone(), tx_args.force, context.client) .await?; // Check that the source address exists on chain let source = match source.clone() { - Some(source) => { - source_exists_or_err::<_, IO>(source, tx_args.force, client) - .await - .map(Some) - } + Some(source) => source_exists_or_err(source, tx_args.force, context.client) + .await + .map(Some), None => Ok(source.clone()), }?; // Check bond's source (source for delegation or validator for self-bonds) // balance let bond_source = source.as_ref().unwrap_or(&validator); - let balance_key = token::balance_key(&native_token, bond_source); + let balance_key = token::balance_key(native_token, bond_source); // TODO Should we state the same error message for the native token? - let post_balance = check_balance_too_low_err::<_, IO>( - &native_token, + let post_balance = check_balance_too_low_err( + native_token, bond_source, - amount, + *amount, balance_key, tx_args.force, - client, + context.client, ) .await?; let tx_source_balance = Some(TxSourcePostBalance { post_balance, source: bond_source.clone(), - token: native_token, + token: native_token.clone(), }); let data = pos::Bond { validator, - amount, + amount: *amount, source, }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, do_nothing, &signing_data.fee_payer, @@ -1186,15 +1095,8 @@ pub async fn build_bond< } /// Build a default proposal governance -pub async fn build_default_proposal< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_default_proposal<'a>( + context: &mut impl Namada<'a>, args::InitProposal { tx, proposal_data: _, @@ -1203,14 +1105,13 @@ pub async fn build_default_proposal< is_pgf_stewards: _, is_pgf_funding: _, tx_code_path, - }: args::InitProposal, + }: &args::InitProposal, proposal: DefaultProposal, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx, + let signing_data = signing::aux_signing_data( + context, + tx, Some(proposal.proposal.author.clone()), default_signer, ) @@ -1233,12 +1134,10 @@ pub async fn build_default_proposal< }; Ok(()) }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx, - tx_code_path, + build( + context, + tx, + tx_code_path.clone(), init_proposal_data, push_data, &signing_data.fee_payer, @@ -1248,15 +1147,8 @@ pub async fn build_default_proposal< } /// Build a proposal vote -pub async fn build_vote_proposal< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_vote_proposal<'a>( + context: &mut impl Namada<'a>, args::VoteProposal { tx, proposal_id, @@ -1265,27 +1157,26 @@ pub async fn build_vote_proposal< is_offline: _, proposal_data: _, tx_code_path, - }: args::VoteProposal, + }: &args::VoteProposal, epoch: Epoch, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(voter.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx, + let signing_data = signing::aux_signing_data( + context, + tx, Some(voter.clone()), default_signer.clone(), ) .await?; - let proposal_vote = ProposalVote::try_from(vote) + let proposal_vote = ProposalVote::try_from(vote.clone()) .map_err(|_| TxError::InvalidProposalVote)?; let proposal_id = proposal_id.ok_or_else(|| { Error::Other("Proposal id must be defined.".to_string()) })?; let proposal = if let Some(proposal) = - rpc::query_proposal_by_id(client, proposal_id).await? + rpc::query_proposal_by_id(context.client, proposal_id).await? { proposal } else { @@ -1300,7 +1191,7 @@ pub async fn build_vote_proposal< )) })?; - let is_validator = rpc::is_validator(client, &voter).await?; + let is_validator = rpc::is_validator(context.client, voter).await?; if !proposal.can_be_voted(epoch, is_validator) { if tx.force { @@ -1313,8 +1204,8 @@ pub async fn build_vote_proposal< } let delegations = rpc::get_delegators_delegation_at( - client, - &voter, + context.client, + voter, proposal.voting_start_epoch, ) .await? @@ -1329,12 +1220,10 @@ pub async fn build_vote_proposal< delegations, }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx, - tx_code_path, + build( + context, + tx, + tx_code_path.clone(), data, do_nothing, &signing_data.fee_payer, @@ -1344,15 +1233,8 @@ pub async fn build_vote_proposal< } /// Build a pgf funding proposal governance -pub async fn build_pgf_funding_proposal< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_pgf_funding_proposal<'a>( + context: &mut impl Namada<'a>, args::InitProposal { tx, proposal_data: _, @@ -1361,14 +1243,13 @@ pub async fn build_pgf_funding_proposal< is_pgf_stewards: _, is_pgf_funding: _, tx_code_path, - }: args::InitProposal, + }: &args::InitProposal, proposal: PgfFundingProposal, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx, + let signing_data = signing::aux_signing_data( + context, + tx, Some(proposal.proposal.author.clone()), default_signer, ) @@ -1383,12 +1264,10 @@ pub async fn build_pgf_funding_proposal< data.content = extra_section_hash; Ok(()) }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx, - tx_code_path, + build( + context, + tx, + tx_code_path.clone(), init_proposal_data, add_section, &signing_data.fee_payer, @@ -1398,15 +1277,8 @@ pub async fn build_pgf_funding_proposal< } /// Build a pgf funding proposal governance -pub async fn build_pgf_stewards_proposal< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_pgf_stewards_proposal<'a>( + context: &mut impl Namada<'a>, args::InitProposal { tx, proposal_data: _, @@ -1415,14 +1287,13 @@ pub async fn build_pgf_stewards_proposal< is_pgf_stewards: _, is_pgf_funding: _, tx_code_path, - }: args::InitProposal, + }: &args::InitProposal, proposal: PgfStewardProposal, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx, + let signing_data = signing::aux_signing_data( + context, + tx, Some(proposal.proposal.author.clone()), default_signer, ) @@ -1438,12 +1309,10 @@ pub async fn build_pgf_stewards_proposal< Ok(()) }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx, - tx_code_path, + build( + context, + tx, + tx_code_path.clone(), init_proposal_data, add_section, &signing_data.fee_payer, @@ -1453,43 +1322,29 @@ pub async fn build_pgf_stewards_proposal< } /// Submit an IBC transfer -pub async fn build_ibc_transfer< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, - args: args::TxIbcTransfer, +pub async fn build_ibc_transfer<'a>( + context: &mut impl Namada<'a>, + args: &args::TxIbcTransfer, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(args.source.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, + let signing_data = signing::aux_signing_data( + context, &args.tx, Some(args.source.clone()), default_signer, ) .await?; // Check that the source address exists on chain - let source = source_exists_or_err::<_, IO>( - args.source.clone(), - args.tx.force, - client, - ) - .await?; + let source = + source_exists_or_err(args.source.clone(), args.tx.force, context.client) + .await?; // We cannot check the receiver // validate the amount given - let validated_amount = validate_amount::<_, IO>( - client, - args.amount, - &args.token, - args.tx.force, - ) - .await?; + let validated_amount = + validate_amount(context.client, args.amount, &args.token, args.tx.force) + .await + .expect("expected to validate amount"); if validated_amount.canonical().denom.0 != 0 { return Err(Error::Other(format!( "The amount for the IBC transfer should be an integer: {}", @@ -1500,13 +1355,13 @@ pub async fn build_ibc_transfer< // Check source balance let balance_key = token::balance_key(&args.token, &source); - let post_balance = check_balance_too_low_err::<_, IO>( + let post_balance = check_balance_too_low_err( &args.token, &source, validated_amount.amount, balance_key, args.tx.force, - client, + context.client, ) .await?; let tx_source_balance = Some(TxSourcePostBalance { @@ -1515,17 +1370,15 @@ pub async fn build_ibc_transfer< token: args.token.clone(), }); - let tx_code_hash = query_wasm_code_hash::<_, IO>( - client, - args.tx_code_path.to_str().unwrap(), - ) - .await - .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; + let tx_code_hash = + query_wasm_code_hash(context.client, args.tx_code_path.to_str().unwrap()) + .await + .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; let ibc_denom = match &args.token { Address::Internal(InternalAddress::IbcToken(hash)) => { let ibc_denom_key = ibc_denom_key(hash); - rpc::query_storage_value::(client, &ibc_denom_key) + rpc::query_storage_value::<_, String>(context.client, &ibc_denom_key) .await .map_err(|_e| TxError::TokenDoesNotExist(args.token.clone()))? } @@ -1539,8 +1392,8 @@ pub async fn build_ibc_transfer< let packet_data = PacketData { token, sender: source.to_string().into(), - receiver: args.receiver.into(), - memo: args.memo.unwrap_or_default().into(), + receiver: args.receiver.clone().into(), + memo: args.memo.clone().unwrap_or_default().into(), }; // this height should be that of the destination chain, not this chain @@ -1571,8 +1424,8 @@ pub async fn build_ibc_transfer< }; let msg = MsgTransfer { - port_id_on_a: args.port_id, - chan_id_on_a: args.channel_id, + port_id_on_a: args.port_id.clone(), + chan_id_on_a: args.channel_id.clone(), packet_data, timeout_height_on_b: timeout_height, timeout_timestamp_on_b: timeout_timestamp, @@ -1588,10 +1441,8 @@ pub async fn build_ibc_transfer< tx.add_code_from_hash(tx_code_hash) .add_serialized_data(data); - let epoch = prepare_tx::( - client, - wallet, - shielded, + let epoch = prepare_tx( + context, &args.tx, &mut tx, signing_data.fee_payer.clone(), @@ -1604,10 +1455,8 @@ pub async fn build_ibc_transfer< /// Abstraction for helping build transactions #[allow(clippy::too_many_arguments)] -pub async fn build( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build<'a, F, D>( + context: &mut impl Namada<'a>, tx_args: &crate::sdk::args::Tx, path: PathBuf, data: D, @@ -1618,14 +1467,9 @@ pub async fn build( where F: FnOnce(&mut Tx, &mut D) -> Result<()>, D: BorshSerialize, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, { - build_pow_flag::<_, _, _, _, _, IO>( - client, - wallet, - shielded, + build_pow_flag( + context, tx_args, path, data, @@ -1637,17 +1481,8 @@ where } #[allow(clippy::too_many_arguments)] -async fn build_pow_flag< - C: crate::ledger::queries::Client + Sync, - U, - V, - F, - D, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +async fn build_pow_flag<'a, F, D>( + context: &mut impl Namada<'a>, tx_args: &crate::sdk::args::Tx, path: PathBuf, mut data: D, @@ -1658,26 +1493,21 @@ async fn build_pow_flag< where F: FnOnce(&mut Tx, &mut D) -> Result<()>, D: BorshSerialize, - U: WalletUtils, - V: ShieldedUtils, { let chain_id = tx_args.chain_id.clone().unwrap(); let mut tx_builder = Tx::new(chain_id, tx_args.expiration); - let tx_code_hash = - query_wasm_code_hash::<_, IO>(client, path.to_string_lossy()) - .await - .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; + let tx_code_hash = query_wasm_code_hash(context.client, path.to_string_lossy()) + .await + .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; on_tx(&mut tx_builder, &mut data)?; tx_builder.add_code_from_hash(tx_code_hash).add_data(data); - let epoch = prepare_tx::( - client, - wallet, - shielded, + let epoch = prepare_tx( + context, tx_args, &mut tx_builder, gas_payer.clone(), @@ -1689,17 +1519,14 @@ where /// Try to decode the given asset type and add its decoding to the supplied set. /// Returns true only if a new decoding has been added to the given set. -async fn add_asset_type< - C: crate::sdk::queries::Client + Sync, - U: ShieldedUtils, ->( +async fn add_asset_type<'a>( asset_types: &mut HashSet<(Address, MaspDenom, Epoch)>, - shielded: &mut ShieldedContext, - client: &C, + context: &mut impl Namada<'a>, asset_type: AssetType, ) -> bool { + let context = &mut **context; if let Some(asset_type) = - shielded.decode_asset_type(client, asset_type).await + context.shielded.decode_asset_type(context.client, asset_type).await { asset_types.insert(asset_type) } else { @@ -1710,42 +1537,33 @@ async fn add_asset_type< /// Collect the asset types used in the given Builder and decode them. This /// function provides the data necessary for offline wallets to present asset /// type information. -async fn used_asset_types< - C: crate::sdk::queries::Client + Sync, - U: ShieldedUtils, - P, - R, - K, - N, ->( - shielded: &mut ShieldedContext, - client: &C, +async fn used_asset_types<'a, P, R, K, N>( + context: &mut impl Namada<'a>, builder: &Builder, ) -> std::result::Result, RpcError> { let mut asset_types = HashSet::new(); // Collect all the asset types used in the Sapling inputs for input in builder.sapling_inputs() { - add_asset_type(&mut asset_types, shielded, client, input.asset_type()) + add_asset_type(&mut asset_types, context, input.asset_type()) .await; } // Collect all the asset types used in the transparent inputs for input in builder.transparent_inputs() { add_asset_type( &mut asset_types, - shielded, - client, + context, input.coin().asset_type(), ) .await; } // Collect all the asset types used in the Sapling outputs for output in builder.sapling_outputs() { - add_asset_type(&mut asset_types, shielded, client, output.asset_type()) + add_asset_type(&mut asset_types, context, output.asset_type()) .await; } // Collect all the asset types used in the transparent outputs for output in builder.transparent_outputs() { - add_asset_type(&mut asset_types, shielded, client, output.asset_type()) + add_asset_type(&mut asset_types, context, output.asset_type()) .await; } // Collect all the asset types used in the Sapling converts @@ -1753,7 +1571,7 @@ async fn used_asset_types< for (asset_type, _) in I32Sum::from(output.conversion().clone()).components() { - add_asset_type(&mut asset_types, shielded, client, *asset_type) + add_asset_type(&mut asset_types, context, *asset_type) .await; } } @@ -1761,21 +1579,13 @@ async fn used_asset_types< } /// Submit an ordinary transfer -pub async fn build_transfer< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_transfer<'a, N: Namada<'a>>( + context: &mut N, mut args: args::TxTransfer, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(args.source.effective_address()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, + let signing_data = signing::aux_signing_data( + context, &args.tx, Some(args.source.effective_address()), default_signer, @@ -1787,27 +1597,24 @@ pub async fn build_transfer< let token = args.token.clone(); // Check that the source address exists on chain - source_exists_or_err::<_, IO>(source.clone(), args.tx.force, client) - .await?; + source_exists_or_err(source.clone(), args.tx.force, context.client).await?; // Check that the target address exists on chain - target_exists_or_err::<_, IO>(target.clone(), args.tx.force, client) - .await?; + target_exists_or_err(target.clone(), args.tx.force, context.client).await?; // Check source balance let balance_key = token::balance_key(&token, &source); // validate the amount given let validated_amount = - validate_amount::<_, IO>(client, args.amount, &token, args.tx.force) - .await?; + validate_amount(context.client, args.amount, &token, args.tx.force).await?; args.amount = InputAmount::Validated(validated_amount); - let post_balance = check_balance_too_low_err::( + let post_balance = check_balance_too_low_err( &token, &source, validated_amount.amount, balance_key, args.tx.force, - client, + context.client, ) .await?; let tx_source_balance = Some(TxSourcePostBalance { @@ -1835,9 +1642,7 @@ pub async fn build_transfer< }; // Construct the shielded part of the transaction, if any - let stx_result = shielded - .gen_shielded_transfer::<_, IO>(client, args.clone()) - .await; + let stx_result = ShieldedContext::::gen_shielded_transfer(context, &args).await; let shielded_parts = match stx_result { Ok(stx) => Ok(stx), @@ -1859,7 +1664,7 @@ pub async fn build_transfer< // Get the decoded asset types used in the transaction to give // offline wallet users more information let asset_types = - used_asset_types(shielded, client, &transfer.builder) + used_asset_types(context, &transfer.builder) .await .unwrap_or_default(); Some(asset_types) @@ -1905,12 +1710,10 @@ pub async fn build_transfer< }; Ok(()) }; - let (tx, unshielding_epoch) = build_pow_flag::<_, _, _, _, _, IO>( - client, - wallet, - shielded, + let (tx, unshielding_epoch) = build_pow_flag( + context, &args.tx, - args.tx_code_path, + args.tx_code_path.clone(), transfer, add_shielded, &signing_data.fee_payer, @@ -1940,30 +1743,23 @@ pub async fn build_transfer< } /// Submit a transaction to initialize an account -pub async fn build_init_account< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_init_account<'a>( + context: &mut impl Namada<'a>, args::TxInitAccount { tx: tx_args, vp_code_path, tx_code_path, public_keys, threshold, - }: args::TxInitAccount, + }: &args::TxInitAccount, ) -> Result<(Tx, SigningTxData, Option)> { let signing_data = - signing::aux_signing_data::<_, _, IO>(client, wallet, &tx_args, None, None).await?; + signing::aux_signing_data(context, tx_args, None, None).await?; - let vp_code_hash = query_wasm_code_hash_buf::<_, IO>(client, &vp_code_path).await?; + let vp_code_hash = query_wasm_code_hash_buf(context.client, vp_code_path).await?; let threshold = match threshold { - Some(threshold) => threshold, + Some(threshold) => *threshold, None => { if public_keys.len() == 1 { 1u8 @@ -1974,7 +1770,7 @@ pub async fn build_init_account< }; let data = InitAccount { - public_keys, + public_keys: public_keys.clone(), // We will add the hash inside the add_code_hash function vp_code_hash: Hash::zero(), threshold, @@ -1985,12 +1781,10 @@ pub async fn build_init_account< data.vp_code_hash = extra_section_hash; Ok(()) }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, add_code_hash, &signing_data.fee_payer, @@ -2000,15 +1794,8 @@ pub async fn build_init_account< } /// Submit a transaction to update a VP -pub async fn build_update_account< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_update_account<'a>( + context: &mut impl Namada<'a>, args::TxUpdateAccount { tx: tx_args, vp_code_path, @@ -2016,31 +1803,29 @@ pub async fn build_update_account< addr, public_keys, threshold, - }: args::TxUpdateAccount, + }: &args::TxUpdateAccount, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(addr.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx_args, + let signing_data = signing::aux_signing_data( + context, + tx_args, Some(addr.clone()), default_signer, ) .await?; let addr = - if let Some(account) = rpc::get_account_info(client, &addr).await? { + if let Some(account) = rpc::get_account_info(context.client, addr).await? { account.address } else if tx_args.force { - addr + addr.clone() } else { - return Err(Error::from(TxError::LocationDoesNotExist(addr))); + return Err(Error::from(TxError::LocationDoesNotExist(addr.clone()))); }; let vp_code_hash = match vp_code_path { Some(code_path) => { - let vp_hash = - query_wasm_code_hash_buf::<_, IO>(client, &code_path).await?; + let vp_hash = query_wasm_code_hash_buf(context.client, code_path).await?; Some(vp_hash) } None => None, @@ -2054,8 +1839,8 @@ pub async fn build_update_account< let data = UpdateAccount { addr, vp_code_hash: extra_section_hash, - public_keys, - threshold, + public_keys: public_keys.clone(), + threshold: *threshold, }; let add_code_hash = |tx: &mut Tx, data: &mut UpdateAccount| { @@ -2064,12 +1849,10 @@ pub async fn build_update_account< data.vp_code_hash = extra_section_hash; Ok(()) }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, add_code_hash, &signing_data.fee_payer, @@ -2079,28 +1862,20 @@ pub async fn build_update_account< } /// Submit a custom transaction -pub async fn build_custom< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_custom<'a>( + context: &mut impl Namada<'a>, args::TxCustom { tx: tx_args, code_path, data_path, serialized_tx, owner, - }: args::TxCustom, + }: &args::TxCustom, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(owner.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - &tx_args, + let signing_data = signing::aux_signing_data( + context, + tx_args, Some(owner.clone()), default_signer, ) @@ -2111,24 +1886,22 @@ pub async fn build_custom< Error::Other("Invalid tx deserialization.".to_string()) })? } else { - let tx_code_hash = query_wasm_code_hash_buf::<_, IO>( - client, - &code_path + let tx_code_hash = query_wasm_code_hash_buf( + context.client, + code_path.as_ref() .ok_or(Error::Other("No code path supplied".to_string()))?, ) .await?; let chain_id = tx_args.chain_id.clone().unwrap(); let mut tx = Tx::new(chain_id, tx_args.expiration); tx.add_code_from_hash(tx_code_hash); - data_path.map(|data| tx.add_serialized_data(data)); + data_path.clone().map(|data| tx.add_serialized_data(data)); tx }; - let epoch = prepare_tx::( - client, - wallet, - shielded, - &tx_args, + let epoch = prepare_tx( + context, + tx_args, &mut tx, signing_data.fee_payer.clone(), None, @@ -2167,7 +1940,6 @@ fn lift_rpc_error(res: std::result::Result) -> Result { /// if it isn't a validator async fn known_validator_or_err< C: crate::ledger::queries::Client + Sync, - IO: Io, >( validator: Address, force: bool, @@ -2178,7 +1950,7 @@ async fn known_validator_or_err< if !is_validator { if force { edisplay_line!( - IO, + StdIo, "The address {} doesn't belong to any known validator account.", validator ); @@ -2194,7 +1966,7 @@ async fn known_validator_or_err< /// general pattern for checking if an address exists on the chain, or /// throwing an error if it's not forced. Takes a generic error /// message and the error type. -async fn address_exists_or_err( +async fn address_exists_or_err( addr: Address, force: bool, client: &C, @@ -2208,7 +1980,7 @@ where let addr_exists = rpc::known_address::(client, &addr).await?; if !addr_exists { if force { - edisplay_line!(IO, "{}", message); + edisplay_line!(StdIo, "{}", message); Ok(addr) } else { Err(err(addr)) @@ -2223,7 +1995,6 @@ where /// if it isn't on chain async fn source_exists_or_err< C: crate::ledger::queries::Client + Sync, - IO: Io, >( token: Address, force: bool, @@ -2231,7 +2002,7 @@ async fn source_exists_or_err< ) -> Result
{ let message = format!("The source address {} doesn't exist on chain.", token); - address_exists_or_err::<_, _, IO>(token, force, client, message, |err| { + address_exists_or_err(token, force, client, message, |err| { Error::from(TxError::SourceDoesNotExist(err)) }) .await @@ -2242,7 +2013,6 @@ async fn source_exists_or_err< /// if it isn't on chain async fn target_exists_or_err< C: crate::ledger::queries::Client + Sync, - IO: Io, >( token: Address, force: bool, @@ -2250,7 +2020,7 @@ async fn target_exists_or_err< ) -> Result
{ let message = format!("The target address {} doesn't exist on chain.", token); - address_exists_or_err::<_, _, IO>(token, force, client, message, |err| { + address_exists_or_err(token, force, client, message, |err| { Error::from(TxError::TargetLocationDoesNotExist(err)) }) .await @@ -2261,7 +2031,6 @@ async fn target_exists_or_err< /// overrides this. Returns the updated balance for fee check if necessary async fn check_balance_too_low_err< C: crate::ledger::queries::Client + Sync, - IO: Io, >( token: &Address, source: &Address, @@ -2278,17 +2047,17 @@ async fn check_balance_too_low_err< None => { if force { edisplay_line!( - IO, + StdIo, "The balance of the source {} of token {} is lower \ than the amount to be transferred. Amount to \ transfer is {} and the balance is {}.", source, token, - format_denominated_amount::<_, IO>( + format_denominated_amount( client, token, amount ) .await, - format_denominated_amount::<_, IO>( + format_denominated_amount( client, token, balance ) .await, @@ -2309,7 +2078,7 @@ async fn check_balance_too_low_err< )) => { if force { edisplay_line!( - IO, + StdIo, "No balance found for the source {} of token {}", source, token @@ -2350,12 +2119,11 @@ fn validate_untrusted_code_err( } async fn query_wasm_code_hash_buf< C: crate::ledger::queries::Client + Sync, - IO: Io, >( client: &C, path: &Path, ) -> Result { - query_wasm_code_hash::<_, IO>(client, path.to_string_lossy()).await + query_wasm_code_hash(client, path.to_string_lossy()).await } /// A helper for [`fn build`] that can be used for `on_tx` arg that does nothing diff --git a/shared/src/types/io.rs b/shared/src/types/io.rs index 462dbef95f..007d5acd93 100644 --- a/shared/src/types/io.rs +++ b/shared/src/types/io.rs @@ -3,10 +3,10 @@ //! functions. /// Rust native I/O handling. -pub struct DefaultIo; +pub struct StdIo; #[async_trait::async_trait(?Send)] -impl Io for DefaultIo {} +impl Io for StdIo {} #[async_trait::async_trait(?Send)] #[allow(missing_docs)] diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 2f5bbe4ea7..b763aec013 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -20,7 +20,7 @@ use borsh::BorshSerialize; use color_eyre::eyre::Result; use data_encoding::HEXLOWER; use namada::types::address::Address; -use namada::types::io::DefaultIo; +use namada::types::io::StdIo; use namada::types::storage::Epoch; use namada::types::token; use namada_apps::client::tx::CLIShieldedUtils; @@ -688,7 +688,7 @@ fn ledger_txs_and_queries() -> Result<()> { #[test] fn masp_txs_and_queries() -> Result<()> { // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. @@ -836,7 +836,7 @@ fn masp_txs_and_queries() -> Result<()> { #[test] fn wrapper_disposable_signer() -> Result<()> { // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. diff --git a/tests/src/integration/masp.rs b/tests/src/integration/masp.rs index ecd1b34465..1cd08b9976 100644 --- a/tests/src/integration/masp.rs +++ b/tests/src/integration/masp.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use color_eyre::eyre::Result; use color_eyre::owo_colors::OwoColorize; -use namada::types::io::DefaultIo; +use namada::types::io::StdIo; use namada_apps::client::tx::CLIShieldedUtils; use namada_apps::node::ledger::shell::testing::client::run; use namada_apps::node::ledger::shell::testing::utils::{Bin, CapturedOutput}; @@ -29,7 +29,7 @@ fn masp_incentives() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. @@ -765,7 +765,7 @@ fn masp_pinned_txs() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); let mut node = setup::setup()?; // Wait till epoch boundary @@ -928,7 +928,7 @@ fn masp_txs_and_queries() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); enum Response { Ok(&'static str), @@ -1234,7 +1234,7 @@ fn wrapper_fee_unshielding() { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. From d10ba96b8039d9ac480d0cd1798e599ac6c76914 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Mon, 25 Sep 2023 10:48:29 +0200 Subject: [PATCH 03/14] Allow the prototypical Tx builder to be modified in NamadaImpl instances. --- shared/src/ledger/mod.rs | 78 ++++++++++++++++++++++++++++++++++------ shared/src/sdk/args.rs | 10 +++++- shared/src/sdk/tx.rs | 5 +-- 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 505699d707..d82e09bd8a 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -23,6 +23,7 @@ use crate::sdk::masp::{ShieldedContext, ShieldedUtils}; use crate::types::masp::{TransferSource, TransferTarget}; use crate::types::address::Address; use crate::sdk::args::{self, InputAmount}; +use crate::sdk::args::SdkTypes; use crate::sdk::tx::{ TX_TRANSFER_WASM, TX_REVEAL_PK, TX_BOND_WASM, TX_UNBOND_WASM, TX_IBC_WASM, TX_INIT_PROPOSAL, TX_UPDATE_ACCOUNT_WASM, TX_VOTE_PROPOSAL, VP_USER_WASM, @@ -68,6 +69,11 @@ pub trait Namada<'a> : DerefMut Address { + self.wallet.find_address(args::NAM).expect("NAM not in wallet").clone() + } /// Make a tx builder using no arguments fn tx_builder(&mut self) -> args::Tx { @@ -83,7 +89,7 @@ pub trait Namada<'a> : DerefMut : DerefMut : DerefMut : DerefMut args::InitProposal { args::InitProposal { proposal_data, - native_token: self.wallet.find_address(args::NAM).expect("NAM not in wallet").clone(), + native_token: self.native_token(), is_offline: false, is_pgf_stewards: false, is_pgf_funding: false, @@ -310,7 +316,7 @@ pub trait Namada<'a> : DerefMut : DerefMut(NamadaStruct<'a, C, U, V>) where +pub struct NamadaImpl<'a, C, U, V> where C: crate::ledger::queries::Client + Sync, U: WalletUtils, - V: ShieldedUtils; + V: ShieldedUtils, +{ + namada: NamadaStruct<'a, C, U, V>, + prototype: args::Tx, +} impl<'a, C, U, V> NamadaImpl<'a, C, U, V> where C: crate::ledger::queries::Client + Sync, @@ -392,7 +402,37 @@ impl<'a, C, U, V> NamadaImpl<'a, C, U, V> where wallet: &'a mut Wallet, shielded: &'a mut ShieldedContext, ) -> Self { - Self(NamadaStruct { client, wallet, shielded }) + let fee_token = wallet + .find_address(args::NAM) + .expect("NAM not in wallet") + .clone(); + Self { + namada: NamadaStruct { client, wallet, shielded }, + prototype: args::Tx { + dry_run: false, + dry_run_wrapper: false, + dump_tx: false, + output_folder: None, + force: false, + broadcast_only: false, + ledger_address: (), + initialized_account_alias: None, + wallet_alias_force: false, + fee_amount: None, + wrapper_fee_payer: None, + fee_token, + fee_unshield: None, + gas_limit: GasLimit::from(20_000), + expiration: None, + disposable_signing_key: false, + chain_id: None, + signing_keys: vec![], + signatures: vec![], + tx_reveal_code_path: PathBuf::from(TX_REVEAL_PK), + verification_key: None, + password: None, + }, + } } } @@ -404,7 +444,7 @@ impl<'a, C, U, V> Deref for NamadaImpl<'a, C, U, V> where type Target = NamadaStruct<'a, C, U, V>; fn deref(&self) -> &Self::Target { - &self.0 + &self.namada } } @@ -414,7 +454,7 @@ impl<'a, C, U, V> DerefMut for NamadaImpl<'a, C, U, V> where V: ShieldedUtils, { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + &mut self.namada } } @@ -426,4 +466,22 @@ impl<'a, C, U, V> Namada<'a> for NamadaImpl<'a, C, U, V> where type Client = C; type WalletUtils = U; type ShieldedUtils = V; + + /// Obtain the prototypical Tx builder + fn tx_builder(&mut self) -> args::Tx { + self.prototype.clone() + } +} + +/// Allow the prototypical Tx builder to be modified +impl<'a, C, U, V> args::TxBuilder for NamadaImpl<'a, C, U, V> where + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, + V: ShieldedUtils, +{ + fn tx(self, func: F) -> Self where + F: FnOnce(args::Tx) -> args::Tx, + { + Self { prototype: func(self.prototype), ..self } + } } diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs index 6e1381b18f..3c6dca8cfa 100644 --- a/shared/src/sdk/args.rs +++ b/shared/src/sdk/args.rs @@ -180,6 +180,14 @@ pub enum InputAmount { Unvalidated(token::DenominatedAmount), } +impl std::str::FromStr for InputAmount { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + token::DenominatedAmount::from_str(s).map(InputAmount::Unvalidated) + } +} + /// Transfer transaction arguments #[derive(Clone, Debug)] pub struct TxTransfer { @@ -234,7 +242,7 @@ impl TxTransfer { impl TxTransfer { /// Build a transaction from this builder - pub async fn build<'a>(self, context: &mut impl Namada<'a>) -> + pub async fn build<'a>(&mut self, context: &mut impl Namada<'a>) -> crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> { tx::build_transfer(context, self).await diff --git a/shared/src/sdk/tx.rs b/shared/src/sdk/tx.rs index 2ca4879bfe..4178986caf 100644 --- a/shared/src/sdk/tx.rs +++ b/shared/src/sdk/tx.rs @@ -110,6 +110,7 @@ pub const TX_UPDATE_STEWARD_COMMISSION: &str = const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; /// Capture the result of running a transaction +#[derive(Debug)] pub enum ProcessTxResponse { /// Result of submitting a transaction to the blockchain Applied(TxResponse), @@ -1581,7 +1582,7 @@ async fn used_asset_types<'a, P, R, K, N>( /// Submit an ordinary transfer pub async fn build_transfer<'a, N: Namada<'a>>( context: &mut N, - mut args: args::TxTransfer, + args: &mut args::TxTransfer, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(args.source.effective_address()); let signing_data = signing::aux_signing_data( @@ -1642,7 +1643,7 @@ pub async fn build_transfer<'a, N: Namada<'a>>( }; // Construct the shielded part of the transaction, if any - let stx_result = ShieldedContext::::gen_shielded_transfer(context, &args).await; + let stx_result = ShieldedContext::::gen_shielded_transfer(context, args).await; let shielded_parts = match stx_result { Ok(stx) => Ok(stx), From e471b6c17f6751575ee03eca5864506fd6490c26 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 27 Sep 2023 17:18:57 +0200 Subject: [PATCH 04/14] Moved FsShieldedUtils into the SDK behind a feature flag. --- apps/src/lib/cli/context.rs | 6 +- apps/src/lib/client/tx.rs | 197 +----- benches/lib.rs | 18 +- shared/src/ledger/eth_bridge/bridge_pool.rs | 12 +- shared/src/ledger/mod.rs | 80 ++- shared/src/sdk/args.rs | 688 +++++++++++++++----- shared/src/sdk/masp.rs | 138 +++- shared/src/sdk/signing.rs | 49 +- shared/src/sdk/tx.rs | 299 +++++---- tests/src/e2e/ledger_tests.rs | 7 +- tests/src/integration/masp.rs | 11 +- 11 files changed, 969 insertions(+), 536 deletions(-) diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 4aac8b1026..f2efec7fee 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use color_eyre::eyre::Result; use namada::sdk::masp::ShieldedContext; use namada::sdk::wallet::Wallet; +use namada::sdk::masp::fs::FsShieldedUtils; use namada::types::address::{Address, InternalAddress}; use namada::types::chain::ChainId; use namada::types::ethereum_events::EthAddress; @@ -16,7 +17,6 @@ use namada::types::key::*; use namada::types::masp::*; use super::args; -use crate::client::tx::CLIShieldedUtils; #[cfg(any(test, feature = "dev"))] use crate::config::genesis; use crate::config::genesis::genesis_config; @@ -78,7 +78,7 @@ pub struct Context { /// The ledger configuration for a specific chain ID pub config: Config, /// The context fr shielded operations - pub shielded: ShieldedContext, + pub shielded: ShieldedContext, /// Native token's address pub native_token: Address, } @@ -145,7 +145,7 @@ impl Context { wallet, global_config, config, - shielded: CLIShieldedUtils::new::(chain_dir), + shielded: FsShieldedUtils::new(chain_dir), native_token, }) } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 4b2aa9b865..1289492faa 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,11 +1,5 @@ -use std::env; -use std::fmt::Debug; -use std::fs::{File, OpenOptions}; -use std::io::{Read, Write}; -use std::path::PathBuf; - -use borsh::{BorshDeserialize, BorshSerialize}; -use masp_proofs::prover::LocalTxProver; +use std::fs::File; + use namada::core::ledger::governance::cli::offline::{ OfflineProposal, OfflineSignedProposal, OfflineVote, }; @@ -13,11 +7,12 @@ use namada::core::ledger::governance::cli::onchain::{ DefaultProposal, PgfFundingProposal, PgfStewardProposal, ProposalVote, }; use namada::ledger::pos; -use namada::proof_of_stake::parameters::PosParams; -use namada::proto::Tx; use namada::sdk::rpc::{TxBroadcastData, TxResponse}; use namada::sdk::wallet::{Wallet, WalletUtils}; -use namada::sdk::{error, masp, signing, tx}; +use namada::ledger::{Namada, NamadaImpl}; +use namada::proof_of_stake::parameters::PosParams; +use namada::proto::Tx; +use namada::sdk::{error, signing, tx}; use namada::tendermint_rpc::HttpClient; use namada::types::address::{Address, ImplicitAddress}; use namada::types::dec::Dec; @@ -36,8 +31,6 @@ use crate::node::ledger::tendermint_node; use crate::wallet::{ gen_validator_keys, read_and_confirm_encryption_password, CliWalletUtils, }; -use namada::ledger::NamadaImpl; -use namada::ledger::Namada; use namada::types::io::StdIo; /// Wrapper around `signing::aux_signing_data` that stores the optional @@ -49,8 +42,7 @@ pub async fn aux_signing_data<'a>( default_signer: Option
, ) -> Result { let signing_data = - signing::aux_signing_data(context, args, owner, default_signer) - .await?; + signing::aux_signing_data(context, args, owner, default_signer).await?; if args.disposable_signing_key { if !(args.dry_run || args.dry_run_wrapper) { @@ -91,14 +83,11 @@ pub async fn submit_reveal_aux<'a>( if tx::is_reveal_pk_needed(context.client, address, args.force).await? { println!( - "Submitting a tx to reveal the public key for address {address}..." + "Submitting a tx to reveal the public key for address \ + {address}..." ); - let (mut tx, signing_data, _epoch) = tx::build_reveal_pk( - context, - &args, - &public_key, - ) - .await?; + let (mut tx, signing_data, _epoch) = + tx::build_reveal_pk(context, &args, &public_key).await?; signing::generate_test_vector(context, &tx).await?; @@ -123,7 +112,7 @@ where let mut namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); submit_reveal_aux(&mut namada, args.tx.clone(), &args.owner).await?; - + let (mut tx, signing_data, _epoch) = args.build(&mut namada).await?; signing::generate_test_vector(&mut namada, &tx).await?; @@ -174,8 +163,8 @@ where { let mut namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - let (mut tx, signing_data, _epoch) = tx::build_init_account(&mut namada, &args) - .await?; + let (mut tx, signing_data, _epoch) = + tx::build_init_account(&mut namada, &args).await?; signing::generate_test_vector(&mut namada, &tx).await?; @@ -507,122 +496,7 @@ where Ok(()) } -/// Shielded context file name -const FILE_NAME: &str = "shielded.dat"; -const TMP_FILE_NAME: &str = "shielded.tmp"; - -#[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] -pub struct CLIShieldedUtils { - #[borsh_skip] - context_dir: PathBuf, -} - -impl CLIShieldedUtils { - /// Initialize a shielded transaction context that identifies notes - /// decryptable by any viewing key in the given set - pub fn new(context_dir: PathBuf) -> masp::ShieldedContext { - // Make sure that MASP parameters are downloaded to enable MASP - // transaction building and verification later on - let params_dir = masp::get_params_dir(); - let spend_path = params_dir.join(masp::SPEND_NAME); - let convert_path = params_dir.join(masp::CONVERT_NAME); - let output_path = params_dir.join(masp::OUTPUT_NAME); - if !(spend_path.exists() - && convert_path.exists() - && output_path.exists()) - { - display_line!(IO, "MASP parameters not present, downloading..."); - masp_proofs::download_masp_parameters(None) - .expect("MASP parameters not present or downloadable"); - display_line!( - IO, - "MASP parameter download complete, resuming execution..." - ); - } - // Finally initialize a shielded context with the supplied directory - let utils = Self { context_dir }; - masp::ShieldedContext { - utils, - ..Default::default() - } - } -} - -impl Default for CLIShieldedUtils { - fn default() -> Self { - Self { - context_dir: PathBuf::from(FILE_NAME), - } - } -} - -#[cfg_attr(feature = "async-send", async_trait::async_trait)] -#[cfg_attr(not(feature = "async-send"), async_trait::async_trait(?Send))] -impl masp::ShieldedUtils for CLIShieldedUtils { - fn local_tx_prover(&self) -> LocalTxProver { - if let Ok(params_dir) = env::var(masp::ENV_VAR_MASP_PARAMS_DIR) { - let params_dir = PathBuf::from(params_dir); - let spend_path = params_dir.join(masp::SPEND_NAME); - let convert_path = params_dir.join(masp::CONVERT_NAME); - let output_path = params_dir.join(masp::OUTPUT_NAME); - LocalTxProver::new(&spend_path, &output_path, &convert_path) - } else { - LocalTxProver::with_default_location() - .expect("unable to load MASP Parameters") - } - } - - /// Try to load the last saved shielded context from the given context - /// directory. If this fails, then leave the current context unchanged. - async fn load(self) -> std::io::Result> { - // Try to load shielded context from file - let mut ctx_file = File::open(self.context_dir.join(FILE_NAME))?; - let mut bytes = Vec::new(); - ctx_file.read_to_end(&mut bytes)?; - let mut new_ctx = masp::ShieldedContext::deserialize(&mut &bytes[..])?; - // Associate the originating context directory with the - // shielded context under construction - new_ctx.utils = self; - Ok(new_ctx) - } - - /// Save this shielded context into its associated context directory - async fn save( - &self, - ctx: &masp::ShieldedContext, - ) -> std::io::Result<()> { - // TODO: use mktemp crate? - let tmp_path = self.context_dir.join(TMP_FILE_NAME); - { - // First serialize the shielded context into a temporary file. - // Inability to create this file implies a simultaneuous write is in - // progress. In this case, immediately fail. This is unproblematic - // because the data intended to be stored can always be re-fetched - // from the blockchain. - let mut ctx_file = OpenOptions::new() - .write(true) - .create_new(true) - .open(tmp_path.clone())?; - let mut bytes = Vec::new(); - ctx.serialize(&mut bytes) - .expect("cannot serialize shielded context"); - ctx_file.write_all(&bytes[..])?; - } - // Atomically update the old shielded context file with new data. - // Atomicity is required to prevent other client instances from reading - // corrupt data. - std::fs::rename(tmp_path.clone(), self.context_dir.join(FILE_NAME))?; - // Finally, remove our temporary file to allow future saving of shielded - // contexts. - std::fs::remove_file(tmp_path)?; - Ok(()) - } -} - -pub async fn submit_transfer< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( +pub async fn submit_transfer( client: &C, mut ctx: Context, args: args::TxTransfer, @@ -630,7 +504,7 @@ pub async fn submit_transfer< for _ in 0..2 { let mut namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - + submit_reveal_aux( &mut namada, args.tx.clone(), @@ -638,7 +512,8 @@ pub async fn submit_transfer< ) .await?; - let (mut tx, signing_data, tx_epoch) = args.clone().build(&mut namada).await?; + let (mut tx, signing_data, tx_epoch) = + args.clone().build(&mut namada).await?; signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { @@ -715,8 +590,7 @@ where let governance_parameters = rpc::query_governance_parameters(client).await; let mut namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args - .is_offline + let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline { let proposal = OfflineProposal::try_from(args.proposal_data.as_ref()) .map_err(|e| { @@ -768,12 +642,7 @@ where ) .await?; - tx::build_pgf_funding_proposal( - &mut namada, - &args, - proposal, - ) - .await? + tx::build_pgf_funding_proposal(&mut namada, &args, proposal).await? } else if args.is_pgf_stewards { let proposal = PgfStewardProposal::try_from( args.proposal_data.as_ref(), @@ -803,12 +672,7 @@ where ) .await?; - tx::build_pgf_stewards_proposal( - &mut namada, - &args, - proposal, - ) - .await? + tx::build_pgf_stewards_proposal(&mut namada, &args, proposal).await? } else { let proposal = DefaultProposal::try_from(args.proposal_data.as_ref()) .map_err(|e| { @@ -836,12 +700,7 @@ where ) .await?; - tx::build_default_proposal( - &mut namada, - &args, - proposal, - ) - .await? + tx::build_default_proposal(&mut namada, &args, proposal).await? }; signing::generate_test_vector(&mut namada, &tx_builder).await?; @@ -866,7 +725,8 @@ where { let mut namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline { + let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline + { let default_signer = Some(args.voter.clone()); let signing_data = aux_signing_data( &mut namada, @@ -874,8 +734,8 @@ where Some(args.voter.clone()), default_signer.clone(), ) - .await?; - + .await?; + let proposal_vote = ProposalVote::try_from(args.vote) .map_err(|_| error::TxError::InvalidProposalVote)?; @@ -1049,14 +909,15 @@ where let default_address = args.source.clone().unwrap_or(args.validator.clone()); submit_reveal_aux(&mut namada, args.tx.clone(), &default_address).await?; - let (mut tx, signing_data, _fee_unshield_epoch) = args.build(&mut namada).await?; + let (mut tx, signing_data, _fee_unshield_epoch) = + args.build(&mut namada).await?; signing::generate_test_vector(&mut namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data)?; - + namada.submit(tx, &args.tx).await?; } diff --git a/benches/lib.rs b/benches/lib.rs index b420d24a43..a816451477 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -69,13 +69,14 @@ use namada::ledger::queries::{ Client, EncodedResponseQuery, RequestCtx, RequestQuery, Router, RPC, }; use namada::ledger::storage_api::StorageRead; +use namada::sdk::wallet::Wallet; +use namada::ledger::NamadaImpl; use namada::proof_of_stake; use namada::proto::{Code, Data, Section, Signature, Tx}; use namada::sdk::args::InputAmount; use namada::sdk::masp::{ self, ShieldedContext, ShieldedTransfer, ShieldedUtils, }; -use namada::sdk::wallet::Wallet; use namada::tendermint::Hash; use namada::tendermint_rpc::{self}; use namada::types::address::InternalAddress; @@ -104,7 +105,6 @@ use namada_test_utils::tx_data::TxWriteData; use rand_core::OsRng; use sha2::{Digest, Sha256}; use tempfile::TempDir; -use namada::ledger::NamadaImpl; pub const WASM_DIR: &str = "../wasm"; pub const TX_BOND_WASM: &str = "tx_bond.wasm"; @@ -804,13 +804,15 @@ impl BenchShieldedCtx { &[], )) .unwrap(); - let mut namada = NamadaImpl::new( - &self.shell, - &mut self.wallet, - &mut self.shielded, - ); + let mut namada = + NamadaImpl::new(&self.shell, &mut self.wallet, &mut self.shielded); let shielded = async_runtime - .block_on(ShieldedContext::::gen_shielded_transfer(&mut namada, &args)) + .block_on( + ShieldedContext::::gen_shielded_transfer( + &mut namada, + &args, + ), + ) .unwrap() .map( |ShieldedTransfer { diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 38cfcd51cb..430537fdc2 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -19,11 +19,12 @@ use crate::ledger::queries::{ Client, GenBridgePoolProofReq, GenBridgePoolProofRsp, TransferToErcArgs, RPC, }; +use crate::sdk::rpc::{query_wasm_code_hash, validate_amount}; +use crate::ledger::signing::aux_signing_data; +use crate::ledger::tx::prepare_tx; +use crate::ledger::{args, Namada, SigningTxData}; use crate::proto::Tx; -use crate::sdk::args; use crate::sdk::error::Error; -use crate::sdk::rpc::{query_wasm_code_hash, validate_amount}; -use crate::sdk::tx::prepare_tx; use crate::types::address::Address; use crate::types::control_flow::time::{Duration, Instant}; use crate::types::control_flow::{ @@ -38,9 +39,6 @@ use crate::types::keccak::KeccakHash; use crate::types::token::{Amount, DenominatedAmount}; use crate::types::voting_power::FractionalVotingPower; use crate::{display, display_line}; -use crate::ledger::Namada; -use crate::sdk::signing::aux_signing_data; -use crate::sdk::signing::SigningTxData; /// Craft a transaction that adds a transfer to the Ethereum bridge pool. @@ -66,7 +64,7 @@ pub async fn build_bridge_pool_tx<'a>( Some(sender.clone()), default_signer, ) - .await?; + .await?; let fee_payer = fee_payer.unwrap_or_else(|| sender.clone()); let DenominatedAmount { amount, .. } = validate_amount( context.client, diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index d82e09bd8a..7686635f98 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -14,6 +14,10 @@ pub mod queries; pub mod storage; pub mod vp_host_fns; +use std::ops::{Deref, DerefMut}; +use std::path::PathBuf; +use std::str::FromStr; + pub use namada_core::ledger::{ gas, parameters, replay_protection, storage_api, tx_env, vp_env, }; @@ -31,7 +35,6 @@ use crate::sdk::tx::{ TX_WITHDRAW_WASM, TX_BRIDGE_POOL_WASM, TX_RESIGN_STEWARD, TX_UPDATE_STEWARD_COMMISSION, self, }; -use std::path::PathBuf; use crate::types::transaction::GasLimit; use crate::sdk::signing::{SigningTxData, self}; use crate::proto::Tx; @@ -40,13 +43,12 @@ use crate::types::token; use crate::sdk::tx::ProcessTxResponse; use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use crate::types::token::NATIVE_MAX_DECIMAL_PLACES; -use std::str::FromStr; -use std::ops::{Deref, DerefMut}; use namada_core::types::dec::Dec; use namada_core::types::ethereum_events::EthAddress; /// Encapsulates a Namada session to enable splitting borrows of its parts -pub struct NamadaStruct<'a, C, U, V> where +pub struct NamadaStruct<'a, C, U, V> +where C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, @@ -61,7 +63,16 @@ pub struct NamadaStruct<'a, C, U, V> where #[async_trait::async_trait(?Send)] /// An interface for high-level interaction with the Namada SDK -pub trait Namada<'a> : DerefMut> { +pub trait Namada<'a>: + DerefMut< + Target = NamadaStruct< + 'a, + Self::Client, + Self::WalletUtils, + Self::ShieldedUtils, + >, +> +{ /// A client with async request dispatcher method type Client: 'a + crate::ledger::queries::Client + Sync; /// Captures the interactive parts of the wallet's functioning @@ -72,9 +83,12 @@ pub trait Namada<'a> : DerefMut Address { - self.wallet.find_address(args::NAM).expect("NAM not in wallet").clone() + self.wallet + .find_address(args::NAM) + .expect("NAM not in wallet") + .clone() } - + /// Make a tx builder using no arguments fn tx_builder(&mut self) -> args::Tx { args::Tx { @@ -205,10 +219,7 @@ pub trait Namada<'a> : DerefMut args::TxUpdateAccount { + fn new_update_account(&mut self, addr: Address) -> args::TxUpdateAccount { args::TxUpdateAccount { addr, vp_code_path: None, @@ -236,7 +247,8 @@ pub trait Namada<'a> : DerefMut : DerefMut args::Withdraw { + fn new_withdraw(&mut self, validator: Address) -> args::Withdraw { args::Withdraw { validator, source: None, @@ -348,10 +357,7 @@ pub trait Namada<'a> : DerefMut args::TxCustom { + fn new_custom(&mut self, owner: Address) -> args::TxCustom { args::TxCustom { owner, tx: self.tx_builder(), @@ -382,7 +388,8 @@ pub trait Namada<'a> : DerefMut where +pub struct NamadaImpl<'a, C, U, V> +where C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, @@ -391,7 +398,8 @@ pub struct NamadaImpl<'a, C, U, V> where prototype: args::Tx, } -impl<'a, C, U, V> NamadaImpl<'a, C, U, V> where +impl<'a, C, U, V> NamadaImpl<'a, C, U, V> +where C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, @@ -407,7 +415,11 @@ impl<'a, C, U, V> NamadaImpl<'a, C, U, V> where .expect("NAM not in wallet") .clone(); Self { - namada: NamadaStruct { client, wallet, shielded }, + namada: NamadaStruct { + client, + wallet, + shielded, + }, prototype: args::Tx { dry_run: false, dry_run_wrapper: false, @@ -436,7 +448,8 @@ impl<'a, C, U, V> NamadaImpl<'a, C, U, V> where } } -impl<'a, C, U, V> Deref for NamadaImpl<'a, C, U, V> where +impl<'a, C, U, V> Deref for NamadaImpl<'a, C, U, V> +where C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, @@ -448,7 +461,8 @@ impl<'a, C, U, V> Deref for NamadaImpl<'a, C, U, V> where } } -impl<'a, C, U, V> DerefMut for NamadaImpl<'a, C, U, V> where +impl<'a, C, U, V> DerefMut for NamadaImpl<'a, C, U, V> +where C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, @@ -458,14 +472,15 @@ impl<'a, C, U, V> DerefMut for NamadaImpl<'a, C, U, V> where } } -impl<'a, C, U, V> Namada<'a> for NamadaImpl<'a, C, U, V> where +impl<'a, C, U, V> Namada<'a> for NamadaImpl<'a, C, U, V> +where C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, { type Client = C; - type WalletUtils = U; type ShieldedUtils = V; + type WalletUtils = U; /// Obtain the prototypical Tx builder fn tx_builder(&mut self) -> args::Tx { @@ -474,14 +489,19 @@ impl<'a, C, U, V> Namada<'a> for NamadaImpl<'a, C, U, V> where } /// Allow the prototypical Tx builder to be modified -impl<'a, C, U, V> args::TxBuilder for NamadaImpl<'a, C, U, V> where +impl<'a, C, U, V> args::TxBuilder for NamadaImpl<'a, C, U, V> +where C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, { - fn tx(self, func: F) -> Self where + fn tx(self, func: F) -> Self + where F: FnOnce(args::Tx) -> args::Tx, { - Self { prototype: func(self.prototype), ..self } + Self { + prototype: func(self.prototype), + ..self + } } } diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs index 3c6dca8cfa..8a1d0d81b0 100644 --- a/shared/src/sdk/args.rs +++ b/shared/src/sdk/args.rs @@ -4,6 +4,9 @@ use std::collections::HashMap; use std::path::PathBuf; use std::time::Duration as StdDuration; +use namada_core::ledger::governance::cli::onchain::{ + DefaultProposal, PgfFundingProposal, PgfStewardProposal, +}; use namada_core::types::chain::ChainId; use namada_core::types::dec::Dec; use namada_core::types::ethereum_events::EthAddress; @@ -12,6 +15,8 @@ use serde::{Deserialize, Serialize}; use zeroize::Zeroizing; use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; +use crate::ledger::eth_bridge::bridge_pool; +use crate::ledger::Namada; use crate::types::address::Address; use crate::types::keccak::KeccakHash; use crate::types::key::{common, SchemeType}; @@ -19,13 +24,8 @@ use crate::types::masp::MaspValue; use crate::types::storage::Epoch; use crate::types::transaction::GasLimit; use crate::types::{storage, token}; -use crate::ledger::Namada; use crate::sdk::signing::SigningTxData; use crate::sdk::{tx, rpc}; -use crate::ledger::eth_bridge::bridge_pool; -use namada_core::ledger::governance::cli::onchain::{ - DefaultProposal, PgfFundingProposal, PgfStewardProposal, -}; /// The Namada token pub const NAM: &str = "NAM"; @@ -135,24 +135,42 @@ pub struct TxCustom { } impl TxBuilder for TxCustom { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - TxCustom { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + TxCustom { + tx: func(self.tx), + ..self + } } } impl TxCustom { /// Path to the tx WASM code file pub fn code_path(self, code_path: PathBuf) -> Self { - Self { code_path: Some(code_path), ..self } + Self { + code_path: Some(code_path), + ..self + } } + /// Path to the data file pub fn data_path(self, data_path: C::Data) -> Self { - Self { data_path: Some(data_path), ..self } + Self { + data_path: Some(data_path), + ..self + } } + /// Path to the serialized transaction pub fn serialized_tx(self, serialized_tx: C::Data) -> Self { - Self { serialized_tx: Some(serialized_tx), ..self } + Self { + serialized_tx: Some(serialized_tx), + ..self + } } + /// The address that correspond to the signatures/signing-keys pub fn owner(self, owner: C::Address) -> Self { Self { owner, ..self } @@ -161,9 +179,14 @@ impl TxCustom { impl TxCustom { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { tx::build_custom(context, self).await } } @@ -182,7 +205,7 @@ pub enum InputAmount { impl std::str::FromStr for InputAmount { type Err = ::Err; - + fn from_str(s: &str) -> Result { token::DenominatedAmount::from_str(s).map(InputAmount::Unvalidated) } @@ -208,8 +231,14 @@ pub struct TxTransfer { } impl TxBuilder for TxTransfer { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - TxTransfer { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + TxTransfer { + tx: func(self.tx), + ..self + } } } @@ -218,33 +247,49 @@ impl TxTransfer { pub fn source(self, source: C::TransferSource) -> Self { Self { source, ..self } } + /// Transfer target address pub fn receiver(self, target: C::TransferTarget) -> Self { Self { target, ..self } } + /// Transferred token address pub fn token(self, token: C::Address) -> Self { Self { token, ..self } } + /// Transferred token amount pub fn amount(self, amount: InputAmount) -> Self { Self { amount, ..self } } + /// Native token address pub fn native_token(self, native_token: C::NativeAddress) -> Self { - Self { native_token, ..self } + Self { + native_token, + ..self + } } + /// Path to the TX WASM code file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } } impl TxTransfer { /// Build a transaction from this builder - pub async fn build<'a>(&mut self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &mut self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { tx::build_transfer(context, self).await } } @@ -277,8 +322,14 @@ pub struct TxIbcTransfer { } impl TxBuilder for TxIbcTransfer { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - TxIbcTransfer { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + TxIbcTransfer { + tx: func(self.tx), + ..self + } } } @@ -287,54 +338,79 @@ impl TxIbcTransfer { pub fn source(self, source: C::Address) -> Self { Self { source, ..self } } + /// Transfer target address pub fn receiver(self, receiver: String) -> Self { Self { receiver, ..self } } + /// Transferred token address pub fn token(self, token: C::Address) -> Self { Self { token, ..self } } + /// Transferred token amount pub fn amount(self, amount: InputAmount) -> Self { Self { amount, ..self } } + /// Port ID pub fn port_id(self, port_id: PortId) -> Self { Self { port_id, ..self } } + /// Channel ID pub fn channel_id(self, channel_id: ChannelId) -> Self { Self { channel_id, ..self } } + /// Timeout height of the destination chain pub fn timeout_height(self, timeout_height: u64) -> Self { - Self { timeout_height: Some(timeout_height), ..self } + Self { + timeout_height: Some(timeout_height), + ..self + } } + /// Timeout timestamp offset pub fn timeout_sec_offset(self, timeout_sec_offset: u64) -> Self { - Self { timeout_sec_offset: Some(timeout_sec_offset), ..self } + Self { + timeout_sec_offset: Some(timeout_sec_offset), + ..self + } } + /// Memo pub fn memo(self, memo: String) -> Self { - Self { memo: Some(memo), ..self } + Self { + memo: Some(memo), + ..self + } } + /// Path to the TX WASM code file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } } impl TxIbcTransfer { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { tx::build_ibc_transfer(context, self).await } } - /// Transaction to initialize create a new proposal #[derive(Clone, Debug)] pub struct InitProposal { @@ -355,46 +431,78 @@ pub struct InitProposal { } impl TxBuilder for InitProposal { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - InitProposal { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + InitProposal { + tx: func(self.tx), + ..self + } } } impl InitProposal { /// The proposal data pub fn proposal_data(self, proposal_data: C::Data) -> Self { - Self { proposal_data, ..self } + Self { + proposal_data, + ..self + } } + /// Native token address pub fn native_token(self, native_token: C::NativeAddress) -> Self { - Self { native_token, ..self } + Self { + native_token, + ..self + } } + /// Flag if proposal should be run offline pub fn is_offline(self, is_offline: bool) -> Self { Self { is_offline, ..self } } + /// Flag if proposal is of type Pgf stewards pub fn is_pgf_stewards(self, is_pgf_stewards: bool) -> Self { - Self { is_pgf_stewards, ..self } + Self { + is_pgf_stewards, + ..self + } } + /// Flag if proposal is of type Pgf funding pub fn is_pgf_funding(self, is_pgf_funding: bool) -> Self { - Self { is_pgf_funding, ..self } + Self { + is_pgf_funding, + ..self + } } + /// Path to the tx WASM file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } } impl InitProposal { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { let current_epoch = rpc::query_epoch(context.client).await?; - let governance_parameters = rpc::query_governance_parameters(context.client).await; - + let governance_parameters = + rpc::query_governance_parameters(context.client).await; + if self.is_pgf_funding { let proposal = PgfFundingProposal::try_from(self.proposal_data.as_ref()) @@ -405,13 +513,8 @@ impl InitProposal { })? .validate(&governance_parameters, current_epoch, self.tx.force) .map_err(|e| crate::sdk::error::TxError::InvalidProposal(e.to_string()))?; - - tx::build_pgf_funding_proposal( - context, - self, - proposal, - ) - .await + + tx::build_pgf_funding_proposal(context, self, proposal).await } else if self.is_pgf_stewards { let proposal = PgfStewardProposal::try_from( self.proposal_data.as_ref(), @@ -424,7 +527,7 @@ impl InitProposal { context.wallet.find_address(NAM).expect("NAM not in wallet"), &proposal.proposal.author, ) - .await?; + .await?; let proposal = proposal .validate( &governance_parameters, @@ -432,14 +535,11 @@ impl InitProposal { author_balance, self.tx.force, ) - .map_err(|e| crate::sdk::error::TxError::InvalidProposal(e.to_string()))?; - - tx::build_pgf_stewards_proposal( - context, - self, - proposal, - ) - .await + .map_err(|e| { + crate::sdk::error::TxError::InvalidProposal(e.to_string()) + })?; + + tx::build_pgf_stewards_proposal(context, self, proposal).await } else { let proposal = DefaultProposal::try_from(self.proposal_data.as_ref()) .map_err(|e| { @@ -450,7 +550,7 @@ impl InitProposal { context.wallet.find_address(NAM).expect("NAM not in wallet"), &proposal.proposal.author, ) - .await?; + .await?; let proposal = proposal .validate( &governance_parameters, @@ -458,13 +558,10 @@ impl InitProposal { author_balance, self.tx.force, ) - .map_err(|e| crate::sdk::error::TxError::InvalidProposal(e.to_string()))?; - tx::build_default_proposal( - context, - self, - proposal, - ) - .await + .map_err(|e| { + crate::sdk::error::TxError::InvalidProposal(e.to_string()) + })?; + tx::build_default_proposal(context, self, proposal).await } } } @@ -489,43 +586,68 @@ pub struct VoteProposal { } impl TxBuilder for VoteProposal { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - VoteProposal { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + VoteProposal { + tx: func(self.tx), + ..self + } } } impl VoteProposal { /// Proposal id pub fn proposal_id(self, proposal_id: u64) -> Self { - Self { proposal_id: Some(proposal_id), ..self } + Self { + proposal_id: Some(proposal_id), + ..self + } } + /// The vote pub fn vote(self, vote: String) -> Self { Self { vote, ..self } } + /// The address of the voter pub fn voter(self, voter: C::Address) -> Self { Self { voter, ..self } } + /// Flag if proposal vote should be run offline pub fn is_offline(self, is_offline: bool) -> Self { Self { is_offline, ..self } } + /// The proposal file path pub fn proposal_data(self, proposal_data: C::Data) -> Self { - Self { proposal_data: Some(proposal_data), ..self } + Self { + proposal_data: Some(proposal_data), + ..self + } } + /// Path to the TX WASM code file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } } impl VoteProposal { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { let current_epoch = rpc::query_epoch(context.client).await?; tx::build_vote_proposal(context, self, current_epoch).await } @@ -595,39 +717,66 @@ pub struct TxUpdateAccount { } impl TxBuilder for TxUpdateAccount { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - TxUpdateAccount { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + TxUpdateAccount { + tx: func(self.tx), + ..self + } } } impl TxUpdateAccount { /// Path to the VP WASM code file pub fn vp_code_path(self, vp_code_path: PathBuf) -> Self { - Self { vp_code_path: Some(vp_code_path), ..self } + Self { + vp_code_path: Some(vp_code_path), + ..self + } } + /// Path to the TX WASM code file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } + /// Address of the account whose VP is to be updated pub fn addr(self, addr: C::Address) -> Self { Self { addr, ..self } } + /// Public keys pub fn public_keys(self, public_keys: Vec) -> Self { - Self { public_keys, ..self } + Self { + public_keys, + ..self + } } + /// The account threshold pub fn threshold(self, threshold: u8) -> Self { - Self { threshold: Some(threshold), ..self } + Self { + threshold: Some(threshold), + ..self + } } } impl TxUpdateAccount { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { tx::build_update_account(context, self).await } } @@ -651,8 +800,14 @@ pub struct Bond { } impl TxBuilder for Bond { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - Bond { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + Bond { + tx: func(self.tx), + ..self + } } } @@ -661,30 +816,48 @@ impl Bond { pub fn validator(self, validator: C::Address) -> Self { Self { validator, ..self } } + /// Amount of tokens to stake in a bond pub fn amount(self, amount: token::Amount) -> Self { Self { amount, ..self } } + /// Source address for delegations. For self-bonds, the validator is /// also the source. pub fn source(self, source: C::Address) -> Self { - Self { source: Some(source), ..self } + Self { + source: Some(source), + ..self + } } + /// Native token address pub fn native_token(self, native_token: C::NativeAddress) -> Self { - Self { native_token, ..self } + Self { + native_token, + ..self + } } + /// Path to the TX WASM code file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } } impl Bond { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { tx::build_bond(context, self).await } } @@ -707,16 +880,28 @@ pub struct Unbond { impl Unbond { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option, Option<(Epoch, token::Amount)>)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + Option<(Epoch, token::Amount)>, + )> { tx::build_unbond(context, self).await } } impl TxBuilder for Unbond { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - Unbond { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + Unbond { + tx: func(self.tx), + ..self + } } } @@ -725,18 +910,27 @@ impl Unbond { pub fn validator(self, validator: C::Address) -> Self { Self { validator, ..self } } + /// Amount of tokens to unbond from a bond pub fn amount(self, amount: token::Amount) -> Self { Self { amount, ..self } } + /// Source address for unbonding from delegations. For unbonding from /// self-bonds, the validator is also the source pub fn source(self, source: C::Address) -> Self { - Self { source: Some(source), ..self } + Self { + source: Some(source), + ..self + } } + /// Path to the TX WASM code file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } } @@ -750,8 +944,14 @@ pub struct RevealPk { } impl TxBuilder for RevealPk { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - RevealPk { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + RevealPk { + tx: func(self.tx), + ..self + } } } @@ -764,14 +964,15 @@ impl RevealPk { impl RevealPk { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { - tx::build_reveal_pk( - context, - &self.tx, - &self.public_key, - ).await + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { + tx::build_reveal_pk(context, &self.tx, &self.public_key).await } } @@ -813,8 +1014,14 @@ pub struct Withdraw { } impl TxBuilder for Withdraw { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - Withdraw { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + Withdraw { + tx: func(self.tx), + ..self + } } } @@ -823,22 +1030,35 @@ impl Withdraw { pub fn validator(self, validator: C::Address) -> Self { Self { validator, ..self } } + /// Source address for withdrawing from delegations. For withdrawing /// from self-bonds, the validator is also the source pub fn source(self, source: C::Address) -> Self { - Self { source: Some(source), ..self } + Self { + source: Some(source), + ..self + } } + /// Path to the TX WASM code file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } } impl Withdraw { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { tx::build_withdraw(context, self).await } } @@ -934,32 +1154,47 @@ pub struct CommissionRateChange { } impl TxBuilder for CommissionRateChange { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - CommissionRateChange { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + CommissionRateChange { + tx: func(self.tx), + ..self + } } } - impl CommissionRateChange { /// Validator address (should be self) pub fn validator(self, validator: C::Address) -> Self { Self { validator, ..self } } + /// Value to which the tx changes the commission rate pub fn rate(self, rate: Dec) -> Self { Self { rate, ..self } } + /// Path to the TX WASM code file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } } impl CommissionRateChange { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { tx::build_validator_commission_change(context, self).await } } @@ -978,8 +1213,14 @@ pub struct UpdateStewardCommission { } impl TxBuilder for UpdateStewardCommission { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - UpdateStewardCommission { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + UpdateStewardCommission { + tx: func(self.tx), + ..self + } } } @@ -988,21 +1229,31 @@ impl UpdateStewardCommission { pub fn steward(self, steward: C::Address) -> Self { Self { steward, ..self } } + /// Value to which the tx changes the commission rate pub fn commission(self, commission: C::Data) -> Self { Self { commission, ..self } } + /// Path to the TX WASM code file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } } impl UpdateStewardCommission { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { tx::build_update_steward_commission(context, self).await } } @@ -1019,8 +1270,14 @@ pub struct ResignSteward { } impl TxBuilder for ResignSteward { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - ResignSteward { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + ResignSteward { + tx: func(self.tx), + ..self + } } } @@ -1029,17 +1286,26 @@ impl ResignSteward { pub fn steward(self, steward: C::Address) -> Self { Self { steward, ..self } } + /// Path to the TX WASM code file pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } } impl ResignSteward { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { tx::build_resign_steward(context, self).await } } @@ -1056,8 +1322,14 @@ pub struct TxUnjailValidator { } impl TxBuilder for TxUnjailValidator { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - TxUnjailValidator { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + TxUnjailValidator { + tx: func(self.tx), + ..self + } } } @@ -1066,17 +1338,26 @@ impl TxUnjailValidator { pub fn validator(self, validator: C::Address) -> Self { Self { validator, ..self } } + /// Path to the TX WASM code file pub fn tc_code_path(self, tx_code_path: PathBuf) -> Self { - Self { tx_code_path, ..self } + Self { + tx_code_path, + ..self + } } } impl TxUnjailValidator { /// Build a transaction from this builder - pub async fn build<'a>(&self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + &self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { tx::build_unjail_validator(context, self).await } } @@ -1192,16 +1473,21 @@ pub struct Tx { } /// Builder functions for Tx -pub trait TxBuilder : Sized { +pub trait TxBuilder: Sized { /// Apply the given function to the Tx inside self - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx; + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx; /// Simulate applying the transaction fn dry_run(self, dry_run: bool) -> Self { self.tx(|x| Tx { dry_run, ..x }) } /// Simulate applying both the wrapper and inner transactions fn dry_run_wrapper(self, dry_run_wrapper: bool) -> Self { - self.tx(|x| Tx { dry_run_wrapper, ..x }) + self.tx(|x| Tx { + dry_run_wrapper, + ..x + }) } /// Dump the transaction bytes to file fn dump_tx(self, dump_tx: bool) -> Self { @@ -1209,7 +1495,10 @@ pub trait TxBuilder : Sized { } /// The output directory path to where serialize the data fn output_folder(self, output_folder: PathBuf) -> Self { - self.tx(|x| Tx { output_folder: Some(output_folder), ..x }) + self.tx(|x| Tx { + output_folder: Some(output_folder), + ..x + }) } /// Submit the transaction even if it doesn't pass client checks fn force(self, force: bool) -> Self { @@ -1217,29 +1506,50 @@ pub trait TxBuilder : Sized { } /// Do not wait for the transaction to be added to the blockchain fn broadcast_only(self, broadcast_only: bool) -> Self { - self.tx(|x| Tx { broadcast_only, ..x }) + self.tx(|x| Tx { + broadcast_only, + ..x + }) } /// The address of the ledger node as host:port fn ledger_address(self, ledger_address: C::TendermintAddress) -> Self { - self.tx(|x| Tx { ledger_address, ..x }) + self.tx(|x| Tx { + ledger_address, + ..x + }) } /// If any new account is initialized by the tx, use the given alias to /// save it in the wallet. - fn initialized_account_alias(self, initialized_account_alias: String) -> Self { - self.tx(|x| Tx { initialized_account_alias: Some(initialized_account_alias), ..x }) + fn initialized_account_alias( + self, + initialized_account_alias: String, + ) -> Self { + self.tx(|x| Tx { + initialized_account_alias: Some(initialized_account_alias), + ..x + }) } /// Whether to force overwrite the above alias, if it is provided, in the /// wallet. fn wallet_alias_force(self, wallet_alias_force: bool) -> Self { - self.tx(|x| Tx { wallet_alias_force, ..x }) + self.tx(|x| Tx { + wallet_alias_force, + ..x + }) } /// The amount being payed (for gas unit) to include the transaction fn fee_amount(self, fee_amount: InputAmount) -> Self { - self.tx(|x| Tx { fee_amount: Some(fee_amount), ..x }) + self.tx(|x| Tx { + fee_amount: Some(fee_amount), + ..x + }) } /// The fee payer signing key fn wrapper_fee_payer(self, wrapper_fee_payer: C::Keypair) -> Self { - self.tx(|x| Tx { wrapper_fee_payer: Some(wrapper_fee_payer), ..x }) + self.tx(|x| Tx { + wrapper_fee_payer: Some(wrapper_fee_payer), + ..x + }) } /// The token in which the fee is being paid fn fee_token(self, fee_token: C::Address) -> Self { @@ -1247,7 +1557,10 @@ pub trait TxBuilder : Sized { } /// The optional spending key for fee unshielding fn fee_unshield(self, fee_unshield: C::TransferSource) -> Self { - self.tx(|x| Tx { fee_unshield: Some(fee_unshield), ..x }) + self.tx(|x| Tx { + fee_unshield: Some(fee_unshield), + ..x + }) } /// The max amount of gas used to process tx fn gas_limit(self, gas_limit: GasLimit) -> Self { @@ -1255,16 +1568,25 @@ pub trait TxBuilder : Sized { } /// The optional expiration of the transaction fn expiration(self, expiration: DateTimeUtc) -> Self { - self.tx(|x| Tx { expiration: Some(expiration), ..x }) + self.tx(|x| Tx { + expiration: Some(expiration), + ..x + }) } /// Generate an ephimeral signing key to be used only once to sign a /// wrapper tx fn disposable_signing_key(self, disposable_signing_key: bool) -> Self { - self.tx(|x| Tx { disposable_signing_key, ..x }) + self.tx(|x| Tx { + disposable_signing_key, + ..x + }) } /// The chain id for which the transaction is intended fn chain_id(self, chain_id: ChainId) -> Self { - self.tx(|x| Tx { chain_id: Some(chain_id), ..x }) + self.tx(|x| Tx { + chain_id: Some(chain_id), + ..x + }) } /// Sign the tx with the key for the given alias from your wallet fn signing_keys(self, signing_keys: Vec) -> Self { @@ -1276,20 +1598,32 @@ pub trait TxBuilder : Sized { } /// Path to the TX WASM code file to reveal PK fn tx_reveal_code_path(self, tx_reveal_code_path: PathBuf) -> Self { - self.tx(|x| Tx { tx_reveal_code_path, ..x }) + self.tx(|x| Tx { + tx_reveal_code_path, + ..x + }) } /// Sign the tx with the public key for the given alias from your wallet fn verification_key(self, verification_key: C::PublicKey) -> Self { - self.tx(|x| Tx { verification_key: Some(verification_key), ..x }) + self.tx(|x| Tx { + verification_key: Some(verification_key), + ..x + }) } /// Password to decrypt key fn password(self, password: Zeroizing) -> Self { - self.tx(|x| Tx { password: Some(password), ..x }) + self.tx(|x| Tx { + password: Some(password), + ..x + }) } } impl TxBuilder for Tx { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { func(self) } } @@ -1473,8 +1807,14 @@ pub struct EthereumBridgePool { } impl TxBuilder for EthereumBridgePool { - fn tx(self, func: F) -> Self where F: FnOnce(Tx) -> Tx { - EthereumBridgePool { tx: func(self.tx), ..self } + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + EthereumBridgePool { + tx: func(self.tx), + ..self + } } } @@ -1486,36 +1826,47 @@ impl EthereumBridgePool { pub fn nut(self, nut: bool) -> Self { Self { nut, ..self } } + /// The type of token pub fn asset(self, asset: EthAddress) -> Self { Self { asset, ..self } } + /// The recipient address pub fn recipient(self, recipient: EthAddress) -> Self { Self { recipient, ..self } } + /// The sender of the transfer pub fn sender(self, sender: C::Address) -> Self { Self { sender, ..self } } + /// The amount to be transferred pub fn amount(self, amount: InputAmount) -> Self { Self { amount, ..self } } + /// The amount of gas fees pub fn fee_amount(self, fee_amount: InputAmount) -> Self { Self { fee_amount, ..self } } + /// The account of fee payer. /// /// If unset, it is the same as the sender. pub fn fee_payer(self, fee_payer: C::Address) -> Self { - Self { fee_payer: Some(fee_payer), ..self } + Self { + fee_payer: Some(fee_payer), + ..self + } } + /// The token in which the gas is being paid pub fn fee_token(self, fee_token: C::Address) -> Self { Self { fee_token, ..self } } + /// Path to the tx WASM code file pub fn code_path(self, code_path: PathBuf) -> Self { Self { code_path, ..self } @@ -1524,9 +1875,14 @@ impl EthereumBridgePool { impl EthereumBridgePool { /// Build a transaction from this builder - pub async fn build<'a>(self, context: &mut impl Namada<'a>) -> - crate::sdk::error::Result<(crate::proto::Tx, SigningTxData, Option)> - { + pub async fn build<'a>( + self, + context: &mut impl Namada<'a>, + ) -> crate::sdk::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + )> { bridge_pool::build_bridge_pool_tx(context, self).await } } diff --git a/shared/src/sdk/masp.rs b/shared/src/sdk/masp.rs index 6b44dd018d..1cd9ae7e5b 100644 --- a/shared/src/sdk/masp.rs +++ b/shared/src/sdk/masp.rs @@ -58,12 +58,13 @@ use ripemd::Digest as RipemdDigest; use sha2::Digest; use thiserror::Error; -use crate::proto::Tx; use crate::sdk::args::InputAmount; -use crate::sdk::error::{EncodingError, Error, PinnedBalanceError, QueryError}; -use crate::sdk::queries::Client; +use crate::ledger::queries::Client; use crate::sdk::rpc::{query_conversion, query_storage_value}; use crate::sdk::tx::decode_component; +use crate::ledger::Namada; +use crate::proto::Tx; +use crate::sdk::error::{EncodingError, Error, PinnedBalanceError, QueryError}; use crate::sdk::{args, rpc}; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; @@ -77,7 +78,6 @@ use crate::types::token::{ }; use crate::types::transaction::{EllipticCurve, PairingEngine, WrapperTx}; use crate::{display_line, edisplay_line}; -use crate::ledger::Namada; /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. @@ -1509,7 +1509,10 @@ impl ShieldedContext { // Load the current shielded context given the spending key we possess let _ = context.shielded.load().await; let context = &mut **context; - context.shielded.fetch(context.client, &spending_keys, &[]).await?; + context + .shielded + .fetch(context.client, &spending_keys, &[]) + .await?; // Save the update state so that future fetches can be short-circuited let _ = context.shielded.save().await; // Determine epoch in which to submit potential shielded transaction @@ -2110,3 +2113,128 @@ mod tests { super::load_pvks(); } } + +#[cfg(feature = "std")] +/// Implementation of MASP functionality depending on a standard filesystem +pub mod fs { + use std::fs::{File, OpenOptions}; + use std::io::{Read, Write}; + use async_trait::async_trait; + + use super::*; + + /// Shielded context file name + const FILE_NAME: &str = "shielded.dat"; + const TMP_FILE_NAME: &str = "shielded.tmp"; + + #[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] + /// An implementation of ShieldedUtils for standard filesystems + pub struct FsShieldedUtils { + #[borsh_skip] + context_dir: PathBuf, + } + + impl FsShieldedUtils { + /// Initialize a shielded transaction context that identifies notes + /// decryptable by any viewing key in the given set + pub fn new(context_dir: PathBuf) -> ShieldedContext { + // Make sure that MASP parameters are downloaded to enable MASP + // transaction building and verification later on + let params_dir = get_params_dir(); + let spend_path = params_dir.join(SPEND_NAME); + let convert_path = params_dir.join(CONVERT_NAME); + let output_path = params_dir.join(OUTPUT_NAME); + if !(spend_path.exists() + && convert_path.exists() + && output_path.exists()) + { + println!("MASP parameters not present, downloading..."); + masp_proofs::download_masp_parameters(None) + .expect("MASP parameters not present or downloadable"); + println!( + "MASP parameter download complete, resuming execution..." + ); + } + // Finally initialize a shielded context with the supplied directory + let utils = Self { context_dir }; + ShieldedContext { + utils, + ..Default::default() + } + } + } + + impl Default for FsShieldedUtils { + fn default() -> Self { + Self { + context_dir: PathBuf::from(FILE_NAME), + } + } + } + + #[async_trait(?Send)] + impl ShieldedUtils for FsShieldedUtils { + fn local_tx_prover(&self) -> LocalTxProver { + if let Ok(params_dir) = env::var(ENV_VAR_MASP_PARAMS_DIR) { + let params_dir = PathBuf::from(params_dir); + let spend_path = params_dir.join(SPEND_NAME); + let convert_path = params_dir.join(CONVERT_NAME); + let output_path = params_dir.join(OUTPUT_NAME); + LocalTxProver::new(&spend_path, &output_path, &convert_path) + } else { + LocalTxProver::with_default_location() + .expect("unable to load MASP Parameters") + } + } + + /// Try to load the last saved shielded context from the given context + /// directory. If this fails, then leave the current context unchanged. + async fn load(self) -> std::io::Result> { + // Try to load shielded context from file + let mut ctx_file = File::open(self.context_dir.join(FILE_NAME))?; + let mut bytes = Vec::new(); + ctx_file.read_to_end(&mut bytes)?; + let mut new_ctx = ShieldedContext::deserialize(&mut &bytes[..])?; + // Associate the originating context directory with the + // shielded context under construction + new_ctx.utils = self; + Ok(new_ctx) + } + + /// Save this shielded context into its associated context directory + async fn save( + &self, + ctx: &ShieldedContext, + ) -> std::io::Result<()> { + // TODO: use mktemp crate? + let tmp_path = self.context_dir.join(TMP_FILE_NAME); + { + // First serialize the shielded context into a temporary file. + // Inability to create this file implies a simultaneuous write + // is in progress. In this case, immediately + // fail. This is unproblematic because the data + // intended to be stored can always be re-fetched + // from the blockchain. + let mut ctx_file = OpenOptions::new() + .write(true) + .create_new(true) + .open(tmp_path.clone())?; + let mut bytes = Vec::new(); + ctx.serialize(&mut bytes) + .expect("cannot serialize shielded context"); + ctx_file.write_all(&bytes[..])?; + } + // Atomically update the old shielded context file with new data. + // Atomicity is required to prevent other client instances from + // reading corrupt data. + std::fs::rename( + tmp_path.clone(), + self.context_dir.join(FILE_NAME), + )?; + // Finally, remove our temporary file to allow future saving of + // shielded contexts. + std::fs::remove_file(tmp_path)?; + Ok(()) + } + } +} diff --git a/shared/src/sdk/signing.rs b/shared/src/sdk/signing.rs index 3afb990422..aa2ac368f6 100644 --- a/shared/src/sdk/signing.rs +++ b/shared/src/sdk/signing.rs @@ -44,6 +44,7 @@ pub use crate::sdk::wallet::store::AddressVpType; use crate::sdk::wallet::{Wallet, WalletUtils}; use crate::sdk::{args, rpc}; use crate::types::io::*; +use crate::sdk::args::SdkTypes; use crate::types::key::*; use crate::types::masp::{ExtendedViewingKey, PaymentAddress}; use crate::types::storage::Epoch; @@ -53,9 +54,8 @@ use crate::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use crate::types::transaction::pos::InitValidator; -use crate::types::transaction::Fee; use crate::ledger::Namada; -use crate::sdk::args::SdkTypes; +use crate::types::transaction::Fee; #[cfg(feature = "std")] /// Env. var specifying where to store signing test vectors @@ -174,8 +174,13 @@ pub async fn tx_signers<'a>( Some(signer) if signer == masp() => Ok(vec![masp_tx_key().ref_to()]), Some(signer) => Ok(vec![ - find_pk(context.client, context.wallet, &signer, args.password.clone()) - .await?, + find_pk( + context.client, + context.wallet, + &signer, + args.password.clone(), + ) + .await?, ]), None => other_err( "All transactions must be signed; please either specify the key \ @@ -348,10 +353,14 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( }; let fee_amount = match args.fee_amount { Some(amount) => { - let validated_fee_amount = - validate_amount(context.client, amount, &args.fee_token, args.force) - .await - .expect("Expected to be able to validate fee"); + let validated_fee_amount = validate_amount( + context.client, + amount, + &args.fee_token, + args.force, + ) + .await + .expect("Expected to be able to validate fee"); let amount = Amount::from_uint(validated_fee_amount.amount, 0).unwrap(); @@ -385,9 +394,12 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( let balance_key = token::balance_key(&args.fee_token, &fee_payer_address); - rpc::query_storage_value::<_, token::Amount>(context.client, &balance_key) - .await - .unwrap_or_default() + rpc::query_storage_value::<_, token::Amount>( + context.client, + &balance_key, + ) + .await + .unwrap_or_default() } }; @@ -832,20 +844,25 @@ pub async fn to_ledger_vector<'a>( query_wasm_code_hash(context.client, TX_INIT_PROPOSAL).await?; let vote_proposal_hash = query_wasm_code_hash(context.client, TX_VOTE_PROPOSAL).await?; - let reveal_pk_hash = query_wasm_code_hash(context.client, TX_REVEAL_PK).await?; + let reveal_pk_hash = + query_wasm_code_hash(context.client, TX_REVEAL_PK).await?; let update_account_hash = query_wasm_code_hash(context.client, TX_UPDATE_ACCOUNT_WASM).await?; - let transfer_hash = query_wasm_code_hash(context.client, TX_TRANSFER_WASM).await?; + let transfer_hash = + query_wasm_code_hash(context.client, TX_TRANSFER_WASM).await?; let ibc_hash = query_wasm_code_hash(context.client, TX_IBC_WASM).await?; let bond_hash = query_wasm_code_hash(context.client, TX_BOND_WASM).await?; - let unbond_hash = query_wasm_code_hash(context.client, TX_UNBOND_WASM).await?; - let withdraw_hash = query_wasm_code_hash(context.client, TX_WITHDRAW_WASM).await?; + let unbond_hash = + query_wasm_code_hash(context.client, TX_UNBOND_WASM).await?; + let withdraw_hash = + query_wasm_code_hash(context.client, TX_WITHDRAW_WASM).await?; let change_commission_hash = query_wasm_code_hash(context.client, TX_CHANGE_COMMISSION_WASM).await?; let user_hash = query_wasm_code_hash(context.client, VP_USER_WASM).await?; // To facilitate lookups of human-readable token names - let tokens: HashMap = context.wallet + let tokens: HashMap = context + .wallet .get_addresses_with_vp_type(AddressVpType::Token) .into_iter() .map(|addr| { diff --git a/shared/src/sdk/tx.rs b/shared/src/sdk/tx.rs index 4178986caf..e7a86a5d3b 100644 --- a/shared/src/sdk/tx.rs +++ b/shared/src/sdk/tx.rs @@ -50,6 +50,7 @@ use crate::sdk::rpc::{ TxResponse, query_wasm_code_hash }; use crate::sdk::wallet::{Wallet, WalletUtils}; +use crate::ledger::Namada; use crate::proto::{MaspBuilder, Tx}; use crate::sdk::args::{self, InputAmount}; use crate::sdk::error::{EncodingError, Error, QueryError, Result, TxError}; @@ -66,7 +67,6 @@ use crate::types::transaction::account::{InitAccount, UpdateAccount}; use crate::types::transaction::{pos, TxType}; use crate::types::{storage, token}; use crate::{display_line, edisplay_line, vm}; -use crate::ledger::Namada; /// Initialize account transaction WASM pub const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; @@ -103,7 +103,7 @@ pub const TX_CHANGE_COMMISSION_WASM: &str = pub const TX_RESIGN_STEWARD: &str = "tx_resign_steward.wasm"; /// Update steward commission WASM path pub const TX_UPDATE_STEWARD_COMMISSION: &str = - "tx_update_steward_commission.wasm"; + "tx_update_steward_commission.wasm"; /// Default timeout in seconds for requests to the `/accepted` /// and `/applied` ABCI query endpoints. @@ -273,8 +273,7 @@ pub async fn build_reveal_pk<'a>( public_key: &common::PublicKey, ) -> Result<(Tx, SigningTxData, Option)> { let signing_data = - signing::aux_signing_data(context, args, None, None) - .await?; + signing::aux_signing_data(context, args, None, None).await?; build( context, @@ -285,7 +284,8 @@ pub async fn build_reveal_pk<'a>( &signing_data.fee_payer, None, ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Broadcast a transaction to be included in the blockchain and checks that @@ -522,8 +522,8 @@ pub async fn build_validator_commission_change<'a>( Some(validator.clone()), default_signer, ) - .await?; - + .await?; + let epoch = rpc::query_epoch(context.client).await?; let params: PosParams = rpc::get_pos_params(context.client).await?; @@ -598,7 +598,8 @@ pub async fn build_validator_commission_change<'a>( &signing_data.fee_payer, None, ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Craft transaction to update a steward commission @@ -618,8 +619,8 @@ pub async fn build_update_steward_commission<'a>( Some(steward.clone()), default_signer, ) - .await?; - + .await?; + if !rpc::is_steward(context.client, steward).await && !tx_args.force { edisplay_line!(StdIo, "The given address {} is not a steward.", &steward); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); @@ -652,7 +653,8 @@ pub async fn build_update_steward_commission<'a>( &signing_data.fee_payer, None, ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Craft transaction to resign as a steward @@ -671,8 +673,8 @@ pub async fn build_resign_steward<'a>( Some(steward.clone()), default_signer, ) - .await?; - + .await?; + if !rpc::is_steward(context.client, steward).await && !tx_args.force { edisplay_line!(StdIo, "The given address {} is not a steward.", &steward); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); @@ -687,7 +689,8 @@ pub async fn build_resign_steward<'a>( &signing_data.fee_payer, None, ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit transaction to unjail a jailed validator @@ -706,8 +709,8 @@ pub async fn build_unjail_validator<'a>( Some(validator.clone()), default_signer, ) - .await?; - + .await?; + if !rpc::is_validator(context.client, validator).await? { edisplay_line!( StdIo, @@ -725,14 +728,17 @@ pub async fn build_unjail_validator<'a>( let current_epoch = rpc::query_epoch(context.client).await?; let pipeline_epoch = current_epoch + params.pipeline_len; - let validator_state_at_pipeline = - rpc::get_validator_state(context.client, validator, Some(pipeline_epoch)) - .await? - .ok_or_else(|| { - Error::from(TxError::Other( - "Validator state should be defined.".to_string(), - )) - })?; + let validator_state_at_pipeline = rpc::get_validator_state( + context.client, + validator, + Some(pipeline_epoch), + ) + .await? + .ok_or_else(|| { + Error::from(TxError::Other( + "Validator state should be defined.".to_string(), + )) + })?; if validator_state_at_pipeline != ValidatorState::Jailed { edisplay_line!( StdIo, @@ -749,9 +755,11 @@ pub async fn build_unjail_validator<'a>( let last_slash_epoch_key = crate::ledger::pos::validator_last_slash_key(validator); - let last_slash_epoch = - rpc::query_storage_value::<_, Epoch>(context.client, &last_slash_epoch_key) - .await; + let last_slash_epoch = rpc::query_storage_value::<_, Epoch>( + context.client, + &last_slash_epoch_key, + ) + .await; match last_slash_epoch { Ok(last_slash_epoch) => { let eligible_epoch = @@ -793,7 +801,8 @@ pub async fn build_unjail_validator<'a>( &signing_data.fee_payer, None, ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit transaction to withdraw an unbond @@ -814,13 +823,16 @@ pub async fn build_withdraw<'a>( Some(default_address), default_signer, ) - .await?; - + .await?; + let epoch = rpc::query_epoch(context.client).await?; - let validator = - known_validator_or_err(validator.clone(), tx_args.force, context.client) - .await?; + let validator = known_validator_or_err( + validator.clone(), + tx_args.force, + context.client, + ) + .await?; let source = source.clone(); @@ -841,7 +853,8 @@ pub async fn build_withdraw<'a>( epoch {}.", epoch ); - rpc::query_and_print_unbonds(context.client, &bond_source, &validator).await?; + rpc::query_and_print_unbonds(context.client, &bond_source, &validator) + .await?; if !tx_args.force { return Err(Error::from(TxError::NoUnbondReady(epoch))); } @@ -865,7 +878,8 @@ pub async fn build_withdraw<'a>( &signing_data.fee_payer, None, ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit a transaction to unbond @@ -878,7 +892,12 @@ pub async fn build_unbond<'a>( source, tx_code_path, }: &args::Unbond, -) -> Result<(Tx, SigningTxData, Option, Option<(Epoch, token::Amount)>)> { +) -> Result<( + Tx, + SigningTxData, + Option, + Option<(Epoch, token::Amount)>, +)> { let default_address = source.clone().unwrap_or(validator.clone()); let default_signer = Some(default_address.clone()); let signing_data = signing::aux_signing_data( @@ -887,15 +906,19 @@ pub async fn build_unbond<'a>( Some(default_address), default_signer, ) - .await?; - + .await?; + let source = source.clone(); // Check the source's current bond amount let bond_source = source.clone().unwrap_or_else(|| validator.clone()); if !tx_args.force { - known_validator_or_err(validator.clone(), tx_args.force, context.client) - .await?; + known_validator_or_err( + validator.clone(), + tx_args.force, + context.client, + ) + .await?; let bond_amount = rpc::query_bond(context.client, &bond_source, validator, None).await?; @@ -926,9 +949,12 @@ pub async fn build_unbond<'a>( } // Query the unbonds before submitting the tx - let unbonds = - rpc::query_unbond_with_slashing(context.client, &bond_source, validator) - .await?; + let unbonds = rpc::query_unbond_with_slashing( + context.client, + &bond_source, + validator, + ) + .await?; let mut withdrawable = BTreeMap::::new(); for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); @@ -1043,17 +1069,22 @@ pub async fn build_bond<'a>( Some(default_address.clone()), default_signer, ) - .await?; - - let validator = - known_validator_or_err(validator.clone(), tx_args.force, context.client) - .await?; + .await?; + + let validator = known_validator_or_err( + validator.clone(), + tx_args.force, + context.client, + ) + .await?; // Check that the source address exists on chain let source = match source.clone() { - Some(source) => source_exists_or_err(source, tx_args.force, context.client) - .await - .map(Some), + Some(source) => { + source_exists_or_err(source, tx_args.force, context.client) + .await + .map(Some) + } None => Ok(source.clone()), }?; // Check bond's source (source for delegation or validator for self-bonds) @@ -1092,7 +1123,8 @@ pub async fn build_bond<'a>( &signing_data.fee_payer, tx_source_balance, ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a default proposal governance @@ -1116,8 +1148,8 @@ pub async fn build_default_proposal<'a>( Some(proposal.proposal.author.clone()), default_signer, ) - .await?; - + .await?; + let init_proposal_data = InitProposalData::try_from(proposal.clone()) .map_err(|e| TxError::InvalidProposal(e.to_string()))?; @@ -1144,7 +1176,8 @@ pub async fn build_default_proposal<'a>( &signing_data.fee_payer, None, // TODO: need to pay the fee to submit a proposal ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a proposal vote @@ -1168,8 +1201,8 @@ pub async fn build_vote_proposal<'a>( Some(voter.clone()), default_signer.clone(), ) - .await?; - + .await?; + let proposal_vote = ProposalVote::try_from(vote.clone()) .map_err(|_| TxError::InvalidProposalVote)?; @@ -1230,7 +1263,8 @@ pub async fn build_vote_proposal<'a>( &signing_data.fee_payer, None, ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a pgf funding proposal governance @@ -1254,8 +1288,8 @@ pub async fn build_pgf_funding_proposal<'a>( Some(proposal.proposal.author.clone()), default_signer, ) - .await?; - + .await?; + let init_proposal_data = InitProposalData::try_from(proposal.clone()) .map_err(|e| TxError::InvalidProposal(e.to_string()))?; @@ -1274,7 +1308,8 @@ pub async fn build_pgf_funding_proposal<'a>( &signing_data.fee_payer, None, // TODO: need to pay the fee to submit a proposal ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a pgf funding proposal governance @@ -1298,8 +1333,8 @@ pub async fn build_pgf_stewards_proposal<'a>( Some(proposal.proposal.author.clone()), default_signer, ) - .await?; - + .await?; + let init_proposal_data = InitProposalData::try_from(proposal.clone()) .map_err(|e| TxError::InvalidProposal(e.to_string()))?; @@ -1319,7 +1354,8 @@ pub async fn build_pgf_stewards_proposal<'a>( &signing_data.fee_payer, None, // TODO: need to pay the fee to submit a proposal ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit an IBC transfer @@ -1336,16 +1372,23 @@ pub async fn build_ibc_transfer<'a>( ) .await?; // Check that the source address exists on chain - let source = - source_exists_or_err(args.source.clone(), args.tx.force, context.client) - .await?; + let source = source_exists_or_err( + args.source.clone(), + args.tx.force, + context.client, + ) + .await?; // We cannot check the receiver // validate the amount given - let validated_amount = - validate_amount(context.client, args.amount, &args.token, args.tx.force) - .await - .expect("expected to validate amount"); + let validated_amount = validate_amount( + context.client, + args.amount, + &args.token, + args.tx.force, + ) + .await + .expect("expected to validate amount"); if validated_amount.canonical().denom.0 != 0 { return Err(Error::Other(format!( "The amount for the IBC transfer should be an integer: {}", @@ -1371,17 +1414,22 @@ pub async fn build_ibc_transfer<'a>( token: args.token.clone(), }); - let tx_code_hash = - query_wasm_code_hash(context.client, args.tx_code_path.to_str().unwrap()) - .await - .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; + let tx_code_hash = query_wasm_code_hash( + context.client, + args.tx_code_path.to_str().unwrap(), + ) + .await + .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; let ibc_denom = match &args.token { Address::Internal(InternalAddress::IbcToken(hash)) => { let ibc_denom_key = ibc_denom_key(hash); - rpc::query_storage_value::<_, String>(context.client, &ibc_denom_key) - .await - .map_err(|_e| TxError::TokenDoesNotExist(args.token.clone()))? + rpc::query_storage_value::<_, String>( + context.client, + &ibc_denom_key, + ) + .await + .map_err(|_e| TxError::TokenDoesNotExist(args.token.clone()))? } _ => args.token.to_string(), }; @@ -1499,9 +1547,10 @@ where let mut tx_builder = Tx::new(chain_id, tx_args.expiration); - let tx_code_hash = query_wasm_code_hash(context.client, path.to_string_lossy()) - .await - .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; + let tx_code_hash = + query_wasm_code_hash(context.client, path.to_string_lossy()) + .await + .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; on_tx(&mut tx_builder, &mut data)?; @@ -1526,8 +1575,10 @@ async fn add_asset_type<'a>( asset_type: AssetType, ) -> bool { let context = &mut **context; - if let Some(asset_type) = - context.shielded.decode_asset_type(context.client, asset_type).await + if let Some(asset_type) = context + .shielded + .decode_asset_type(context.client, asset_type) + .await { asset_types.insert(asset_type) } else { @@ -1545,35 +1596,27 @@ async fn used_asset_types<'a, P, R, K, N>( let mut asset_types = HashSet::new(); // Collect all the asset types used in the Sapling inputs for input in builder.sapling_inputs() { - add_asset_type(&mut asset_types, context, input.asset_type()) - .await; + add_asset_type(&mut asset_types, context, input.asset_type()).await; } // Collect all the asset types used in the transparent inputs for input in builder.transparent_inputs() { - add_asset_type( - &mut asset_types, - context, - input.coin().asset_type(), - ) - .await; + add_asset_type(&mut asset_types, context, input.coin().asset_type()) + .await; } // Collect all the asset types used in the Sapling outputs for output in builder.sapling_outputs() { - add_asset_type(&mut asset_types, context, output.asset_type()) - .await; + add_asset_type(&mut asset_types, context, output.asset_type()).await; } // Collect all the asset types used in the transparent outputs for output in builder.transparent_outputs() { - add_asset_type(&mut asset_types, context, output.asset_type()) - .await; + add_asset_type(&mut asset_types, context, output.asset_type()).await; } // Collect all the asset types used in the Sapling converts for output in builder.sapling_converts() { for (asset_type, _) in I32Sum::from(output.conversion().clone()).components() { - add_asset_type(&mut asset_types, context, *asset_type) - .await; + add_asset_type(&mut asset_types, context, *asset_type).await; } } Ok(asset_types) @@ -1591,8 +1634,8 @@ pub async fn build_transfer<'a, N: Namada<'a>>( Some(args.source.effective_address()), default_signer, ) - .await?; - + .await?; + let source = args.source.effective_address(); let target = args.target.effective_address(); let token = args.token.clone(); @@ -1606,7 +1649,8 @@ pub async fn build_transfer<'a, N: Namada<'a>>( // validate the amount given let validated_amount = - validate_amount(context.client, args.amount, &token, args.tx.force).await?; + validate_amount(context.client, args.amount, &token, args.tx.force) + .await?; args.amount = InputAmount::Validated(validated_amount); let post_balance = check_balance_too_low_err( @@ -1643,7 +1687,11 @@ pub async fn build_transfer<'a, N: Namada<'a>>( }; // Construct the shielded part of the transaction, if any - let stx_result = ShieldedContext::::gen_shielded_transfer(context, args).await; + let stx_result = + ShieldedContext::::gen_shielded_transfer( + context, args, + ) + .await; let shielded_parts = match stx_result { Ok(stx) => Ok(stx), @@ -1664,10 +1712,9 @@ pub async fn build_transfer<'a, N: Namada<'a>>( Some(transfer) => { // Get the decoded asset types used in the transaction to give // offline wallet users more information - let asset_types = - used_asset_types(context, &transfer.builder) - .await - .unwrap_or_default(); + let asset_types = used_asset_types(context, &transfer.builder) + .await + .unwrap_or_default(); Some(asset_types) } }; @@ -1756,8 +1803,9 @@ pub async fn build_init_account<'a>( ) -> Result<(Tx, SigningTxData, Option)> { let signing_data = signing::aux_signing_data(context, tx_args, None, None).await?; - - let vp_code_hash = query_wasm_code_hash_buf(context.client, vp_code_path).await?; + + let vp_code_hash = + query_wasm_code_hash_buf(context.client, vp_code_path).await?; let threshold = match threshold { Some(threshold) => *threshold, @@ -1791,7 +1839,8 @@ pub async fn build_init_account<'a>( &signing_data.fee_payer, None, ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit a transaction to update a VP @@ -1813,20 +1862,22 @@ pub async fn build_update_account<'a>( Some(addr.clone()), default_signer, ) - .await?; - - let addr = - if let Some(account) = rpc::get_account_info(context.client, addr).await? { - account.address - } else if tx_args.force { - addr.clone() - } else { - return Err(Error::from(TxError::LocationDoesNotExist(addr.clone()))); - }; + .await?; + + let addr = if let Some(account) = + rpc::get_account_info(context.client, addr).await? + { + account.address + } else if tx_args.force { + addr.clone() + } else { + return Err(Error::from(TxError::LocationDoesNotExist(addr.clone()))); + }; let vp_code_hash = match vp_code_path { Some(code_path) => { - let vp_hash = query_wasm_code_hash_buf(context.client, code_path).await?; + let vp_hash = + query_wasm_code_hash_buf(context.client, code_path).await?; Some(vp_hash) } None => None, @@ -1859,7 +1910,8 @@ pub async fn build_update_account<'a>( &signing_data.fee_payer, None, ) - .await.map(|(tx, epoch)| (tx, signing_data, epoch)) + .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit a custom transaction @@ -1880,8 +1932,8 @@ pub async fn build_custom<'a>( Some(owner.clone()), default_signer, ) - .await?; - + .await?; + let mut tx = if let Some(serialized_tx) = serialized_tx { Tx::deserialize(serialized_tx.as_ref()).map_err(|_| { Error::Other("Invalid tx deserialization.".to_string()) @@ -1889,7 +1941,8 @@ pub async fn build_custom<'a>( } else { let tx_code_hash = query_wasm_code_hash_buf( context.client, - code_path.as_ref() + code_path + .as_ref() .ok_or(Error::Other("No code path supplied".to_string()))?, ) .await?; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index b763aec013..025f360c42 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -19,11 +19,10 @@ use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; use data_encoding::HEXLOWER; +use namada::sdk::masp::fs::FsShieldedUtils; use namada::types::address::Address; -use namada::types::io::StdIo; use namada::types::storage::Epoch; use namada::types::token; -use namada_apps::client::tx::CLIShieldedUtils; use namada_apps::config::ethereum_bridge; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PgfParametersConfig, PosParamsConfig, @@ -688,7 +687,7 @@ fn ledger_txs_and_queries() -> Result<()> { #[test] fn masp_txs_and_queries() -> Result<()> { // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. @@ -836,7 +835,7 @@ fn masp_txs_and_queries() -> Result<()> { #[test] fn wrapper_disposable_signer() -> Result<()> { // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. diff --git a/tests/src/integration/masp.rs b/tests/src/integration/masp.rs index 1cd08b9976..261d3acd08 100644 --- a/tests/src/integration/masp.rs +++ b/tests/src/integration/masp.rs @@ -2,8 +2,7 @@ use std::path::PathBuf; use color_eyre::eyre::Result; use color_eyre::owo_colors::OwoColorize; -use namada::types::io::StdIo; -use namada_apps::client::tx::CLIShieldedUtils; +use namada::sdk::masp::fs::FsShieldedUtils; use namada_apps::node::ledger::shell::testing::client::run; use namada_apps::node::ledger::shell::testing::utils::{Bin, CapturedOutput}; use namada_core::types::address::{btc, eth, masp_rewards}; @@ -29,7 +28,7 @@ fn masp_incentives() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. @@ -765,7 +764,7 @@ fn masp_pinned_txs() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); let mut node = setup::setup()?; // Wait till epoch boundary @@ -928,7 +927,7 @@ fn masp_txs_and_queries() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); enum Response { Ok(&'static str), @@ -1234,7 +1233,7 @@ fn wrapper_fee_unshielding() { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. From 1142e6470a38191a4149c49c24be2aedfc48066e Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 28 Sep 2023 16:34:31 +0200 Subject: [PATCH 05/14] Adding saving and loading function to Wallet. --- Cargo.lock | 1 + apps/src/lib/cli/wallet.rs | 22 +- apps/src/lib/client/tx.rs | 10 +- apps/src/lib/client/utils.rs | 45 ++- apps/src/lib/wallet/cli_utils.rs | 22 +- apps/src/lib/wallet/mod.rs | 81 ++-- apps/src/lib/wallet/pre_genesis.rs | 4 +- apps/src/lib/wallet/store.rs | 71 +--- shared/Cargo.toml | 3 +- shared/src/ledger/mod.rs | 18 +- shared/src/sdk/signing.rs | 8 +- shared/src/sdk/tx.rs | 6 +- shared/src/sdk/wallet/keys.rs | 6 +- shared/src/sdk/wallet/mod.rs | 569 ++++++++++++++++++----------- shared/src/sdk/wallet/store.rs | 16 +- 15 files changed, 499 insertions(+), 383 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84cbd6f48e..d8303e37f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4026,6 +4026,7 @@ dependencies = [ "ethbridge-bridge-contract", "ethers", "eyre", + "fd-lock", "futures", "itertools", "libsecp256k1 0.7.0", diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index d039c7ef10..09d66096af 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -8,7 +8,7 @@ use color_eyre::eyre::Result; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::sdk::masp::find_valid_diversifier; -use namada::sdk::wallet::{DecryptionError, FindKeyError}; +use namada::sdk::wallet::{DecryptionError, FindKeyError, GenRestoreKeyError}; use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; @@ -356,6 +356,7 @@ fn key_and_address_restore( alias, alias_force, derivation_path, + None, encryption_password, ) .unwrap_or_else(|err| { @@ -393,21 +394,26 @@ fn key_and_address_gen( let mut rng = OsRng; let derivation_path_and_mnemonic_rng = derivation_path.map(|p| (p, &mut rng)); - let (alias, _key) = wallet + let (alias, _key, _mnemonic) = wallet .gen_key( scheme, alias, alias_force, + None, encryption_password, derivation_path_and_mnemonic_rng, ) .unwrap_or_else(|err| { - edisplay_line!(IO, "{}", err); - cli::safe_exit(1); - }) - .unwrap_or_else(|| { - display_line!(IO, "No changes are persisted. Exiting."); - cli::safe_exit(0); + match err { + GenRestoreKeyError::KeyStorageError => { + println!("No changes are persisted. Exiting."); + cli::safe_exit(0); + }, + _ => { + eprintln!("{}", err); + cli::safe_exit(1); + } + } }); crate::wallet::save(&wallet) .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 1289492faa..2928ce755f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -8,7 +8,7 @@ use namada::core::ledger::governance::cli::onchain::{ }; use namada::ledger::pos; use namada::sdk::rpc::{TxBroadcastData, TxResponse}; -use namada::sdk::wallet::{Wallet, WalletUtils}; +use namada::sdk::wallet::{Wallet, WalletIo}; use namada::ledger::{Namada, NamadaImpl}; use namada::proof_of_stake::parameters::PosParams; use namada::proto::Tx; @@ -247,11 +247,11 @@ where SchemeType::Ed25519, Some(consensus_key_alias.clone()), tx_args.wallet_alias_force, + None, password, None, ) .expect("Key generation should not fail.") - .expect("No existing alias expected.") .1 }); @@ -273,11 +273,11 @@ where SchemeType::Secp256k1, Some(eth_cold_key_alias.clone()), tx_args.wallet_alias_force, + None, password, None, ) .expect("Key generation should not fail.") - .expect("No existing alias expected.") .1 .ref_to() }); @@ -300,11 +300,11 @@ where SchemeType::Secp256k1, Some(eth_hot_key_alias.clone()), tx_args.wallet_alias_force, + None, password, None, ) .expect("Key generation should not fail.") - .expect("No existing alias expected.") .1 .ref_to() }); @@ -1087,7 +1087,7 @@ where } /// Save accounts initialized from a tx into the wallet, if any. -pub async fn save_initialized_accounts( +pub async fn save_initialized_accounts( wallet: &mut Wallet, args: &args::Tx, initialized_accounts: Vec
, diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 0caab25d35..7e243fd502 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -505,10 +505,9 @@ pub fn init_network( println!("Generating validator {} consensus key...", name); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet - .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + let (_alias, keypair, _mnemonic) = wallet + .gen_key(SchemeType::Ed25519, Some(alias), true, None, password, None) + .expect("Key generation should not fail."); // Write consensus key for Tendermint tendermint_node::write_validator_key(&tm_home_dir, &keypair); @@ -525,10 +524,9 @@ pub fn init_network( println!("Generating validator {} account key...", name); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet - .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + let (_alias, keypair, _mnemonic) = wallet + .gen_key(SchemeType::Ed25519, Some(alias), true, None, password, None) + .expect("Key generation should not fail."); keypair.ref_to() }); @@ -541,10 +539,9 @@ pub fn init_network( println!("Generating validator {} protocol signing key...", name); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet - .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + let (_alias, keypair, _mnemonic) = wallet + .gen_key(SchemeType::Ed25519, Some(alias), true, None, password, None) + .expect("Key generation should not fail."); keypair.ref_to() }); @@ -557,16 +554,16 @@ pub fn init_network( println!("Generating validator {} eth hot key...", name); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet + let (_alias, keypair, _mnemonic) = wallet .gen_key( SchemeType::Secp256k1, Some(alias), true, + None, password, None, ) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + .expect("Key generation should not fail."); keypair.ref_to() }); @@ -579,16 +576,16 @@ pub fn init_network( println!("Generating validator {} eth cold key...", name); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet + let (_alias, keypair, _mnemonic) = wallet .gen_key( SchemeType::Secp256k1, Some(alias), true, + None, password, None, ) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + .expect("Key generation should not fail."); keypair.ref_to() }); @@ -675,16 +672,16 @@ pub fn init_network( ); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet + let (_alias, keypair, _mnemonic) = wallet .gen_key( SchemeType::Ed25519, Some(name.clone()), true, + None, password, None, ) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + .expect("Key generation should not fail."); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); config.public_key = Some(public_key); @@ -938,16 +935,16 @@ fn init_established_account( println!("Generating established account {} key...", name.as_ref()); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet + let (_alias, keypair, _mnemonic) = wallet .gen_key( SchemeType::Ed25519, Some(format!("{}-key", name.as_ref())), true, + None, password, None, // do not use mnemonic code / HD derivation path ) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + .expect("Key generation should not fail."); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); config.public_key = Some(public_key); diff --git a/apps/src/lib/wallet/cli_utils.rs b/apps/src/lib/wallet/cli_utils.rs index 72bb0acaab..fb288df193 100644 --- a/apps/src/lib/wallet/cli_utils.rs +++ b/apps/src/lib/wallet/cli_utils.rs @@ -5,7 +5,7 @@ use borsh::BorshSerialize; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::sdk::masp::find_valid_diversifier; -use namada::sdk::wallet::{DecryptionError, FindKeyError}; +use namada::sdk::wallet::{DecryptionError, FindKeyError, GenRestoreKeyError}; use namada::types::key::{PublicKeyHash, RefTo}; use namada::types::masp::{MaspValue, PaymentAddress}; use rand_core::OsRng; @@ -271,6 +271,7 @@ pub fn key_and_address_restore( alias, alias_force, derivation_path, + None, encryption_password, ) .unwrap_or_else(|err| { @@ -306,21 +307,26 @@ pub fn key_and_address_gen( let mut rng = OsRng; let derivation_path_and_mnemonic_rng = derivation_path.map(|p| (p, &mut rng)); - let (alias, _key) = wallet + let (alias, _key, _mnemonic) = wallet .gen_key( scheme, alias, alias_force, + None, encryption_password, derivation_path_and_mnemonic_rng, ) .unwrap_or_else(|err| { - eprintln!("{}", err); - cli::safe_exit(1); - }) - .unwrap_or_else(|| { - println!("No changes are persisted. Exiting."); - cli::safe_exit(0); + match err { + GenRestoreKeyError::KeyStorageError => { + println!("No changes are persisted. Exiting."); + cli::safe_exit(0); + }, + _ => { + eprintln!("{}", err); + cli::safe_exit(1); + } + } }); crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index f6611ebe18..01967a5c24 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -12,7 +12,7 @@ use namada::bip39::{Language, Mnemonic}; pub use namada::sdk::wallet::alias::Alias; use namada::sdk::wallet::{ AddressVpType, ConfirmationResponse, FindKeyError, GenRestoreKeyError, - Wallet, WalletUtils, + Wallet, WalletIo, }; pub use namada::sdk::wallet::{ValidatorData, ValidatorKeys}; use namada::types::address::Address; @@ -20,36 +20,34 @@ use namada::types::key::*; use rand_core::OsRng; pub use store::wallet_file; use zeroize::Zeroizing; +use namada::sdk::wallet::store::Store; +use namada::sdk::wallet::fs::FsWalletStorage; use crate::cli; use crate::config::genesis::genesis_config::GenesisConfig; -#[derive(Debug)] -pub struct CliWalletUtils; +#[derive(Debug, Clone)] +pub struct CliWalletUtils { + store_dir: PathBuf, +} -impl WalletUtils for CliWalletUtils { - type Rng = OsRng; - type Storage = PathBuf; +impl CliWalletUtils { + /// Initialize a wallet at the given directory + pub fn new(store_dir: PathBuf) -> Wallet { + Wallet::new(Self { store_dir }, Store::default()) + } +} - fn read_decryption_password() -> Zeroizing { - match env::var("NAMADA_WALLET_PASSWORD_FILE") { - Ok(path) => Zeroizing::new( - fs::read_to_string(path) - .expect("Something went wrong reading the file"), - ), - Err(_) => match env::var("NAMADA_WALLET_PASSWORD") { - Ok(password) => Zeroizing::new(password), - Err(_) => { - let prompt = "Enter your decryption password: "; - rpassword::read_password_from_tty(Some(prompt)) - .map(Zeroizing::new) - .expect("Failed reading password from tty.") - } - }, - } +impl FsWalletStorage for CliWalletUtils { + fn store_dir(&self) -> &PathBuf { + &self.store_dir } +} + +impl WalletIo for CliWalletUtils { + type Rng = OsRng; - fn read_encryption_password() -> Zeroizing { + fn read_password(confirm: bool) -> Zeroizing { let pwd = match env::var("NAMADA_WALLET_PASSWORD_FILE") { Ok(path) => Zeroizing::new( fs::read_to_string(path) @@ -57,7 +55,7 @@ impl WalletUtils for CliWalletUtils { ), Err(_) => match env::var("NAMADA_WALLET_PASSWORD") { Ok(password) => Zeroizing::new(password), - Err(_) => { + Err(_) if confirm => { let prompt = "Enter your encryption password: "; read_and_confirm_passphrase_tty(prompt).unwrap_or_else( |e| { @@ -68,10 +66,16 @@ impl WalletUtils for CliWalletUtils { cli::safe_exit(1) }, ) + }, + Err(_) => { + let prompt = "Enter your decryption password: "; + rpassword::read_password_from_tty(Some(prompt)) + .map(Zeroizing::new) + .expect("Failed reading password from tty.") } }, }; - if pwd.as_str().is_empty() { + if confirm && pwd.as_str().is_empty() { eprintln!("Password cannot be empty"); eprintln!("Action cancelled, no changes persisted."); cli::safe_exit(1) @@ -190,7 +194,7 @@ pub fn read_and_confirm_passphrase_tty( /// for signing protocol txs and for the DKG (which will also be stored) /// A protocol keypair may be optionally provided, indicating that /// we should re-use a keypair already in the wallet -pub fn gen_validator_keys( +pub fn gen_validator_keys( wallet: &mut Wallet, eth_bridge_pk: Option, protocol_pk: Option, @@ -221,7 +225,7 @@ fn find_secret_key( ) -> Result, FindKeyError> where F: Fn(&ValidatorData) -> common::SecretKey, - U: WalletUtils, + U: WalletIo, { maybe_pk .map(|pk| { @@ -254,19 +258,18 @@ pub fn add_genesis_addresses( /// Save the wallet store to a file. pub fn save(wallet: &Wallet) -> std::io::Result<()> { - self::store::save(wallet.store(), wallet.store_dir()) + wallet.save() + .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) } /// Load a wallet from the store file. pub fn load(store_dir: &Path) -> Option> { - let store = self::store::load(store_dir).unwrap_or_else(|err| { + let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); + wallet.load().unwrap_or_else(|err| { eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Some(Wallet::::new( - store_dir.to_path_buf(), - store, - )) + Some(wallet) } /// Load a wallet from the store file or create a new wallet without any @@ -276,7 +279,9 @@ pub fn load_or_new(store_dir: &Path) -> Wallet { eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Wallet::::new(store_dir.to_path_buf(), store) + let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); + *wallet.store_mut() = store; + wallet } /// Load a wallet from the store file or create a new one with the default @@ -290,7 +295,9 @@ pub fn load_or_new_from_genesis( eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Wallet::::new(store_dir.to_path_buf(), store) + let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); + *wallet.store_mut() = store; + wallet } /// Read the password for encryption from the file/env/stdin, with @@ -302,14 +309,14 @@ pub fn read_and_confirm_encryption_password( println!("Warning: The keypair will NOT be encrypted."); None } else { - Some(CliWalletUtils::read_encryption_password()) + Some(CliWalletUtils::read_password(true)) } } #[cfg(test)] mod tests { use namada::bip39::MnemonicType; - use namada::sdk::wallet::WalletUtils; + use namada::sdk::wallet::WalletIo; use rand_core; use super::CliWalletUtils; diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 21a80267f1..13a2c21f2b 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -6,7 +6,7 @@ use fd_lock::RwLock; use namada::sdk::wallet::pre_genesis::{ ReadError, ValidatorStore, ValidatorWallet, }; -use namada::sdk::wallet::{gen_key_to_store, WalletUtils}; +use namada::sdk::wallet::{gen_key_to_store, WalletIo}; use namada::types::key::SchemeType; use zeroize::Zeroizing; @@ -75,7 +75,7 @@ pub fn load(store_dir: &Path) -> Result { || store.consensus_key.is_encrypted() || store.account_key.is_encrypted() { - Some(CliWalletUtils::read_decryption_password()) + Some(CliWalletUtils::read_password(false)) } else { None }; diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 0f2aa86b7b..24c62e4866 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -1,13 +1,9 @@ -use std::fs; -use std::io::prelude::*; -use std::io::Write; use std::path::{Path, PathBuf}; #[cfg(not(feature = "dev"))] use std::str::FromStr; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; -use fd_lock::RwLock; #[cfg(not(feature = "dev"))] use namada::sdk::wallet::store::AddressVpType; #[cfg(feature = "dev")] @@ -17,21 +13,11 @@ use namada::sdk::wallet::{gen_sk_rng, Store, ValidatorKeys}; use namada::types::address::Address; use namada::types::key::*; use namada::types::transaction::EllipticCurve; -use thiserror::Error; +use namada::sdk::wallet::LoadStoreError; use crate::config::genesis::genesis_config::GenesisConfig; use crate::wallet::CliWalletUtils; -#[derive(Error, Debug)] -pub enum LoadStoreError { - #[error("Failed decoding the wallet store: {0}")] - Decode(toml::de::Error), - #[error("Failed to read the wallet store from {0}: {1}")] - ReadWallet(String, String), - #[error("Failed to write the wallet store: {0}")] - StoreNewWallet(String), -} - /// Wallet file name const FILE_NAME: &str = "wallet.toml"; @@ -40,28 +26,12 @@ pub fn wallet_file(store_dir: impl AsRef) -> PathBuf { store_dir.as_ref().join(FILE_NAME) } -/// Save the wallet store to a file. -pub fn save(store: &Store, store_dir: &Path) -> std::io::Result<()> { - let data = store.encode(); - let wallet_path = wallet_file(store_dir); - // Make sure the dir exists - let wallet_dir = wallet_path.parent().unwrap(); - fs::create_dir_all(wallet_dir)?; - // Write the file - let mut options = fs::OpenOptions::new(); - options.create(true).write(true).truncate(true); - let mut lock = RwLock::new(options.open(wallet_path)?); - let mut guard = lock.write()?; - guard.write_all(&data) -} - /// Load the store file or create a new one without any keys or addresses. pub fn load_or_new(store_dir: &Path) -> Result { load(store_dir).or_else(|_| { - let store = Store::default(); - save(&store, store_dir) - .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?; - Ok(store) + let wallet = CliWalletUtils::new(store_dir.to_path_buf()); + wallet.save()?; + Ok(wallet.into()) }) } @@ -80,37 +50,18 @@ pub fn load_or_new_from_genesis( let _ = genesis_cfg; new() }; - save(&store, store_dir) - .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?; - Ok(store) + let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); + *wallet.store_mut() = store; + wallet.save()?; + Ok(wallet.into()) }) } /// Attempt to load the store file. pub fn load(store_dir: &Path) -> Result { - let wallet_file = wallet_file(store_dir); - let mut options = fs::OpenOptions::new(); - options.read(true).write(false); - let lock = RwLock::new(options.open(&wallet_file).map_err(|err| { - LoadStoreError::ReadWallet( - wallet_file.to_string_lossy().into_owned(), - err.to_string(), - ) - })?); - let guard = lock.read().map_err(|err| { - LoadStoreError::ReadWallet( - wallet_file.to_string_lossy().into_owned(), - err.to_string(), - ) - })?; - let mut store = Vec::::new(); - (&*guard).read_to_end(&mut store).map_err(|err| { - LoadStoreError::ReadWallet( - store_dir.to_str().unwrap().parse().unwrap(), - err.to_string(), - ) - })?; - Store::decode(store).map_err(LoadStoreError::Decode) + let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); + wallet.load()?; + Ok(wallet.into()) } /// Add addresses from a genesis configuration. diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 3259afd2c7..660a810087 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -19,7 +19,7 @@ default = ["abciplus", "namada-sdk", "wasm-runtime"] mainnet = [ "namada_core/mainnet", ] -std = [] +std = ["fd-lock"] # NOTE "dev" features that shouldn't be used in live networks are enabled by default for now dev = [] ferveo-tpke = [ @@ -107,6 +107,7 @@ derivative.workspace = true ethbridge-bridge-contract.workspace = true ethers.workspace = true eyre.workspace = true +fd-lock = { workspace = true, optional = true } futures.workspace = true itertools.workspace = true loupe = {version = "0.1.3", optional = true} diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 7686635f98..e952c99a08 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -22,7 +22,7 @@ pub use namada_core::ledger::{ gas, parameters, replay_protection, storage_api, tx_env, vp_env, }; -use crate::sdk::wallet::{Wallet, WalletUtils}; +use crate::sdk::wallet::{Wallet, WalletIo, WalletStorage}; use crate::sdk::masp::{ShieldedContext, ShieldedUtils}; use crate::types::masp::{TransferSource, TransferTarget}; use crate::types::address::Address; @@ -50,7 +50,7 @@ use namada_core::types::ethereum_events::EthAddress; pub struct NamadaStruct<'a, C, U, V> where C: crate::ledger::queries::Client + Sync, - U: WalletUtils, + U: WalletIo, V: ShieldedUtils, { /// Used to send and receive messages from the ledger @@ -76,7 +76,7 @@ pub trait Namada<'a>: /// A client with async request dispatcher method type Client: 'a + crate::ledger::queries::Client + Sync; /// Captures the interactive parts of the wallet's functioning - type WalletUtils: 'a + WalletUtils; + type WalletUtils: 'a + WalletIo + WalletStorage; /// Abstracts platform specific details away from the logic of shielded pool /// operations. type ShieldedUtils: 'a + ShieldedUtils; @@ -391,7 +391,7 @@ pub trait Namada<'a>: pub struct NamadaImpl<'a, C, U, V> where C: crate::ledger::queries::Client + Sync, - U: WalletUtils, + U: WalletIo, V: ShieldedUtils, { namada: NamadaStruct<'a, C, U, V>, @@ -401,7 +401,7 @@ where impl<'a, C, U, V> NamadaImpl<'a, C, U, V> where C: crate::ledger::queries::Client + Sync, - U: WalletUtils, + U: WalletIo, V: ShieldedUtils, { /// Construct a new Namada context @@ -451,7 +451,7 @@ where impl<'a, C, U, V> Deref for NamadaImpl<'a, C, U, V> where C: crate::ledger::queries::Client + Sync, - U: WalletUtils, + U: WalletIo, V: ShieldedUtils, { type Target = NamadaStruct<'a, C, U, V>; @@ -464,7 +464,7 @@ where impl<'a, C, U, V> DerefMut for NamadaImpl<'a, C, U, V> where C: crate::ledger::queries::Client + Sync, - U: WalletUtils, + U: WalletIo, V: ShieldedUtils, { fn deref_mut(&mut self) -> &mut Self::Target { @@ -475,7 +475,7 @@ where impl<'a, C, U, V> Namada<'a> for NamadaImpl<'a, C, U, V> where C: crate::ledger::queries::Client + Sync, - U: WalletUtils, + U: WalletIo + WalletStorage, V: ShieldedUtils, { type Client = C; @@ -492,7 +492,7 @@ where impl<'a, C, U, V> args::TxBuilder for NamadaImpl<'a, C, U, V> where C: crate::ledger::queries::Client + Sync, - U: WalletUtils, + U: WalletIo, V: ShieldedUtils, { fn tx(self, func: F) -> Self diff --git a/shared/src/sdk/signing.rs b/shared/src/sdk/signing.rs index aa2ac368f6..24f3b12c7b 100644 --- a/shared/src/sdk/signing.rs +++ b/shared/src/sdk/signing.rs @@ -41,7 +41,7 @@ use crate::sdk::tx::{ VP_USER_WASM, }; pub use crate::sdk::wallet::store::AddressVpType; -use crate::sdk::wallet::{Wallet, WalletUtils}; +use crate::sdk::wallet::{Wallet, WalletIo}; use crate::sdk::{args, rpc}; use crate::types::io::*; use crate::sdk::args::SdkTypes; @@ -85,7 +85,7 @@ pub struct SigningTxData { /// found or loaded. pub async fn find_pk< C: crate::ledger::queries::Client + Sync, - U: WalletUtils, + U: WalletIo, >( client: &C, wallet: &mut Wallet, @@ -127,7 +127,7 @@ pub async fn find_pk< /// Load the secret key corresponding to the given public key from the wallet. /// If the keypair is encrypted but a password is not supplied, then it is /// interactively prompted. Errors if the key cannot be found or loaded. -pub fn find_key_by_pk( +pub fn find_key_by_pk( wallet: &mut Wallet, args: &args::Tx, public_key: &common::PublicKey, @@ -201,7 +201,7 @@ pub async fn tx_signers<'a>( /// hashes needed for monitoring the tx on chain. /// /// If it is a dry run, it is not put in a wrapper, but returned as is. -pub fn sign_tx( +pub fn sign_tx( wallet: &mut Wallet, args: &args::Tx, tx: &mut Tx, diff --git a/shared/src/sdk/tx.rs b/shared/src/sdk/tx.rs index e7a86a5d3b..f718084709 100644 --- a/shared/src/sdk/tx.rs +++ b/shared/src/sdk/tx.rs @@ -49,7 +49,7 @@ use crate::sdk::rpc::{ self, format_denominated_amount, validate_amount, TxBroadcastData, TxResponse, query_wasm_code_hash }; -use crate::sdk::wallet::{Wallet, WalletUtils}; +use crate::sdk::wallet::{Wallet, WalletIo}; use crate::ledger::Namada; use crate::proto::{MaspBuilder, Tx}; use crate::sdk::args::{self, InputAmount}; @@ -187,7 +187,7 @@ pub async fn prepare_tx<'a>( /// initialized in the transaction if any. In dry run, this is always empty. pub async fn process_tx< C: crate::sdk::queries::Client + Sync, - U: WalletUtils, + U: WalletIo, >( client: &C, wallet: &mut Wallet, @@ -450,7 +450,7 @@ pub fn decode_component( } /// Save accounts initialized from a tx into the wallet, if any. -pub async fn save_initialized_accounts( +pub async fn save_initialized_accounts( wallet: &mut Wallet, args: &args::Tx, initialized_accounts: Vec
, diff --git a/shared/src/sdk/wallet/keys.rs b/shared/src/sdk/wallet/keys.rs index 867a2b1ad0..6b10352c8b 100644 --- a/shared/src/sdk/wallet/keys.rs +++ b/shared/src/sdk/wallet/keys.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use zeroize::Zeroizing; -use crate::sdk::wallet::WalletUtils; +use crate::sdk::wallet::WalletIo; const ENCRYPTED_KEY_PREFIX: &str = "encrypted:"; const UNENCRYPTED_KEY_PREFIX: &str = "unencrypted:"; @@ -166,7 +166,7 @@ where /// Get a raw keypair from a stored keypair. If the keypair is encrypted and /// no password is provided in the argument, a password will be prompted /// from stdin. - pub fn get( + pub fn get( &self, decrypt: bool, password: Option>, @@ -175,7 +175,7 @@ where StoredKeypair::Encrypted(encrypted_keypair) => { if decrypt { let password = password - .unwrap_or_else(|| U::read_decryption_password()); + .unwrap_or_else(|| U::read_password(false)); let key = encrypted_keypair.decrypt(password)?; Ok(key) } else { diff --git a/shared/src/sdk/wallet/mod.rs b/shared/src/sdk/wallet/mod.rs index 371c97806b..580b2db5a1 100644 --- a/shared/src/sdk/wallet/mod.rs +++ b/shared/src/sdk/wallet/mod.rs @@ -40,12 +40,13 @@ pub enum GenRestoreKeyError { /// Mnemonic input error #[error("Mnemonic input error")] MnemonicInputError, + /// Key storage error + #[error("Key storage error")] + KeyStorageError, } /// Captures the interactive parts of the wallet's functioning -pub trait WalletUtils { - /// The location where the wallet is stored - type Storage; +pub trait WalletIo: Sized + Clone { /// Secure random number generator type Rng: RngCore; @@ -67,29 +68,150 @@ pub trait WalletUtils { } /// Read the password for decryption from the file/env/stdin. - fn read_decryption_password() -> Zeroizing; - - /// Read the password for encryption from the file/env/stdin. - /// If the password is read from stdin, the implementation is expected - /// to ask for a confirmation. - fn read_encryption_password() -> Zeroizing; + fn read_password(_confirm: bool) -> Zeroizing { + panic!("attempted to prompt for password in non-interactive mode"); + } /// Read an alias from the file/env/stdin. - fn read_alias(prompt_msg: &str) -> String; + fn read_alias(_prompt_msg: &str) -> String { + panic!("attempted to prompt for alias in non-interactive mode"); + } /// Read mnemonic code from the file/env/stdin. - fn read_mnemonic_code() -> Result; + fn read_mnemonic_code() -> Result { + panic!("attempted to prompt for alias in non-interactive mode"); + } /// Read a mnemonic code from the file/env/stdin. - fn read_mnemonic_passphrase(confirm: bool) -> Zeroizing; + fn read_mnemonic_passphrase(_confirm: bool) -> Zeroizing { + panic!("attempted to prompt for alias in non-interactive mode"); + } /// The given alias has been selected but conflicts with another alias in /// the store. Offer the user to either replace existing mapping, alter the /// chosen alias to a name of their choice, or cancel the aliasing. fn show_overwrite_confirmation( - alias: &Alias, - alias_for: &str, - ) -> store::ConfirmationResponse; + _alias: &Alias, + _alias_for: &str, + ) -> store::ConfirmationResponse { + // Automatically replace aliases in non-interactive mode + store::ConfirmationResponse::Replace + } +} + +/// Errors of wallet loading and storing +#[derive(Error, Debug)] +pub enum LoadStoreError { + /// Wallet store decoding error + #[error("Failed decoding the wallet store: {0}")] + Decode(toml::de::Error), + /// Wallet store reading error + #[error("Failed to read the wallet store from {0}: {1}")] + ReadWallet(String, String), + /// Wallet store writing error + #[error("Failed to write the wallet store: {0}")] + StoreNewWallet(String), +} + +/// Captures the permanent storage parts of the wallet's functioning +pub trait WalletStorage: Sized + Clone { + /// Save the wallet store to a file. + fn save(&self, wallet: &Wallet) -> Result<(), LoadStoreError>; + + /// Load a wallet from the store file. + fn load(&self, wallet: &mut Wallet) -> Result<(), LoadStoreError>; +} + +#[cfg(feature = "std")] +/// Implementation of wallet functionality depending on a standard filesystem +pub mod fs { + use super::*; + use std::fs; + use fd_lock::RwLock; + use std::path::PathBuf; + use rand_core::OsRng; + use std::io::{Read, Write}; + + /// A trait for deriving WalletStorage for standard filesystems + pub trait FsWalletStorage: Clone { + /// The directory in which the wallet is supposed to be stored + fn store_dir(&self) -> &PathBuf; + } + + /// Wallet file name + const FILE_NAME: &str = "wallet.toml"; + + impl WalletStorage for F { + fn save(&self, wallet: &Wallet) -> Result<(), LoadStoreError> { + let data = wallet.store.encode(); + let wallet_path = self.store_dir().join(FILE_NAME); + // Make sure the dir exists + let wallet_dir = wallet_path.parent().unwrap(); + fs::create_dir_all(wallet_dir) + .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?; + // Write the file + let mut options = fs::OpenOptions::new(); + options.create(true).write(true).truncate(true); + let mut lock = RwLock::new( + options.open(wallet_path) + .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?, + ); + let mut guard = lock.write() + .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?; + guard.write_all(&data).map_err(|err| LoadStoreError::StoreNewWallet(err.to_string())) + } + + fn load(&self, wallet: &mut Wallet) -> Result<(), LoadStoreError> { + let wallet_file = self.store_dir().join(FILE_NAME); + let mut options = fs::OpenOptions::new(); + options.read(true).write(false); + let lock = RwLock::new(options.open(&wallet_file).map_err(|err| { + LoadStoreError::ReadWallet( + wallet_file.to_string_lossy().into_owned(), + err.to_string(), + ) + })?); + let guard = lock.read().map_err(|err| { + LoadStoreError::ReadWallet( + wallet_file.to_string_lossy().into_owned(), + err.to_string(), + ) + })?; + let mut store = Vec::::new(); + (&*guard).read_to_end(&mut store).map_err(|err| { + LoadStoreError::ReadWallet( + self.store_dir().to_str().unwrap().parse().unwrap(), + err.to_string(), + ) + })?; + wallet.store = Store::decode(store).map_err(LoadStoreError::Decode)?; + Ok(()) + } + } + + /// For a non-interactive filesystem based wallet + #[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] + pub struct FsWalletUtils { + #[borsh_skip] + store_dir: PathBuf, + } + + impl FsWalletUtils { + /// Initialize a wallet at the given directory + pub fn new(store_dir: PathBuf) -> Wallet { + Wallet::new(Self { store_dir }, Store::default()) + } + } + + impl WalletIo for FsWalletUtils { + type Rng = OsRng; + } + + impl FsWalletStorage for FsWalletUtils { + fn store_dir(&self) -> &PathBuf { + &self.store_dir + } + } } /// The error that is produced when a given key cannot be obtained @@ -105,24 +227,217 @@ pub enum FindKeyError { /// Represents a collection of keys and addresses while caching key decryptions #[derive(Debug)] -pub struct Wallet { - store_dir: U::Storage, +pub struct Wallet { + /// Location where this shielded context is saved + utils: U, store: Store, decrypted_key_cache: HashMap, decrypted_spendkey_cache: HashMap, } -impl Wallet { +impl From> for Store { + fn from(wallet: Wallet) -> Self { + wallet.store + } +} + +impl Wallet { /// Create a new wallet from the given backing store and storage location - pub fn new(store_dir: U::Storage, store: Store) -> Self { + pub fn new(utils: U, store: Store) -> Self { Self { - store_dir, + utils, store, decrypted_key_cache: HashMap::default(), decrypted_spendkey_cache: HashMap::default(), } } + /// Add validator data to the store + pub fn add_validator_data( + &mut self, + address: Address, + keys: ValidatorKeys, + ) { + self.store.add_validator_data(address, keys); + } + + /// Returns a reference to the validator data, if it exists. + pub fn get_validator_data(&self) -> Option<&ValidatorData> { + self.store.get_validator_data() + } + + /// Returns a mut reference to the validator data, if it exists. + pub fn get_validator_data_mut(&mut self) -> Option<&mut ValidatorData> { + self.store.get_validator_data_mut() + } + + /// Take the validator data, if it exists. + pub fn take_validator_data(&mut self) -> Option { + self.store.take_validator_data() + } + + /// Returns the validator data, if it exists. + pub fn into_validator_data(self) -> Option { + self.store.into_validator_data() + } + + /// Provide immutable access to the backing store + pub fn store(&self) -> &Store { + &self.store + } + + /// Provide mutable access to the backing store + pub fn store_mut(&mut self) -> &mut Store { + &mut self.store + } + + /// Extend this wallet from pre-genesis validator wallet. + pub fn extend_from_pre_genesis_validator( + &mut self, + validator_address: Address, + validator_alias: Alias, + other: pre_genesis::ValidatorWallet, + ) { + self.store.extend_from_pre_genesis_validator( + validator_address, + validator_alias, + other, + ) + } + + /// Gets all addresses given a vp_type + pub fn get_addresses_with_vp_type( + &self, + vp_type: AddressVpType, + ) -> HashSet
{ + self.store.get_addresses_with_vp_type(vp_type) + } + + /// Add a vp_type to a given address + pub fn add_vp_type_to_address( + &mut self, + vp_type: AddressVpType, + address: Address, + ) { + // defaults to an empty set + self.store.add_vp_type_to_address(vp_type, address) + } + + /// Get addresses with tokens VP type keyed and ordered by their aliases. + pub fn tokens_with_aliases(&self) -> BTreeMap { + self.get_addresses_with_vp_type(AddressVpType::Token) + .into_iter() + .map(|addr| { + let alias = self.lookup_alias(&addr); + (alias, addr) + }) + .collect() + } + + /// Find the stored address by an alias. + pub fn find_address(&self, alias: impl AsRef) -> Option<&Address> { + self.store.find_address(alias) + } + + /// Find an alias by the address if it's in the wallet. + pub fn find_alias(&self, address: &Address) -> Option<&Alias> { + self.store.find_alias(address) + } + + /// Try to find an alias for a given address from the wallet. If not found, + /// formats the address into a string. + pub fn lookup_alias(&self, addr: &Address) -> String { + match self.find_alias(addr) { + Some(alias) => format!("{}", alias), + None => format!("{}", addr), + } + } + + /// Find the viewing key with the given alias in the wallet and return it + pub fn find_viewing_key( + &mut self, + alias: impl AsRef, + ) -> Result<&ExtendedViewingKey, FindKeyError> { + self.store + .find_viewing_key(alias.as_ref()) + .ok_or(FindKeyError::KeyNotFound) + } + + /// Find the payment address with the given alias in the wallet and return + /// it + pub fn find_payment_addr( + &self, + alias: impl AsRef, + ) -> Option<&PaymentAddress> { + self.store.find_payment_addr(alias.as_ref()) + } + + /// Get all known keys by their alias, paired with PKH, if known. + pub fn get_keys( + &self, + ) -> HashMap< + String, + (&StoredKeypair, Option<&PublicKeyHash>), + > { + self.store + .get_keys() + .into_iter() + .map(|(alias, value)| (alias.into(), value)) + .collect() + } + + /// Get all known addresses by their alias, paired with PKH, if known. + pub fn get_addresses(&self) -> HashMap { + self.store + .get_addresses() + .iter() + .map(|(alias, value)| (alias.into(), value.clone())) + .collect() + } + + /// Get all known payment addresses by their alias + pub fn get_payment_addrs(&self) -> HashMap { + self.store + .get_payment_addrs() + .iter() + .map(|(alias, value)| (alias.into(), *value)) + .collect() + } + + /// Get all known viewing keys by their alias + pub fn get_viewing_keys(&self) -> HashMap { + self.store + .get_viewing_keys() + .iter() + .map(|(alias, value)| (alias.into(), *value)) + .collect() + } + + /// Get all known viewing keys by their alias + pub fn get_spending_keys( + &self, + ) -> HashMap> { + self.store + .get_spending_keys() + .iter() + .map(|(alias, value)| (alias.into(), value)) + .collect() + } +} + +impl Wallet { + /// Load a wallet from the store file. + pub fn load(&mut self) -> Result<(), LoadStoreError> { + self.utils.clone().load(self) + } + + /// Save the wallet store to a file. + pub fn save(&self) -> Result<(), LoadStoreError> { + self.utils.save(self) + } +} + +impl Wallet { fn gen_and_store_key( &mut self, scheme: SchemeType, @@ -161,6 +476,7 @@ impl Wallet { alias: Option, alias_force: bool, derivation_path: Option, + mnemonic_passphrase: Option<(Mnemonic, Zeroizing)>, password: Option>, ) -> Result, GenRestoreKeyError> { let parsed_derivation_path = derivation_path @@ -182,8 +498,12 @@ impl Wallet { ) } println!("Using HD derivation path {}", parsed_derivation_path); - let mnemonic = U::read_mnemonic_code()?; - let passphrase = U::read_mnemonic_passphrase(false); + let (mnemonic, passphrase) = + if let Some(mnemonic_passphrase) = mnemonic_passphrase { + mnemonic_passphrase + } else { + (U::read_mnemonic_code()?, U::read_mnemonic_passphrase(false)) + }; let seed = Seed::new(&mnemonic, &passphrase); Ok(self.gen_and_store_key( @@ -212,9 +532,10 @@ impl Wallet { scheme: SchemeType, alias: Option, alias_force: bool, + passphrase: Option>, password: Option>, derivation_path_and_mnemonic_rng: Option<(String, &mut U::Rng)>, - ) -> Result, GenRestoreKeyError> { + ) -> Result<(String, common::SecretKey, Option), GenRestoreKeyError> { let parsed_path_and_rng = derivation_path_and_mnemonic_rng .map(|(raw_derivation_path, rng)| { let is_default = @@ -242,27 +563,31 @@ impl Wallet { println!("Using HD derivation path {}", parsed_derivation_path); } + let mut mnemonic_opt = None; let seed_and_derivation_path //: Option> = parsed_path_and_rng.map(|(path, rng)| { const MNEMONIC_TYPE: MnemonicType = MnemonicType::Words24; - let mnemonic = U::generate_mnemonic_code(MNEMONIC_TYPE, rng)?; + let mnemonic = mnemonic_opt + .insert(U::generate_mnemonic_code(MNEMONIC_TYPE, rng)?); println!( "Safely store your {} words mnemonic.", MNEMONIC_TYPE.word_count() ); println!("{}", mnemonic.clone().into_phrase()); - let passphrase = U::read_mnemonic_passphrase(true); - Ok((Seed::new(&mnemonic, &passphrase), path)) + let passphrase = passphrase + .unwrap_or_else(|| U::read_mnemonic_passphrase(true)); + Ok((Seed::new(mnemonic, &passphrase), path)) }).transpose()?; - Ok(self.gen_and_store_key( + let (alias, key) = self.gen_and_store_key( scheme, alias, alias_force, seed_and_derivation_path, password, - )) + ).ok_or(GenRestoreKeyError::KeyStorageError)?; + Ok((alias, key, mnemonic_opt)) } /// Generate a disposable signing key for fee payment and store it under the @@ -280,10 +605,9 @@ impl Wallet { // Generate a disposable keypair to sign the wrapper if requested // TODO: once the wrapper transaction has been accepted, this key can be // deleted from wallet - let (alias, disposable_keypair) = self - .gen_key(SchemeType::Ed25519, Some(alias), false, None, None) - .expect("Failed to initialize disposable keypair") - .expect("Missing alias and secret key"); + let (alias, disposable_keypair, _mnemonic) = self + .gen_key(SchemeType::Ed25519, Some(alias), false, None, None, None) + .expect("Failed to initialize disposable keypair"); println!("Created disposable keypair with alias {alias}"); disposable_keypair @@ -304,35 +628,6 @@ impl Wallet { (alias.into(), key) } - /// Add validator data to the store - pub fn add_validator_data( - &mut self, - address: Address, - keys: ValidatorKeys, - ) { - self.store.add_validator_data(address, keys); - } - - /// Returns a reference to the validator data, if it exists. - pub fn get_validator_data(&self) -> Option<&ValidatorData> { - self.store.get_validator_data() - } - - /// Returns a mut reference to the validator data, if it exists. - pub fn get_validator_data_mut(&mut self) -> Option<&mut ValidatorData> { - self.store.get_validator_data_mut() - } - - /// Take the validator data, if it exists. - pub fn take_validator_data(&mut self) -> Option { - self.store.take_validator_data() - } - - /// Returns the validator data, if it exists. - pub fn into_validator_data(self) -> Option { - self.store.into_validator_data() - } - /// Find the stored key by an alias, a public key hash or a public key. /// If the key is encrypted and password not supplied, then password will be /// interactively prompted. Any keys that are decrypted are stored in and @@ -389,25 +684,6 @@ impl Wallet { ) } - /// Find the viewing key with the given alias in the wallet and return it - pub fn find_viewing_key( - &mut self, - alias: impl AsRef, - ) -> Result<&ExtendedViewingKey, FindKeyError> { - self.store - .find_viewing_key(alias.as_ref()) - .ok_or(FindKeyError::KeyNotFound) - } - - /// Find the payment address with the given alias in the wallet and return - /// it - pub fn find_payment_addr( - &self, - alias: impl AsRef, - ) -> Option<&PaymentAddress> { - self.store.find_payment_addr(alias.as_ref()) - } - /// Find the stored key by a public key. /// If the key is encrypted and password not supplied, then password will be /// interactively prompted for. Any keys that are decrypted are stored in @@ -463,7 +739,7 @@ impl Wallet { .store .find_key_by_pkh(pkh) .ok_or(FindKeyError::KeyNotFound)?; - Self::decrypt_stored_key::<_>( + Self::decrypt_stored_key( &mut self.decrypted_key_cache, stored_key, alias, @@ -489,7 +765,7 @@ impl Wallet { match stored_key { StoredKeypair::Encrypted(encrypted) => { let password = - password.unwrap_or_else(U::read_decryption_password); + password.unwrap_or_else(|| U::read_password(false)); let key = encrypted .decrypt(password) .map_err(FindKeyError::KeyDecryptionError)?; @@ -503,77 +779,6 @@ impl Wallet { } } - /// Get all known keys by their alias, paired with PKH, if known. - pub fn get_keys( - &self, - ) -> HashMap< - String, - (&StoredKeypair, Option<&PublicKeyHash>), - > { - self.store - .get_keys() - .into_iter() - .map(|(alias, value)| (alias.into(), value)) - .collect() - } - - /// Find the stored address by an alias. - pub fn find_address(&self, alias: impl AsRef) -> Option<&Address> { - self.store.find_address(alias) - } - - /// Find an alias by the address if it's in the wallet. - pub fn find_alias(&self, address: &Address) -> Option<&Alias> { - self.store.find_alias(address) - } - - /// Try to find an alias for a given address from the wallet. If not found, - /// formats the address into a string. - pub fn lookup_alias(&self, addr: &Address) -> String { - match self.find_alias(addr) { - Some(alias) => format!("{}", alias), - None => format!("{}", addr), - } - } - - /// Get all known addresses by their alias, paired with PKH, if known. - pub fn get_addresses(&self) -> HashMap { - self.store - .get_addresses() - .iter() - .map(|(alias, value)| (alias.into(), value.clone())) - .collect() - } - - /// Get all known payment addresses by their alias - pub fn get_payment_addrs(&self) -> HashMap { - self.store - .get_payment_addrs() - .iter() - .map(|(alias, value)| (alias.into(), *value)) - .collect() - } - - /// Get all known viewing keys by their alias - pub fn get_viewing_keys(&self) -> HashMap { - self.store - .get_viewing_keys() - .iter() - .map(|(alias, value)| (alias.into(), *value)) - .collect() - } - - /// Get all known viewing keys by their alias - pub fn get_spending_keys( - &self, - ) -> HashMap> { - self.store - .get_spending_keys() - .iter() - .map(|(alias, value)| (alias.into(), value)) - .collect() - } - /// Add a new address with the given alias. If the alias is already used, /// will ask whether the existing alias should be replaced, a different /// alias is desired, or the alias creation should be cancelled. Return @@ -664,62 +869,4 @@ impl Wallet { .insert_payment_addr::(alias.into(), payment_addr, force_alias) .map(Into::into) } - - /// Extend this wallet from pre-genesis validator wallet. - pub fn extend_from_pre_genesis_validator( - &mut self, - validator_address: Address, - validator_alias: Alias, - other: pre_genesis::ValidatorWallet, - ) { - self.store.extend_from_pre_genesis_validator( - validator_address, - validator_alias, - other, - ) - } - - /// Gets all addresses given a vp_type - pub fn get_addresses_with_vp_type( - &self, - vp_type: AddressVpType, - ) -> HashSet
{ - self.store.get_addresses_with_vp_type(vp_type) - } - - /// Add a vp_type to a given address - pub fn add_vp_type_to_address( - &mut self, - vp_type: AddressVpType, - address: Address, - ) { - // defaults to an empty set - self.store.add_vp_type_to_address(vp_type, address) - } - - /// Provide immutable access to the backing store - pub fn store(&self) -> &Store { - &self.store - } - - /// Provide mutable access to the backing store - pub fn store_mut(&mut self) -> &mut Store { - &mut self.store - } - - /// Access storage location data - pub fn store_dir(&self) -> &U::Storage { - &self.store_dir - } - - /// Get addresses with tokens VP type keyed and ordered by their aliases. - pub fn tokens_with_aliases(&self) -> BTreeMap { - self.get_addresses_with_vp_type(AddressVpType::Token) - .into_iter() - .map(|addr| { - let alias = self.lookup_alias(&addr); - (alias, addr) - }) - .collect() - } } diff --git a/shared/src/sdk/wallet/store.rs b/shared/src/sdk/wallet/store.rs index 509ff5afe6..b674391127 100644 --- a/shared/src/sdk/wallet/store.rs +++ b/shared/src/sdk/wallet/store.rs @@ -17,7 +17,7 @@ use zeroize::Zeroizing; use super::alias::{self, Alias}; use super::derivation_path::DerivationPath; use super::pre_genesis; -use crate::sdk::wallet::{StoredKeypair, WalletUtils}; +use crate::sdk::wallet::{StoredKeypair, WalletIo}; use crate::types::address::{Address, ImplicitAddress}; use crate::types::key::dkg_session_keys::DkgKeypair; use crate::types::key::*; @@ -239,7 +239,7 @@ impl Store { /// key. /// Returns None if the alias already exists and the user decides to skip /// it. No changes in the wallet store are made. - pub fn gen_key( + pub fn gen_key( &mut self, scheme: SchemeType, alias: Option, @@ -277,7 +277,7 @@ impl Store { } /// Generate a spending key similarly to how it's done for keypairs - pub fn gen_spending_key( + pub fn gen_spending_key( &mut self, alias: String, password: Option>, @@ -335,7 +335,7 @@ impl Store { /// will prompt for overwrite/reselection confirmation. If declined, then /// keypair is not inserted and nothing is returned, otherwise selected /// alias is returned. - pub fn insert_keypair( + pub fn insert_keypair( &mut self, alias: Alias, keypair: StoredKeypair, @@ -388,7 +388,7 @@ impl Store { } /// Insert spending keys similarly to how it's done for keypairs - pub fn insert_spending_key( + pub fn insert_spending_key( &mut self, alias: Alias, spendkey: StoredKeypair, @@ -418,7 +418,7 @@ impl Store { } /// Insert viewing keys similarly to how it's done for keypairs - pub fn insert_viewing_key( + pub fn insert_viewing_key( &mut self, alias: Alias, viewkey: ExtendedViewingKey, @@ -463,7 +463,7 @@ impl Store { } /// Insert payment addresses similarly to how it's done for keypairs - pub fn insert_payment_addr( + pub fn insert_payment_addr( &mut self, alias: Alias, payment_addr: PaymentAddress, @@ -507,7 +507,7 @@ impl Store { /// will prompt for overwrite/reselection confirmation, which when declined, /// the address won't be added. Return the selected alias if the address has /// been added. - pub fn insert_address( + pub fn insert_address( &mut self, alias: Alias, address: Address, From f99eaa3e09e6b194a45c39961257c19fb23c303f Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 1 Oct 2023 03:42:32 +0200 Subject: [PATCH 06/14] Made the ShieldedUtils trait similar to the WalletStorage trait. --- apps/src/lib/cli/wallet.rs | 18 ++++---- apps/src/lib/client/utils.rs | 27 ++++++++++-- apps/src/lib/wallet/cli_utils.rs | 18 ++++---- apps/src/lib/wallet/mod.rs | 5 ++- apps/src/lib/wallet/store.rs | 3 +- benches/lib.rs | 15 +++---- shared/src/sdk/masp.rs | 36 +++++++++------- shared/src/sdk/signing.rs | 5 +-- shared/src/sdk/wallet/keys.rs | 4 +- shared/src/sdk/wallet/mod.rs | 70 +++++++++++++++++++------------- 10 files changed, 119 insertions(+), 82 deletions(-) diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 09d66096af..33b443edd9 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -403,16 +403,14 @@ fn key_and_address_gen( encryption_password, derivation_path_and_mnemonic_rng, ) - .unwrap_or_else(|err| { - match err { - GenRestoreKeyError::KeyStorageError => { - println!("No changes are persisted. Exiting."); - cli::safe_exit(0); - }, - _ => { - eprintln!("{}", err); - cli::safe_exit(1); - } + .unwrap_or_else(|err| match err { + GenRestoreKeyError::KeyStorageError => { + println!("No changes are persisted. Exiting."); + cli::safe_exit(0); + } + _ => { + eprintln!("{}", err); + cli::safe_exit(1); } }); crate::wallet::save(&wallet) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 7e243fd502..f508267db3 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -506,7 +506,14 @@ pub fn init_network( let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let (_alias, keypair, _mnemonic) = wallet - .gen_key(SchemeType::Ed25519, Some(alias), true, None, password, None) + .gen_key( + SchemeType::Ed25519, + Some(alias), + true, + None, + password, + None, + ) .expect("Key generation should not fail."); // Write consensus key for Tendermint @@ -525,7 +532,14 @@ pub fn init_network( let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let (_alias, keypair, _mnemonic) = wallet - .gen_key(SchemeType::Ed25519, Some(alias), true, None, password, None) + .gen_key( + SchemeType::Ed25519, + Some(alias), + true, + None, + password, + None, + ) .expect("Key generation should not fail."); keypair.ref_to() }); @@ -540,7 +554,14 @@ pub fn init_network( let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let (_alias, keypair, _mnemonic) = wallet - .gen_key(SchemeType::Ed25519, Some(alias), true, None, password, None) + .gen_key( + SchemeType::Ed25519, + Some(alias), + true, + None, + password, + None, + ) .expect("Key generation should not fail."); keypair.ref_to() }); diff --git a/apps/src/lib/wallet/cli_utils.rs b/apps/src/lib/wallet/cli_utils.rs index fb288df193..ada1b16684 100644 --- a/apps/src/lib/wallet/cli_utils.rs +++ b/apps/src/lib/wallet/cli_utils.rs @@ -316,16 +316,14 @@ pub fn key_and_address_gen( encryption_password, derivation_path_and_mnemonic_rng, ) - .unwrap_or_else(|err| { - match err { - GenRestoreKeyError::KeyStorageError => { - println!("No changes are persisted. Exiting."); - cli::safe_exit(0); - }, - _ => { - eprintln!("{}", err); - cli::safe_exit(1); - } + .unwrap_or_else(|err| match err { + GenRestoreKeyError::KeyStorageError => { + println!("No changes are persisted. Exiting."); + cli::safe_exit(0); + } + _ => { + eprintln!("{}", err); + cli::safe_exit(1); } }); crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 01967a5c24..cca43a43d6 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -66,7 +66,7 @@ impl WalletIo for CliWalletUtils { cli::safe_exit(1) }, ) - }, + } Err(_) => { let prompt = "Enter your decryption password: "; rpassword::read_password_from_tty(Some(prompt)) @@ -258,7 +258,8 @@ pub fn add_genesis_addresses( /// Save the wallet store to a file. pub fn save(wallet: &Wallet) -> std::io::Result<()> { - wallet.save() + wallet + .save() .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) } diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 24c62e4866..c035925160 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -8,12 +8,11 @@ use ark_std::rand::SeedableRng; use namada::sdk::wallet::store::AddressVpType; #[cfg(feature = "dev")] use namada::sdk::wallet::StoredKeypair; -use namada::sdk::wallet::{gen_sk_rng, Store, ValidatorKeys}; +use namada::sdk::wallet::{gen_sk_rng, LoadStoreError, Store, ValidatorKeys}; #[cfg(not(feature = "dev"))] use namada::types::address::Address; use namada::types::key::*; use namada::types::transaction::EllipticCurve; -use namada::sdk::wallet::LoadStoreError; use crate::config::genesis::genesis_config::GenesisConfig; use crate::wallet::CliWalletUtils; diff --git a/benches/lib.rs b/benches/lib.rs index a816451477..5ea8b091ee 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -586,22 +586,23 @@ impl ShieldedUtils for BenchShieldedUtils { /// Try to load the last saved shielded context from the given context /// directory. If this fails, then leave the current context unchanged. - async fn load(self) -> std::io::Result> { + async fn load(&self, ctx: &mut ShieldedContext) -> std::io::Result<()> { // Try to load shielded context from file let mut ctx_file = File::open( self.context_dir.0.path().to_path_buf().join(FILE_NAME), )?; let mut bytes = Vec::new(); ctx_file.read_to_end(&mut bytes)?; - let mut new_ctx = ShieldedContext::deserialize(&mut &bytes[..])?; - // Associate the originating context directory with the - // shielded context under construction - new_ctx.utils = self; - Ok(new_ctx) + // Fill the supplied context with the deserialized object + *ctx = ShieldedContext { + utils: ctx.utils.clone(), + ..ShieldedContext::deserialize(&mut &bytes[..])? + }; + Ok(()) } /// Save this shielded context into its associated context directory - async fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()> { + async fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()> { let tmp_path = self.context_dir.0.path().to_path_buf().join(TMP_FILE_NAME); { diff --git a/shared/src/sdk/masp.rs b/shared/src/sdk/masp.rs index 1cd9ae7e5b..7959916944 100644 --- a/shared/src/sdk/masp.rs +++ b/shared/src/sdk/masp.rs @@ -397,10 +397,16 @@ pub trait ShieldedUtils: fn local_tx_prover(&self) -> LocalTxProver; /// Load up the currently saved ShieldedContext - async fn load(self) -> std::io::Result>; + async fn load( + &self, + ctx: &mut ShieldedContext, + ) -> std::io::Result<()>; - /// Sace the given ShieldedContext for future loads - async fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()>; + /// Save the given ShieldedContext for future loads + async fn save( + &self, + ctx: &ShieldedContext, + ) -> std::io::Result<()>; } /// Make a ViewingKey that can view notes encrypted by given ExtendedSpendingKey @@ -621,9 +627,7 @@ impl ShieldedContext { /// Try to load the last saved shielded context from the given context /// directory. If this fails, then leave the current context unchanged. pub async fn load(&mut self) -> std::io::Result<()> { - let new_ctx = self.utils.clone().load().await?; - *self = new_ctx; - Ok(()) + self.utils.clone().load(self).await } /// Save this shielded context into its associated context directory @@ -2189,22 +2193,26 @@ pub mod fs { /// Try to load the last saved shielded context from the given context /// directory. If this fails, then leave the current context unchanged. - async fn load(self) -> std::io::Result> { + async fn load( + &self, + ctx: &mut ShieldedContext, + ) -> std::io::Result<()> { // Try to load shielded context from file let mut ctx_file = File::open(self.context_dir.join(FILE_NAME))?; let mut bytes = Vec::new(); ctx_file.read_to_end(&mut bytes)?; - let mut new_ctx = ShieldedContext::deserialize(&mut &bytes[..])?; - // Associate the originating context directory with the - // shielded context under construction - new_ctx.utils = self; - Ok(new_ctx) + // Fill the supplied context with the deserialized object + *ctx = ShieldedContext { + utils: ctx.utils.clone(), + ..ShieldedContext::::deserialize(&mut &bytes[..])? + }; + Ok(()) } /// Save this shielded context into its associated context directory - async fn save( + async fn save( &self, - ctx: &ShieldedContext, + ctx: &ShieldedContext, ) -> std::io::Result<()> { // TODO: use mktemp crate? let tmp_path = self.context_dir.join(TMP_FILE_NAME); diff --git a/shared/src/sdk/signing.rs b/shared/src/sdk/signing.rs index 24f3b12c7b..9d534217a4 100644 --- a/shared/src/sdk/signing.rs +++ b/shared/src/sdk/signing.rs @@ -83,10 +83,7 @@ pub struct SigningTxData { /// for it from the wallet. If the keypair is encrypted but a password is not /// supplied, then it is interactively prompted. Errors if the key cannot be /// found or loaded. -pub async fn find_pk< - C: crate::ledger::queries::Client + Sync, - U: WalletIo, ->( +pub async fn find_pk( client: &C, wallet: &mut Wallet, addr: &Address, diff --git a/shared/src/sdk/wallet/keys.rs b/shared/src/sdk/wallet/keys.rs index 6b10352c8b..749fa1e25f 100644 --- a/shared/src/sdk/wallet/keys.rs +++ b/shared/src/sdk/wallet/keys.rs @@ -174,8 +174,8 @@ where match self { StoredKeypair::Encrypted(encrypted_keypair) => { if decrypt { - let password = password - .unwrap_or_else(|| U::read_password(false)); + let password = + password.unwrap_or_else(|| U::read_password(false)); let key = encrypted_keypair.decrypt(password)?; Ok(key) } else { diff --git a/shared/src/sdk/wallet/mod.rs b/shared/src/sdk/wallet/mod.rs index 580b2db5a1..cadfdc718c 100644 --- a/shared/src/sdk/wallet/mod.rs +++ b/shared/src/sdk/wallet/mod.rs @@ -125,12 +125,14 @@ pub trait WalletStorage: Sized + Clone { #[cfg(feature = "std")] /// Implementation of wallet functionality depending on a standard filesystem pub mod fs { - use super::*; use std::fs; - use fd_lock::RwLock; + use std::io::{Read, Write}; use std::path::PathBuf; + + use fd_lock::RwLock; use rand_core::OsRng; - use std::io::{Read, Write}; + + use super::*; /// A trait for deriving WalletStorage for standard filesystems pub trait FsWalletStorage: Clone { @@ -147,30 +149,38 @@ pub mod fs { let wallet_path = self.store_dir().join(FILE_NAME); // Make sure the dir exists let wallet_dir = wallet_path.parent().unwrap(); - fs::create_dir_all(wallet_dir) - .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?; + fs::create_dir_all(wallet_dir).map_err(|err| { + LoadStoreError::StoreNewWallet(err.to_string()) + })?; // Write the file let mut options = fs::OpenOptions::new(); options.create(true).write(true).truncate(true); - let mut lock = RwLock::new( - options.open(wallet_path) - .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?, - ); - let mut guard = lock.write() - .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?; - guard.write_all(&data).map_err(|err| LoadStoreError::StoreNewWallet(err.to_string())) + let mut lock = + RwLock::new(options.open(wallet_path).map_err(|err| { + LoadStoreError::StoreNewWallet(err.to_string()) + })?); + let mut guard = lock.write().map_err(|err| { + LoadStoreError::StoreNewWallet(err.to_string()) + })?; + guard + .write_all(&data) + .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string())) } - fn load(&self, wallet: &mut Wallet) -> Result<(), LoadStoreError> { + fn load( + &self, + wallet: &mut Wallet, + ) -> Result<(), LoadStoreError> { let wallet_file = self.store_dir().join(FILE_NAME); let mut options = fs::OpenOptions::new(); options.read(true).write(false); - let lock = RwLock::new(options.open(&wallet_file).map_err(|err| { - LoadStoreError::ReadWallet( - wallet_file.to_string_lossy().into_owned(), - err.to_string(), - ) - })?); + let lock = + RwLock::new(options.open(&wallet_file).map_err(|err| { + LoadStoreError::ReadWallet( + wallet_file.to_string_lossy().into_owned(), + err.to_string(), + ) + })?); let guard = lock.read().map_err(|err| { LoadStoreError::ReadWallet( wallet_file.to_string_lossy().into_owned(), @@ -184,7 +194,8 @@ pub mod fs { err.to_string(), ) })?; - wallet.store = Store::decode(store).map_err(LoadStoreError::Decode)?; + wallet.store = + Store::decode(store).map_err(LoadStoreError::Decode)?; Ok(()) } } @@ -535,7 +546,8 @@ impl Wallet { passphrase: Option>, password: Option>, derivation_path_and_mnemonic_rng: Option<(String, &mut U::Rng)>, - ) -> Result<(String, common::SecretKey, Option), GenRestoreKeyError> { + ) -> Result<(String, common::SecretKey, Option), GenRestoreKeyError> + { let parsed_path_and_rng = derivation_path_and_mnemonic_rng .map(|(raw_derivation_path, rng)| { let is_default = @@ -580,13 +592,15 @@ impl Wallet { Ok((Seed::new(mnemonic, &passphrase), path)) }).transpose()?; - let (alias, key) = self.gen_and_store_key( - scheme, - alias, - alias_force, - seed_and_derivation_path, - password, - ).ok_or(GenRestoreKeyError::KeyStorageError)?; + let (alias, key) = self + .gen_and_store_key( + scheme, + alias, + alias_force, + seed_and_derivation_path, + password, + ) + .ok_or(GenRestoreKeyError::KeyStorageError)?; Ok((alias, key, mnemonic_opt)) } From 79f006aed09501f96a21a611e6693fb0a2a52937 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 1 Oct 2023 04:57:10 +0200 Subject: [PATCH 07/14] Added function to construct DenominatedAmounts from Amounts. --- benches/lib.rs | 10 ++++++++-- shared/src/sdk/args.rs | 6 ++++++ shared/src/sdk/rpc.rs | 24 +++++++++++++++++------- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/benches/lib.rs b/benches/lib.rs index 5ea8b091ee..95c722de77 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -586,7 +586,10 @@ impl ShieldedUtils for BenchShieldedUtils { /// Try to load the last saved shielded context from the given context /// directory. If this fails, then leave the current context unchanged. - async fn load(&self, ctx: &mut ShieldedContext) -> std::io::Result<()> { + async fn load( + &self, + ctx: &mut ShieldedContext, + ) -> std::io::Result<()> { // Try to load shielded context from file let mut ctx_file = File::open( self.context_dir.0.path().to_path_buf().join(FILE_NAME), @@ -602,7 +605,10 @@ impl ShieldedUtils for BenchShieldedUtils { } /// Save this shielded context into its associated context directory - async fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()> { + async fn save( + &self, + ctx: &ShieldedContext, + ) -> std::io::Result<()> { let tmp_path = self.context_dir.0.path().to_path_buf().join(TMP_FILE_NAME); { diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs index 8a1d0d81b0..cf43d5dbd7 100644 --- a/shared/src/sdk/args.rs +++ b/shared/src/sdk/args.rs @@ -211,6 +211,12 @@ impl std::str::FromStr for InputAmount { } } +impl From for InputAmount { + fn from(amt: token::DenominatedAmount) -> Self { + InputAmount::Unvalidated(amt) + } +} + /// Transfer transaction arguments #[derive(Clone, Debug)] pub struct TxTransfer { diff --git a/shared/src/sdk/rpc.rs b/shared/src/sdk/rpc.rs index cb0e55835a..8a40d8a8dd 100644 --- a/shared/src/sdk/rpc.rs +++ b/shared/src/sdk/rpc.rs @@ -1058,15 +1058,13 @@ where .try_halt(|_| ()) } -/// Look up the denomination of a token in order to format it -/// correctly as a string. -pub async fn format_denominated_amount< - C: crate::ledger::queries::Client + Sync, ->( +/// Look up the denomination of a token in order to make a correctly denominated +/// amount. +pub async fn denominate_amount( client: &C, token: &Address, amount: token::Amount, -) -> String { +) -> DenominatedAmount { let denom = convert_response::>( RPC.vp().token().denomination(client, token).await, ) @@ -1082,5 +1080,17 @@ pub async fn format_denominated_amount< ); 0.into() }); - DenominatedAmount { amount, denom }.to_string() + DenominatedAmount { amount, denom } +} + +/// Look up the denomination of a token in order to format it +/// correctly as a string. +pub async fn format_denominated_amount< + C: crate::ledger::queries::Client + Sync, +>( + client: &C, + token: &Address, + amount: token::Amount, +) -> String { + denominate_amount(client, token, amount).await.to_string() } From c775ec720c94da3f119b2ea2457d819c1dcc8c38 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 1 Oct 2023 12:28:31 +0200 Subject: [PATCH 08/14] Removed unnecessary requirement for mutable references in Namada trait. --- shared/src/ledger/mod.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index e952c99a08..59397be235 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -82,7 +82,7 @@ pub trait Namada<'a>: type ShieldedUtils: 'a + ShieldedUtils; /// Return the native token - fn native_token(&mut self) -> Address { + fn native_token(&self) -> Address { self.wallet .find_address(args::NAM) .expect("NAM not in wallet") @@ -90,7 +90,7 @@ pub trait Namada<'a>: } /// Make a tx builder using no arguments - fn tx_builder(&mut self) -> args::Tx { + fn tx_builder(&self) -> args::Tx { args::Tx { dry_run: false, dry_run_wrapper: false, @@ -119,7 +119,7 @@ pub trait Namada<'a>: /// Make a TxTransfer builder from the given minimum set of arguments fn new_transfer( - &mut self, + &self, source: TransferSource, target: TransferTarget, token: Address, @@ -138,7 +138,7 @@ pub trait Namada<'a>: /// Make a RevealPK builder from the given minimum set of arguments fn new_reveal_pk( - &mut self, + &self, public_key: common::PublicKey, ) -> args::RevealPk { args::RevealPk { @@ -149,7 +149,7 @@ pub trait Namada<'a>: /// Make a Bond builder from the given minimum set of arguments fn new_bond( - &mut self, + &self, validator: Address, amount: token::Amount, ) -> args::Bond { @@ -165,7 +165,7 @@ pub trait Namada<'a>: /// Make a Unbond builder from the given minimum set of arguments fn new_unbond( - &mut self, + &self, validator: Address, amount: token::Amount, ) -> args::Unbond { @@ -180,7 +180,7 @@ pub trait Namada<'a>: /// Make a TxIbcTransfer builder from the given minimum set of arguments fn new_ibc_transfer( - &mut self, + &self, source: Address, receiver: String, token: Address, @@ -204,7 +204,7 @@ pub trait Namada<'a>: /// Make a InitProposal builder from the given minimum set of arguments fn new_init_proposal( - &mut self, + &self, proposal_data: Vec, ) -> args::InitProposal { args::InitProposal { @@ -219,7 +219,7 @@ pub trait Namada<'a>: } /// Make a TxUpdateAccount builder from the given minimum set of arguments - fn new_update_account(&mut self, addr: Address) -> args::TxUpdateAccount { + fn new_update_account(&self, addr: Address) -> args::TxUpdateAccount { args::TxUpdateAccount { addr, vp_code_path: None, @@ -232,7 +232,7 @@ pub trait Namada<'a>: /// Make a VoteProposal builder from the given minimum set of arguments fn new_vote_prposal( - &mut self, + &self, vote: String, voter: Address, ) -> args::VoteProposal { @@ -250,7 +250,7 @@ pub trait Namada<'a>: /// Make a CommissionRateChange builder from the given minimum set of /// arguments fn new_change_commission_rate( - &mut self, + &self, rate: Dec, validator: Address, ) -> args::CommissionRateChange { @@ -264,7 +264,7 @@ pub trait Namada<'a>: /// Make a TxInitValidator builder from the given minimum set of arguments fn new_init_validator( - &mut self, + &self, commission_rate: Dec, max_commission_rate_change: Dec, ) -> args::TxInitValidator { @@ -287,7 +287,7 @@ pub trait Namada<'a>: /// Make a TxUnjailValidator builder from the given minimum set of arguments fn new_unjail_validator( - &mut self, + &self, validator: Address, ) -> args::TxUnjailValidator { args::TxUnjailValidator { @@ -298,7 +298,7 @@ pub trait Namada<'a>: } /// Make a Withdraw builder from the given minimum set of arguments - fn new_withdraw(&mut self, validator: Address) -> args::Withdraw { + fn new_withdraw(&self, validator: Address) -> args::Withdraw { args::Withdraw { validator, source: None, @@ -309,7 +309,7 @@ pub trait Namada<'a>: /// Make a Withdraw builder from the given minimum set of arguments fn new_add_erc20_transfer( - &mut self, + &self, sender: Address, recipient: EthAddress, asset: EthAddress, @@ -333,7 +333,7 @@ pub trait Namada<'a>: } /// Make a ResignSteward builder from the given minimum set of arguments - fn new_resign_steward(&mut self, steward: Address) -> args::ResignSteward { + fn new_resign_steward(&self, steward: Address) -> args::ResignSteward { args::ResignSteward { steward, tx: self.tx_builder(), @@ -344,7 +344,7 @@ pub trait Namada<'a>: /// Make a UpdateStewardCommission builder from the given minimum set of /// arguments fn new_update_steward_rewards( - &mut self, + &self, steward: Address, commission: Vec, ) -> args::UpdateStewardCommission { @@ -357,7 +357,7 @@ pub trait Namada<'a>: } /// Make a TxCustom builder from the given minimum set of arguments - fn new_custom(&mut self, owner: Address) -> args::TxCustom { + fn new_custom(&self, owner: Address) -> args::TxCustom { args::TxCustom { owner, tx: self.tx_builder(), @@ -483,7 +483,7 @@ where type WalletUtils = U; /// Obtain the prototypical Tx builder - fn tx_builder(&mut self) -> args::Tx { + fn tx_builder(&self) -> args::Tx { self.prototype.clone() } } From 9f06a4bd546352839c4cde833f0debac5adcd647 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 1 Oct 2023 15:09:48 +0200 Subject: [PATCH 09/14] Enabled parallel usage of the Namada trait using read and write locks. --- apps/src/lib/cli/client.rs | 12 +- apps/src/lib/client/tx.rs | 202 ++++++++---------- benches/lib.rs | 5 +- shared/Cargo.toml | 1 + shared/src/ledger/eth_bridge/bridge_pool.rs | 27 ++- shared/src/ledger/mod.rs | 217 +++++++++++--------- shared/src/sdk/args.rs | 56 +++-- shared/src/sdk/masp.rs | 60 +++--- shared/src/sdk/signing.rs | 78 ++++--- shared/src/sdk/tx.rs | 136 ++++++------ 10 files changed, 412 insertions(+), 382 deletions(-) diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 5af2aaa2b2..449d4f38ce 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -258,28 +258,30 @@ impl CliApi { let args = args.to_sdk(&mut ctx); let tx_args = args.tx.clone(); - let mut namada = NamadaImpl::new( + let namada = NamadaImpl::new( &client, &mut ctx.wallet, &mut ctx.shielded, ); let (mut tx, signing_data, _epoch) = - args.clone().build(&mut namada).await?; + args.clone().build(&namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { dump_tx::(&args.tx, tx); } else { tx::submit_reveal_aux( - &mut namada, + &namada, tx_args.clone(), &args.sender, ) .await?; - namada.sign(&mut tx, &tx_args, signing_data)?; + namada + .sign(&mut tx, &tx_args, signing_data) + .await?; namada.submit(tx, &tx_args).await?; } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 2928ce755f..c64fdf0044 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -36,7 +36,7 @@ use namada::types::io::StdIo; /// Wrapper around `signing::aux_signing_data` that stores the optional /// disposable address to the wallet pub async fn aux_signing_data<'a>( - context: &mut impl Namada<'a, WalletUtils = CliWalletUtils>, + context: &impl Namada<'a, WalletUtils = CliWalletUtils>, args: &args::Tx, owner: Option
, default_signer: Option
, @@ -47,7 +47,7 @@ pub async fn aux_signing_data<'a>( if args.disposable_signing_key { if !(args.dry_run || args.dry_run_wrapper) { // Store the generated signing key to wallet in case of need - crate::wallet::save(context.wallet).map_err(|_| { + crate::wallet::save(*context.wallet().await).map_err(|_| { error::Error::Other( "Failed to save disposable address to wallet".to_string(), ) @@ -66,7 +66,7 @@ pub async fn aux_signing_data<'a>( // Build a transaction to reveal the signer of the given transaction. pub async fn submit_reveal_aux<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args: args::Tx, address: &Address, ) -> Result<(), error::Error> { @@ -76,12 +76,15 @@ pub async fn submit_reveal_aux<'a>( if let Address::Implicit(ImplicitAddress(pkh)) = address { let key = context - .wallet + .wallet_mut() + .await .find_key_by_pkh(pkh, args.clone().password) .map_err(|e| error::Error::Other(e.to_string()))?; let public_key = key.ref_to(); - if tx::is_reveal_pk_needed(context.client, address, args.force).await? { + if tx::is_reveal_pk_needed(context.client(), address, args.force) + .await? + { println!( "Submitting a tx to reveal the public key for address \ {address}..." @@ -91,7 +94,7 @@ pub async fn submit_reveal_aux<'a>( signing::generate_test_vector(context, &tx).await?; - context.sign(&mut tx, &args, signing_data)?; + context.sign(&mut tx, &args, signing_data).await?; context.submit(tx, &args).await?; } @@ -109,18 +112,17 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - submit_reveal_aux(&mut namada, args.tx.clone(), &args.owner).await?; + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + submit_reveal_aux(&namada, args.tx.clone(), &args.owner).await?; - let (mut tx, signing_data, _epoch) = args.build(&mut namada).await?; + let (mut tx, signing_data, _epoch) = args.build(&namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; } @@ -136,16 +138,15 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - let (mut tx, signing_data, _epoch) = args.build(&mut namada).await?; + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let (mut tx, signing_data, _epoch) = args.build(&namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; } @@ -161,17 +162,16 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _epoch) = - tx::build_init_account(&mut namada, &args).await?; + tx::build_init_account(&namada, &args).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; } @@ -384,13 +384,11 @@ where tx.add_code_from_hash(tx_code_hash).add_data(data); - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - let signing_data = - aux_signing_data(&mut namada, &tx_args, None, None).await?; + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let signing_data = aux_signing_data(&namada, &tx_args, None, None).await?; tx::prepare_tx( - &mut namada, + &namada, &tx_args, &mut tx, signing_data.fee_payer.clone(), @@ -398,12 +396,12 @@ where ) .await?; - signing::generate_test_vector(&mut namada, &tx).await?; + signing::generate_test_vector(&namada, &tx).await?; if tx_args.dump_tx { tx::dump_tx::(&tx_args, tx); } else { - namada.sign(&mut tx, &tx_args, signing_data)?; + namada.sign(&mut tx, &tx_args, signing_data).await?; let result = namada.submit(tx, &tx_args).await?.initialized_accounts(); @@ -502,25 +500,25 @@ pub async fn submit_transfer( args: args::TxTransfer, ) -> Result<(), error::Error> { for _ in 0..2 { - let mut namada = + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); submit_reveal_aux( - &mut namada, + &namada, args.tx.clone(), &args.source.effective_address(), ) .await?; let (mut tx, signing_data, tx_epoch) = - args.clone().build(&mut namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + args.clone().build(&namada).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); break; } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; let result = namada.submit(tx, &args.tx).await?; let submission_epoch = @@ -561,16 +559,15 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - submit_reveal_aux(&mut namada, args.tx.clone(), &args.source).await?; - let (mut tx, signing_data, _epoch) = args.build(&mut namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + submit_reveal_aux(&namada, args.tx.clone(), &args.source).await?; + let (mut tx, signing_data, _epoch) = args.build(&namada).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; } @@ -588,8 +585,7 @@ where { let current_epoch = rpc::query_and_print_epoch::<_, IO>(client).await; let governance_parameters = rpc::query_governance_parameters(client).await; - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline { let proposal = OfflineProposal::try_from(args.proposal_data.as_ref()) @@ -603,7 +599,7 @@ where let default_signer = Some(proposal.author.clone()); let signing_data = aux_signing_data( - &mut namada, + &namada, &args.tx, Some(proposal.author.clone()), default_signer, @@ -635,14 +631,10 @@ where .validate(&governance_parameters, current_epoch, args.tx.force) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - submit_reveal_aux( - &mut namada, - args.tx.clone(), - &proposal.proposal.author, - ) - .await?; + submit_reveal_aux(&namada, args.tx.clone(), &proposal.proposal.author) + .await?; - tx::build_pgf_funding_proposal(&mut namada, &args, proposal).await? + tx::build_pgf_funding_proposal(&namada, &args, proposal).await? } else if args.is_pgf_stewards { let proposal = PgfStewardProposal::try_from( args.proposal_data.as_ref(), @@ -665,14 +657,10 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - submit_reveal_aux( - &mut namada, - args.tx.clone(), - &proposal.proposal.author, - ) - .await?; + submit_reveal_aux(&namada, args.tx.clone(), &proposal.proposal.author) + .await?; - tx::build_pgf_stewards_proposal(&mut namada, &args, proposal).await? + tx::build_pgf_stewards_proposal(&namada, &args, proposal).await? } else { let proposal = DefaultProposal::try_from(args.proposal_data.as_ref()) .map_err(|e| { @@ -693,21 +681,17 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - submit_reveal_aux( - &mut namada, - args.tx.clone(), - &proposal.proposal.author, - ) - .await?; + submit_reveal_aux(&namada, args.tx.clone(), &proposal.proposal.author) + .await?; - tx::build_default_proposal(&mut namada, &args, proposal).await? + tx::build_default_proposal(&namada, &args, proposal).await? }; - signing::generate_test_vector(&mut namada, &tx_builder).await?; + signing::generate_test_vector(&namada, &tx_builder).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx_builder); } else { - namada.sign(&mut tx_builder, &args.tx, signing_data)?; + namada.sign(&mut tx_builder, &args.tx, signing_data).await?; namada.submit(tx_builder, &args.tx).await?; } @@ -723,13 +707,12 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline { let default_signer = Some(args.voter.clone()); let signing_data = aux_signing_data( - &mut namada, + &namada, &args.tx, Some(args.voter.clone()), default_signer.clone(), @@ -777,14 +760,14 @@ where display_line!(IO, "Proposal vote serialized to: {}", output_file_path); return Ok(()); } else { - args.build(&mut namada).await? + args.build(&namada).await? }; - signing::generate_test_vector(&mut namada, &tx_builder).await?; + signing::generate_test_vector(&namada, &tx_builder).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx_builder); } else { - namada.sign(&mut tx_builder, &args.tx, signing_data)?; + namada.sign(&mut tx_builder, &args.tx, signing_data).await?; namada.submit(tx_builder, &args.tx).await?; } @@ -810,11 +793,10 @@ where edisplay_line!(IO, "Couldn't decode the transaction."); safe_exit(1) }; - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let default_signer = Some(owner.clone()); let signing_data = aux_signing_data( - &mut namada, + &namada, &tx_args, Some(owner.clone()), default_signer, @@ -888,9 +870,8 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - submit_reveal_aux(&mut namada, args.tx, &(&args.public_key).into()).await?; + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + submit_reveal_aux(&namada, args.tx, &(&args.public_key).into()).await?; Ok(()) } @@ -904,19 +885,18 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let default_address = args.source.clone().unwrap_or(args.validator.clone()); - submit_reveal_aux(&mut namada, args.tx.clone(), &default_address).await?; + submit_reveal_aux(&namada, args.tx.clone(), &default_address).await?; let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(&mut namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + args.build(&namada).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; } @@ -933,16 +913,15 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _fee_unshield_epoch, latest_withdrawal_pre) = - args.build(&mut namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + args.build(&namada).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; @@ -962,16 +941,15 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(&mut namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + args.build(&namada).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; } @@ -987,16 +965,15 @@ pub async fn submit_validator_commission_change( where C: namada::ledger::queries::Client + Sync, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(&mut namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + args.build(&namada).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; } @@ -1015,16 +992,15 @@ pub async fn submit_unjail_validator< where C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(&mut namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + args.build(&namada).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; } @@ -1044,17 +1020,16 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(&mut namada).await?; + args.build(&namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; } @@ -1070,16 +1045,15 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let mut namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - let (mut tx, signing_data, _epoch) = args.build(&mut namada).await?; + let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let (mut tx, signing_data, _epoch) = args.build(&namada).await?; - signing::generate_test_vector(&mut namada, &tx).await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { tx::dump_tx::(&args.tx, tx); } else { - namada.sign(&mut tx, &args.tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; } diff --git a/benches/lib.rs b/benches/lib.rs index 95c722de77..3aacaef90f 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -811,13 +811,12 @@ impl BenchShieldedCtx { &[], )) .unwrap(); - let mut namada = + let namada = NamadaImpl::new(&self.shell, &mut self.wallet, &mut self.shielded); let shielded = async_runtime .block_on( ShieldedContext::::gen_shielded_transfer( - &mut namada, - &args, + &namada, &args, ), ) .unwrap() diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 660a810087..f3c5428594 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -150,6 +150,7 @@ zeroize.workspace = true tokio = {workspace = true, features = ["full"]} [target.'cfg(target_family = "wasm")'.dependencies] +tokio = {workspace = true, default-features = false, features = ["sync"]} wasmtimer = "0.2.0" [dev-dependencies] diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 430537fdc2..14002b53d3 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -43,7 +43,7 @@ use crate::{display, display_line}; /// Craft a transaction that adds a transfer to the Ethereum bridge pool. pub async fn build_bridge_pool_tx<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::EthereumBridgePool { tx: tx_args, nut, @@ -67,7 +67,7 @@ pub async fn build_bridge_pool_tx<'a>( .await?; let fee_payer = fee_payer.unwrap_or_else(|| sender.clone()); let DenominatedAmount { amount, .. } = validate_amount( - context.client, + context.client(), amount, &wrapped_erc20s::token(&asset), tx_args.force, @@ -76,14 +76,19 @@ pub async fn build_bridge_pool_tx<'a>( .map_err(|e| Error::Other(format!("Failed to validate amount. {}", e)))?; let DenominatedAmount { amount: fee_amount, .. - } = validate_amount(context.client, fee_amount, &fee_token, tx_args.force) - .await - .map_err(|e| { - Error::Other(format!( - "Failed to validate Bridge pool fee amount. {}", - e - )) - })?; + } = validate_amount( + context.client(), + fee_amount, + &fee_token, + tx_args.force, + ) + .await + .map_err(|e| { + Error::Other(format!( + "Failed to validate Bridge pool fee amount. {}", + e + )) + })?; let transfer = PendingTransfer { transfer: TransferToEthereum { asset, @@ -104,7 +109,7 @@ pub async fn build_bridge_pool_tx<'a>( }; let tx_code_hash = - query_wasm_code_hash(context.client, code_path.to_str().unwrap()) + query_wasm_code_hash(context.client(), code_path.to_str().unwrap()) .await .unwrap(); diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 59397be235..a8159834a5 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -14,9 +14,9 @@ pub mod queries; pub mod storage; pub mod vp_host_fns; -use std::ops::{Deref, DerefMut}; use std::path::PathBuf; use std::str::FromStr; +use std::sync::Arc; pub use namada_core::ledger::{ gas, parameters, replay_protection, storage_api, tx_env, vp_env, @@ -45,34 +45,11 @@ use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use crate::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada_core::types::dec::Dec; use namada_core::types::ethereum_events::EthAddress; - -/// Encapsulates a Namada session to enable splitting borrows of its parts -pub struct NamadaStruct<'a, C, U, V> -where - C: crate::ledger::queries::Client + Sync, - U: WalletIo, - V: ShieldedUtils, -{ - /// Used to send and receive messages from the ledger - pub client: &'a C, - /// Stores the addresses and keys required for ledger interactions - pub wallet: &'a mut Wallet, - /// Stores the current state of the shielded pool - pub shielded: &'a mut ShieldedContext, -} +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; #[async_trait::async_trait(?Send)] /// An interface for high-level interaction with the Namada SDK -pub trait Namada<'a>: - DerefMut< - Target = NamadaStruct< - 'a, - Self::Client, - Self::WalletUtils, - Self::ShieldedUtils, - >, -> -{ +pub trait Namada<'a> { /// A client with async request dispatcher method type Client: 'a + crate::ledger::queries::Client + Sync; /// Captures the interactive parts of the wallet's functioning @@ -81,16 +58,40 @@ pub trait Namada<'a>: /// operations. type ShieldedUtils: 'a + ShieldedUtils; + /// Obtain the client for communicating with the ledger + fn client(&self) -> &'a Self::Client; + + /// Obtain read lock on the wallet + async fn wallet( + &self, + ) -> RwLockReadGuard<&'a mut Wallet>; + + /// Obtain write lock on the wallet + async fn wallet_mut( + &self, + ) -> RwLockWriteGuard<&'a mut Wallet>; + + /// Obtain read lock on the shielded context + async fn shielded( + &self, + ) -> RwLockReadGuard<&'a mut ShieldedContext>; + + /// Obtain write lock on the shielded context + async fn shielded_mut( + &self, + ) -> RwLockWriteGuard<&'a mut ShieldedContext>; + /// Return the native token - fn native_token(&self) -> Address { - self.wallet + async fn native_token(&self) -> Address { + self.wallet() + .await .find_address(args::NAM) .expect("NAM not in wallet") .clone() } /// Make a tx builder using no arguments - fn tx_builder(&self) -> args::Tx { + async fn tx_builder(&self) -> args::Tx { args::Tx { dry_run: false, dry_run_wrapper: false, @@ -103,7 +104,7 @@ pub trait Namada<'a>: wallet_alias_force: false, fee_amount: None, wrapper_fee_payer: None, - fee_token: self.native_token(), + fee_token: self.native_token().await, fee_unshield: None, gas_limit: GasLimit::from(20_000), expiration: None, @@ -118,7 +119,7 @@ pub trait Namada<'a>: } /// Make a TxTransfer builder from the given minimum set of arguments - fn new_transfer( + async fn new_transfer( &self, source: TransferSource, target: TransferTarget, @@ -131,24 +132,24 @@ pub trait Namada<'a>: token, amount, tx_code_path: PathBuf::from(TX_TRANSFER_WASM), - tx: self.tx_builder(), - native_token: self.native_token(), + tx: self.tx_builder().await, + native_token: self.native_token().await, } } /// Make a RevealPK builder from the given minimum set of arguments - fn new_reveal_pk( + async fn new_reveal_pk( &self, public_key: common::PublicKey, ) -> args::RevealPk { args::RevealPk { public_key, - tx: self.tx_builder(), + tx: self.tx_builder().await, } } /// Make a Bond builder from the given minimum set of arguments - fn new_bond( + async fn new_bond( &self, validator: Address, amount: token::Amount, @@ -157,14 +158,14 @@ pub trait Namada<'a>: validator, amount, source: None, - tx: self.tx_builder(), - native_token: self.native_token(), + tx: self.tx_builder().await, + native_token: self.native_token().await, tx_code_path: PathBuf::from(TX_BOND_WASM), } } /// Make a Unbond builder from the given minimum set of arguments - fn new_unbond( + async fn new_unbond( &self, validator: Address, amount: token::Amount, @@ -173,13 +174,13 @@ pub trait Namada<'a>: validator, amount, source: None, - tx: self.tx_builder(), + tx: self.tx_builder().await, tx_code_path: PathBuf::from(TX_UNBOND_WASM), } } /// Make a TxIbcTransfer builder from the given minimum set of arguments - fn new_ibc_transfer( + async fn new_ibc_transfer( &self, source: Address, receiver: String, @@ -197,41 +198,41 @@ pub trait Namada<'a>: timeout_height: None, timeout_sec_offset: None, memo: None, - tx: self.tx_builder(), + tx: self.tx_builder().await, tx_code_path: PathBuf::from(TX_IBC_WASM), } } /// Make a InitProposal builder from the given minimum set of arguments - fn new_init_proposal( + async fn new_init_proposal( &self, proposal_data: Vec, ) -> args::InitProposal { args::InitProposal { proposal_data, - native_token: self.native_token(), + native_token: self.native_token().await, is_offline: false, is_pgf_stewards: false, is_pgf_funding: false, tx_code_path: PathBuf::from(TX_INIT_PROPOSAL), - tx: self.tx_builder(), + tx: self.tx_builder().await, } } /// Make a TxUpdateAccount builder from the given minimum set of arguments - fn new_update_account(&self, addr: Address) -> args::TxUpdateAccount { + async fn new_update_account(&self, addr: Address) -> args::TxUpdateAccount { args::TxUpdateAccount { addr, vp_code_path: None, public_keys: vec![], threshold: None, tx_code_path: PathBuf::from(TX_UPDATE_ACCOUNT_WASM), - tx: self.tx_builder(), + tx: self.tx_builder().await, } } /// Make a VoteProposal builder from the given minimum set of arguments - fn new_vote_prposal( + async fn new_vote_prposal( &self, vote: String, voter: Address, @@ -243,13 +244,13 @@ pub trait Namada<'a>: is_offline: false, proposal_data: None, tx_code_path: PathBuf::from(TX_VOTE_PROPOSAL), - tx: self.tx_builder(), + tx: self.tx_builder().await, } } /// Make a CommissionRateChange builder from the given minimum set of /// arguments - fn new_change_commission_rate( + async fn new_change_commission_rate( &self, rate: Dec, validator: Address, @@ -258,12 +259,12 @@ pub trait Namada<'a>: rate, validator, tx_code_path: PathBuf::from(TX_CHANGE_COMMISSION_WASM), - tx: self.tx_builder(), + tx: self.tx_builder().await, } } /// Make a TxInitValidator builder from the given minimum set of arguments - fn new_init_validator( + async fn new_init_validator( &self, commission_rate: Dec, max_commission_rate_change: Dec, @@ -281,34 +282,34 @@ pub trait Namada<'a>: validator_vp_code_path: PathBuf::from(VP_USER_WASM), unsafe_dont_encrypt: false, tx_code_path: PathBuf::from(TX_INIT_VALIDATOR_WASM), - tx: self.tx_builder(), + tx: self.tx_builder().await, } } /// Make a TxUnjailValidator builder from the given minimum set of arguments - fn new_unjail_validator( + async fn new_unjail_validator( &self, validator: Address, ) -> args::TxUnjailValidator { args::TxUnjailValidator { validator, tx_code_path: PathBuf::from(TX_UNJAIL_VALIDATOR_WASM), - tx: self.tx_builder(), + tx: self.tx_builder().await, } } /// Make a Withdraw builder from the given minimum set of arguments - fn new_withdraw(&self, validator: Address) -> args::Withdraw { + async fn new_withdraw(&self, validator: Address) -> args::Withdraw { args::Withdraw { validator, source: None, tx_code_path: PathBuf::from(TX_WITHDRAW_WASM), - tx: self.tx_builder(), + tx: self.tx_builder().await, } } /// Make a Withdraw builder from the given minimum set of arguments - fn new_add_erc20_transfer( + async fn new_add_erc20_transfer( &self, sender: Address, recipient: EthAddress, @@ -325,25 +326,28 @@ pub trait Namada<'a>: denom: NATIVE_MAX_DECIMAL_PLACES.into(), }), fee_payer: None, - fee_token: self.native_token(), + fee_token: self.native_token().await, nut: false, code_path: PathBuf::from(TX_BRIDGE_POOL_WASM), - tx: self.tx_builder(), + tx: self.tx_builder().await, } } /// Make a ResignSteward builder from the given minimum set of arguments - fn new_resign_steward(&self, steward: Address) -> args::ResignSteward { + async fn new_resign_steward( + &self, + steward: Address, + ) -> args::ResignSteward { args::ResignSteward { steward, - tx: self.tx_builder(), + tx: self.tx_builder().await, tx_code_path: PathBuf::from(TX_RESIGN_STEWARD), } } /// Make a UpdateStewardCommission builder from the given minimum set of /// arguments - fn new_update_steward_rewards( + async fn new_update_steward_rewards( &self, steward: Address, commission: Vec, @@ -351,16 +355,16 @@ pub trait Namada<'a>: args::UpdateStewardCommission { steward, commission, - tx: self.tx_builder(), + tx: self.tx_builder().await, tx_code_path: PathBuf::from(TX_UPDATE_STEWARD_COMMISSION), } } /// Make a TxCustom builder from the given minimum set of arguments - fn new_custom(&self, owner: Address) -> args::TxCustom { + async fn new_custom(&self, owner: Address) -> args::TxCustom { args::TxCustom { owner, - tx: self.tx_builder(), + tx: self.tx_builder().await, code_path: None, data_path: None, serialized_tx: None, @@ -368,22 +372,22 @@ pub trait Namada<'a>: } /// Sign the given transaction using the given signing data - fn sign( - &mut self, + async fn sign( + &self, tx: &mut Tx, args: &args::Tx, signing_data: SigningTxData, ) -> crate::sdk::error::Result<()> { - signing::sign_tx(self.wallet, args, tx, signing_data) + signing::sign_tx(*self.wallet_mut().await, args, tx, signing_data) } /// Process the given transaction using the given flags async fn submit( - &mut self, + &self, tx: Tx, args: &args::Tx, ) -> crate::sdk::error::Result { - tx::process_tx(self.client, self.wallet, args, tx).await + tx::process_tx(self.client(), *self.wallet_mut().await, args, tx).await } } @@ -394,7 +398,13 @@ where U: WalletIo, V: ShieldedUtils, { - namada: NamadaStruct<'a, C, U, V>, + /// Used to send and receive messages from the ledger + pub client: &'a C, + /// Stores the addresses and keys required for ledger interactions + pub wallet: Arc>>, + /// Stores the current state of the shielded pool + pub shielded: Arc>>, + /// The default builder for a Tx prototype: args::Tx, } @@ -415,11 +425,9 @@ where .expect("NAM not in wallet") .clone(); Self { - namada: NamadaStruct { - client, - wallet, - shielded, - }, + client, + wallet: Arc::new(RwLock::new(wallet)), + shielded: Arc::new(RwLock::new(shielded)), prototype: args::Tx { dry_run: false, dry_run_wrapper: false, @@ -448,30 +456,7 @@ where } } -impl<'a, C, U, V> Deref for NamadaImpl<'a, C, U, V> -where - C: crate::ledger::queries::Client + Sync, - U: WalletIo, - V: ShieldedUtils, -{ - type Target = NamadaStruct<'a, C, U, V>; - - fn deref(&self) -> &Self::Target { - &self.namada - } -} - -impl<'a, C, U, V> DerefMut for NamadaImpl<'a, C, U, V> -where - C: crate::ledger::queries::Client + Sync, - U: WalletIo, - V: ShieldedUtils, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.namada - } -} - +#[async_trait::async_trait(?Send)] impl<'a, C, U, V> Namada<'a> for NamadaImpl<'a, C, U, V> where C: crate::ledger::queries::Client + Sync, @@ -483,9 +468,37 @@ where type WalletUtils = U; /// Obtain the prototypical Tx builder - fn tx_builder(&self) -> args::Tx { + async fn tx_builder(&self) -> args::Tx { self.prototype.clone() } + + fn client(&self) -> &'a Self::Client { + self.client + } + + async fn wallet( + &self, + ) -> RwLockReadGuard<&'a mut Wallet> { + self.wallet.read().await + } + + async fn wallet_mut( + &self, + ) -> RwLockWriteGuard<&'a mut Wallet> { + self.wallet.write().await + } + + async fn shielded( + &self, + ) -> RwLockReadGuard<&'a mut ShieldedContext> { + self.shielded.read().await + } + + async fn shielded_mut( + &self, + ) -> RwLockWriteGuard<&'a mut ShieldedContext> { + self.shielded.write().await + } } /// Allow the prototypical Tx builder to be modified diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs index cf43d5dbd7..91e00dee68 100644 --- a/shared/src/sdk/args.rs +++ b/shared/src/sdk/args.rs @@ -181,7 +181,7 @@ impl TxCustom { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -290,7 +290,7 @@ impl TxTransfer { /// Build a transaction from this builder pub async fn build<'a>( &mut self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -407,7 +407,7 @@ impl TxIbcTransfer { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -499,15 +499,15 @@ impl InitProposal { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, Option, )> { - let current_epoch = rpc::query_epoch(context.client).await?; + let current_epoch = rpc::query_epoch(context.client()).await?; let governance_parameters = - rpc::query_governance_parameters(context.client).await; + rpc::query_governance_parameters(context.client()).await; if self.is_pgf_funding { let proposal = @@ -528,9 +528,15 @@ impl InitProposal { .map_err(|e| { crate::sdk::error::TxError::FailedGovernaneProposalDeserialize(e.to_string()) })?; + let nam_address = context + .wallet() + .await + .find_address(NAM) + .expect("NAM not in wallet") + .clone(); let author_balance = rpc::get_token_balance( - context.client, - context.wallet.find_address(NAM).expect("NAM not in wallet"), + context.client(), + &nam_address, &proposal.proposal.author, ) .await?; @@ -551,9 +557,15 @@ impl InitProposal { .map_err(|e| { crate::sdk::error::TxError::FailedGovernaneProposalDeserialize(e.to_string()) })?; + let nam_address = context + .wallet() + .await + .find_address(NAM) + .expect("NAM not in wallet") + .clone(); let author_balance = rpc::get_token_balance( - context.client, - context.wallet.find_address(NAM).expect("NAM not in wallet"), + context.client(), + &nam_address, &proposal.proposal.author, ) .await?; @@ -648,13 +660,13 @@ impl VoteProposal { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, Option, )> { - let current_epoch = rpc::query_epoch(context.client).await?; + let current_epoch = rpc::query_epoch(context.client()).await?; tx::build_vote_proposal(context, self, current_epoch).await } } @@ -777,7 +789,7 @@ impl TxUpdateAccount { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -858,7 +870,7 @@ impl Bond { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -888,7 +900,7 @@ impl Unbond { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -972,7 +984,7 @@ impl RevealPk { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -1059,7 +1071,7 @@ impl Withdraw { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -1195,7 +1207,7 @@ impl CommissionRateChange { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -1254,7 +1266,7 @@ impl UpdateStewardCommission { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -1306,7 +1318,7 @@ impl ResignSteward { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -1358,7 +1370,7 @@ impl TxUnjailValidator { /// Build a transaction from this builder pub async fn build<'a>( &self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, @@ -1883,7 +1895,7 @@ impl EthereumBridgePool { /// Build a transaction from this builder pub async fn build<'a>( self, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, ) -> crate::sdk::error::Result<( crate::proto::Tx, SigningTxData, diff --git a/shared/src/sdk/masp.rs b/shared/src/sdk/masp.rs index 7959916944..35c4f59956 100644 --- a/shared/src/sdk/masp.rs +++ b/shared/src/sdk/masp.rs @@ -1489,7 +1489,7 @@ impl ShieldedContext { /// amounts and signatures specified by the containing Transfer object. #[cfg(feature = "masp-tx-gen")] pub async fn gen_shielded_transfer<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args: &args::TxTransfer, ) -> Result, TransferErr> { // No shielded components are needed when neither source nor destination @@ -1510,17 +1510,20 @@ impl ShieldedContext { // We want to fund our transaction solely from supplied spending key let spending_key = spending_key.map(|x| x.into()); let spending_keys: Vec<_> = spending_key.into_iter().collect(); - // Load the current shielded context given the spending key we possess - let _ = context.shielded.load().await; - let context = &mut **context; - context - .shielded - .fetch(context.client, &spending_keys, &[]) - .await?; - // Save the update state so that future fetches can be short-circuited - let _ = context.shielded.save().await; + { + // Load the current shielded context given the spending key we + // possess + let mut shielded = context.shielded_mut().await; + let _ = shielded.load().await; + shielded + .fetch(context.client(), &spending_keys, &[]) + .await?; + // Save the update state so that future fetches can be + // short-circuited + let _ = shielded.save().await; + } // Determine epoch in which to submit potential shielded transaction - let epoch = rpc::query_epoch(context.client).await?; + let epoch = rpc::query_epoch(context.client()).await?; // Context required for storing which notes are in the source's // possesion let memo = MemoBytes::empty(); @@ -1561,9 +1564,10 @@ impl ShieldedContext { if let Some(sk) = spending_key { // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = context - .shielded + .shielded_mut() + .await .collect_unspent_notes( - context.client, + context.client(), &to_viewing_key(&sk).vk, I128Sum::from_sum(amount), epoch, @@ -1749,17 +1753,17 @@ impl ShieldedContext { Error::from(EncodingError::Conversion(e.to_string())) })?; - let build_transfer = - || -> Result> { - let (masp_tx, metadata) = builder.build( - &context.shielded.utils.local_tx_prover(), - &FeeRule::non_standard(U64Sum::zero()), - )?; - Ok(ShieldedTransfer { - builder: builder_clone, - masp_tx, - metadata, - epoch, + let build_transfer = |prover: LocalTxProver| -> Result< + ShieldedTransfer, + builder::Error, + > { + let (masp_tx, metadata) = builder + .build(&prover, &FeeRule::non_standard(U64Sum::zero()))?; + Ok(ShieldedTransfer { + builder: builder_clone, + masp_tx, + metadata, + epoch, }) }; @@ -1805,7 +1809,9 @@ impl ShieldedContext { Ok(Some(loaded)) } else { // Build and return the constructed transaction - let built = build_transfer()?; + let built = build_transfer( + context.shielded().await.utils.local_tx_prover(), + )?; if let LoadOrSaveProofs::Save = load_or_save { let built_bytes = BorshSerialize::try_to_vec(&built) .map_err(|e| { @@ -1824,7 +1830,9 @@ impl ShieldedContext { #[cfg(not(feature = "testing"))] { // Build and return the constructed transaction - let built = build_transfer()?; + let built = build_transfer( + context.shielded().await.utils.local_tx_prover(), + )?; Ok(Some(built)) } } diff --git a/shared/src/sdk/signing.rs b/shared/src/sdk/signing.rs index 9d534217a4..048d72517f 100644 --- a/shared/src/sdk/signing.rs +++ b/shared/src/sdk/signing.rs @@ -151,7 +151,7 @@ pub fn find_key_by_pk( /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, an `Error` is returned. pub async fn tx_signers<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args: &args::Tx, default: Option
, ) -> Result, Error> { @@ -172,8 +172,8 @@ pub async fn tx_signers<'a>( Some(signer) => Ok(vec![ find_pk( - context.client, - context.wallet, + context.client(), + *context.wallet_mut().await, &signer, args.password.clone(), ) @@ -240,7 +240,7 @@ pub fn sign_tx( /// Return the necessary data regarding an account to be able to generate a /// multisignature section pub async fn aux_signing_data<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args: &args::Tx, owner: Option
, default_signer: Option
, @@ -253,7 +253,8 @@ pub async fn aux_signing_data<'a>( let (account_public_keys_map, threshold) = match &owner { Some(owner @ Address::Established(_)) => { - let account = rpc::get_account_info(context.client, owner).await?; + let account = + rpc::get_account_info(context.client(), owner).await?; if let Some(account) = account { (Some(account.public_keys_map), account.threshold) } else { @@ -273,7 +274,11 @@ pub async fn aux_signing_data<'a>( }; let fee_payer = if args.disposable_signing_key { - context.wallet.generate_disposable_signing_key().to_public() + context + .wallet_mut() + .await + .generate_disposable_signing_key() + .to_public() } else { match &args.wrapper_fee_payer { Some(keypair) => keypair.to_public(), @@ -314,7 +319,7 @@ pub struct TxSourcePostBalance { /// progress on chain. #[allow(clippy::too_many_arguments)] pub async fn wrap_tx<'a, N: Namada<'a>>( - context: &mut N, + context: &N, tx: &mut Tx, args: &args::Tx, tx_source_balance: Option, @@ -327,7 +332,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( let minimum_fee = match rpc::query_storage_value::< _, BTreeMap, - >(context.client, &gas_cost_key) + >(context.client(), &gas_cost_key) .await .and_then(|map| { map.get(&args.fee_token) @@ -351,7 +356,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( let fee_amount = match args.fee_amount { Some(amount) => { let validated_fee_amount = validate_amount( - context.client, + context.client(), amount, &args.fee_token, args.force, @@ -392,7 +397,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( token::balance_key(&args.fee_token, &fee_payer_address); rpc::query_storage_value::<_, token::Amount>( - context.client, + context.client(), &balance_key, ) .await @@ -460,7 +465,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( let descriptions_limit_key= parameter_storage::get_fee_unshielding_descriptions_limit_key(); let descriptions_limit = rpc::query_storage_value::<_, u64>( - context.client, + context.client(), &descriptions_limit_key, ) .await @@ -508,14 +513,14 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( let token_addr = args.fee_token.clone(); if !args.force { let fee_amount = format_denominated_amount( - context.client, + context.client(), &token_addr, total_fee, ) .await; let balance = format_denominated_amount( - context.client, + context.client(), &token_addr, updated_balance, ) @@ -779,7 +784,7 @@ pub async fn make_ledger_masp_endpoints< /// Internal method used to generate transaction test vectors #[cfg(feature = "std")] pub async fn generate_test_vector<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, tx: &Tx, ) -> Result<(), Error> { use std::env; @@ -830,40 +835,45 @@ pub async fn generate_test_vector<'a>( /// Converts the given transaction to the form that is displayed on the Ledger /// device pub async fn to_ledger_vector<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, tx: &Tx, ) -> Result { let init_account_hash = - query_wasm_code_hash(context.client, TX_INIT_ACCOUNT_WASM).await?; + query_wasm_code_hash(context.client(), TX_INIT_ACCOUNT_WASM).await?; let init_validator_hash = - query_wasm_code_hash(context.client, TX_INIT_VALIDATOR_WASM).await?; + query_wasm_code_hash(context.client(), TX_INIT_VALIDATOR_WASM).await?; let init_proposal_hash = - query_wasm_code_hash(context.client, TX_INIT_PROPOSAL).await?; + query_wasm_code_hash(context.client(), TX_INIT_PROPOSAL).await?; let vote_proposal_hash = - query_wasm_code_hash(context.client, TX_VOTE_PROPOSAL).await?; + query_wasm_code_hash(context.client(), TX_VOTE_PROPOSAL).await?; let reveal_pk_hash = - query_wasm_code_hash(context.client, TX_REVEAL_PK).await?; + query_wasm_code_hash(context.client(), TX_REVEAL_PK).await?; let update_account_hash = - query_wasm_code_hash(context.client, TX_UPDATE_ACCOUNT_WASM).await?; + query_wasm_code_hash(context.client(), TX_UPDATE_ACCOUNT_WASM).await?; let transfer_hash = - query_wasm_code_hash(context.client, TX_TRANSFER_WASM).await?; - let ibc_hash = query_wasm_code_hash(context.client, TX_IBC_WASM).await?; - let bond_hash = query_wasm_code_hash(context.client, TX_BOND_WASM).await?; + query_wasm_code_hash(context.client(), TX_TRANSFER_WASM).await?; + let ibc_hash = query_wasm_code_hash(context.client(), TX_IBC_WASM).await?; + let bond_hash = + query_wasm_code_hash(context.client(), TX_BOND_WASM).await?; let unbond_hash = - query_wasm_code_hash(context.client, TX_UNBOND_WASM).await?; + query_wasm_code_hash(context.client(), TX_UNBOND_WASM).await?; let withdraw_hash = - query_wasm_code_hash(context.client, TX_WITHDRAW_WASM).await?; + query_wasm_code_hash(context.client(), TX_WITHDRAW_WASM).await?; let change_commission_hash = - query_wasm_code_hash(context.client, TX_CHANGE_COMMISSION_WASM).await?; - let user_hash = query_wasm_code_hash(context.client, VP_USER_WASM).await?; + query_wasm_code_hash(context.client(), TX_CHANGE_COMMISSION_WASM) + .await?; + let user_hash = + query_wasm_code_hash(context.client(), VP_USER_WASM).await?; // To facilitate lookups of human-readable token names + let wallet = context.wallet().await; let tokens: HashMap = context - .wallet + .wallet() + .await .get_addresses_with_vp_type(AddressVpType::Token) .into_iter() .map(|addr| { - let alias = match context.wallet.find_alias(&addr) { + let alias = match wallet.find_alias(&addr) { Some(alias) => alias.to_string(), None => addr.to_string(), }; @@ -1150,7 +1160,7 @@ pub async fn to_ledger_vector<'a>( tv.output.push("Type : Transfer".to_string()); make_ledger_masp_endpoints( - context.client, + context.client(), &tokens, &mut tv.output, &transfer, @@ -1159,7 +1169,7 @@ pub async fn to_ledger_vector<'a>( ) .await; make_ledger_masp_endpoints( - context.client, + context.client(), &tokens, &mut tv.output_expert, &transfer, @@ -1332,13 +1342,13 @@ pub async fn to_ledger_vector<'a>( if let Some(wrapper) = tx.header.wrapper() { let gas_token = wrapper.fee.token.clone(); let gas_limit = format_denominated_amount( - context.client, + context.client(), &gas_token, Amount::from(wrapper.gas_limit), ) .await; let fee_amount_per_gas_unit = format_denominated_amount( - context.client, + context.client(), &gas_token, wrapper.fee.amount_per_gas_unit, ) diff --git a/shared/src/sdk/tx.rs b/shared/src/sdk/tx.rs index f718084709..e6272c0d65 100644 --- a/shared/src/sdk/tx.rs +++ b/shared/src/sdk/tx.rs @@ -160,14 +160,14 @@ pub fn dump_tx(args: &args::Tx, tx: Tx) { /// to it. #[allow(clippy::too_many_arguments)] pub async fn prepare_tx<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args: &args::Tx, tx: &mut Tx, fee_payer: common::PublicKey, tx_source_balance: Option, ) -> Result> { if !args.dry_run { - let epoch = rpc::query_epoch(context.client).await?; + let epoch = rpc::query_epoch(context.client()).await?; signing::wrap_tx( context, @@ -268,7 +268,7 @@ pub async fn has_revealed_pk( /// Submit transaction to reveal the given public key pub async fn build_reveal_pk<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args: &args::Tx, public_key: &common::PublicKey, ) -> Result<(Tx, SigningTxData, Option)> { @@ -507,7 +507,7 @@ pub async fn save_initialized_accounts( /// Submit validator comission rate change pub async fn build_validator_commission_change<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::CommissionRateChange { tx: tx_args, validator, @@ -524,12 +524,12 @@ pub async fn build_validator_commission_change<'a>( ) .await?; - let epoch = rpc::query_epoch(context.client).await?; + let epoch = rpc::query_epoch(context.client()).await?; - let params: PosParams = rpc::get_pos_params(context.client).await?; + let params: PosParams = rpc::get_pos_params(context.client()).await?; let validator = validator.clone(); - if rpc::is_validator(context.client, &validator).await? { + if rpc::is_validator(context.client(), &validator).await? { if *rate < Dec::zero() || *rate > Dec::one() { edisplay_line!( StdIo, @@ -542,7 +542,7 @@ pub async fn build_validator_commission_change<'a>( let pipeline_epoch_minus_one = epoch + params.pipeline_len - 1; match rpc::query_commission_rate( - context.client, + context.client(), &validator, Some(pipeline_epoch_minus_one), ) @@ -604,7 +604,7 @@ pub async fn build_validator_commission_change<'a>( /// Craft transaction to update a steward commission pub async fn build_update_steward_commission<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::UpdateStewardCommission { tx: tx_args, steward, @@ -621,7 +621,7 @@ pub async fn build_update_steward_commission<'a>( ) .await?; - if !rpc::is_steward(context.client, steward).await && !tx_args.force { + if !rpc::is_steward(context.client(), steward).await && !tx_args.force { edisplay_line!(StdIo, "The given address {} is not a steward.", &steward); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); }; @@ -659,7 +659,7 @@ pub async fn build_update_steward_commission<'a>( /// Craft transaction to resign as a steward pub async fn build_resign_steward<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::ResignSteward { tx: tx_args, steward, @@ -675,7 +675,7 @@ pub async fn build_resign_steward<'a>( ) .await?; - if !rpc::is_steward(context.client, steward).await && !tx_args.force { + if !rpc::is_steward(context.client(), steward).await && !tx_args.force { edisplay_line!(StdIo, "The given address {} is not a steward.", &steward); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); }; @@ -695,7 +695,7 @@ pub async fn build_resign_steward<'a>( /// Submit transaction to unjail a jailed validator pub async fn build_unjail_validator<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::TxUnjailValidator { tx: tx_args, validator, @@ -711,7 +711,7 @@ pub async fn build_unjail_validator<'a>( ) .await?; - if !rpc::is_validator(context.client, validator).await? { + if !rpc::is_validator(context.client(), validator).await? { edisplay_line!( StdIo, "The given address {} is not a validator.", @@ -724,12 +724,12 @@ pub async fn build_unjail_validator<'a>( } } - let params: PosParams = rpc::get_pos_params(context.client).await?; - let current_epoch = rpc::query_epoch(context.client).await?; + let params: PosParams = rpc::get_pos_params(context.client()).await?; + let current_epoch = rpc::query_epoch(context.client()).await?; let pipeline_epoch = current_epoch + params.pipeline_len; let validator_state_at_pipeline = rpc::get_validator_state( - context.client, + context.client(), validator, Some(pipeline_epoch), ) @@ -756,7 +756,7 @@ pub async fn build_unjail_validator<'a>( let last_slash_epoch_key = crate::ledger::pos::validator_last_slash_key(validator); let last_slash_epoch = rpc::query_storage_value::<_, Epoch>( - context.client, + context.client(), &last_slash_epoch_key, ) .await; @@ -807,7 +807,7 @@ pub async fn build_unjail_validator<'a>( /// Submit transaction to withdraw an unbond pub async fn build_withdraw<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::Withdraw { tx: tx_args, validator, @@ -825,12 +825,12 @@ pub async fn build_withdraw<'a>( ) .await?; - let epoch = rpc::query_epoch(context.client).await?; + let epoch = rpc::query_epoch(context.client()).await?; let validator = known_validator_or_err( validator.clone(), tx_args.force, - context.client, + context.client(), ) .await?; @@ -839,7 +839,7 @@ pub async fn build_withdraw<'a>( // Check the source's current unbond amount let bond_source = source.clone().unwrap_or_else(|| validator.clone()); let tokens = rpc::query_withdrawable_tokens( - context.client, + context.client(), &bond_source, &validator, Some(epoch), @@ -853,8 +853,12 @@ pub async fn build_withdraw<'a>( epoch {}.", epoch ); - rpc::query_and_print_unbonds(context.client, &bond_source, &validator) - .await?; + rpc::query_and_print_unbonds( + context.client(), + &bond_source, + &validator, + ) + .await?; if !tx_args.force { return Err(Error::from(TxError::NoUnbondReady(epoch))); } @@ -884,7 +888,7 @@ pub async fn build_withdraw<'a>( /// Submit a transaction to unbond pub async fn build_unbond<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::Unbond { tx: tx_args, validator, @@ -916,12 +920,12 @@ pub async fn build_unbond<'a>( known_validator_or_err( validator.clone(), tx_args.force, - context.client, + context.client(), ) .await?; let bond_amount = - rpc::query_bond(context.client, &bond_source, validator, None).await?; + rpc::query_bond(context.client(), &bond_source, validator, None).await?; display_line!( StdIo, "Bond amount available for unbonding: {} NAM", @@ -950,7 +954,7 @@ pub async fn build_unbond<'a>( // Query the unbonds before submitting the tx let unbonds = rpc::query_unbond_with_slashing( - context.client, + context.client(), &bond_source, validator, ) @@ -1051,7 +1055,7 @@ pub async fn query_unbonds( /// Submit a transaction to bond pub async fn build_bond<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::Bond { tx: tx_args, validator, @@ -1074,14 +1078,14 @@ pub async fn build_bond<'a>( let validator = known_validator_or_err( validator.clone(), tx_args.force, - context.client, + context.client(), ) .await?; // Check that the source address exists on chain let source = match source.clone() { Some(source) => { - source_exists_or_err(source, tx_args.force, context.client) + source_exists_or_err(source, tx_args.force, context.client()) .await .map(Some) } @@ -1099,7 +1103,7 @@ pub async fn build_bond<'a>( *amount, balance_key, tx_args.force, - context.client, + context.client(), ) .await?; let tx_source_balance = Some(TxSourcePostBalance { @@ -1129,7 +1133,7 @@ pub async fn build_bond<'a>( /// Build a default proposal governance pub async fn build_default_proposal<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::InitProposal { tx, proposal_data: _, @@ -1182,7 +1186,7 @@ pub async fn build_default_proposal<'a>( /// Build a proposal vote pub async fn build_vote_proposal<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::VoteProposal { tx, proposal_id, @@ -1210,7 +1214,7 @@ pub async fn build_vote_proposal<'a>( Error::Other("Proposal id must be defined.".to_string()) })?; let proposal = if let Some(proposal) = - rpc::query_proposal_by_id(context.client, proposal_id).await? + rpc::query_proposal_by_id(context.client(), proposal_id).await? { proposal } else { @@ -1225,7 +1229,7 @@ pub async fn build_vote_proposal<'a>( )) })?; - let is_validator = rpc::is_validator(context.client, voter).await?; + let is_validator = rpc::is_validator(context.client(), voter).await?; if !proposal.can_be_voted(epoch, is_validator) { if tx.force { @@ -1238,7 +1242,7 @@ pub async fn build_vote_proposal<'a>( } let delegations = rpc::get_delegators_delegation_at( - context.client, + context.client(), voter, proposal.voting_start_epoch, ) @@ -1269,7 +1273,7 @@ pub async fn build_vote_proposal<'a>( /// Build a pgf funding proposal governance pub async fn build_pgf_funding_proposal<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::InitProposal { tx, proposal_data: _, @@ -1314,7 +1318,7 @@ pub async fn build_pgf_funding_proposal<'a>( /// Build a pgf funding proposal governance pub async fn build_pgf_stewards_proposal<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::InitProposal { tx, proposal_data: _, @@ -1360,7 +1364,7 @@ pub async fn build_pgf_stewards_proposal<'a>( /// Submit an IBC transfer pub async fn build_ibc_transfer<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args: &args::TxIbcTransfer, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(args.source.clone()); @@ -1375,14 +1379,14 @@ pub async fn build_ibc_transfer<'a>( let source = source_exists_or_err( args.source.clone(), args.tx.force, - context.client, + context.client(), ) .await?; // We cannot check the receiver // validate the amount given let validated_amount = validate_amount( - context.client, + context.client(), args.amount, &args.token, args.tx.force, @@ -1405,7 +1409,7 @@ pub async fn build_ibc_transfer<'a>( validated_amount.amount, balance_key, args.tx.force, - context.client, + context.client(), ) .await?; let tx_source_balance = Some(TxSourcePostBalance { @@ -1415,7 +1419,7 @@ pub async fn build_ibc_transfer<'a>( }); let tx_code_hash = query_wasm_code_hash( - context.client, + context.client(), args.tx_code_path.to_str().unwrap(), ) .await @@ -1425,7 +1429,7 @@ pub async fn build_ibc_transfer<'a>( Address::Internal(InternalAddress::IbcToken(hash)) => { let ibc_denom_key = ibc_denom_key(hash); rpc::query_storage_value::<_, String>( - context.client, + context.client(), &ibc_denom_key, ) .await @@ -1505,7 +1509,7 @@ pub async fn build_ibc_transfer<'a>( /// Abstraction for helping build transactions #[allow(clippy::too_many_arguments)] pub async fn build<'a, F, D>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, tx_args: &crate::sdk::args::Tx, path: PathBuf, data: D, @@ -1531,7 +1535,7 @@ where #[allow(clippy::too_many_arguments)] async fn build_pow_flag<'a, F, D>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, tx_args: &crate::sdk::args::Tx, path: PathBuf, mut data: D, @@ -1548,7 +1552,7 @@ where let mut tx_builder = Tx::new(chain_id, tx_args.expiration); let tx_code_hash = - query_wasm_code_hash(context.client, path.to_string_lossy()) + query_wasm_code_hash(context.client(), path.to_string_lossy()) .await .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; @@ -1571,13 +1575,13 @@ where /// Returns true only if a new decoding has been added to the given set. async fn add_asset_type<'a>( asset_types: &mut HashSet<(Address, MaspDenom, Epoch)>, - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, asset_type: AssetType, ) -> bool { - let context = &mut **context; if let Some(asset_type) = context - .shielded - .decode_asset_type(context.client, asset_type) + .shielded_mut() + .await + .decode_asset_type(context.client(), asset_type) .await { asset_types.insert(asset_type) @@ -1590,7 +1594,7 @@ async fn add_asset_type<'a>( /// function provides the data necessary for offline wallets to present asset /// type information. async fn used_asset_types<'a, P, R, K, N>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, builder: &Builder, ) -> std::result::Result, RpcError> { let mut asset_types = HashSet::new(); @@ -1624,7 +1628,7 @@ async fn used_asset_types<'a, P, R, K, N>( /// Submit an ordinary transfer pub async fn build_transfer<'a, N: Namada<'a>>( - context: &mut N, + context: &N, args: &mut args::TxTransfer, ) -> Result<(Tx, SigningTxData, Option)> { let default_signer = Some(args.source.effective_address()); @@ -1641,15 +1645,17 @@ pub async fn build_transfer<'a, N: Namada<'a>>( let token = args.token.clone(); // Check that the source address exists on chain - source_exists_or_err(source.clone(), args.tx.force, context.client).await?; + source_exists_or_err(source.clone(), args.tx.force, context.client()) + .await?; // Check that the target address exists on chain - target_exists_or_err(target.clone(), args.tx.force, context.client).await?; + target_exists_or_err(target.clone(), args.tx.force, context.client()) + .await?; // Check source balance let balance_key = token::balance_key(&token, &source); // validate the amount given let validated_amount = - validate_amount(context.client, args.amount, &token, args.tx.force) + validate_amount(context.client(), args.amount, &token, args.tx.force) .await?; args.amount = InputAmount::Validated(validated_amount); @@ -1659,7 +1665,7 @@ pub async fn build_transfer<'a, N: Namada<'a>>( validated_amount.amount, balance_key, args.tx.force, - context.client, + context.client(), ) .await?; let tx_source_balance = Some(TxSourcePostBalance { @@ -1792,7 +1798,7 @@ pub async fn build_transfer<'a, N: Namada<'a>>( /// Submit a transaction to initialize an account pub async fn build_init_account<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::TxInitAccount { tx: tx_args, vp_code_path, @@ -1805,7 +1811,7 @@ pub async fn build_init_account<'a>( signing::aux_signing_data(context, tx_args, None, None).await?; let vp_code_hash = - query_wasm_code_hash_buf(context.client, vp_code_path).await?; + query_wasm_code_hash_buf(context.client(), vp_code_path).await?; let threshold = match threshold { Some(threshold) => *threshold, @@ -1845,7 +1851,7 @@ pub async fn build_init_account<'a>( /// Submit a transaction to update a VP pub async fn build_update_account<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::TxUpdateAccount { tx: tx_args, vp_code_path, @@ -1865,7 +1871,7 @@ pub async fn build_update_account<'a>( .await?; let addr = if let Some(account) = - rpc::get_account_info(context.client, addr).await? + rpc::get_account_info(context.client(), addr).await? { account.address } else if tx_args.force { @@ -1877,7 +1883,7 @@ pub async fn build_update_account<'a>( let vp_code_hash = match vp_code_path { Some(code_path) => { let vp_hash = - query_wasm_code_hash_buf(context.client, code_path).await?; + query_wasm_code_hash_buf(context.client(), code_path).await?; Some(vp_hash) } None => None, @@ -1916,7 +1922,7 @@ pub async fn build_update_account<'a>( /// Submit a custom transaction pub async fn build_custom<'a>( - context: &mut impl Namada<'a>, + context: &impl Namada<'a>, args::TxCustom { tx: tx_args, code_path, @@ -1940,7 +1946,7 @@ pub async fn build_custom<'a>( })? } else { let tx_code_hash = query_wasm_code_hash_buf( - context.client, + context.client(), code_path .as_ref() .ok_or(Error::Other("No code path supplied".to_string()))?, From b1bc8450eca547827010532d50375a23c80e75a3 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 4 Oct 2023 06:57:00 +0200 Subject: [PATCH 10/14] Reintegrated generic IO support. --- apps/src/bin/namada-client/main.rs | 1 + apps/src/bin/namada-relayer/main.rs | 3 +- apps/src/bin/namada-wallet/main.rs | 2 +- apps/src/lib/cli.rs | 1 + apps/src/lib/cli/api.rs | 6 +- apps/src/lib/cli/client.rs | 305 +++-- apps/src/lib/cli/context.rs | 16 +- apps/src/lib/cli/relayer.rs | 52 +- apps/src/lib/cli/utils.rs | 3 +- apps/src/lib/cli/wallet.rs | 261 ++-- apps/src/lib/client/rpc.rs | 1135 +++++++++-------- apps/src/lib/client/tx.rs | 474 ++++--- .../lib/node/ledger/shell/testing/client.rs | 6 +- .../lib/node/ledger/shell/testing/utils.rs | 16 +- apps/src/lib/wallet/mod.rs | 4 +- benches/lib.rs | 23 +- shared/src/ledger/eth_bridge.rs | 14 +- shared/src/ledger/eth_bridge/bridge_pool.rs | 270 ++-- shared/src/ledger/eth_bridge/validator_set.rs | 83 +- shared/src/ledger/mod.rs | 104 +- shared/src/sdk/args.rs | 64 +- shared/src/sdk/masp.rs | 84 +- shared/src/sdk/rpc.rs | 125 +- shared/src/sdk/signing.rs | 122 +- shared/src/sdk/tx.rs | 383 +++--- shared/src/types/io.rs | 46 +- 26 files changed, 1782 insertions(+), 1821 deletions(-) diff --git a/apps/src/bin/namada-client/main.rs b/apps/src/bin/namada-client/main.rs index 9b43ca8f91..167674f65e 100644 --- a/apps/src/bin/namada-client/main.rs +++ b/apps/src/bin/namada-client/main.rs @@ -16,6 +16,7 @@ async fn main() -> Result<()> { CliApi::::handle_client_command::( None, cli::namada_client_cli()?, + &CliIo, ) .await } diff --git a/apps/src/bin/namada-relayer/main.rs b/apps/src/bin/namada-relayer/main.rs index 05d2620bcb..ef5e05f913 100644 --- a/apps/src/bin/namada-relayer/main.rs +++ b/apps/src/bin/namada-relayer/main.rs @@ -14,5 +14,6 @@ async fn main() -> Result<()> { let cmd = cli::namada_relayer_cli()?; // run the CLI - CliApi::::handle_relayer_command::(None, cmd).await + CliApi::::handle_relayer_command::(None, cmd, &CliIo) + .await } diff --git a/apps/src/bin/namada-wallet/main.rs b/apps/src/bin/namada-wallet/main.rs index 5e94831716..987e9d2699 100644 --- a/apps/src/bin/namada-wallet/main.rs +++ b/apps/src/bin/namada-wallet/main.rs @@ -6,5 +6,5 @@ pub fn main() -> Result<()> { color_eyre::install()?; let (cmd, ctx) = cli::namada_wallet_cli()?; // run the CLI - CliApi::::handle_wallet_command(cmd, ctx) + CliApi::::handle_wallet_command(cmd, ctx, &CliIo) } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 531027a102..ce5bf600b5 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2534,6 +2534,7 @@ pub mod args { use super::context::*; use super::utils::*; use super::{ArgGroup, ArgMatches}; + use crate::cli::context::FromContext; use crate::config::{self, Action, ActionAtHeight}; use crate::facade::tendermint::Timeout; use crate::facade::tendermint_config::net::Address as TendermintAddress; diff --git a/apps/src/lib/cli/api.rs b/apps/src/lib/cli/api.rs index bb387c5d9a..052a834f55 100644 --- a/apps/src/lib/cli/api.rs +++ b/apps/src/lib/cli/api.rs @@ -13,7 +13,7 @@ use crate::client::utils; #[async_trait::async_trait(?Send)] pub trait CliClient: Client + Sync { fn from_tendermint_address(address: &mut TendermintAddress) -> Self; - async fn wait_until_node_is_synced(&self) -> Halt<()>; + async fn wait_until_node_is_synced(&self, io: &impl Io) -> Halt<()>; } #[async_trait::async_trait(?Send)] @@ -22,8 +22,8 @@ impl CliClient for HttpClient { HttpClient::new(utils::take_config_address(address)).unwrap() } - async fn wait_until_node_is_synced(&self) -> Halt<()> { - wait_until_node_is_synched::<_, IO>(self).await + async fn wait_until_node_is_synced(&self, io: &impl Io) -> Halt<()> { + wait_until_node_is_synched(self, io).await } } diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 449d4f38ce..ac1ca1e34d 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -1,11 +1,9 @@ use color_eyre::eyre::{eyre, Report, Result}; - -use namada::sdk::tx::dump_tx; +use namada::ledger::{Namada, NamadaImpl}; use namada::sdk::signing; +use namada::sdk::tx::dump_tx; use namada::types::control_flow::ProceedOrElse; use namada::types::io::Io; -use namada::ledger::NamadaImpl; -use namada::ledger::Namada; use crate::cli; use crate::cli::api::{CliApi, CliClient}; @@ -21,6 +19,7 @@ impl CliApi { pub async fn handle_client_command( client: Option, cmd: cli::NamadaClient, + io: &IO, ) -> Result<()> where C: CliClient, @@ -38,19 +37,22 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); + let namada = ctx.to_sdk(&client, io); let dry_run = args.tx.dry_run || args.tx.dry_run_wrapper; - tx::submit_custom::<_, IO>(&client, &mut ctx, args) - .await?; + tx::submit_custom(&namada, args).await?; if !dry_run { - crate::wallet::save(&ctx.wallet) + namada + .wallet() + .await + .save() .unwrap_or_else(|err| eprintln!("{}", err)); } else { - IO::println( + io.println( "Transaction dry run. No addresses have been \ saved.", ) @@ -63,12 +65,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_transfer::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_transfer(&namada, args).await?; } Sub::TxIbcTransfer(TxIbcTransfer(mut args)) => { let client = client.unwrap_or_else(|| { @@ -77,12 +79,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_ibc_transfer(&namada, args).await?; } Sub::TxUpdateAccount(TxUpdateAccount(mut args)) => { let client = client.unwrap_or_else(|| { @@ -91,14 +93,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_update_account::<_, IO>( - &client, &mut ctx, args, - ) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_update_account(&namada, args).await?; } Sub::TxInitAccount(TxInitAccount(mut args)) => { let client = client.unwrap_or_else(|| { @@ -107,21 +107,22 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); + let namada = ctx.to_sdk(&client, io); let dry_run = args.tx.dry_run || args.tx.dry_run_wrapper; - tx::submit_init_account::<_, IO>( - &client, &mut ctx, args, - ) - .await?; + tx::submit_init_account(&namada, args).await?; if !dry_run { - crate::wallet::save(&ctx.wallet) + namada + .wallet() + .await + .save() .unwrap_or_else(|err| eprintln!("{}", err)); } else { - IO::println( + io.println( "Transaction dry run. No addresses have been \ saved.", ) @@ -134,12 +135,22 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_init_validator::<_, IO>(&client, ctx, args) - .await?; + let namada = NamadaImpl::new( + &client, + &mut ctx.wallet, + &mut ctx.shielded, + io, + ); + tx::submit_init_validator( + &namada, + &mut ctx.config, + args, + ) + .await?; } Sub::TxInitProposal(TxInitProposal(mut args)) => { let client = client.unwrap_or_else(|| { @@ -148,12 +159,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_init_proposal::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_init_proposal(&namada, args).await?; } Sub::TxVoteProposal(TxVoteProposal(mut args)) => { let client = client.unwrap_or_else(|| { @@ -162,12 +173,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_vote_proposal(&namada, args).await?; } Sub::TxRevealPk(TxRevealPk(mut args)) => { let client = client.unwrap_or_else(|| { @@ -176,12 +187,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk::<_, IO>(&client, &mut ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_reveal_pk(&namada, args).await?; } Sub::Bond(Bond(mut args)) => { let client = client.unwrap_or_else(|| { @@ -190,12 +201,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_bond::<_, IO>(&client, &mut ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_bond(&namada, args).await?; } Sub::Unbond(Unbond(mut args)) => { let client = client.unwrap_or_else(|| { @@ -204,12 +215,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_unbond::<_, IO>(&client, &mut ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_unbond(&namada, args).await?; } Sub::Withdraw(Withdraw(mut args)) => { let client = client.unwrap_or_else(|| { @@ -218,12 +229,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_withdraw::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_withdraw(&namada, args).await?; } Sub::TxCommissionRateChange(TxCommissionRateChange( mut args, @@ -234,14 +245,13 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_validator_commission_change::<_, IO>( - &client, ctx, args, - ) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_validator_commission_change(&namada, args) + .await?; } // Eth bridge Sub::AddToEthBridgePool(args) => { @@ -252,25 +262,19 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); + let namada = ctx.to_sdk(&client, io); let tx_args = args.tx.clone(); - - let namada = NamadaImpl::new( - &client, - &mut ctx.wallet, - &mut ctx.shielded, - ); - let (mut tx, signing_data, _epoch) = args.clone().build(&namada).await?; signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { - dump_tx::(&args.tx, tx); + dump_tx::(io, &args.tx, tx); } else { tx::submit_reveal_aux( &namada, @@ -293,14 +297,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_unjail_validator::<_, IO>( - &client, ctx, args, - ) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_unjail_validator(&namada, args).await?; } Sub::TxUpdateStewardCommission( TxUpdateStewardCommission(mut args), @@ -311,14 +313,13 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_update_steward_commission::<_, IO>( - &client, ctx, args, - ) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_update_steward_commission(&namada, args) + .await?; } Sub::TxResignSteward(TxResignSteward(mut args)) => { let client = client.unwrap_or_else(|| { @@ -327,12 +328,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_resign_steward::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_resign_steward(&namada, args).await?; } // Ledger queries Sub::QueryEpoch(QueryEpoch(mut args)) => { @@ -340,10 +341,11 @@ impl CliApi { C::from_tendermint_address(&mut args.ledger_address) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; - rpc::query_and_print_epoch::<_, IO>(&client).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_and_print_epoch(&namada).await; } Sub::QueryValidatorState(QueryValidatorState(mut args)) => { let client = client.unwrap_or_else(|| { @@ -352,16 +354,13 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_and_print_validator_state::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_and_print_validator_state(&namada, args) + .await; } Sub::QueryTransfers(QueryTransfers(mut args)) => { let client = client.unwrap_or_else(|| { @@ -370,17 +369,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_transfers::<_, _, IO>( - &client, - &mut ctx.wallet, - &mut ctx.shielded, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_transfers(&namada, args).await; } Sub::QueryConversions(QueryConversions(mut args)) => { let client = client.unwrap_or_else(|| { @@ -389,26 +383,23 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_conversions::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_conversions(&namada, args).await; } Sub::QueryBlock(QueryBlock(mut args)) => { let client = client.unwrap_or_else(|| { C::from_tendermint_address(&mut args.ledger_address) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; - rpc::query_block::<_, IO>(&client).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_block(&namada).await; } Sub::QueryBalance(QueryBalance(mut args)) => { let client = client.unwrap_or_else(|| { @@ -417,17 +408,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_balance::<_, _, IO>( - &client, - &mut ctx.wallet, - &mut ctx.shielded, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_balance(&namada, args).await; } Sub::QueryBonds(QueryBonds(mut args)) => { let client = client.unwrap_or_else(|| { @@ -436,17 +422,14 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_bonds::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await - .expect("expected successful query of bonds"); + let namada = ctx.to_sdk(&client, io); + rpc::query_bonds(&namada, args) + .await + .expect("expected successful query of bonds"); } Sub::QueryBondedStake(QueryBondedStake(mut args)) => { let client = client.unwrap_or_else(|| { @@ -455,11 +438,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_bonded_stake::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_bonded_stake(&namada, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(mut args)) => { let client = client.unwrap_or_else(|| { @@ -468,16 +452,13 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_and_print_commission_rate::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_and_print_commission_rate(&namada, args) + .await; } Sub::QuerySlashes(QuerySlashes(mut args)) => { let client = client.unwrap_or_else(|| { @@ -486,16 +467,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_slashes::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_slashes(&namada, args).await; } Sub::QueryDelegations(QueryDelegations(mut args)) => { let client = client.unwrap_or_else(|| { @@ -504,16 +481,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_delegations::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_delegations(&namada, args).await; } Sub::QueryFindValidator(QueryFindValidator(mut args)) => { let client = client.unwrap_or_else(|| { @@ -522,11 +495,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_find_validator::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_find_validator(&namada, args).await; } Sub::QueryResult(QueryResult(mut args)) => { let client = client.unwrap_or_else(|| { @@ -535,11 +509,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_result::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_result(&namada, args).await; } Sub::QueryRawBytes(QueryRawBytes(mut args)) => { let client = client.unwrap_or_else(|| { @@ -548,11 +523,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_raw_bytes::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_raw_bytes(&namada, args).await; } Sub::QueryProposal(QueryProposal(mut args)) => { let client = client.unwrap_or_else(|| { @@ -561,11 +537,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_proposal::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_proposal(&namada, args).await; } Sub::QueryProposalResult(QueryProposalResult(mut args)) => { let client = client.unwrap_or_else(|| { @@ -574,12 +551,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_proposal_result::<_, IO>(&client, args) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_proposal_result(&namada, args).await; } Sub::QueryProtocolParameters(QueryProtocolParameters( mut args, @@ -590,12 +567,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_protocol_parameters::<_, IO>(&client, args) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_protocol_parameters(&namada, args).await; } Sub::QueryPgf(QueryPgf(mut args)) => { let client = client.unwrap_or_else(|| { @@ -604,11 +581,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_pgf::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_pgf(&namada, args).await; } Sub::QueryAccount(QueryAccount(mut args)) => { let client = client.unwrap_or_else(|| { @@ -617,11 +595,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_account::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_account(&namada, args).await; } Sub::SignTx(SignTx(mut args)) => { let client = client.unwrap_or_else(|| { @@ -630,11 +609,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::sign_tx::<_, IO>(&client, &mut ctx, args).await?; + let namada = ctx.to_sdk(&client, io); + tx::sign_tx(&namada, args).await?; } } } @@ -668,11 +648,12 @@ impl CliApi { let client = C::from_tendermint_address(&mut ledger_address); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::epoch_sleep::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::epoch_sleep(&namada, args).await; } }, } diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index f2efec7fee..4772ef98b9 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -6,9 +6,10 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use color_eyre::eyre::Result; +use namada::ledger::{Namada, NamadaImpl}; +use namada::sdk::masp::fs::FsShieldedUtils; use namada::sdk::masp::ShieldedContext; use namada::sdk::wallet::Wallet; -use namada::sdk::masp::fs::FsShieldedUtils; use namada::types::address::{Address, InternalAddress}; use namada::types::chain::ChainId; use namada::types::ethereum_events::EthAddress; @@ -150,6 +151,19 @@ impl Context { }) } + /// Make an implementation of Namada from this object and parameters. + pub fn to_sdk<'a, C, IO>( + &'a mut self, + client: &'a C, + io: &'a IO, + ) -> impl Namada + where + C: namada::ledger::queries::Client + Sync, + IO: Io, + { + NamadaImpl::new(client, &mut self.wallet, &mut self.shielded, io) + } + /// Parse and/or look-up the value from the context. pub fn get(&self, from_context: &FromContext) -> T where diff --git a/apps/src/lib/cli/relayer.rs b/apps/src/lib/cli/relayer.rs index 3322e84e2f..d94fd5a09d 100644 --- a/apps/src/lib/cli/relayer.rs +++ b/apps/src/lib/cli/relayer.rs @@ -19,6 +19,7 @@ impl CliApi { pub async fn handle_relayer_command( client: Option, cmd: cli::NamadaRelayer, + io: &IO, ) -> Result<()> where C: CliClient, @@ -36,11 +37,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - bridge_pool::recommend_batch::<_, IO>(&client, args) + let namada = ctx.to_sdk(&client, io); + bridge_pool::recommend_batch(&namada, args) .await .proceed_or_else(error)?; } @@ -56,11 +58,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - bridge_pool::construct_proof::<_, IO>(&client, args) + bridge_pool::construct_proof(&client, io, args) .await .proceed_or_else(error)?; } @@ -71,7 +73,7 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let eth_client = Arc::new( @@ -79,8 +81,8 @@ impl CliApi { .unwrap(), ); let args = args.to_sdk_ctxless(); - bridge_pool::relay_bridge_pool_proof::<_, _, IO>( - eth_client, &client, args, + bridge_pool::relay_bridge_pool_proof( + eth_client, &client, io, args, ) .await .proceed_or_else(error)?; @@ -92,10 +94,10 @@ impl CliApi { C::from_tendermint_address(&mut query.ledger_address) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; - bridge_pool::query_bridge_pool::<_, IO>(&client).await; + bridge_pool::query_bridge_pool(&client, io).await; } EthBridgePoolWithoutCtx::QuerySigned( QuerySignedBridgePool(mut query), @@ -104,10 +106,10 @@ impl CliApi { C::from_tendermint_address(&mut query.ledger_address) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; - bridge_pool::query_signed_bridge_pool::<_, IO>(&client) + bridge_pool::query_signed_bridge_pool(&client, io) .await .proceed_or_else(error)?; } @@ -118,10 +120,10 @@ impl CliApi { C::from_tendermint_address(&mut query.ledger_address) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; - bridge_pool::query_relay_progress::<_, IO>(&client).await; + bridge_pool::query_relay_progress(&client, io).await; } }, cli::NamadaRelayer::ValidatorSet(sub) => match sub { @@ -134,12 +136,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - validator_set::query_bridge_validator_set::<_, IO>( - &client, args, + validator_set::query_bridge_validator_set( + &client, io, args, ) .await; } @@ -152,12 +154,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - validator_set::query_governnace_validator_set::<_, IO>( - &client, args, + validator_set::query_governnace_validator_set( + &client, io, args, ) .await; } @@ -170,12 +172,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - validator_set::query_validator_set_update_proof::<_, IO>( - &client, args, + validator_set::query_validator_set_update_proof( + &client, io, args, ) .await; } @@ -188,7 +190,7 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let eth_client = Arc::new( @@ -196,8 +198,8 @@ impl CliApi { .unwrap(), ); let args = args.to_sdk_ctxless(); - validator_set::relay_validator_set_update::<_, _, IO>( - eth_client, &client, args, + validator_set::relay_validator_set_update( + eth_client, &client, io, args, ) .await .proceed_or_else(error)?; diff --git a/apps/src/lib/cli/utils.rs b/apps/src/lib/cli/utils.rs index 26cc38ff7f..7c8bc4100c 100644 --- a/apps/src/lib/cli/utils.rs +++ b/apps/src/lib/cli/utils.rs @@ -8,8 +8,9 @@ use clap::{ArgAction, ArgMatches}; use color_eyre::eyre::Result; use super::args; -use super::context::{Context, FromContext}; +use super::context::Context; use crate::cli::api::CliIo; +use crate::cli::context::FromContext; // We only use static strings pub type App = clap::Command; diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 33b443edd9..5dc223cd64 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -8,11 +8,15 @@ use color_eyre::eyre::Result; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::sdk::masp::find_valid_diversifier; -use namada::sdk::wallet::{DecryptionError, FindKeyError, GenRestoreKeyError}; +use namada::sdk::wallet::{ + DecryptionError, FindKeyError, GenRestoreKeyError, Wallet, WalletIo, + WalletStorage, +}; use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; use namada::{display, display_line, edisplay_line}; +use rand::RngCore; use rand_core::OsRng; use crate::cli; @@ -25,61 +29,62 @@ impl CliApi { pub fn handle_wallet_command( cmd: cmds::NamadaWallet, mut ctx: Context, + io: &impl Io, ) -> Result<()> { match cmd { cmds::NamadaWallet::Key(sub) => match sub { cmds::WalletKey::Restore(cmds::KeyRestore(args)) => { - key_and_address_restore::(ctx, args) + key_and_address_restore(&mut ctx.wallet, io, args) } cmds::WalletKey::Gen(cmds::KeyGen(args)) => { - key_and_address_gen::(ctx, args) + key_and_address_gen(&mut ctx.wallet, io, &mut OsRng, args) } cmds::WalletKey::Find(cmds::KeyFind(args)) => { - key_find::(ctx, args) + key_find(&mut ctx.wallet, io, args) } cmds::WalletKey::List(cmds::KeyList(args)) => { - key_list::(ctx, args) + key_list(&mut ctx.wallet, io, args) } cmds::WalletKey::Export(cmds::Export(args)) => { - key_export::(ctx, args) + key_export(&mut ctx.wallet, io, args) } }, cmds::NamadaWallet::Address(sub) => match sub { cmds::WalletAddress::Gen(cmds::AddressGen(args)) => { - key_and_address_gen::(ctx, args) + key_and_address_gen(&mut ctx.wallet, io, &mut OsRng, args) } cmds::WalletAddress::Restore(cmds::AddressRestore(args)) => { - key_and_address_restore::(ctx, args) + key_and_address_restore(&mut ctx.wallet, io, args) } cmds::WalletAddress::Find(cmds::AddressOrAliasFind(args)) => { - address_or_alias_find::(ctx, args) + address_or_alias_find(&mut ctx.wallet, io, args) } cmds::WalletAddress::List(cmds::AddressList) => { - address_list::(ctx) + address_list(&mut ctx.wallet, io) } cmds::WalletAddress::Add(cmds::AddressAdd(args)) => { - address_add::(ctx, args) + address_add(&mut ctx.wallet, io, args) } }, cmds::NamadaWallet::Masp(sub) => match sub { cmds::WalletMasp::GenSpendKey(cmds::MaspGenSpendKey(args)) => { - spending_key_gen::(ctx, args) + spending_key_gen(&mut ctx.wallet, io, args) } cmds::WalletMasp::GenPayAddr(cmds::MaspGenPayAddr(args)) => { let args = args.to_sdk(&mut ctx); - payment_address_gen::(ctx, args) + payment_address_gen(&mut ctx.wallet, io, args) } cmds::WalletMasp::AddAddrKey(cmds::MaspAddAddrKey(args)) => { - address_key_add::(ctx, args) + address_key_add(&mut ctx.wallet, io, args) } cmds::WalletMasp::ListPayAddrs(cmds::MaspListPayAddrs) => { - payment_addresses_list::(ctx) + payment_addresses_list(&mut ctx.wallet, io) } cmds::WalletMasp::ListKeys(cmds::MaspListKeys(args)) => { - spending_keys_list::(ctx, args) + spending_keys_list(&mut ctx.wallet, io, args) } cmds::WalletMasp::FindAddrKey(cmds::MaspFindAddrKey(args)) => { - address_key_find::(ctx, args) + address_key_find(&mut ctx.wallet, io, args) } }, } @@ -88,35 +93,35 @@ impl CliApi { } /// Find shielded address or key -fn address_key_find( - ctx: Context, +fn address_key_find( + wallet: &mut Wallet, + io: &impl Io, args::AddrKeyFind { alias, unsafe_show_secret, }: args::AddrKeyFind, ) { - let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); if let Ok(viewing_key) = wallet.find_viewing_key(&alias) { // Check if alias is a viewing key - display_line!(IO, "Viewing key: {}", viewing_key); + display_line!(io, "Viewing key: {}", viewing_key); if unsafe_show_secret { // Check if alias is also a spending key match wallet.find_spending_key(&alias, None) { Ok(spending_key) => { - display_line!(IO, "Spending key: {}", spending_key) + display_line!(io, "Spending key: {}", spending_key) } Err(FindKeyError::KeyNotFound) => {} - Err(err) => edisplay_line!(IO, "{}", err), + Err(err) => edisplay_line!(io, "{}", err), } } } else if let Some(payment_addr) = wallet.find_payment_addr(&alias) { // Failing that, check if alias is a payment address - display_line!(IO, "Payment address: {}", payment_addr); + display_line!(io, "Payment address: {}", payment_addr); } else { // Otherwise alias cannot be referring to any shielded value display_line!( - IO, + io, "No shielded address or key with alias {} found. Use the commands \ `masp list-addrs` and `masp list-keys` to see all the known \ addresses and keys.", @@ -126,44 +131,44 @@ fn address_key_find( } /// List spending keys. -fn spending_keys_list( - ctx: Context, +fn spending_keys_list( + wallet: &mut Wallet, + io: &impl Io, args::MaspKeysList { decrypt, unsafe_show_secret, }: args::MaspKeysList, ) { - let wallet = ctx.wallet; let known_view_keys = wallet.get_viewing_keys(); let known_spend_keys = wallet.get_spending_keys(); if known_view_keys.is_empty() { display_line!( - IO, + io, "No known keys. Try `masp add --alias my-addr --value ...` to add \ a new key to the wallet.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, &mut w; "Known keys:").unwrap(); + display_line!(io, &mut w; "Known keys:").unwrap(); for (alias, key) in known_view_keys { - display!(IO, &mut w; " Alias \"{}\"", alias).unwrap(); + display!(io, &mut w; " Alias \"{}\"", alias).unwrap(); let spending_key_opt = known_spend_keys.get(&alias); // If this alias is associated with a spending key, indicate whether // or not the spending key is encrypted // TODO: consider turning if let into match if let Some(spending_key) = spending_key_opt { if spending_key.is_encrypted() { - display_line!(IO, &mut w; " (encrypted):") + display_line!(io, &mut w; " (encrypted):") } else { - display_line!(IO, &mut w; " (not encrypted):") + display_line!(io, &mut w; " (not encrypted):") } .unwrap(); } else { - display_line!(IO, &mut w; ":").unwrap(); + display_line!(io, &mut w; ":").unwrap(); } // Always print the corresponding viewing key - display_line!(IO, &mut w; " Viewing Key: {}", key).unwrap(); + display_line!(io, &mut w; " Viewing Key: {}", key).unwrap(); // A subset of viewing keys will have corresponding spending keys. // Print those too if they are available and requested. if unsafe_show_secret { @@ -172,7 +177,7 @@ fn spending_keys_list( // Here the spending key is unencrypted or successfully // decrypted Ok(spending_key) => { - display_line!(IO, + display_line!(io, &mut w; " Spending key: {}", spending_key, ) @@ -186,7 +191,7 @@ fn spending_keys_list( // Here the key is encrypted but incorrect password has // been provided Err(err) => { - display_line!(IO, + display_line!(io, &mut w; " Couldn't decrypt the spending key: {}", err, @@ -201,49 +206,52 @@ fn spending_keys_list( } /// List payment addresses. -fn payment_addresses_list(ctx: Context) { - let wallet = ctx.wallet; +fn payment_addresses_list( + wallet: &mut Wallet, + io: &impl Io, +) { let known_addresses = wallet.get_payment_addrs(); if known_addresses.is_empty() { display_line!( - IO, + io, "No known payment addresses. Try `masp gen-addr --alias my-addr` \ to generate a new payment address.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, &mut w; "Known payment addresses:").unwrap(); + display_line!(io, &mut w; "Known payment addresses:").unwrap(); for (alias, address) in sorted(known_addresses) { - display_line!(IO, &mut w; " \"{}\": {}", alias, address).unwrap(); + display_line!(io, &mut w; " \"{}\": {}", alias, address).unwrap(); } } } /// Generate a spending key. -fn spending_key_gen( - ctx: Context, +fn spending_key_gen( + wallet: &mut Wallet, + io: &impl Io, args::MaspSpendKeyGen { alias, alias_force, unsafe_dont_encrypt, }: args::MaspSpendKeyGen, ) { - let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let (alias, _key) = wallet.gen_spending_key(alias, password, alias_force); - crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); + wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); display_line!( - IO, + io, "Successfully added a spending key with alias: \"{}\"", alias ); } /// Generate a shielded payment address from the given key. -fn payment_address_gen( - ctx: Context, +fn payment_address_gen( + wallet: &mut Wallet, + io: &impl Io, args::MaspPayAddrGen { alias, alias_force, @@ -257,7 +265,6 @@ fn payment_address_gen( let payment_addr = viewing_key .to_payment_address(div) .expect("a PaymentAddress"); - let mut wallet = ctx.wallet; let alias = wallet .insert_payment_addr( alias, @@ -265,20 +272,21 @@ fn payment_address_gen( alias_force, ) .unwrap_or_else(|| { - edisplay_line!(IO, "Payment address not added"); + edisplay_line!(io, "Payment address not added"); cli::safe_exit(1); }); - crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); + wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); display_line!( - IO, + io, "Successfully generated a payment address with the following alias: {}", alias, ); } /// Add a viewing key, spending key, or payment address to wallet. -fn address_key_add( - mut ctx: Context, +fn address_key_add( + wallet: &mut Wallet, + io: &impl Io, args::MaspAddrKeyAdd { alias, alias_force, @@ -289,11 +297,10 @@ fn address_key_add( let alias = alias.to_lowercase(); let (alias, typ) = match value { MaspValue::FullViewingKey(viewing_key) => { - let alias = ctx - .wallet + let alias = wallet .insert_viewing_key(alias, viewing_key, alias_force) .unwrap_or_else(|| { - edisplay_line!(IO, "Viewing key not added"); + edisplay_line!(io, "Viewing key not added"); cli::safe_exit(1); }); (alias, "viewing key") @@ -301,8 +308,7 @@ fn address_key_add( MaspValue::ExtendedSpendingKey(spending_key) => { let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let alias = ctx - .wallet + let alias = wallet .encrypt_insert_spending_key( alias, spending_key, @@ -310,25 +316,24 @@ fn address_key_add( alias_force, ) .unwrap_or_else(|| { - edisplay_line!(IO, "Spending key not added"); + edisplay_line!(io, "Spending key not added"); cli::safe_exit(1); }); (alias, "spending key") } MaspValue::PaymentAddress(payment_addr) => { - let alias = ctx - .wallet + let alias = wallet .insert_payment_addr(alias, payment_addr, alias_force) .unwrap_or_else(|| { - edisplay_line!(IO, "Payment address not added"); + edisplay_line!(io, "Payment address not added"); cli::safe_exit(1); }); (alias, "payment address") } }; - crate::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); + wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); display_line!( - IO, + io, "Successfully added a {} with the following alias to wallet: {}", typ, alias, @@ -337,8 +342,9 @@ fn address_key_add( /// Restore a keypair and an implicit address from the mnemonic code in the /// wallet. -fn key_and_address_restore( - ctx: Context, +fn key_and_address_restore( + wallet: &mut Wallet, + io: &impl Io, args::KeyAndAddressRestore { scheme, alias, @@ -347,7 +353,6 @@ fn key_and_address_restore( derivation_path, }: args::KeyAndAddressRestore, ) { - let mut wallet = ctx.wallet; let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let (alias, _key) = wallet @@ -360,17 +365,18 @@ fn key_and_address_restore( encryption_password, ) .unwrap_or_else(|err| { - edisplay_line!(IO, "{}", err); + edisplay_line!(io, "{}", err); cli::safe_exit(1) }) .unwrap_or_else(|| { - display_line!(IO, "No changes are persisted. Exiting."); + display_line!(io, "No changes are persisted. Exiting."); cli::safe_exit(0); }); - crate::wallet::save(&wallet) - .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); + wallet + .save() + .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( - IO, + io, "Successfully added a key and an address with alias: \"{}\"", alias ); @@ -378,8 +384,10 @@ fn key_and_address_restore( /// Generate a new keypair and derive implicit address from it and store them in /// the wallet. -fn key_and_address_gen( - ctx: Context, +fn key_and_address_gen( + wallet: &mut Wallet>, + io: &impl Io, + rng: &mut R, args::KeyAndAddressGen { scheme, alias, @@ -388,12 +396,9 @@ fn key_and_address_gen( derivation_path, }: args::KeyAndAddressGen, ) { - let mut wallet = ctx.wallet; let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let mut rng = OsRng; - let derivation_path_and_mnemonic_rng = - derivation_path.map(|p| (p, &mut rng)); + let derivation_path_and_mnemonic_rng = derivation_path.map(|p| (p, rng)); let (alias, _key, _mnemonic) = wallet .gen_key( scheme, @@ -413,18 +418,20 @@ fn key_and_address_gen( cli::safe_exit(1); } }); - crate::wallet::save(&wallet) - .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); + wallet + .save() + .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( - IO, + io, "Successfully added a key and an address with alias: \"{}\"", alias ); } /// Find a keypair in the wallet store. -fn key_find( - ctx: Context, +fn key_find( + wallet: &mut Wallet, + io: &impl Io, args::KeyFind { public_key, alias, @@ -432,7 +439,6 @@ fn key_find( unsafe_show_secret, }: args::KeyFind, ) { - let mut wallet = ctx.wallet; let found_keypair = match public_key { Some(pk) => wallet.find_key_by_pk(&pk, None), None => { @@ -440,7 +446,7 @@ fn key_find( match alias { None => { edisplay_line!( - IO, + io, "An alias, public key or public key hash needs to be \ supplied", ); @@ -453,62 +459,62 @@ fn key_find( match found_keypair { Ok(keypair) => { let pkh: PublicKeyHash = (&keypair.ref_to()).into(); - display_line!(IO, "Public key hash: {}", pkh); - display_line!(IO, "Public key: {}", keypair.ref_to()); + display_line!(io, "Public key hash: {}", pkh); + display_line!(io, "Public key: {}", keypair.ref_to()); if unsafe_show_secret { - display_line!(IO, "Secret key: {}", keypair); + display_line!(io, "Secret key: {}", keypair); } } Err(err) => { - edisplay_line!(IO, "{}", err); + edisplay_line!(io, "{}", err); } } } /// List all known keys. -fn key_list( - ctx: Context, +fn key_list( + wallet: &mut Wallet, + io: &impl Io, args::KeyList { decrypt, unsafe_show_secret, }: args::KeyList, ) { - let wallet = ctx.wallet; let known_keys = wallet.get_keys(); if known_keys.is_empty() { display_line!( - IO, + io, "No known keys. Try `key gen --alias my-key` to generate a new \ key.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, &mut w; "Known keys:").unwrap(); + display_line!(io, &mut w; "Known keys:").unwrap(); for (alias, (stored_keypair, pkh)) in known_keys { let encrypted = if stored_keypair.is_encrypted() { "encrypted" } else { "not encrypted" }; - display_line!(IO, + display_line!(io, &mut w; " Alias \"{}\" ({}):", alias, encrypted, ) .unwrap(); if let Some(pkh) = pkh { - display_line!(IO, &mut w; " Public key hash: {}", pkh) + display_line!(io, &mut w; " Public key hash: {}", pkh) .unwrap(); } match stored_keypair.get::(decrypt, None) { Ok(keypair) => { - display_line!(IO, + display_line!(io, &mut w; " Public key: {}", keypair.ref_to(), ) .unwrap(); if unsafe_show_secret { - display_line!(IO, + display_line!(io, &mut w; " Secret key: {}", keypair, ) @@ -519,7 +525,7 @@ fn key_list( continue; } Err(err) => { - display_line!(IO, + display_line!(io, &mut w; " Couldn't decrypt the keypair: {}", err, ) @@ -531,11 +537,11 @@ fn key_list( } /// Export a keypair to a file. -fn key_export( - ctx: Context, +fn key_export( + wallet: &mut Wallet, + io: &impl Io, args::KeyExport { alias }: args::KeyExport, ) { - let mut wallet = ctx.wallet; wallet .find_key(alias.to_lowercase(), None) .map(|keypair| { @@ -546,30 +552,32 @@ fn key_export( let mut file = File::create(&file_name).unwrap(); file.write_all(file_data.as_ref()).unwrap(); - display_line!(IO, "Exported to file {}", file_name); + display_line!(io, "Exported to file {}", file_name); }) .unwrap_or_else(|err| { - edisplay_line!(IO, "{}", err); + edisplay_line!(io, "{}", err); cli::safe_exit(1) }) } /// List all known addresses. -fn address_list(ctx: Context) { - let wallet = ctx.wallet; +fn address_list( + wallet: &mut Wallet, + io: &impl Io, +) { let known_addresses = wallet.get_addresses(); if known_addresses.is_empty() { display_line!( - IO, + io, "No known addresses. Try `address gen --alias my-addr` to \ generate a new implicit address.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, &mut w; "Known addresses:").unwrap(); + display_line!(io, &mut w; "Known addresses:").unwrap(); for (alias, address) in sorted(known_addresses) { - display_line!(IO, + display_line!(io, &mut w; " \"{}\": {}", alias, address.to_pretty_string(), ) @@ -579,8 +587,11 @@ fn address_list(ctx: Context) { } /// Find address (alias) by its alias (address). -fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { - let wallet = ctx.wallet; +fn address_or_alias_find( + wallet: &mut Wallet, + io: &impl Io, + args: args::AddressOrAliasFind, +) { if args.address.is_some() && args.alias.is_some() { panic!( "This should not be happening: clap should emit its own error \ @@ -589,10 +600,10 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { } else if args.alias.is_some() { if let Some(address) = wallet.find_address(args.alias.as_ref().unwrap()) { - display_line!(IO, "Found address {}", address.to_pretty_string()); + display_line!(io, "Found address {}", address.to_pretty_string()); } else { display_line!( - IO, + io, "No address with alias {} found. Use the command `address \ list` to see all the known addresses.", args.alias.unwrap().to_lowercase() @@ -600,10 +611,10 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { } } else if args.address.is_some() { if let Some(alias) = wallet.find_alias(args.address.as_ref().unwrap()) { - display_line!(IO, "Found alias {}", alias); + display_line!(io, "Found alias {}", alias); } else { display_line!( - IO, + io, "No alias with address {} found. Use the command `address \ list` to see all the known addresses.", args.address.unwrap() @@ -613,8 +624,11 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { } /// Add an address to the wallet. -fn address_add(ctx: Context, args: args::AddressAdd) { - let mut wallet = ctx.wallet; +fn address_add( + wallet: &mut Wallet, + io: &impl Io, + args: args::AddressAdd, +) { if wallet .add_address( args.alias.clone().to_lowercase(), @@ -623,13 +637,14 @@ fn address_add(ctx: Context, args: args::AddressAdd) { ) .is_none() { - edisplay_line!(IO, "Address not added"); + edisplay_line!(io, "Address not added"); cli::safe_exit(1); } - crate::wallet::save(&wallet) - .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); + wallet + .save() + .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( - IO, + io, "Successfully added a key and an address with alias: \"{}\"", args.alias.to_lowercase() ); diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 72d6514c35..24b7708f42 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -33,17 +33,16 @@ use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::{CommissionPair, PosParams, Slash}; use namada::ledger::queries::RPC; use namada::ledger::storage::ConversionState; +use namada::ledger::Namada; use namada::proof_of_stake::types::{ValidatorState, WeightedValidator}; use namada::sdk::error; use namada::sdk::error::{is_pinned_error, Error, PinnedBalanceError}; -use namada::sdk::masp::{ - Conversions, MaspAmount, MaspChange, ShieldedContext, ShieldedUtils, -}; +use namada::sdk::masp::{Conversions, MaspAmount, MaspChange}; use namada::sdk::rpc::{ self, enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, TxResponse, }; -use namada::sdk::wallet::{AddressVpType, Wallet}; +use namada::sdk::wallet::AddressVpType; use namada::types::address::{masp, Address}; use namada::types::control_flow::ProceedOrElse; use namada::types::hash::Hash; @@ -59,46 +58,37 @@ use tokio::time::Instant; use crate::cli::{self, args}; use crate::facade::tendermint::merkle::proof::Proof; use crate::facade::tendermint_rpc::error::Error as TError; -use crate::wallet::CliWalletUtils; /// Query the status of a given transaction. /// /// If a response is not delivered until `deadline`, we exit the cli with an /// error. -pub async fn query_tx_status< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_tx_status<'a>( + namada: &impl Namada<'a>, status: namada::sdk::rpc::TxEventQuery<'_>, deadline: Instant, ) -> Event { - rpc::query_tx_status::<_, IO>(client, status, deadline) + rpc::query_tx_status(namada, status, deadline) .await .proceed() } /// Query and print the epoch of the last committed block -pub async fn query_and_print_epoch< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, -) -> Epoch { - let epoch = rpc::query_epoch(client).await.unwrap(); - display_line!(IO, "Last committed epoch: {}", epoch); +pub async fn query_and_print_epoch<'a>(context: &impl Namada<'a>) -> Epoch { + let epoch = rpc::query_epoch(context.client()).await.unwrap(); + display_line!(context.io(), "Last committed epoch: {}", epoch); epoch } /// Query the last committed block -pub async fn query_block( - client: &C, -) { - let block = namada::sdk::rpc::query_block(client).await.unwrap(); +pub async fn query_block<'a>(context: &impl Namada<'a>) { + let block = namada::sdk::rpc::query_block(context.client()) + .await + .unwrap(); match block { Some(block) => { display_line!( - IO, + context.io(), "Last committed block ID: {}, height: {}, time: {}", block.hash, block.height, @@ -106,7 +96,7 @@ pub async fn query_block( ); } None => { - display_line!(IO, "No block has been committed yet."); + display_line!(context.io(), "No block has been committed yet."); } } } @@ -122,26 +112,22 @@ pub async fn query_results( } /// Query the specified accepted transfers from the ledger -pub async fn query_transfers< - C: namada::ledger::queries::Client + Sync, - U: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn query_transfers<'a>( + context: &impl Namada<'a>, args: args::QueryTransfers, ) { let query_token = args.token; + let wallet = context.wallet().await; let query_owner = args.owner.map_or_else( || Either::Right(wallet.get_addresses().into_values().collect()), Either::Left, ); + let mut shielded = context.shielded_mut().await; let _ = shielded.load().await; // Obtain the effects of all shielded and transparent transactions let transfers = shielded .query_tx_deltas( - client, + context.client(), &query_owner, &query_token, &wallet.get_viewing_keys(), @@ -174,7 +160,7 @@ pub async fn query_transfers< // transaction's reception let amt = shielded .compute_exchanged_amount( - client, + context, amt, epoch, Conversions::new(), @@ -182,7 +168,8 @@ pub async fn query_transfers< .await .unwrap() .0; - let dec = shielded.decode_amount(client, amt, epoch).await; + let dec = + shielded.decode_amount(context.client(), amt, epoch).await; shielded_accounts.insert(acc, dec); } // Check if this transfer pertains to the supplied token @@ -205,7 +192,7 @@ pub async fn query_transfers< continue; } display_line!( - IO, + context.io(), "Height: {}, Index: {}, Transparent Transfer:", height, idx @@ -213,7 +200,7 @@ pub async fn query_transfers< // Display the transparent changes first for (account, MaspChange { ref asset, change }) in tfer_delta { if account != masp() { - display!(IO, " {}:", account); + display!(context.io(), " {}:", account); let token_alias = wallet.lookup_alias(asset); let sign = match change.cmp(&Change::zero()) { Ordering::Greater => "+", @@ -221,26 +208,22 @@ pub async fn query_transfers< Ordering::Equal => "", }; display!( - IO, + context.io(), " {}{} {}", sign, - format_denominated_amount( - client, - asset, - change.into(), - ) - .await, + format_denominated_amount(context, asset, change.into(),) + .await, token_alias ); } - display_line!(IO, ""); + display_line!(context.io(), ""); } // Then display the shielded changes afterwards // TODO: turn this to a display impl // (account, amt) for (account, masp_change) in shielded_accounts { if fvk_map.contains_key(&account) { - display!(IO, " {}:", fvk_map[&account]); + display!(context.io(), " {}:", fvk_map[&account]); for (token_addr, val) in masp_change { let token_alias = wallet.lookup_alias(&token_addr); let sign = match val.cmp(&Change::zero()) { @@ -249,11 +232,11 @@ pub async fn query_transfers< Ordering::Equal => "", }; display!( - IO, + context.io(), " {}{} {}", sign, format_denominated_amount( - client, + context, &token_addr, val.into(), ) @@ -261,113 +244,102 @@ pub async fn query_transfers< token_alias, ); } - display_line!(IO, ""); + display_line!(context.io(), ""); } } } } /// Query the raw bytes of given storage key -pub async fn query_raw_bytes< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_raw_bytes<'a, N: Namada<'a>>( + context: &N, args: args::QueryRawBytes, ) { - let response = unwrap_client_response::( + let response = unwrap_client_response::( RPC.shell() - .storage_value(client, None, None, false, &args.storage_key) + .storage_value( + context.client(), + None, + None, + false, + &args.storage_key, + ) .await, ); if !response.data.is_empty() { - display_line!(IO, "Found data: 0x{}", HEXLOWER.encode(&response.data)); + display_line!( + context.io(), + "Found data: 0x{}", + HEXLOWER.encode(&response.data) + ); } else { - display_line!(IO, "No data found for key {}", args.storage_key); + display_line!( + context.io(), + "No data found for key {}", + args.storage_key + ); } } /// Query token balance(s) -pub async fn query_balance< - C: namada::ledger::queries::Client + Sync, - U: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn query_balance<'a>( + context: &impl Namada<'a>, args: args::QueryBalance, ) { // Query the balances of shielded or transparent account types depending on // the CLI arguments match &args.owner { Some(BalanceOwner::FullViewingKey(_viewing_key)) => { - query_shielded_balance::<_, _, IO>(client, wallet, shielded, args) - .await + query_shielded_balance(context, args).await } Some(BalanceOwner::Address(_owner)) => { - query_transparent_balance::<_, IO>(client, wallet, args).await + query_transparent_balance(context, args).await } Some(BalanceOwner::PaymentAddress(_owner)) => { - query_pinned_balance::<_, _, IO>(client, wallet, shielded, args) - .await + query_pinned_balance(context, args).await } None => { // Print pinned balance - query_pinned_balance::<_, _, IO>( - client, - wallet, - shielded, - args.clone(), - ) - .await; + query_pinned_balance(context, args.clone()).await; // Print shielded balance - query_shielded_balance::<_, _, IO>( - client, - wallet, - shielded, - args.clone(), - ) - .await; + query_shielded_balance(context, args.clone()).await; // Then print transparent balance - query_transparent_balance::<_, IO>(client, wallet, args).await; + query_transparent_balance(context, args).await; } }; } /// Query token balance(s) -pub async fn query_transparent_balance< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn query_transparent_balance<'a>( + context: &impl Namada<'a>, args: args::QueryBalance, ) { let prefix = Key::from( Address::Internal(namada::types::address::InternalAddress::Multitoken) .to_db_key(), ); - let tokens = wallet.tokens_with_aliases(); + let tokens = context.wallet().await.tokens_with_aliases(); match (args.token, args.owner) { (Some(token), Some(owner)) => { let balance_key = token::balance_key(&token, &owner.address().unwrap()); - let token_alias = wallet.lookup_alias(&token); - match query_storage_value::(client, &balance_key) - .await + let token_alias = context.wallet().await.lookup_alias(&token); + match query_storage_value::<_, token::Amount>( + context.client(), + &balance_key, + ) + .await { Ok(balance) => { - let balance = format_denominated_amount( - client, &token, balance, - ) - .await; - display_line!(IO, "{}: {}", token_alias, balance); + let balance = + format_denominated_amount(context, &token, balance) + .await; + display_line!(context.io(), "{}: {}", token_alias, balance); } Err(e) => { - display_line!(IO, "Eror in querying: {e}"); + display_line!(context.io(), "Eror in querying: {e}"); display_line!( - IO, + context.io(), "No {} balance found for {}", token_alias, owner @@ -378,56 +350,40 @@ pub async fn query_transparent_balance< (None, Some(owner)) => { let owner = owner.address().unwrap(); for (token_alias, token) in tokens { - let balance = get_token_balance(client, &token, &owner).await; + let balance = + get_token_balance(context.client(), &token, &owner).await; if !balance.is_zero() { - let balance = format_denominated_amount( - client, &token, balance, - ) - .await; - display_line!(IO, "{}: {}", token_alias, balance); + let balance = + format_denominated_amount(context, &token, balance) + .await; + display_line!(context.io(), "{}: {}", token_alias, balance); } } } (Some(token), None) => { let prefix = token::balance_prefix(&token); let balances = - query_storage_prefix::(client, &prefix) - .await; + query_storage_prefix::(context, &prefix).await; if let Some(balances) = balances { - print_balances::<_, IO>( - client, - wallet, - balances, - Some(&token), - None, - ) - .await; + print_balances(context, balances, Some(&token), None).await; } } (None, None) => { - let balances = - query_storage_prefix::(client, &prefix) - .await; + let balances = query_storage_prefix(context, &prefix).await; if let Some(balances) = balances { - print_balances::<_, IO>(client, wallet, balances, None, None) - .await; + print_balances(context, balances, None, None).await; } } } } /// Query the token pinned balance(s) -pub async fn query_pinned_balance< - C: namada::ledger::queries::Client + Sync, - U: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn query_pinned_balance<'a>( + context: &impl Namada<'a>, args: args::QueryBalance, ) { // Map addresses to token names + let wallet = context.wallet().await; let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); let owners = if let Some(pa) = args.owner.and_then(|x| x.payment_address()) { @@ -445,7 +401,7 @@ pub async fn query_pinned_balance< .values() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - let _ = shielded.load().await; + let _ = context.shielded_mut().await.load().await; // Print the token balances by payment address let pinned_error = Err(Error::from(PinnedBalanceError::InvalidViewingKey)); for owner in owners { @@ -453,8 +409,10 @@ pub async fn query_pinned_balance< // Find the viewing key that can recognize payments the current payment // address for vk in &viewing_keys { - balance = shielded - .compute_exchanged_pinned_balance::<_, IO>(client, owner, vk) + balance = context + .shielded_mut() + .await + .compute_exchanged_pinned_balance(context, owner, vk) .await; if !is_pinned_error(&balance) { break; @@ -463,18 +421,21 @@ pub async fn query_pinned_balance< // If a suitable viewing key was not found, then demand it from the user if is_pinned_error(&balance) { let vk_str = - prompt!(IO, "Enter the viewing key for {}: ", owner).await; + prompt!(context.io(), "Enter the viewing key for {}: ", owner) + .await; let fvk = match ExtendedViewingKey::from_str(vk_str.trim()) { Ok(fvk) => fvk, _ => { - edisplay_line!(IO, "Invalid viewing key entered"); + edisplay_line!(context.io(), "Invalid viewing key entered"); continue; } }; let vk = ExtendedFullViewingKey::from(fvk).fvk.vk; // Use the given viewing key to decrypt pinned transaction data - balance = shielded - .compute_exchanged_pinned_balance::<_, IO>(client, owner, &vk) + balance = context + .shielded_mut() + .await + .compute_exchanged_pinned_balance(context, owner, &vk) .await } @@ -482,7 +443,7 @@ pub async fn query_pinned_balance< match (balance, args.token.as_ref()) { (Err(Error::Pinned(PinnedBalanceError::InvalidViewingKey)), _) => { display_line!( - IO, + context.io(), "Supplied viewing key cannot decode transactions to given \ payment address." ) @@ -492,13 +453,17 @@ pub async fn query_pinned_balance< _, ) => { display_line!( - IO, + context.io(), "Payment address {} has not yet been consumed.", owner ) } (Err(other), _) => { - display_line!(IO, "Error in Querying Pinned balance {}", other) + display_line!( + context.io(), + "Error in Querying Pinned balance {}", + other + ) } (Ok((balance, epoch)), Some(token)) => { let token_alias = wallet.lookup_alias(token); @@ -510,7 +475,7 @@ pub async fn query_pinned_balance< if total_balance.is_zero() { display_line!( - IO, + context.io(), "Payment address {} was consumed during epoch {}. \ Received no shielded {}", owner, @@ -519,13 +484,13 @@ pub async fn query_pinned_balance< ); } else { let formatted = format_denominated_amount( - client, + context, token, total_balance.into(), ) .await; display_line!( - IO, + context.io(), "Payment address {} was consumed during epoch {}. \ Received {} {}", owner, @@ -544,7 +509,7 @@ pub async fn query_pinned_balance< { if !found_any { display_line!( - IO, + context.io(), "Payment address {} was consumed during epoch {}. \ Received:", owner, @@ -553,7 +518,7 @@ pub async fn query_pinned_balance< found_any = true; } let formatted = format_denominated_amount( - client, + context, token_addr, (*value).into(), ) @@ -562,11 +527,16 @@ pub async fn query_pinned_balance< .get(token_addr) .map(|a| a.to_string()) .unwrap_or_else(|| token_addr.to_string()); - display_line!(IO, " {}: {}", token_alias, formatted,); + display_line!( + context.io(), + " {}: {}", + token_alias, + formatted, + ); } if !found_any { display_line!( - IO, + context.io(), "Payment address {} was consumed during epoch {}. \ Received no shielded assets.", owner, @@ -578,15 +548,15 @@ pub async fn query_pinned_balance< } } -async fn print_balances( - client: &C, - wallet: &Wallet, +async fn print_balances<'a>( + context: &impl Namada<'a>, balances: impl Iterator, token: Option<&Address>, target: Option<&Address>, ) { let stdout = io::stdout(); let mut w = stdout.lock(); + let wallet = context.wallet().await; let mut print_num = 0; let mut print_token = None; @@ -599,8 +569,7 @@ async fn print_balances( owner.clone(), format!( ": {}, owned by {}", - format_denominated_amount(client, tok, balance) - .await, + format_denominated_amount(context, tok, balance).await, wallet.lookup_alias(owner) ), ), @@ -629,19 +598,20 @@ async fn print_balances( } _ => { let token_alias = wallet.lookup_alias(&t); - display_line!(IO, &mut w; "Token {}", token_alias).unwrap(); + display_line!(context.io(), &mut w; "Token {}", token_alias) + .unwrap(); print_token = Some(t); } } // Print the balance - display_line!(IO, &mut w; "{}", s).unwrap(); + display_line!(context.io(), &mut w; "{}", s).unwrap(); print_num += 1; } if print_num == 0 { match (token, target) { (Some(_), Some(target)) | (None, Some(target)) => display_line!( - IO, + context.io(), &mut w; "No balances owned by {}", wallet.lookup_alias(target) @@ -649,38 +619,38 @@ async fn print_balances( .unwrap(), (Some(token), None) => { let token_alias = wallet.lookup_alias(token); - display_line!(IO, &mut w; "No balances for token {}", token_alias).unwrap() + display_line!(context.io(), &mut w; "No balances for token {}", token_alias).unwrap() + } + (None, None) => { + display_line!(context.io(), &mut w; "No balances").unwrap() } - (None, None) => display_line!(IO, &mut w; "No balances").unwrap(), } } } /// Query Proposals -pub async fn query_proposal< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_proposal<'a>( + context: &impl Namada<'a>, args: args::QueryProposal, ) { - let current_epoch = query_and_print_epoch::<_, IO>(client).await; + let current_epoch = query_and_print_epoch(context).await; if let Some(id) = args.proposal_id { - let proposal = query_proposal_by_id(client, id).await.unwrap(); + let proposal = + query_proposal_by_id(context.client(), id).await.unwrap(); if let Some(proposal) = proposal { display_line!( - IO, + context.io(), "{}", proposal.to_string_with_status(current_epoch) ); } else { - edisplay_line!(IO, "No proposal found with id: {}", id); + edisplay_line!(context.io(), "No proposal found with id: {}", id); } } else { let last_proposal_id_key = governance_storage::get_counter_key(); - let last_proposal_id = - query_storage_value::(client, &last_proposal_id_key) + let last_proposal_id: u64 = + query_storage_value(context.client(), &last_proposal_id_key) .await .unwrap(); @@ -690,14 +660,14 @@ pub async fn query_proposal< 0 }; - display_line!(IO, "id: {}", last_proposal_id); + display_line!(context.io(), "id: {}", last_proposal_id); for id in from_id..last_proposal_id { - let proposal = query_proposal_by_id(client, id) + let proposal = query_proposal_by_id(context.client(), id) .await .unwrap() .expect("Proposal should be written to storage."); - display_line!(IO, "{}", proposal); + display_line!(context.io(), "{}", proposal); } } } @@ -711,14 +681,8 @@ pub async fn query_proposal_by_id( } /// Query token shielded balance(s) -pub async fn query_shielded_balance< - C: namada::ledger::queries::Client + Sync, - U: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn query_shielded_balance<'a>( + context: &impl Namada<'a>, args: args::QueryBalance, ) { // Used to control whether balances for all keys or a specific key are @@ -730,20 +694,32 @@ pub async fn query_shielded_balance< // provided, then convert to a viewing key first. let viewing_keys = match owner { Some(viewing_key) => vec![viewing_key], - None => wallet.get_viewing_keys().values().copied().collect(), + None => context + .wallet() + .await + .get_viewing_keys() + .values() + .copied() + .collect(), }; - let _ = shielded.load().await; - let fvks: Vec<_> = viewing_keys - .iter() - .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) - .collect(); - shielded.fetch(client, &[], &fvks).await.unwrap(); - // Save the update state so that future fetches can be short-circuited - let _ = shielded.save().await; + { + let mut shielded = context.shielded_mut().await; + let _ = shielded.load().await; + let fvks: Vec<_> = viewing_keys + .iter() + .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) + .collect(); + shielded.fetch(context.client(), &[], &fvks).await.unwrap(); + // Save the update state so that future fetches can be short-circuited + let _ = shielded.save().await; + } // The epoch is required to identify timestamped tokens - let epoch = query_and_print_epoch::<_, IO>(client).await; + let epoch = query_and_print_epoch(context).await; // Map addresses to token names - let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); + let tokens = context + .wallet() + .await + .get_addresses_with_vp_type(AddressVpType::Token); match (args.token, owner.is_some()) { // Here the user wants to know the balance for a specific token (Some(token), true) => { @@ -751,24 +727,24 @@ pub async fn query_shielded_balance< let viewing_key = ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; let balance: MaspAmount = if no_conversions { - shielded - .compute_shielded_balance(client, &viewing_key) + context + .shielded_mut() + .await + .compute_shielded_balance(context.client(), &viewing_key) .await .unwrap() .expect("context should contain viewing key") } else { - shielded - .compute_exchanged_balance::<_, IO>( - client, - &viewing_key, - epoch, - ) + context + .shielded_mut() + .await + .compute_exchanged_balance(context, &viewing_key, epoch) .await .unwrap() .expect("context should contain viewing key") }; - let token_alias = wallet.lookup_alias(&token); + let token_alias = context.wallet().await.lookup_alias(&token); let total_balance = balance .get(&(epoch, token.clone())) @@ -776,17 +752,17 @@ pub async fn query_shielded_balance< .unwrap_or_default(); if total_balance.is_zero() { display_line!( - IO, + context.io(), "No shielded {} balance found for given key", token_alias ); } else { display_line!( - IO, + context.io(), "{}: {}", token_alias, format_denominated_amount( - client, + context, &token, token::Amount::from(total_balance) ) @@ -802,18 +778,21 @@ pub async fn query_shielded_balance< // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; let balance = if no_conversions { - shielded - .compute_shielded_balance(client, &viewing_key) + context + .shielded_mut() + .await + .compute_shielded_balance( + context.client(), + &viewing_key, + ) .await .unwrap() .expect("context should contain viewing key") } else { - shielded - .compute_exchanged_balance::<_, IO>( - client, - &viewing_key, - epoch, - ) + context + .shielded_mut() + .await + .compute_exchanged_balance(context, &viewing_key, epoch) .await .unwrap() .expect("context should contain viewing key") @@ -836,7 +815,7 @@ pub async fn query_shielded_balance< // hashtable creation any uglier if balances.is_empty() { display_line!( - IO, + context.io(), "No shielded {} balance found for any wallet key", &token_addr ); @@ -852,14 +831,19 @@ pub async fn query_shielded_balance< .get(&token) .map(|a| a.to_string()) .unwrap_or_else(|| token.to_string()); - display_line!(IO, "Shielded Token {}:", alias); + display_line!(context.io(), "Shielded Token {}:", alias); let formatted = format_denominated_amount( - client, + context, &token, token_balance.into(), ) .await; - display_line!(IO, " {}, owned by {}", formatted, fvk); + display_line!( + context.io(), + " {}, owned by {}", + formatted, + fvk + ); } } // Here the user wants to know the balance for a specific token across @@ -874,27 +858,30 @@ pub async fn query_shielded_balance< .as_ref(), ) .unwrap(); - let token_alias = wallet.lookup_alias(&token); - display_line!(IO, "Shielded Token {}:", token_alias); + let token_alias = context.wallet().await.lookup_alias(&token); + display_line!(context.io(), "Shielded Token {}:", token_alias); let mut found_any = false; - let token_alias = wallet.lookup_alias(&token); - display_line!(IO, "Shielded Token {}:", token_alias,); + let token_alias = context.wallet().await.lookup_alias(&token); + display_line!(context.io(), "Shielded Token {}:", token_alias,); for fvk in viewing_keys { // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; let balance = if no_conversions { - shielded - .compute_shielded_balance(client, &viewing_key) + context + .shielded_mut() + .await + .compute_shielded_balance( + context.client(), + &viewing_key, + ) .await .unwrap() .expect("context should contain viewing key") } else { - shielded - .compute_exchanged_balance::<_, IO>( - client, - &viewing_key, - epoch, - ) + context + .shielded_mut() + .await + .compute_exchanged_balance(context, &viewing_key, epoch) .await .unwrap() .expect("context should contain viewing key") @@ -905,17 +892,22 @@ pub async fn query_shielded_balance< found_any = true; } let formatted = format_denominated_amount( - client, + context, address, (*val).into(), ) .await; - display_line!(IO, " {}, owned by {}", formatted, fvk); + display_line!( + context.io(), + " {}, owned by {}", + formatted, + fvk + ); } } if !found_any { display_line!( - IO, + context.io(), "No shielded {} balance found for any wallet key", token_alias, ); @@ -927,56 +919,48 @@ pub async fn query_shielded_balance< let viewing_key = ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; if no_conversions { - let balance = shielded - .compute_shielded_balance(client, &viewing_key) + let balance = context + .shielded_mut() + .await + .compute_shielded_balance(context.client(), &viewing_key) .await .unwrap() .expect("context should contain viewing key"); // Print balances by human-readable token names - print_decoded_balance_with_epoch::<_, IO>( - client, wallet, balance, - ) - .await; + print_decoded_balance_with_epoch(context, balance).await; } else { - let balance = shielded - .compute_exchanged_balance::<_, IO>( - client, - &viewing_key, - epoch, - ) + let balance = context + .shielded_mut() + .await + .compute_exchanged_balance(context, &viewing_key, epoch) .await .unwrap() .expect("context should contain viewing key"); // Print balances by human-readable token names - print_decoded_balance::<_, IO>(client, wallet, balance, epoch) - .await; + print_decoded_balance(context, balance, epoch).await; } } } } -pub async fn print_decoded_balance< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn print_decoded_balance<'a>( + context: &impl Namada<'a>, decoded_balance: MaspAmount, epoch: Epoch, ) { if decoded_balance.is_empty() { - display_line!(IO, "No shielded balance found for given key"); + display_line!(context.io(), "No shielded balance found for given key"); } else { for ((_, token_addr), amount) in decoded_balance .iter() .filter(|((token_epoch, _), _)| *token_epoch == epoch) { display_line!( - IO, + context.io(), "{} : {}", - wallet.lookup_alias(token_addr), + context.wallet().await.lookup_alias(token_addr), format_denominated_amount( - client, + context, token_addr, (*amount).into() ) @@ -986,17 +970,16 @@ pub async fn print_decoded_balance< } } -pub async fn print_decoded_balance_with_epoch< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn print_decoded_balance_with_epoch<'a>( + context: &impl Namada<'a>, decoded_balance: MaspAmount, ) { - let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); + let tokens = context + .wallet() + .await + .get_addresses_with_vp_type(AddressVpType::Token); if decoded_balance.is_empty() { - display_line!(IO, "No shielded balance found for given key"); + display_line!(context.io(), "No shielded balance found for given key"); } for ((epoch, token_addr), value) in decoded_balance.iter() { let asset_value = (*value).into(); @@ -1005,12 +988,11 @@ pub async fn print_decoded_balance_with_epoch< .map(|a| a.to_string()) .unwrap_or_else(|| token_addr.to_string()); display_line!( - IO, + context.io(), "{} | {} : {}", alias, epoch, - format_denominated_amount(client, token_addr, asset_value) - .await, + format_denominated_amount(context, token_addr, asset_value).await, ); } } @@ -1026,35 +1008,37 @@ pub async fn get_token_balance( .unwrap() } -pub async fn query_proposal_result< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_proposal_result<'a>( + context: &impl Namada<'a>, args: args::QueryProposalResult, ) { if args.proposal_id.is_some() { let proposal_id = args.proposal_id.expect("Proposal id should be defined."); let proposal = if let Some(proposal) = - query_proposal_by_id(client, proposal_id).await.unwrap() + query_proposal_by_id(context.client(), proposal_id) + .await + .unwrap() { proposal } else { - edisplay_line!(IO, "Proposal {} not found.", proposal_id); + edisplay_line!(context.io(), "Proposal {} not found.", proposal_id); return; }; - let is_author_steward = query_pgf_stewards(client) + let is_author_steward = query_pgf_stewards(context.client()) .await .iter() .any(|steward| steward.address.eq(&proposal.author)); let tally_type = proposal.get_tally_type(is_author_steward); - let total_voting_power = - get_total_staked_tokens(client, proposal.voting_end_epoch).await; + let total_voting_power = get_total_staked_tokens( + context.client(), + proposal.voting_end_epoch, + ) + .await; let votes = compute_proposal_votes( - client, + context.client(), proposal_id, proposal.voting_end_epoch, ) @@ -1063,8 +1047,8 @@ pub async fn query_proposal_result< let proposal_result = compute_proposal_result(votes, total_voting_power, tally_type); - display_line!(IO, "Proposal Id: {} ", proposal_id); - display_line!(IO, "{:4}{}", "", proposal_result); + display_line!(context.io(), "Proposal Id: {} ", proposal_id); + display_line!(context.io(), "{:4}{}", "", proposal_result); } else { let proposal_folder = args.proposal_folder.expect( "The argument --proposal-folder is required with --offline.", @@ -1085,11 +1069,13 @@ pub async fn query_proposal_result< serde_json::from_reader(proposal_file) .expect("file should be proper JSON"); - let author_account = - rpc::get_account_info(client, &proposal.proposal.author) - .await - .unwrap() - .expect("Account should exist."); + let author_account = rpc::get_account_info( + context.client(), + &proposal.proposal.author, + ) + .await + .unwrap() + .expect("Account should exist."); let proposal = proposal.validate( &author_account.public_keys_map, @@ -1100,12 +1086,15 @@ pub async fn query_proposal_result< if proposal.is_ok() { proposal.unwrap() } else { - edisplay_line!(IO, "The offline proposal is not valid."); + edisplay_line!( + context.io(), + "The offline proposal is not valid." + ); return; } } else { edisplay_line!( - IO, + context.io(), "Couldn't find a file name offline_proposal_*.json." ); return; @@ -1121,15 +1110,14 @@ pub async fn query_proposal_result< }) .collect::>(); - let proposal_votes = compute_offline_proposal_votes::<_, IO>( - client, - &proposal, - votes.clone(), + let proposal_votes = + compute_offline_proposal_votes(context, &proposal, votes.clone()) + .await; + let total_voting_power = get_total_staked_tokens( + context.client(), + proposal.proposal.tally_epoch, ) .await; - let total_voting_power = - get_total_staked_tokens(client, proposal.proposal.tally_epoch) - .await; let proposal_result = compute_proposal_result( proposal_votes, @@ -1137,51 +1125,54 @@ pub async fn query_proposal_result< TallyType::TwoThird, ); - display_line!(IO, "Proposal offline: {}", proposal.proposal.hash()); - display_line!(IO, "Parsed {} votes.", votes.len()); - display_line!(IO, "{:4}{}", "", proposal_result); + display_line!( + context.io(), + "Proposal offline: {}", + proposal.proposal.hash() + ); + display_line!(context.io(), "Parsed {} votes.", votes.len()); + display_line!(context.io(), "{:4}{}", "", proposal_result); } } -pub async fn query_account< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_account<'a>( + context: &impl Namada<'a>, args: args::QueryAccount, ) { - let account = rpc::get_account_info(client, &args.owner).await.unwrap(); + let account = rpc::get_account_info(context.client(), &args.owner) + .await + .unwrap(); if let Some(account) = account { - display_line!(IO, "Address: {}", account.address); - display_line!(IO, "Threshold: {}", account.threshold); - display_line!(IO, "Public keys:"); + display_line!(context.io(), "Address: {}", account.address); + display_line!(context.io(), "Threshold: {}", account.threshold); + display_line!(context.io(), "Public keys:"); for (public_key, _) in account.public_keys_map.pk_to_idx { - display_line!(IO, "- {}", public_key); + display_line!(context.io(), "- {}", public_key); } } else { - display_line!(IO, "No account exists for {}", args.owner); + display_line!(context.io(), "No account exists for {}", args.owner); } } -pub async fn query_pgf( - client: &C, - _args: args::QueryPgf, -) { - let stewards = query_pgf_stewards(client).await; - let fundings = query_pgf_fundings(client).await; +pub async fn query_pgf<'a>(context: &impl Namada<'a>, _args: args::QueryPgf) { + let stewards = query_pgf_stewards(context.client()).await; + let fundings = query_pgf_fundings(context.client()).await; match stewards.is_empty() { true => { - display_line!(IO, "Pgf stewards: no stewards are currectly set.") + display_line!( + context.io(), + "Pgf stewards: no stewards are currectly set." + ) } false => { - display_line!(IO, "Pgf stewards:"); + display_line!(context.io(), "Pgf stewards:"); for steward in stewards { - display_line!(IO, "{:4}- {}", "", steward.address); - display_line!(IO, "{:4} Reward distribution:", ""); + display_line!(context.io(), "{:4}- {}", "", steward.address); + display_line!(context.io(), "{:4} Reward distribution:", ""); for (address, percentage) in steward.reward_distribution { display_line!( - IO, + context.io(), "{:6}- {} to {}", "", percentage, @@ -1194,13 +1185,16 @@ pub async fn query_pgf( match fundings.is_empty() { true => { - display_line!(IO, "Pgf fundings: no fundings are currently set.") + display_line!( + context.io(), + "Pgf fundings: no fundings are currently set." + ) } false => { - display_line!(IO, "Pgf fundings:"); + display_line!(context.io(), "Pgf fundings:"); for funding in fundings { display_line!( - IO, + context.io(), "{:4}- {} for {}", "", funding.detail.target, @@ -1211,180 +1205,198 @@ pub async fn query_pgf( } } -pub async fn query_protocol_parameters< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_protocol_parameters<'a>( + context: &impl Namada<'a>, _args: args::QueryProtocolParameters, ) { - let governance_parameters = query_governance_parameters(client).await; - display_line!(IO, "Governance Parameters\n"); + let governance_parameters = + query_governance_parameters(context.client()).await; + display_line!(context.io(), "Governance Parameters\n"); display_line!( - IO, + context.io(), "{:4}Min. proposal fund: {}", "", governance_parameters.min_proposal_fund.to_string_native() ); display_line!( - IO, + context.io(), "{:4}Max. proposal code size: {}", "", governance_parameters.max_proposal_code_size ); display_line!( - IO, + context.io(), "{:4}Min. proposal voting period: {}", "", governance_parameters.min_proposal_voting_period ); display_line!( - IO, + context.io(), "{:4}Max. proposal period: {}", "", governance_parameters.max_proposal_period ); display_line!( - IO, + context.io(), "{:4}Max. proposal content size: {}", "", governance_parameters.max_proposal_content_size ); display_line!( - IO, + context.io(), "{:4}Min. proposal grace epochs: {}", "", governance_parameters.min_proposal_grace_epochs ); - let pgf_parameters = query_pgf_parameters(client).await; - display_line!(IO, "Public Goods Funding Parameters\n"); + let pgf_parameters = query_pgf_parameters(context.client()).await; + display_line!(context.io(), "Public Goods Funding Parameters\n"); display_line!( - IO, + context.io(), "{:4}Pgf inflation rate: {}", "", pgf_parameters.pgf_inflation_rate ); display_line!( - IO, + context.io(), "{:4}Steward inflation rate: {}", "", pgf_parameters.stewards_inflation_rate ); - display_line!(IO, "Protocol parameters"); + display_line!(context.io(), "Protocol parameters"); let key = param_storage::get_epoch_duration_storage_key(); - let epoch_duration = query_storage_value::(client, &key) - .await - .expect("Parameter should be definied."); + let epoch_duration: EpochDuration = + query_storage_value(context.client(), &key) + .await + .expect("Parameter should be definied."); display_line!( - IO, + context.io(), "{:4}Min. epoch duration: {}", "", epoch_duration.min_duration ); display_line!( - IO, + context.io(), "{:4}Min. number of blocks: {}", "", epoch_duration.min_num_of_blocks ); let key = param_storage::get_max_expected_time_per_block_key(); - let max_block_duration = query_storage_value::(client, &key) + let max_block_duration: u64 = query_storage_value(context.client(), &key) .await .expect("Parameter should be defined."); - display_line!(IO, "{:4}Max. block duration: {}", "", max_block_duration); + display_line!( + context.io(), + "{:4}Max. block duration: {}", + "", + max_block_duration + ); let key = param_storage::get_tx_whitelist_storage_key(); - let vp_whitelist = query_storage_value::>(client, &key) + let vp_whitelist: Vec = query_storage_value(context.client(), &key) .await .expect("Parameter should be defined."); - display_line!(IO, "{:4}VP whitelist: {:?}", "", vp_whitelist); + display_line!(context.io(), "{:4}VP whitelist: {:?}", "", vp_whitelist); let key = param_storage::get_tx_whitelist_storage_key(); - let tx_whitelist = query_storage_value::>(client, &key) + let tx_whitelist: Vec = query_storage_value(context.client(), &key) .await .expect("Parameter should be defined."); - display_line!(IO, "{:4}Transactions whitelist: {:?}", "", tx_whitelist); + display_line!( + context.io(), + "{:4}Transactions whitelist: {:?}", + "", + tx_whitelist + ); let key = param_storage::get_max_block_gas_key(); - let max_block_gas = query_storage_value::(client, &key) + let max_block_gas: u64 = query_storage_value(context.client(), &key) .await .expect("Parameter should be defined."); - display_line!(IO, "{:4}Max block gas: {:?}", "", max_block_gas); + display_line!(context.io(), "{:4}Max block gas: {:?}", "", max_block_gas); let key = param_storage::get_fee_unshielding_gas_limit_key(); - let fee_unshielding_gas_limit = query_storage_value::(client, &key) - .await - .expect("Parameter should be defined."); + let fee_unshielding_gas_limit: u64 = + query_storage_value(context.client(), &key) + .await + .expect("Parameter should be defined."); display_line!( - IO, + context.io(), "{:4}Fee unshielding gas limit: {:?}", "", fee_unshielding_gas_limit ); let key = param_storage::get_fee_unshielding_descriptions_limit_key(); - let fee_unshielding_descriptions_limit = - query_storage_value::(client, &key) + let fee_unshielding_descriptions_limit: u64 = + query_storage_value(context.client(), &key) .await .expect("Parameter should be defined."); display_line!( - IO, + context.io(), "{:4}Fee unshielding descriptions limit: {:?}", "", fee_unshielding_descriptions_limit ); let key = param_storage::get_gas_cost_key(); - let gas_cost_table = query_storage_value::< - C, - BTreeMap, - >(client, &key) - .await - .expect("Parameter should be defined."); - display_line!(IO, "{:4}Gas cost table:", ""); + let gas_cost_table: BTreeMap = + query_storage_value(context.client(), &key) + .await + .expect("Parameter should be defined."); + display_line!(context.io(), "{:4}Gas cost table:", ""); for (token, gas_cost) in gas_cost_table { - display_line!(IO, "{:8}{}: {:?}", "", token, gas_cost); + display_line!(context.io(), "{:8}{}: {:?}", "", token, gas_cost); } - display_line!(IO, "PoS parameters"); - let pos_params = query_pos_parameters(client).await; + display_line!(context.io(), "PoS parameters"); + let pos_params = query_pos_parameters(context.client()).await; display_line!( - IO, + context.io(), "{:4}Block proposer reward: {}", "", pos_params.block_proposer_reward ); display_line!( - IO, + context.io(), "{:4}Block vote reward: {}", "", pos_params.block_vote_reward ); display_line!( - IO, + context.io(), "{:4}Duplicate vote minimum slash rate: {}", "", pos_params.duplicate_vote_min_slash_rate ); display_line!( - IO, + context.io(), "{:4}Light client attack minimum slash rate: {}", "", pos_params.light_client_attack_min_slash_rate ); display_line!( - IO, + context.io(), "{:4}Max. validator slots: {}", "", pos_params.max_validator_slots ); - display_line!(IO, "{:4}Pipeline length: {}", "", pos_params.pipeline_len); - display_line!(IO, "{:4}Unbonding length: {}", "", pos_params.unbonding_len); display_line!( - IO, + context.io(), + "{:4}Pipeline length: {}", + "", + pos_params.pipeline_len + ); + display_line!( + context.io(), + "{:4}Unbonding length: {}", + "", + pos_params.unbonding_len + ); + display_line!( + context.io(), "{:4}Votes per token: {}", "", pos_params.tm_votes_per_token @@ -1443,16 +1455,14 @@ pub async fn query_pgf_parameters( unwrap_client_response::(RPC.vp().pgf().parameters(client).await) } -pub async fn query_and_print_unbonds< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_and_print_unbonds<'a>( + context: &impl Namada<'a>, source: &Address, validator: &Address, ) { - let unbonds = query_unbond_with_slashing(client, source, validator).await; - let current_epoch = query_epoch(client).await.unwrap(); + let unbonds = + query_unbond_with_slashing(context.client(), source, validator).await; + let current_epoch = query_epoch(context.client()).await.unwrap(); let mut total_withdrawable = token::Amount::default(); let mut not_yet_withdrawable = HashMap::::new(); @@ -1467,17 +1477,17 @@ pub async fn query_and_print_unbonds< } if total_withdrawable != token::Amount::default() { display_line!( - IO, + context.io(), "Total withdrawable now: {}.", total_withdrawable.to_string_native() ); } if !not_yet_withdrawable.is_empty() { - display_line!(IO, "Current epoch: {current_epoch}."); + display_line!(context.io(), "Current epoch: {current_epoch}."); } for (withdraw_epoch, amount) in not_yet_withdrawable { display_line!( - IO, + context.io(), "Amount {} withdrawable starting from epoch {withdraw_epoch}.", amount.to_string_native(), ); @@ -1501,12 +1511,11 @@ pub async fn query_withdrawable_tokens< } /// Query PoS bond(s) and unbond(s) -pub async fn query_bonds( - client: &C, - _wallet: &mut Wallet, +pub async fn query_bonds<'a>( + context: &impl Namada<'a>, args: args::QueryBonds, ) -> std::io::Result<()> { - let epoch = query_and_print_epoch::<_, IO>(client).await; + let epoch = query_and_print_epoch(context).await; let source = args.owner; let validator = args.validator; @@ -1514,10 +1523,14 @@ pub async fn query_bonds( let stdout = io::stdout(); let mut w = stdout.lock(); - let bonds_and_unbonds = - enriched_bonds_and_unbonds(client, epoch, &source, &validator) - .await - .unwrap(); + let bonds_and_unbonds = enriched_bonds_and_unbonds( + context.client(), + epoch, + &source, + &validator, + ) + .await + .unwrap(); for (bond_id, details) in &bonds_and_unbonds.data { let bond_type = if bond_id.source == bond_id.validator { @@ -1528,10 +1541,10 @@ pub async fn query_bonds( bond_id.source, bond_id.validator ) }; - display_line!(IO, &mut w; "{}:", bond_type)?; + display_line!(context.io(), &mut w; "{}:", bond_type)?; for bond in &details.data.bonds { display_line!( - IO, + context.io(), &mut w; " Remaining active bond from epoch {}: Δ {}", bond.start, @@ -1540,14 +1553,14 @@ pub async fn query_bonds( } if details.bonds_total != token::Amount::zero() { display_line!( - IO, + context.io(), &mut w; "Active (slashed) bonds total: {}", details.bonds_total_active().to_string_native() )?; } - display_line!(IO, &mut w; "Bonds total: {}", details.bonds_total.to_string_native())?; - display_line!(IO, &mut w; "")?; + display_line!(context.io(), &mut w; "Bonds total: {}", details.bonds_total.to_string_native())?; + display_line!(context.io(), &mut w; "")?; if !details.data.unbonds.is_empty() { let bond_type = if bond_id.source == bond_id.validator { @@ -1555,10 +1568,10 @@ pub async fn query_bonds( } else { format!("Unbonded delegations from {}", bond_id.source) }; - display_line!(IO, &mut w; "{}:", bond_type)?; + display_line!(context.io(), &mut w; "{}:", bond_type)?; for unbond in &details.data.unbonds { display_line!( - IO, + context.io(), &mut w; " Withdrawable from epoch {} (active from {}): Δ {}", unbond.withdraw, @@ -1567,30 +1580,30 @@ pub async fn query_bonds( )?; } display_line!( - IO, + context.io(), &mut w; "Unbonded total: {}", details.unbonds_total.to_string_native() )?; } display_line!( - IO, + context.io(), &mut w; "Withdrawable total: {}", details.total_withdrawable.to_string_native() )?; - display_line!(IO, &mut w; "")?; + display_line!(context.io(), &mut w; "")?; } if bonds_and_unbonds.bonds_total != bonds_and_unbonds.bonds_total_slashed { display_line!( - IO, + context.io(), &mut w; "All bonds total active: {}", bonds_and_unbonds.bonds_total_active().to_string_native() )?; } display_line!( - IO, + context.io(), &mut w; "All bonds total: {}", bonds_and_unbonds.bonds_total.to_string_native() @@ -1600,20 +1613,20 @@ pub async fn query_bonds( != bonds_and_unbonds.unbonds_total_slashed { display_line!( - IO, + context.io(), &mut w; "All unbonds total active: {}", bonds_and_unbonds.unbonds_total_active().to_string_native() )?; } display_line!( - IO, + context.io(), &mut w; "All unbonds total: {}", bonds_and_unbonds.unbonds_total.to_string_native() )?; display_line!( - IO, + context.io(), &mut w; "All unbonds total withdrawable: {}", bonds_and_unbonds.total_withdrawable.to_string_native() @@ -1622,51 +1635,55 @@ pub async fn query_bonds( } /// Query PoS bonded stake -pub async fn query_bonded_stake< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_bonded_stake<'a, N: Namada<'a>>( + context: &N, args: args::QueryBondedStake, ) { let epoch = match args.epoch { Some(epoch) => epoch, - None => query_and_print_epoch::<_, IO>(client).await, + None => query_and_print_epoch(context).await, }; match args.validator { Some(validator) => { let validator = validator; // Find bonded stake for the given validator - let stake = get_validator_stake(client, epoch, &validator).await; + let stake = + get_validator_stake(context.client(), epoch, &validator).await; match stake { Some(stake) => { // TODO: show if it's in consensus set, below capacity, or // below threshold set display_line!( - IO, + context.io(), "Bonded stake of validator {validator}: {}", stake.to_string_native() ) } None => { - display_line!(IO, "No bonded stake found for {validator}"); + display_line!( + context.io(), + "No bonded stake found for {validator}" + ); } } } None => { - let consensus = - unwrap_client_response::>( + let consensus: BTreeSet = + unwrap_client_response::( RPC.vp() .pos() - .consensus_validator_set(client, &Some(epoch)) + .consensus_validator_set(context.client(), &Some(epoch)) .await, ); - let below_capacity = - unwrap_client_response::>( + let below_capacity: BTreeSet = + unwrap_client_response::( RPC.vp() .pos() - .below_capacity_validator_set(client, &Some(epoch)) + .below_capacity_validator_set( + context.client(), + &Some(epoch), + ) .await, ); @@ -1674,10 +1691,11 @@ pub async fn query_bonded_stake< let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, &mut w; "Consensus validators:").unwrap(); + display_line!(context.io(), &mut w; "Consensus validators:") + .unwrap(); for val in consensus.into_iter().rev() { display_line!( - IO, + context.io(), &mut w; " {}: {}", val.address.encode(), @@ -1686,11 +1704,11 @@ pub async fn query_bonded_stake< .unwrap(); } if !below_capacity.is_empty() { - display_line!(IO, &mut w; "Below capacity validators:") + display_line!(context.io(), &mut w; "Below capacity validators:") .unwrap(); for val in below_capacity.into_iter().rev() { display_line!( - IO, + context.io(), &mut w; " {}: {}", val.address.encode(), @@ -1702,9 +1720,10 @@ pub async fn query_bonded_stake< } } - let total_staked_tokens = get_total_staked_tokens(client, epoch).await; + let total_staked_tokens = + get_total_staked_tokens(context.client(), epoch).await; display_line!( - IO, + context.io(), "Total bonded stake: {}", total_staked_tokens.to_string_native() ); @@ -1744,47 +1763,43 @@ pub async fn query_validator_state< } /// Query a validator's state information -pub async fn query_and_print_validator_state< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - _wallet: &mut Wallet, +pub async fn query_and_print_validator_state<'a>( + context: &impl Namada<'a>, args: args::QueryValidatorState, ) { let validator = args.validator; let state: Option = - query_validator_state(client, &validator, args.epoch).await; + query_validator_state(context.client(), &validator, args.epoch).await; match state { Some(state) => match state { ValidatorState::Consensus => { display_line!( - IO, + context.io(), "Validator {validator} is in the consensus set" ) } ValidatorState::BelowCapacity => { display_line!( - IO, + context.io(), "Validator {validator} is in the below-capacity set" ) } ValidatorState::BelowThreshold => { display_line!( - IO, + context.io(), "Validator {validator} is in the below-threshold set" ) } ValidatorState::Inactive => { - display_line!(IO, "Validator {validator} is inactive") + display_line!(context.io(), "Validator {validator} is inactive") } ValidatorState::Jailed => { - display_line!(IO, "Validator {validator} is jailed") + display_line!(context.io(), "Validator {validator} is jailed") } }, None => display_line!( - IO, + context.io(), "Validator {validator} is either not a validator, or an epoch \ before the current epoch has been queried (and the validator \ state information is no longer stored)" @@ -1793,25 +1808,21 @@ pub async fn query_and_print_validator_state< } /// Query PoS validator's commission rate information -pub async fn query_and_print_commission_rate< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - _wallet: &mut Wallet, +pub async fn query_and_print_commission_rate<'a>( + context: &impl Namada<'a>, args: args::QueryCommissionRate, ) { let validator = args.validator; let info: Option = - query_commission_rate(client, &validator, args.epoch).await; + query_commission_rate(context.client(), &validator, args.epoch).await; match info { Some(CommissionPair { commission_rate: rate, max_commission_change_per_epoch: change, }) => { display_line!( - IO, + context.io(), "Validator {} commission rate: {}, max change per epoch: {}", validator.encode(), rate, @@ -1820,7 +1831,7 @@ pub async fn query_and_print_commission_rate< } None => { display_line!( - IO, + context.io(), "Address {} is not a validator (did not find commission rate \ and max change)", validator.encode(), @@ -1830,28 +1841,27 @@ pub async fn query_and_print_commission_rate< } /// Query PoS slashes -pub async fn query_slashes< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - _wallet: &mut Wallet, +pub async fn query_slashes<'a, N: Namada<'a>>( + context: &N, args: args::QuerySlashes, ) { match args.validator { Some(validator) => { let validator = validator; // Find slashes for the given validator - let slashes: Vec = unwrap_client_response::>( - RPC.vp().pos().validator_slashes(client, &validator).await, + let slashes: Vec = unwrap_client_response::( + RPC.vp() + .pos() + .validator_slashes(context.client(), &validator) + .await, ); if !slashes.is_empty() { - display_line!(IO, "Processed slashes:"); + display_line!(context.io(), "Processed slashes:"); let stdout = io::stdout(); let mut w = stdout.lock(); for slash in slashes { display_line!( - IO, + context.io(), &mut w; "Infraction epoch {}, block height {}, type {}, rate \ {}", @@ -1864,7 +1874,7 @@ pub async fn query_slashes< } } else { display_line!( - IO, + context.io(), "No processed slashes found for {}", validator.encode() ) @@ -1874,20 +1884,26 @@ pub async fn query_slashes< let enqueued_slashes: HashMap< Address, BTreeMap>, - > = unwrap_client_response::< - C, - HashMap>>, - >(RPC.vp().pos().enqueued_slashes(client).await); + > = unwrap_client_response::( + RPC.vp().pos().enqueued_slashes(context.client()).await, + ); let enqueued_slashes = enqueued_slashes.get(&validator).cloned(); if let Some(enqueued) = enqueued_slashes { - display_line!(IO, "\nEnqueued slashes for future processing"); + display_line!( + context.io(), + "\nEnqueued slashes for future processing" + ); for (epoch, slashes) in enqueued { - display_line!(IO, "To be processed in epoch {}", epoch); + display_line!( + context.io(), + "To be processed in epoch {}", + epoch + ); for slash in slashes { let stdout = io::stdout(); let mut w = stdout.lock(); display_line!( - IO, + context.io(), &mut w; "Infraction epoch {}, block height {}, type {}", slash.epoch, slash.block_height, slash.r#type, @@ -1897,7 +1913,7 @@ pub async fn query_slashes< } } else { display_line!( - IO, + context.io(), "No enqueued slashes found for {}", validator.encode() ) @@ -1905,18 +1921,18 @@ pub async fn query_slashes< } None => { let all_slashes: HashMap> = - unwrap_client_response::>>( - RPC.vp().pos().slashes(client).await, + unwrap_client_response::( + RPC.vp().pos().slashes(context.client()).await, ); if !all_slashes.is_empty() { let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, "Processed slashes:"); + display_line!(context.io(), "Processed slashes:"); for (validator, slashes) in all_slashes.into_iter() { for slash in slashes { display_line!( - IO, + context.io(), &mut w; "Infraction epoch {}, block height {}, rate {}, \ type {}, validator {}", @@ -1930,7 +1946,7 @@ pub async fn query_slashes< } } } else { - display_line!(IO, "No processed slashes found") + display_line!(context.io(), "No processed slashes found") } // Find enqueued slashes to be processed in the future for the given @@ -1938,16 +1954,18 @@ pub async fn query_slashes< let enqueued_slashes: HashMap< Address, BTreeMap>, - > = unwrap_client_response::< - C, - HashMap>>, - >(RPC.vp().pos().enqueued_slashes(client).await); + > = unwrap_client_response::( + RPC.vp().pos().enqueued_slashes(context.client()).await, + ); if !enqueued_slashes.is_empty() { - display_line!(IO, "\nEnqueued slashes for future processing"); + display_line!( + context.io(), + "\nEnqueued slashes for future processing" + ); for (validator, slashes_by_epoch) in enqueued_slashes { for (epoch, slashes) in slashes_by_epoch { display_line!( - IO, + context.io(), "\nTo be processed in epoch {}", epoch ); @@ -1955,7 +1973,7 @@ pub async fn query_slashes< let stdout = io::stdout(); let mut w = stdout.lock(); display_line!( - IO, + context.io(), &mut w; "Infraction epoch {}, block height {}, type \ {}, validator {}", @@ -1970,7 +1988,7 @@ pub async fn query_slashes< } } else { display_line!( - IO, + context.io(), "\nNo enqueued slashes found for future processing" ) } @@ -1978,55 +1996,57 @@ pub async fn query_slashes< } } -pub async fn query_delegations< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - _wallet: &mut Wallet, +pub async fn query_delegations<'a, N: Namada<'a>>( + context: &N, args: args::QueryDelegations, ) { let owner = args.owner; - let delegations = unwrap_client_response::>( - RPC.vp().pos().delegation_validators(client, &owner).await, + let delegations: HashSet
= unwrap_client_response::( + RPC.vp() + .pos() + .delegation_validators(context.client(), &owner) + .await, ); if delegations.is_empty() { - display_line!(IO, "No delegations found"); + display_line!(context.io(), "No delegations found"); } else { - display_line!(IO, "Found delegations to:"); + display_line!(context.io(), "Found delegations to:"); for delegation in delegations { - display_line!(IO, " {delegation}"); + display_line!(context.io(), " {delegation}"); } } } -pub async fn query_find_validator< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_find_validator<'a, N: Namada<'a>>( + context: &N, args: args::QueryFindValidator, ) { let args::QueryFindValidator { query: _, tm_addr } = args; if tm_addr.len() != 40 { edisplay_line!( - IO, + context.io(), "Expected 40 characters in Tendermint address, got {}", tm_addr.len() ); cli::safe_exit(1); } let tm_addr = tm_addr.to_ascii_uppercase(); - let validator = unwrap_client_response::( - RPC.vp().pos().validator_by_tm_addr(client, &tm_addr).await, + let validator = unwrap_client_response::( + RPC.vp() + .pos() + .validator_by_tm_addr(context.client(), &tm_addr) + .await, ); match validator { Some(address) => { - display_line!(IO, "Found validator address \"{address}\".") + display_line!( + context.io(), + "Found validator address \"{address}\"." + ) } None => { display_line!( - IO, + context.io(), "No validator with Tendermint address {tm_addr} found." ) } @@ -2034,18 +2054,17 @@ pub async fn query_find_validator< } /// Dry run a transaction -pub async fn dry_run_tx( - client: &C, +pub async fn dry_run_tx<'a, N: Namada<'a>>( + context: &N, tx_bytes: Vec, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { display_line!( - IO, + context.io(), "Dry-run result: {}", - rpc::dry_run_tx::<_, IO>(client, tx_bytes).await? + rpc::dry_run_tx(context, tx_bytes).await? ); Ok(()) } @@ -2102,25 +2121,24 @@ pub async fn known_address( } /// Query for all conversions. -pub async fn query_conversions< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn query_conversions<'a>( + context: &impl Namada<'a>, args: args::QueryConversions, ) { // The chosen token type of the conversions let target_token = args.token; // To facilitate human readable token addresses - let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); + let tokens = context + .wallet() + .await + .get_addresses_with_vp_type(AddressVpType::Token); let masp_addr = masp(); let key_prefix: Key = masp_addr.to_db_key().into(); let state_key = key_prefix .push(&(token::CONVERSION_KEY_PREFIX.to_owned())) .unwrap(); - let conv_state = - query_storage_value::(client, &state_key) + let conv_state: ConversionState = + query_storage_value(context.client(), &state_key) .await .expect("Conversions should be defined"); // Track whether any non-sentinel conversions are found @@ -2139,7 +2157,7 @@ pub async fn query_conversions< conversions_found = true; // Print the asset to which the conversion applies display!( - IO, + context.io(), "{}[{}]: ", tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), epoch, @@ -2152,7 +2170,7 @@ pub async fn query_conversions< let ((addr, _), epoch, _, _) = &conv_state.assets[asset_type]; // Now print out this component of the conversion display!( - IO, + context.io(), "{}{} {}[{}]", prefix, val, @@ -2163,11 +2181,11 @@ pub async fn query_conversions< prefix = " + "; } // Allowed conversions are always implicit equations - display_line!(IO, " = 0"); + display_line!(context.io(), " = 0"); } if !conversions_found { display_line!( - IO, + context.io(), "No conversions found satisfying specified criteria." ); } @@ -2188,14 +2206,11 @@ pub async fn query_conversion( } /// Query a wasm code hash -pub async fn query_wasm_code_hash< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_wasm_code_hash<'a>( + context: &impl Namada<'a>, code_path: impl AsRef, ) -> Result { - rpc::query_wasm_code_hash(client, code_path).await + rpc::query_wasm_code_hash(context, code_path).await } /// Query a storage value and decode it with [`BorshDeserialize`]. @@ -2226,20 +2241,14 @@ pub async fn query_storage_value_bytes< /// Query a range of storage values with a matching prefix and decode them with /// [`BorshDeserialize`]. Returns an iterator of the storage keys paired with /// their associated values. -pub async fn query_storage_prefix< - C: namada::ledger::queries::Client + Sync, - T, - IO: Io, ->( - client: &C, +pub async fn query_storage_prefix<'a, 'b, T>( + context: &'b impl Namada<'a>, key: &storage::Key, -) -> Option> +) -> Option> where T: BorshDeserialize, { - rpc::query_storage_prefix::<_, IO, _>(client, key) - .await - .unwrap() + rpc::query_storage_prefix(context, key).await.unwrap() } /// Query to check if the given storage key exists. @@ -2277,20 +2286,20 @@ pub async fn query_tx_response( /// Lookup the results of applying the specified transaction to the /// blockchain. -pub async fn query_result( - client: &C, +pub async fn query_result<'a>( + context: &impl Namada<'a>, args: args::QueryResult, ) { // First try looking up application event pertaining to given hash. let tx_response = query_tx_response( - client, + context.client(), namada::sdk::rpc::TxEventQuery::Applied(&args.tx_hash), ) .await; match tx_response { Ok(result) => { display_line!( - IO, + context.io(), "Transaction was applied with result: {}", serde_json::to_string_pretty(&result).unwrap() ) @@ -2298,19 +2307,19 @@ pub async fn query_result( Err(err1) => { // If this fails then instead look for an acceptance event. let tx_response = query_tx_response( - client, + context.client(), namada::sdk::rpc::TxEventQuery::Accepted(&args.tx_hash), ) .await; match tx_response { Ok(result) => display_line!( - IO, + context.io(), "Transaction was accepted with result: {}", serde_json::to_string_pretty(&result).unwrap() ), Err(err2) => { // Print the errors that caused the lookups to fail - edisplay_line!(IO, "{}\n{}", err1, err2); + edisplay_line!(context.io(), "{}\n{}", err1, err2); cli::safe_exit(1) } } @@ -2318,16 +2327,13 @@ pub async fn query_result( } } -pub async fn epoch_sleep( - client: &C, - _args: args::Query, -) { - let start_epoch = query_and_print_epoch::<_, IO>(client).await; +pub async fn epoch_sleep<'a>(context: &impl Namada<'a>, _args: args::Query) { + let start_epoch = query_and_print_epoch(context).await; loop { tokio::time::sleep(core::time::Duration::from_secs(1)).await; - let current_epoch = query_epoch(client).await.unwrap(); + let current_epoch = query_epoch(context.client()).await.unwrap(); if current_epoch > start_epoch { - display_line!(IO, "Reached epoch {}", current_epoch); + display_line!(context.io(), "Reached epoch {}", current_epoch); break; } } @@ -2427,11 +2433,8 @@ fn unwrap_client_response( }) } -pub async fn compute_offline_proposal_votes< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn compute_offline_proposal_votes<'a>( + context: &impl Namada<'a>, proposal: &OfflineSignedProposal, votes: Vec, ) -> ProposalVotes { @@ -2444,11 +2447,11 @@ pub async fn compute_offline_proposal_votes< HashMap, > = HashMap::default(); for vote in votes { - let is_validator = is_validator(client, &vote.address).await; - let is_delegator = is_delegator(client, &vote.address).await; + let is_validator = is_validator(context.client(), &vote.address).await; + let is_delegator = is_delegator(context.client(), &vote.address).await; if is_validator { let validator_stake = get_validator_stake( - client, + context.client(), proposal.proposal.tally_epoch, &vote.address, ) @@ -2459,7 +2462,7 @@ pub async fn compute_offline_proposal_votes< .insert(vote.address.clone(), validator_stake); } else if is_delegator { let validators = get_delegators_delegation_at( - client, + context.client(), &vote.address.clone(), proposal.proposal.tally_epoch, ) @@ -2478,7 +2481,7 @@ pub async fn compute_offline_proposal_votes< } } else { display_line!( - IO, + context.io(), "Skipping vote, not a validator/delegator at epoch {}.", proposal.proposal.tally_epoch ); diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index c64fdf0044..22f0c1b1b4 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -6,14 +6,11 @@ use namada::core::ledger::governance::cli::offline::{ use namada::core::ledger::governance::cli::onchain::{ DefaultProposal, PgfFundingProposal, PgfStewardProposal, ProposalVote, }; -use namada::ledger::pos; -use namada::sdk::rpc::{TxBroadcastData, TxResponse}; -use namada::sdk::wallet::{Wallet, WalletIo}; -use namada::ledger::{Namada, NamadaImpl}; +use namada::ledger::{pos, Namada}; use namada::proof_of_stake::parameters::PosParams; use namada::proto::Tx; +use namada::sdk::rpc::{TxBroadcastData, TxResponse}; use namada::sdk::{error, signing, tx}; -use namada::tendermint_rpc::HttpClient; use namada::types::address::{Address, ImplicitAddress}; use namada::types::dec::Dec; use namada::types::io::Io; @@ -22,21 +19,18 @@ use namada::types::transaction::pos::InitValidator; use namada::{display_line, edisplay_line}; use super::rpc; -use crate::cli::{args, safe_exit, Context}; +use crate::cli::{args, safe_exit}; use crate::client::rpc::query_wasm_code_hash; use crate::client::tx::tx::ProcessTxResponse; use crate::config::TendermintMode; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::node::ledger::tendermint_node; -use crate::wallet::{ - gen_validator_keys, read_and_confirm_encryption_password, CliWalletUtils, -}; -use namada::types::io::StdIo; +use crate::wallet::{gen_validator_keys, read_and_confirm_encryption_password}; /// Wrapper around `signing::aux_signing_data` that stores the optional /// disposable address to the wallet pub async fn aux_signing_data<'a>( - context: &impl Namada<'a, WalletUtils = CliWalletUtils>, + context: &impl Namada<'a>, args: &args::Tx, owner: Option
, default_signer: Option
, @@ -47,14 +41,14 @@ pub async fn aux_signing_data<'a>( if args.disposable_signing_key { if !(args.dry_run || args.dry_run_wrapper) { // Store the generated signing key to wallet in case of need - crate::wallet::save(*context.wallet().await).map_err(|_| { + context.wallet().await.save().map_err(|_| { error::Error::Other( "Failed to save disposable address to wallet".to_string(), ) })?; } else { display_line!( - StdIo, + context.io(), "Transaction dry run. The disposable address will not be \ saved to wallet." ) @@ -103,24 +97,21 @@ pub async fn submit_reveal_aux<'a>( Ok(()) } -pub async fn submit_custom( - client: &C, - ctx: &mut Context, +pub async fn submit_custom<'a, N: Namada<'a>>( + namada: &N, args: args::TxCustom, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - submit_reveal_aux(&namada, args.tx.clone(), &args.owner).await?; + submit_reveal_aux(namada, args.tx.clone(), &args.owner).await?; - let (mut tx, signing_data, _epoch) = args.build(&namada).await?; + let (mut tx, signing_data, _epoch) = args.build(namada).await?; - signing::generate_test_vector(&namada, &tx).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; @@ -129,22 +120,19 @@ where Ok(()) } -pub async fn submit_update_account( - client: &C, - ctx: &mut Context, +pub async fn submit_update_account<'a, N: Namada<'a>>( + namada: &N, args: args::TxUpdateAccount, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - let (mut tx, signing_data, _epoch) = args.build(&namada).await?; + let (mut tx, signing_data, _epoch) = args.build(namada).await?; - signing::generate_test_vector(&namada, &tx).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; @@ -153,23 +141,20 @@ where Ok(()) } -pub async fn submit_init_account( - client: &C, - ctx: &mut Context, +pub async fn submit_init_account<'a, N: Namada<'a>>( + namada: &N, args: args::TxInitAccount, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _epoch) = - tx::build_init_account(&namada, &args).await?; + tx::build_init_account(namada, &args).await?; - signing::generate_test_vector(&namada, &tx).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; @@ -178,9 +163,9 @@ where Ok(()) } -pub async fn submit_init_validator( - client: &C, - mut ctx: Context, +pub async fn submit_init_validator<'a>( + namada: &impl Namada<'a>, + config: &mut crate::config::Config, args::TxInitValidator { tx: tx_args, scheme, @@ -196,15 +181,12 @@ pub async fn submit_init_validator( unsafe_dont_encrypt, tx_code_path: _, }: args::TxInitValidator, -) -> Result<(), error::Error> -where - C: namada::ledger::queries::Client + Sync, -{ +) -> Result<(), error::Error> { let tx_args = args::Tx { chain_id: tx_args .clone() .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())), + .or_else(|| Some(config.ledger.chain_id.clone())), ..tx_args.clone() }; let alias = tx_args @@ -229,19 +211,23 @@ where let eth_hot_key_alias = format!("{}-eth-hot-key", alias); let eth_cold_key_alias = format!("{}-eth-cold-key", alias); + let mut wallet = namada.wallet_mut().await; let consensus_key = consensus_key .map(|key| match key { common::SecretKey::Ed25519(_) => key, common::SecretKey::Secp256k1(_) => { - edisplay_line!(IO, "Consensus key can only be ed25519"); + edisplay_line!( + namada.io(), + "Consensus key can only be ed25519" + ); safe_exit(1) } }) .unwrap_or_else(|| { - display_line!(IO, "Generating consensus key..."); + display_line!(namada.io(), "Generating consensus key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet + wallet .gen_key( // Note that TM only allows ed25519 for consensus key SchemeType::Ed25519, @@ -259,15 +245,18 @@ where .map(|key| match key { common::SecretKey::Secp256k1(_) => key.ref_to(), common::SecretKey::Ed25519(_) => { - edisplay_line!(IO, "Eth cold key can only be secp256k1"); + edisplay_line!( + namada.io(), + "Eth cold key can only be secp256k1" + ); safe_exit(1) } }) .unwrap_or_else(|| { - display_line!(IO, "Generating Eth cold key..."); + display_line!(namada.io(), "Generating Eth cold key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet + wallet .gen_key( // Note that ETH only allows secp256k1 SchemeType::Secp256k1, @@ -286,15 +275,18 @@ where .map(|key| match key { common::SecretKey::Secp256k1(_) => key.ref_to(), common::SecretKey::Ed25519(_) => { - edisplay_line!(IO, "Eth hot key can only be secp256k1"); + edisplay_line!( + namada.io(), + "Eth hot key can only be secp256k1" + ); safe_exit(1) } }) .unwrap_or_else(|| { - display_line!(IO, "Generating Eth hot key..."); + display_line!(namada.io(), "Generating Eth hot key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet + wallet .gen_key( // Note that ETH only allows secp256k1 SchemeType::Secp256k1, @@ -308,13 +300,15 @@ where .1 .ref_to() }); + // To avoid wallet deadlocks in following operations + drop(wallet); if protocol_key.is_none() { - display_line!(IO, "Generating protocol signing key..."); + display_line!(namada.io(), "Generating protocol signing key..."); } // Generate the validator keys let validator_keys = gen_validator_keys( - &mut ctx.wallet, + *namada.wallet_mut().await, Some(eth_hot_pk.clone()), protocol_key, scheme, @@ -327,17 +321,15 @@ where .expect("DKG sessions keys should have been created") .public(); - let validator_vp_code_hash = query_wasm_code_hash::( - client, - validator_vp_code_path.to_str().unwrap(), - ) - .await - .unwrap(); + let validator_vp_code_hash = + query_wasm_code_hash(namada, validator_vp_code_path.to_str().unwrap()) + .await + .unwrap(); // Validate the commission rate data if commission_rate > Dec::one() || commission_rate < Dec::zero() { edisplay_line!( - IO, + namada.io(), "The validator commission rate must not exceed 1.0 or 100%, and \ it must be 0 or positive" ); @@ -349,7 +341,7 @@ where || max_commission_rate_change < Dec::zero() { edisplay_line!( - IO, + namada.io(), "The validator maximum change in commission rate per epoch must \ not exceed 1.0 or 100%" ); @@ -358,7 +350,7 @@ where } } let tx_code_hash = - query_wasm_code_hash::<_, IO>(client, args::TX_INIT_VALIDATOR_WASM) + query_wasm_code_hash(namada, args::TX_INIT_VALIDATOR_WASM) .await .unwrap(); @@ -384,11 +376,10 @@ where tx.add_code_from_hash(tx_code_hash).add_data(data); - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - let signing_data = aux_signing_data(&namada, &tx_args, None, None).await?; + let signing_data = aux_signing_data(namada, &tx_args, None, None).await?; tx::prepare_tx( - &namada, + namada, &tx_args, &mut tx, signing_data.fee_payer.clone(), @@ -396,10 +387,10 @@ where ) .await?; - signing::generate_test_vector(&namada, &tx).await?; + signing::generate_test_vector(namada, &tx).await?; if tx_args.dump_tx { - tx::dump_tx::(&tx_args, tx); + tx::dump_tx(namada.io(), &tx_args, tx); } else { namada.sign(&mut tx, &tx_args, signing_data).await?; @@ -411,29 +402,37 @@ where // There should be 1 account for the validator itself [validator_address] => { if let Some(alias) = - ctx.wallet.find_alias(validator_address) + namada.wallet().await.find_alias(validator_address) { (alias.clone(), validator_address.clone()) } else { edisplay_line!( - IO, + namada.io(), "Expected one account to be created" ); safe_exit(1) } } _ => { - edisplay_line!(IO, "Expected one account to be created"); + edisplay_line!( + namada.io(), + "Expected one account to be created" + ); safe_exit(1) } }; // add validator address and keys to the wallet - ctx.wallet + namada + .wallet_mut() + .await .add_validator_data(validator_address, validator_keys); - crate::wallet::save(&ctx.wallet) - .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); + namada + .wallet_mut() + .await + .save() + .unwrap_or_else(|err| edisplay_line!(namada.io(), "{}", err)); - let tendermint_home = ctx.config.ledger.cometbft_dir(); + let tendermint_home = config.ledger.cometbft_dir(); tendermint_node::write_validator_key( &tendermint_home, &consensus_key, @@ -442,51 +441,55 @@ where // Write Namada config stuff or figure out how to do the above // tendermint_node things two epochs in the future!!! - ctx.config.ledger.shell.tendermint_mode = TendermintMode::Validator; - ctx.config + config.ledger.shell.tendermint_mode = TendermintMode::Validator; + config .write( - &ctx.config.ledger.shell.base_dir, - &ctx.config.ledger.chain_id, + &config.ledger.shell.base_dir, + &config.ledger.chain_id, true, ) .unwrap(); let key = pos::params_key(); - let pos_params = - rpc::query_storage_value::(client, &key) + let pos_params: PosParams = + rpc::query_storage_value(namada.client(), &key) .await .expect("Pos parameter should be defined."); - display_line!(IO, ""); + display_line!(namada.io(), ""); display_line!( - IO, + namada.io(), "The validator's addresses and keys were stored in the wallet:" ); display_line!( - IO, + namada.io(), " Validator address \"{}\"", validator_address_alias ); display_line!( - IO, + namada.io(), " Validator account key \"{}\"", validator_key_alias ); - display_line!(IO, " Consensus key \"{}\"", consensus_key_alias); display_line!( - IO, + namada.io(), + " Consensus key \"{}\"", + consensus_key_alias + ); + display_line!( + namada.io(), "The ledger node has been setup to use this validator's \ address and consensus key." ); display_line!( - IO, + namada.io(), "Your validator will be active in {} epochs. Be sure to \ restart your node for the changes to take effect!", pos_params.pipeline_len ); } else { display_line!( - IO, + namada.io(), "Transaction dry run. No addresses have been saved." ); } @@ -494,35 +497,30 @@ where Ok(()) } -pub async fn submit_transfer( - client: &C, - mut ctx: Context, +pub async fn submit_transfer<'a>( + namada: &impl Namada<'a>, args: args::TxTransfer, ) -> Result<(), error::Error> { for _ in 0..2 { - let namada = - NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - submit_reveal_aux( - &namada, + namada, args.tx.clone(), &args.source.effective_address(), ) .await?; let (mut tx, signing_data, tx_epoch) = - args.clone().build(&namada).await?; - signing::generate_test_vector(&namada, &tx).await?; + args.clone().build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); break; } else { namada.sign(&mut tx, &args.tx, signing_data).await?; let result = namada.submit(tx, &args.tx).await?; - let submission_epoch = - rpc::query_and_print_epoch::<_, IO>(client).await; + let submission_epoch = rpc::query_and_print_epoch(namada).await; match result { ProcessTxResponse::Applied(resp) if @@ -534,7 +532,7 @@ pub async fn submit_transfer( tx_epoch.unwrap() != submission_epoch => { // Then we probably straddled an epoch boundary. Let's retry... - edisplay_line!(IO, + edisplay_line!(namada.io(), "MASP transaction rejected and this may be due to the \ epoch changing. Attempting to resubmit transaction.", ); @@ -550,22 +548,19 @@ pub async fn submit_transfer( Ok(()) } -pub async fn submit_ibc_transfer( - client: &C, - mut ctx: Context, +pub async fn submit_ibc_transfer<'a, N: Namada<'a>>( + namada: &N, args: args::TxIbcTransfer, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - submit_reveal_aux(&namada, args.tx.clone(), &args.source).await?; - let (mut tx, signing_data, _epoch) = args.build(&namada).await?; - signing::generate_test_vector(&namada, &tx).await?; + submit_reveal_aux(namada, args.tx.clone(), &args.source).await?; + let (mut tx, signing_data, _epoch) = args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; @@ -574,18 +569,16 @@ where Ok(()) } -pub async fn submit_init_proposal( - client: &C, - mut ctx: Context, +pub async fn submit_init_proposal<'a, N: Namada<'a>>( + namada: &N, args: args::InitProposal, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let current_epoch = rpc::query_and_print_epoch::<_, IO>(client).await; - let governance_parameters = rpc::query_governance_parameters(client).await; - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); + let current_epoch = rpc::query_and_print_epoch(namada).await; + let governance_parameters = + rpc::query_governance_parameters(namada.client()).await; let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline { let proposal = OfflineProposal::try_from(args.proposal_data.as_ref()) @@ -599,7 +592,7 @@ where let default_signer = Some(proposal.author.clone()); let signing_data = aux_signing_data( - &namada, + namada, &args.tx, Some(proposal.author.clone()), default_signer, @@ -618,7 +611,11 @@ where ) })?; - display_line!(IO, "Proposal serialized to: {}", output_file_path); + display_line!( + namada.io(), + "Proposal serialized to: {}", + output_file_path + ); return Ok(()); } else if args.is_pgf_funding { let proposal = @@ -631,10 +628,10 @@ where .validate(&governance_parameters, current_epoch, args.tx.force) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - submit_reveal_aux(&namada, args.tx.clone(), &proposal.proposal.author) + submit_reveal_aux(namada, args.tx.clone(), &proposal.proposal.author) .await?; - tx::build_pgf_funding_proposal(&namada, &args, proposal).await? + tx::build_pgf_funding_proposal(namada, &args, proposal).await? } else if args.is_pgf_stewards { let proposal = PgfStewardProposal::try_from( args.proposal_data.as_ref(), @@ -643,8 +640,8 @@ where error::TxError::FailedGovernaneProposalDeserialize(e.to_string()) })?; let author_balance = rpc::get_token_balance( - client, - &ctx.native_token, + namada.client(), + &namada.native_token().await, &proposal.proposal.author, ) .await; @@ -657,18 +654,18 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - submit_reveal_aux(&namada, args.tx.clone(), &proposal.proposal.author) + submit_reveal_aux(namada, args.tx.clone(), &proposal.proposal.author) .await?; - tx::build_pgf_stewards_proposal(&namada, &args, proposal).await? + tx::build_pgf_stewards_proposal(namada, &args, proposal).await? } else { let proposal = DefaultProposal::try_from(args.proposal_data.as_ref()) .map_err(|e| { error::TxError::FailedGovernaneProposalDeserialize(e.to_string()) })?; let author_balane = rpc::get_token_balance( - client, - &ctx.native_token, + namada.client(), + &namada.native_token().await, &proposal.proposal.author, ) .await; @@ -681,15 +678,15 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - submit_reveal_aux(&namada, args.tx.clone(), &proposal.proposal.author) + submit_reveal_aux(namada, args.tx.clone(), &proposal.proposal.author) .await?; - tx::build_default_proposal(&namada, &args, proposal).await? + tx::build_default_proposal(namada, &args, proposal).await? }; - signing::generate_test_vector(&namada, &tx_builder).await?; + signing::generate_test_vector(namada, &tx_builder).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx_builder); + tx::dump_tx(namada.io(), &args.tx, tx_builder); } else { namada.sign(&mut tx_builder, &args.tx, signing_data).await?; namada.submit(tx_builder, &args.tx).await?; @@ -698,21 +695,18 @@ where Ok(()) } -pub async fn submit_vote_proposal( - client: &C, - mut ctx: Context, +pub async fn submit_vote_proposal<'a, N: Namada<'a>>( + namada: &N, args: args::VoteProposal, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline { let default_signer = Some(args.voter.clone()); let signing_data = aux_signing_data( - &namada, + namada, &args.tx, Some(args.voter.clone()), default_signer.clone(), @@ -733,7 +727,7 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; let delegations = rpc::get_delegators_delegation_at( - client, + namada.client(), &args.voter, proposal.proposal.tally_epoch, ) @@ -757,15 +751,19 @@ where .serialize(args.tx.output_folder) .expect("Should be able to serialize the offline proposal"); - display_line!(IO, "Proposal vote serialized to: {}", output_file_path); + display_line!( + namada.io(), + "Proposal vote serialized to: {}", + output_file_path + ); return Ok(()); } else { - args.build(&namada).await? + args.build(namada).await? }; - signing::generate_test_vector(&namada, &tx_builder).await?; + signing::generate_test_vector(namada, &tx_builder).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx_builder); + tx::dump_tx(namada.io(), &args.tx, tx_builder); } else { namada.sign(&mut tx_builder, &args.tx, signing_data).await?; namada.submit(tx_builder, &args.tx).await?; @@ -774,9 +772,8 @@ where Ok(()) } -pub async fn sign_tx( - client: &C, - ctx: &mut Context, +pub async fn sign_tx<'a, N: Namada<'a>>( + namada: &N, args::SignTx { tx: tx_args, tx_data, @@ -784,36 +781,31 @@ pub async fn sign_tx( }: args::SignTx, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { let tx = if let Ok(transaction) = Tx::deserialize(tx_data.as_ref()) { transaction } else { - edisplay_line!(IO, "Couldn't decode the transaction."); + edisplay_line!(namada.io(), "Couldn't decode the transaction."); safe_exit(1) }; - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let default_signer = Some(owner.clone()); - let signing_data = aux_signing_data( - &namada, - &tx_args, - Some(owner.clone()), - default_signer, - ) - .await?; + let signing_data = + aux_signing_data(namada, &tx_args, Some(owner.clone()), default_signer) + .await?; + let mut wallet = namada.wallet_mut().await; let secret_keys = &signing_data .public_keys .iter() .filter_map(|public_key| { if let Ok(secret_key) = - signing::find_key_by_pk(&mut ctx.wallet, &tx_args, public_key) + signing::find_key_by_pk(&mut wallet, &tx_args, public_key) { Some(secret_key) } else { edisplay_line!( - IO, + namada.io(), "Couldn't find the secret key for {}. Skipping signature \ generation.", public_key @@ -851,7 +843,7 @@ where ) .expect("Signature should be deserializable."); display_line!( - IO, + namada.io(), "Signature for {} serialized at {}", signature.pubkey, output_path.display() @@ -861,40 +853,34 @@ where Ok(()) } -pub async fn submit_reveal_pk( - client: &C, - ctx: &mut Context, +pub async fn submit_reveal_pk<'a, N: Namada<'a>>( + namada: &N, args: args::RevealPk, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - submit_reveal_aux(&namada, args.tx, &(&args.public_key).into()).await?; + submit_reveal_aux(namada, args.tx, &(&args.public_key).into()).await?; Ok(()) } -pub async fn submit_bond( - client: &C, - ctx: &mut Context, +pub async fn submit_bond<'a, N: Namada<'a>>( + namada: &N, args: args::Bond, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let default_address = args.source.clone().unwrap_or(args.validator.clone()); - submit_reveal_aux(&namada, args.tx.clone(), &default_address).await?; + submit_reveal_aux(namada, args.tx.clone(), &default_address).await?; let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(&namada).await?; - signing::generate_test_vector(&namada, &tx).await?; + args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data).await?; @@ -904,50 +890,43 @@ where Ok(()) } -pub async fn submit_unbond( - client: &C, - ctx: &mut Context, +pub async fn submit_unbond<'a, N: Namada<'a>>( + namada: &N, args: args::Unbond, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _fee_unshield_epoch, latest_withdrawal_pre) = - args.build(&namada).await?; - signing::generate_test_vector(&namada, &tx).await?; + args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; - tx::query_unbonds::<_, IO>(client, args.clone(), latest_withdrawal_pre) - .await?; + tx::query_unbonds(namada, args.clone(), latest_withdrawal_pre).await?; } Ok(()) } -pub async fn submit_withdraw( - client: &C, - mut ctx: Context, +pub async fn submit_withdraw<'a, N: Namada<'a>>( + namada: &N, args: args::Withdraw, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(&namada).await?; - signing::generate_test_vector(&namada, &tx).await?; + args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data).await?; @@ -957,21 +936,19 @@ where Ok(()) } -pub async fn submit_validator_commission_change( - client: &C, - mut ctx: Context, +pub async fn submit_validator_commission_change<'a, N: Namada<'a>>( + namada: &N, args: args::CommissionRateChange, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(&namada).await?; - signing::generate_test_vector(&namada, &tx).await?; + args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data).await?; @@ -981,24 +958,19 @@ where Ok(()) } -pub async fn submit_unjail_validator< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - mut ctx: Context, +pub async fn submit_unjail_validator<'a, N: Namada<'a>>( + namada: &N, args: args::TxUnjailValidator, ) -> Result<(), error::Error> where - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(&namada).await?; - signing::generate_test_vector(&namada, &tx).await?; + args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data).await?; @@ -1008,26 +980,20 @@ where Ok(()) } -pub async fn submit_update_steward_commission< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - mut ctx: Context, +pub async fn submit_update_steward_commission<'a, N: Namada<'a>>( + namada: &N, args: args::UpdateStewardCommission, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); let (mut tx, signing_data, _fee_unshield_epoch) = - args.build(&namada).await?; + args.build(namada).await?; - signing::generate_test_vector(&namada, &tx).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; @@ -1036,22 +1002,19 @@ where Ok(()) } -pub async fn submit_resign_steward( - client: &C, - mut ctx: Context, +pub async fn submit_resign_steward<'a, N: Namada<'a>>( + namada: &N, args: args::ResignSteward, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let namada = NamadaImpl::new(client, &mut ctx.wallet, &mut ctx.shielded); - let (mut tx, signing_data, _epoch) = args.build(&namada).await?; + let (mut tx, signing_data, _epoch) = args.build(namada).await?; - signing::generate_test_vector(&namada, &tx).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { namada.sign(&mut tx, &args.tx, signing_data).await?; namada.submit(tx, &args.tx).await?; @@ -1061,24 +1024,23 @@ where } /// Save accounts initialized from a tx into the wallet, if any. -pub async fn save_initialized_accounts( - wallet: &mut Wallet, +pub async fn save_initialized_accounts<'a>( + namada: &impl Namada<'a>, args: &args::Tx, initialized_accounts: Vec
, ) { - tx::save_initialized_accounts::(wallet, args, initialized_accounts) - .await + tx::save_initialized_accounts(namada, args, initialized_accounts).await } /// Broadcast a transaction to be included in the blockchain and checks that /// the tx has been successfully included into the mempool of a validator /// /// In the case of errors in any of those stages, an error message is returned -pub async fn broadcast_tx( - rpc_cli: &HttpClient, +pub async fn broadcast_tx<'a>( + namada: &impl Namada<'a>, to_broadcast: &TxBroadcastData, ) -> Result { - tx::broadcast_tx::<_, IO>(rpc_cli, to_broadcast).await + tx::broadcast_tx(namada, to_broadcast).await } /// Broadcast a transaction to be included in the blockchain. @@ -1089,9 +1051,9 @@ pub async fn broadcast_tx( /// 3. The decrypted payload of the tx has been included on the blockchain. /// /// In the case of errors in any of those stages, an error message is returned -pub async fn submit_tx( - client: &HttpClient, +pub async fn submit_tx<'a>( + namada: &impl Namada<'a>, to_broadcast: TxBroadcastData, ) -> Result { - tx::submit_tx::<_, IO>(client, to_broadcast).await + tx::submit_tx(namada, to_broadcast).await } diff --git a/apps/src/lib/node/ledger/shell/testing/client.rs b/apps/src/lib/node/ledger/shell/testing/client.rs index 9ebc825f54..504bdc7e5b 100644 --- a/apps/src/lib/node/ledger/shell/testing/client.rs +++ b/apps/src/lib/node/ledger/shell/testing/client.rs @@ -50,6 +50,7 @@ pub fn run( rt.block_on(CliApi::::handle_client_command( Some(node), cmd, + &TestingIo, )) } Bin::Wallet => { @@ -60,7 +61,7 @@ pub fn run( let cmd = cmds::NamadaWallet::parse(&matches) .expect("Could not parse wallet command"); - CliApi::::handle_wallet_command(cmd, ctx) + CliApi::::handle_wallet_command(cmd, ctx, &TestingIo) } Bin::Relayer => { args.insert(0, "relayer"); @@ -85,6 +86,7 @@ pub fn run( rt.block_on(CliApi::::handle_relayer_command( Some(node), cmd, + &TestingIo, )) } } @@ -96,7 +98,7 @@ impl<'a> CliClient for &'a MockNode { unreachable!("MockNode should always be instantiated at test start.") } - async fn wait_until_node_is_synced(&self) -> Halt<()> { + async fn wait_until_node_is_synced(&self, _io: &impl Io) -> Halt<()> { ControlFlow::Continue(()) } } diff --git a/apps/src/lib/node/ledger/shell/testing/utils.rs b/apps/src/lib/node/ledger/shell/testing/utils.rs index bfcb7f50ab..451e20c2df 100644 --- a/apps/src/lib/node/ledger/shell/testing/utils.rs +++ b/apps/src/lib/node/ledger/shell/testing/utils.rs @@ -74,13 +74,13 @@ pub struct TestingIo; #[async_trait::async_trait(?Send)] impl Io for TestingIo { - fn print(output: impl AsRef) { + fn print(&self, output: impl AsRef) { let mut testout = TESTOUT.lock().unwrap(); testout.append(output.as_ref().as_bytes().to_vec()); print!("{}", output.as_ref()); } - fn println(output: impl AsRef) { + fn println(&self, output: impl AsRef) { let mut testout = TESTOUT.lock().unwrap(); let mut bytes = output.as_ref().as_bytes().to_vec(); bytes.extend_from_slice("\n".as_bytes()); @@ -89,22 +89,24 @@ impl Io for TestingIo { } fn write( + &self, _: W, output: impl AsRef, ) -> std::io::Result<()> { - Self::print(output); + self.print(output); Ok(()) } fn writeln( + &self, _: W, output: impl AsRef, ) -> std::io::Result<()> { - Self::println(output); + self.println(output); Ok(()) } - fn eprintln(output: impl AsRef) { + fn eprintln(&self, output: impl AsRef) { let mut testout = TESTOUT.lock().unwrap(); let mut bytes = output.as_ref().as_bytes().to_vec(); bytes.extend_from_slice("\n".as_bytes()); @@ -112,11 +114,11 @@ impl Io for TestingIo { eprintln!("{}", output.as_ref()); } - async fn read() -> tokio::io::Result { + async fn read(&self) -> tokio::io::Result { read_aux(&*TESTIN).await } - async fn prompt(question: impl AsRef) -> String { + async fn prompt(&self, question: impl AsRef) -> String { prompt_aux(&*TESTIN, tokio::io::stdout(), question.as_ref()).await } } diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index cca43a43d6..61c49fe580 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -10,6 +10,8 @@ use std::{env, fs}; use namada::bip39::{Language, Mnemonic}; pub use namada::sdk::wallet::alias::Alias; +use namada::sdk::wallet::fs::FsWalletStorage; +use namada::sdk::wallet::store::Store; use namada::sdk::wallet::{ AddressVpType, ConfirmationResponse, FindKeyError, GenRestoreKeyError, Wallet, WalletIo, @@ -20,8 +22,6 @@ use namada::types::key::*; use rand_core::OsRng; pub use store::wallet_file; use zeroize::Zeroizing; -use namada::sdk::wallet::store::Store; -use namada::sdk::wallet::fs::FsWalletStorage; use crate::cli; use crate::config::genesis::genesis_config::GenesisConfig; diff --git a/benches/lib.rs b/benches/lib.rs index 3aacaef90f..b5036d5f66 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -69,7 +69,6 @@ use namada::ledger::queries::{ Client, EncodedResponseQuery, RequestCtx, RequestQuery, Router, RPC, }; use namada::ledger::storage_api::StorageRead; -use namada::sdk::wallet::Wallet; use namada::ledger::NamadaImpl; use namada::proof_of_stake; use namada::proto::{Code, Data, Section, Signature, Tx}; @@ -77,6 +76,7 @@ use namada::sdk::args::InputAmount; use namada::sdk::masp::{ self, ShieldedContext, ShieldedTransfer, ShieldedUtils, }; +use namada::sdk::wallet::Wallet; use namada::tendermint::Hash; use namada::tendermint_rpc::{self}; use namada::types::address::InternalAddress; @@ -689,13 +689,12 @@ impl Default for BenchShieldedCtx { fn default() -> Self { let mut shell = BenchShell::default(); - let mut ctx = - Context::new::(namada_apps::cli::args::Global { - chain_id: None, - base_dir: shell.tempdir.as_ref().canonicalize().unwrap(), - wasm_dir: Some(WASM_DIR.into()), - }) - .unwrap(); + let mut ctx = Context::new::(namada_apps::cli::args::Global { + chain_id: None, + base_dir: shell.tempdir.as_ref().canonicalize().unwrap(), + wasm_dir: Some(WASM_DIR.into()), + }) + .unwrap(); // Generate spending key for Albert and Bertha ctx.wallet.gen_spending_key( @@ -811,8 +810,12 @@ impl BenchShieldedCtx { &[], )) .unwrap(); - let namada = - NamadaImpl::new(&self.shell, &mut self.wallet, &mut self.shielded); + let namada = NamadaImpl::new( + &self.shell, + &mut self.wallet, + &mut self.shielded, + &StdIo, + ); let shielded = async_runtime .block_on( ShieldedContext::::gen_shielded_transfer( diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs index a73f5efd77..66ec22a63b 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/shared/src/ledger/eth_bridge.rs @@ -102,6 +102,7 @@ pub struct BlockOnEthSync { /// Block until Ethereum finishes synchronizing. pub async fn block_on_eth_sync( client: &C, + io: &IO, args: BlockOnEthSync, ) -> Halt<()> where @@ -111,7 +112,7 @@ where deadline, delta_sleep, } = args; - display_line!(IO, "Attempting to synchronize with the Ethereum network"); + display_line!(io, "Attempting to synchronize with the Ethereum network"); Sleep { strategy: LinearBackoff { delta: delta_sleep }, } @@ -128,11 +129,11 @@ where .await .try_halt(|_| { edisplay_line!( - IO, + io, "Timed out while waiting for Ethereum to synchronize" ); })?; - display_line!(IO, "The Ethereum node is up to date"); + display_line!(io, "The Ethereum node is up to date"); control_flow::proceed(()) } @@ -140,6 +141,7 @@ where /// not, perform `action`. pub async fn eth_sync_or( client: &C, + io: &IO, mut action: F, ) -> Halt> where @@ -151,7 +153,7 @@ where .map(|status| status.is_synchronized()) .try_halt(|err| { edisplay_line!( - IO, + io, "An error occurred while fetching the Ethereum \ synchronization status: {err}" ); @@ -165,11 +167,11 @@ where /// Check if Ethereum has finished synchronizing. In case it has /// not, end execution. -pub async fn eth_sync_or_exit(client: &C) -> Halt<()> +pub async fn eth_sync_or_exit(client: &C, io: &IO) -> Halt<()> where C: Middleware, { - eth_sync_or::<_, _, _, IO>(client, || { + eth_sync_or(client, io, || { tracing::error!("The Ethereum node has not finished synchronizing"); }) .await? diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 14002b53d3..b4c633ca78 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -19,12 +19,12 @@ use crate::ledger::queries::{ Client, GenBridgePoolProofReq, GenBridgePoolProofRsp, TransferToErcArgs, RPC, }; -use crate::sdk::rpc::{query_wasm_code_hash, validate_amount}; use crate::ledger::signing::aux_signing_data; use crate::ledger::tx::prepare_tx; use crate::ledger::{args, Namada, SigningTxData}; use crate::proto::Tx; use crate::sdk::error::Error; +use crate::sdk::rpc::{query_wasm_code_hash, validate_amount}; use crate::types::address::Address; use crate::types::control_flow::time::{Duration, Instant}; use crate::types::control_flow::{ @@ -40,7 +40,6 @@ use crate::types::token::{Amount, DenominatedAmount}; use crate::types::voting_power::FractionalVotingPower; use crate::{display, display_line}; - /// Craft a transaction that adds a transfer to the Ethereum bridge pool. pub async fn build_bridge_pool_tx<'a>( context: &impl Namada<'a>, @@ -67,7 +66,7 @@ pub async fn build_bridge_pool_tx<'a>( .await?; let fee_payer = fee_payer.unwrap_or_else(|| sender.clone()); let DenominatedAmount { amount, .. } = validate_amount( - context.client(), + context, amount, &wrapped_erc20s::token(&asset), tx_args.force, @@ -76,19 +75,14 @@ pub async fn build_bridge_pool_tx<'a>( .map_err(|e| Error::Other(format!("Failed to validate amount. {}", e)))?; let DenominatedAmount { amount: fee_amount, .. - } = validate_amount( - context.client(), - fee_amount, - &fee_token, - tx_args.force, - ) - .await - .map_err(|e| { - Error::Other(format!( - "Failed to validate Bridge pool fee amount. {}", - e - )) - })?; + } = validate_amount(context, fee_amount, &fee_token, tx_args.force) + .await + .map_err(|e| { + Error::Other(format!( + "Failed to validate Bridge pool fee amount. {}", + e + )) + })?; let transfer = PendingTransfer { transfer: TransferToEthereum { asset, @@ -109,7 +103,7 @@ pub async fn build_bridge_pool_tx<'a>( }; let tx_code_hash = - query_wasm_code_hash(context.client(), code_path.to_str().unwrap()) + query_wasm_code_hash(context, code_path.to_str().unwrap()) .await .unwrap(); @@ -140,10 +134,10 @@ struct BridgePoolResponse { /// Query the contents of the Ethereum bridge pool. /// Prints out a json payload. -pub async fn query_bridge_pool(client: &C) -where - C: Client + Sync, -{ +pub async fn query_bridge_pool<'a>( + client: &(impl Client + Sync), + io: &impl Io, +) { let response: Vec = RPC .shell() .eth_bridge() @@ -155,24 +149,22 @@ where .map(|transfer| (transfer.keccak256().to_string(), transfer)) .collect(); if pool_contents.is_empty() { - display_line!(IO, "Bridge pool is empty."); + display_line!(io, "Bridge pool is empty."); return; } let contents = BridgePoolResponse { bridge_pool_contents: pool_contents, }; - display_line!(IO, "{}", serde_json::to_string_pretty(&contents).unwrap()); + display_line!(io, "{}", serde_json::to_string_pretty(&contents).unwrap()); } /// Query the contents of the Ethereum bridge pool that /// is covered by the latest signed root. /// Prints out a json payload. -pub async fn query_signed_bridge_pool( - client: &C, -) -> Halt> -where - C: Client + Sync, -{ +pub async fn query_signed_bridge_pool<'a>( + client: &(impl Client + Sync), + io: &impl Io, +) -> Halt> { let response: Vec = RPC .shell() .eth_bridge() @@ -184,13 +176,13 @@ where .map(|transfer| (transfer.keccak256().to_string(), transfer)) .collect(); if pool_contents.is_empty() { - display_line!(IO, "Bridge pool is empty."); + display_line!(io, "Bridge pool is empty."); return control_flow::halt(); } let contents = BridgePoolResponse { bridge_pool_contents: pool_contents.clone(), }; - display_line!(IO, "{}", serde_json::to_string_pretty(&contents).unwrap()); + display_line!(io, "{}", serde_json::to_string_pretty(&contents).unwrap()); control_flow::proceed(pool_contents) } @@ -199,28 +191,26 @@ where /// backing each `TransferToEthereum` event. /// /// Prints a json payload. -pub async fn query_relay_progress(client: &C) -where - C: Client + Sync, -{ +pub async fn query_relay_progress<'a>( + client: &(impl Client + Sync), + io: &impl Io, +) { let resp = RPC .shell() .eth_bridge() .transfer_to_ethereum_progress(client) .await .unwrap(); - display_line!(IO, "{}", serde_json::to_string_pretty(&resp).unwrap()); + display_line!(io, "{}", serde_json::to_string_pretty(&resp).unwrap()); } /// Internal methdod to construct a proof that a set of transfers are in the /// bridge pool. -async fn construct_bridge_pool_proof( - client: &C, +async fn construct_bridge_pool_proof<'a>( + client: &(impl Client + Sync), + io: &impl Io, args: GenBridgePoolProofReq<'_, '_>, -) -> Halt -where - C: Client + Sync, -{ +) -> Halt { let in_progress = RPC .shell() .eth_bridge() @@ -245,19 +235,19 @@ where let warning = warning.bold(); let warning = warning.blink(); display_line!( - IO, + io, "{warning}: The following hashes correspond to transfers that \ have surpassed the security threshold in Namada, therefore have \ likely been relayed to Ethereum, but do not yet have a quorum of \ validator signatures behind them in Namada; thus they are still \ in the Bridge pool:\n{warnings:?}", ); - display!(IO, "\nDo you wish to proceed? (y/n): "); - IO::flush(); + display!(io, "\nDo you wish to proceed? (y/n): "); + io.flush(); loop { - let resp = IO::read().await.try_halt(|e| { + let resp = io.read().await.try_halt(|e| { display_line!( - IO, + io, "Encountered error reading from STDIN: {e:?}" ); })?; @@ -265,8 +255,8 @@ where "y" => break, "n" => return control_flow::halt(), _ => { - display!(IO, "Expected 'y' or 'n'. Please try again: "); - IO::flush(); + display!(io, "Expected 'y' or 'n'. Please try again: "); + io.flush(); } } } @@ -280,7 +270,7 @@ where .await; response.map(|response| response.data).try_halt(|e| { - display_line!(IO, "Encountered error constructing proof:\n{:?}", e); + display_line!(io, "Encountered error constructing proof:\n{:?}", e); }) } @@ -296,18 +286,17 @@ struct BridgePoolProofResponse { /// Construct a merkle proof of a batch of transfers in /// the bridge pool and return it to the user (as opposed /// to relaying it to ethereum). -pub async fn construct_proof( - client: &C, +pub async fn construct_proof<'a>( + client: &(impl Client + Sync), + io: &impl Io, args: args::BridgePoolProof, -) -> Halt<()> -where - C: Client + Sync, -{ +) -> Halt<()> { let GenBridgePoolProofRsp { abi_encoded_args, appendices, - } = construct_bridge_pool_proof::<_, IO>( + } = construct_bridge_pool_proof( client, + io, GenBridgePoolProofReq { transfers: args.transfers.as_slice().into(), relayer: Cow::Borrowed(&args.relayer), @@ -336,26 +325,27 @@ where .unwrap_or_default(), abi_encoded_args, }; - display_line!(IO, "{}", serde_json::to_string(&resp).unwrap()); + display_line!(io, "{}", serde_json::to_string(&resp).unwrap()); control_flow::proceed(()) } /// Relay a validator set update, signed off for a given epoch. -pub async fn relay_bridge_pool_proof( +pub async fn relay_bridge_pool_proof<'a, E>( eth_client: Arc, - nam_client: &C, + client: &(impl Client + Sync), + io: &impl Io, args: args::RelayBridgePoolProof, ) -> Halt<()> where - C: Client + Sync, E: Middleware, E::Error: std::fmt::Debug + std::fmt::Display, { let _signal_receiver = args.safe_mode.then(install_shutdown_signal); if args.sync { - block_on_eth_sync::<_, IO>( + block_on_eth_sync( &*eth_client, + io, BlockOnEthSync { deadline: Instant::now() + Duration::from_secs(60), delta_sleep: Duration::from_secs(1), @@ -363,13 +353,14 @@ where ) .await?; } else { - eth_sync_or_exit::<_, IO>(&*eth_client).await?; + eth_sync_or_exit(&*eth_client, io).await?; } let GenBridgePoolProofRsp { abi_encoded_args, .. - } = construct_bridge_pool_proof::<_, IO>( - nam_client, + } = construct_bridge_pool_proof( + client, + io, GenBridgePoolProofReq { transfers: Cow::Owned(args.transfers), relayer: Cow::Owned(args.relayer), @@ -377,32 +368,28 @@ where }, ) .await?; - let bridge = match RPC - .shell() - .eth_bridge() - .read_bridge_contract(nam_client) - .await - { - Ok(address) => Bridge::new(address.address, eth_client), - Err(err_msg) => { - let error = "Error".on_red(); - let error = error.bold(); - let error = error.blink(); - display_line!( - IO, - "{error}: Failed to retrieve the Ethereum Bridge smart \ - contract address from storage with \ - reason:\n{err_msg}\n\nPerhaps the Ethereum bridge is not \ - active.", - ); - return control_flow::halt(); - } - }; + let bridge = + match RPC.shell().eth_bridge().read_bridge_contract(client).await { + Ok(address) => Bridge::new(address.address, eth_client), + Err(err_msg) => { + let error = "Error".on_red(); + let error = error.bold(); + let error = error.blink(); + display_line!( + io, + "{error}: Failed to retrieve the Ethereum Bridge smart \ + contract address from storage with \ + reason:\n{err_msg}\n\nPerhaps the Ethereum bridge is not \ + active.", + ); + return control_flow::halt(); + } + }; let (validator_set, signatures, bp_proof): TransferToErcArgs = AbiDecode::decode(&abi_encoded_args).try_halt(|error| { display_line!( - IO, + io, "Unable to decode the generated proof: {:?}", error ); @@ -419,7 +406,7 @@ where let error = error.bold(); let error = error.blink(); display_line!( - IO, + io, "{error}: The Bridge pool nonce in the smart contract is \ {contract_nonce}, while the nonce in Namada is still {}. A \ relay of the former one has already happened, but a proof \ @@ -433,7 +420,7 @@ where let error = error.bold(); let error = error.blink(); display_line!( - IO, + io, "{error}: The Bridge pool nonce in the smart contract is \ {contract_nonce}, while the nonce in Namada is still {}. \ Somehow, Namada's nonce is ahead of the contract's nonce!", @@ -461,7 +448,7 @@ where .await .unwrap(); - display_line!(IO, "{transf_result:?}"); + display_line!(io, "{transf_result:?}"); control_flow::proceed(()) } @@ -560,19 +547,16 @@ mod recommendations { /// Recommend the most economical batch of transfers to relay based /// on a conversion rate estimates from NAM to ETH and gas usage /// heuristics. - pub async fn recommend_batch( - client: &C, + pub async fn recommend_batch<'a>( + context: &impl Namada<'a>, args: args::RecommendBatch, - ) -> Halt<()> - where - C: Client + Sync, - { + ) -> Halt<()> { // get transfers that can already been relayed but are awaiting a quorum // of backing votes. let in_progress = RPC .shell() .eth_bridge() - .transfer_to_ethereum_progress(client) + .transfer_to_ethereum_progress(context.client()) .await .unwrap() .into_keys() @@ -585,7 +569,7 @@ mod recommendations { <(BridgePoolRootProof, BlockHeight)>::try_from_slice( &RPC.shell() .storage_value( - client, + context.client(), None, None, false, @@ -594,36 +578,48 @@ mod recommendations { .await .try_halt(|err| { edisplay_line!( - IO, + context.io(), "Failed to query Bridge pool proof: {err}" ); })? .data, ) .try_halt(|err| { - edisplay_line!(IO, "Failed to decode Bridge pool proof: {err}"); + edisplay_line!( + context.io(), + "Failed to decode Bridge pool proof: {err}" + ); })?; // get the latest bridge pool nonce let latest_bp_nonce = EthUint::try_from_slice( &RPC.shell() - .storage_value(client, None, None, false, &get_nonce_key()) + .storage_value( + context.client(), + None, + None, + false, + &get_nonce_key(), + ) .await .try_halt(|err| { edisplay_line!( - IO, + context.io(), "Failed to query Bridge pool nonce: {err}" ); })? .data, ) .try_halt(|err| { - edisplay_line!(IO, "Failed to decode Bridge pool nonce: {err}"); + edisplay_line!( + context.io(), + "Failed to decode Bridge pool nonce: {err}" + ); })?; if latest_bp_nonce != bp_root.data.1 { edisplay_line!( - IO, + context.io(), "The signed Bridge pool nonce is not up to date, repeat this \ query at a later time" ); @@ -635,7 +631,7 @@ mod recommendations { let voting_powers = RPC .shell() .eth_bridge() - .voting_powers_at_height(client, &height) + .voting_powers_at_height(context.client(), &height) .await .unwrap(); let valset_size = Uint::from_u64(voting_powers.len() as u64); @@ -647,17 +643,19 @@ mod recommendations { + valset_fee() * valset_size; // we don't recommend transfers that have already been relayed - let eligible = generate_eligible::( + let eligible = generate_eligible( + context.io(), &args.conversion_table, &in_progress, - query_signed_bridge_pool::<_, IO>(client).await?, + query_signed_bridge_pool(context.client(), context.io()).await?, )?; let max_gas = args.max_gas.map(Uint::from_u64).unwrap_or(uint::MAX_VALUE); let max_cost = args.gas.map(I256::from).unwrap_or_default(); - generate_recommendations::( + generate_recommendations( + context.io(), eligible, &args.conversion_table, validator_gas, @@ -671,22 +669,28 @@ mod recommendations { net_profit, bridge_pool_gas_fees, }| { - display_line!(IO, "Recommended batch: {transfer_hashes:#?}"); display_line!( - IO, + context.io(), + "Recommended batch: {transfer_hashes:#?}" + ); + display_line!( + context.io(), "Estimated Ethereum transaction gas (in gwei): \ {ethereum_gas_fees}", ); display_line!( - IO, + context.io(), "Estimated net profit (in gwei): {net_profit}" ); - display_line!(IO, "Total fees: {bridge_pool_gas_fees:#?}"); + display_line!( + context.io(), + "Total fees: {bridge_pool_gas_fees:#?}" + ); }, ) .unwrap_or_else(|| { display_line!( - IO, + context.io(), "Unable to find a recommendation satisfying the input \ parameters." ); @@ -731,6 +735,7 @@ mod recommendations { /// Generate eligible recommendations. fn generate_eligible( + io: &IO, conversion_table: &HashMap, in_progress: &BTreeSet, signed_pool: HashMap, @@ -747,7 +752,7 @@ mod recommendations { .and_then(|entry| match entry.conversion_rate { r if r == 0.0f64 => { edisplay_line!( - IO, + io, "{}: Ignoring null conversion rate", pending.gas_fee.token, ); @@ -755,7 +760,7 @@ mod recommendations { } r if r < 0.0f64 => { edisplay_line!( - IO, + io, "{}: Ignoring negative conversion rate: {r:.1}", pending.gas_fee.token, ); @@ -763,7 +768,7 @@ mod recommendations { } r if r > 1e9 => { edisplay_line!( - IO, + io, "{}: Ignoring high conversion rate: {r:.1} > \ 10^9", pending.gas_fee.token, @@ -814,6 +819,7 @@ mod recommendations { /// Generates the actual recommendation from restrictions given by the /// input parameters. fn generate_recommendations( + io: &IO, contents: Vec, conversion_table: &HashMap, validator_gas: Uint, @@ -882,7 +888,7 @@ mod recommendations { }) } else { display_line!( - IO, + io, "Unable to find a recommendation satisfying the input \ parameters." ); @@ -1021,12 +1027,9 @@ mod recommendations { signed_pool: &mut signed_pool, expected_eligible: &mut expected, }); - let eligible = generate_eligible::( - &table, - &in_progress, - signed_pool, - ) - .proceed(); + let eligible = + generate_eligible(&StdIo, &table, &in_progress, signed_pool) + .proceed(); assert_eq!(eligible, expected); eligible } @@ -1116,7 +1119,8 @@ mod recommendations { let profitable = vec![transfer(100_000); 17]; let hash = profitable[0].keccak256().to_string(); let expected = vec![hash; 17]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(profitable), &Default::default(), Uint::from_u64(800_000), @@ -1135,7 +1139,8 @@ mod recommendations { let hash = transfers[0].keccak256().to_string(); transfers.push(transfer(0)); let expected: Vec<_> = vec![hash; 17]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(transfers), &Default::default(), Uint::from_u64(800_000), @@ -1153,7 +1158,8 @@ mod recommendations { let transfers = vec![transfer(75_000); 4]; let hash = transfers[0].keccak256().to_string(); let expected = vec![hash; 2]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(transfers), &Default::default(), Uint::from_u64(50_000), @@ -1175,7 +1181,8 @@ mod recommendations { .map(|t| t.keccak256().to_string()) .take(5) .collect(); - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(transfers), &Default::default(), Uint::from_u64(150_000), @@ -1194,7 +1201,8 @@ mod recommendations { let hash = transfers[0].keccak256().to_string(); let expected = vec![hash; 4]; transfers.extend([transfer(17_500), transfer(17_500)]); - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(transfers), &Default::default(), Uint::from_u64(150_000), @@ -1210,7 +1218,8 @@ mod recommendations { #[test] fn test_wholly_infeasible() { let transfers = vec![transfer(75_000); 4]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(transfers), &Default::default(), Uint::from_u64(300_000), @@ -1291,7 +1300,8 @@ mod recommendations { const VALIDATOR_GAS_FEE: Uint = Uint::from_u64(100_000); - let recommended_batch = generate_recommendations::( + let recommended_batch = generate_recommendations( + &StdIo, eligible, &conversion_table, // gas spent by validator signature checks diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index be99e130f8..90f043fe18 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -26,7 +26,7 @@ use crate::types::control_flow::{ self, install_shutdown_signal, Halt, TryHalt, }; use crate::types::ethereum_events::EthAddress; -use crate::types::io::{Io, StdIo}; +use crate::types::io::Io; use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; use crate::{display_line, edisplay_line}; @@ -268,12 +268,11 @@ impl From> for RelayResult { /// Query an ABI encoding of the validator set to be installed /// at the given epoch, and its associated proof. -pub async fn query_validator_set_update_proof( - client: &C, +pub async fn query_validator_set_update_proof<'a>( + client: &(impl Client + Sync), + io: &impl Io, args: args::ValidatorSetProof, -) where - C: Client + Sync, -{ +) { let epoch = if let Some(epoch) = args.epoch { epoch } else { @@ -287,17 +286,15 @@ pub async fn query_validator_set_update_proof( .await .unwrap(); - display_line!(IO, "0x{}", HEXLOWER.encode(encoded_proof.as_ref())); + display_line!(io, "0x{}", HEXLOWER.encode(encoded_proof.as_ref())); } /// Query an ABI encoding of the Bridge validator set at a given epoch. -pub async fn query_bridge_validator_set( - client: &C, +pub async fn query_bridge_validator_set<'a>( + client: &(impl Client + Sync), + io: &impl Io, args: args::BridgeValidatorSet, -) -> Halt<()> -where - C: Client + Sync, -{ +) -> Halt<()> { let epoch = if let Some(epoch) = args.epoch { epoch } else { @@ -313,18 +310,16 @@ where tracing::error!(%err, "Failed to fetch Bridge validator set"); })?; - display_validator_set::(args); + display_validator_set(io, args); control_flow::proceed(()) } /// Query an ABI encoding of the Governance validator set at a given epoch. -pub async fn query_governnace_validator_set( - client: &C, +pub async fn query_governnace_validator_set<'a>( + client: &(impl Client + Sync), + io: &impl Io, args: args::GovernanceValidatorSet, -) -> Halt<()> -where - C: Client + Sync, -{ +) -> Halt<()> { let epoch = if let Some(epoch) = args.epoch { epoch } else { @@ -340,12 +335,12 @@ where tracing::error!(%err, "Failed to fetch Governance validator set"); })?; - display_validator_set::(args); + display_validator_set(io, args); control_flow::proceed(()) } /// Display the given [`ValidatorSetArgs`]. -fn display_validator_set(args: ValidatorSetArgs) { +fn display_validator_set(io: &IO, args: ValidatorSetArgs) { use serde::Serialize; #[derive(Serialize)] @@ -373,28 +368,29 @@ fn display_validator_set(args: ValidatorSetArgs) { }; display_line!( - IO, + io, "{}", serde_json::to_string_pretty(&validator_set).unwrap() ); } /// Relay a validator set update, signed off for a given epoch. -pub async fn relay_validator_set_update( +pub async fn relay_validator_set_update<'a, E>( eth_client: Arc, - nam_client: &C, + client: &(impl Client + Sync), + io: &impl Io, args: args::ValidatorSetUpdateRelay, ) -> Halt<()> where - C: Client + Sync, E: Middleware, E::Error: std::fmt::Debug + std::fmt::Display, { let mut signal_receiver = args.safe_mode.then(install_shutdown_signal); if args.sync { - block_on_eth_sync::<_, IO>( + block_on_eth_sync( &*eth_client, + io, BlockOnEthSync { deadline: Instant::now() + Duration::from_secs(60), delta_sleep: Duration::from_secs(1), @@ -402,14 +398,15 @@ where ) .await?; } else { - eth_sync_or_exit::<_, IO>(&*eth_client).await?; + eth_sync_or_exit(&*eth_client, io).await?; } if args.daemon { relay_validator_set_update_daemon( args, eth_client, - nam_client, + client, + io, &mut signal_receiver, ) .await @@ -417,11 +414,11 @@ where relay_validator_set_update_once::( &args, eth_client, - nam_client, + client, |relay_result| match relay_result { RelayResult::BridgeCallError(reason) => { edisplay_line!( - IO, + io, "Calling Bridge failed due to: {reason}" ); } @@ -432,27 +429,27 @@ where Ordering::Greater => "too far ahead of", }; edisplay_line!( - IO, + io, "Argument nonce <{argument}> is {whence} contract \ nonce <{contract}>" ); } RelayResult::NoReceipt => { edisplay_line!( - IO, + io, "No transfer receipt received from the Ethereum node" ); } RelayResult::Receipt { receipt } => { if receipt.is_successful() { display_line!( - IO, + io, "Ethereum transfer succeeded: {:?}", receipt ); } else { display_line!( - IO, + io, "Ethereum transfer failed: {:?}", receipt ); @@ -465,14 +462,14 @@ where } } -async fn relay_validator_set_update_daemon( +async fn relay_validator_set_update_daemon<'a, E, F>( mut args: args::ValidatorSetUpdateRelay, eth_client: Arc, - nam_client: &C, + client: &(impl Client + Sync), + io: &impl Io, shutdown_receiver: &mut Option, ) -> Halt<()> where - C: Client + Sync, E: Middleware, E::Error: std::fmt::Debug + std::fmt::Display, F: Future + Unpin, @@ -513,9 +510,7 @@ where time::sleep(sleep_for).await; let is_synchronizing = - eth_sync_or::<_, _, _, StdIo>(&*eth_client, || ()) - .await - .is_break(); + eth_sync_or(&*eth_client, io, || ()).await.is_break(); if is_synchronizing { tracing::debug!("The Ethereum node is synchronizing"); last_call_succeeded = false; @@ -525,7 +520,7 @@ where // we could be racing against governance updates, // so it is best to always fetch the latest Bridge // contract address - let bridge = get_bridge_contract(nam_client, Arc::clone(ð_client)) + let bridge = get_bridge_contract(client, Arc::clone(ð_client)) .await .try_halt(|err| { // only care about displaying errors, @@ -544,7 +539,7 @@ where }); let shell = RPC.shell(); - let nam_current_epoch_fut = shell.epoch(nam_client).map(|result| { + let nam_current_epoch_fut = shell.epoch(client).map(|result| { result .map_err(|err| { tracing::error!( @@ -596,7 +591,7 @@ where let result = relay_validator_set_update_once::( &args, Arc::clone(ð_client), - nam_client, + client, |transf_result| { let Some(receipt) = transf_result else { tracing::warn!("No transfer receipt received from the Ethereum node"); diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index a8159834a5..5536f46b63 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -16,40 +16,39 @@ pub mod vp_host_fns; use std::path::PathBuf; use std::str::FromStr; -use std::sync::Arc; pub use namada_core::ledger::{ gas, parameters, replay_protection, storage_api, tx_env, vp_env, }; +use namada_core::types::dec::Dec; +use namada_core::types::ethereum_events::EthAddress; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use crate::sdk::wallet::{Wallet, WalletIo, WalletStorage}; +use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; +use crate::proto::Tx; +use crate::sdk::args::{self, InputAmount, SdkTypes}; use crate::sdk::masp::{ShieldedContext, ShieldedUtils}; -use crate::types::masp::{TransferSource, TransferTarget}; -use crate::types::address::Address; -use crate::sdk::args::{self, InputAmount}; -use crate::sdk::args::SdkTypes; +use crate::sdk::signing::{self, SigningTxData}; use crate::sdk::tx::{ - TX_TRANSFER_WASM, TX_REVEAL_PK, TX_BOND_WASM, TX_UNBOND_WASM, TX_IBC_WASM, - TX_INIT_PROPOSAL, TX_UPDATE_ACCOUNT_WASM, TX_VOTE_PROPOSAL, VP_USER_WASM, - TX_CHANGE_COMMISSION_WASM, TX_INIT_VALIDATOR_WASM, TX_UNJAIL_VALIDATOR_WASM, - TX_WITHDRAW_WASM, TX_BRIDGE_POOL_WASM, TX_RESIGN_STEWARD, - TX_UPDATE_STEWARD_COMMISSION, self, + self, ProcessTxResponse, TX_BOND_WASM, TX_BRIDGE_POOL_WASM, + TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, TX_INIT_PROPOSAL, + TX_INIT_VALIDATOR_WASM, TX_RESIGN_STEWARD, TX_REVEAL_PK, TX_TRANSFER_WASM, + TX_UNBOND_WASM, TX_UNJAIL_VALIDATOR_WASM, TX_UPDATE_ACCOUNT_WASM, + TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, + VP_USER_WASM, }; -use crate::types::transaction::GasLimit; -use crate::sdk::signing::{SigningTxData, self}; -use crate::proto::Tx; +use crate::sdk::wallet::{Wallet, WalletIo, WalletStorage}; +use crate::types::address::Address; +use crate::types::io::Io; use crate::types::key::*; +use crate::types::masp::{TransferSource, TransferTarget}; use crate::types::token; -use crate::sdk::tx::ProcessTxResponse; -use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use crate::types::token::NATIVE_MAX_DECIMAL_PLACES; -use namada_core::types::dec::Dec; -use namada_core::types::ethereum_events::EthAddress; -use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use crate::types::transaction::GasLimit; #[async_trait::async_trait(?Send)] /// An interface for high-level interaction with the Namada SDK -pub trait Namada<'a> { +pub trait Namada<'a>: Sized { /// A client with async request dispatcher method type Client: 'a + crate::ledger::queries::Client + Sync; /// Captures the interactive parts of the wallet's functioning @@ -57,38 +56,37 @@ pub trait Namada<'a> { /// Abstracts platform specific details away from the logic of shielded pool /// operations. type ShieldedUtils: 'a + ShieldedUtils; + /// Captures the input/output streams used by this object + type Io: 'a + Io; /// Obtain the client for communicating with the ledger fn client(&self) -> &'a Self::Client; - /// Obtain read lock on the wallet + /// Obtain the input/output handle for this context + fn io(&self) -> &'a Self::Io; + + /// Obtain read guard on the wallet async fn wallet( &self, ) -> RwLockReadGuard<&'a mut Wallet>; - /// Obtain write lock on the wallet + /// Obtain write guard on the wallet async fn wallet_mut( &self, ) -> RwLockWriteGuard<&'a mut Wallet>; - /// Obtain read lock on the shielded context + /// Obtain read guard on the shielded context async fn shielded( &self, ) -> RwLockReadGuard<&'a mut ShieldedContext>; - /// Obtain write lock on the shielded context + /// Obtain write guard on the shielded context async fn shielded_mut( &self, ) -> RwLockWriteGuard<&'a mut ShieldedContext>; /// Return the native token - async fn native_token(&self) -> Address { - self.wallet() - .await - .find_address(args::NAM) - .expect("NAM not in wallet") - .clone() - } + async fn native_token(&self) -> Address; /// Make a tx builder using no arguments async fn tx_builder(&self) -> args::Tx { @@ -387,47 +385,52 @@ pub trait Namada<'a> { tx: Tx, args: &args::Tx, ) -> crate::sdk::error::Result { - tx::process_tx(self.client(), *self.wallet_mut().await, args, tx).await + tx::process_tx(self, args, tx).await } } /// Provides convenience methods for common Namada interactions -pub struct NamadaImpl<'a, C, U, V> +pub struct NamadaImpl<'a, C, U, V, I> where C: crate::ledger::queries::Client + Sync, U: WalletIo, V: ShieldedUtils, + I: Io, { /// Used to send and receive messages from the ledger pub client: &'a C, /// Stores the addresses and keys required for ledger interactions - pub wallet: Arc>>, + pub wallet: RwLock<&'a mut Wallet>, /// Stores the current state of the shielded pool - pub shielded: Arc>>, + pub shielded: RwLock<&'a mut ShieldedContext>, + /// Captures the input/output streams used by this object + pub io: &'a I, /// The default builder for a Tx prototype: args::Tx, } -impl<'a, C, U, V> NamadaImpl<'a, C, U, V> +/// The Namada token +pub const NAM: &str = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5"; + +impl<'a, C, U, V, I> NamadaImpl<'a, C, U, V, I> where C: crate::ledger::queries::Client + Sync, U: WalletIo, V: ShieldedUtils, + I: Io, { /// Construct a new Namada context pub fn new( client: &'a C, wallet: &'a mut Wallet, shielded: &'a mut ShieldedContext, + io: &'a I, ) -> Self { - let fee_token = wallet - .find_address(args::NAM) - .expect("NAM not in wallet") - .clone(); Self { client, - wallet: Arc::new(RwLock::new(wallet)), - shielded: Arc::new(RwLock::new(shielded)), + wallet: RwLock::new(wallet), + shielded: RwLock::new(shielded), + io, prototype: args::Tx { dry_run: false, dry_run_wrapper: false, @@ -440,7 +443,7 @@ where wallet_alias_force: false, fee_amount: None, wrapper_fee_payer: None, - fee_token, + fee_token: Address::from_str(NAM).unwrap(), fee_unshield: None, gas_limit: GasLimit::from(20_000), expiration: None, @@ -457,13 +460,15 @@ where } #[async_trait::async_trait(?Send)] -impl<'a, C, U, V> Namada<'a> for NamadaImpl<'a, C, U, V> +impl<'a, C, U, V, I> Namada<'a> for NamadaImpl<'a, C, U, V, I> where C: crate::ledger::queries::Client + Sync, U: WalletIo + WalletStorage, V: ShieldedUtils, + I: Io, { type Client = C; + type Io = I; type ShieldedUtils = V; type WalletUtils = U; @@ -472,6 +477,14 @@ where self.prototype.clone() } + async fn native_token(&self) -> Address { + Address::from_str(NAM).unwrap() + } + + fn io(&self) -> &'a Self::Io { + self.io + } + fn client(&self) -> &'a Self::Client { self.client } @@ -502,11 +515,12 @@ where } /// Allow the prototypical Tx builder to be modified -impl<'a, C, U, V> args::TxBuilder for NamadaImpl<'a, C, U, V> +impl<'a, C, U, V, I> args::TxBuilder for NamadaImpl<'a, C, U, V, I> where C: crate::ledger::queries::Client + Sync, U: WalletIo, V: ShieldedUtils, + I: Io, { fn tx(self, func: F) -> Self where diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs index 91e00dee68..0853dc251b 100644 --- a/shared/src/sdk/args.rs +++ b/shared/src/sdk/args.rs @@ -17,6 +17,8 @@ use zeroize::Zeroizing; use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use crate::ledger::eth_bridge::bridge_pool; use crate::ledger::Namada; +use crate::sdk::signing::SigningTxData; +use crate::sdk::{rpc, tx}; use crate::types::address::Address; use crate::types::keccak::KeccakHash; use crate::types::key::{common, SchemeType}; @@ -24,11 +26,6 @@ use crate::types::masp::MaspValue; use crate::types::storage::Epoch; use crate::types::transaction::GasLimit; use crate::types::{storage, token}; -use crate::sdk::signing::SigningTxData; -use crate::sdk::{tx, rpc}; - -/// The Namada token -pub const NAM: &str = "NAM"; /// [`Duration`](StdDuration) wrapper that provides a /// method to parse a value from a string. @@ -510,30 +507,30 @@ impl InitProposal { rpc::query_governance_parameters(context.client()).await; if self.is_pgf_funding { - let proposal = - PgfFundingProposal::try_from(self.proposal_data.as_ref()) - .map_err(|e| { - crate::sdk::error::TxError::FailedGovernaneProposalDeserialize( - e.to_string(), - ) - })? - .validate(&governance_parameters, current_epoch, self.tx.force) - .map_err(|e| crate::sdk::error::TxError::InvalidProposal(e.to_string()))?; + let proposal = PgfFundingProposal::try_from( + self.proposal_data.as_ref(), + ) + .map_err(|e| { + crate::sdk::error::TxError::FailedGovernaneProposalDeserialize( + e.to_string(), + ) + })? + .validate(&governance_parameters, current_epoch, self.tx.force) + .map_err(|e| { + crate::sdk::error::TxError::InvalidProposal(e.to_string()) + })?; tx::build_pgf_funding_proposal(context, self, proposal).await } else if self.is_pgf_stewards { let proposal = PgfStewardProposal::try_from( self.proposal_data.as_ref(), ) - .map_err(|e| { - crate::sdk::error::TxError::FailedGovernaneProposalDeserialize(e.to_string()) - })?; - let nam_address = context - .wallet() - .await - .find_address(NAM) - .expect("NAM not in wallet") - .clone(); + .map_err(|e| { + crate::sdk::error::TxError::FailedGovernaneProposalDeserialize( + e.to_string(), + ) + })?; + let nam_address = context.native_token().await; let author_balance = rpc::get_token_balance( context.client(), &nam_address, @@ -553,16 +550,15 @@ impl InitProposal { tx::build_pgf_stewards_proposal(context, self, proposal).await } else { - let proposal = DefaultProposal::try_from(self.proposal_data.as_ref()) - .map_err(|e| { - crate::sdk::error::TxError::FailedGovernaneProposalDeserialize(e.to_string()) - })?; - let nam_address = context - .wallet() - .await - .find_address(NAM) - .expect("NAM not in wallet") - .clone(); + let proposal = DefaultProposal::try_from( + self.proposal_data.as_ref(), + ) + .map_err(|e| { + crate::sdk::error::TxError::FailedGovernaneProposalDeserialize( + e.to_string(), + ) + })?; + let nam_address = context.native_token().await; let author_balance = rpc::get_token_balance( context.client(), &nam_address, @@ -1358,7 +1354,7 @@ impl TxUnjailValidator { } /// Path to the TX WASM code file - pub fn tc_code_path(self, tx_code_path: PathBuf) -> Self { + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { Self { tx_code_path, ..self diff --git a/shared/src/sdk/masp.rs b/shared/src/sdk/masp.rs index 35c4f59956..96ab27fbd5 100644 --- a/shared/src/sdk/masp.rs +++ b/shared/src/sdk/masp.rs @@ -58,18 +58,18 @@ use ripemd::Digest as RipemdDigest; use sha2::Digest; use thiserror::Error; -use crate::sdk::args::InputAmount; use crate::ledger::queries::Client; -use crate::sdk::rpc::{query_conversion, query_storage_value}; -use crate::sdk::tx::decode_component; use crate::ledger::Namada; use crate::proto::Tx; +use crate::sdk::args::InputAmount; use crate::sdk::error::{EncodingError, Error, PinnedBalanceError, QueryError}; +use crate::sdk::rpc::{query_conversion, query_storage_value}; +use crate::sdk::tx::decode_component; use crate::sdk::{args, rpc}; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; use crate::types::address::{masp, Address}; -use crate::types::io::{Io, StdIo}; +use crate::types::io::Io; use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use crate::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; use crate::types::token; @@ -1030,18 +1030,19 @@ impl ShieldedContext { /// context and express that value in terms of the currently timestamped /// asset types. If the key is not in the context, then we do not know the /// balance and hence we return None. - pub async fn compute_exchanged_balance( + pub async fn compute_exchanged_balance<'a>( &mut self, - client: &C, + context: &impl Namada<'a>, vk: &ViewingKey, target_epoch: Epoch, ) -> Result, Error> { // First get the unexchanged balance - if let Some(balance) = self.compute_shielded_balance(client, vk).await? + if let Some(balance) = + self.compute_shielded_balance(context.client(), vk).await? { let exchanged_amount = self .compute_exchanged_amount( - client, + context, balance, target_epoch, BTreeMap::new(), @@ -1050,7 +1051,8 @@ impl ShieldedContext { .0; // And then exchange balance into current asset types Ok(Some( - self.decode_all_amounts(client, exchanged_amount).await, + self.decode_all_amounts(context.client(), exchanged_amount) + .await, )) } else { Ok(None) @@ -1063,9 +1065,9 @@ impl ShieldedContext { /// the trace amount that could not be converted is moved from input to /// output. #[allow(clippy::too_many_arguments)] - async fn apply_conversion( + async fn apply_conversion<'a>( &mut self, - client: &C, + context: &impl Namada<'a>, conv: AllowedConversion, asset_type: (Epoch, Address, MaspDenom), value: i128, @@ -1085,7 +1087,7 @@ impl ShieldedContext { let threshold = -conv[&masp_asset]; if threshold == 0 { edisplay_line!( - StdIo, + context.io(), "Asset threshold of selected conversion for asset type {} is \ 0, this is a bug, please report it.", masp_asset @@ -1104,7 +1106,7 @@ impl ShieldedContext { *usage += required; // Apply the conversions to input and move the trace amount to output *input += self - .decode_all_amounts(client, conv.clone() * required) + .decode_all_amounts(context.client(), conv.clone() * required) .await - trace.clone(); *output += trace; @@ -1115,9 +1117,9 @@ impl ShieldedContext { /// note of the conversions that were used. Note that this function does /// not assume that allowed conversions from the ledger are expressed in /// terms of the latest asset types. - pub async fn compute_exchanged_amount( + pub async fn compute_exchanged_amount<'a>( &mut self, - client: &C, + context: &impl Namada<'a>, mut input: MaspAmount, target_epoch: Epoch, mut conversions: Conversions, @@ -1139,13 +1141,13 @@ impl ShieldedContext { let denom_value = denom.denominate_i128(&value); self.query_allowed_conversion( - client, + context.client(), target_asset_type, &mut conversions, ) .await; self.query_allowed_conversion( - client, + context.client(), asset_type, &mut conversions, ) @@ -1154,7 +1156,7 @@ impl ShieldedContext { (conversions.get_mut(&asset_type), at_target_asset_type) { display_line!( - StdIo, + context.io(), "converting current asset type to latest asset type..." ); // Not at the target asset type, not at the latest asset @@ -1162,7 +1164,7 @@ impl ShieldedContext { // current asset type to the latest // asset type. self.apply_conversion( - client, + context, conv.clone(), (asset_epoch, token_addr.clone(), denom), denom_value, @@ -1176,7 +1178,7 @@ impl ShieldedContext { at_target_asset_type, ) { display_line!( - StdIo, + context.io(), "converting latest asset type to target asset type..." ); // Not at the target asset type, yet at the latest asset @@ -1184,7 +1186,7 @@ impl ShieldedContext { // from latest asset type to the target // asset type. self.apply_conversion( - client, + context, conv.clone(), (asset_epoch, token_addr.clone(), denom), denom_value, @@ -1218,9 +1220,9 @@ impl ShieldedContext { /// of the specified asset type. Return the total value accumulated plus /// notes and the corresponding diversifiers/merkle paths that were used to /// achieve the total value. - pub async fn collect_unspent_notes( + pub async fn collect_unspent_notes<'a>( &mut self, - client: &C, + context: &impl Namada<'a>, vk: &ViewingKey, target: I128Sum, target_epoch: Epoch, @@ -1262,10 +1264,11 @@ impl ShieldedContext { .to_string(), ) })?; - let input = self.decode_all_amounts(client, pre_contr).await; + let input = + self.decode_all_amounts(context.client(), pre_contr).await; let (contr, proposed_convs) = self .compute_exchanged_amount( - client, + context, input, target_epoch, conversions.clone(), @@ -1403,31 +1406,31 @@ impl ShieldedContext { /// the epoch of the transaction or even before, so exchange all these /// amounts to the epoch of the transaction in order to get the value that /// would have been displayed in the epoch of the transaction. - pub async fn compute_exchanged_pinned_balance( + pub async fn compute_exchanged_pinned_balance<'a>( &mut self, - client: &C, + context: &impl Namada<'a>, owner: PaymentAddress, viewing_key: &ViewingKey, ) -> Result<(MaspAmount, Epoch), Error> { // Obtain the balance that will be exchanged let (amt, ep) = - Self::compute_pinned_balance(client, owner, viewing_key).await?; - display_line!(IO, "Pinned balance: {:?}", amt); + Self::compute_pinned_balance(context.client(), owner, viewing_key) + .await?; + display_line!(context.io(), "Pinned balance: {:?}", amt); // Establish connection with which to do exchange rate queries - let amount = self.decode_all_amounts(client, amt).await; - display_line!(IO, "Decoded pinned balance: {:?}", amount); + let amount = self.decode_all_amounts(context.client(), amt).await; + display_line!(context.io(), "Decoded pinned balance: {:?}", amount); // Finally, exchange the balance to the transaction's epoch let computed_amount = self - .compute_exchanged_amount( - client, - amount, - ep, - BTreeMap::new(), - ) + .compute_exchanged_amount(context, amount, ep, BTreeMap::new()) .await? .0; - display_line!(IO, "Exchanged amount: {:?}", computed_amount); - Ok((self.decode_all_amounts(client, computed_amount).await, ep)) + display_line!(context.io(), "Exchanged amount: {:?}", computed_amount); + Ok(( + self.decode_all_amounts(context.client(), computed_amount) + .await, + ep, + )) } /// Convert an amount whose units are AssetTypes to one whose units are @@ -1567,7 +1570,7 @@ impl ShieldedContext { .shielded_mut() .await .collect_unspent_notes( - context.client(), + context, &to_viewing_key(&sk).vk, I128Sum::from_sum(amount), epoch, @@ -2131,6 +2134,7 @@ mod tests { pub mod fs { use std::fs::{File, OpenOptions}; use std::io::{Read, Write}; + use async_trait::async_trait; use super::*; diff --git a/shared/src/sdk/rpc.rs b/shared/src/sdk/rpc.rs index 8a40d8a8dd..eb8ebd8a11 100644 --- a/shared/src/sdk/rpc.rs +++ b/shared/src/sdk/rpc.rs @@ -27,10 +27,12 @@ use serde::Serialize; use crate::ledger::events::Event; use crate::ledger::queries::vp::pos::EnrichedBondsAndUnbondsDetails; use crate::ledger::queries::RPC; +use crate::ledger::Namada; use crate::proto::Tx; use crate::sdk::args::InputAmount; use crate::sdk::error; use crate::sdk::error::{EncodingError, Error, QueryError}; +use crate::sdk::queries::Client; use crate::tendermint::block::Height; use crate::tendermint::merkle::proof::Proof; use crate::tendermint_rpc::error::Error as TError; @@ -38,7 +40,7 @@ use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; use crate::types::control_flow::{time, Halt, TryHalt}; use crate::types::hash::Hash; -use crate::types::io::{Io, StdIo}; +use crate::types::io::Io; use crate::types::key::common; use crate::types::storage::{BlockHeight, BlockResults, Epoch, PrefixValue}; use crate::types::{storage, token}; @@ -48,14 +50,11 @@ use crate::{display_line, edisplay_line}; /// /// If a response is not delivered until `deadline`, we exit the cli with an /// error. -pub async fn query_tx_status( - client: &C, +pub async fn query_tx_status<'a>( + context: &impl Namada<'a>, status: TxEventQuery<'_>, deadline: time::Instant, -) -> Halt -where - C: crate::ledger::queries::Client + Sync, -{ +) -> Halt { time::Sleep { strategy: time::LinearBackoff { delta: time::Duration::from_secs(1), @@ -63,7 +62,8 @@ where } .timeout(deadline, || async { tracing::debug!(query = ?status, "Querying tx status"); - let maybe_event = match query_tx_events(client, status).await { + let maybe_event = match query_tx_events(context.client(), status).await + { Ok(response) => response, Err(err) => { tracing::debug!( @@ -90,7 +90,7 @@ where .await .try_halt(|_| { edisplay_line!( - IO, + context.io(), "Transaction status query deadline of {deadline:?} exceeded" ); }) @@ -237,21 +237,19 @@ pub async fn query_conversion( } /// Query a wasm code hash -pub async fn query_wasm_code_hash< - C: crate::ledger::queries::Client + Sync, ->( - client: &C, +pub async fn query_wasm_code_hash<'a>( + context: &impl Namada<'a>, code_path: impl AsRef, ) -> Result { let hash_key = Key::wasm_hash(code_path.as_ref()); - match query_storage_value_bytes(client, &hash_key, None, false) + match query_storage_value_bytes(context.client(), &hash_key, None, false) .await? .0 { Some(hash) => Ok(Hash::try_from(&hash[..]).expect("Invalid code hash")), None => { edisplay_line!( - StdIo, + context.io(), "The corresponding wasm code of the code path {} doesn't \ exist on chain.", code_path.as_ref(), @@ -325,20 +323,16 @@ pub async fn query_storage_value_bytes< /// Query a range of storage values with a matching prefix and decode them with /// [`BorshDeserialize`]. Returns an iterator of the storage keys paired with /// their associated values. -pub async fn query_storage_prefix< - C: crate::ledger::queries::Client + Sync, - IO: Io, - T, ->( - client: &C, +pub async fn query_storage_prefix<'a, 'b, N: Namada<'a>, T>( + context: &'b N, key: &storage::Key, -) -> Result>, error::Error> +) -> Result>, error::Error> where T: BorshDeserialize, { - let values = convert_response::( + let values = convert_response::( RPC.shell() - .storage_prefix(client, None, None, false, key) + .storage_prefix(context.client(), None, None, false, key) .await, )?; let decode = @@ -347,7 +341,7 @@ where ) { Err(err) => { edisplay_line!( - IO, + context.io(), "Skipping a value for key {}. Error in decoding: {}", key, err @@ -436,16 +430,18 @@ pub async fn query_tx_events( } /// Dry run a transaction -pub async fn dry_run_tx( - client: &C, +pub async fn dry_run_tx<'a, N: Namada<'a>>( + context: &N, tx_bytes: Vec, ) -> Result { let (data, height, prove) = (Some(tx_bytes), None, false); - let result = convert_response::( - RPC.shell().dry_run_tx(client, data, height, prove).await, + let result = convert_response::( + RPC.shell() + .dry_run_tx(context.client(), data, height, prove) + .await, )? .data; - display_line!(IO, "Dry-run result: {}", result); + display_line!(context.io(), "Dry-run result: {}", result); Ok(result) } @@ -786,15 +782,14 @@ pub async fn get_public_key_at( } /// Query a validator's unbonds for a given epoch -pub async fn query_and_print_unbonds< - C: crate::ledger::queries::Client + Sync, ->( - client: &C, +pub async fn query_and_print_unbonds<'a>( + context: &impl Namada<'a>, source: &Address, validator: &Address, ) -> Result<(), error::Error> { - let unbonds = query_unbond_with_slashing(client, source, validator).await?; - let current_epoch = query_epoch(client).await?; + let unbonds = + query_unbond_with_slashing(context.client(), source, validator).await?; + let current_epoch = query_epoch(context.client()).await?; let mut total_withdrawable = token::Amount::default(); let mut not_yet_withdrawable = HashMap::::new(); @@ -809,17 +804,17 @@ pub async fn query_and_print_unbonds< } if total_withdrawable != token::Amount::default() { display_line!( - StdIo, + context.io(), "Total withdrawable now: {}.", total_withdrawable.to_string_native() ); } if !not_yet_withdrawable.is_empty() { - display_line!(StdIo, "Current epoch: {current_epoch}.") + display_line!(context.io(), "Current epoch: {current_epoch}.") } for (withdraw_epoch, amount) in not_yet_withdrawable { display_line!( - StdIo, + context.io(), "Amount {} withdrawable starting from epoch {withdraw_epoch}.", amount.to_string_native() ); @@ -935,10 +930,8 @@ pub async fn enriched_bonds_and_unbonds< } /// Get the correct representation of the amount given the token type. -pub async fn validate_amount< - C: crate::ledger::queries::Client + Sync, ->( - client: &C, +pub async fn validate_amount<'a, N: Namada<'a>>( + context: &N, amount: InputAmount, token: &Address, force: bool, @@ -948,21 +941,21 @@ pub async fn validate_amount< InputAmount::Unvalidated(amt) => amt.canonical(), InputAmount::Validated(amt) => return Ok(amt), }; - let denom = match convert_response::>( - RPC.vp().token().denomination(client, token).await, + let denom = match convert_response::>( + RPC.vp().token().denomination(context.client(), token).await, )? { Some(denom) => Ok(denom), None => { if force { display_line!( - StdIo, + context.io(), "No denomination found for token: {token}, but --force \ was passed. Defaulting to the provided denomination." ); Ok(input_amount.denom) } else { display_line!( - StdIo, + context.io(), "No denomination found for token: {token}, the input \ arguments could not be parsed." ); @@ -974,7 +967,7 @@ pub async fn validate_amount< }?; if denom < input_amount.denom && !force { display_line!( - StdIo, + context.io(), "The input amount contained a higher precision than allowed by \ {token}." ); @@ -985,7 +978,7 @@ pub async fn validate_amount< } else { input_amount.increase_precision(denom).map_err(|_err| { display_line!( - StdIo, + context.io(), "The amount provided requires more the 256 bits to represent." ); Error::from(QueryError::General( @@ -998,10 +991,10 @@ pub async fn validate_amount< } /// Wait for a first block and node to be synced. -pub async fn wait_until_node_is_synched(client: &C) -> Halt<()> -where - C: crate::ledger::queries::Client + Sync, -{ +pub async fn wait_until_node_is_synched<'a>( + client: &(impl Client + Sync), + io: &impl Io, +) -> Halt<()> { let height_one = Height::try_from(1_u64).unwrap(); let try_count = Cell::new(1_u64); const MAX_TRIES: usize = 5; @@ -1023,7 +1016,7 @@ where return ControlFlow::Break(Ok(())); } display_line!( - IO, + io, " Waiting for {} ({}/{} tries)...", if is_at_least_height_one { "a first block" @@ -1038,7 +1031,7 @@ where } Err(e) => { edisplay_line!( - IO, + io, "Failed to query node status with error: {}", e ); @@ -1050,7 +1043,7 @@ where // maybe time out .try_halt(|_| { display_line!( - IO, + io, "Node is still catching up, wait for it to finish synching." ); })? @@ -1060,21 +1053,21 @@ where /// Look up the denomination of a token in order to make a correctly denominated /// amount. -pub async fn denominate_amount( - client: &C, +pub async fn denominate_amount<'a, N: Namada<'a>>( + context: &N, token: &Address, amount: token::Amount, ) -> DenominatedAmount { - let denom = convert_response::>( - RPC.vp().token().denomination(client, token).await, + let denom = convert_response::>( + RPC.vp().token().denomination(context.client(), token).await, ) .unwrap_or_else(|t| { - display_line!(StdIo, "Error in querying for denomination: {t}"); + display_line!(context.io(), "Error in querying for denomination: {t}"); None }) .unwrap_or_else(|| { display_line!( - StdIo, + context.io(), "No denomination found for token: {token}, defaulting to zero \ decimal places" ); @@ -1085,12 +1078,10 @@ pub async fn denominate_amount( /// Look up the denomination of a token in order to format it /// correctly as a string. -pub async fn format_denominated_amount< - C: crate::ledger::queries::Client + Sync, ->( - client: &C, +pub async fn format_denominated_amount<'a>( + context: &impl Namada<'a>, token: &Address, amount: token::Amount, ) -> String { - denominate_amount(client, token, amount).await.to_string() + denominate_amount(context, token, amount).await.to_string() } diff --git a/shared/src/sdk/signing.rs b/shared/src/sdk/signing.rs index 048d72517f..4edc5e4f41 100644 --- a/shared/src/sdk/signing.rs +++ b/shared/src/sdk/signing.rs @@ -23,12 +23,14 @@ use serde::{Deserialize, Serialize}; use sha2::Digest; use zeroize::Zeroizing; -use crate::display_line; use super::masp::{ShieldedContext, ShieldedTransfer}; +use crate::display_line; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc_proto::google::protobuf::Any; use crate::ledger::parameters::storage as parameter_storage; +use crate::ledger::Namada; use crate::proto::{MaspBuilder, Section, Tx}; +use crate::sdk::args::SdkTypes; use crate::sdk::error::{EncodingError, Error, TxError}; use crate::sdk::masp::make_asset_type; use crate::sdk::rpc::{ @@ -44,7 +46,6 @@ pub use crate::sdk::wallet::store::AddressVpType; use crate::sdk::wallet::{Wallet, WalletIo}; use crate::sdk::{args, rpc}; use crate::types::io::*; -use crate::sdk::args::SdkTypes; use crate::types::key::*; use crate::types::masp::{ExtendedViewingKey, PaymentAddress}; use crate::types::storage::Epoch; @@ -54,7 +55,6 @@ use crate::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use crate::types::transaction::pos::InitValidator; -use crate::ledger::Namada; use crate::types::transaction::Fee; #[cfg(feature = "std")] @@ -83,27 +83,28 @@ pub struct SigningTxData { /// for it from the wallet. If the keypair is encrypted but a password is not /// supplied, then it is interactively prompted. Errors if the key cannot be /// found or loaded. -pub async fn find_pk( - client: &C, - wallet: &mut Wallet, +pub async fn find_pk<'a>( + context: &impl Namada<'a>, addr: &Address, password: Option>, ) -> Result { match addr { Address::Established(_) => { display_line!( - StdIo, + context.io(), "Looking-up public key of {} from the ledger...", addr.encode() ); - rpc::get_public_key_at(client, addr, 0) + rpc::get_public_key_at(context.client(), addr, 0) .await? .ok_or(Error::Other(format!( "No public key found for the address {}", addr.encode() ))) } - Address::Implicit(ImplicitAddress(pkh)) => Ok(wallet + Address::Implicit(ImplicitAddress(pkh)) => Ok(context + .wallet_mut() + .await .find_key_by_pkh(pkh, password) .map_err(|err| { Error::Other(format!( @@ -133,6 +134,21 @@ pub fn find_key_by_pk( // We already know the secret key corresponding to the MASP sentinal key Ok(masp_tx_key()) } else { + // Try to get the signer from the signing-keys argument + for signing_key in &args.signing_keys { + if signing_key.ref_to() == *public_key { + return Ok(signing_key.clone()); + } + } + // Try to get the signer from the wrapper-fee-payer argument + match &args.wrapper_fee_payer { + Some(wrapper_fee_payer) + if &wrapper_fee_payer.ref_to() == public_key => + { + return Ok(wrapper_fee_payer.clone()); + } + _ => {} + } // Otherwise we need to search the wallet for the secret key wallet .find_key_by_pk(public_key, args.password.clone()) @@ -171,13 +187,7 @@ pub async fn tx_signers<'a>( Some(signer) if signer == masp() => Ok(vec![masp_tx_key().ref_to()]), Some(signer) => Ok(vec![ - find_pk( - context.client(), - *context.wallet_mut().await, - &signer, - args.password.clone(), - ) - .await?, + find_pk(context, &signer, args.password.clone()).await?, ]), None => other_err( "All transactions must be signed; please either specify the key \ @@ -355,14 +365,10 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( }; let fee_amount = match args.fee_amount { Some(amount) => { - let validated_fee_amount = validate_amount( - context.client(), - amount, - &args.fee_token, - args.force, - ) - .await - .expect("Expected to be able to validate fee"); + let validated_fee_amount = + validate_amount(context, amount, &args.fee_token, args.force) + .await + .expect("Expected to be able to validate fee"); let amount = Amount::from_uint(validated_fee_amount.amount, 0).unwrap(); @@ -372,7 +378,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( } else if !args.force { // Update the fee amount if it's not enough display_line!( - StdIo, + context.io(), "The provided gas price {} is less than the minimum \ amount required {}, changing it to match the minimum", amount.to_string_native(), @@ -513,14 +519,14 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( let token_addr = args.fee_token.clone(); if !args.force { let fee_amount = format_denominated_amount( - context.client(), + context, &token_addr, total_fee, ) .await; let balance = format_denominated_amount( - context.client(), + context, &token_addr, updated_balance, ) @@ -539,7 +545,7 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( _ => { if args.fee_unshield.is_some() { display_line!( - StdIo, + context.io(), "Enough transparent balance to pay fees: the fee \ unshielding spending key will be ignored" ); @@ -607,10 +613,8 @@ fn make_ledger_amount_addr( /// Adds a Ledger output line describing a given transaction amount and asset /// type -async fn make_ledger_amount_asset< - C: crate::ledger::queries::Client + Sync, ->( - client: &C, +async fn make_ledger_amount_asset<'a>( + context: &impl Namada<'a>, tokens: &HashMap, output: &mut Vec, amount: u64, @@ -621,8 +625,7 @@ async fn make_ledger_amount_asset< if let Some((token, _, _epoch)) = assets.get(token) { // If the AssetType can be decoded, then at least display Addressees let formatted_amt = - format_denominated_amount(client, token, amount.into()) - .await; + format_denominated_amount(context, token, amount.into()).await; if let Some(token) = tokens.get(token) { output .push( @@ -706,10 +709,8 @@ fn format_outputs(output: &mut Vec) { /// Adds a Ledger output for the sender and destination for transparent and MASP /// transactions -pub async fn make_ledger_masp_endpoints< - C: crate::ledger::queries::Client + Sync, ->( - client: &C, +pub async fn make_ledger_masp_endpoints<'a>( + context: &impl Namada<'a>, tokens: &HashMap, output: &mut Vec, transfer: &Transfer, @@ -732,7 +733,7 @@ pub async fn make_ledger_masp_endpoints< let vk = ExtendedViewingKey::from(*sapling_input.key()); output.push(format!("Sender : {}", vk)); make_ledger_amount_asset( - client, + context, tokens, output, sapling_input.value(), @@ -759,7 +760,7 @@ pub async fn make_ledger_masp_endpoints< let pa = PaymentAddress::from(sapling_output.address()); output.push(format!("Destination : {}", pa)); make_ledger_amount_asset( - client, + context, tokens, output, sapling_output.value(), @@ -839,31 +840,24 @@ pub async fn to_ledger_vector<'a>( tx: &Tx, ) -> Result { let init_account_hash = - query_wasm_code_hash(context.client(), TX_INIT_ACCOUNT_WASM).await?; + query_wasm_code_hash(context, TX_INIT_ACCOUNT_WASM).await?; let init_validator_hash = - query_wasm_code_hash(context.client(), TX_INIT_VALIDATOR_WASM).await?; + query_wasm_code_hash(context, TX_INIT_VALIDATOR_WASM).await?; let init_proposal_hash = - query_wasm_code_hash(context.client(), TX_INIT_PROPOSAL).await?; + query_wasm_code_hash(context, TX_INIT_PROPOSAL).await?; let vote_proposal_hash = - query_wasm_code_hash(context.client(), TX_VOTE_PROPOSAL).await?; - let reveal_pk_hash = - query_wasm_code_hash(context.client(), TX_REVEAL_PK).await?; + query_wasm_code_hash(context, TX_VOTE_PROPOSAL).await?; + let reveal_pk_hash = query_wasm_code_hash(context, TX_REVEAL_PK).await?; let update_account_hash = - query_wasm_code_hash(context.client(), TX_UPDATE_ACCOUNT_WASM).await?; - let transfer_hash = - query_wasm_code_hash(context.client(), TX_TRANSFER_WASM).await?; - let ibc_hash = query_wasm_code_hash(context.client(), TX_IBC_WASM).await?; - let bond_hash = - query_wasm_code_hash(context.client(), TX_BOND_WASM).await?; - let unbond_hash = - query_wasm_code_hash(context.client(), TX_UNBOND_WASM).await?; - let withdraw_hash = - query_wasm_code_hash(context.client(), TX_WITHDRAW_WASM).await?; + query_wasm_code_hash(context, TX_UPDATE_ACCOUNT_WASM).await?; + let transfer_hash = query_wasm_code_hash(context, TX_TRANSFER_WASM).await?; + let ibc_hash = query_wasm_code_hash(context, TX_IBC_WASM).await?; + let bond_hash = query_wasm_code_hash(context, TX_BOND_WASM).await?; + let unbond_hash = query_wasm_code_hash(context, TX_UNBOND_WASM).await?; + let withdraw_hash = query_wasm_code_hash(context, TX_WITHDRAW_WASM).await?; let change_commission_hash = - query_wasm_code_hash(context.client(), TX_CHANGE_COMMISSION_WASM) - .await?; - let user_hash = - query_wasm_code_hash(context.client(), VP_USER_WASM).await?; + query_wasm_code_hash(context, TX_CHANGE_COMMISSION_WASM).await?; + let user_hash = query_wasm_code_hash(context, VP_USER_WASM).await?; // To facilitate lookups of human-readable token names let wallet = context.wallet().await; @@ -1160,7 +1154,7 @@ pub async fn to_ledger_vector<'a>( tv.output.push("Type : Transfer".to_string()); make_ledger_masp_endpoints( - context.client(), + context, &tokens, &mut tv.output, &transfer, @@ -1169,7 +1163,7 @@ pub async fn to_ledger_vector<'a>( ) .await; make_ledger_masp_endpoints( - context.client(), + context, &tokens, &mut tv.output_expert, &transfer, @@ -1342,13 +1336,13 @@ pub async fn to_ledger_vector<'a>( if let Some(wrapper) = tx.header.wrapper() { let gas_token = wrapper.fee.token.clone(); let gas_limit = format_denominated_amount( - context.client(), + context, &gas_token, Amount::from(wrapper.gas_limit), ) .await; let fee_amount_per_gas_unit = format_denominated_amount( - context.client(), + context, &gas_token, wrapper.fee.amount_per_gas_unit, ) diff --git a/shared/src/sdk/tx.rs b/shared/src/sdk/tx.rs index e6272c0d65..66b8847b17 100644 --- a/shared/src/sdk/tx.rs +++ b/shared/src/sdk/tx.rs @@ -42,23 +42,23 @@ use crate::ibc::core::timestamp::Timestamp as IbcTimestamp; use crate::ibc::core::Msg; use crate::ibc::Height as IbcHeight; use crate::ledger::ibc::storage::ibc_denom_key; -use crate::sdk::signing::SigningTxData; -use crate::sdk::masp::TransferErr::Build; -use crate::sdk::masp::{ShieldedContext, ShieldedTransfer}; -use crate::sdk::rpc::{ - self, format_denominated_amount, validate_amount, TxBroadcastData, - TxResponse, query_wasm_code_hash -}; -use crate::sdk::wallet::{Wallet, WalletIo}; use crate::ledger::Namada; use crate::proto::{MaspBuilder, Tx}; use crate::sdk::args::{self, InputAmount}; use crate::sdk::error::{EncodingError, Error, QueryError, Result, TxError}; -use crate::sdk::signing::{self, TxSourcePostBalance}; +use crate::sdk::masp::TransferErr::Build; +use crate::sdk::masp::{ShieldedContext, ShieldedTransfer}; +use crate::sdk::queries::Client; +use crate::sdk::rpc::{ + self, format_denominated_amount, query_wasm_code_hash, validate_amount, + TxBroadcastData, TxResponse, +}; +use crate::sdk::signing::{self, SigningTxData, TxSourcePostBalance}; +use crate::sdk::wallet::WalletIo; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::tendermint_rpc::error::Error as RpcError; use crate::types::control_flow::{time, ProceedOrElse}; -use crate::types::io::{Io, StdIo}; +use crate::types::io::Io; use crate::types::key::*; use crate::types::masp::TransferTarget; use crate::types::storage::Epoch; @@ -133,7 +133,7 @@ impl ProcessTxResponse { } /// Build and dump a transaction either to file or to screen -pub fn dump_tx(args: &args::Tx, tx: Tx) { +pub fn dump_tx(io: &IO, args: &args::Tx, tx: Tx) { let tx_id = tx.header_hash(); let serialized_tx = tx.serialize(); match args.output_folder.to_owned() { @@ -144,14 +144,14 @@ pub fn dump_tx(args: &args::Tx, tx: Tx) { serde_json::to_writer_pretty(out, &serialized_tx) .expect("Should be able to write to file."); display_line!( - IO, + io, "Transaction serialized to {}.", tx_path.to_string_lossy() ); } None => { - display_line!(IO, "Below the serialized transaction: \n"); - display_line!(IO, "{}", serialized_tx) + display_line!(io, "Below the serialized transaction: \n"); + display_line!(io, "{}", serialized_tx) } } } @@ -169,15 +169,8 @@ pub async fn prepare_tx<'a>( if !args.dry_run { let epoch = rpc::query_epoch(context.client()).await?; - signing::wrap_tx( - context, - tx, - args, - tx_source_balance, - epoch, - fee_payer, - ) - .await + signing::wrap_tx(context, tx, args, tx_source_balance, epoch, fee_payer) + .await } else { Ok(None) } @@ -185,12 +178,8 @@ pub async fn prepare_tx<'a>( /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. -pub async fn process_tx< - C: crate::sdk::queries::Client + Sync, - U: WalletIo, ->( - client: &C, - wallet: &mut Wallet, +pub async fn process_tx<'a>( + context: &impl Namada<'a>, args: &args::Tx, tx: Tx, ) -> Result { @@ -205,7 +194,7 @@ pub async fn process_tx< // println!("HTTP request body: {}", request_body); if args.dry_run || args.dry_run_wrapper { - expect_dry_broadcast::<_, StdIo>(TxBroadcastData::DryRun(tx), client).await + expect_dry_broadcast(TxBroadcastData::DryRun(tx), context).await } else { // We use this to determine when the wrapper tx makes it on-chain let wrapper_hash = tx.header_hash().to_string(); @@ -225,14 +214,14 @@ pub async fn process_tx< // of masp epoch Either broadcast or submit transaction and // collect result into sum type if args.broadcast_only { - broadcast_tx::<_, StdIo>(client, &to_broadcast) + broadcast_tx(context, &to_broadcast) .await .map(ProcessTxResponse::Broadcast) } else { - match submit_tx::<_, StdIo>(client, to_broadcast).await { + match submit_tx(context, to_broadcast).await { Ok(x) => { - save_initialized_accounts::( - wallet, + save_initialized_accounts( + context, args, x.initialized_accounts.clone(), ) @@ -292,8 +281,8 @@ pub async fn build_reveal_pk<'a>( /// the tx has been successfully included into the mempool of a node /// /// In the case of errors in any of those stages, an error message is returned -pub async fn broadcast_tx( - rpc_cli: &C, +pub async fn broadcast_tx<'a>( + context: &impl Namada<'a>, to_broadcast: &TxBroadcastData, ) -> Result { let (tx, wrapper_tx_hash, decrypted_tx_hash) = match to_broadcast { @@ -313,21 +302,29 @@ pub async fn broadcast_tx( // TODO: configure an explicit timeout value? we need to hack away at // `tendermint-rs` for this, which is currently using a hard-coded 30s // timeout. - let response = - lift_rpc_error(rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await)?; + let response = lift_rpc_error( + context + .client() + .broadcast_tx_sync(tx.to_bytes().into()) + .await, + )?; if response.code == 0.into() { - display_line!(IO, "Transaction added to mempool: {:?}", response); + display_line!( + context.io(), + "Transaction added to mempool: {:?}", + response + ); // Print the transaction identifiers to enable the extraction of // acceptance/application results later { display_line!( - IO, + context.io(), "Wrapper transaction hash: {:?}", wrapper_tx_hash ); display_line!( - IO, + context.io(), "Inner transaction hash: {:?}", decrypted_tx_hash ); @@ -350,13 +347,10 @@ pub async fn broadcast_tx( /// 3. The decrypted payload of the tx has been included on the blockchain. /// /// In the case of errors in any of those stages, an error message is returned -pub async fn submit_tx( - client: &C, +pub async fn submit_tx<'a>( + context: &impl Namada<'a>, to_broadcast: TxBroadcastData, -) -> Result -where - C: crate::sdk::queries::Client + Sync, -{ +) -> Result { let (_, wrapper_hash, decrypted_hash) = match &to_broadcast { TxBroadcastData::Live { tx, @@ -367,7 +361,7 @@ where }?; // Broadcast the supplied transaction - broadcast_tx::<_, IO>(client, &to_broadcast).await?; + broadcast_tx(context, &to_broadcast).await?; let deadline = time::Instant::now() + time::Duration::from_secs( @@ -382,10 +376,9 @@ where let parsed = { let wrapper_query = rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); - let event = - rpc::query_tx_status::<_, IO>(client, wrapper_query, deadline) - .await - .proceed_or(TxError::AcceptTimeout)?; + let event = rpc::query_tx_status(context, wrapper_query, deadline) + .await + .proceed_or(TxError::AcceptTimeout)?; let parsed = TxResponse::from_event(event); let tx_to_str = |parsed| { serde_json::to_string_pretty(parsed).map_err(|err| { @@ -393,7 +386,7 @@ where }) }; display_line!( - IO, + context.io(), "Transaction accepted with result: {}", tx_to_str(&parsed)? ); @@ -404,16 +397,13 @@ where // payload makes its way onto the blockchain let decrypted_query = rpc::TxEventQuery::Applied(decrypted_hash.as_str()); - let event = rpc::query_tx_status::<_, IO>( - client, - decrypted_query, - deadline, - ) - .await - .proceed_or(TxError::AppliedTimeout)?; + let event = + rpc::query_tx_status(context, decrypted_query, deadline) + .await + .proceed_or(TxError::AppliedTimeout)?; let parsed = TxResponse::from_event(event); display_line!( - IO, + context.io(), "Transaction applied with result: {}", tx_to_str(&parsed)? ); @@ -450,8 +440,8 @@ pub fn decode_component( } /// Save accounts initialized from a tx into the wallet, if any. -pub async fn save_initialized_accounts( - wallet: &mut Wallet, +pub async fn save_initialized_accounts<'a, N: Namada<'a>>( + context: &N, args: &args::Tx, initialized_accounts: Vec
, ) { @@ -459,7 +449,7 @@ pub async fn save_initialized_accounts( if len != 0 { // Store newly initialized account addresses in the wallet display_line!( - IO, + context.io(), "The transaction initialized {} new account{}", len, if len == 1 { "" } else { "s" } @@ -480,10 +470,10 @@ pub async fn save_initialized_accounts( format!("{}{}", initialized_account_alias, ix).into() } } - None => U::read_alias(&encoded).into(), + None => N::WalletUtils::read_alias(&encoded).into(), }; let alias = alias.into_owned(); - let added = wallet.add_address( + let added = context.wallet_mut().await.add_address( alias.clone(), address.clone(), args.wallet_alias_force, @@ -491,14 +481,18 @@ pub async fn save_initialized_accounts( match added { Some(new_alias) if new_alias != encoded => { display_line!( - IO, + context.io(), "Added alias {} for address {}.", new_alias, encoded ); } _ => { - display_line!(IO, "No alias added for address {}.", encoded) + display_line!( + context.io(), + "No alias added for address {}.", + encoded + ) } }; } @@ -532,7 +526,7 @@ pub async fn build_validator_commission_change<'a>( if rpc::is_validator(context.client(), &validator).await? { if *rate < Dec::zero() || *rate > Dec::one() { edisplay_line!( - StdIo, + context.io(), "Invalid new commission rate, received {}", rate ); @@ -556,7 +550,7 @@ pub async fn build_validator_commission_change<'a>( > max_commission_change_per_epoch { edisplay_line!( - StdIo, + context.io(), "New rate is too large of a change with respect to \ the predecessor epoch in which the rate will take \ effect." @@ -569,14 +563,17 @@ pub async fn build_validator_commission_change<'a>( } } None => { - edisplay_line!(StdIo, "Error retrieving from storage"); + edisplay_line!(context.io(), "Error retrieving from storage"); if !tx_args.force { return Err(Error::from(TxError::Retrieval)); } } } } else { - edisplay_line!(StdIo, "The given address {validator} is not a validator."); + edisplay_line!( + context.io(), + "The given address {validator} is not a validator." + ); if !tx_args.force { return Err(Error::from(TxError::InvalidValidatorAddress( validator, @@ -622,7 +619,11 @@ pub async fn build_update_steward_commission<'a>( .await?; if !rpc::is_steward(context.client(), steward).await && !tx_args.force { - edisplay_line!(StdIo, "The given address {} is not a steward.", &steward); + edisplay_line!( + context.io(), + "The given address {} is not a steward.", + &steward + ); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); }; @@ -631,7 +632,7 @@ pub async fn build_update_steward_commission<'a>( if !commission.is_valid() && !tx_args.force { edisplay_line!( - StdIo, + context.io(), "The sum of all percentage must not be greater than 1." ); return Err(Error::from(TxError::InvalidStewardCommission( @@ -676,7 +677,11 @@ pub async fn build_resign_steward<'a>( .await?; if !rpc::is_steward(context.client(), steward).await && !tx_args.force { - edisplay_line!(StdIo, "The given address {} is not a steward.", &steward); + edisplay_line!( + context.io(), + "The given address {} is not a steward.", + &steward + ); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); }; @@ -713,7 +718,7 @@ pub async fn build_unjail_validator<'a>( if !rpc::is_validator(context.client(), validator).await? { edisplay_line!( - StdIo, + context.io(), "The given address {} is not a validator.", &validator ); @@ -741,7 +746,7 @@ pub async fn build_unjail_validator<'a>( })?; if validator_state_at_pipeline != ValidatorState::Jailed { edisplay_line!( - StdIo, + context.io(), "The given validator address {} is not jailed at the pipeline \ epoch when it would be restored to one of the validator sets.", &validator @@ -766,7 +771,7 @@ pub async fn build_unjail_validator<'a>( last_slash_epoch + params.slash_processing_epoch_offset(); if current_epoch < eligible_epoch { edisplay_line!( - StdIo, + context.io(), "The given validator address {} is currently frozen and \ not yet eligible to be unjailed.", &validator @@ -827,12 +832,9 @@ pub async fn build_withdraw<'a>( let epoch = rpc::query_epoch(context.client()).await?; - let validator = known_validator_or_err( - validator.clone(), - tx_args.force, - context.client(), - ) - .await?; + let validator = + known_validator_or_err(validator.clone(), tx_args.force, context) + .await?; let source = source.clone(); @@ -848,27 +850,25 @@ pub async fn build_withdraw<'a>( if tokens.is_zero() { edisplay_line!( - StdIo, + context.io(), "There are no unbonded bonds ready to withdraw in the current \ epoch {}.", epoch ); - rpc::query_and_print_unbonds( - context.client(), - &bond_source, - &validator, - ) - .await?; + rpc::query_and_print_unbonds(context, &bond_source, &validator).await?; if !tx_args.force { return Err(Error::from(TxError::NoUnbondReady(epoch))); } } else { display_line!( - StdIo, + context.io(), "Found {} tokens that can be withdrawn.", tokens.to_string_native() ); - display_line!(StdIo, "Submitting transaction to withdraw them..."); + display_line!( + context.io(), + "Submitting transaction to withdraw them..." + ); } let data = pos::Withdraw { validator, source }; @@ -917,24 +917,21 @@ pub async fn build_unbond<'a>( let bond_source = source.clone().unwrap_or_else(|| validator.clone()); if !tx_args.force { - known_validator_or_err( - validator.clone(), - tx_args.force, - context.client(), - ) - .await?; + known_validator_or_err(validator.clone(), tx_args.force, context) + .await?; let bond_amount = - rpc::query_bond(context.client(), &bond_source, validator, None).await?; + rpc::query_bond(context.client(), &bond_source, validator, None) + .await?; display_line!( - StdIo, + context.io(), "Bond amount available for unbonding: {} NAM", bond_amount.to_string_native() ); if *amount > bond_amount { edisplay_line!( - StdIo, + context.io(), "The total bonds of the source {} is lower than the amount to \ be unbonded. Amount to unbond is {} and the total bonds is \ {}.", @@ -986,8 +983,8 @@ pub async fn build_unbond<'a>( } /// Query the unbonds post-tx -pub async fn query_unbonds( - client: &C, +pub async fn query_unbonds<'a>( + context: &impl Namada<'a>, args: args::Unbond, latest_withdrawal_pre: Option<(Epoch, token::Amount)>, ) -> Result<()> { @@ -996,9 +993,12 @@ pub async fn query_unbonds( let bond_source = source.clone().unwrap_or_else(|| args.validator.clone()); // Query the unbonds post-tx - let unbonds = - rpc::query_unbond_with_slashing(client, &bond_source, &args.validator) - .await?; + let unbonds = rpc::query_unbond_with_slashing( + context.client(), + &bond_source, + &args.validator, + ) + .await?; let mut withdrawable = BTreeMap::::new(); for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); @@ -1016,7 +1016,7 @@ pub async fn query_unbonds( std::cmp::Ordering::Less => { if args.tx.force { edisplay_line!( - IO, + context.io(), "Unexpected behavior reading the unbonds data has \ occurred" ); @@ -1026,7 +1026,7 @@ pub async fn query_unbonds( } std::cmp::Ordering::Equal => { display_line!( - IO, + context.io(), "Amount {} withdrawable starting from epoch {}", (latest_withdraw_amount_post - latest_withdraw_amount_pre) .to_string_native(), @@ -1035,7 +1035,7 @@ pub async fn query_unbonds( } std::cmp::Ordering::Greater => { display_line!( - IO, + context.io(), "Amount {} withdrawable starting from epoch {}", latest_withdraw_amount_post.to_string_native(), latest_withdraw_epoch_post, @@ -1044,7 +1044,7 @@ pub async fn query_unbonds( } } else { display_line!( - IO, + context.io(), "Amount {} withdrawable starting from epoch {}", latest_withdraw_amount_post.to_string_native(), latest_withdraw_epoch_post, @@ -1075,20 +1075,15 @@ pub async fn build_bond<'a>( ) .await?; - let validator = known_validator_or_err( - validator.clone(), - tx_args.force, - context.client(), - ) - .await?; + let validator = + known_validator_or_err(validator.clone(), tx_args.force, context) + .await?; // Check that the source address exists on chain let source = match source.clone() { - Some(source) => { - source_exists_or_err(source, tx_args.force, context.client()) - .await - .map(Some) - } + Some(source) => source_exists_or_err(source, tx_args.force, context) + .await + .map(Some), None => Ok(source.clone()), }?; // Check bond's source (source for delegation or validator for self-bonds) @@ -1103,7 +1098,7 @@ pub async fn build_bond<'a>( *amount, balance_key, tx_args.force, - context.client(), + context, ) .await?; let tx_source_balance = Some(TxSourcePostBalance { @@ -1376,23 +1371,16 @@ pub async fn build_ibc_transfer<'a>( ) .await?; // Check that the source address exists on chain - let source = source_exists_or_err( - args.source.clone(), - args.tx.force, - context.client(), - ) - .await?; + let source = + source_exists_or_err(args.source.clone(), args.tx.force, context) + .await?; // We cannot check the receiver // validate the amount given - let validated_amount = validate_amount( - context.client(), - args.amount, - &args.token, - args.tx.force, - ) - .await - .expect("expected to validate amount"); + let validated_amount = + validate_amount(context, args.amount, &args.token, args.tx.force) + .await + .expect("expected to validate amount"); if validated_amount.canonical().denom.0 != 0 { return Err(Error::Other(format!( "The amount for the IBC transfer should be an integer: {}", @@ -1409,7 +1397,7 @@ pub async fn build_ibc_transfer<'a>( validated_amount.amount, balance_key, args.tx.force, - context.client(), + context, ) .await?; let tx_source_balance = Some(TxSourcePostBalance { @@ -1418,12 +1406,10 @@ pub async fn build_ibc_transfer<'a>( token: args.token.clone(), }); - let tx_code_hash = query_wasm_code_hash( - context.client(), - args.tx_code_path.to_str().unwrap(), - ) - .await - .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; + let tx_code_hash = + query_wasm_code_hash(context, args.tx_code_path.to_str().unwrap()) + .await + .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; let ibc_denom = match &args.token { Address::Internal(InternalAddress::IbcToken(hash)) => { @@ -1551,10 +1537,9 @@ where let mut tx_builder = Tx::new(chain_id, tx_args.expiration); - let tx_code_hash = - query_wasm_code_hash(context.client(), path.to_string_lossy()) - .await - .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; + let tx_code_hash = query_wasm_code_hash(context, path.to_string_lossy()) + .await + .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; on_tx(&mut tx_builder, &mut data)?; @@ -1645,18 +1630,15 @@ pub async fn build_transfer<'a, N: Namada<'a>>( let token = args.token.clone(); // Check that the source address exists on chain - source_exists_or_err(source.clone(), args.tx.force, context.client()) - .await?; + 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.client()) - .await?; + target_exists_or_err(target.clone(), args.tx.force, context).await?; // Check source balance let balance_key = token::balance_key(&token, &source); // validate the amount given let validated_amount = - validate_amount(context.client(), args.amount, &token, args.tx.force) - .await?; + validate_amount(context, args.amount, &token, args.tx.force).await?; args.amount = InputAmount::Validated(validated_amount); let post_balance = check_balance_too_low_err( @@ -1665,7 +1647,7 @@ pub async fn build_transfer<'a, N: Namada<'a>>( validated_amount.amount, balance_key, args.tx.force, - context.client(), + context, ) .await?; let tx_source_balance = Some(TxSourcePostBalance { @@ -1810,8 +1792,7 @@ pub async fn build_init_account<'a>( let signing_data = signing::aux_signing_data(context, tx_args, None, None).await?; - let vp_code_hash = - query_wasm_code_hash_buf(context.client(), vp_code_path).await?; + let vp_code_hash = query_wasm_code_hash_buf(context, vp_code_path).await?; let threshold = match threshold { Some(threshold) => *threshold, @@ -1882,8 +1863,7 @@ pub async fn build_update_account<'a>( let vp_code_hash = match vp_code_path { Some(code_path) => { - let vp_hash = - query_wasm_code_hash_buf(context.client(), code_path).await?; + let vp_hash = query_wasm_code_hash_buf(context, code_path).await?; Some(vp_hash) } None => None, @@ -1946,7 +1926,7 @@ pub async fn build_custom<'a>( })? } else { let tx_code_hash = query_wasm_code_hash_buf( - context.client(), + context, code_path .as_ref() .ok_or(Error::Other("No code path supplied".to_string()))?, @@ -1971,16 +1951,13 @@ pub async fn build_custom<'a>( Ok((tx, signing_data, epoch)) } -async fn expect_dry_broadcast< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( +async fn expect_dry_broadcast<'a>( to_broadcast: TxBroadcastData, - client: &C, + context: &impl Namada<'a>, ) -> Result { match to_broadcast { TxBroadcastData::DryRun(tx) => { - rpc::dry_run_tx::<_, IO>(client, tx.to_bytes()).await?; + rpc::dry_run_tx(context, tx.to_bytes()).await?; Ok(ProcessTxResponse::DryRun) } TxBroadcastData::Live { @@ -1998,19 +1975,17 @@ fn lift_rpc_error(res: std::result::Result) -> Result { /// Returns the given validator if the given address is a validator, /// otherwise returns an error, force forces the address through even /// if it isn't a validator -async fn known_validator_or_err< - C: crate::ledger::queries::Client + Sync, ->( +async fn known_validator_or_err<'a>( validator: Address, force: bool, - client: &C, + context: &impl Namada<'a>, ) -> Result
{ // Check that the validator address exists on chain - let is_validator = rpc::is_validator(client, &validator).await?; + let is_validator = rpc::is_validator(context.client(), &validator).await?; if !is_validator { if force { edisplay_line!( - StdIo, + context.io(), "The address {} doesn't belong to any known validator account.", validator ); @@ -2026,21 +2001,20 @@ async fn known_validator_or_err< /// general pattern for checking if an address exists on the chain, or /// throwing an error if it's not forced. Takes a generic error /// message and the error type. -async fn address_exists_or_err( +async fn address_exists_or_err<'a, F>( addr: Address, force: bool, - client: &C, + context: &impl Namada<'a>, message: String, err: F, ) -> Result
where - C: crate::sdk::queries::Client + Sync, F: FnOnce(Address) -> Error, { - let addr_exists = rpc::known_address::(client, &addr).await?; + let addr_exists = rpc::known_address(context.client(), &addr).await?; if !addr_exists { if force { - edisplay_line!(StdIo, "{}", message); + edisplay_line!(context.io(), "{}", message); Ok(addr) } else { Err(err(addr)) @@ -2053,16 +2027,14 @@ where /// Returns the given source address if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain -async fn source_exists_or_err< - C: crate::ledger::queries::Client + Sync, ->( +async fn source_exists_or_err<'a>( token: Address, force: bool, - client: &C, + context: &impl Namada<'a>, ) -> Result
{ let message = format!("The source address {} doesn't exist on chain.", token); - address_exists_or_err(token, force, client, message, |err| { + address_exists_or_err(token, force, context, message, |err| { Error::from(TxError::SourceDoesNotExist(err)) }) .await @@ -2071,16 +2043,14 @@ async fn source_exists_or_err< /// Returns the given target address if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain -async fn target_exists_or_err< - C: crate::ledger::queries::Client + Sync, ->( +async fn target_exists_or_err<'a>( token: Address, force: bool, - client: &C, + context: &impl Namada<'a>, ) -> Result
{ let message = format!("The target address {} doesn't exist on chain.", token); - address_exists_or_err(token, force, client, message, |err| { + address_exists_or_err(token, force, context, message, |err| { Error::from(TxError::TargetLocationDoesNotExist(err)) }) .await @@ -2089,38 +2059,34 @@ async fn target_exists_or_err< /// Checks the balance at the given address is enough to transfer the /// given amount, along with the balance even existing. Force /// overrides this. Returns the updated balance for fee check if necessary -async fn check_balance_too_low_err< - C: crate::ledger::queries::Client + Sync, ->( +async fn check_balance_too_low_err<'a, N: Namada<'a>>( token: &Address, source: &Address, amount: token::Amount, balance_key: storage::Key, force: bool, - client: &C, + context: &N, ) -> Result { - match rpc::query_storage_value::(client, &balance_key) - .await + match rpc::query_storage_value::( + context.client(), + &balance_key, + ) + .await { Ok(balance) => match balance.checked_sub(amount) { Some(diff) => Ok(diff), None => { if force { edisplay_line!( - StdIo, + context.io(), "The balance of the source {} of token {} is lower \ than the amount to be transferred. Amount to \ transfer is {} and the balance is {}.", source, token, - format_denominated_amount( - client, token, amount - ) - .await, - format_denominated_amount( - client, token, balance - ) - .await, + format_denominated_amount(context, token, amount).await, + format_denominated_amount(context, token, balance) + .await, ); Ok(token::Amount::default()) } else { @@ -2138,7 +2104,7 @@ async fn check_balance_too_low_err< )) => { if force { edisplay_line!( - StdIo, + context.io(), "No balance found for the source {} of token {}", source, token @@ -2159,13 +2125,14 @@ async fn check_balance_too_low_err< #[allow(dead_code)] fn validate_untrusted_code_err( + io: &IO, vp_code: &Vec, force: bool, ) -> Result<()> { if let Err(err) = vm::validate_untrusted_wasm(vp_code) { if force { edisplay_line!( - IO, + io, "Validity predicate code validation failed with {}", err ); @@ -2177,13 +2144,11 @@ fn validate_untrusted_code_err( Ok(()) } } -async fn query_wasm_code_hash_buf< - C: crate::ledger::queries::Client + Sync, ->( - client: &C, +async fn query_wasm_code_hash_buf<'a>( + context: &impl Namada<'a>, path: &Path, ) -> Result { - query_wasm_code_hash(client, path.to_string_lossy()).await + query_wasm_code_hash(context, path.to_string_lossy()).await } /// A helper for [`fn build`] that can be used for `on_tx` arg that does nothing diff --git a/shared/src/types/io.rs b/shared/src/types/io.rs index 007d5acd93..f100ca8433 100644 --- a/shared/src/types/io.rs +++ b/shared/src/types/io.rs @@ -11,20 +11,21 @@ impl Io for StdIo {} #[async_trait::async_trait(?Send)] #[allow(missing_docs)] pub trait Io { - fn print(output: impl AsRef) { + fn print(&self, output: impl AsRef) { print!("{}", output.as_ref()); } - fn flush() { + fn flush(&self) { use std::io::Write; std::io::stdout().flush().unwrap(); } - fn println(output: impl AsRef) { + fn println(&self, output: impl AsRef) { println!("{}", output.as_ref()); } fn write( + &self, mut writer: W, output: impl AsRef, ) -> std::io::Result<()> { @@ -32,17 +33,18 @@ pub trait Io { } fn writeln( + &self, mut writer: W, output: impl AsRef, ) -> std::io::Result<()> { writeln!(writer, "{}", output.as_ref()) } - fn eprintln(output: impl AsRef) { + fn eprintln(&self, output: impl AsRef) { eprintln!("{}", output.as_ref()); } - async fn read() -> std::io::Result { + async fn read(&self) -> std::io::Result { #[cfg(not(target_family = "wasm"))] { read_aux(tokio::io::stdin()).await @@ -53,7 +55,7 @@ pub trait Io { } } - async fn prompt(question: impl AsRef) -> String { + async fn prompt(&self, question: impl AsRef) -> String { #[cfg(not(target_family = "wasm"))] { prompt_aux( @@ -111,14 +113,14 @@ where /// [`Io::print`] #[macro_export] macro_rules! display { - ($io:ty) => { - <$io>::print("") + ($io:expr) => { + $io.print("") }; - ($io:ty, $w:expr; $($args:tt)*) => { - <$io>::write($w, format_args!($($args)*).to_string()) + ($io:expr, $w:expr; $($args:tt)*) => { + $io.write($w, format_args!($($args)*).to_string()) }; - ($io:ty,$($args:tt)*) => { - <$io>::print(format_args!($($args)*).to_string()) + ($io:expr,$($args:tt)*) => { + $io.print(format_args!($($args)*).to_string()) }; } @@ -126,14 +128,14 @@ macro_rules! display { /// [`Io::println`] and [`Io::writeln`] #[macro_export] macro_rules! display_line { - ($io:ty) => { - <$io>::println("") + ($io:expr) => { + $io.println("") }; - ($io:ty, $w:expr; $($args:tt)*) => { - <$io>::writeln($w, format_args!($($args)*).to_string()) + ($io:expr, $w:expr; $($args:tt)*) => { + $io.writeln($w, format_args!($($args)*).to_string()) }; - ($io:ty,$($args:tt)*) => { - <$io>::println(format_args!($($args)*).to_string()) + ($io:expr,$($args:tt)*) => { + $io.println(format_args!($($args)*).to_string()) }; } @@ -141,8 +143,8 @@ macro_rules! display_line { /// [`Io::eprintln`] #[macro_export] macro_rules! edisplay_line { - ($io:ty,$($args:tt)*) => { - <$io>::eprintln(format_args!($($args)*).to_string()) + ($io:expr,$($args:tt)*) => { + $io.eprintln(format_args!($($args)*).to_string()) }; } @@ -150,7 +152,7 @@ macro_rules! edisplay_line { /// A convenience macro for formatting the user prompt before /// forwarding it to the [`Io::prompt`] method. macro_rules! prompt { - ($io:ty,$($arg:tt)*) => {{ - <$io>::prompt(format!("{}", format_args!($($arg)*))) + ($io:expr,$($arg:tt)*) => {{ + $io.prompt(format!("{}", format_args!($($arg)*))) }} } From 615ebc8e7dbf3531c446f60ec7ac77676aef3b63 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Mon, 9 Oct 2023 15:25:56 +0200 Subject: [PATCH 11/14] SDK can now query for the native token address from the network. Also added null IO implementation. --- apps/src/bin/namada-client/main.rs | 2 +- apps/src/bin/namada-relayer/main.rs | 3 +- apps/src/bin/namada-wallet/main.rs | 2 +- apps/src/lib/cli/api.rs | 4 +- apps/src/lib/cli/client.rs | 7 +- apps/src/lib/cli/context.rs | 8 +- apps/src/lib/cli/relayer.rs | 4 +- apps/src/lib/cli/wallet.rs | 2 +- apps/src/lib/client/tx.rs | 4 +- .../lib/node/ledger/shell/testing/client.rs | 6 +- benches/lib.rs | 3 +- shared/src/ledger/mod.rs | 126 ++++++++++-------- shared/src/ledger/queries/shell.rs | 12 ++ shared/src/sdk/args.rs | 4 +- shared/src/sdk/rpc.rs | 7 + shared/src/types/io.rs | 60 ++++++++- 16 files changed, 167 insertions(+), 87 deletions(-) diff --git a/apps/src/bin/namada-client/main.rs b/apps/src/bin/namada-client/main.rs index 167674f65e..770dcf5367 100644 --- a/apps/src/bin/namada-client/main.rs +++ b/apps/src/bin/namada-client/main.rs @@ -13,7 +13,7 @@ async fn main() -> Result<()> { let _log_guard = logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI - CliApi::::handle_client_command::( + CliApi::handle_client_command::( None, cli::namada_client_cli()?, &CliIo, diff --git a/apps/src/bin/namada-relayer/main.rs b/apps/src/bin/namada-relayer/main.rs index ef5e05f913..f9d98a2a4e 100644 --- a/apps/src/bin/namada-relayer/main.rs +++ b/apps/src/bin/namada-relayer/main.rs @@ -14,6 +14,5 @@ async fn main() -> Result<()> { let cmd = cli::namada_relayer_cli()?; // run the CLI - CliApi::::handle_relayer_command::(None, cmd, &CliIo) - .await + CliApi::handle_relayer_command::(None, cmd, &CliIo).await } diff --git a/apps/src/bin/namada-wallet/main.rs b/apps/src/bin/namada-wallet/main.rs index 987e9d2699..30d4a64156 100644 --- a/apps/src/bin/namada-wallet/main.rs +++ b/apps/src/bin/namada-wallet/main.rs @@ -6,5 +6,5 @@ pub fn main() -> Result<()> { color_eyre::install()?; let (cmd, ctx) = cli::namada_wallet_cli()?; // run the CLI - CliApi::::handle_wallet_command(cmd, ctx, &CliIo) + CliApi::handle_wallet_command(cmd, ctx, &CliIo) } diff --git a/apps/src/lib/cli/api.rs b/apps/src/lib/cli/api.rs index 052a834f55..1b6851f3a9 100644 --- a/apps/src/lib/cli/api.rs +++ b/apps/src/lib/cli/api.rs @@ -1,5 +1,3 @@ -use std::marker::PhantomData; - use namada::sdk::queries::Client; use namada::sdk::rpc::wait_until_node_is_synched; use namada::tendermint_rpc::HttpClient; @@ -32,4 +30,4 @@ pub struct CliIo; #[async_trait::async_trait(?Send)] impl Io for CliIo {} -pub struct CliApi(PhantomData); +pub struct CliApi; diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index ac1ca1e34d..a342e9ef25 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -15,8 +15,8 @@ fn error() -> Report { eyre!("Fatal error") } -impl CliApi { - pub async fn handle_client_command( +impl CliApi { + pub async fn handle_client_command( client: Option, cmd: cli::NamadaClient, io: &IO, @@ -139,11 +139,12 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - let namada = NamadaImpl::new( + let namada = NamadaImpl::native_new( &client, &mut ctx.wallet, &mut ctx.shielded, io, + ctx.native_token, ); tx::submit_init_validator( &namada, diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 4772ef98b9..f6c3399baf 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -161,7 +161,13 @@ impl Context { C: namada::ledger::queries::Client + Sync, IO: Io, { - NamadaImpl::new(client, &mut self.wallet, &mut self.shielded, io) + NamadaImpl::native_new( + client, + &mut self.wallet, + &mut self.shielded, + io, + self.native_token.clone(), + ) } /// Parse and/or look-up the value from the context. diff --git a/apps/src/lib/cli/relayer.rs b/apps/src/lib/cli/relayer.rs index d94fd5a09d..aadf2d3bda 100644 --- a/apps/src/lib/cli/relayer.rs +++ b/apps/src/lib/cli/relayer.rs @@ -15,11 +15,11 @@ fn error() -> Report { eyre!("Fatal error") } -impl CliApi { +impl CliApi { pub async fn handle_relayer_command( client: Option, cmd: cli::NamadaRelayer, - io: &IO, + io: &impl Io, ) -> Result<()> where C: CliClient, diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 5dc223cd64..6247145b84 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -25,7 +25,7 @@ use crate::cli::args::CliToSdk; use crate::cli::{args, cmds, Context}; use crate::wallet::{read_and_confirm_encryption_password, CliWalletUtils}; -impl CliApi { +impl CliApi { pub fn handle_wallet_command( cmd: cmds::NamadaWallet, mut ctx: Context, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 22f0c1b1b4..53b2232f64 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -641,7 +641,7 @@ where })?; let author_balance = rpc::get_token_balance( namada.client(), - &namada.native_token().await, + &namada.native_token(), &proposal.proposal.author, ) .await; @@ -665,7 +665,7 @@ where })?; let author_balane = rpc::get_token_balance( namada.client(), - &namada.native_token().await, + &namada.native_token(), &proposal.proposal.author, ) .await; diff --git a/apps/src/lib/node/ledger/shell/testing/client.rs b/apps/src/lib/node/ledger/shell/testing/client.rs index 504bdc7e5b..7649156b8e 100644 --- a/apps/src/lib/node/ledger/shell/testing/client.rs +++ b/apps/src/lib/node/ledger/shell/testing/client.rs @@ -47,7 +47,7 @@ pub fn run( NamadaClient::WithoutContext(sub_cmd, global) } }; - rt.block_on(CliApi::::handle_client_command( + rt.block_on(CliApi::handle_client_command( Some(node), cmd, &TestingIo, @@ -61,7 +61,7 @@ pub fn run( let cmd = cmds::NamadaWallet::parse(&matches) .expect("Could not parse wallet command"); - CliApi::::handle_wallet_command(cmd, ctx, &TestingIo) + CliApi::handle_wallet_command(cmd, ctx, &TestingIo) } Bin::Relayer => { args.insert(0, "relayer"); @@ -83,7 +83,7 @@ pub fn run( NamadaRelayer::ValidatorSet(sub_cmd) } }; - rt.block_on(CliApi::::handle_relayer_command( + rt.block_on(CliApi::handle_relayer_command( Some(node), cmd, &TestingIo, diff --git a/benches/lib.rs b/benches/lib.rs index b5036d5f66..f0cba69475 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -810,11 +810,12 @@ impl BenchShieldedCtx { &[], )) .unwrap(); - let namada = NamadaImpl::new( + let namada = NamadaImpl::native_new( &self.shell, &mut self.wallet, &mut self.shielded, &StdIo, + self.shell.wl_storage.storage.native_token.clone(), ); let shielded = async_runtime .block_on( diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 5536f46b63..aecde2d930 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -28,6 +28,7 @@ use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use crate::proto::Tx; use crate::sdk::args::{self, InputAmount, SdkTypes}; use crate::sdk::masp::{ShieldedContext, ShieldedUtils}; +use crate::sdk::rpc::query_native_token; use crate::sdk::signing::{self, SigningTxData}; use crate::sdk::tx::{ self, ProcessTxResponse, TX_BOND_WASM, TX_BRIDGE_POOL_WASM, @@ -86,10 +87,10 @@ pub trait Namada<'a>: Sized { ) -> RwLockWriteGuard<&'a mut ShieldedContext>; /// Return the native token - async fn native_token(&self) -> Address; + fn native_token(&self) -> Address; /// Make a tx builder using no arguments - async fn tx_builder(&self) -> args::Tx { + fn tx_builder(&self) -> args::Tx { args::Tx { dry_run: false, dry_run_wrapper: false, @@ -102,7 +103,7 @@ pub trait Namada<'a>: Sized { wallet_alias_force: false, fee_amount: None, wrapper_fee_payer: None, - fee_token: self.native_token().await, + fee_token: self.native_token(), fee_unshield: None, gas_limit: GasLimit::from(20_000), expiration: None, @@ -117,7 +118,7 @@ pub trait Namada<'a>: Sized { } /// Make a TxTransfer builder from the given minimum set of arguments - async fn new_transfer( + fn new_transfer( &self, source: TransferSource, target: TransferTarget, @@ -130,24 +131,21 @@ pub trait Namada<'a>: Sized { token, amount, tx_code_path: PathBuf::from(TX_TRANSFER_WASM), - tx: self.tx_builder().await, - native_token: self.native_token().await, + tx: self.tx_builder(), + native_token: self.native_token(), } } /// Make a RevealPK builder from the given minimum set of arguments - async fn new_reveal_pk( - &self, - public_key: common::PublicKey, - ) -> args::RevealPk { + fn new_reveal_pk(&self, public_key: common::PublicKey) -> args::RevealPk { args::RevealPk { public_key, - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a Bond builder from the given minimum set of arguments - async fn new_bond( + fn new_bond( &self, validator: Address, amount: token::Amount, @@ -156,14 +154,14 @@ pub trait Namada<'a>: Sized { validator, amount, source: None, - tx: self.tx_builder().await, - native_token: self.native_token().await, + tx: self.tx_builder(), + native_token: self.native_token(), tx_code_path: PathBuf::from(TX_BOND_WASM), } } /// Make a Unbond builder from the given minimum set of arguments - async fn new_unbond( + fn new_unbond( &self, validator: Address, amount: token::Amount, @@ -172,13 +170,13 @@ pub trait Namada<'a>: Sized { validator, amount, source: None, - tx: self.tx_builder().await, + tx: self.tx_builder(), tx_code_path: PathBuf::from(TX_UNBOND_WASM), } } /// Make a TxIbcTransfer builder from the given minimum set of arguments - async fn new_ibc_transfer( + fn new_ibc_transfer( &self, source: Address, receiver: String, @@ -196,41 +194,38 @@ pub trait Namada<'a>: Sized { timeout_height: None, timeout_sec_offset: None, memo: None, - tx: self.tx_builder().await, + tx: self.tx_builder(), tx_code_path: PathBuf::from(TX_IBC_WASM), } } /// Make a InitProposal builder from the given minimum set of arguments - async fn new_init_proposal( - &self, - proposal_data: Vec, - ) -> args::InitProposal { + fn new_init_proposal(&self, proposal_data: Vec) -> args::InitProposal { args::InitProposal { proposal_data, - native_token: self.native_token().await, + native_token: self.native_token(), is_offline: false, is_pgf_stewards: false, is_pgf_funding: false, tx_code_path: PathBuf::from(TX_INIT_PROPOSAL), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a TxUpdateAccount builder from the given minimum set of arguments - async fn new_update_account(&self, addr: Address) -> args::TxUpdateAccount { + fn new_update_account(&self, addr: Address) -> args::TxUpdateAccount { args::TxUpdateAccount { addr, vp_code_path: None, public_keys: vec![], threshold: None, tx_code_path: PathBuf::from(TX_UPDATE_ACCOUNT_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a VoteProposal builder from the given minimum set of arguments - async fn new_vote_prposal( + fn new_vote_prposal( &self, vote: String, voter: Address, @@ -242,13 +237,13 @@ pub trait Namada<'a>: Sized { is_offline: false, proposal_data: None, tx_code_path: PathBuf::from(TX_VOTE_PROPOSAL), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a CommissionRateChange builder from the given minimum set of /// arguments - async fn new_change_commission_rate( + fn new_change_commission_rate( &self, rate: Dec, validator: Address, @@ -257,12 +252,12 @@ pub trait Namada<'a>: Sized { rate, validator, tx_code_path: PathBuf::from(TX_CHANGE_COMMISSION_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a TxInitValidator builder from the given minimum set of arguments - async fn new_init_validator( + fn new_init_validator( &self, commission_rate: Dec, max_commission_rate_change: Dec, @@ -280,34 +275,34 @@ pub trait Namada<'a>: Sized { validator_vp_code_path: PathBuf::from(VP_USER_WASM), unsafe_dont_encrypt: false, tx_code_path: PathBuf::from(TX_INIT_VALIDATOR_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a TxUnjailValidator builder from the given minimum set of arguments - async fn new_unjail_validator( + fn new_unjail_validator( &self, validator: Address, ) -> args::TxUnjailValidator { args::TxUnjailValidator { validator, tx_code_path: PathBuf::from(TX_UNJAIL_VALIDATOR_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a Withdraw builder from the given minimum set of arguments - async fn new_withdraw(&self, validator: Address) -> args::Withdraw { + fn new_withdraw(&self, validator: Address) -> args::Withdraw { args::Withdraw { validator, source: None, tx_code_path: PathBuf::from(TX_WITHDRAW_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a Withdraw builder from the given minimum set of arguments - async fn new_add_erc20_transfer( + fn new_add_erc20_transfer( &self, sender: Address, recipient: EthAddress, @@ -324,28 +319,25 @@ pub trait Namada<'a>: Sized { denom: NATIVE_MAX_DECIMAL_PLACES.into(), }), fee_payer: None, - fee_token: self.native_token().await, + fee_token: self.native_token(), nut: false, code_path: PathBuf::from(TX_BRIDGE_POOL_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a ResignSteward builder from the given minimum set of arguments - async fn new_resign_steward( - &self, - steward: Address, - ) -> args::ResignSteward { + fn new_resign_steward(&self, steward: Address) -> args::ResignSteward { args::ResignSteward { steward, - tx: self.tx_builder().await, + tx: self.tx_builder(), tx_code_path: PathBuf::from(TX_RESIGN_STEWARD), } } /// Make a UpdateStewardCommission builder from the given minimum set of /// arguments - async fn new_update_steward_rewards( + fn new_update_steward_rewards( &self, steward: Address, commission: Vec, @@ -353,16 +345,16 @@ pub trait Namada<'a>: Sized { args::UpdateStewardCommission { steward, commission, - tx: self.tx_builder().await, + tx: self.tx_builder(), tx_code_path: PathBuf::from(TX_UPDATE_STEWARD_COMMISSION), } } /// Make a TxCustom builder from the given minimum set of arguments - async fn new_custom(&self, owner: Address) -> args::TxCustom { + fn new_custom(&self, owner: Address) -> args::TxCustom { args::TxCustom { owner, - tx: self.tx_builder().await, + tx: self.tx_builder(), code_path: None, data_path: None, serialized_tx: None, @@ -405,13 +397,12 @@ where pub shielded: RwLock<&'a mut ShieldedContext>, /// Captures the input/output streams used by this object pub io: &'a I, + /// The address of the native token + native_token: Address, /// The default builder for a Tx prototype: args::Tx, } -/// The Namada token -pub const NAM: &str = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5"; - impl<'a, C, U, V, I> NamadaImpl<'a, C, U, V, I> where C: crate::ledger::queries::Client + Sync, @@ -419,18 +410,20 @@ where V: ShieldedUtils, I: Io, { - /// Construct a new Namada context - pub fn new( + /// Construct a new Namada context with the given native token address + pub fn native_new( client: &'a C, wallet: &'a mut Wallet, shielded: &'a mut ShieldedContext, io: &'a I, + native_token: Address, ) -> Self { - Self { + NamadaImpl { client, wallet: RwLock::new(wallet), shielded: RwLock::new(shielded), io, + native_token: native_token.clone(), prototype: args::Tx { dry_run: false, dry_run_wrapper: false, @@ -443,7 +436,7 @@ where wallet_alias_force: false, fee_amount: None, wrapper_fee_payer: None, - fee_token: Address::from_str(NAM).unwrap(), + fee_token: native_token, fee_unshield: None, gas_limit: GasLimit::from(20_000), expiration: None, @@ -457,6 +450,23 @@ where }, } } + + /// Construct a new Namada context looking up the native token address + pub async fn new( + client: &'a C, + wallet: &'a mut Wallet, + shielded: &'a mut ShieldedContext, + io: &'a I, + ) -> crate::sdk::error::Result> { + let native_token = query_native_token(client).await?; + Ok(NamadaImpl::native_new( + client, + wallet, + shielded, + io, + native_token, + )) + } } #[async_trait::async_trait(?Send)] @@ -473,12 +483,12 @@ where type WalletUtils = U; /// Obtain the prototypical Tx builder - async fn tx_builder(&self) -> args::Tx { + fn tx_builder(&self) -> args::Tx { self.prototype.clone() } - async fn native_token(&self) -> Address { - Address::from_str(NAM).unwrap() + fn native_token(&self) -> Address { + self.native_token.clone() } fn io(&self) -> &'a Self::Io { diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index a766846916..a9f272839f 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -43,6 +43,9 @@ router! {SHELL, // Epoch of the last committed block ( "epoch" ) -> Epoch = epoch, + // The address of the native token + ( "native_token" ) -> Address = native_token, + // Epoch of the input block height ( "epoch_at_height" / [height: BlockHeight]) -> Option = epoch_at_height, @@ -288,6 +291,15 @@ where Ok(data) } +fn native_token(ctx: RequestCtx<'_, D, H>) -> storage_api::Result
+where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let data = ctx.wl_storage.storage.native_token.clone(); + Ok(data) +} + fn epoch_at_height( ctx: RequestCtx<'_, D, H>, height: BlockHeight, diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs index 0853dc251b..d7556d6321 100644 --- a/shared/src/sdk/args.rs +++ b/shared/src/sdk/args.rs @@ -530,7 +530,7 @@ impl InitProposal { e.to_string(), ) })?; - let nam_address = context.native_token().await; + let nam_address = context.native_token(); let author_balance = rpc::get_token_balance( context.client(), &nam_address, @@ -558,7 +558,7 @@ impl InitProposal { e.to_string(), ) })?; - let nam_address = context.native_token().await; + let nam_address = context.native_token(); let author_balance = rpc::get_token_balance( context.client(), &nam_address, diff --git a/shared/src/sdk/rpc.rs b/shared/src/sdk/rpc.rs index eb8ebd8a11..e7da6dcbae 100644 --- a/shared/src/sdk/rpc.rs +++ b/shared/src/sdk/rpc.rs @@ -103,6 +103,13 @@ pub async fn query_epoch( convert_response::(RPC.shell().epoch(client).await) } +/// Query the address of the native token +pub async fn query_native_token( + client: &C, +) -> Result { + convert_response::(RPC.shell().native_token(client).await) +} + /// Query the epoch of the given block height, if it exists. /// Will return none if the input block height is greater than /// the latest committed block height. diff --git a/shared/src/types/io.rs b/shared/src/types/io.rs index f100ca8433..248f6f91d9 100644 --- a/shared/src/types/io.rs +++ b/shared/src/types/io.rs @@ -2,28 +2,26 @@ //! generic IO. The defaults are the obvious Rust native //! functions. -/// Rust native I/O handling. -pub struct StdIo; - +/// A trait that abstracts out I/O operations #[async_trait::async_trait(?Send)] -impl Io for StdIo {} - -#[async_trait::async_trait(?Send)] -#[allow(missing_docs)] pub trait Io { + /// Print the given string fn print(&self, output: impl AsRef) { print!("{}", output.as_ref()); } + /// Flush the output fn flush(&self) { use std::io::Write; std::io::stdout().flush().unwrap(); } + /// Print the given string with a newline fn println(&self, output: impl AsRef) { println!("{}", output.as_ref()); } + /// Print the given string into the given Writer fn write( &self, mut writer: W, @@ -32,6 +30,7 @@ pub trait Io { write!(writer, "{}", output.as_ref()) } + /// Print the given string into the given Writer and terminate with newline fn writeln( &self, mut writer: W, @@ -40,10 +39,12 @@ pub trait Io { writeln!(writer, "{}", output.as_ref()) } + /// Print the given error string fn eprintln(&self, output: impl AsRef) { eprintln!("{}", output.as_ref()); } + /// Read a string from input async fn read(&self) -> std::io::Result { #[cfg(not(target_family = "wasm"))] { @@ -55,6 +56,7 @@ pub trait Io { } } + /// Display the given prompt and return the string input async fn prompt(&self, question: impl AsRef) -> String { #[cfg(not(target_family = "wasm"))] { @@ -76,6 +78,50 @@ pub trait Io { } } +/// Rust native I/O handling. +pub struct StdIo; + +#[async_trait::async_trait(?Send)] +impl Io for StdIo {} + +/// Ignores all I/O operations. +pub struct NullIo; + +#[async_trait::async_trait(?Send)] +impl Io for NullIo { + fn print(&self, _output: impl AsRef) {} + + fn flush(&self) {} + + fn println(&self, _output: impl AsRef) {} + + fn write( + &self, + mut _writer: W, + _output: impl AsRef, + ) -> std::io::Result<()> { + Ok(()) + } + + fn writeln( + &self, + mut _writer: W, + _output: impl AsRef, + ) -> std::io::Result<()> { + Ok(()) + } + + fn eprintln(&self, _output: impl AsRef) {} + + async fn read(&self) -> std::io::Result { + panic!("Unsupported operation") + } + + async fn prompt(&self, _question: impl AsRef) -> String { + panic!("Unsupported operation") + } +} + /// A generic function for displaying a prompt to users and reading /// in their response. #[cfg(not(target_family = "wasm"))] From 17c3e6cdb767ed907e8519b75ab6eff536e86288 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 11 Oct 2023 10:05:02 +0200 Subject: [PATCH 12/14] Separated an SDK crate out of the shared crate. --- Cargo.lock | 51 ++ Cargo.toml | 1 + apps/Cargo.toml | 3 +- apps/src/lib/cli.rs | 2 +- apps/src/lib/cli/api.rs | 4 +- apps/src/lib/cli/client.rs | 5 +- apps/src/lib/cli/context.rs | 8 +- apps/src/lib/cli/relayer.rs | 2 +- apps/src/lib/cli/wallet.rs | 12 +- apps/src/lib/client/rpc.rs | 66 +- apps/src/lib/client/tx.rs | 7 +- apps/src/lib/client/utils.rs | 2 +- apps/src/lib/config/genesis.rs | 8 +- .../lib/node/ledger/ethereum_oracle/mod.rs | 2 +- .../lib/node/ledger/shell/finalize_block.rs | 2 +- apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 2 +- .../lib/node/ledger/shell/process_proposal.rs | 2 +- apps/src/lib/node/ledger/shell/queries.rs | 9 +- .../src/lib/node/ledger/shell/testing/node.rs | 10 +- .../lib/node/ledger/shell/vote_extensions.rs | 2 +- .../shell/vote_extensions/bridge_pool_vext.rs | 4 +- .../shell/vote_extensions/eth_events.rs | 2 +- .../shell/vote_extensions/val_set_update.rs | 2 +- apps/src/lib/wallet/cli_utils.rs | 4 +- apps/src/lib/wallet/defaults.rs | 8 +- apps/src/lib/wallet/mod.rs | 16 +- apps/src/lib/wallet/pre_genesis.rs | 6 +- apps/src/lib/wallet/store.rs | 10 +- benches/Cargo.toml | 1 + benches/lib.rs | 23 +- sdk/Cargo.toml | 121 +++ {shared/src/sdk => sdk/src}/args.rs | 133 +-- .../src/control_flow/mod.rs | 0 .../types => sdk/src}/control_flow/time.rs | 0 {shared/src/sdk => sdk/src}/error.rs | 6 +- .../src}/eth_bridge/bridge_pool.rs | 55 +- .../src/eth_bridge/mod.rs | 9 +- .../src}/eth_bridge/validator_set.rs | 18 +- {shared/src/ledger => sdk/src}/events/log.rs | 7 +- .../src}/events/log/dumb_queries.rs | 9 +- .../ledger/events.rs => sdk/src/events/mod.rs | 20 +- {shared/src/types => sdk/src}/io.rs | 0 sdk/src/lib.rs | 560 ++++++++++++ {shared/src/sdk => sdk/src}/masp.rs | 38 +- .../sdk/queries.rs => sdk/src/queries/mod.rs | 205 ++++- .../src/ledger => sdk/src}/queries/router.rs | 101 +-- .../src/ledger => sdk/src}/queries/shell.rs | 315 ++----- .../src}/queries/shell/eth_bridge.rs | 88 +- .../src/ledger => sdk/src}/queries/types.rs | 29 +- .../src}/queries/vp/governance.rs | 20 +- .../src/ledger => sdk/src}/queries/vp/mod.rs | 0 .../src/ledger => sdk/src}/queries/vp/pgf.rs | 24 +- .../src/ledger => sdk/src}/queries/vp/pos.rs | 107 +-- .../ledger => sdk/src}/queries/vp/token.rs | 13 +- {shared/src/sdk => sdk/src}/rpc.rs | 131 ++- {shared/src/sdk => sdk/src}/signing.rs | 43 +- {shared/src/sdk => sdk/src}/tx.rs | 86 +- {shared/src/sdk => sdk/src}/wallet/alias.rs | 0 .../sdk => sdk/src}/wallet/derivation_path.rs | 6 +- {shared/src/sdk => sdk/src}/wallet/keys.rs | 2 +- {shared/src/sdk => sdk/src}/wallet/mod.rs | 10 +- .../src/sdk => sdk/src}/wallet/pre_genesis.rs | 6 +- {shared/src/sdk => sdk/src}/wallet/store.rs | 14 +- shared/Cargo.toml | 16 +- shared/src/ledger/governance/utils.rs | 11 + shared/src/ledger/mod.rs | 849 +++++++----------- shared/src/ledger/queries/mod.rs | 222 ----- shared/src/lib.rs | 2 +- shared/src/sdk/mod.rs | 12 - shared/src/types/mod.rs | 4 +- shared/src/vm/host_env.rs | 2 +- tests/Cargo.toml | 1 + tests/src/e2e/ledger_tests.rs | 2 +- tests/src/integration/masp.rs | 2 +- tests/src/native_vp/eth_bridge_pool.rs | 8 +- wasm/Cargo.lock | 45 + wasm_for_tests/wasm_source/Cargo.lock | 45 + 78 files changed, 2003 insertions(+), 1672 deletions(-) create mode 100644 sdk/Cargo.toml rename {shared/src/sdk => sdk/src}/args.rs (95%) rename shared/src/types/control_flow.rs => sdk/src/control_flow/mod.rs (100%) rename {shared/src/types => sdk/src}/control_flow/time.rs (100%) rename {shared/src/sdk => sdk/src}/error.rs (97%) rename {shared/src/ledger => sdk/src}/eth_bridge/bridge_pool.rs (97%) rename shared/src/ledger/eth_bridge.rs => sdk/src/eth_bridge/mod.rs (95%) rename {shared/src/ledger => sdk/src}/eth_bridge/validator_set.rs (98%) rename {shared/src/ledger => sdk/src}/events/log.rs (97%) rename {shared/src/ledger => sdk/src}/events/log/dumb_queries.rs (96%) rename shared/src/ledger/events.rs => sdk/src/events/mod.rs (94%) rename {shared/src/types => sdk/src}/io.rs (100%) create mode 100644 sdk/src/lib.rs rename {shared/src/sdk => sdk/src}/masp.rs (99%) rename shared/src/sdk/queries.rs => sdk/src/queries/mod.rs (59%) rename {shared/src/ledger => sdk/src}/queries/router.rs (92%) rename {shared/src/ledger => sdk/src}/queries/shell.rs (58%) rename {shared/src/ledger => sdk/src}/queries/shell/eth_bridge.rs (96%) rename {shared/src/ledger => sdk/src}/queries/types.rs (88%) rename {shared/src/ledger => sdk/src}/queries/vp/governance.rs (75%) rename {shared/src/ledger => sdk/src}/queries/vp/mod.rs (100%) rename {shared/src/ledger => sdk/src}/queries/vp/pgf.rs (76%) rename {shared/src/ledger => sdk/src}/queries/vp/pos.rs (90%) rename {shared/src/ledger => sdk/src}/queries/vp/token.rs (87%) rename {shared/src/sdk => sdk/src}/rpc.rs (89%) rename {shared/src/sdk => sdk/src}/signing.rs (98%) rename {shared/src/sdk => sdk/src}/tx.rs (96%) rename {shared/src/sdk => sdk/src}/wallet/alias.rs (100%) rename {shared/src/sdk => sdk/src}/wallet/derivation_path.rs (98%) rename {shared/src/sdk => sdk/src}/wallet/keys.rs (99%) rename {shared/src/sdk => sdk/src}/wallet/mod.rs (99%) rename {shared/src/sdk => sdk/src}/wallet/pre_genesis.rs (96%) rename {shared/src/sdk => sdk/src}/wallet/store.rs (99%) delete mode 100644 shared/src/ledger/queries/mod.rs delete mode 100644 shared/src/sdk/mod.rs diff --git a/Cargo.lock b/Cargo.lock index d8303e37f0..63ae872e56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4036,6 +4036,7 @@ dependencies = [ "namada_core", "namada_ethereum_bridge", "namada_proof_of_stake", + "namada_sdk", "namada_test_utils", "num256", "orion", @@ -4117,6 +4118,7 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada", + "namada_sdk", "namada_test_utils", "num-derive", "num-rational 0.4.1", @@ -4176,6 +4178,7 @@ dependencies = [ "masp_proofs", "namada", "namada_apps", + "namada_sdk", "namada_test_utils", "prost", "rand 0.8.5", @@ -4306,6 +4309,53 @@ dependencies = [ "tracing-subscriber 0.3.17", ] +[[package]] +name = "namada_sdk" +version = "0.23.0" +dependencies = [ + "assert_matches", + "async-trait", + "bimap", + "borsh 0.9.4", + "circular-queue", + "data-encoding", + "derivation-path", + "ethbridge-bridge-contract", + "ethers", + "fd-lock", + "futures", + "itertools", + "masp_primitives", + "masp_proofs", + "namada_core", + "namada_ethereum_bridge", + "namada_proof_of_stake", + "namada_test_utils", + "num256", + "orion", + "owo-colors 3.5.0", + "parse_duration", + "paste", + "prost", + "rand 0.8.5", + "rand_core 0.6.4", + "ripemd", + "serde 1.0.163", + "serde_json", + "sha2 0.9.9", + "slip10_ed25519", + "tempfile", + "tendermint-rpc", + "thiserror", + "tiny-bip39", + "tiny-hderive", + "tokio", + "toml 0.5.9", + "tracing 0.1.37", + "wasmtimer", + "zeroize", +] + [[package]] name = "namada_test_utils" version = "0.23.0" @@ -4339,6 +4389,7 @@ dependencies = [ "namada", "namada_apps", "namada_core", + "namada_sdk", "namada_test_utils", "namada_tx_prelude", "namada_vp_prelude", diff --git a/Cargo.toml b/Cargo.toml index 9c731f0fdf..813cf99d9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "macros", "vp_prelude", "encoding_spec", + "sdk", ] # wasm packages have to be built separately diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 1d33f55df7..02bcf60373 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -55,7 +55,7 @@ mainnet = [ "namada/mainnet", ] dev = ["namada/dev"] -std = ["ed25519-consensus/std", "rand/std", "rand_core/std", "namada/std"] +std = ["ed25519-consensus/std", "rand/std", "rand_core/std", "namada/std", "namada_sdk/std"] # for integration tests and test utilies testing = ["dev"] @@ -67,6 +67,7 @@ abciplus = [ [dependencies] namada = {path = "../shared", features = ["ferveo-tpke", "masp-tx-gen", "multicore", "http-client"]} +namada_sdk = {path = "../sdk", default-features = false, features = ["wasm-runtime", "masp-tx-gen"]} ark-serialize.workspace = true ark-std.workspace = true arse-merkle-tree = { workspace = true, features = ["blake2b"] } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index ce5bf600b5..421ada0e69 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2517,7 +2517,6 @@ pub mod args { use std::str::FromStr; use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; - pub use namada::sdk::args::*; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; use namada::types::dec::Dec; @@ -2530,6 +2529,7 @@ pub mod args { use namada::types::token; use namada::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada::types::transaction::GasLimit; + pub use namada_sdk::args::*; use super::context::*; use super::utils::*; diff --git a/apps/src/lib/cli/api.rs b/apps/src/lib/cli/api.rs index 1b6851f3a9..79c8be3fa9 100644 --- a/apps/src/lib/cli/api.rs +++ b/apps/src/lib/cli/api.rs @@ -1,8 +1,8 @@ -use namada::sdk::queries::Client; -use namada::sdk::rpc::wait_until_node_is_synched; use namada::tendermint_rpc::HttpClient; use namada::types::control_flow::Halt; use namada::types::io::Io; +use namada_sdk::queries::Client; +use namada_sdk::rpc::wait_until_node_is_synched; use tendermint_config::net::Address as TendermintAddress; use crate::client::utils; diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index a342e9ef25..977442b9cb 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -1,9 +1,8 @@ use color_eyre::eyre::{eyre, Report, Result}; -use namada::ledger::{Namada, NamadaImpl}; -use namada::sdk::signing; -use namada::sdk::tx::dump_tx; use namada::types::control_flow::ProceedOrElse; use namada::types::io::Io; +use namada_sdk::tx::dump_tx; +use namada_sdk::{signing, Namada, NamadaImpl}; use crate::cli; use crate::cli::api::{CliApi, CliClient}; diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index f6c3399baf..a65d5c1830 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -6,16 +6,16 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use color_eyre::eyre::Result; -use namada::ledger::{Namada, NamadaImpl}; -use namada::sdk::masp::fs::FsShieldedUtils; -use namada::sdk::masp::ShieldedContext; -use namada::sdk::wallet::Wallet; use namada::types::address::{Address, InternalAddress}; use namada::types::chain::ChainId; use namada::types::ethereum_events::EthAddress; use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::*; +use namada_sdk::masp::fs::FsShieldedUtils; +use namada_sdk::masp::ShieldedContext; +use namada_sdk::wallet::Wallet; +use namada_sdk::{Namada, NamadaImpl}; use super::args; #[cfg(any(test, feature = "dev"))] diff --git a/apps/src/lib/cli/relayer.rs b/apps/src/lib/cli/relayer.rs index aadf2d3bda..497c69c819 100644 --- a/apps/src/lib/cli/relayer.rs +++ b/apps/src/lib/cli/relayer.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use color_eyre::eyre::{eyre, Report, Result}; use namada::eth_bridge::ethers::providers::{Http, Provider}; -use namada::ledger::eth_bridge::{bridge_pool, validator_set}; use namada::types::control_flow::ProceedOrElse; use namada::types::io::Io; +use namada_sdk::eth_bridge::{bridge_pool, validator_set}; use crate::cli; use crate::cli::api::{CliApi, CliClient}; diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 6247145b84..247835f46b 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -7,15 +7,15 @@ use borsh::BorshSerialize; use color_eyre::eyre::Result; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; -use namada::sdk::masp::find_valid_diversifier; -use namada::sdk::wallet::{ - DecryptionError, FindKeyError, GenRestoreKeyError, Wallet, WalletIo, - WalletStorage, -}; use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; -use namada::{display, display_line, edisplay_line}; +use namada_sdk::masp::find_valid_diversifier; +use namada_sdk::wallet::{ + DecryptionError, FindKeyError, GenRestoreKeyError, Wallet, WalletIo, + WalletStorage, +}; +use namada_sdk::{display, display_line, edisplay_line}; use rand::RngCore; use rand_core::OsRng; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 24b7708f42..d5dc2f23a3 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -33,16 +33,7 @@ use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::{CommissionPair, PosParams, Slash}; use namada::ledger::queries::RPC; use namada::ledger::storage::ConversionState; -use namada::ledger::Namada; use namada::proof_of_stake::types::{ValidatorState, WeightedValidator}; -use namada::sdk::error; -use namada::sdk::error::{is_pinned_error, Error, PinnedBalanceError}; -use namada::sdk::masp::{Conversions, MaspAmount, MaspChange}; -use namada::sdk::rpc::{ - self, enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, - TxResponse, -}; -use namada::sdk::wallet::AddressVpType; use namada::types::address::{masp, Address}; use namada::types::control_flow::ProceedOrElse; use namada::types::hash::Hash; @@ -52,7 +43,14 @@ use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{BlockHeight, BlockResults, Epoch, Key, KeySeg}; use namada::types::token::{Change, MaspDenom}; use namada::types::{storage, token}; -use namada::{display, display_line, edisplay_line, prompt}; +use namada_sdk::error::{is_pinned_error, Error, PinnedBalanceError}; +use namada_sdk::masp::{Conversions, MaspAmount, MaspChange}; +use namada_sdk::rpc::{ + self, enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, + TxResponse, +}; +use namada_sdk::wallet::AddressVpType; +use namada_sdk::{display, display_line, edisplay_line, error, prompt, Namada}; use tokio::time::Instant; use crate::cli::{self, args}; @@ -65,7 +63,7 @@ use crate::facade::tendermint_rpc::error::Error as TError; /// error. pub async fn query_tx_status<'a>( namada: &impl Namada<'a>, - status: namada::sdk::rpc::TxEventQuery<'_>, + status: namada_sdk::rpc::TxEventQuery<'_>, deadline: Instant, ) -> Event { rpc::query_tx_status(namada, status, deadline) @@ -82,7 +80,7 @@ pub async fn query_and_print_epoch<'a>(context: &impl Namada<'a>) -> Epoch { /// Query the last committed block pub async fn query_block<'a>(context: &impl Namada<'a>) { - let block = namada::sdk::rpc::query_block(context.client()) + let block = namada_sdk::rpc::query_block(context.client()) .await .unwrap(); match block { @@ -677,7 +675,7 @@ pub async fn query_proposal_by_id( client: &C, proposal_id: u64, ) -> Result, error::Error> { - namada::sdk::rpc::query_proposal_by_id(client, proposal_id).await + namada_sdk::rpc::query_proposal_by_id(client, proposal_id).await } /// Query token shielded balance(s) @@ -1003,7 +1001,7 @@ pub async fn get_token_balance( token: &Address, owner: &Address, ) -> token::Amount { - namada::sdk::rpc::get_token_balance(client, token, owner) + namada_sdk::rpc::get_token_balance(client, token, owner) .await .unwrap() } @@ -2083,7 +2081,7 @@ pub async fn is_validator( client: &C, address: &Address, ) -> bool { - namada::sdk::rpc::is_validator(client, address) + namada_sdk::rpc::is_validator(client, address) .await .unwrap() } @@ -2093,7 +2091,7 @@ pub async fn is_delegator( client: &C, address: &Address, ) -> bool { - namada::sdk::rpc::is_delegator(client, address) + namada_sdk::rpc::is_delegator(client, address) .await .unwrap() } @@ -2103,7 +2101,7 @@ pub async fn is_delegator_at( address: &Address, epoch: Epoch, ) -> bool { - namada::sdk::rpc::is_delegator_at(client, address, epoch) + namada_sdk::rpc::is_delegator_at(client, address, epoch) .await .unwrap() } @@ -2115,7 +2113,7 @@ pub async fn known_address( client: &C, address: &Address, ) -> bool { - namada::sdk::rpc::known_address(client, address) + namada_sdk::rpc::known_address(client, address) .await .unwrap() } @@ -2202,7 +2200,7 @@ pub async fn query_conversion( masp_primitives::transaction::components::I32Sum, MerklePath, )> { - namada::sdk::rpc::query_conversion(client, asset_type).await + namada_sdk::rpc::query_conversion(client, asset_type).await } /// Query a wasm code hash @@ -2221,7 +2219,7 @@ pub async fn query_storage_value( where T: BorshDeserialize, { - namada::sdk::rpc::query_storage_value(client, key).await + namada_sdk::rpc::query_storage_value(client, key).await } /// Query a storage value and the proof without decoding. @@ -2233,7 +2231,7 @@ pub async fn query_storage_value_bytes< height: Option, prove: bool, ) -> (Option>, Option) { - namada::sdk::rpc::query_storage_value_bytes(client, key, height, prove) + namada_sdk::rpc::query_storage_value_bytes(client, key, height, prove) .await .unwrap() } @@ -2258,7 +2256,7 @@ pub async fn query_has_storage_key< client: &C, key: &storage::Key, ) -> bool { - namada::sdk::rpc::query_has_storage_key(client, key) + namada_sdk::rpc::query_has_storage_key(client, key) .await .unwrap() } @@ -2267,21 +2265,21 @@ pub async fn query_has_storage_key< /// the current status of a transation. pub async fn query_tx_events( client: &C, - tx_event_query: namada::sdk::rpc::TxEventQuery<'_>, + tx_event_query: namada_sdk::rpc::TxEventQuery<'_>, ) -> std::result::Result< Option, ::Error, > { - namada::sdk::rpc::query_tx_events(client, tx_event_query).await + namada_sdk::rpc::query_tx_events(client, tx_event_query).await } /// Lookup the full response accompanying the specified transaction event // TODO: maybe remove this in favor of `query_tx_status` pub async fn query_tx_response( client: &C, - tx_query: namada::sdk::rpc::TxEventQuery<'_>, + tx_query: namada_sdk::rpc::TxEventQuery<'_>, ) -> Result { - namada::sdk::rpc::query_tx_response(client, tx_query).await + namada_sdk::rpc::query_tx_response(client, tx_query).await } /// Lookup the results of applying the specified transaction to the @@ -2293,7 +2291,7 @@ pub async fn query_result<'a>( // First try looking up application event pertaining to given hash. let tx_response = query_tx_response( context.client(), - namada::sdk::rpc::TxEventQuery::Applied(&args.tx_hash), + namada_sdk::rpc::TxEventQuery::Applied(&args.tx_hash), ) .await; match tx_response { @@ -2308,7 +2306,7 @@ pub async fn query_result<'a>( // If this fails then instead look for an acceptance event. let tx_response = query_tx_response( context.client(), - namada::sdk::rpc::TxEventQuery::Accepted(&args.tx_hash), + namada_sdk::rpc::TxEventQuery::Accepted(&args.tx_hash), ) .await; match tx_response { @@ -2359,7 +2357,7 @@ pub async fn get_all_validators( client: &C, epoch: Epoch, ) -> HashSet
{ - namada::sdk::rpc::get_all_validators(client, epoch) + namada_sdk::rpc::get_all_validators(client, epoch) .await .unwrap() } @@ -2370,7 +2368,7 @@ pub async fn get_total_staked_tokens< client: &C, epoch: Epoch, ) -> token::Amount { - namada::sdk::rpc::get_total_staked_tokens(client, epoch) + namada_sdk::rpc::get_total_staked_tokens(client, epoch) .await .unwrap() } @@ -2398,7 +2396,7 @@ pub async fn get_delegators_delegation< client: &C, address: &Address, ) -> HashSet
{ - namada::sdk::rpc::get_delegators_delegation(client, address) + namada_sdk::rpc::get_delegators_delegation(client, address) .await .unwrap() } @@ -2410,7 +2408,7 @@ pub async fn get_delegators_delegation_at< address: &Address, epoch: Epoch, ) -> HashMap { - namada::sdk::rpc::get_delegators_delegation_at(client, address, epoch) + namada_sdk::rpc::get_delegators_delegation_at(client, address, epoch) .await .unwrap() } @@ -2420,7 +2418,7 @@ pub async fn query_governance_parameters< >( client: &C, ) -> GovernanceParameters { - namada::sdk::rpc::query_governance_parameters(client).await + namada_sdk::rpc::query_governance_parameters(client).await } /// A helper to unwrap client's response. Will shut down process on error. @@ -2503,7 +2501,7 @@ pub async fn compute_proposal_votes< proposal_id: u64, epoch: Epoch, ) -> ProposalVotes { - let votes = namada::sdk::rpc::query_proposal_votes(client, proposal_id) + let votes = namada_sdk::rpc::query_proposal_votes(client, proposal_id) .await .unwrap(); diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 53b2232f64..1afefab825 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -6,17 +6,16 @@ use namada::core::ledger::governance::cli::offline::{ use namada::core::ledger::governance::cli::onchain::{ DefaultProposal, PgfFundingProposal, PgfStewardProposal, ProposalVote, }; -use namada::ledger::{pos, Namada}; +use namada::ledger::pos; use namada::proof_of_stake::parameters::PosParams; use namada::proto::Tx; -use namada::sdk::rpc::{TxBroadcastData, TxResponse}; -use namada::sdk::{error, signing, tx}; use namada::types::address::{Address, ImplicitAddress}; use namada::types::dec::Dec; use namada::types::io::Io; use namada::types::key::{self, *}; use namada::types::transaction::pos::InitValidator; -use namada::{display_line, edisplay_line}; +use namada_sdk::rpc::{TxBroadcastData, TxResponse}; +use namada_sdk::{display_line, edisplay_line, error, signing, tx, Namada}; use super::rpc; use crate::cli::{args, safe_exit}; diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index f508267db3..83fd499071 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -9,12 +9,12 @@ use borsh::BorshSerialize; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use namada::sdk::wallet::Wallet; use namada::types::address; use namada::types::chain::ChainId; use namada::types::dec::Dec; use namada::types::key::*; use namada::vm::validate_untrusted_wasm; +use namada_sdk::wallet::Wallet; use prost::bytes::Bytes; use rand::prelude::ThreadRng; use rand::thread_rng; diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index b7281fd4ce..222e7b4f1f 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -6,7 +6,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; use namada::core::ledger::governance::parameters::GovernanceParameters; use namada::core::ledger::pgf::parameters::PgfParameters; -use namada::ledger::eth_bridge::EthereumBridgeConfig; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::{Dec, GenesisValidator, PosParams}; use namada::types::address::Address; @@ -17,6 +16,7 @@ use namada::types::time::{DateTimeUtc, DurationSecs}; use namada::types::token::Denomination; use namada::types::uint::Uint; use namada::types::{storage, token}; +use namada_sdk::eth_bridge::EthereumBridgeConfig; /// Genesis configuration file format pub mod genesis_config { @@ -900,14 +900,14 @@ pub fn genesis( } #[cfg(any(test, feature = "dev"))] pub fn genesis(num_validators: u64) -> Genesis { - use namada::ledger::eth_bridge::{ - Contracts, Erc20WhitelistEntry, UpgradeableContract, - }; use namada::types::address::{ self, apfel, btc, dot, eth, kartoffel, nam, schnitzel, wnam, }; use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; use namada::types::ethereum_events::EthAddress; + use namada_sdk::eth_bridge::{ + Contracts, Erc20WhitelistEntry, UpgradeableContract, + }; use crate::wallet; diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 6980778c07..300ea85347 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -12,9 +12,9 @@ use namada::core::types::ethereum_structs; use namada::eth_bridge::ethers; use namada::eth_bridge::ethers::providers::{Http, Middleware, Provider}; use namada::eth_bridge::oracle::config::Config; -use namada::ledger::eth_bridge::{eth_syncing_status_timeout, SyncStatus}; use namada::types::control_flow::time::{Constant, Duration, Instant, Sleep}; use namada::types::ethereum_events::EthereumEvent; +use namada_sdk::eth_bridge::{eth_syncing_status_timeout, SyncStatus}; use num256::Uint256; use thiserror::Error; use tokio::sync::mpsc::error::TryRecvError; diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 53252065f1..9a32f6dd60 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1049,7 +1049,6 @@ mod test_finalize_block { self, get_key_from_hash, get_nonce_key, get_signed_root_key, }; use namada::eth_bridge::storage::min_confirmations_key; - use namada::ledger::eth_bridge::MinimumConfirmations; use namada::ledger::gas::VpGasMeter; use namada::ledger::native_vp::parameters::ParametersVp; use namada::ledger::native_vp::NativeVp; @@ -1087,6 +1086,7 @@ mod test_finalize_block { use namada::types::transaction::{Fee, WrapperTx}; use namada::types::uint::Uint; use namada::types::vote_extensions::ethereum_events; + use namada_sdk::eth_bridge::MinimumConfirmations; use namada_test_utils::TestWasms; use test_log::test; diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index d6b2efe4dd..6faf48f2d1 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use std::hash::Hash; -use namada::ledger::eth_bridge::EthBridgeStatus; use namada::ledger::parameters::{self, Parameters}; use namada::ledger::pos::{staking_token_address, PosParams}; use namada::ledger::storage::traits::StorageHasher; @@ -17,6 +16,7 @@ use namada::types::hash::Hash as CodeHash; use namada::types::key::*; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::vm::validate_untrusted_wasm; +use namada_sdk::eth_bridge::EthBridgeStatus; use super::*; use crate::facade::tendermint_proto::google::protobuf; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a1c17fe450..eecf6aea06 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -30,7 +30,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::transaction::Transaction; use namada::core::hints; use namada::core::ledger::eth_bridge; -use namada::ledger::eth_bridge::{EthBridgeQueries, EthereumOracleConfig}; use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::{Gas, TxGasMeter}; @@ -66,6 +65,7 @@ use namada::types::transaction::{ use namada::types::{address, hash, token}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::{WasmCacheAccess, WasmCacheRwAccess}; +use namada_sdk::eth_bridge::{EthBridgeQueries, EthereumOracleConfig}; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use thiserror::Error; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ab544de3f8..e5b1e94e13 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -4,7 +4,6 @@ use data_encoding::HEXUPPER; use namada::core::hints; use namada::core::ledger::storage::WlStorage; -use namada::ledger::eth_bridge::{EthBridgeQueries, SendValsetUpd}; use namada::ledger::pos::PosQueries; use namada::ledger::protocol::get_fee_unshielding_transaction; use namada::ledger::storage::TempWlStorage; @@ -15,6 +14,7 @@ use namada::types::transaction::protocol::{ }; #[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; +use namada_sdk::eth_bridge::{EthBridgeQueries, SendValsetUpd}; use super::block_alloc::{BlockSpace, EncryptedTxsBins}; use super::*; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index a62c3ec4b4..f1e56c295f 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -2,6 +2,7 @@ use borsh::BorshSerialize; use ferveo_common::TendermintValidator; +use namada::ledger::dry_run_tx; use namada::ledger::pos::into_tm_voting_power; use namada::ledger::queries::{RequestCtx, ResponseQuery}; use namada::ledger::storage_api::token; @@ -49,7 +50,11 @@ where }; // Invoke the root RPC handler - returns borsh-encoded data on success - let result = namada::ledger::queries::handle_path(ctx, &request); + let result = if request.path == "/shell/dry_run_tx" { + dry_run_tx(ctx, &request) + } else { + namada::ledger::queries::handle_path(ctx, &request) + }; match result { Ok(ResponseQuery { data, info, proof }) => response::Query { value: data, @@ -137,10 +142,10 @@ where #[cfg(not(feature = "abcipp"))] mod test_queries { use namada::core::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; - use namada::ledger::eth_bridge::{EthBridgeQueries, SendValsetUpd}; use namada::ledger::pos::PosQueries; use namada::proof_of_stake::types::WeightedValidator; use namada::types::storage::Epoch; + use namada_sdk::eth_bridge::{EthBridgeQueries, SendValsetUpd}; use super::*; use crate::facade::tendermint_proto::abci::VoteInfo; diff --git a/apps/src/lib/node/ledger/shell/testing/node.rs b/apps/src/lib/node/ledger/shell/testing/node.rs index 034ac80845..7ebf3ce0b8 100644 --- a/apps/src/lib/node/ledger/shell/testing/node.rs +++ b/apps/src/lib/node/ledger/shell/testing/node.rs @@ -6,6 +6,7 @@ use std::sync::{Arc, Mutex}; use color_eyre::eyre::{Report, Result}; use data_encoding::HEXUPPER; use lazy_static::lazy_static; +use namada::ledger::dry_run_tx; use namada::ledger::events::log::dumb_queries; use namada::ledger::queries::{ EncodedResponseQuery, RequestCtx, RequestQuery, Router, RPC, @@ -19,7 +20,6 @@ use namada::proof_of_stake::{ read_consensus_validator_set_addresses_with_stake, validator_consensus_key_handle, }; -use namada::sdk::queries::Client; use namada::tendermint_proto::abci::VoteInfo; use namada::tendermint_rpc::endpoint::abci_info; use namada::tendermint_rpc::SimpleRequest; @@ -27,6 +27,7 @@ use namada::types::hash::Hash; use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::{BlockHash, BlockHeight, Epoch, Header}; use namada::types::time::DateTimeUtc; +use namada_sdk::queries::Client; use num_traits::cast::FromPrimitive; use regex::Regex; use tokio::sync::mpsc::UnboundedReceiver; @@ -352,7 +353,12 @@ impl<'a> Client for &'a MockNode { tx_wasm_cache: borrowed.tx_wasm_cache.read_only(), storage_read_past_height_limit: None, }; - rpc.handle(ctx, &request).map_err(Report::new) + if request.path == "/shell/dry_run_tx" { + dry_run_tx(ctx, &request) + } else { + rpc.handle(ctx, &request) + } + .map_err(Report::new) } async fn perform( diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 658c35a121..e7e19bf96d 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -4,7 +4,6 @@ pub mod bridge_pool_vext; pub mod eth_events; pub mod val_set_update; -use namada::ledger::eth_bridge::{EthBridgeQueries, SendValsetUpd}; #[cfg(feature = "abcipp")] use namada::ledger::pos::PosQueries; use namada::proto::{SignableEthMessage, Signed}; @@ -15,6 +14,7 @@ use namada::types::vote_extensions::VoteExtensionDigest; use namada::types::vote_extensions::{ bridge_pool_roots, ethereum_events, validator_set_update, VoteExtension, }; +use namada_sdk::eth_bridge::{EthBridgeQueries, SendValsetUpd}; use super::*; #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs b/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs index 002bd18904..201c96983f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs @@ -273,8 +273,6 @@ mod test_bp_vote_extensions { use borsh::BorshSerialize; #[cfg(not(feature = "abcipp"))] use namada::core::ledger::eth_bridge::storage::bridge_pool::get_key_from_hash; - #[cfg(not(feature = "abcipp"))] - use namada::ledger::eth_bridge::EthBridgeQueries; use namada::ledger::pos::PosQueries; use namada::ledger::storage_api::StorageWrite; use namada::proof_of_stake::types::{ @@ -297,6 +295,8 @@ mod test_bp_vote_extensions { use namada::types::vote_extensions::bridge_pool_roots; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::VoteExtension; + #[cfg(not(feature = "abcipp"))] + use namada_sdk::eth_bridge::EthBridgeQueries; #[cfg(feature = "abcipp")] use tendermint_proto_abcipp::abci::response_verify_vote_extension::VerifyStatus; #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 0dd85bfd70..e7dbdc255b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -2,7 +2,6 @@ use std::collections::{BTreeMap, HashMap}; -use namada::ledger::eth_bridge::EthBridgeQueries; use namada::ledger::pos::PosQueries; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; @@ -15,6 +14,7 @@ use namada::types::vote_extensions::ethereum_events::{ }; #[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; +use namada_sdk::eth_bridge::EthBridgeQueries; use super::*; use crate::node::ledger::shell::{Shell, ShellMode}; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 03843b4717..4888c10be1 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -311,7 +311,6 @@ mod test_vote_extensions { use namada::core::ledger::storage_api::collections::lazy_map::{ NestedSubKey, SubKey, }; - use namada::ledger::eth_bridge::EthBridgeQueries; use namada::ledger::pos::PosQueries; use namada::proof_of_stake::types::WeightedValidator; use namada::proof_of_stake::{ @@ -337,6 +336,7 @@ mod test_vote_extensions { use namada::types::vote_extensions::validator_set_update; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::VoteExtension; + use namada_sdk::eth_bridge::EthBridgeQueries; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; diff --git a/apps/src/lib/wallet/cli_utils.rs b/apps/src/lib/wallet/cli_utils.rs index ada1b16684..46349167ef 100644 --- a/apps/src/lib/wallet/cli_utils.rs +++ b/apps/src/lib/wallet/cli_utils.rs @@ -4,10 +4,10 @@ use std::io::{self, Write}; use borsh::BorshSerialize; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; -use namada::sdk::masp::find_valid_diversifier; -use namada::sdk::wallet::{DecryptionError, FindKeyError, GenRestoreKeyError}; use namada::types::key::{PublicKeyHash, RefTo}; use namada::types::masp::{MaspValue, PaymentAddress}; +use namada_sdk::masp::find_valid_diversifier; +use namada_sdk::wallet::{DecryptionError, FindKeyError, GenRestoreKeyError}; use rand_core::OsRng; use crate::cli; diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index 00b0f49d26..82a9524daa 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -8,10 +8,10 @@ pub use dev::{ validator_keys, }; use namada::core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; -use namada::ledger::{eth_bridge, governance, pgf, pos}; -use namada::sdk::wallet::alias::Alias; +use namada::ledger::{governance, pgf, pos}; use namada::types::address::Address; use namada::types::key::*; +use namada_sdk::wallet::alias::Alias; use crate::config::genesis::genesis_config::GenesisConfig; @@ -22,7 +22,7 @@ pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { ("pos".into(), pos::ADDRESS), ("pos_slash_pool".into(), pos::SLASH_POOL_ADDRESS), ("governance".into(), governance::ADDRESS), - ("eth_bridge".into(), eth_bridge::ADDRESS), + ("eth_bridge".into(), namada_sdk::eth_bridge::ADDRESS), ("bridge_pool".into(), BRIDGE_POOL_ADDRESS), ("pgf".into(), pgf::ADDRESS), ]; @@ -78,12 +78,12 @@ mod dev { use borsh::BorshDeserialize; use namada::ledger::{governance, pgf, pos}; - use namada::sdk::wallet::alias::Alias; use namada::types::address::{ apfel, btc, dot, eth, kartoffel, nam, schnitzel, Address, }; use namada::types::key::dkg_session_keys::DkgKeypair; use namada::types::key::*; + use namada_sdk::wallet::alias::Alias; /// Generate a new protocol signing keypair, eth hot key and DKG session /// keypair diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 61c49fe580..18818daef5 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -9,16 +9,16 @@ use std::str::FromStr; use std::{env, fs}; use namada::bip39::{Language, Mnemonic}; -pub use namada::sdk::wallet::alias::Alias; -use namada::sdk::wallet::fs::FsWalletStorage; -use namada::sdk::wallet::store::Store; -use namada::sdk::wallet::{ +use namada::types::address::Address; +use namada::types::key::*; +pub use namada_sdk::wallet::alias::Alias; +use namada_sdk::wallet::fs::FsWalletStorage; +use namada_sdk::wallet::store::Store; +use namada_sdk::wallet::{ AddressVpType, ConfirmationResponse, FindKeyError, GenRestoreKeyError, Wallet, WalletIo, }; -pub use namada::sdk::wallet::{ValidatorData, ValidatorKeys}; -use namada::types::address::Address; -use namada::types::key::*; +pub use namada_sdk::wallet::{ValidatorData, ValidatorKeys}; use rand_core::OsRng; pub use store::wallet_file; use zeroize::Zeroizing; @@ -317,7 +317,7 @@ pub fn read_and_confirm_encryption_password( #[cfg(test)] mod tests { use namada::bip39::MnemonicType; - use namada::sdk::wallet::WalletIo; + use namada_sdk::wallet::WalletIo; use rand_core; use super::CliWalletUtils; diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 13a2c21f2b..da12c2dcce 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -3,11 +3,11 @@ use std::path::{Path, PathBuf}; use ark_serialize::{Read, Write}; use fd_lock::RwLock; -use namada::sdk::wallet::pre_genesis::{ +use namada::types::key::SchemeType; +use namada_sdk::wallet::pre_genesis::{ ReadError, ValidatorStore, ValidatorWallet, }; -use namada::sdk::wallet::{gen_key_to_store, WalletIo}; -use namada::types::key::SchemeType; +use namada_sdk::wallet::{gen_key_to_store, WalletIo}; use zeroize::Zeroizing; use crate::wallet::store::gen_validator_keys; diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index c035925160..62eae8ac0e 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -5,14 +5,14 @@ use std::str::FromStr; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; #[cfg(not(feature = "dev"))] -use namada::sdk::wallet::store::AddressVpType; -#[cfg(feature = "dev")] -use namada::sdk::wallet::StoredKeypair; -use namada::sdk::wallet::{gen_sk_rng, LoadStoreError, Store, ValidatorKeys}; -#[cfg(not(feature = "dev"))] use namada::types::address::Address; use namada::types::key::*; use namada::types::transaction::EllipticCurve; +#[cfg(not(feature = "dev"))] +use namada_sdk::wallet::store::AddressVpType; +#[cfg(feature = "dev")] +use namada_sdk::wallet::StoredKeypair; +use namada_sdk::wallet::{gen_sk_rng, LoadStoreError, Store, ValidatorKeys}; use crate::config::genesis::genesis_config::GenesisConfig; use crate::wallet::CliWalletUtils; diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 91a5d45333..ebd99eedda 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -49,6 +49,7 @@ masp_primitives.workspace = true masp_proofs.workspace = true namada = { path = "../shared", features = ["testing"] } namada_apps = { path = "../apps", features = ["testing"] } +namada_sdk = {path = "../sdk", features = ["testing"] } namada_test_utils = { path = "../test_utils" } prost.workspace = true rand.workspace = true diff --git a/benches/lib.rs b/benches/lib.rs index f0cba69475..038d7017f0 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -63,20 +63,15 @@ use namada::ibc::core::Msg; use namada::ibc::Height as IbcHeight; use namada::ibc_proto::google::protobuf::Any; use namada::ibc_proto::protobuf::Protobuf; +use namada::ledger::dry_run_tx; use namada::ledger::gas::TxGasMeter; use namada::ledger::ibc::storage::{channel_key, connection_key}; use namada::ledger::queries::{ Client, EncodedResponseQuery, RequestCtx, RequestQuery, Router, RPC, }; use namada::ledger::storage_api::StorageRead; -use namada::ledger::NamadaImpl; use namada::proof_of_stake; use namada::proto::{Code, Data, Section, Signature, Tx}; -use namada::sdk::args::InputAmount; -use namada::sdk::masp::{ - self, ShieldedContext, ShieldedTransfer, ShieldedUtils, -}; -use namada::sdk::wallet::Wallet; use namada::tendermint::Hash; use namada::tendermint_rpc::{self}; use namada::types::address::InternalAddress; @@ -101,6 +96,12 @@ use namada_apps::facade::tendermint_proto::google::protobuf::Timestamp; use namada_apps::node::ledger::shell::Shell; use namada_apps::wallet::{defaults, CliWalletUtils}; use namada_apps::{config, wasm_loader}; +use namada_sdk::args::InputAmount; +use namada_sdk::masp::{ + self, ShieldedContext, ShieldedTransfer, ShieldedUtils, +}; +use namada_sdk::wallet::Wallet; +use namada_sdk::NamadaImpl; use namada_test_utils::tx_data::TxWriteData; use rand_core::OsRng; use sha2::{Digest, Sha256}; @@ -670,8 +671,12 @@ impl Client for BenchShell { storage_read_past_height_limit: None, }; - RPC.handle(ctx, &request) - .map_err(|_| std::io::Error::from(std::io::ErrorKind::NotFound)) + if request.path == "/shell/dry_run_tx" { + dry_run_tx(ctx, &request) + } else { + RPC.handle(ctx, &request) + } + .map_err(|_| std::io::Error::from(std::io::ErrorKind::NotFound)) } async fn perform( @@ -727,7 +732,7 @@ impl Default for BenchShieldedCtx { .fvk .vk; let (div, _g_d) = - namada::sdk::masp::find_valid_diversifier(&mut OsRng); + namada_sdk::masp::find_valid_diversifier(&mut OsRng); let payment_addr = viewing_key.to_payment_address(div).unwrap(); let _ = ctx .wallet diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml new file mode 100644 index 0000000000..f0eb9289ed --- /dev/null +++ b/sdk/Cargo.toml @@ -0,0 +1,121 @@ +[package] +name = "namada_sdk" +description = "The main Namada SDK crate" +resolver = "2" +authors.workspace = true +edition.workspace = true +documentation.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +abciplus = [ + "namada_core/abciplus", + "namada_proof_of_stake/abciplus", + "namada_ethereum_bridge/abciplus", +] + +ferveo-tpke = [ + "namada_core/ferveo-tpke", +] + +masp-tx-gen = [ + "rand", + "rand_core", +] + +multicore = ["masp_proofs/multicore"] + +namada-sdk = [ + "tendermint-rpc", + "masp-tx-gen", + "ferveo-tpke", + "masp_primitives/transparent-inputs" +] + +std = ["fd-lock"] + +# tendermint-rpc support +tendermint-rpc = [ + "async-client", + "dep:tendermint-rpc", +] + +wasm-runtime = [ + "namada_core/wasm-runtime", +] + +# Enable queries support for an async client +async-client = [ + "async-trait", +] + +ibc-mocks = [ + "namada_core/ibc-mocks", +] + +# for integration tests and test utilies +testing = [ + "namada_core/testing", + "namada_ethereum_bridge/testing", + "namada_proof_of_stake/testing", + "async-client", + "rand_core", + "rand", +] + +[dependencies] +async-trait = {version = "0.1.51", optional = true} +bimap.workspace = true +borsh.workspace = true +circular-queue.workspace = true +data-encoding.workspace = true +derivation-path.workspace = true +ethbridge-bridge-contract.workspace = true +ethers.workspace = true +fd-lock = { workspace = true, optional = true } +futures.workspace = true +itertools.workspace = true +masp_primitives.workspace = true +masp_proofs = { workspace = true, features = ["download-params"] } +namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign"]} +namada_ethereum_bridge = {path = "../ethereum_bridge", default-features = false} +namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} +num256.workspace = true +orion.workspace = true +owo-colors = "3.5.0" +parse_duration = "2.1.1" +paste.workspace = true +prost.workspace = true +rand = {optional = true, workspace = true} +rand_core = {optional = true, workspace = true} +ripemd.workspace = true +serde.workspace = true +serde_json.workspace = true +sha2.workspace = true +slip10_ed25519.workspace = true +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "b7d1e5afc6f2ccb3fd1545c2174bab1cc48d7fa7", default-features = false, features = ["trait-client"], optional = true} +thiserror.workspace = true +tiny-bip39.workspace = true +tiny-hderive.workspace = true +toml.workspace = true +tracing.workspace = true +zeroize.workspace = true + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +tokio = {workspace = true, features = ["full"]} + +[target.'cfg(target_family = "wasm")'.dependencies] +tokio = {workspace = true, default-features = false, features = ["sync"]} +wasmtimer = "0.2.0" + +[dev-dependencies] +assert_matches.workspace = true +namada_test_utils = {path = "../test_utils"} +tempfile.workspace = true diff --git a/shared/src/sdk/args.rs b/sdk/src/args.rs similarity index 95% rename from shared/src/sdk/args.rs rename to sdk/src/args.rs index d7556d6321..de4fd5bb98 100644 --- a/shared/src/sdk/args.rs +++ b/sdk/src/args.rs @@ -7,25 +7,24 @@ use std::time::Duration as StdDuration; use namada_core::ledger::governance::cli::onchain::{ DefaultProposal, PgfFundingProposal, PgfStewardProposal, }; +use namada_core::types::address::Address; use namada_core::types::chain::ChainId; use namada_core::types::dec::Dec; use namada_core::types::ethereum_events::EthAddress; +use namada_core::types::keccak::KeccakHash; +use namada_core::types::key::{common, SchemeType}; +use namada_core::types::masp::MaspValue; +use namada_core::types::storage::Epoch; use namada_core::types::time::DateTimeUtc; +use namada_core::types::transaction::GasLimit; +use namada_core::types::{storage, token}; use serde::{Deserialize, Serialize}; use zeroize::Zeroizing; +use crate::eth_bridge::bridge_pool; use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; -use crate::ledger::eth_bridge::bridge_pool; -use crate::ledger::Namada; -use crate::sdk::signing::SigningTxData; -use crate::sdk::{rpc, tx}; -use crate::types::address::Address; -use crate::types::keccak::KeccakHash; -use crate::types::key::{common, SchemeType}; -use crate::types::masp::MaspValue; -use crate::types::storage::Epoch; -use crate::types::transaction::GasLimit; -use crate::types::{storage, token}; +use crate::signing::SigningTxData; +use crate::{rpc, tx, Namada}; /// [`Duration`](StdDuration) wrapper that provides a /// method to parse a value from a string. @@ -179,11 +178,8 @@ impl TxCustom { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { tx::build_custom(context, self).await } } @@ -288,11 +284,8 @@ impl TxTransfer { pub async fn build<'a>( &mut self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { tx::build_transfer(context, self).await } } @@ -405,11 +398,8 @@ impl TxIbcTransfer { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { tx::build_ibc_transfer(context, self).await } } @@ -497,11 +487,8 @@ impl InitProposal { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { let current_epoch = rpc::query_epoch(context.client()).await?; let governance_parameters = rpc::query_governance_parameters(context.client()).await; @@ -511,13 +498,13 @@ impl InitProposal { self.proposal_data.as_ref(), ) .map_err(|e| { - crate::sdk::error::TxError::FailedGovernaneProposalDeserialize( + crate::error::TxError::FailedGovernaneProposalDeserialize( e.to_string(), ) })? .validate(&governance_parameters, current_epoch, self.tx.force) .map_err(|e| { - crate::sdk::error::TxError::InvalidProposal(e.to_string()) + crate::error::TxError::InvalidProposal(e.to_string()) })?; tx::build_pgf_funding_proposal(context, self, proposal).await @@ -526,7 +513,7 @@ impl InitProposal { self.proposal_data.as_ref(), ) .map_err(|e| { - crate::sdk::error::TxError::FailedGovernaneProposalDeserialize( + crate::error::TxError::FailedGovernaneProposalDeserialize( e.to_string(), ) })?; @@ -545,7 +532,7 @@ impl InitProposal { self.tx.force, ) .map_err(|e| { - crate::sdk::error::TxError::InvalidProposal(e.to_string()) + crate::error::TxError::InvalidProposal(e.to_string()) })?; tx::build_pgf_stewards_proposal(context, self, proposal).await @@ -554,7 +541,7 @@ impl InitProposal { self.proposal_data.as_ref(), ) .map_err(|e| { - crate::sdk::error::TxError::FailedGovernaneProposalDeserialize( + crate::error::TxError::FailedGovernaneProposalDeserialize( e.to_string(), ) })?; @@ -573,7 +560,7 @@ impl InitProposal { self.tx.force, ) .map_err(|e| { - crate::sdk::error::TxError::InvalidProposal(e.to_string()) + crate::error::TxError::InvalidProposal(e.to_string()) })?; tx::build_default_proposal(context, self, proposal).await } @@ -657,11 +644,8 @@ impl VoteProposal { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { let current_epoch = rpc::query_epoch(context.client()).await?; tx::build_vote_proposal(context, self, current_epoch).await } @@ -786,11 +770,8 @@ impl TxUpdateAccount { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { tx::build_update_account(context, self).await } } @@ -867,11 +848,8 @@ impl Bond { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { tx::build_bond(context, self).await } } @@ -897,7 +875,7 @@ impl Unbond { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( + ) -> crate::error::Result<( crate::proto::Tx, SigningTxData, Option, @@ -981,11 +959,8 @@ impl RevealPk { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { tx::build_reveal_pk(context, &self.tx, &self.public_key).await } } @@ -1068,11 +1043,8 @@ impl Withdraw { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { tx::build_withdraw(context, self).await } } @@ -1204,11 +1176,8 @@ impl CommissionRateChange { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { tx::build_validator_commission_change(context, self).await } } @@ -1263,11 +1232,8 @@ impl UpdateStewardCommission { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { tx::build_update_steward_commission(context, self).await } } @@ -1315,11 +1281,8 @@ impl ResignSteward { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { tx::build_resign_steward(context, self).await } } @@ -1367,11 +1330,8 @@ impl TxUnjailValidator { pub async fn build<'a>( &self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { tx::build_unjail_validator(context, self).await } } @@ -1892,11 +1852,8 @@ impl EthereumBridgePool { pub async fn build<'a>( self, context: &impl Namada<'a>, - ) -> crate::sdk::error::Result<( - crate::proto::Tx, - SigningTxData, - Option, - )> { + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { bridge_pool::build_bridge_pool_tx(context, self).await } } diff --git a/shared/src/types/control_flow.rs b/sdk/src/control_flow/mod.rs similarity index 100% rename from shared/src/types/control_flow.rs rename to sdk/src/control_flow/mod.rs diff --git a/shared/src/types/control_flow/time.rs b/sdk/src/control_flow/time.rs similarity index 100% rename from shared/src/types/control_flow/time.rs rename to sdk/src/control_flow/time.rs diff --git a/shared/src/sdk/error.rs b/sdk/src/error.rs similarity index 97% rename from shared/src/sdk/error.rs rename to sdk/src/error.rs index b103a9523f..a3091a3d7c 100644 --- a/shared/src/sdk/error.rs +++ b/sdk/src/error.rs @@ -9,8 +9,7 @@ use prost::EncodeError; use tendermint_rpc::Error as RpcError; use thiserror::Error; -use crate::sdk::error::Error::Pinned; -use crate::vm::WasmValidationError; +use crate::error::Error::Pinned; /// The standard Result type that most code ought to return pub type Result = std::result::Result; @@ -222,9 +221,6 @@ pub enum TxError { /// Error in the fee unshielding transaction #[error("Error in fee unshielding: {0}")] FeeUnshieldingError(String), - /// Wasm validation failed - #[error("Validity predicate code validation failed with {0}")] - WasmValidationFailure(WasmValidationError), /// Encoding transaction failure #[error("Encoding tx data, {0}, shouldn't fail")] EncodeTxFailure(String), diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/sdk/src/eth_bridge/bridge_pool.rs similarity index 97% rename from shared/src/ledger/eth_bridge/bridge_pool.rs rename to sdk/src/eth_bridge/bridge_pool.rs index b4c633ca78..da80bdf41f 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/sdk/src/eth_bridge/bridge_pool.rs @@ -9,36 +9,33 @@ use borsh::BorshSerialize; use ethbridge_bridge_contract::Bridge; use ethers::providers::Middleware; use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; +use namada_core::types::address::Address; +use namada_core::types::eth_abi::Encode; +use namada_core::types::eth_bridge_pool::{ + GasFee, PendingTransfer, TransferToEthereum, TransferToEthereumKind, +}; +use namada_core::types::keccak::KeccakHash; use namada_core::types::storage::Epoch; +use namada_core::types::token::{Amount, DenominatedAmount}; +use namada_core::types::voting_power::FractionalVotingPower; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; use super::{block_on_eth_sync, eth_sync_or_exit, BlockOnEthSync}; +use crate::control_flow::time::{Duration, Instant}; +use crate::control_flow::{self, install_shutdown_signal, Halt, TryHalt}; +use crate::error::Error; use crate::eth_bridge::ethers::abi::AbiDecode; -use crate::ledger::queries::{ +use crate::io::Io; +use crate::proto::Tx; +use crate::queries::{ Client, GenBridgePoolProofReq, GenBridgePoolProofRsp, TransferToErcArgs, RPC, }; -use crate::ledger::signing::aux_signing_data; -use crate::ledger::tx::prepare_tx; -use crate::ledger::{args, Namada, SigningTxData}; -use crate::proto::Tx; -use crate::sdk::error::Error; -use crate::sdk::rpc::{query_wasm_code_hash, validate_amount}; -use crate::types::address::Address; -use crate::types::control_flow::time::{Duration, Instant}; -use crate::types::control_flow::{ - self, install_shutdown_signal, Halt, TryHalt, -}; -use crate::types::eth_abi::Encode; -use crate::types::eth_bridge_pool::{ - GasFee, PendingTransfer, TransferToEthereum, TransferToEthereumKind, -}; -use crate::types::io::Io; -use crate::types::keccak::KeccakHash; -use crate::types::token::{Amount, DenominatedAmount}; -use crate::types::voting_power::FractionalVotingPower; -use crate::{display, display_line}; +use crate::rpc::{query_wasm_code_hash, validate_amount}; +use crate::signing::aux_signing_data; +use crate::tx::prepare_tx; +use crate::{args, display, display_line, Namada, SigningTxData}; /// Craft a transaction that adds a transfer to the Ethereum bridge pool. pub async fn build_bridge_pool_tx<'a>( @@ -456,7 +453,12 @@ mod recommendations { use std::collections::BTreeSet; use borsh::BorshDeserialize; + use namada_core::types::ethereum_events::Uint as EthUint; + use namada_core::types::storage::BlockHeight; use namada_core::types::uint::{self, Uint, I256}; + use namada_core::types::vote_extensions::validator_set_update::{ + EthAddrBook, VotingPowersMap, VotingPowersMapExt, + }; use super::*; use crate::edisplay_line; @@ -464,12 +466,7 @@ mod recommendations { get_nonce_key, get_signed_root_key, }; use crate::eth_bridge::storage::proof::BridgePoolRootProof; - use crate::types::ethereum_events::Uint as EthUint; - use crate::types::io::Io; - use crate::types::storage::BlockHeight; - use crate::types::vote_extensions::validator_set_update::{ - EthAddrBook, VotingPowersMap, VotingPowersMapExt, - }; + use crate::io::Io; const fn unsigned_transfer_fee() -> Uint { Uint::from_u64(37_500_u64) @@ -920,8 +917,8 @@ mod recommendations { use namada_core::types::ethereum_events::EthAddress; use super::*; - use crate::types::control_flow::ProceedOrElse; - use crate::types::io::StdIo; + use crate::control_flow::ProceedOrElse; + use crate::io::StdIo; /// An established user address for testing & development pub fn bertha_address() -> Address { diff --git a/shared/src/ledger/eth_bridge.rs b/sdk/src/eth_bridge/mod.rs similarity index 95% rename from shared/src/ledger/eth_bridge.rs rename to sdk/src/eth_bridge/mod.rs index 66ec22a63b..49b77705a3 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/sdk/src/eth_bridge/mod.rs @@ -5,19 +5,22 @@ pub mod validator_set; use std::ops::ControlFlow; +pub use ethers; use ethers::providers::Middleware; use itertools::Either; pub use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; +pub use namada_core::types::ethereum_structs as structs; pub use namada_ethereum_bridge::parameters::*; pub use namada_ethereum_bridge::storage::eth_bridge_queries::*; +pub use namada_ethereum_bridge::*; use num256::Uint256; -use crate::types::control_flow::time::{ +use crate::control_flow::time::{ Constant, Duration, Error as TimeoutError, Instant, LinearBackoff, Sleep, }; -use crate::types::control_flow::{self, Halt, TryHalt}; -use crate::types::io::Io; +use crate::control_flow::{self, Halt, TryHalt}; +use crate::io::Io; use crate::{display_line, edisplay_line}; const DEFAULT_BACKOFF: Duration = std::time::Duration::from_millis(500); diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/sdk/src/eth_bridge/validator_set.rs similarity index 98% rename from shared/src/ledger/eth_bridge/validator_set.rs rename to sdk/src/eth_bridge/validator_set.rs index 90f043fe18..5c98b39ae0 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/sdk/src/eth_bridge/validator_set.rs @@ -12,23 +12,19 @@ use ethbridge_bridge_contract::Bridge; use ethers::providers::Middleware; use futures::future::{self, FutureExt}; use namada_core::hints; +use namada_core::types::ethereum_events::EthAddress; use namada_core::types::storage::Epoch; +use namada_core::types::vote_extensions::validator_set_update::ValidatorSetArgs; use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit, BlockOnEthSync}; +use crate::control_flow::time::{self, Duration, Instant}; +use crate::control_flow::{self, install_shutdown_signal, Halt, TryHalt}; use crate::eth_bridge::ethers::abi::{AbiDecode, AbiType, Tokenizable}; use crate::eth_bridge::ethers::core::types::TransactionReceipt; use crate::eth_bridge::structs::Signature; -use crate::ledger::queries::RPC; -use crate::sdk::args; -use crate::sdk::queries::Client; -use crate::types::control_flow::time::{self, Duration, Instant}; -use crate::types::control_flow::{ - self, install_shutdown_signal, Halt, TryHalt, -}; -use crate::types::ethereum_events::EthAddress; -use crate::types::io::Io; -use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; -use crate::{display_line, edisplay_line}; +use crate::io::Io; +use crate::queries::{Client, RPC}; +use crate::{args, display_line, edisplay_line}; /// Relayer related errors. #[derive(Debug, Default)] diff --git a/shared/src/ledger/events/log.rs b/sdk/src/events/log.rs similarity index 97% rename from shared/src/ledger/events/log.rs rename to sdk/src/events/log.rs index a2dc3978d0..596c23bdc9 100644 --- a/shared/src/ledger/events/log.rs +++ b/sdk/src/events/log.rs @@ -8,7 +8,7 @@ use std::default::Default; use circular_queue::CircularQueue; -use crate::ledger::events::Event; +use crate::events::Event; pub mod dumb_queries; @@ -85,9 +85,10 @@ impl EventLog { #[cfg(test)] mod tests { + use namada_core::types::hash::Hash; + use super::*; - use crate::ledger::events::{EventLevel, EventType}; - use crate::types::hash::Hash; + use crate::events::{EventLevel, EventType}; const HASH: &str = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"; diff --git a/shared/src/ledger/events/log/dumb_queries.rs b/sdk/src/events/log/dumb_queries.rs similarity index 96% rename from shared/src/ledger/events/log/dumb_queries.rs rename to sdk/src/events/log/dumb_queries.rs index 5ff7c8d54f..44988fb0dc 100644 --- a/shared/src/ledger/events/log/dumb_queries.rs +++ b/sdk/src/events/log/dumb_queries.rs @@ -8,12 +8,13 @@ use std::collections::HashMap; +use namada_core::types::hash::Hash; +use namada_core::types::storage::BlockHeight; + +use crate::events::{Event, EventType}; use crate::ibc::core::ics04_channel::packet::Sequence; use crate::ibc::core::ics24_host::identifier::{ChannelId, ClientId, PortId}; use crate::ibc::Height as IbcHeight; -use crate::ledger::events::{Event, EventType}; -use crate::types::hash::Hash; -use crate::types::storage::BlockHeight; /// A [`QueryMatcher`] verifies if a Namada event matches a /// given Tendermint query. @@ -118,7 +119,7 @@ impl QueryMatcher { #[cfg(test)] mod tests { use super::*; - use crate::ledger::events::EventLevel; + use crate::events::EventLevel; /// Test if query matching is working as expected. #[test] diff --git a/shared/src/ledger/events.rs b/sdk/src/events/mod.rs similarity index 94% rename from shared/src/ledger/events.rs rename to sdk/src/events/mod.rs index ff5b9f108d..141867c63d 100644 --- a/shared/src/ledger/events.rs +++ b/sdk/src/events/mod.rs @@ -8,14 +8,14 @@ use std::ops::{Index, IndexMut}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::types::ibc::IbcEvent; +#[cfg(feature = "ferveo-tpke")] +use namada_core::types::transaction::TxType; use serde_json::Value; -use crate::ledger::governance::utils::ProposalEvent; -use crate::sdk::error::{EncodingError, Error, EventError}; +// use crate::ledger::governance::utils::ProposalEvent; +use crate::error::{EncodingError, Error, EventError}; use crate::tendermint_proto::abci::EventAttribute; -use crate::types::ibc::IbcEvent; -#[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::TxType; /// Indicates if an event is emitted do to /// an individual Tx or the nature of a finalized block @@ -171,16 +171,6 @@ impl From for Event { } } -impl From for Event { - fn from(proposal_event: ProposalEvent) -> Self { - Self { - event_type: EventType::Proposal, - level: EventLevel::Block, - attributes: proposal_event.attributes, - } - } -} - /// Convert our custom event into the necessary tendermint proto type impl From for crate::tendermint_proto::abci::Event { fn from(event: Event) -> Self { diff --git a/shared/src/types/io.rs b/sdk/src/io.rs similarity index 100% rename from shared/src/types/io.rs rename to sdk/src/io.rs diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs new file mode 100644 index 0000000000..78a5caf812 --- /dev/null +++ b/sdk/src/lib.rs @@ -0,0 +1,560 @@ +pub use namada_core::proto; +#[cfg(feature = "tendermint-rpc")] +pub use tendermint_rpc; +#[cfg(feature = "tendermint-rpc-abcipp")] +pub use tendermint_rpc_abcipp as tendermint_rpc; +pub use {bip39, namada_core as core, namada_proof_of_stake as proof_of_stake}; +#[cfg(feature = "abcipp")] +pub use { + ibc_abcipp as ibc, ibc_proto_abcipp as ibc_proto, + tendermint_abcipp as tendermint, + tendermint_proto_abcipp as tendermint_proto, +}; +#[cfg(feature = "abciplus")] +pub use { + namada_core::ibc, namada_core::ibc_proto, namada_core::tendermint, + namada_core::tendermint_proto, +}; + +pub mod eth_bridge; + +pub mod rpc; + +pub mod args; +pub mod masp; +pub mod signing; +#[allow(clippy::result_large_err)] +pub mod tx; + +pub mod control_flow; +pub mod error; +pub mod events; +pub mod io; +pub mod queries; +pub mod wallet; + +use std::path::PathBuf; +use std::str::FromStr; + +use args::{InputAmount, SdkTypes}; +use namada_core::types::address::Address; +use namada_core::types::dec::Dec; +use namada_core::types::ethereum_events::EthAddress; +use namada_core::types::key::*; +use namada_core::types::masp::{TransferSource, TransferTarget}; +use namada_core::types::token; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; +use namada_core::types::transaction::GasLimit; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; +use crate::io::Io; +use crate::masp::{ShieldedContext, ShieldedUtils}; +use crate::proto::Tx; +use crate::rpc::query_native_token; +use crate::signing::SigningTxData; +use crate::tx::{ + ProcessTxResponse, TX_BOND_WASM, TX_BRIDGE_POOL_WASM, + TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, TX_INIT_PROPOSAL, + TX_INIT_VALIDATOR_WASM, TX_RESIGN_STEWARD, TX_REVEAL_PK, TX_TRANSFER_WASM, + TX_UNBOND_WASM, TX_UNJAIL_VALIDATOR_WASM, TX_UPDATE_ACCOUNT_WASM, + TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, + VP_USER_WASM, +}; +use crate::wallet::{Wallet, WalletIo, WalletStorage}; + +#[async_trait::async_trait(?Send)] +/// An interface for high-level interaction with the Namada SDK +pub trait Namada<'a>: Sized { + /// A client with async request dispatcher method + type Client: 'a + queries::Client + Sync; + /// Captures the interactive parts of the wallet's functioning + type WalletUtils: 'a + WalletIo + WalletStorage; + /// Abstracts platform specific details away from the logic of shielded pool + /// operations. + type ShieldedUtils: 'a + ShieldedUtils; + /// Captures the input/output streams used by this object + type Io: 'a + Io; + + /// Obtain the client for communicating with the ledger + fn client(&self) -> &'a Self::Client; + + /// Obtain the input/output handle for this context + fn io(&self) -> &'a Self::Io; + + /// Obtain read guard on the wallet + async fn wallet( + &self, + ) -> RwLockReadGuard<&'a mut Wallet>; + + /// Obtain write guard on the wallet + async fn wallet_mut( + &self, + ) -> RwLockWriteGuard<&'a mut Wallet>; + + /// Obtain read guard on the shielded context + async fn shielded( + &self, + ) -> RwLockReadGuard<&'a mut ShieldedContext>; + + /// Obtain write guard on the shielded context + async fn shielded_mut( + &self, + ) -> RwLockWriteGuard<&'a mut ShieldedContext>; + + /// Return the native token + fn native_token(&self) -> Address; + + /// Make a tx builder using no arguments + fn tx_builder(&self) -> args::Tx { + args::Tx { + dry_run: false, + dry_run_wrapper: false, + dump_tx: false, + output_folder: None, + force: false, + broadcast_only: false, + ledger_address: (), + initialized_account_alias: None, + wallet_alias_force: false, + fee_amount: None, + wrapper_fee_payer: None, + fee_token: self.native_token(), + fee_unshield: None, + gas_limit: GasLimit::from(20_000), + expiration: None, + disposable_signing_key: false, + chain_id: None, + signing_keys: vec![], + signatures: vec![], + tx_reveal_code_path: PathBuf::from(TX_REVEAL_PK), + verification_key: None, + password: None, + } + } + + /// Make a TxTransfer builder from the given minimum set of arguments + fn new_transfer( + &self, + source: TransferSource, + target: TransferTarget, + token: Address, + amount: InputAmount, + ) -> args::TxTransfer { + args::TxTransfer { + source, + target, + token, + amount, + tx_code_path: PathBuf::from(TX_TRANSFER_WASM), + tx: self.tx_builder(), + native_token: self.native_token(), + } + } + + /// Make a RevealPK builder from the given minimum set of arguments + fn new_reveal_pk(&self, public_key: common::PublicKey) -> args::RevealPk { + args::RevealPk { + public_key, + tx: self.tx_builder(), + } + } + + /// Make a Bond builder from the given minimum set of arguments + fn new_bond( + &self, + validator: Address, + amount: token::Amount, + ) -> args::Bond { + args::Bond { + validator, + amount, + source: None, + tx: self.tx_builder(), + native_token: self.native_token(), + tx_code_path: PathBuf::from(TX_BOND_WASM), + } + } + + /// Make a Unbond builder from the given minimum set of arguments + fn new_unbond( + &self, + validator: Address, + amount: token::Amount, + ) -> args::Unbond { + args::Unbond { + validator, + amount, + source: None, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_UNBOND_WASM), + } + } + + /// Make a TxIbcTransfer builder from the given minimum set of arguments + fn new_ibc_transfer( + &self, + source: Address, + receiver: String, + token: Address, + amount: InputAmount, + channel_id: ChannelId, + ) -> args::TxIbcTransfer { + args::TxIbcTransfer { + source, + receiver, + token, + amount, + channel_id, + port_id: PortId::from_str("transfer").unwrap(), + timeout_height: None, + timeout_sec_offset: None, + memo: None, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_IBC_WASM), + } + } + + /// Make a InitProposal builder from the given minimum set of arguments + fn new_init_proposal(&self, proposal_data: Vec) -> args::InitProposal { + args::InitProposal { + proposal_data, + native_token: self.native_token(), + is_offline: false, + is_pgf_stewards: false, + is_pgf_funding: false, + tx_code_path: PathBuf::from(TX_INIT_PROPOSAL), + tx: self.tx_builder(), + } + } + + /// Make a TxUpdateAccount builder from the given minimum set of arguments + fn new_update_account(&self, addr: Address) -> args::TxUpdateAccount { + args::TxUpdateAccount { + addr, + vp_code_path: None, + public_keys: vec![], + threshold: None, + tx_code_path: PathBuf::from(TX_UPDATE_ACCOUNT_WASM), + tx: self.tx_builder(), + } + } + + /// Make a VoteProposal builder from the given minimum set of arguments + fn new_vote_prposal( + &self, + vote: String, + voter: Address, + ) -> args::VoteProposal { + args::VoteProposal { + vote, + voter, + proposal_id: None, + is_offline: false, + proposal_data: None, + tx_code_path: PathBuf::from(TX_VOTE_PROPOSAL), + tx: self.tx_builder(), + } + } + + /// Make a CommissionRateChange builder from the given minimum set of + /// arguments + fn new_change_commission_rate( + &self, + rate: Dec, + validator: Address, + ) -> args::CommissionRateChange { + args::CommissionRateChange { + rate, + validator, + tx_code_path: PathBuf::from(TX_CHANGE_COMMISSION_WASM), + tx: self.tx_builder(), + } + } + + /// Make a TxInitValidator builder from the given minimum set of arguments + fn new_init_validator( + &self, + commission_rate: Dec, + max_commission_rate_change: Dec, + ) -> args::TxInitValidator { + args::TxInitValidator { + commission_rate, + max_commission_rate_change, + scheme: SchemeType::Ed25519, + account_keys: vec![], + threshold: None, + consensus_key: None, + eth_cold_key: None, + eth_hot_key: None, + protocol_key: None, + validator_vp_code_path: PathBuf::from(VP_USER_WASM), + unsafe_dont_encrypt: false, + tx_code_path: PathBuf::from(TX_INIT_VALIDATOR_WASM), + tx: self.tx_builder(), + } + } + + /// Make a TxUnjailValidator builder from the given minimum set of arguments + fn new_unjail_validator( + &self, + validator: Address, + ) -> args::TxUnjailValidator { + args::TxUnjailValidator { + validator, + tx_code_path: PathBuf::from(TX_UNJAIL_VALIDATOR_WASM), + tx: self.tx_builder(), + } + } + + /// Make a Withdraw builder from the given minimum set of arguments + fn new_withdraw(&self, validator: Address) -> args::Withdraw { + args::Withdraw { + validator, + source: None, + tx_code_path: PathBuf::from(TX_WITHDRAW_WASM), + tx: self.tx_builder(), + } + } + + /// Make a Withdraw builder from the given minimum set of arguments + fn new_add_erc20_transfer( + &self, + sender: Address, + recipient: EthAddress, + asset: EthAddress, + amount: InputAmount, + ) -> args::EthereumBridgePool { + args::EthereumBridgePool { + sender, + recipient, + asset, + amount, + fee_amount: InputAmount::Unvalidated(token::DenominatedAmount { + amount: token::Amount::default(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }), + fee_payer: None, + fee_token: self.native_token(), + nut: false, + code_path: PathBuf::from(TX_BRIDGE_POOL_WASM), + tx: self.tx_builder(), + } + } + + /// Make a ResignSteward builder from the given minimum set of arguments + fn new_resign_steward(&self, steward: Address) -> args::ResignSteward { + args::ResignSteward { + steward, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_RESIGN_STEWARD), + } + } + + /// Make a UpdateStewardCommission builder from the given minimum set of + /// arguments + fn new_update_steward_rewards( + &self, + steward: Address, + commission: Vec, + ) -> args::UpdateStewardCommission { + args::UpdateStewardCommission { + steward, + commission, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_UPDATE_STEWARD_COMMISSION), + } + } + + /// Make a TxCustom builder from the given minimum set of arguments + fn new_custom(&self, owner: Address) -> args::TxCustom { + args::TxCustom { + owner, + tx: self.tx_builder(), + code_path: None, + data_path: None, + serialized_tx: None, + } + } + + /// Sign the given transaction using the given signing data + async fn sign( + &self, + tx: &mut Tx, + args: &args::Tx, + signing_data: SigningTxData, + ) -> crate::error::Result<()> { + signing::sign_tx(*self.wallet_mut().await, args, tx, signing_data) + } + + /// Process the given transaction using the given flags + async fn submit( + &self, + tx: Tx, + args: &args::Tx, + ) -> crate::error::Result { + tx::process_tx(self, args, tx).await + } +} + +/// Provides convenience methods for common Namada interactions +pub struct NamadaImpl<'a, C, U, V, I> +where + C: queries::Client + Sync, + U: WalletIo, + V: ShieldedUtils, + I: Io, +{ + /// Used to send and receive messages from the ledger + pub client: &'a C, + /// Stores the addresses and keys required for ledger interactions + pub wallet: RwLock<&'a mut Wallet>, + /// Stores the current state of the shielded pool + pub shielded: RwLock<&'a mut ShieldedContext>, + /// Captures the input/output streams used by this object + pub io: &'a I, + /// The address of the native token + native_token: Address, + /// The default builder for a Tx + prototype: args::Tx, +} + +impl<'a, C, U, V, I> NamadaImpl<'a, C, U, V, I> +where + C: queries::Client + Sync, + U: WalletIo, + V: ShieldedUtils, + I: Io, +{ + /// Construct a new Namada context with the given native token address + pub fn native_new( + client: &'a C, + wallet: &'a mut Wallet, + shielded: &'a mut ShieldedContext, + io: &'a I, + native_token: Address, + ) -> Self { + NamadaImpl { + client, + wallet: RwLock::new(wallet), + shielded: RwLock::new(shielded), + io, + native_token: native_token.clone(), + prototype: args::Tx { + dry_run: false, + dry_run_wrapper: false, + dump_tx: false, + output_folder: None, + force: false, + broadcast_only: false, + ledger_address: (), + initialized_account_alias: None, + wallet_alias_force: false, + fee_amount: None, + wrapper_fee_payer: None, + fee_token: native_token, + fee_unshield: None, + gas_limit: GasLimit::from(20_000), + expiration: None, + disposable_signing_key: false, + chain_id: None, + signing_keys: vec![], + signatures: vec![], + tx_reveal_code_path: PathBuf::from(TX_REVEAL_PK), + verification_key: None, + password: None, + }, + } + } + + /// Construct a new Namada context looking up the native token address + pub async fn new( + client: &'a C, + wallet: &'a mut Wallet, + shielded: &'a mut ShieldedContext, + io: &'a I, + ) -> crate::error::Result> { + let native_token = query_native_token(client).await?; + Ok(NamadaImpl::native_new( + client, + wallet, + shielded, + io, + native_token, + )) + } +} + +#[async_trait::async_trait(?Send)] +impl<'a, C, U, V, I> Namada<'a> for NamadaImpl<'a, C, U, V, I> +where + C: queries::Client + Sync, + U: WalletIo + WalletStorage, + V: ShieldedUtils, + I: Io, +{ + type Client = C; + type Io = I; + type ShieldedUtils = V; + type WalletUtils = U; + + /// Obtain the prototypical Tx builder + fn tx_builder(&self) -> args::Tx { + self.prototype.clone() + } + + fn native_token(&self) -> Address { + self.native_token.clone() + } + + fn io(&self) -> &'a Self::Io { + self.io + } + + fn client(&self) -> &'a Self::Client { + self.client + } + + async fn wallet( + &self, + ) -> RwLockReadGuard<&'a mut Wallet> { + self.wallet.read().await + } + + async fn wallet_mut( + &self, + ) -> RwLockWriteGuard<&'a mut Wallet> { + self.wallet.write().await + } + + async fn shielded( + &self, + ) -> RwLockReadGuard<&'a mut ShieldedContext> { + self.shielded.read().await + } + + async fn shielded_mut( + &self, + ) -> RwLockWriteGuard<&'a mut ShieldedContext> { + self.shielded.write().await + } +} + +/// Allow the prototypical Tx builder to be modified +impl<'a, C, U, V, I> args::TxBuilder for NamadaImpl<'a, C, U, V, I> +where + C: queries::Client + Sync, + U: WalletIo, + V: ShieldedUtils, + I: Io, +{ + fn tx(self, func: F) -> Self + where + F: FnOnce(args::Tx) -> args::Tx, + { + Self { + prototype: func(self.prototype), + ..self + } + } +} diff --git a/shared/src/sdk/masp.rs b/sdk/src/masp.rs similarity index 99% rename from shared/src/sdk/masp.rs rename to sdk/src/masp.rs index 96ab27fbd5..b010bde7d7 100644 --- a/shared/src/sdk/masp.rs +++ b/sdk/src/masp.rs @@ -49,8 +49,18 @@ use masp_proofs::bellman::groth16::PreparedVerifyingKey; use masp_proofs::bls12_381::Bls12; use masp_proofs::prover::LocalTxProver; use masp_proofs::sapling::SaplingVerificationContext; -use namada_core::types::token::{Change, MaspDenom}; -use namada_core::types::transaction::AffineCurve; +use namada_core::types::address::{masp, Address}; +use namada_core::types::masp::{ + BalanceOwner, ExtendedViewingKey, PaymentAddress, +}; +use namada_core::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; +use namada_core::types::token; +use namada_core::types::token::{ + Change, MaspDenom, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, +}; +use namada_core::types::transaction::{ + AffineCurve, EllipticCurve, PairingEngine, WrapperTx, +}; #[cfg(feature = "masp-tx-gen")] use rand_core::{CryptoRng, OsRng, RngCore}; use ripemd::Digest as RipemdDigest; @@ -58,26 +68,16 @@ use ripemd::Digest as RipemdDigest; use sha2::Digest; use thiserror::Error; -use crate::ledger::queries::Client; -use crate::ledger::Namada; +use crate::args::InputAmount; +use crate::error::{EncodingError, Error, PinnedBalanceError, QueryError}; +use crate::io::Io; use crate::proto::Tx; -use crate::sdk::args::InputAmount; -use crate::sdk::error::{EncodingError, Error, PinnedBalanceError, QueryError}; -use crate::sdk::rpc::{query_conversion, query_storage_value}; -use crate::sdk::tx::decode_component; -use crate::sdk::{args, rpc}; +use crate::queries::Client; +use crate::rpc::{query_conversion, query_storage_value}; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; -use crate::types::address::{masp, Address}; -use crate::types::io::Io; -use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; -use crate::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; -use crate::types::token; -use crate::types::token::{ - Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, -}; -use crate::types::transaction::{EllipticCurve, PairingEngine, WrapperTx}; -use crate::{display_line, edisplay_line}; +use crate::tx::decode_component; +use crate::{args, display_line, edisplay_line, rpc, Namada}; /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. diff --git a/shared/src/sdk/queries.rs b/sdk/src/queries/mod.rs similarity index 59% rename from shared/src/sdk/queries.rs rename to sdk/src/queries/mod.rs index a7cb9badb1..fdd5b042a8 100644 --- a/shared/src/sdk/queries.rs +++ b/sdk/src/queries/mod.rs @@ -1,7 +1,207 @@ -//! Query functionality related to the SDK -use std::fmt::{Debug, Display}; +//! Ledger read-only queries can be handled and dispatched via the [`RPC`] +//! defined via `router!` macro. +// Re-export to show in rustdoc! +use namada_core::ledger::storage::traits::StorageHasher; +use namada_core::ledger::storage::{DBIter, DB}; +use namada_core::ledger::storage_api; use namada_core::types::storage::BlockHeight; +pub use shell::Shell; +use shell::SHELL; +pub use types::{ + EncodedResponseQuery, Error, RequestCtx, RequestQuery, ResponseQuery, + Router, +}; +use vp::{Vp, VP}; + +pub use self::shell::eth_bridge::{ + Erc20FlowControl, GenBridgePoolProofReq, GenBridgePoolProofRsp, + TransferToErcArgs, +}; + +#[macro_use] +mod router; +mod shell; +mod types; +pub mod vp; + +// Most commonly expected patterns should be declared first +router! {RPC, + // Shell provides storage read access, block metadata and can dry-run a tx + ( "shell" ) = (sub SHELL), + + // Validity-predicate's specific storage queries + ( "vp" ) = (sub VP), +} + +/// Handle RPC query request in the ledger. On success, returns response with +/// borsh-encoded data. +pub fn handle_path( + ctx: RequestCtx<'_, D, H, V, T>, + request: &RequestQuery, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + RPC.handle(ctx, request) +} + +// Handler helpers: + +/// For queries that only support latest height, check that the given height is +/// not different from latest height, otherwise return an error. +pub fn require_latest_height( + ctx: &RequestCtx<'_, D, H, V, T>, + request: &RequestQuery, +) -> storage_api::Result<()> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + if request.height != BlockHeight(0) + && request.height != ctx.wl_storage.storage.get_last_block_height() + { + return Err(storage_api::Error::new_const( + "This query doesn't support arbitrary block heights, only the \ + latest committed block height ('0' can be used as a special \ + value that means the latest block height)", + )); + } + Ok(()) +} + +/// For queries that do not support proofs, check that proof is not requested, +/// otherwise return an error. +pub fn require_no_proof(request: &RequestQuery) -> storage_api::Result<()> { + if request.prove { + return Err(storage_api::Error::new_const( + "This query doesn't support proofs", + )); + } + Ok(()) +} + +/// For queries that don't use request data, require that there are no data +/// attached. +pub fn require_no_data(request: &RequestQuery) -> storage_api::Result<()> { + if !request.data.is_empty() { + return Err(storage_api::Error::new_const( + "This query doesn't accept request data", + )); + } + Ok(()) +} + +/// Queries testing helpers +#[cfg(any(test, feature = "testing"))] +mod testing { + + use namada_core::ledger::storage::testing::TestWlStorage; + use namada_core::types::storage::BlockHeight; + use tendermint_rpc::Response; + + use super::*; + use crate::events::log::EventLog; + use crate::tendermint_rpc::error::Error as RpcError; + + /// A test client that has direct access to the storage + pub struct TestClient + where + RPC: Router, + { + /// RPC router + pub rpc: RPC, + /// storage + pub wl_storage: TestWlStorage, + /// event log + pub event_log: EventLog, + } + + impl TestClient + where + RPC: Router, + { + #[allow(dead_code)] + /// Initialize a test client for the given root RPC router + pub fn new(rpc: RPC) -> Self { + // Initialize the `TestClient` + let mut wl_storage = TestWlStorage::default(); + + // Initialize mock gas limit + let max_block_gas_key = + namada_core::ledger::parameters::storage::get_max_block_gas_key( + ); + wl_storage + .storage + .write( + &max_block_gas_key, + namada_core::ledger::storage::types::encode( + &20_000_000_u64, + ), + ) + .expect( + "Max block gas parameter must be initialized in storage", + ); + let event_log = EventLog::default(); + Self { + rpc, + wl_storage, + event_log, + } + } + } + + #[cfg_attr(feature = "async-send", async_trait::async_trait)] + #[cfg_attr(not(feature = "async-send"), async_trait::async_trait(?Send))] + impl Client for TestClient + where + RPC: Router + Sync, + { + type Error = std::io::Error; + + async fn request( + &self, + path: String, + data: Option>, + height: Option, + prove: bool, + ) -> Result { + let data = data.unwrap_or_default(); + let height = height.unwrap_or_default(); + // Handle a path by invoking the `RPC.handle` directly with the + // borrowed storage + let request = RequestQuery { + data, + path, + height, + prove, + }; + let ctx = RequestCtx { + wl_storage: &self.wl_storage, + event_log: &self.event_log, + vp_wasm_cache: (), + tx_wasm_cache: (), + storage_read_past_height_limit: None, + }; + // TODO: this is a hack to propagate errors to the caller, we should + // really permit error types other than [`std::io::Error`] + self.rpc.handle(ctx, &request).map_err(|err| { + std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) + }) + } + + async fn perform(&self, _request: R) -> Result + where + R: tendermint_rpc::SimpleRequest, + { + Response::from_string("TODO") + } + } +} + +use std::fmt::{Debug, Display}; + use tendermint_rpc::endpoint::{ abci_info, block, block_results, blockchain, commit, consensus_params, consensus_state, health, net_info, status, @@ -9,7 +209,6 @@ use tendermint_rpc::endpoint::{ use tendermint_rpc::query::Query; use tendermint_rpc::{Error as RpcError, Order}; -use crate::ledger::queries::{EncodedResponseQuery, Error}; use crate::tendermint::block::Height; /// A client with async request dispatcher method, which can be used to invoke diff --git a/shared/src/ledger/queries/router.rs b/sdk/src/queries/router.rs similarity index 92% rename from shared/src/ledger/queries/router.rs rename to sdk/src/queries/router.rs index 799a34e5bd..9783d21309 100644 --- a/shared/src/ledger/queries/router.rs +++ b/sdk/src/queries/router.rs @@ -82,16 +82,16 @@ macro_rules! handle_match { break } // Check that the request is not sent with unsupported non-default - $crate::ledger::queries::require_latest_height(&$ctx, $request)?; - $crate::ledger::queries::require_no_proof($request)?; - $crate::ledger::queries::require_no_data($request)?; + $crate::queries::require_latest_height(&$ctx, $request)?; + $crate::queries::require_no_proof($request)?; + $crate::queries::require_no_data($request)?; // If you get a compile error from here with `expected function, found // queries::Storage`, you're probably missing the marker `(sub _)` let data = $handle($ctx, $( $matched_args ),* )?; // Encode the returned data with borsh let data = borsh::BorshSerialize::try_to_vec(&data).into_storage_result()?; - return Ok($crate::ledger::queries::EncodedResponseQuery { + return Ok($crate::queries::EncodedResponseQuery { data, info: Default::default(), proof: None, @@ -401,22 +401,22 @@ macro_rules! pattern_and_handler_to_method { `storage_value` and `storage_prefix`) from `storage_value`."] pub async fn storage_value(&self, client: &CLIENT, data: Option>, - height: Option<$crate::types::storage::BlockHeight>, + height: Option, prove: bool, $( $param: &$param_ty ),* ) -> std::result::Result< - $crate::ledger::queries::ResponseQuery>, - ::Error + $crate::queries::ResponseQuery>, + ::Error > - where CLIENT: $crate::ledger::queries::Client + std::marker::Sync { + where CLIENT: $crate::queries::Client + std::marker::Sync { let path = self.storage_value_path( $( $param ),* ); - let $crate::ledger::queries::ResponseQuery { + let $crate::queries::ResponseQuery { data, info, proof } = client.request(path, data, height, prove).await?; - Ok($crate::ledger::queries::ResponseQuery { + Ok($crate::queries::ResponseQuery { data, info, proof, @@ -453,25 +453,25 @@ macro_rules! pattern_and_handler_to_method { `storage_value` and `storage_prefix`) from `" $handle "`."] pub async fn $handle(&self, client: &CLIENT, data: Option>, - height: Option<$crate::types::storage::BlockHeight>, + height: Option, prove: bool, $( $param: &$param_ty ),* ) -> std::result::Result< - $crate::ledger::queries::ResponseQuery<$return_type>, - ::Error + $crate::queries::ResponseQuery<$return_type>, + ::Error > - where CLIENT: $crate::ledger::queries::Client + std::marker::Sync { + where CLIENT: $crate::queries::Client + std::marker::Sync { let path = self.[<$handle _path>]( $( $param ),* ); - let $crate::ledger::queries::ResponseQuery { + let $crate::queries::ResponseQuery { data, info, proof } = client.request(path, data, height, prove).await?; let decoded: $return_type = borsh::BorshDeserialize::try_from_slice(&data[..])?; - Ok($crate::ledger::queries::ResponseQuery { + Ok($crate::queries::ResponseQuery { data: decoded, info, proof, @@ -510,9 +510,9 @@ macro_rules! pattern_and_handler_to_method { ) -> std::result::Result< $return_type, - ::Error + ::Error > - where CLIENT: $crate::ledger::queries::Client + std::marker::Sync { + where CLIENT: $crate::queries::Client + std::marker::Sync { let path = self.[<$handle _path>]( $( $param ),* ); let data = client.simple_request(path).await?; @@ -783,25 +783,25 @@ macro_rules! router { router_type!{[<$name:camel>] {}, $( $pattern $( -> $return_type )? = $handle ),* } - impl $crate::ledger::queries::Router for [<$name:camel>] { + impl $crate::queries::Router for [<$name:camel>] { // TODO: for some patterns, there's unused assignment of `$end` #[allow(unused_assignments)] - fn internal_handle( + fn internal_handle( &self, - ctx: $crate::ledger::queries::RequestCtx<'_, D, H>, - request: &$crate::ledger::queries::RequestQuery, + ctx: $crate::queries::RequestCtx<'_, D, H, V, T>, + request: &$crate::queries::RequestQuery, start: usize - ) -> $crate::ledger::storage_api::Result<$crate::ledger::queries::EncodedResponseQuery> + ) -> namada_core::ledger::storage_api::Result<$crate::queries::EncodedResponseQuery> where - D: 'static + $crate::ledger::storage::DB + for<'iter> $crate::ledger::storage::DBIter<'iter> + Sync, - H: 'static + $crate::ledger::storage::StorageHasher + Sync, + D: 'static + namada_core::ledger::storage::DB + for<'iter> namada_core::ledger::storage::DBIter<'iter> + Sync, + H: 'static + namada_core::ledger::storage::StorageHasher + Sync, { // Import for `.into_storage_result()` - use $crate::ledger::storage_api::ResultExt; + use namada_core::ledger::storage_api::ResultExt; // Import helper from this crate used inside the macros - use $crate::ledger::queries::router::find_next_slash_index; + use $crate::queries::router::find_next_slash_index; $( // This loop never repeats, it's only used for a breaking @@ -816,7 +816,7 @@ macro_rules! router { )* return Err( - $crate::ledger::queries::router::Error::WrongPath(request.path.clone())) + $crate::queries::router::Error::WrongPath(request.path.clone())) .into_storage_result(); } } @@ -835,14 +835,14 @@ macro_rules! router { #[cfg(test)] mod test_rpc_handlers { use borsh::BorshSerialize; + use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; + use namada_core::ledger::storage_api::{self, ResultExt}; + use namada_core::types::storage::Epoch; + use namada_core::types::token; - use crate::ledger::queries::{ + use crate::queries::{ EncodedResponseQuery, RequestCtx, RequestQuery, ResponseQuery, }; - use crate::ledger::storage::{DBIter, StorageHasher, DB}; - use crate::ledger::storage_api::{self, ResultExt}; - use crate::types::storage::Epoch; - use crate::types::token; /// A little macro to generate boilerplate for RPC handler functions. /// These are implemented to return their name as a String, joined by @@ -854,8 +854,8 @@ mod test_rpc_handlers { // optional trailing comma $(,)? ) => { $( - pub fn $name( - _ctx: RequestCtx<'_, D, H>, + pub fn $name( + _ctx: RequestCtx<'_, D, H, V, T>, $( $( $param: $param_ty ),* )? ) -> storage_api::Result where @@ -901,8 +901,8 @@ mod test_rpc_handlers { /// This handler is hand-written, because the test helper macro doesn't /// support optional args. - pub fn b3iii( - _ctx: RequestCtx<'_, D, H>, + pub fn b3iii( + _ctx: RequestCtx<'_, D, H, V, T>, a1: token::DenominatedAmount, a2: token::DenominatedAmount, a3: Option, @@ -920,8 +920,8 @@ mod test_rpc_handlers { /// This handler is hand-written, because the test helper macro doesn't /// support optional args. - pub fn b3iiii( - _ctx: RequestCtx<'_, D, H>, + pub fn b3iiii( + _ctx: RequestCtx<'_, D, H, V, T>, a1: token::DenominatedAmount, a2: token::DenominatedAmount, a3: Option, @@ -941,8 +941,8 @@ mod test_rpc_handlers { /// This handler is hand-written, because the test helper macro doesn't /// support handlers with `with_options`. - pub fn c( - _ctx: RequestCtx<'_, D, H>, + pub fn c( + _ctx: RequestCtx<'_, D, H, V, T>, _request: &RequestQuery, ) -> storage_api::Result where @@ -963,9 +963,10 @@ mod test_rpc_handlers { /// ``` #[cfg(test)] mod test_rpc { + use namada_core::types::storage::Epoch; + use namada_core::types::token; + use super::test_rpc_handlers::*; - use crate::types::storage::Epoch; - use crate::types::token; // Setup an RPC router for testing router! {TEST_RPC, @@ -1000,14 +1001,14 @@ mod test_rpc { #[cfg(test)] mod test { + use namada_core::ledger::storage_api; + use namada_core::types::storage::Epoch; + use namada_core::types::token; use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use super::test_rpc::TEST_RPC; - use crate::ledger::queries::testing::TestClient; - use crate::ledger::queries::{RequestCtx, RequestQuery, Router}; - use crate::ledger::storage_api; - use crate::types::storage::Epoch; - use crate::types::token; + use crate::queries::testing::TestClient; + use crate::queries::{RequestCtx, RequestQuery, Router}; /// Test all the possible paths in `TEST_RPC` router. #[tokio::test] @@ -1022,8 +1023,8 @@ mod test { let ctx = RequestCtx { event_log: &client.event_log, wl_storage: &client.wl_storage, - vp_wasm_cache: client.vp_wasm_cache.clone(), - tx_wasm_cache: client.tx_wasm_cache.clone(), + vp_wasm_cache: (), + tx_wasm_cache: (), storage_read_past_height_limit: None, }; let result = TEST_RPC.handle(ctx, &request); diff --git a/shared/src/ledger/queries/shell.rs b/sdk/src/queries/shell.rs similarity index 58% rename from shared/src/ledger/queries/shell.rs rename to sdk/src/queries/shell.rs index a9f272839f..22ba39feda 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/sdk/src/queries/shell.rs @@ -4,27 +4,27 @@ use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; -use namada_core::ledger::storage::LastBlock; +use namada_core::ledger::storage::traits::StorageHasher; +use namada_core::ledger::storage::{DBIter, LastBlock, DB}; +use namada_core::ledger::storage_api::{self, ResultExt, StorageRead}; use namada_core::types::account::{Account, AccountPublicKeysMap}; use namada_core::types::address::Address; use namada_core::types::hash::Hash; -use namada_core::types::storage::{BlockHeight, BlockResults, KeySeg}; +use namada_core::types::storage::{ + self, BlockHeight, BlockResults, Epoch, KeySeg, PrefixValue, +}; use namada_core::types::token::MaspDenom; +#[cfg(any(test, feature = "async-client"))] +use namada_core::types::transaction::TxResult; use self::eth_bridge::{EthBridge, ETH_BRIDGE}; +use crate::events::log::dumb_queries; +use crate::events::{Event, EventType}; use crate::ibc::core::ics04_channel::packet::Sequence; use crate::ibc::core::ics24_host::identifier::{ChannelId, ClientId, PortId}; -use crate::ledger::events::log::dumb_queries; -use crate::ledger::events::{Event, EventType}; -use crate::ledger::queries::types::{RequestCtx, RequestQuery}; -use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; -use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, DB}; -use crate::ledger::storage_api::{self, ResultExt, StorageRead}; +use crate::queries::types::{RequestCtx, RequestQuery}; +use crate::queries::{require_latest_height, EncodedResponseQuery}; use crate::tendermint::merkle::proof::Proof; -use crate::types::storage::{self, Epoch, PrefixValue}; -#[cfg(any(test, feature = "async-client"))] -use crate::types::transaction::TxResult; type Conversion = ( Address, @@ -94,114 +94,20 @@ router! {SHELL, // Handlers: -#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] -fn dry_run_tx( - mut ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, +fn dry_run_tx( + _ctx: RequestCtx<'_, D, H, V, T>, + _request: &RequestQuery, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - use namada_core::ledger::gas::{Gas, GasMetering, TxGasMeter}; - use namada_core::ledger::storage::TempWlStorage; - use namada_core::types::transaction::DecryptedTx; - - use crate::ledger::protocol::{self, ShellParams}; - use crate::proto::Tx; - use crate::types::storage::TxIndex; - use crate::types::transaction::wrapper::wrapper_tx::PairingEngine; - use crate::types::transaction::{AffineCurve, EllipticCurve, TxType}; - - let mut tx = Tx::try_from(&request.data[..]).into_storage_result()?; - tx.validate_tx().into_storage_result()?; - - let mut temp_wl_storage = TempWlStorage::new(&ctx.wl_storage.storage); - let mut cumulated_gas = Gas::default(); - - // Wrapper dry run to allow estimating the gas cost of a transaction - let mut tx_gas_meter = match tx.header().tx_type { - TxType::Wrapper(wrapper) => { - let mut tx_gas_meter = - TxGasMeter::new(wrapper.gas_limit.to_owned()); - protocol::apply_wrapper_tx( - &wrapper, - None, - &request.data, - ShellParams::new( - &mut tx_gas_meter, - &mut temp_wl_storage, - &mut ctx.vp_wasm_cache, - &mut ctx.tx_wasm_cache, - ), - None, - ) - .into_storage_result()?; - - temp_wl_storage.write_log.commit_tx(); - cumulated_gas = tx_gas_meter.get_tx_consumed_gas(); - - // NOTE: the encryption key for a dry-run should always be an - // hardcoded, dummy one - let _privkey = - ::G2Affine::prime_subgroup_generator(); - tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); - TxGasMeter::new_from_sub_limit(tx_gas_meter.get_available_gas()) - } - TxType::Protocol(_) | TxType::Decrypted(_) => { - // If dry run only the inner tx, use the max block gas as the gas - // limit - TxGasMeter::new( - namada_core::ledger::gas::get_max_block_gas(ctx.wl_storage) - .unwrap() - .into(), - ) - } - TxType::Raw => { - // Cast tx to a decrypted for execution - tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); - - // If dry run only the inner tx, use the max block gas as the gas - // limit - TxGasMeter::new( - namada_core::ledger::gas::get_max_block_gas(ctx.wl_storage) - .unwrap() - .into(), - ) - } - }; - - let mut data = protocol::apply_wasm_tx( - tx, - &TxIndex(0), - ShellParams::new( - &mut tx_gas_meter, - &mut temp_wl_storage, - &mut ctx.vp_wasm_cache, - &mut ctx.tx_wasm_cache, - ), - ) - .into_storage_result()?; - cumulated_gas = cumulated_gas - .checked_add(tx_gas_meter.get_tx_consumed_gas()) - .ok_or(namada_core::ledger::storage_api::Error::SimpleMessage( - "Overflow in gas", - ))?; - // Account gas for both inner and wrapper (if available) - data.gas_used = cumulated_gas; - // NOTE: the keys changed by the wrapper transaction (if any) are not - // returned from this function - let data = data.try_to_vec().into_storage_result()?; - Ok(EncodedResponseQuery { - data, - proof: None, - info: Default::default(), - }) + unimplemented!("Dry running tx requires \"wasm-runtime\" feature.") } /// Query to read block results from storage -pub fn read_results( - ctx: RequestCtx<'_, D, H>, +pub fn read_results( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -237,8 +143,8 @@ where } /// Query to read a conversion from storage -fn read_conversion( - ctx: RequestCtx<'_, D, H>, +fn read_conversion( + ctx: RequestCtx<'_, D, H, V, T>, asset_type: AssetType, ) -> storage_api::Result where @@ -270,19 +176,9 @@ where } } -#[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] -fn dry_run_tx( - _ctx: RequestCtx<'_, D, H>, - _request: &RequestQuery, -) -> storage_api::Result -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - unimplemented!("Dry running tx requires \"wasm-runtime\" feature.") -} - -fn epoch(ctx: RequestCtx<'_, D, H>) -> storage_api::Result +fn epoch( + ctx: RequestCtx<'_, D, H, V, T>, +) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -291,7 +187,9 @@ where Ok(data) } -fn native_token(ctx: RequestCtx<'_, D, H>) -> storage_api::Result
+fn native_token( + ctx: RequestCtx<'_, D, H, V, T>, +) -> storage_api::Result
where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -300,8 +198,8 @@ where Ok(data) } -fn epoch_at_height( - ctx: RequestCtx<'_, D, H>, +fn epoch_at_height( + ctx: RequestCtx<'_, D, H, V, T>, height: BlockHeight, ) -> storage_api::Result> where @@ -311,8 +209,8 @@ where Ok(ctx.wl_storage.storage.block.pred_epochs.get_epoch(height)) } -fn last_block( - ctx: RequestCtx<'_, D, H>, +fn last_block( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -325,8 +223,8 @@ where /// borsh-encoded types, it is safe to check `data.is_empty()` to see if the /// value was found, except for unit - see `fn query_storage_value` in /// `apps/src/lib/client/rpc.rs` for unit type handling via `storage_has_key`. -fn storage_value( - ctx: RequestCtx<'_, D, H>, +fn storage_value( + ctx: RequestCtx<'_, D, H, V, T>, request: &RequestQuery, storage_key: storage::Key, ) -> storage_api::Result @@ -392,8 +290,8 @@ where } } -fn storage_prefix( - ctx: RequestCtx<'_, D, H>, +fn storage_prefix( + ctx: RequestCtx<'_, D, H, V, T>, request: &RequestQuery, storage_key: storage::Key, ) -> storage_api::Result @@ -435,8 +333,8 @@ where }) } -fn storage_has_key( - ctx: RequestCtx<'_, D, H>, +fn storage_has_key( + ctx: RequestCtx<'_, D, H, V, T>, storage_key: storage::Key, ) -> storage_api::Result where @@ -447,8 +345,8 @@ where Ok(data) } -fn accepted( - ctx: RequestCtx<'_, D, H>, +fn accepted( + ctx: RequestCtx<'_, D, H, V, T>, tx_hash: Hash, ) -> storage_api::Result> where @@ -464,8 +362,8 @@ where .cloned()) } -fn applied( - ctx: RequestCtx<'_, D, H>, +fn applied( + ctx: RequestCtx<'_, D, H, V, T>, tx_hash: Hash, ) -> storage_api::Result> where @@ -481,8 +379,8 @@ where .cloned()) } -fn ibc_client_update( - ctx: RequestCtx<'_, D, H>, +fn ibc_client_update( + ctx: RequestCtx<'_, D, H, V, T>, client_id: ClientId, consensus_height: BlockHeight, ) -> storage_api::Result> @@ -502,8 +400,8 @@ where .cloned()) } -fn ibc_packet( - ctx: RequestCtx<'_, D, H>, +fn ibc_packet( + ctx: RequestCtx<'_, D, H, V, T>, event_type: EventType, source_port: PortId, source_channel: ChannelId, @@ -531,8 +429,8 @@ where .cloned()) } -fn account( - ctx: RequestCtx<'_, D, H>, +fn account( + ctx: RequestCtx<'_, D, H, V, T>, owner: Address, ) -> storage_api::Result> where @@ -557,8 +455,8 @@ where } } -fn revealed( - ctx: RequestCtx<'_, D, H>, +fn revealed( + ctx: RequestCtx<'_, D, H, V, T>, owner: Address, ) -> storage_api::Result where @@ -573,18 +471,9 @@ where #[cfg(test)] mod test { - use borsh::{BorshDeserialize, BorshSerialize}; - use namada_test_utils::TestWasms; - - use crate::ledger::queries::testing::TestClient; - use crate::ledger::queries::RPC; - use crate::ledger::storage_api::{self, StorageWrite}; - use crate::proto::{Code, Data, Tx}; - use crate::types::hash::Hash; - use crate::types::storage::Key; - use crate::types::transaction::decrypted::DecryptedTx; - use crate::types::transaction::TxType; - use crate::types::{address, token}; + use namada_core::types::{address, token}; + + use crate::queries::RPC; #[test] fn test_shell_queries_router_paths() { @@ -606,106 +495,4 @@ mod test { let path = RPC.shell().storage_has_key_path(&key); assert_eq!(format!("/shell/has_key/{}", key), path); } - - #[tokio::test] - async fn test_shell_queries_router_with_client() -> storage_api::Result<()> - { - // Initialize the `TestClient` - let mut client = TestClient::new(RPC); - // store the wasm code - let tx_no_op = TestWasms::TxNoOp.read_bytes(); - let tx_hash = Hash::sha256(&tx_no_op); - let key = Key::wasm_code(&tx_hash); - let len_key = Key::wasm_code_len(&tx_hash); - client.wl_storage.storage.write(&key, &tx_no_op).unwrap(); - client - .wl_storage - .storage - .write(&len_key, (tx_no_op.len() as u64).try_to_vec().unwrap()) - .unwrap(); - - // Request last committed epoch - let read_epoch = RPC.shell().epoch(&client).await.unwrap(); - let current_epoch = client.wl_storage.storage.last_epoch; - assert_eq!(current_epoch, read_epoch); - - // Request dry run tx - let mut outer_tx = - Tx::from_type(TxType::Decrypted(DecryptedTx::Decrypted)); - outer_tx.header.chain_id = client.wl_storage.storage.chain_id.clone(); - outer_tx.set_code(Code::from_hash(tx_hash)); - outer_tx.set_data(Data::new(vec![])); - let tx_bytes = outer_tx.to_bytes(); - let result = RPC - .shell() - .dry_run_tx(&client, Some(tx_bytes), None, false) - .await - .unwrap(); - assert!(result.data.is_accepted()); - - // Request storage value for a balance key ... - let token_addr = address::testing::established_address_1(); - let owner = address::testing::established_address_2(); - let balance_key = token::balance_key(&token_addr, &owner); - // ... there should be no value yet. - let read_balance = RPC - .shell() - .storage_value(&client, None, None, false, &balance_key) - .await - .unwrap(); - assert!(read_balance.data.is_empty()); - - // Request storage prefix iterator - let balance_prefix = token::balance_prefix(&token_addr); - let read_balances = RPC - .shell() - .storage_prefix(&client, None, None, false, &balance_prefix) - .await - .unwrap(); - assert!(read_balances.data.is_empty()); - - // Request storage has key - let has_balance_key = RPC - .shell() - .storage_has_key(&client, &balance_key) - .await - .unwrap(); - assert!(!has_balance_key); - - // Then write some balance ... - let balance = token::Amount::native_whole(1000); - StorageWrite::write(&mut client.wl_storage, &balance_key, balance)?; - // It has to be committed to be visible in a query - client.wl_storage.commit_tx(); - client.wl_storage.commit_block().unwrap(); - // ... there should be the same value now - let read_balance = RPC - .shell() - .storage_value(&client, None, None, false, &balance_key) - .await - .unwrap(); - assert_eq!( - balance, - token::Amount::try_from_slice(&read_balance.data).unwrap() - ); - - // Request storage prefix iterator - let balance_prefix = token::balance_prefix(&token_addr); - let read_balances = RPC - .shell() - .storage_prefix(&client, None, None, false, &balance_prefix) - .await - .unwrap(); - assert_eq!(read_balances.data.len(), 1); - - // Request storage has key - let has_balance_key = RPC - .shell() - .storage_has_key(&client, &balance_key) - .await - .unwrap(); - assert!(has_balance_key); - - Ok(()) - } } diff --git a/shared/src/ledger/queries/shell/eth_bridge.rs b/sdk/src/queries/shell/eth_bridge.rs similarity index 96% rename from shared/src/ledger/queries/shell/eth_bridge.rs rename to sdk/src/queries/shell/eth_bridge.rs index 0bbc0aa679..6baf649992 100644 --- a/shared/src/ledger/queries/shell/eth_bridge.rs +++ b/sdk/src/queries/shell/eth_bridge.rs @@ -12,12 +12,17 @@ use namada_core::ledger::storage_api::{ self, CustomError, ResultExt, StorageRead, }; use namada_core::types::address::Address; -use namada_core::types::eth_bridge_pool::PendingTransferAppendix; +use namada_core::types::eth_abi::{Encode, EncodeCell}; +use namada_core::types::eth_bridge_pool::{ + PendingTransfer, PendingTransferAppendix, +}; use namada_core::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; use namada_core::types::ethereum_structs; -use namada_core::types::storage::{BlockHeight, DbKeySeg, Key}; +use namada_core::types::keccak::KeccakHash; +use namada_core::types::storage::MembershipProof::BridgePool; +use namada_core::types::storage::{BlockHeight, DbKeySeg, Epoch, Key}; use namada_core::types::token::Amount; use namada_core::types::vote_extensions::validator_set_update::{ ValidatorSetArgs, VotingPowersMap, @@ -36,12 +41,7 @@ use namada_ethereum_bridge::storage::{ use namada_proof_of_stake::pos_queries::PosQueries; use crate::eth_bridge::ethers::abi::AbiDecode; -use crate::ledger::queries::{EncodedResponseQuery, RequestCtx, RequestQuery}; -use crate::types::eth_abi::{Encode, EncodeCell}; -use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::KeccakHash; -use crate::types::storage::Epoch; -use crate::types::storage::MembershipProof::BridgePool; +use crate::queries::{EncodedResponseQuery, RequestCtx, RequestQuery}; /// Contains information about the flow control of some ERC20 /// wrapped asset. @@ -167,8 +167,8 @@ router! {ETH_BRIDGE, /// Read the total supply and respective cap of some wrapped /// ERC20 token in Namada. -fn get_erc20_flow_control( - ctx: RequestCtx<'_, D, H>, +fn get_erc20_flow_control( + ctx: RequestCtx<'_, D, H, V, T>, asset: EthAddress, ) -> storage_api::Result where @@ -191,9 +191,9 @@ where } /// Helper function to read a smart contract from storage. -fn read_contract( +fn read_contract( key: &Key, - ctx: RequestCtx<'_, D, H>, + ctx: RequestCtx<'_, D, H, V, U>, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -212,8 +212,8 @@ where /// Read the address and version of the Ethereum bridge's Bridge /// smart contract. #[inline] -fn read_bridge_contract( - ctx: RequestCtx<'_, D, H>, +fn read_bridge_contract( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -225,8 +225,8 @@ where /// Read the address of the Ethereum bridge's native ERC20 /// smart contract. #[inline] -fn read_native_erc20_contract( - ctx: RequestCtx<'_, D, H>, +fn read_native_erc20_contract( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -237,8 +237,8 @@ where /// Read the current contents of the Ethereum bridge /// pool. -fn read_ethereum_bridge_pool( - ctx: RequestCtx<'_, D, H>, +fn read_ethereum_bridge_pool( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -252,8 +252,8 @@ where /// Read the contents of the Ethereum bridge /// pool covered by the latest signed root. -fn read_signed_ethereum_bridge_pool( - ctx: RequestCtx<'_, D, H>, +fn read_signed_ethereum_bridge_pool( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -272,9 +272,9 @@ where } /// Read the Ethereum bridge pool contents at a specified height. -fn read_ethereum_bridge_pool_at_height( +fn read_ethereum_bridge_pool_at_height( height: BlockHeight, - ctx: RequestCtx<'_, D, H>, + ctx: RequestCtx<'_, D, H, V, T>, ) -> Vec where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -311,8 +311,8 @@ where /// Generate a merkle proof for the inclusion of the /// requested transfers in the Ethereum bridge pool. -fn generate_bridge_pool_proof( - ctx: RequestCtx<'_, D, H>, +fn generate_bridge_pool_proof( + ctx: RequestCtx<'_, D, H, V, T>, request: &RequestQuery, ) -> storage_api::Result where @@ -444,8 +444,8 @@ where /// Iterates over all ethereum events /// and returns the amount of voting power /// backing each `TransferToEthereum` event. -fn transfer_to_ethereum_progress( - ctx: RequestCtx<'_, D, H>, +fn transfer_to_ethereum_progress( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -518,8 +518,8 @@ where /// /// This method may fail if a complete proof (i.e. with more than /// 2/3 of the total voting power behind it) is not available yet. -fn read_valset_upd_proof( - ctx: RequestCtx<'_, D, H>, +fn read_valset_upd_proof( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Epoch, ) -> storage_api::Result>> where @@ -568,8 +568,8 @@ where /// /// This method may fail if no set of validators exists yet, /// at that [`Epoch`]. -fn read_bridge_valset( - ctx: RequestCtx<'_, D, H>, +fn read_bridge_valset( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Epoch, ) -> storage_api::Result where @@ -598,8 +598,8 @@ where /// /// This method may fail if no set of validators exists yet, /// at that [`Epoch`]. -fn read_governance_valset( - ctx: RequestCtx<'_, D, H>, +fn read_governance_valset( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Epoch, ) -> storage_api::Result where @@ -626,8 +626,8 @@ where /// Retrieve the consensus validator voting powers at the /// given [`BlockHeight`]. -fn voting_powers_at_height( - ctx: RequestCtx<'_, D, H>, +fn voting_powers_at_height( + ctx: RequestCtx<'_, D, H, V, T>, height: BlockHeight, ) -> storage_api::Result where @@ -645,8 +645,8 @@ where /// Retrieve the consensus validator voting powers at the /// given [`Epoch`]. -fn voting_powers_at_epoch( - ctx: RequestCtx<'_, D, H>, +fn voting_powers_at_epoch( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Epoch, ) -> storage_api::Result where @@ -678,7 +678,13 @@ mod test_ethbridge_router { use namada_core::ledger::eth_bridge::storage::whitelist; use namada_core::ledger::storage::mockdb::MockDBWriteBatch; use namada_core::ledger::storage_api::StorageWrite; + use namada_core::types::address::nam; use namada_core::types::address::testing::established_address_1; + use namada_core::types::eth_abi::Encode; + use namada_core::types::eth_bridge_pool::{ + GasFee, PendingTransfer, TransferToEthereum, TransferToEthereumKind, + }; + use namada_core::types::ethereum_events::EthAddress; use namada_core::types::storage::BlockHeight; use namada_core::types::vote_extensions::validator_set_update; use namada_core::types::vote_extensions::validator_set_update::{ @@ -693,14 +699,8 @@ mod test_ethbridge_router { use super::test_utils::bertha_address; use super::*; - use crate::ledger::queries::testing::TestClient; - use crate::ledger::queries::RPC; - use crate::types::address::nam; - use crate::types::eth_abi::Encode; - use crate::types::eth_bridge_pool::{ - GasFee, PendingTransfer, TransferToEthereum, TransferToEthereumKind, - }; - use crate::types::ethereum_events::EthAddress; + use crate::queries::testing::TestClient; + use crate::queries::RPC; /// Test that reading the bridge validator set works. #[tokio::test] diff --git a/shared/src/ledger/queries/types.rs b/sdk/src/queries/types.rs similarity index 88% rename from shared/src/ledger/queries/types.rs rename to sdk/src/queries/types.rs index 235bf76e99..7283982099 100644 --- a/shared/src/ledger/queries/types.rs +++ b/sdk/src/queries/types.rs @@ -1,21 +1,16 @@ use std::fmt::Debug; -use namada_core::ledger::storage::WlStorage; +use namada_core::ledger::storage::{DBIter, StorageHasher, WlStorage, DB}; +use namada_core::ledger::storage_api; +use namada_core::types::storage::BlockHeight; use thiserror::Error; -use crate::ledger::events::log::EventLog; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; -use crate::ledger::storage_api; +use crate::events::log::EventLog; use crate::tendermint::merkle::proof::Proof; -use crate::types::storage::BlockHeight; -#[cfg(feature = "wasm-runtime")] -use crate::vm::wasm::{TxCache, VpCache}; -#[cfg(feature = "wasm-runtime")] -use crate::vm::WasmCacheRoAccess; /// A request context provides read-only access to storage and WASM compilation /// caches to request handlers. #[derive(Debug, Clone)] -pub struct RequestCtx<'shell, D, H> +pub struct RequestCtx<'shell, D, H, VpCache, TxCache> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -25,11 +20,9 @@ where /// Log of events emitted by `FinalizeBlock` ABCI calls. pub event_log: &'shell EventLog, /// Cache of VP wasm compiled artifacts. - #[cfg(feature = "wasm-runtime")] - pub vp_wasm_cache: VpCache, + pub vp_wasm_cache: VpCache, /// Cache of transaction wasm compiled artifacts. - #[cfg(feature = "wasm-runtime")] - pub tx_wasm_cache: TxCache, + pub tx_wasm_cache: TxCache, /// Taken from config `storage_read_past_height_limit`. When set, will /// limit the how many block heights in the past can the storage be /// queried for reading values. @@ -41,9 +34,9 @@ where pub trait Router { /// Handle a given request using the provided context. This must be invoked /// on the root `Router` to be able to match the `request.path` fully. - fn handle( + fn handle( &self, - ctx: RequestCtx<'_, D, H>, + ctx: RequestCtx<'_, D, H, V, T>, request: &RequestQuery, ) -> storage_api::Result where @@ -59,9 +52,9 @@ pub trait Router { /// Handle a given request using the provided context, starting to /// try to match `request.path` against the `Router`'s patterns at the /// given `start` offset. - fn internal_handle( + fn internal_handle( &self, - ctx: RequestCtx<'_, D, H>, + ctx: RequestCtx<'_, D, H, V, T>, request: &RequestQuery, start: usize, ) -> storage_api::Result diff --git a/shared/src/ledger/queries/vp/governance.rs b/sdk/src/queries/vp/governance.rs similarity index 75% rename from shared/src/ledger/queries/vp/governance.rs rename to sdk/src/queries/vp/governance.rs index 92c3495f24..1e3a5a8ece 100644 --- a/shared/src/ledger/queries/vp/governance.rs +++ b/sdk/src/queries/vp/governance.rs @@ -1,12 +1,12 @@ // cd shared && cargo expand ledger::queries::vp::governance +use namada_core::ledger::governance::parameters::GovernanceParameters; use namada_core::ledger::governance::storage::proposal::StorageProposal; use namada_core::ledger::governance::utils::Vote; +use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; +use namada_core::ledger::storage_api; -use crate::core::ledger::governance::parameters::GovernanceParameters; -use crate::ledger::queries::types::RequestCtx; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; -use crate::ledger::storage_api; +use crate::queries::types::RequestCtx; // Governance queries router! {GOV, @@ -16,8 +16,8 @@ router! {GOV, } /// Find if the given address belongs to a validator account. -fn proposal_id( - ctx: RequestCtx<'_, D, H>, +fn proposal_id( + ctx: RequestCtx<'_, D, H, V, T>, id: u64, ) -> storage_api::Result> where @@ -28,8 +28,8 @@ where } /// Find if the given address belongs to a validator account. -fn proposal_id_votes( - ctx: RequestCtx<'_, D, H>, +fn proposal_id_votes( + ctx: RequestCtx<'_, D, H, V, T>, id: u64, ) -> storage_api::Result> where @@ -40,8 +40,8 @@ where } /// Get the governane parameters -fn parameters( - ctx: RequestCtx<'_, D, H>, +fn parameters( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, diff --git a/shared/src/ledger/queries/vp/mod.rs b/sdk/src/queries/vp/mod.rs similarity index 100% rename from shared/src/ledger/queries/vp/mod.rs rename to sdk/src/queries/vp/mod.rs diff --git a/shared/src/ledger/queries/vp/pgf.rs b/sdk/src/queries/vp/pgf.rs similarity index 76% rename from shared/src/ledger/queries/vp/pgf.rs rename to sdk/src/queries/vp/pgf.rs index 8f5b14c91b..9e8ea2f5cc 100644 --- a/shared/src/ledger/queries/vp/pgf.rs +++ b/sdk/src/queries/vp/pgf.rs @@ -1,11 +1,11 @@ use namada_core::ledger::governance::storage::proposal::StoragePgfFunding; +use namada_core::ledger::pgf::parameters::PgfParameters; use namada_core::ledger::pgf::storage::steward::StewardDetail; +use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; +use namada_core::ledger::storage_api; use namada_core::types::address::Address; -use crate::core::ledger::pgf::parameters::PgfParameters; -use crate::ledger::queries::types::RequestCtx; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; -use crate::ledger::storage_api; +use crate::queries::types::RequestCtx; // PoS validity predicate queries router! {PGF, @@ -16,8 +16,8 @@ router! {PGF, } /// Query the currect pgf steward set -fn stewards( - ctx: RequestCtx<'_, D, H>, +fn stewards( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -27,8 +27,8 @@ where } /// Check if an address is a pgf steward -fn is_steward( - ctx: RequestCtx<'_, D, H>, +fn is_steward( + ctx: RequestCtx<'_, D, H, V, T>, address: Address, ) -> storage_api::Result where @@ -39,8 +39,8 @@ where } /// Query the continous pgf fundings -fn funding( - ctx: RequestCtx<'_, D, H>, +fn funding( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -50,8 +50,8 @@ where } /// Query the PGF parameters -fn parameters( - ctx: RequestCtx<'_, D, H>, +fn parameters( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, diff --git a/shared/src/ledger/queries/vp/pos.rs b/sdk/src/queries/vp/pos.rs similarity index 90% rename from shared/src/ledger/queries/vp/pos.rs rename to sdk/src/queries/vp/pos.rs index e78bff146b..875779447d 100644 --- a/shared/src/ledger/queries/vp/pos.rs +++ b/sdk/src/queries/vp/pos.rs @@ -3,8 +3,13 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; +use namada_core::ledger::storage_api; use namada_core::ledger::storage_api::collections::lazy_map; use namada_core::ledger::storage_api::OptionExt; +use namada_core::types::address::Address; +use namada_core::types::storage::Epoch; +use namada_core::types::token; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{ BondId, BondsAndUnbondsDetail, BondsAndUnbondsDetails, CommissionPair, @@ -21,12 +26,7 @@ use namada_proof_of_stake::{ validator_slashes_handle, validator_state_handle, }; -use crate::ledger::queries::types::RequestCtx; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; -use crate::ledger::storage_api; -use crate::types::address::Address; -use crate::types::storage::Epoch; -use crate::types::token; +use crate::queries::types::RequestCtx; type AmountPair = (token::Amount, token::Amount); @@ -148,7 +148,9 @@ impl Enriched { // Handlers that implement the functions via `trait StorageRead`: /// Get the PoS parameters -fn pos_params(ctx: RequestCtx<'_, D, H>) -> storage_api::Result +fn pos_params( + ctx: RequestCtx<'_, D, H, V, T>, +) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -157,8 +159,8 @@ where } /// Find if the given address belongs to a validator account. -fn is_validator( - ctx: RequestCtx<'_, D, H>, +fn is_validator( + ctx: RequestCtx<'_, D, H, V, T>, addr: Address, ) -> storage_api::Result where @@ -169,8 +171,8 @@ where } /// Find if the given address is a delegator -fn is_delegator( - ctx: RequestCtx<'_, D, H>, +fn is_delegator( + ctx: RequestCtx<'_, D, H, V, T>, addr: Address, epoch: Option, ) -> storage_api::Result @@ -183,8 +185,8 @@ where /// Get all the validator known addresses. These validators may be in any state, /// e.g. consensus, below-capacity, inactive or jailed. -fn validator_addresses( - ctx: RequestCtx<'_, D, H>, +fn validator_addresses( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Option, ) -> storage_api::Result> where @@ -196,8 +198,8 @@ where } /// Get the validator commission rate and max commission rate change per epoch -fn validator_commission( - ctx: RequestCtx<'_, D, H>, +fn validator_commission( + ctx: RequestCtx<'_, D, H, V, T>, validator: Address, epoch: Option, ) -> storage_api::Result> @@ -227,8 +229,8 @@ where } /// Get the validator state -fn validator_state( - ctx: RequestCtx<'_, D, H>, +fn validator_state( + ctx: RequestCtx<'_, D, H, V, T>, validator: Address, epoch: Option, ) -> storage_api::Result> @@ -251,8 +253,8 @@ where /// to their address. /// Returns `None` when the given address is not a validator address. For a /// validator with `0` stake, this returns `Ok(token::Amount::zero())`. -fn validator_stake( - ctx: RequestCtx<'_, D, H>, +fn validator_stake( + ctx: RequestCtx<'_, D, H, V, T>, validator: Address, epoch: Option, ) -> storage_api::Result> @@ -266,8 +268,8 @@ where } /// Get all the validator in the consensus set with their bonded stake. -fn consensus_validator_set( - ctx: RequestCtx<'_, D, H>, +fn consensus_validator_set( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Option, ) -> storage_api::Result> where @@ -279,8 +281,8 @@ where } /// Get all the validator in the below-capacity set with their bonded stake. -fn below_capacity_validator_set( - ctx: RequestCtx<'_, D, H>, +fn below_capacity_validator_set( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Option, ) -> storage_api::Result> where @@ -295,8 +297,8 @@ where } /// Get the total stake in PoS system at the given epoch or current when `None`. -fn total_stake( - ctx: RequestCtx<'_, D, H>, +fn total_stake( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Option, ) -> storage_api::Result where @@ -308,8 +310,8 @@ where read_total_stake(ctx.wl_storage, ¶ms, epoch) } -fn bond_deltas( - ctx: RequestCtx<'_, D, H>, +fn bond_deltas( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, ) -> storage_api::Result> @@ -322,8 +324,8 @@ where /// Find the sum of bond amount up the given epoch when `Some`, or up to the /// pipeline length parameter offset otherwise -fn bond( - ctx: RequestCtx<'_, D, H>, +fn bond( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, epoch: Option, @@ -343,8 +345,8 @@ where .ok_or_err_msg("Cannot find bond") } -fn bond_with_slashing( - ctx: RequestCtx<'_, D, H>, +fn bond_with_slashing( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, epoch: Option, @@ -359,8 +361,8 @@ where bond_amount(ctx.wl_storage, &bond_id, epoch) } -fn unbond( - ctx: RequestCtx<'_, D, H>, +fn unbond( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, ) -> storage_api::Result> @@ -384,8 +386,8 @@ where .collect() } -fn unbond_with_slashing( - ctx: RequestCtx<'_, D, H>, +fn unbond_with_slashing( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, ) -> storage_api::Result> @@ -410,8 +412,8 @@ where .collect() } -fn withdrawable_tokens( - ctx: RequestCtx<'_, D, H>, +fn withdrawable_tokens( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, epoch: Option, @@ -439,8 +441,8 @@ where Ok(total) } -fn bonds_and_unbonds( - ctx: RequestCtx<'_, D, H>, +fn bonds_and_unbonds( + ctx: RequestCtx<'_, D, H, V, T>, source: Option
, validator: Option
, ) -> storage_api::Result @@ -453,8 +455,8 @@ where /// Find all the validator addresses to whom the given `owner` address has /// some delegation in any epoch -fn delegation_validators( - ctx: RequestCtx<'_, D, H>, +fn delegation_validators( + ctx: RequestCtx<'_, D, H, V, T>, owner: Address, ) -> storage_api::Result> where @@ -466,8 +468,8 @@ where /// Find all the validator addresses to whom the given `owner` address has /// some delegation in any epoch -fn delegations( - ctx: RequestCtx<'_, D, H>, +fn delegations( + ctx: RequestCtx<'_, D, H, V, T>, owner: Address, epoch: Option, ) -> storage_api::Result> @@ -480,8 +482,8 @@ where } /// Validator slashes -fn validator_slashes( - ctx: RequestCtx<'_, D, H>, +fn validator_slashes( + ctx: RequestCtx<'_, D, H, V, T>, validator: Address, ) -> storage_api::Result> where @@ -493,8 +495,8 @@ where } /// All slashes -fn slashes( - ctx: RequestCtx<'_, D, H>, +fn slashes( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result>> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -504,8 +506,8 @@ where } /// Enqueued slashes -fn enqueued_slashes( - ctx: RequestCtx<'_, D, H>, +fn enqueued_slashes( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result>>> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -516,8 +518,8 @@ where } /// Native validator address by looking up the Tendermint address -fn validator_by_tm_addr( - ctx: RequestCtx<'_, D, H>, +fn validator_by_tm_addr( + ctx: RequestCtx<'_, D, H, V, T>, tm_addr: String, ) -> storage_api::Result> where @@ -531,8 +533,7 @@ where #[cfg(any(test, feature = "async-client"))] pub mod client_only_methods { use super::*; - use crate::ledger::queries::RPC; - use crate::sdk::queries::Client; + use crate::queries::{Client, RPC}; impl Pos { /// Get bonds and unbonds with all details (slashes and rewards, if any) diff --git a/shared/src/ledger/queries/vp/token.rs b/sdk/src/queries/vp/token.rs similarity index 87% rename from shared/src/ledger/queries/vp/token.rs rename to sdk/src/queries/vp/token.rs index 3b99cb0fda..0a2a5df509 100644 --- a/shared/src/ledger/queries/vp/token.rs +++ b/sdk/src/queries/vp/token.rs @@ -6,7 +6,7 @@ use namada_core::ledger::storage_api::token::read_denom; use namada_core::types::address::Address; use namada_core::types::token; -use crate::ledger::queries::RequestCtx; +use crate::queries::RequestCtx; router! {TOKEN, ( "denomination" / [addr: Address] ) -> Option = denomination, @@ -14,8 +14,8 @@ router! {TOKEN, /// Get the number of decimal places (in base 10) for a /// token specified by `addr`. -fn denomination( - ctx: RequestCtx<'_, D, H>, +fn denomination( + ctx: RequestCtx<'_, D, H, V, T>, addr: Address, ) -> storage_api::Result> where @@ -28,12 +28,11 @@ where #[cfg(any(test, feature = "async-client"))] pub mod client_only_methods { use borsh::BorshDeserialize; + use namada_core::types::address::Address; + use namada_core::types::token; use super::Token; - use crate::ledger::queries::RPC; - use crate::sdk::queries::Client; - use crate::types::address::Address; - use crate::types::token; + use crate::queries::{Client, RPC}; impl Token { /// Get the balance of the given `token` belonging to the given `owner`. diff --git a/shared/src/sdk/rpc.rs b/sdk/src/rpc.rs similarity index 89% rename from shared/src/sdk/rpc.rs rename to sdk/src/rpc.rs index e7da6dcbae..9307374663 100644 --- a/shared/src/sdk/rpc.rs +++ b/sdk/src/rpc.rs @@ -14,37 +14,35 @@ use namada_core::ledger::governance::utils::Vote; use namada_core::ledger::storage::LastBlock; use namada_core::types::account::Account; use namada_core::types::address::Address; -use namada_core::types::storage::Key; +use namada_core::types::hash::Hash; +use namada_core::types::key::common; +use namada_core::types::storage::{ + BlockHeight, BlockResults, Epoch, Key, PrefixValue, +}; use namada_core::types::token::{ Amount, DenominatedAmount, Denomination, MaspDenom, }; +use namada_core::types::{storage, token}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{ BondsAndUnbondsDetails, CommissionPair, ValidatorState, }; use serde::Serialize; -use crate::ledger::events::Event; -use crate::ledger::queries::vp::pos::EnrichedBondsAndUnbondsDetails; -use crate::ledger::queries::RPC; -use crate::ledger::Namada; +use crate::args::InputAmount; +use crate::control_flow::{time, Halt, TryHalt}; +use crate::error::{EncodingError, Error, QueryError}; +use crate::events::Event; +use crate::io::Io; use crate::proto::Tx; -use crate::sdk::args::InputAmount; -use crate::sdk::error; -use crate::sdk::error::{EncodingError, Error, QueryError}; -use crate::sdk::queries::Client; +use crate::queries::vp::pos::EnrichedBondsAndUnbondsDetails; +use crate::queries::{Client, RPC}; use crate::tendermint::block::Height; use crate::tendermint::merkle::proof::Proof; use crate::tendermint_rpc::error::Error as TError; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; -use crate::types::control_flow::{time, Halt, TryHalt}; -use crate::types::hash::Hash; -use crate::types::io::Io; -use crate::types::key::common; -use crate::types::storage::{BlockHeight, BlockResults, Epoch, PrefixValue}; -use crate::types::{storage, token}; -use crate::{display_line, edisplay_line}; +use crate::{display_line, edisplay_line, error, Namada}; /// Query the status of a given transaction. /// @@ -97,14 +95,14 @@ pub async fn query_tx_status<'a>( } /// Query the epoch of the last committed block -pub async fn query_epoch( +pub async fn query_epoch( client: &C, ) -> Result { convert_response::(RPC.shell().epoch(client).await) } /// Query the address of the native token -pub async fn query_native_token( +pub async fn query_native_token( client: &C, ) -> Result { convert_response::(RPC.shell().native_token(client).await) @@ -113,7 +111,7 @@ pub async fn query_native_token( /// Query the epoch of the given block height, if it exists. /// Will return none if the input block height is greater than /// the latest committed block height. -pub async fn query_epoch_at_height( +pub async fn query_epoch_at_height( client: &C, height: BlockHeight, ) -> Result, error::Error> { @@ -121,7 +119,7 @@ pub async fn query_epoch_at_height( } /// Query the last committed block, if any. -pub async fn query_block( +pub async fn query_block( client: &C, ) -> Result, error::Error> { // NOTE: We're not using `client.latest_block()` because it may return an @@ -130,7 +128,7 @@ pub async fn query_block( } /// A helper to unwrap client's response. Will shut down process on error. -fn unwrap_client_response( +fn unwrap_client_response( response: Result, ) -> T { response.unwrap_or_else(|err| { @@ -141,21 +139,21 @@ fn unwrap_client_response( /// A helper to turn client's response into an error type that can be used with /// ? The exact error type is a `QueryError::NoResponse`, and thus should be /// seen as getting no response back from a query. -fn convert_response( +fn convert_response( response: Result, ) -> Result { response.map_err(|err| Error::from(QueryError::NoResponse(err.to_string()))) } /// Query the results of the last committed block -pub async fn query_results( +pub async fn query_results( client: &C, ) -> Result, Error> { convert_response::(RPC.shell().read_results(client).await) } /// Query token amount of owner. -pub async fn get_token_balance( +pub async fn get_token_balance( client: &C, token: &Address, owner: &Address, @@ -166,7 +164,7 @@ pub async fn get_token_balance( } /// Check if the given address is a known validator. -pub async fn is_validator( +pub async fn is_validator( client: &C, address: &Address, ) -> Result { @@ -174,7 +172,7 @@ pub async fn is_validator( } /// Check if the given address is a pgf steward. -pub async fn is_steward( +pub async fn is_steward( client: &C, address: &Address, ) -> bool { @@ -184,7 +182,7 @@ pub async fn is_steward( } /// Check if a given address is a known delegator -pub async fn is_delegator( +pub async fn is_delegator( client: &C, address: &Address, ) -> Result { @@ -194,7 +192,7 @@ pub async fn is_delegator( } /// Check if a given address is a known delegator at the given epoch -pub async fn is_delegator_at( +pub async fn is_delegator_at( client: &C, address: &Address, epoch: Epoch, @@ -210,7 +208,7 @@ pub async fn is_delegator_at( /// Check if the address exists on chain. Established address exists if it has a /// stored validity predicate. Implicit and internal addresses always return /// true. -pub async fn known_address( +pub async fn known_address( client: &C, address: &Address, ) -> Result { @@ -228,7 +226,7 @@ pub async fn known_address( // often ignore the optional value and do not have any error type surrounding // it. /// Query a conversion. -pub async fn query_conversion( +pub async fn query_conversion( client: &C, asset_type: AssetType, ) -> Option<( @@ -275,7 +273,7 @@ pub async fn query_storage_value( ) -> Result where T: BorshDeserialize, - C: crate::ledger::queries::Client + Sync, + C: crate::queries::Client + Sync, { // In case `T` is a unit (only thing that encodes to 0 bytes), we have to // use `storage_has_key` instead of `storage_value`, because `storage_value` @@ -306,9 +304,7 @@ where } /// Query a storage value and the proof without decoding. -pub async fn query_storage_value_bytes< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn query_storage_value_bytes( client: &C, key: &storage::Key, height: Option, @@ -365,7 +361,7 @@ where } /// Query to check if the given storage key exists. -pub async fn query_has_storage_key( +pub async fn query_has_storage_key( client: &C, key: &storage::Key, ) -> Result { @@ -415,13 +411,10 @@ impl<'a> From> for Query { /// Call the corresponding `tx_event_query` RPC method, to fetch /// the current status of a transation. -pub async fn query_tx_events( +pub async fn query_tx_events( client: &C, tx_event_query: TxEventQuery<'_>, -) -> std::result::Result< - Option, - ::Error, -> { +) -> std::result::Result, ::Error> { let tx_hash: Hash = tx_event_query.tx_hash().try_into().unwrap(); match tx_event_query { TxEventQuery::Accepted(_) => { @@ -557,7 +550,7 @@ impl TxResponse { /// Lookup the full response accompanying the specified transaction event // TODO: maybe remove this in favor of `query_tx_status` -pub async fn query_tx_response( +pub async fn query_tx_response( client: &C, tx_query: TxEventQuery<'_>, ) -> Result { @@ -627,14 +620,14 @@ pub async fn query_tx_response( } /// Get the PoS parameters -pub async fn get_pos_params( +pub async fn get_pos_params( client: &C, ) -> Result { convert_response::(RPC.vp().pos().pos_params(client).await) } /// Get all validators in the given epoch -pub async fn get_all_validators( +pub async fn get_all_validators( client: &C, epoch: Epoch, ) -> Result, error::Error> { @@ -647,9 +640,7 @@ pub async fn get_all_validators( } /// Get the total staked tokens in the given epoch -pub async fn get_total_staked_tokens< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn get_total_staked_tokens( client: &C, epoch: Epoch, ) -> Result { @@ -659,7 +650,7 @@ pub async fn get_total_staked_tokens< } /// Get the given validator's stake at the given epoch -pub async fn get_validator_stake( +pub async fn get_validator_stake( client: &C, epoch: Epoch, validator: &Address, @@ -674,7 +665,7 @@ pub async fn get_validator_stake( } /// Query and return a validator's state -pub async fn get_validator_state( +pub async fn get_validator_state( client: &C, validator: &Address, epoch: Option, @@ -688,9 +679,7 @@ pub async fn get_validator_state( } /// Get the delegator's delegation -pub async fn get_delegators_delegation< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn get_delegators_delegation( client: &C, address: &Address, ) -> Result, error::Error> { @@ -700,9 +689,7 @@ pub async fn get_delegators_delegation< } /// Get the delegator's delegation at some epoh -pub async fn get_delegators_delegation_at< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn get_delegators_delegation_at( client: &C, address: &Address, epoch: Epoch, @@ -716,7 +703,7 @@ pub async fn get_delegators_delegation_at< } /// Query proposal by Id -pub async fn query_proposal_by_id( +pub async fn query_proposal_by_id( client: &C, proposal_id: u64, ) -> Result, Error> { @@ -727,7 +714,7 @@ pub async fn query_proposal_by_id( /// Query and return validator's commission rate and max commission rate change /// per epoch -pub async fn query_commission_rate( +pub async fn query_commission_rate( client: &C, validator: &Address, epoch: Option, @@ -741,7 +728,7 @@ pub async fn query_commission_rate( } /// Query a validator's bonds for a given epoch -pub async fn query_bond( +pub async fn query_bond( client: &C, source: &Address, validator: &Address, @@ -753,7 +740,7 @@ pub async fn query_bond( } /// Query the accunt substorage space of an address -pub async fn get_account_info( +pub async fn get_account_info( client: &C, owner: &Address, ) -> Result, error::Error> { @@ -763,9 +750,7 @@ pub async fn get_account_info( } /// Query if the public_key is revealed -pub async fn is_public_key_revealed< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn is_public_key_revealed( client: &C, owner: &Address, ) -> Result { @@ -773,7 +758,7 @@ pub async fn is_public_key_revealed< } /// Query an account substorage at a specific index -pub async fn get_public_key_at( +pub async fn get_public_key_at( client: &C, owner: &Address, index: u8, @@ -830,9 +815,7 @@ pub async fn query_and_print_unbonds<'a>( } /// Query withdrawable tokens in a validator account for a given epoch -pub async fn query_withdrawable_tokens< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn query_withdrawable_tokens( client: &C, bond_source: &Address, validator: &Address, @@ -847,9 +830,7 @@ pub async fn query_withdrawable_tokens< } /// Query all unbonds for a validator, applying slashes -pub async fn query_unbond_with_slashing< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn query_unbond_with_slashing( client: &C, source: &Address, validator: &Address, @@ -863,16 +844,14 @@ pub async fn query_unbond_with_slashing< } /// Get the givernance parameters -pub async fn query_governance_parameters< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn query_governance_parameters( client: &C, ) -> GovernanceParameters { unwrap_client_response::(RPC.vp().gov().parameters(client).await) } /// Get the givernance parameters -pub async fn query_proposal_votes( +pub async fn query_proposal_votes( client: &C, proposal_id: u64, ) -> Result, error::Error> { @@ -882,7 +861,7 @@ pub async fn query_proposal_votes( } /// Get the bond amount at the given epoch -pub async fn get_bond_amount_at( +pub async fn get_bond_amount_at( client: &C, delegator: &Address, validator: &Address, @@ -899,7 +878,7 @@ pub async fn get_bond_amount_at( /// Get bonds and unbonds with all details (slashes and rewards, if any) /// grouped by their bond IDs. -pub async fn bonds_and_unbonds( +pub async fn bonds_and_unbonds( client: &C, source: &Option
, validator: &Option
, @@ -915,9 +894,7 @@ pub async fn bonds_and_unbonds( /// Get bonds and unbonds with all details (slashes and rewards, if any) /// grouped by their bond IDs, enriched with extra information calculated from /// the data. -pub async fn enriched_bonds_and_unbonds< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn enriched_bonds_and_unbonds( client: &C, current_epoch: Epoch, source: &Option
, diff --git a/shared/src/sdk/signing.rs b/sdk/src/signing.rs similarity index 98% rename from shared/src/sdk/signing.rs rename to sdk/src/signing.rs index 4edc5e4f41..7680adfdd2 100644 --- a/shared/src/sdk/signing.rs +++ b/sdk/src/signing.rs @@ -9,53 +9,50 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::transaction::components::sapling::fees::{ InputView, OutputView, }; +use namada_core::ledger::parameters::storage as parameter_storage; use namada_core::proto::SignatureIndex; use namada_core::types::account::AccountPublicKeysMap; use namada_core::types::address::{ masp, masp_tx_key, Address, ImplicitAddress, }; +use namada_core::types::key::*; +use namada_core::types::masp::{ExtendedViewingKey, PaymentAddress}; +use namada_core::types::storage::Epoch; use namada_core::types::token; +use namada_core::types::token::Transfer; // use namada_core::types::storage::Key; use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::pos; +use namada_core::types::transaction::account::{InitAccount, UpdateAccount}; +use namada_core::types::transaction::governance::{ + InitProposalData, VoteProposalData, +}; +use namada_core::types::transaction::pos::InitValidator; +use namada_core::types::transaction::{pos, Fee}; use prost::Message; use serde::{Deserialize, Serialize}; use sha2::Digest; use zeroize::Zeroizing; use super::masp::{ShieldedContext, ShieldedTransfer}; -use crate::display_line; +use crate::args::SdkTypes; +use crate::error::{EncodingError, Error, TxError}; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc_proto::google::protobuf::Any; -use crate::ledger::parameters::storage as parameter_storage; -use crate::ledger::Namada; +use crate::io::*; +use crate::masp::make_asset_type; use crate::proto::{MaspBuilder, Section, Tx}; -use crate::sdk::args::SdkTypes; -use crate::sdk::error::{EncodingError, Error, TxError}; -use crate::sdk::masp::make_asset_type; -use crate::sdk::rpc::{ +use crate::rpc::{ format_denominated_amount, query_wasm_code_hash, validate_amount, }; -use crate::sdk::tx::{ +use crate::tx::{ TX_BOND_WASM, TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL, TX_INIT_VALIDATOR_WASM, TX_REVEAL_PK, TX_TRANSFER_WASM, TX_UNBOND_WASM, TX_UPDATE_ACCOUNT_WASM, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, VP_USER_WASM, }; -pub use crate::sdk::wallet::store::AddressVpType; -use crate::sdk::wallet::{Wallet, WalletIo}; -use crate::sdk::{args, rpc}; -use crate::types::io::*; -use crate::types::key::*; -use crate::types::masp::{ExtendedViewingKey, PaymentAddress}; -use crate::types::storage::Epoch; -use crate::types::token::Transfer; -use crate::types::transaction::account::{InitAccount, UpdateAccount}; -use crate::types::transaction::governance::{ - InitProposalData, VoteProposalData, -}; -use crate::types::transaction::pos::InitValidator; -use crate::types::transaction::Fee; +pub use crate::wallet::store::AddressVpType; +use crate::wallet::{Wallet, WalletIo}; +use crate::{args, display_line, rpc, Namada}; #[cfg(feature = "std")] /// Env. var specifying where to store signing test vectors diff --git a/shared/src/sdk/tx.rs b/sdk/src/tx.rs similarity index 96% rename from shared/src/sdk/tx.rs rename to sdk/src/tx.rs index 66b8847b17..b4b059e249 100644 --- a/shared/src/sdk/tx.rs +++ b/sdk/src/tx.rs @@ -16,57 +16,56 @@ use masp_primitives::transaction::components::transparent::fees::{ InputView as TransparentInputView, OutputView as TransparentOutputView, }; use masp_primitives::transaction::components::I32Sum; +use namada_core::ibc::applications::transfer::msgs::transfer::MsgTransfer; +use namada_core::ibc::applications::transfer::packet::PacketData; +use namada_core::ibc::applications::transfer::PrefixedCoin; +use namada_core::ibc::core::ics04_channel::timeout::TimeoutHeight; +use namada_core::ibc::core::timestamp::Timestamp as IbcTimestamp; +use namada_core::ibc::core::Msg; +use namada_core::ibc::Height as IbcHeight; use namada_core::ledger::governance::cli::onchain::{ DefaultProposal, OnChainProposal, PgfFundingProposal, PgfStewardProposal, ProposalVote, }; use namada_core::ledger::governance::storage::proposal::ProposalType; use namada_core::ledger::governance::storage::vote::StorageProposalVote; +use namada_core::ledger::ibc::storage::ibc_denom_key; use namada_core::ledger::pgf::cli::steward::Commission; use namada_core::types::address::{masp, Address, InternalAddress}; use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; +use namada_core::types::key::*; +use namada_core::types::masp::TransferTarget; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; use namada_core::types::token::MaspDenom; +use namada_core::types::transaction::account::{InitAccount, UpdateAccount}; use namada_core::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use namada_core::types::transaction::pgf::UpdateStewardCommission; +use namada_core::types::transaction::{pos, TxType}; +use namada_core::types::{storage, token}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{CommissionPair, ValidatorState}; -use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; -use crate::ibc::applications::transfer::packet::PacketData; -use crate::ibc::applications::transfer::PrefixedCoin; -use crate::ibc::core::ics04_channel::timeout::TimeoutHeight; -use crate::ibc::core::timestamp::Timestamp as IbcTimestamp; -use crate::ibc::core::Msg; -use crate::ibc::Height as IbcHeight; -use crate::ledger::ibc::storage::ibc_denom_key; -use crate::ledger::Namada; +use crate::args::{self, InputAmount}; +use crate::control_flow::{time, ProceedOrElse}; +use crate::error::{EncodingError, Error, QueryError, Result, TxError}; +use crate::io::Io; +use crate::masp::TransferErr::Build; +use crate::masp::{ShieldedContext, ShieldedTransfer}; use crate::proto::{MaspBuilder, Tx}; -use crate::sdk::args::{self, InputAmount}; -use crate::sdk::error::{EncodingError, Error, QueryError, Result, TxError}; -use crate::sdk::masp::TransferErr::Build; -use crate::sdk::masp::{ShieldedContext, ShieldedTransfer}; -use crate::sdk::queries::Client; -use crate::sdk::rpc::{ +use crate::queries::Client; +use crate::rpc::{ self, format_denominated_amount, query_wasm_code_hash, validate_amount, TxBroadcastData, TxResponse, }; -use crate::sdk::signing::{self, SigningTxData, TxSourcePostBalance}; -use crate::sdk::wallet::WalletIo; +use crate::signing::{self, SigningTxData, TxSourcePostBalance}; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::tendermint_rpc::error::Error as RpcError; -use crate::types::control_flow::{time, ProceedOrElse}; -use crate::types::io::Io; -use crate::types::key::*; -use crate::types::masp::TransferTarget; -use crate::types::storage::Epoch; -use crate::types::time::DateTimeUtc; -use crate::types::transaction::account::{InitAccount, UpdateAccount}; -use crate::types::transaction::{pos, TxType}; -use crate::types::{storage, token}; -use crate::{display_line, edisplay_line, vm}; +use crate::wallet::WalletIo; +use crate::{display_line, edisplay_line, Namada}; /// Initialize account transaction WASM pub const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; @@ -235,20 +234,20 @@ pub async fn process_tx<'a>( } /// Check if a reveal public key transaction is needed -pub async fn is_reveal_pk_needed( +pub async fn is_reveal_pk_needed( client: &C, address: &Address, force: bool, ) -> Result where - C: crate::sdk::queries::Client + Sync, + C: crate::queries::Client + Sync, { // Check if PK revealed Ok(force || !has_revealed_pk(client, address).await?) } /// Check if the public key for the given address has been revealed -pub async fn has_revealed_pk( +pub async fn has_revealed_pk( client: &C, address: &Address, ) -> Result { @@ -759,7 +758,7 @@ pub async fn build_unjail_validator<'a>( } let last_slash_epoch_key = - crate::ledger::pos::validator_last_slash_key(validator); + namada_proof_of_stake::storage::validator_last_slash_key(validator); let last_slash_epoch = rpc::query_storage_value::<_, Epoch>( context.client(), &last_slash_epoch_key, @@ -1496,7 +1495,7 @@ pub async fn build_ibc_transfer<'a>( #[allow(clippy::too_many_arguments)] pub async fn build<'a, F, D>( context: &impl Namada<'a>, - tx_args: &crate::sdk::args::Tx, + tx_args: &crate::args::Tx, path: PathBuf, data: D, on_tx: F, @@ -1522,7 +1521,7 @@ where #[allow(clippy::too_many_arguments)] async fn build_pow_flag<'a, F, D>( context: &impl Namada<'a>, - tx_args: &crate::sdk::args::Tx, + tx_args: &crate::args::Tx, path: PathBuf, mut data: D, on_tx: F, @@ -2123,27 +2122,6 @@ async fn check_balance_too_low_err<'a, N: Namada<'a>>( } } -#[allow(dead_code)] -fn validate_untrusted_code_err( - io: &IO, - vp_code: &Vec, - force: bool, -) -> Result<()> { - if let Err(err) = vm::validate_untrusted_wasm(vp_code) { - if force { - edisplay_line!( - io, - "Validity predicate code validation failed with {}", - err - ); - Ok(()) - } else { - Err(Error::from(TxError::WasmValidationFailure(err))) - } - } else { - Ok(()) - } -} async fn query_wasm_code_hash_buf<'a>( context: &impl Namada<'a>, path: &Path, diff --git a/shared/src/sdk/wallet/alias.rs b/sdk/src/wallet/alias.rs similarity index 100% rename from shared/src/sdk/wallet/alias.rs rename to sdk/src/wallet/alias.rs diff --git a/shared/src/sdk/wallet/derivation_path.rs b/sdk/src/wallet/derivation_path.rs similarity index 98% rename from shared/src/sdk/wallet/derivation_path.rs rename to sdk/src/wallet/derivation_path.rs index 7f639161d2..7751e51701 100644 --- a/shared/src/sdk/wallet/derivation_path.rs +++ b/sdk/src/wallet/derivation_path.rs @@ -2,6 +2,7 @@ use core::fmt; use std::str::FromStr; use derivation_path::{ChildIndex, DerivationPath as DerivationPathInner}; +use namada_core::types::key::SchemeType; use thiserror::Error; use tiny_hderive::bip44::{ DerivationPath as HDeriveDerivationPath, @@ -9,8 +10,6 @@ use tiny_hderive::bip44::{ }; use tiny_hderive::Error as HDeriveError; -use crate::types::key::SchemeType; - const ETH_COIN_TYPE: u32 = 60; const NAMADA_COIN_TYPE: u32 = 877; @@ -114,8 +113,9 @@ impl IntoHDeriveDerivationPath for DerivationPath { #[cfg(test)] mod tests { + use namada_core::types::key::SchemeType; + use super::DerivationPath; - use crate::types::key::SchemeType; #[test] fn path_is_compatible() { diff --git a/shared/src/sdk/wallet/keys.rs b/sdk/src/wallet/keys.rs similarity index 99% rename from shared/src/sdk/wallet/keys.rs rename to sdk/src/wallet/keys.rs index 749fa1e25f..a8d267c898 100644 --- a/shared/src/sdk/wallet/keys.rs +++ b/sdk/src/wallet/keys.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use zeroize::Zeroizing; -use crate::sdk::wallet::WalletIo; +use crate::wallet::WalletIo; const ENCRYPTED_KEY_PREFIX: &str = "encrypted:"; const UNENCRYPTED_KEY_PREFIX: &str = "unencrypted:"; diff --git a/shared/src/sdk/wallet/mod.rs b/sdk/src/wallet/mod.rs similarity index 99% rename from shared/src/sdk/wallet/mod.rs rename to sdk/src/wallet/mod.rs index cadfdc718c..4ee3e13947 100644 --- a/shared/src/sdk/wallet/mod.rs +++ b/sdk/src/wallet/mod.rs @@ -13,6 +13,11 @@ use alias::Alias; use bip39::{Language, Mnemonic, MnemonicType, Seed}; use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::zip32::ExtendedFullViewingKey; +use namada_core::types::address::Address; +use namada_core::types::key::*; +use namada_core::types::masp::{ + ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, +}; pub use pre_genesis::gen_key_to_store; use rand_core::RngCore; pub use store::{gen_sk_rng, AddressVpType, Store}; @@ -22,11 +27,6 @@ use zeroize::Zeroizing; use self::derivation_path::{DerivationPath, DerivationPathError}; pub use self::keys::{DecryptionError, StoredKeypair}; pub use self::store::{ConfirmationResponse, ValidatorData, ValidatorKeys}; -use crate::types::address::Address; -use crate::types::key::*; -use crate::types::masp::{ - ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, -}; /// Errors of key generation / recovery #[derive(Error, Debug)] diff --git a/shared/src/sdk/wallet/pre_genesis.rs b/sdk/src/wallet/pre_genesis.rs similarity index 96% rename from shared/src/sdk/wallet/pre_genesis.rs rename to sdk/src/wallet/pre_genesis.rs index fd66dedbfe..916ec43781 100644 --- a/shared/src/sdk/wallet/pre_genesis.rs +++ b/sdk/src/wallet/pre_genesis.rs @@ -1,11 +1,11 @@ //! Provides functionality for managing validator keys +use namada_core::types::key::{common, SchemeType}; use serde::{Deserialize, Serialize}; use thiserror::Error; use zeroize::Zeroizing; -use crate::sdk::wallet; -use crate::sdk::wallet::{store, StoredKeypair}; -use crate::types::key::{common, SchemeType}; +use crate::wallet; +use crate::wallet::{store, StoredKeypair}; /// Ways in which wallet store operations can fail #[derive(Error, Debug)] diff --git a/shared/src/sdk/wallet/store.rs b/sdk/src/wallet/store.rs similarity index 99% rename from shared/src/sdk/wallet/store.rs rename to sdk/src/wallet/store.rs index b674391127..201cc885a4 100644 --- a/shared/src/sdk/wallet/store.rs +++ b/sdk/src/wallet/store.rs @@ -8,6 +8,12 @@ use bimap::BiHashMap; use bip39::Seed; use itertools::Itertools; use masp_primitives::zip32::ExtendedFullViewingKey; +use namada_core::types::address::{Address, ImplicitAddress}; +use namada_core::types::key::dkg_session_keys::DkgKeypair; +use namada_core::types::key::*; +use namada_core::types::masp::{ + ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, +}; #[cfg(feature = "masp-tx-gen")] use rand_core::RngCore; use serde::{Deserialize, Serialize}; @@ -17,13 +23,7 @@ use zeroize::Zeroizing; use super::alias::{self, Alias}; use super::derivation_path::DerivationPath; use super::pre_genesis; -use crate::sdk::wallet::{StoredKeypair, WalletIo}; -use crate::types::address::{Address, ImplicitAddress}; -use crate::types::key::dkg_session_keys::DkgKeypair; -use crate::types::key::*; -use crate::types::masp::{ - ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, -}; +use crate::wallet::{StoredKeypair, WalletIo}; /// Actions that can be taken when there is an alias conflict pub enum ConfirmationResponse { diff --git a/shared/Cargo.toml b/shared/Cargo.toml index f3c5428594..21eb023e18 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -24,6 +24,7 @@ std = ["fd-lock"] dev = [] ferveo-tpke = [ "namada_core/ferveo-tpke", + "namada_sdk/ferveo-tpke", ] wasm-runtime = [ "namada_core/wasm-runtime", @@ -41,6 +42,7 @@ wasm-runtime = [ # Enable queries support for an async client async-client = [ "async-trait", + "namada_sdk/async-client" ] # Requires async traits to be safe to send across threads @@ -50,6 +52,7 @@ async-send = [] tendermint-rpc = [ "async-client", "dep:tendermint-rpc", + "namada_sdk/tendermint-rpc", ] # tendermint-rpc HttpClient http-client = [ @@ -60,15 +63,18 @@ abciplus = [ "namada_core/abciplus", "namada_proof_of_stake/abciplus", "namada_ethereum_bridge/abciplus", + "namada_sdk/abciplus", ] ibc-mocks = [ "namada_core/ibc-mocks", + "namada_sdk/ibc-mocks", ] masp-tx-gen = [ "rand", "rand_core", + "namada_sdk/masp-tx-gen", ] # for integration tests and test utilies @@ -76,6 +82,7 @@ testing = [ "namada_core/testing", "namada_ethereum_bridge/testing", "namada_proof_of_stake/testing", + "namada_sdk/testing", "async-client", "proptest", "rand_core", @@ -87,13 +94,18 @@ namada-sdk = [ "tendermint-rpc", "masp-tx-gen", "ferveo-tpke", - "masp_primitives/transparent-inputs" + "masp_primitives/transparent-inputs", + "namada_sdk/namada-sdk", ] -multicore = ["masp_proofs/multicore"] +multicore = [ + "masp_proofs/multicore", + "namada_sdk/multicore", +] [dependencies] namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign"]} +namada_sdk = {path = "../sdk", default-features = false} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} namada_ethereum_bridge = {path = "../ethereum_bridge", default-features = false} async-trait = {version = "0.1.51", optional = true} diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index d4b4d1316c..a254556cce 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use namada_core::ledger::governance::utils::TallyResult; +use namada_sdk::events::{Event, EventLevel}; use thiserror::Error; use crate::ledger::events::EventType; @@ -34,6 +35,16 @@ pub struct ProposalEvent { pub attributes: HashMap, } +impl From for Event { + fn from(proposal_event: ProposalEvent) -> Self { + Self { + event_type: EventType::Proposal, + level: EventLevel::Block, + attributes: proposal_event.attributes, + } + } +} + impl ProposalEvent { /// Create a proposal event pub fn new( diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index aecde2d930..676d57190f 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -1,7 +1,6 @@ //! The ledger modules -pub mod eth_bridge; -pub mod events; +pub use namada_sdk::{eth_bridge, events}; pub mod governance; pub mod ibc; pub mod inflation; @@ -10,535 +9,367 @@ pub mod pgf; pub mod pos; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] pub mod protocol; -pub mod queries; +pub use namada_sdk::queries; pub mod storage; pub mod vp_host_fns; -use std::path::PathBuf; -use std::str::FromStr; - +use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; +use namada_core::ledger::storage_api::ResultExt; pub use namada_core::ledger::{ gas, parameters, replay_protection, storage_api, tx_env, vp_env, }; -use namada_core::types::dec::Dec; -use namada_core::types::ethereum_events::EthAddress; -use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - -use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; -use crate::proto::Tx; -use crate::sdk::args::{self, InputAmount, SdkTypes}; -use crate::sdk::masp::{ShieldedContext, ShieldedUtils}; -use crate::sdk::rpc::query_native_token; -use crate::sdk::signing::{self, SigningTxData}; -use crate::sdk::tx::{ - self, ProcessTxResponse, TX_BOND_WASM, TX_BRIDGE_POOL_WASM, - TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, TX_INIT_PROPOSAL, - TX_INIT_VALIDATOR_WASM, TX_RESIGN_STEWARD, TX_REVEAL_PK, TX_TRANSFER_WASM, - TX_UNBOND_WASM, TX_UNJAIL_VALIDATOR_WASM, TX_UPDATE_ACCOUNT_WASM, - TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, - VP_USER_WASM, -}; -use crate::sdk::wallet::{Wallet, WalletIo, WalletStorage}; -use crate::types::address::Address; -use crate::types::io::Io; -use crate::types::key::*; -use crate::types::masp::{TransferSource, TransferTarget}; -use crate::types::token; -use crate::types::token::NATIVE_MAX_DECIMAL_PLACES; -use crate::types::transaction::GasLimit; - -#[async_trait::async_trait(?Send)] -/// An interface for high-level interaction with the Namada SDK -pub trait Namada<'a>: Sized { - /// A client with async request dispatcher method - type Client: 'a + crate::ledger::queries::Client + Sync; - /// Captures the interactive parts of the wallet's functioning - type WalletUtils: 'a + WalletIo + WalletStorage; - /// Abstracts platform specific details away from the logic of shielded pool - /// operations. - type ShieldedUtils: 'a + ShieldedUtils; - /// Captures the input/output streams used by this object - type Io: 'a + Io; - - /// Obtain the client for communicating with the ledger - fn client(&self) -> &'a Self::Client; - - /// Obtain the input/output handle for this context - fn io(&self) -> &'a Self::Io; - - /// Obtain read guard on the wallet - async fn wallet( - &self, - ) -> RwLockReadGuard<&'a mut Wallet>; - - /// Obtain write guard on the wallet - async fn wallet_mut( - &self, - ) -> RwLockWriteGuard<&'a mut Wallet>; - - /// Obtain read guard on the shielded context - async fn shielded( - &self, - ) -> RwLockReadGuard<&'a mut ShieldedContext>; - - /// Obtain write guard on the shielded context - async fn shielded_mut( - &self, - ) -> RwLockWriteGuard<&'a mut ShieldedContext>; - - /// Return the native token - fn native_token(&self) -> Address; - - /// Make a tx builder using no arguments - fn tx_builder(&self) -> args::Tx { - args::Tx { - dry_run: false, - dry_run_wrapper: false, - dump_tx: false, - output_folder: None, - force: false, - broadcast_only: false, - ledger_address: (), - initialized_account_alias: None, - wallet_alias_force: false, - fee_amount: None, - wrapper_fee_payer: None, - fee_token: self.native_token(), - fee_unshield: None, - gas_limit: GasLimit::from(20_000), - expiration: None, - disposable_signing_key: false, - chain_id: None, - signing_keys: vec![], - signatures: vec![], - tx_reveal_code_path: PathBuf::from(TX_REVEAL_PK), - verification_key: None, - password: None, - } - } - - /// Make a TxTransfer builder from the given minimum set of arguments - fn new_transfer( - &self, - source: TransferSource, - target: TransferTarget, - token: Address, - amount: InputAmount, - ) -> args::TxTransfer { - args::TxTransfer { - source, - target, - token, - amount, - tx_code_path: PathBuf::from(TX_TRANSFER_WASM), - tx: self.tx_builder(), - native_token: self.native_token(), - } - } - - /// Make a RevealPK builder from the given minimum set of arguments - fn new_reveal_pk(&self, public_key: common::PublicKey) -> args::RevealPk { - args::RevealPk { - public_key, - tx: self.tx_builder(), - } - } - - /// Make a Bond builder from the given minimum set of arguments - fn new_bond( - &self, - validator: Address, - amount: token::Amount, - ) -> args::Bond { - args::Bond { - validator, - amount, - source: None, - tx: self.tx_builder(), - native_token: self.native_token(), - tx_code_path: PathBuf::from(TX_BOND_WASM), - } - } - - /// Make a Unbond builder from the given minimum set of arguments - fn new_unbond( - &self, - validator: Address, - amount: token::Amount, - ) -> args::Unbond { - args::Unbond { - validator, - amount, - source: None, - tx: self.tx_builder(), - tx_code_path: PathBuf::from(TX_UNBOND_WASM), - } - } - - /// Make a TxIbcTransfer builder from the given minimum set of arguments - fn new_ibc_transfer( - &self, - source: Address, - receiver: String, - token: Address, - amount: InputAmount, - channel_id: ChannelId, - ) -> args::TxIbcTransfer { - args::TxIbcTransfer { - source, - receiver, - token, - amount, - channel_id, - port_id: PortId::from_str("transfer").unwrap(), - timeout_height: None, - timeout_sec_offset: None, - memo: None, - tx: self.tx_builder(), - tx_code_path: PathBuf::from(TX_IBC_WASM), - } - } - - /// Make a InitProposal builder from the given minimum set of arguments - fn new_init_proposal(&self, proposal_data: Vec) -> args::InitProposal { - args::InitProposal { - proposal_data, - native_token: self.native_token(), - is_offline: false, - is_pgf_stewards: false, - is_pgf_funding: false, - tx_code_path: PathBuf::from(TX_INIT_PROPOSAL), - tx: self.tx_builder(), - } - } - - /// Make a TxUpdateAccount builder from the given minimum set of arguments - fn new_update_account(&self, addr: Address) -> args::TxUpdateAccount { - args::TxUpdateAccount { - addr, - vp_code_path: None, - public_keys: vec![], - threshold: None, - tx_code_path: PathBuf::from(TX_UPDATE_ACCOUNT_WASM), - tx: self.tx_builder(), - } - } - - /// Make a VoteProposal builder from the given minimum set of arguments - fn new_vote_prposal( - &self, - vote: String, - voter: Address, - ) -> args::VoteProposal { - args::VoteProposal { - vote, - voter, - proposal_id: None, - is_offline: false, - proposal_data: None, - tx_code_path: PathBuf::from(TX_VOTE_PROPOSAL), - tx: self.tx_builder(), - } - } +use namada_sdk::queries::{EncodedResponseQuery, RequestCtx, RequestQuery}; - /// Make a CommissionRateChange builder from the given minimum set of - /// arguments - fn new_change_commission_rate( - &self, - rate: Dec, - validator: Address, - ) -> args::CommissionRateChange { - args::CommissionRateChange { - rate, - validator, - tx_code_path: PathBuf::from(TX_CHANGE_COMMISSION_WASM), - tx: self.tx_builder(), - } - } +#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] +use crate::vm::wasm::{TxCache, VpCache}; +use crate::vm::WasmCacheAccess; - /// Make a TxInitValidator builder from the given minimum set of arguments - fn new_init_validator( - &self, - commission_rate: Dec, - max_commission_rate_change: Dec, - ) -> args::TxInitValidator { - args::TxInitValidator { - commission_rate, - max_commission_rate_change, - scheme: SchemeType::Ed25519, - account_keys: vec![], - threshold: None, - consensus_key: None, - eth_cold_key: None, - eth_hot_key: None, - protocol_key: None, - validator_vp_code_path: PathBuf::from(VP_USER_WASM), - unsafe_dont_encrypt: false, - tx_code_path: PathBuf::from(TX_INIT_VALIDATOR_WASM), - tx: self.tx_builder(), +/// Dry run a transaction +#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] +pub fn dry_run_tx( + mut ctx: RequestCtx<'_, D, H, VpCache, TxCache>, + request: &RequestQuery, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + CA: 'static + WasmCacheAccess + Sync, +{ + use borsh::BorshSerialize; + use namada_core::ledger::gas::{Gas, GasMetering, TxGasMeter}; + use namada_core::ledger::storage::TempWlStorage; + use namada_core::proto::Tx; + use namada_core::types::transaction::wrapper::wrapper_tx::PairingEngine; + use namada_core::types::transaction::{ + AffineCurve, DecryptedTx, EllipticCurve, + }; + + use crate::ledger::protocol::ShellParams; + use crate::types::storage::TxIndex; + use crate::types::transaction::TxType; + + let mut tx = Tx::try_from(&request.data[..]).into_storage_result()?; + tx.validate_tx().into_storage_result()?; + + let mut temp_wl_storage = TempWlStorage::new(&ctx.wl_storage.storage); + let mut cumulated_gas = Gas::default(); + + // Wrapper dry run to allow estimating the gas cost of a transaction + let mut tx_gas_meter = match tx.header().tx_type { + TxType::Wrapper(wrapper) => { + let mut tx_gas_meter = + TxGasMeter::new(wrapper.gas_limit.to_owned()); + protocol::apply_wrapper_tx( + &wrapper, + None, + &request.data, + ShellParams::new( + &mut tx_gas_meter, + &mut temp_wl_storage, + &mut ctx.vp_wasm_cache, + &mut ctx.tx_wasm_cache, + ), + None, + ) + .into_storage_result()?; + + temp_wl_storage.write_log.commit_tx(); + cumulated_gas = tx_gas_meter.get_tx_consumed_gas(); + + // NOTE: the encryption key for a dry-run should always be an + // hardcoded, dummy one + let _privkey = + ::G2Affine::prime_subgroup_generator(); + tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); + TxGasMeter::new_from_sub_limit(tx_gas_meter.get_available_gas()) } - } - - /// Make a TxUnjailValidator builder from the given minimum set of arguments - fn new_unjail_validator( - &self, - validator: Address, - ) -> args::TxUnjailValidator { - args::TxUnjailValidator { - validator, - tx_code_path: PathBuf::from(TX_UNJAIL_VALIDATOR_WASM), - tx: self.tx_builder(), + TxType::Protocol(_) | TxType::Decrypted(_) => { + // If dry run only the inner tx, use the max block gas as the gas + // limit + TxGasMeter::new( + namada_core::ledger::gas::get_max_block_gas(ctx.wl_storage) + .unwrap() + .into(), + ) } - } - - /// Make a Withdraw builder from the given minimum set of arguments - fn new_withdraw(&self, validator: Address) -> args::Withdraw { - args::Withdraw { - validator, - source: None, - tx_code_path: PathBuf::from(TX_WITHDRAW_WASM), - tx: self.tx_builder(), + TxType::Raw => { + // Cast tx to a decrypted for execution + tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); + + // If dry run only the inner tx, use the max block gas as the gas + // limit + TxGasMeter::new( + namada_core::ledger::gas::get_max_block_gas(ctx.wl_storage) + .unwrap() + .into(), + ) } - } - - /// Make a Withdraw builder from the given minimum set of arguments - fn new_add_erc20_transfer( - &self, - sender: Address, - recipient: EthAddress, - asset: EthAddress, - amount: InputAmount, - ) -> args::EthereumBridgePool { - args::EthereumBridgePool { - sender, - recipient, - asset, - amount, - fee_amount: InputAmount::Unvalidated(token::DenominatedAmount { - amount: token::Amount::default(), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), - }), - fee_payer: None, - fee_token: self.native_token(), - nut: false, - code_path: PathBuf::from(TX_BRIDGE_POOL_WASM), - tx: self.tx_builder(), - } - } + }; + + let mut data = protocol::apply_wasm_tx( + tx, + &TxIndex(0), + ShellParams::new( + &mut tx_gas_meter, + &mut temp_wl_storage, + &mut ctx.vp_wasm_cache, + &mut ctx.tx_wasm_cache, + ), + ) + .into_storage_result()?; + cumulated_gas = cumulated_gas + .checked_add(tx_gas_meter.get_tx_consumed_gas()) + .ok_or(namada_core::ledger::storage_api::Error::SimpleMessage( + "Overflow in gas", + ))?; + // Account gas for both inner and wrapper (if available) + data.gas_used = cumulated_gas; + // NOTE: the keys changed by the wrapper transaction (if any) are not + // returned from this function + let data = data.try_to_vec().into_storage_result()?; + Ok(EncodedResponseQuery { + data, + proof: None, + info: Default::default(), + }) +} - /// Make a ResignSteward builder from the given minimum set of arguments - fn new_resign_steward(&self, steward: Address) -> args::ResignSteward { - args::ResignSteward { - steward, - tx: self.tx_builder(), - tx_code_path: PathBuf::from(TX_RESIGN_STEWARD), - } +#[cfg(test)] +mod test { + use borsh::{BorshDeserialize, BorshSerialize}; + use namada_core::ledger::storage::testing::TestWlStorage; + use namada_core::ledger::storage_api::{self, StorageWrite}; + use namada_core::types::hash::Hash; + use namada_core::types::storage::{BlockHeight, Key}; + use namada_core::types::transaction::decrypted::DecryptedTx; + use namada_core::types::transaction::TxType; + use namada_core::types::{address, token}; + use namada_sdk::queries::{Router, RPC}; + use namada_test_utils::TestWasms; + use tempfile::TempDir; + use tendermint_rpc::{Error as RpcError, Response}; + + use crate::ledger::events::log::EventLog; + use crate::ledger::queries::Client; + use crate::ledger::{EncodedResponseQuery, RequestCtx, RequestQuery}; + use crate::proto::{Code, Data, Tx}; + use crate::vm::wasm::{TxCache, VpCache}; + use crate::vm::{wasm, WasmCacheRoAccess}; + + /// A test client that has direct access to the storage + pub struct TestClient + where + RPC: Router, + { + /// RPC router + pub rpc: RPC, + /// storage + pub wl_storage: TestWlStorage, + /// event log + pub event_log: EventLog, + /// VP wasm compilation cache + pub vp_wasm_cache: VpCache, + /// tx wasm compilation cache + pub tx_wasm_cache: TxCache, + /// VP wasm compilation cache directory + pub vp_cache_dir: TempDir, + /// tx wasm compilation cache directory + pub tx_cache_dir: TempDir, } - /// Make a UpdateStewardCommission builder from the given minimum set of - /// arguments - fn new_update_steward_rewards( - &self, - steward: Address, - commission: Vec, - ) -> args::UpdateStewardCommission { - args::UpdateStewardCommission { - steward, - commission, - tx: self.tx_builder(), - tx_code_path: PathBuf::from(TX_UPDATE_STEWARD_COMMISSION), + impl TestClient + where + RPC: Router, + { + #[allow(dead_code)] + /// Initialize a test client for the given root RPC router + pub fn new(rpc: RPC) -> Self { + // Initialize the `TestClient` + let mut wl_storage = TestWlStorage::default(); + + // Initialize mock gas limit + let max_block_gas_key = + namada_core::ledger::parameters::storage::get_max_block_gas_key( + ); + wl_storage + .storage + .write( + &max_block_gas_key, + namada_core::ledger::storage::types::encode( + &20_000_000_u64, + ), + ) + .expect( + "Max block gas parameter must be initialized in storage", + ); + let event_log = EventLog::default(); + let (vp_wasm_cache, vp_cache_dir) = + wasm::compilation_cache::common::testing::cache(); + let (tx_wasm_cache, tx_cache_dir) = + wasm::compilation_cache::common::testing::cache(); + Self { + rpc, + wl_storage, + event_log, + vp_wasm_cache: vp_wasm_cache.read_only(), + tx_wasm_cache: tx_wasm_cache.read_only(), + vp_cache_dir, + tx_cache_dir, + } } } - /// Make a TxCustom builder from the given minimum set of arguments - fn new_custom(&self, owner: Address) -> args::TxCustom { - args::TxCustom { - owner, - tx: self.tx_builder(), - code_path: None, - data_path: None, - serialized_tx: None, + #[cfg_attr(feature = "async-send", async_trait::async_trait)] + #[cfg_attr(not(feature = "async-send"), async_trait::async_trait(?Send))] + impl Client for TestClient + where + RPC: Router + Sync, + { + type Error = std::io::Error; + + async fn request( + &self, + path: String, + data: Option>, + height: Option, + prove: bool, + ) -> Result { + let data = data.unwrap_or_default(); + let height = height.unwrap_or_default(); + // Handle a path by invoking the `RPC.handle` directly with the + // borrowed storage + let request = RequestQuery { + data, + path, + height, + prove, + }; + let ctx = RequestCtx { + wl_storage: &self.wl_storage, + event_log: &self.event_log, + vp_wasm_cache: self.vp_wasm_cache.clone(), + tx_wasm_cache: self.tx_wasm_cache.clone(), + storage_read_past_height_limit: None, + }; + // TODO: this is a hack to propagate errors to the caller, we should + // really permit error types other than [`std::io::Error`] + if request.path == "/shell/dry_run_tx" { + super::dry_run_tx(ctx, &request) + } else { + self.rpc.handle(ctx, &request) + } + .map_err(|err| { + std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) + }) } - } - - /// Sign the given transaction using the given signing data - async fn sign( - &self, - tx: &mut Tx, - args: &args::Tx, - signing_data: SigningTxData, - ) -> crate::sdk::error::Result<()> { - signing::sign_tx(*self.wallet_mut().await, args, tx, signing_data) - } - /// Process the given transaction using the given flags - async fn submit( - &self, - tx: Tx, - args: &args::Tx, - ) -> crate::sdk::error::Result { - tx::process_tx(self, args, tx).await - } -} - -/// Provides convenience methods for common Namada interactions -pub struct NamadaImpl<'a, C, U, V, I> -where - C: crate::ledger::queries::Client + Sync, - U: WalletIo, - V: ShieldedUtils, - I: Io, -{ - /// Used to send and receive messages from the ledger - pub client: &'a C, - /// Stores the addresses and keys required for ledger interactions - pub wallet: RwLock<&'a mut Wallet>, - /// Stores the current state of the shielded pool - pub shielded: RwLock<&'a mut ShieldedContext>, - /// Captures the input/output streams used by this object - pub io: &'a I, - /// The address of the native token - native_token: Address, - /// The default builder for a Tx - prototype: args::Tx, -} - -impl<'a, C, U, V, I> NamadaImpl<'a, C, U, V, I> -where - C: crate::ledger::queries::Client + Sync, - U: WalletIo, - V: ShieldedUtils, - I: Io, -{ - /// Construct a new Namada context with the given native token address - pub fn native_new( - client: &'a C, - wallet: &'a mut Wallet, - shielded: &'a mut ShieldedContext, - io: &'a I, - native_token: Address, - ) -> Self { - NamadaImpl { - client, - wallet: RwLock::new(wallet), - shielded: RwLock::new(shielded), - io, - native_token: native_token.clone(), - prototype: args::Tx { - dry_run: false, - dry_run_wrapper: false, - dump_tx: false, - output_folder: None, - force: false, - broadcast_only: false, - ledger_address: (), - initialized_account_alias: None, - wallet_alias_force: false, - fee_amount: None, - wrapper_fee_payer: None, - fee_token: native_token, - fee_unshield: None, - gas_limit: GasLimit::from(20_000), - expiration: None, - disposable_signing_key: false, - chain_id: None, - signing_keys: vec![], - signatures: vec![], - tx_reveal_code_path: PathBuf::from(TX_REVEAL_PK), - verification_key: None, - password: None, - }, + async fn perform(&self, _request: R) -> Result + where + R: tendermint_rpc::SimpleRequest, + { + Response::from_string("TODO") } } - /// Construct a new Namada context looking up the native token address - pub async fn new( - client: &'a C, - wallet: &'a mut Wallet, - shielded: &'a mut ShieldedContext, - io: &'a I, - ) -> crate::sdk::error::Result> { - let native_token = query_native_token(client).await?; - Ok(NamadaImpl::native_new( - client, - wallet, - shielded, - io, - native_token, - )) - } -} - -#[async_trait::async_trait(?Send)] -impl<'a, C, U, V, I> Namada<'a> for NamadaImpl<'a, C, U, V, I> -where - C: crate::ledger::queries::Client + Sync, - U: WalletIo + WalletStorage, - V: ShieldedUtils, - I: Io, -{ - type Client = C; - type Io = I; - type ShieldedUtils = V; - type WalletUtils = U; - - /// Obtain the prototypical Tx builder - fn tx_builder(&self) -> args::Tx { - self.prototype.clone() - } - - fn native_token(&self) -> Address { - self.native_token.clone() - } - - fn io(&self) -> &'a Self::Io { - self.io - } - - fn client(&self) -> &'a Self::Client { - self.client - } - - async fn wallet( - &self, - ) -> RwLockReadGuard<&'a mut Wallet> { - self.wallet.read().await - } - - async fn wallet_mut( - &self, - ) -> RwLockWriteGuard<&'a mut Wallet> { - self.wallet.write().await - } - - async fn shielded( - &self, - ) -> RwLockReadGuard<&'a mut ShieldedContext> { - self.shielded.read().await - } - - async fn shielded_mut( - &self, - ) -> RwLockWriteGuard<&'a mut ShieldedContext> { - self.shielded.write().await - } -} - -/// Allow the prototypical Tx builder to be modified -impl<'a, C, U, V, I> args::TxBuilder for NamadaImpl<'a, C, U, V, I> -where - C: crate::ledger::queries::Client + Sync, - U: WalletIo, - V: ShieldedUtils, - I: Io, -{ - fn tx(self, func: F) -> Self - where - F: FnOnce(args::Tx) -> args::Tx, + #[tokio::test] + async fn test_shell_queries_router_with_client() -> storage_api::Result<()> { - Self { - prototype: func(self.prototype), - ..self - } + // Initialize the `TestClient` + let mut client = TestClient::new(RPC); + // store the wasm code + let tx_no_op = TestWasms::TxNoOp.read_bytes(); + let tx_hash = Hash::sha256(&tx_no_op); + let key = Key::wasm_code(&tx_hash); + let len_key = Key::wasm_code_len(&tx_hash); + client.wl_storage.storage.write(&key, &tx_no_op).unwrap(); + client + .wl_storage + .storage + .write(&len_key, (tx_no_op.len() as u64).try_to_vec().unwrap()) + .unwrap(); + + // Request last committed epoch + let read_epoch = RPC.shell().epoch(&client).await.unwrap(); + let current_epoch = client.wl_storage.storage.last_epoch; + assert_eq!(current_epoch, read_epoch); + + // Request dry run tx + let mut outer_tx = + Tx::from_type(TxType::Decrypted(DecryptedTx::Decrypted)); + outer_tx.header.chain_id = client.wl_storage.storage.chain_id.clone(); + outer_tx.set_code(Code::from_hash(tx_hash)); + outer_tx.set_data(Data::new(vec![])); + let tx_bytes = outer_tx.to_bytes(); + let result = RPC + .shell() + .dry_run_tx(&client, Some(tx_bytes), None, false) + .await + .unwrap(); + assert!(result.data.is_accepted()); + + // Request storage value for a balance key ... + let token_addr = address::testing::established_address_1(); + let owner = address::testing::established_address_2(); + let balance_key = token::balance_key(&token_addr, &owner); + // ... there should be no value yet. + let read_balance = RPC + .shell() + .storage_value(&client, None, None, false, &balance_key) + .await + .unwrap(); + assert!(read_balance.data.is_empty()); + + // Request storage prefix iterator + let balance_prefix = token::balance_prefix(&token_addr); + let read_balances = RPC + .shell() + .storage_prefix(&client, None, None, false, &balance_prefix) + .await + .unwrap(); + assert!(read_balances.data.is_empty()); + + // Request storage has key + let has_balance_key = RPC + .shell() + .storage_has_key(&client, &balance_key) + .await + .unwrap(); + assert!(!has_balance_key); + + // Then write some balance ... + let balance = token::Amount::native_whole(1000); + StorageWrite::write(&mut client.wl_storage, &balance_key, balance)?; + // It has to be committed to be visible in a query + client.wl_storage.commit_tx(); + client.wl_storage.commit_block().unwrap(); + // ... there should be the same value now + let read_balance = RPC + .shell() + .storage_value(&client, None, None, false, &balance_key) + .await + .unwrap(); + assert_eq!( + balance, + token::Amount::try_from_slice(&read_balance.data).unwrap() + ); + + // Request storage prefix iterator + let balance_prefix = token::balance_prefix(&token_addr); + let read_balances = RPC + .shell() + .storage_prefix(&client, None, None, false, &balance_prefix) + .await + .unwrap(); + assert_eq!(read_balances.data.len(), 1); + + // Request storage has key + let has_balance_key = RPC + .shell() + .storage_has_key(&client, &balance_key) + .await + .unwrap(); + assert!(has_balance_key); + + Ok(()) } } diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs deleted file mode 100644 index e78313d804..0000000000 --- a/shared/src/ledger/queries/mod.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! Ledger read-only queries can be handled and dispatched via the [`RPC`] -//! defined via `router!` macro. - -// Re-export to show in rustdoc! -pub use shell::Shell; -use shell::SHELL; -pub use types::{ - EncodedResponseQuery, Error, RequestCtx, RequestQuery, ResponseQuery, - Router, -}; -use vp::{Vp, VP}; - -pub use self::shell::eth_bridge::{ - Erc20FlowControl, GenBridgePoolProofReq, GenBridgePoolProofRsp, - TransferToErcArgs, -}; -use super::storage::traits::StorageHasher; -use super::storage::{DBIter, DB}; -use super::storage_api; -#[cfg(any(test, feature = "async-client"))] -pub use crate::sdk::queries::Client; -use crate::types::storage::BlockHeight; - -#[macro_use] -mod router; -mod shell; -mod types; -pub mod vp; - -// Most commonly expected patterns should be declared first -router! {RPC, - // Shell provides storage read access, block metadata and can dry-run a tx - ( "shell" ) = (sub SHELL), - - // Validity-predicate's specific storage queries - ( "vp" ) = (sub VP), -} - -/// Handle RPC query request in the ledger. On success, returns response with -/// borsh-encoded data. -pub fn handle_path( - ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, -) -> storage_api::Result -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - RPC.handle(ctx, request) -} - -// Handler helpers: - -/// For queries that only support latest height, check that the given height is -/// not different from latest height, otherwise return an error. -pub fn require_latest_height( - ctx: &RequestCtx<'_, D, H>, - request: &RequestQuery, -) -> storage_api::Result<()> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - if request.height != BlockHeight(0) - && request.height != ctx.wl_storage.storage.get_last_block_height() - { - return Err(storage_api::Error::new_const( - "This query doesn't support arbitrary block heights, only the \ - latest committed block height ('0' can be used as a special \ - value that means the latest block height)", - )); - } - Ok(()) -} - -/// For queries that do not support proofs, check that proof is not requested, -/// otherwise return an error. -pub fn require_no_proof(request: &RequestQuery) -> storage_api::Result<()> { - if request.prove { - return Err(storage_api::Error::new_const( - "This query doesn't support proofs", - )); - } - Ok(()) -} - -/// For queries that don't use request data, require that there are no data -/// attached. -pub fn require_no_data(request: &RequestQuery) -> storage_api::Result<()> { - if !request.data.is_empty() { - return Err(storage_api::Error::new_const( - "This query doesn't accept request data", - )); - } - Ok(()) -} - -/// Queries testing helpers -#[cfg(any(test, feature = "testing"))] -mod testing { - - use tempfile::TempDir; - use tendermint_rpc::Response; - - use super::*; - use crate::ledger::events::log::EventLog; - use crate::ledger::storage::testing::TestWlStorage; - use crate::tendermint_rpc::error::Error as RpcError; - use crate::types::storage::BlockHeight; - use crate::vm::wasm::{self, TxCache, VpCache}; - use crate::vm::WasmCacheRoAccess; - - /// A test client that has direct access to the storage - pub struct TestClient - where - RPC: Router, - { - /// RPC router - pub rpc: RPC, - /// storage - pub wl_storage: TestWlStorage, - /// event log - pub event_log: EventLog, - /// VP wasm compilation cache - pub vp_wasm_cache: VpCache, - /// tx wasm compilation cache - pub tx_wasm_cache: TxCache, - /// VP wasm compilation cache directory - pub vp_cache_dir: TempDir, - /// tx wasm compilation cache directory - pub tx_cache_dir: TempDir, - } - - impl TestClient - where - RPC: Router, - { - #[allow(dead_code)] - /// Initialize a test client for the given root RPC router - pub fn new(rpc: RPC) -> Self { - // Initialize the `TestClient` - let mut wl_storage = TestWlStorage::default(); - - // Initialize mock gas limit - let max_block_gas_key = - namada_core::ledger::parameters::storage::get_max_block_gas_key( - ); - wl_storage - .storage - .write( - &max_block_gas_key, - namada_core::ledger::storage::types::encode( - &20_000_000_u64, - ), - ) - .expect( - "Max block gas parameter must be initialized in storage", - ); - let event_log = EventLog::default(); - let (vp_wasm_cache, vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - let (tx_wasm_cache, tx_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - Self { - rpc, - wl_storage, - event_log, - vp_wasm_cache: vp_wasm_cache.read_only(), - tx_wasm_cache: tx_wasm_cache.read_only(), - vp_cache_dir, - tx_cache_dir, - } - } - } - - #[cfg_attr(feature = "async-send", async_trait::async_trait)] - #[cfg_attr(not(feature = "async-send"), async_trait::async_trait(?Send))] - impl Client for TestClient - where - RPC: Router + Sync, - { - type Error = std::io::Error; - - async fn request( - &self, - path: String, - data: Option>, - height: Option, - prove: bool, - ) -> Result { - let data = data.unwrap_or_default(); - let height = height.unwrap_or_default(); - // Handle a path by invoking the `RPC.handle` directly with the - // borrowed storage - let request = RequestQuery { - data, - path, - height, - prove, - }; - let ctx = RequestCtx { - wl_storage: &self.wl_storage, - event_log: &self.event_log, - vp_wasm_cache: self.vp_wasm_cache.clone(), - tx_wasm_cache: self.tx_wasm_cache.clone(), - storage_read_past_height_limit: None, - }; - // TODO: this is a hack to propagate errors to the caller, we should - // really permit error types other than [`std::io::Error`] - self.rpc.handle(ctx, &request).map_err(|err| { - std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) - }) - } - - async fn perform(&self, _request: R) -> Result - where - R: tendermint_rpc::SimpleRequest, - { - Response::from_string("TODO") - } - } -} diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 3036c4cb47..d0d1ea8b2b 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -24,7 +24,7 @@ pub use { }; pub mod ledger; pub use namada_core::proto; -pub mod sdk; +pub use namada_sdk; pub mod types; pub mod vm; diff --git a/shared/src/sdk/mod.rs b/shared/src/sdk/mod.rs deleted file mode 100644 index 381bac03d1..0000000000 --- a/shared/src/sdk/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Namada's SDK API -pub mod rpc; - -pub mod args; -pub mod masp; -pub mod signing; -#[allow(clippy::result_large_err)] -pub mod tx; - -pub mod error; -pub mod queries; -pub mod wallet; diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 83e58f0fa8..04801cf6c5 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -1,8 +1,8 @@ //! Types definitions. -pub mod control_flow; +pub use namada_sdk::control_flow; pub mod ibc; -pub mod io; +pub use namada_sdk::io; pub mod key; pub use namada_core::types::{ diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 7806d1abef..5229c65908 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -1885,7 +1885,7 @@ where // TODO: once the runtime gas meter is implemented we need to benchmark // this funcion and charge the gas here. For the moment, the cost of // this is included in the benchmark of the masp vp - HostEnvResult::from(crate::sdk::masp::verify_shielded_tx(&shielded)) + HostEnvResult::from(namada_sdk::masp::verify_shielded_tx(&shielded)) .to_i64(), ) } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 674bce9538..5ab740b9ca 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -31,6 +31,7 @@ wasm-runtime = ["namada/wasm-runtime"] [dependencies] namada = {path = "../shared", features = ["testing"]} namada_core = {path = "../core", features = ["testing"]} +namada_sdk = {path = "../sdk"} namada_test_utils = {path = "../test_utils"} namada_vp_prelude = {path = "../vp_prelude"} namada_tx_prelude = {path = "../tx_prelude"} diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 025f360c42..7bfbf12e2b 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -19,7 +19,6 @@ use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; use data_encoding::HEXLOWER; -use namada::sdk::masp::fs::FsShieldedUtils; use namada::types::address::Address; use namada::types::storage::Epoch; use namada::types::token; @@ -32,6 +31,7 @@ use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; use namada_core::ledger::governance::cli::onchain::{ PgfFunding, PgfFundingTarget, StewardsUpdate, }; +use namada_sdk::masp::fs::FsShieldedUtils; use namada_test_utils::TestWasms; use namada_vp_prelude::BTreeSet; use serde_json::json; diff --git a/tests/src/integration/masp.rs b/tests/src/integration/masp.rs index 261d3acd08..28d5375f79 100644 --- a/tests/src/integration/masp.rs +++ b/tests/src/integration/masp.rs @@ -2,12 +2,12 @@ use std::path::PathBuf; use color_eyre::eyre::Result; use color_eyre::owo_colors::OwoColorize; -use namada::sdk::masp::fs::FsShieldedUtils; use namada_apps::node::ledger::shell::testing::client::run; use namada_apps::node::ledger::shell::testing::utils::{Bin, CapturedOutput}; use namada_core::types::address::{btc, eth, masp_rewards}; use namada_core::types::token; use namada_core::types::token::{DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}; +use namada_sdk::masp::fs::FsShieldedUtils; use test_log::test; use super::setup; diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index 364dcd074c..6fc304f909 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -4,10 +4,6 @@ mod test_bridge_pool_vp { use borsh::{BorshDeserialize, BorshSerialize}; use namada::core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; - use namada::ledger::eth_bridge::{ - wrapped_erc20s, Contracts, Erc20WhitelistEntry, EthereumBridgeConfig, - UpgradeableContract, - }; use namada::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; use namada::proto::Tx; use namada::types::address::{nam, wnam}; @@ -20,6 +16,10 @@ mod test_bridge_pool_vp { use namada::types::token::Amount; use namada_apps::wallet::defaults::{albert_address, bertha_address}; use namada_apps::wasm_loader; + use namada_sdk::eth_bridge::{ + wrapped_erc20s, Contracts, Erc20WhitelistEntry, EthereumBridgeConfig, + UpgradeableContract, + }; use crate::native_vp::TestNativeVpEnv; use crate::tx::{tx_host_env, TestTxEnv}; diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 5dbe91c1e4..8984c1708f 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3332,6 +3332,7 @@ dependencies = [ "namada_core", "namada_ethereum_bridge", "namada_proof_of_stake", + "namada_sdk", "num256", "orion", "owo-colors", @@ -3464,6 +3465,49 @@ dependencies = [ "tracing", ] +[[package]] +name = "namada_sdk" +version = "0.23.0" +dependencies = [ + "async-trait", + "bimap", + "borsh 0.9.4", + "circular-queue", + "data-encoding", + "derivation-path", + "ethbridge-bridge-contract", + "ethers", + "futures", + "itertools", + "masp_primitives", + "masp_proofs", + "namada_core", + "namada_ethereum_bridge", + "namada_proof_of_stake", + "num256", + "orion", + "owo-colors", + "parse_duration", + "paste", + "prost", + "rand 0.8.5", + "rand_core 0.6.4", + "ripemd", + "serde", + "serde_json", + "sha2 0.9.9", + "slip10_ed25519", + "tendermint-rpc", + "thiserror", + "tiny-bip39", + "tiny-hderive", + "tokio", + "toml 0.5.11", + "tracing", + "wasmtimer", + "zeroize", +] + [[package]] name = "namada_test_utils" version = "0.23.0" @@ -3486,6 +3530,7 @@ dependencies = [ "lazy_static", "namada", "namada_core", + "namada_sdk", "namada_test_utils", "namada_tx_prelude", "namada_vp_prelude", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 8e3bc2bb20..43c75ed658 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3332,6 +3332,7 @@ dependencies = [ "namada_core", "namada_ethereum_bridge", "namada_proof_of_stake", + "namada_sdk", "num256", "orion", "owo-colors", @@ -3464,6 +3465,49 @@ dependencies = [ "tracing", ] +[[package]] +name = "namada_sdk" +version = "0.23.0" +dependencies = [ + "async-trait", + "bimap", + "borsh 0.9.4", + "circular-queue", + "data-encoding", + "derivation-path", + "ethbridge-bridge-contract", + "ethers", + "futures", + "itertools", + "masp_primitives", + "masp_proofs", + "namada_core", + "namada_ethereum_bridge", + "namada_proof_of_stake", + "num256", + "orion", + "owo-colors", + "parse_duration", + "paste", + "prost", + "rand 0.8.5", + "rand_core 0.6.4", + "ripemd", + "serde", + "serde_json", + "sha2 0.9.9", + "slip10_ed25519", + "tendermint-rpc", + "thiserror", + "tiny-bip39", + "tiny-hderive", + "tokio", + "toml 0.5.11", + "tracing", + "wasmtimer", + "zeroize", +] + [[package]] name = "namada_test_utils" version = "0.23.0" @@ -3486,6 +3530,7 @@ dependencies = [ "lazy_static", "namada", "namada_core", + "namada_sdk", "namada_test_utils", "namada_tx_prelude", "namada_vp_prelude", From f892ab4040465d2d6388da6e5eefdc4a29f69513 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 12 Oct 2023 09:53:16 +0200 Subject: [PATCH 13/14] Changes to enable better usage of the SDK in the absence of wallet or shielded context. --- apps/src/lib/client/rpc.rs | 109 +++++++++++++++++-------------------- sdk/src/lib.rs | 25 ++++++++- sdk/src/masp.rs | 53 +++++++++++------- sdk/src/rpc.rs | 22 +++++--- sdk/src/signing.rs | 44 +++++---------- sdk/src/tx.rs | 8 +-- 6 files changed, 136 insertions(+), 125 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d5dc2f23a3..b3902e0900 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -46,8 +46,7 @@ use namada::types::{storage, token}; use namada_sdk::error::{is_pinned_error, Error, PinnedBalanceError}; use namada_sdk::masp::{Conversions, MaspAmount, MaspChange}; use namada_sdk::rpc::{ - self, enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, - TxResponse, + self, enriched_bonds_and_unbonds, query_epoch, TxResponse, }; use namada_sdk::wallet::AddressVpType; use namada_sdk::{display, display_line, edisplay_line, error, prompt, Namada}; @@ -158,7 +157,8 @@ pub async fn query_transfers<'a>( // transaction's reception let amt = shielded .compute_exchanged_amount( - context, + context.client(), + context.io(), amt, epoch, Conversions::new(), @@ -209,8 +209,7 @@ pub async fn query_transfers<'a>( context.io(), " {}{} {}", sign, - format_denominated_amount(context, asset, change.into(),) - .await, + context.format_amount(asset, change.into()).await, token_alias ); } @@ -233,12 +232,7 @@ pub async fn query_transfers<'a>( context.io(), " {}{} {}", sign, - format_denominated_amount( - context, - &token_addr, - val.into(), - ) - .await, + context.format_amount(&token_addr, val.into()).await, token_alias, ); } @@ -329,9 +323,7 @@ pub async fn query_transparent_balance<'a>( .await { Ok(balance) => { - let balance = - format_denominated_amount(context, &token, balance) - .await; + let balance = context.format_amount(&token, balance).await; display_line!(context.io(), "{}: {}", token_alias, balance); } Err(e) => { @@ -351,9 +343,7 @@ pub async fn query_transparent_balance<'a>( let balance = get_token_balance(context.client(), &token, &owner).await; if !balance.is_zero() { - let balance = - format_denominated_amount(context, &token, balance) - .await; + let balance = context.format_amount(&token, balance).await; display_line!(context.io(), "{}: {}", token_alias, balance); } } @@ -481,12 +471,9 @@ pub async fn query_pinned_balance<'a>( token_alias ); } else { - let formatted = format_denominated_amount( - context, - token, - total_balance.into(), - ) - .await; + let formatted = context + .format_amount(token, total_balance.into()) + .await; display_line!( context.io(), "Payment address {} was consumed during epoch {}. \ @@ -515,12 +502,9 @@ pub async fn query_pinned_balance<'a>( ); found_any = true; } - let formatted = format_denominated_amount( - context, - token_addr, - (*value).into(), - ) - .await; + let formatted = context + .format_amount(token_addr, (*value).into()) + .await; let token_alias = tokens .get(token_addr) .map(|a| a.to_string()) @@ -567,7 +551,7 @@ async fn print_balances<'a>( owner.clone(), format!( ": {}, owned by {}", - format_denominated_amount(context, tok, balance).await, + context.format_amount(tok, balance).await, wallet.lookup_alias(owner) ), ), @@ -736,7 +720,12 @@ pub async fn query_shielded_balance<'a>( context .shielded_mut() .await - .compute_exchanged_balance(context, &viewing_key, epoch) + .compute_exchanged_balance( + context.client(), + context.io(), + &viewing_key, + epoch, + ) .await .unwrap() .expect("context should contain viewing key") @@ -759,12 +748,12 @@ pub async fn query_shielded_balance<'a>( context.io(), "{}: {}", token_alias, - format_denominated_amount( - context, - &token, - token::Amount::from(total_balance) - ) - .await + context + .format_amount( + &token, + token::Amount::from(total_balance), + ) + .await ); } } @@ -790,7 +779,12 @@ pub async fn query_shielded_balance<'a>( context .shielded_mut() .await - .compute_exchanged_balance(context, &viewing_key, epoch) + .compute_exchanged_balance( + context.client(), + context.io(), + &viewing_key, + epoch, + ) .await .unwrap() .expect("context should contain viewing key") @@ -830,12 +824,8 @@ pub async fn query_shielded_balance<'a>( .map(|a| a.to_string()) .unwrap_or_else(|| token.to_string()); display_line!(context.io(), "Shielded Token {}:", alias); - let formatted = format_denominated_amount( - context, - &token, - token_balance.into(), - ) - .await; + let formatted = + context.format_amount(&token, token_balance.into()).await; display_line!( context.io(), " {}, owned by {}", @@ -879,7 +869,12 @@ pub async fn query_shielded_balance<'a>( context .shielded_mut() .await - .compute_exchanged_balance(context, &viewing_key, epoch) + .compute_exchanged_balance( + context.client(), + context.io(), + &viewing_key, + epoch, + ) .await .unwrap() .expect("context should contain viewing key") @@ -889,12 +884,8 @@ pub async fn query_shielded_balance<'a>( if !val.is_zero() { found_any = true; } - let formatted = format_denominated_amount( - context, - address, - (*val).into(), - ) - .await; + let formatted = + context.format_amount(address, (*val).into()).await; display_line!( context.io(), " {}, owned by {}", @@ -930,7 +921,12 @@ pub async fn query_shielded_balance<'a>( let balance = context .shielded_mut() .await - .compute_exchanged_balance(context, &viewing_key, epoch) + .compute_exchanged_balance( + context.client(), + context.io(), + &viewing_key, + epoch, + ) .await .unwrap() .expect("context should contain viewing key"); @@ -957,12 +953,7 @@ pub async fn print_decoded_balance<'a>( context.io(), "{} : {}", context.wallet().await.lookup_alias(token_addr), - format_denominated_amount( - context, - token_addr, - (*amount).into() - ) - .await, + context.format_amount(token_addr, (*amount).into()).await, ); } } @@ -990,7 +981,7 @@ pub async fn print_decoded_balance_with_epoch<'a>( "{} | {} : {}", alias, epoch, - format_denominated_amount(context, token_addr, asset_value).await, + context.format_amount(token_addr, asset_value).await, ); } } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 78a5caf812..622a63a1d1 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -51,8 +51,11 @@ use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use crate::io::Io; use crate::masp::{ShieldedContext, ShieldedUtils}; use crate::proto::Tx; -use crate::rpc::query_native_token; +use crate::rpc::{ + denominate_amount, format_denominated_amount, query_native_token, +}; use crate::signing::SigningTxData; +use crate::token::DenominatedAmount; use crate::tx::{ ProcessTxResponse, TX_BOND_WASM, TX_BRIDGE_POOL_WASM, TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, TX_INIT_PROPOSAL, @@ -395,6 +398,26 @@ pub trait Namada<'a>: Sized { ) -> crate::error::Result { tx::process_tx(self, args, tx).await } + + /// Look up the denomination of a token in order to make a correctly + /// denominated amount. + async fn denominate_amount( + &self, + token: &Address, + amount: token::Amount, + ) -> DenominatedAmount { + denominate_amount(self.client(), self.io(), token, amount).await + } + + /// Look up the denomination of a token in order to format it correctly as a + /// string. + async fn format_amount( + &self, + token: &Address, + amount: token::Amount, + ) -> String { + format_denominated_amount(self.client(), self.io(), token, amount).await + } } /// Provides convenience methods for common Namada interactions diff --git a/sdk/src/masp.rs b/sdk/src/masp.rs index b010bde7d7..a860839e3a 100644 --- a/sdk/src/masp.rs +++ b/sdk/src/masp.rs @@ -1030,19 +1030,20 @@ impl ShieldedContext { /// context and express that value in terms of the currently timestamped /// asset types. If the key is not in the context, then we do not know the /// balance and hence we return None. - pub async fn compute_exchanged_balance<'a>( + pub async fn compute_exchanged_balance( &mut self, - context: &impl Namada<'a>, + client: &(impl Client + Sync), + io: &impl Io, vk: &ViewingKey, target_epoch: Epoch, ) -> Result, Error> { // First get the unexchanged balance - if let Some(balance) = - self.compute_shielded_balance(context.client(), vk).await? + if let Some(balance) = self.compute_shielded_balance(client, vk).await? { let exchanged_amount = self .compute_exchanged_amount( - context, + client, + io, balance, target_epoch, BTreeMap::new(), @@ -1051,8 +1052,7 @@ impl ShieldedContext { .0; // And then exchange balance into current asset types Ok(Some( - self.decode_all_amounts(context.client(), exchanged_amount) - .await, + self.decode_all_amounts(client, exchanged_amount).await, )) } else { Ok(None) @@ -1065,9 +1065,10 @@ impl ShieldedContext { /// the trace amount that could not be converted is moved from input to /// output. #[allow(clippy::too_many_arguments)] - async fn apply_conversion<'a>( + async fn apply_conversion( &mut self, - context: &impl Namada<'a>, + client: &(impl Client + Sync), + io: &impl Io, conv: AllowedConversion, asset_type: (Epoch, Address, MaspDenom), value: i128, @@ -1087,7 +1088,7 @@ impl ShieldedContext { let threshold = -conv[&masp_asset]; if threshold == 0 { edisplay_line!( - context.io(), + io, "Asset threshold of selected conversion for asset type {} is \ 0, this is a bug, please report it.", masp_asset @@ -1106,7 +1107,7 @@ impl ShieldedContext { *usage += required; // Apply the conversions to input and move the trace amount to output *input += self - .decode_all_amounts(context.client(), conv.clone() * required) + .decode_all_amounts(client, conv.clone() * required) .await - trace.clone(); *output += trace; @@ -1117,9 +1118,10 @@ impl ShieldedContext { /// note of the conversions that were used. Note that this function does /// not assume that allowed conversions from the ledger are expressed in /// terms of the latest asset types. - pub async fn compute_exchanged_amount<'a>( + pub async fn compute_exchanged_amount( &mut self, - context: &impl Namada<'a>, + client: &(impl Client + Sync), + io: &impl Io, mut input: MaspAmount, target_epoch: Epoch, mut conversions: Conversions, @@ -1141,13 +1143,13 @@ impl ShieldedContext { let denom_value = denom.denominate_i128(&value); self.query_allowed_conversion( - context.client(), + client, target_asset_type, &mut conversions, ) .await; self.query_allowed_conversion( - context.client(), + client, asset_type, &mut conversions, ) @@ -1156,7 +1158,7 @@ impl ShieldedContext { (conversions.get_mut(&asset_type), at_target_asset_type) { display_line!( - context.io(), + io, "converting current asset type to latest asset type..." ); // Not at the target asset type, not at the latest asset @@ -1164,7 +1166,8 @@ impl ShieldedContext { // current asset type to the latest // asset type. self.apply_conversion( - context, + client, + io, conv.clone(), (asset_epoch, token_addr.clone(), denom), denom_value, @@ -1178,7 +1181,7 @@ impl ShieldedContext { at_target_asset_type, ) { display_line!( - context.io(), + io, "converting latest asset type to target asset type..." ); // Not at the target asset type, yet at the latest asset @@ -1186,7 +1189,8 @@ impl ShieldedContext { // from latest asset type to the target // asset type. self.apply_conversion( - context, + client, + io, conv.clone(), (asset_epoch, token_addr.clone(), denom), denom_value, @@ -1268,7 +1272,8 @@ impl ShieldedContext { self.decode_all_amounts(context.client(), pre_contr).await; let (contr, proposed_convs) = self .compute_exchanged_amount( - context, + context.client(), + context.io(), input, target_epoch, conversions.clone(), @@ -1422,7 +1427,13 @@ impl ShieldedContext { display_line!(context.io(), "Decoded pinned balance: {:?}", amount); // Finally, exchange the balance to the transaction's epoch let computed_amount = self - .compute_exchanged_amount(context, amount, ep, BTreeMap::new()) + .compute_exchanged_amount( + context.client(), + context.io(), + amount, + ep, + BTreeMap::new(), + ) .await? .0; display_line!(context.io(), "Exchanged amount: {:?}", computed_amount); diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index 9307374663..f1267fe8a8 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -1037,21 +1037,22 @@ pub async fn wait_until_node_is_synched<'a>( /// Look up the denomination of a token in order to make a correctly denominated /// amount. -pub async fn denominate_amount<'a, N: Namada<'a>>( - context: &N, +pub async fn denominate_amount( + client: &C, + io: &impl Io, token: &Address, amount: token::Amount, ) -> DenominatedAmount { - let denom = convert_response::>( - RPC.vp().token().denomination(context.client(), token).await, + let denom = convert_response::>( + RPC.vp().token().denomination(client, token).await, ) .unwrap_or_else(|t| { - display_line!(context.io(), "Error in querying for denomination: {t}"); + display_line!(io, "Error in querying for denomination: {t}"); None }) .unwrap_or_else(|| { display_line!( - context.io(), + io, "No denomination found for token: {token}, defaulting to zero \ decimal places" ); @@ -1062,10 +1063,13 @@ pub async fn denominate_amount<'a, N: Namada<'a>>( /// Look up the denomination of a token in order to format it /// correctly as a string. -pub async fn format_denominated_amount<'a>( - context: &impl Namada<'a>, +pub async fn format_denominated_amount( + client: &(impl Client + Sync), + io: &impl Io, token: &Address, amount: token::Amount, ) -> String { - denominate_amount(context, token, amount).await.to_string() + denominate_amount(client, io, token, amount) + .await + .to_string() } diff --git a/sdk/src/signing.rs b/sdk/src/signing.rs index 7680adfdd2..db2aab3482 100644 --- a/sdk/src/signing.rs +++ b/sdk/src/signing.rs @@ -41,9 +41,7 @@ use crate::ibc_proto::google::protobuf::Any; use crate::io::*; use crate::masp::make_asset_type; use crate::proto::{MaspBuilder, Section, Tx}; -use crate::rpc::{ - format_denominated_amount, query_wasm_code_hash, validate_amount, -}; +use crate::rpc::{query_wasm_code_hash, validate_amount}; use crate::tx::{ TX_BOND_WASM, TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL, TX_INIT_VALIDATOR_WASM, TX_REVEAL_PK, TX_TRANSFER_WASM, @@ -515,19 +513,12 @@ pub async fn wrap_tx<'a, N: Namada<'a>>( } else { let token_addr = args.fee_token.clone(); if !args.force { - let fee_amount = format_denominated_amount( - context, - &token_addr, - total_fee, - ) - .await; - - let balance = format_denominated_amount( - context, - &token_addr, - updated_balance, - ) - .await; + let fee_amount = + context.format_amount(&token_addr, total_fee).await; + + let balance = context + .format_amount(&token_addr, updated_balance) + .await; return Err(Error::from(TxError::BalanceTooLowForFees( fee_payer_address, token_addr, @@ -621,8 +612,7 @@ async fn make_ledger_amount_asset<'a>( ) { if let Some((token, _, _epoch)) = assets.get(token) { // If the AssetType can be decoded, then at least display Addressees - let formatted_amt = - format_denominated_amount(context, token, amount.into()).await; + let formatted_amt = context.format_amount(token, amount.into()).await; if let Some(token) = tokens.get(token) { output .push( @@ -1332,18 +1322,12 @@ pub async fn to_ledger_vector<'a>( if let Some(wrapper) = tx.header.wrapper() { let gas_token = wrapper.fee.token.clone(); - let gas_limit = format_denominated_amount( - context, - &gas_token, - Amount::from(wrapper.gas_limit), - ) - .await; - let fee_amount_per_gas_unit = format_denominated_amount( - context, - &gas_token, - wrapper.fee.amount_per_gas_unit, - ) - .await; + let gas_limit = context + .format_amount(&gas_token, Amount::from(wrapper.gas_limit)) + .await; + let fee_amount_per_gas_unit = context + .format_amount(&gas_token, wrapper.fee.amount_per_gas_unit) + .await; tv.output_expert.extend(vec![ format!("Timestamp : {}", tx.header.timestamp.0), format!("PK : {}", wrapper.pk), diff --git a/sdk/src/tx.rs b/sdk/src/tx.rs index b4b059e249..acfe12f6bd 100644 --- a/sdk/src/tx.rs +++ b/sdk/src/tx.rs @@ -58,8 +58,7 @@ use crate::masp::{ShieldedContext, ShieldedTransfer}; use crate::proto::{MaspBuilder, Tx}; use crate::queries::Client; use crate::rpc::{ - self, format_denominated_amount, query_wasm_code_hash, validate_amount, - TxBroadcastData, TxResponse, + self, query_wasm_code_hash, validate_amount, TxBroadcastData, TxResponse, }; use crate::signing::{self, SigningTxData, TxSourcePostBalance}; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; @@ -2083,9 +2082,8 @@ async fn check_balance_too_low_err<'a, N: Namada<'a>>( transfer is {} and the balance is {}.", source, token, - format_denominated_amount(context, token, amount).await, - format_denominated_amount(context, token, balance) - .await, + context.format_amount(token, amount).await, + context.format_amount(token, balance).await, ); Ok(token::Amount::default()) } else { From 94b2d4bdd0b4d668db7cf4e10358ba1a5cb98db4 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 12 Oct 2023 10:02:54 +0200 Subject: [PATCH 14/14] Added a change log entry. --- .changelog/unreleased/SDK/1963-sdk-refactor-rebased.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/SDK/1963-sdk-refactor-rebased.md diff --git a/.changelog/unreleased/SDK/1963-sdk-refactor-rebased.md b/.changelog/unreleased/SDK/1963-sdk-refactor-rebased.md new file mode 100644 index 0000000000..6add26845e --- /dev/null +++ b/.changelog/unreleased/SDK/1963-sdk-refactor-rebased.md @@ -0,0 +1,2 @@ +- Improved the usability of the SDK and moved it to separate crate. + ([\#1963](https://github.com/anoma/namada/pull/1963)) \ No newline at end of file