From 0572aa3a09ae654bf46445255f66a5998d2f162b Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 4 Oct 2023 06:57:00 +0200 Subject: [PATCH] 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 | 107 +- shared/src/sdk/tx.rs | 383 +++--- shared/src/types/io.rs | 46 +- 26 files changed, 1767 insertions(+), 1821 deletions(-) diff --git a/apps/src/bin/namada-client/main.rs b/apps/src/bin/namada-client/main.rs index 9b43ca8f91b..167674f65e5 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 05d2620bcb0..ef5e05f913e 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 5e94831716c..987e9d26993 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 531027a102a..ce5bf600b5b 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 bb387c5d9a5..052a834f559 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 449d4f38ce7..ac1ca1e34df 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 f2efec7fee4..4772ef98b98 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 3322e84e2fb..d94fd5a09d1 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 26cc38ff7fb..7c8bc4100cd 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 33b443edd99..5dc223cd64c 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 72d6514c352..24b7708f420 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 c64fdf00443..22f0c1b1b47 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 9ebc825f545..504bdc7e5bc 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 bfcb7f50ab9..451e20c2df8 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 cca43a43d61..61c49fe5809 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 3aacaef90fe..b5036d5f666 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 a73f5efd770..66ec22a63b1 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 14002b53d36..b4c633ca786 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 be99e130f8b..90f043fe18b 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 a8159834a58..5536f46b630 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 91e00dee68b..0853dc251bc 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 35c4f599568..96ab27fbd51 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 8a40d8a8ddf..eb8ebd8a11c 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 048d72517fe..7801f10d1da 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!( @@ -171,13 +172,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 +350,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 +363,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 +504,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 +530,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 +598,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 +610,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 +694,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 +718,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 +745,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 +825,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 +1139,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 +1148,7 @@ pub async fn to_ledger_vector<'a>( ) .await; make_ledger_masp_endpoints( - context.client(), + context, &tokens, &mut tv.output_expert, &transfer, @@ -1342,13 +1321,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 e6272c0d650..66b8847b17d 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 007d5acd937..f100ca8433a 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)*))) }} }