diff --git a/.changelog/unreleased/features/1746-generic-io.md b/.changelog/unreleased/features/1746-generic-io.md new file mode 100644 index 0000000000..ff469ec863 --- /dev/null +++ b/.changelog/unreleased/features/1746-generic-io.md @@ -0,0 +1,2 @@ +- Replaced standard IO in SDK and client code with a trait that allows custom + handling. ([\#1746](https://github.com/anoma/namada/pull/1746)) \ No newline at end of file diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 283ac65c69..e69de29bb2 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -1,544 +0,0 @@ -//! Namada client CLI. - -use color_eyre::eyre::{eyre, Report, Result}; -use namada::ledger::eth_bridge::bridge_pool; -use namada::ledger::rpc::wait_until_node_is_synched; -use namada::ledger::tx::dump_tx; -use namada::ledger::{signing, tx as sdk_tx}; -use namada::types::control_flow::ProceedOrElse; -use namada_apps::cli; -use namada_apps::cli::args::CliToSdk; -use namada_apps::cli::cmds::*; -use namada_apps::client::{rpc, tx, utils}; -use namada_apps::facade::tendermint_rpc::HttpClient; - -fn error() -> Report { - eyre!("Fatal error") -} - -pub async fn main() -> Result<()> { - match cli::namada_client_cli()? { - cli::NamadaClient::WithContext(cmd_box) => { - let (cmd, mut ctx) = *cmd_box; - use NamadaClientWithContext as Sub; - match cmd { - // Ledger cmds - Sub::TxCustom(TxCustom(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - let dry_run = args.tx.dry_run; - tx::submit_custom::(&client, &mut ctx, args) - .await?; - if !dry_run { - namada_apps::wallet::save(&ctx.wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); - } else { - println!( - "Transaction dry run. No addresses have been \ - saved." - ) - } - } - Sub::TxTransfer(TxTransfer(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - tx::submit_transfer(&client, ctx, args).await?; - } - Sub::TxIbcTransfer(TxIbcTransfer(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer::(&client, ctx, args) - .await?; - } - Sub::TxUpdateAccount(TxUpdateAccount(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - tx::submit_update_account::( - &client, &mut ctx, args, - ) - .await?; - } - Sub::TxInitAccount(TxInitAccount(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - let dry_run = args.tx.dry_run; - tx::submit_init_account::( - &client, &mut ctx, args, - ) - .await?; - if !dry_run { - namada_apps::wallet::save(&ctx.wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); - } else { - println!( - "Transaction dry run. No addresses have been \ - saved." - ) - } - } - Sub::TxInitValidator(TxInitValidator(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - tx::submit_init_validator::(&client, ctx, args) - .await?; - } - Sub::TxInitProposal(TxInitProposal(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - tx::submit_init_proposal::(&client, ctx, args) - .await?; - } - Sub::TxVoteProposal(TxVoteProposal(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal::(&client, ctx, args) - .await?; - } - Sub::TxRevealPk(TxRevealPk(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk::(&client, &mut ctx, args) - .await?; - } - Sub::Bond(Bond(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - tx::submit_bond::(&client, &mut ctx, args) - .await?; - } - Sub::Unbond(Unbond(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - tx::submit_unbond::(&client, &mut ctx, args) - .await?; - } - Sub::Withdraw(Withdraw(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - tx::submit_withdraw::(&client, ctx, args) - .await?; - } - Sub::TxCommissionRateChange(TxCommissionRateChange( - mut args, - )) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client).await; - let args = args.to_sdk(&mut ctx); - tx::submit_validator_commission_change::( - &client, ctx, args, - ) - .await?; - } - // Eth bridge - Sub::AddToEthBridgePool(args) => { - let mut args = args.0; - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - let tx_args = args.tx.clone(); - - let default_signer = Some(args.sender.clone()); - let signing_data = signing::aux_signing_data( - &client, - &mut ctx.wallet, - &args.tx, - &args.sender, - default_signer, - ) - .await?; - - tx::submit_reveal_aux( - &client, - &mut ctx, - tx_args.clone(), - &args.sender, - ) - .await?; - - let tx = bridge_pool::build_bridge_pool_tx( - &client, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - - signing::generate_test_vector( - &client, - &mut ctx.wallet, - &tx, - ) - .await; - - if args.tx.dump_tx { - dump_tx(&args.tx, tx); - } else { - signing::sign_tx( - &mut ctx.wallet, - &tx_args, - &mut tx, - signing_data, - )?; - - sdk_tx::process_tx( - &client, - &mut ctx.wallet, - &tx_args, - tx, - ) - .await?; - } - } - Sub::TxUnjailValidator(TxUnjailValidator(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - tx::submit_unjail_validator::( - &client, ctx, args, - ) - .await?; - } - // Ledger queries - Sub::QueryEpoch(QueryEpoch(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - rpc::query_and_print_epoch(&client).await; - } - Sub::QueryTransfers(QueryTransfers(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_transfers( - &client, - &mut ctx.wallet, - &mut ctx.shielded, - args, - ) - .await; - } - Sub::QueryConversions(QueryConversions(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_conversions(&client, &mut ctx.wallet, args) - .await; - } - Sub::QueryBlock(QueryBlock(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - rpc::query_block(&client).await; - } - Sub::QueryBalance(QueryBalance(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_balance( - &client, - &mut ctx.wallet, - &mut ctx.shielded, - args, - ) - .await; - } - Sub::QueryBonds(QueryBonds(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_bonds(&client, &mut ctx.wallet, args) - .await - .expect("expected successful query of bonds"); - } - Sub::QueryBondedStake(QueryBondedStake(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_bonded_stake(&client, args).await; - } - Sub::QueryValidatorState(QueryValidatorState(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_and_print_validator_state( - &client, - &mut ctx.wallet, - args, - ) - .await; - } - Sub::QueryCommissionRate(QueryCommissionRate(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_and_print_commission_rate( - &client, - &mut ctx.wallet, - args, - ) - .await; - } - Sub::QuerySlashes(QuerySlashes(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_slashes(&client, &mut ctx.wallet, args).await; - } - Sub::QueryDelegations(QueryDelegations(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_delegations(&client, &mut ctx.wallet, args) - .await; - } - Sub::QueryFindValidator(QueryFindValidator(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_find_validator(&client, args).await; - } - Sub::QueryResult(QueryResult(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_result(&client, args).await; - } - Sub::QueryRawBytes(QueryRawBytes(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_raw_bytes(&client, args).await; - } - - Sub::QueryProposal(QueryProposal(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_proposal(&client, args).await; - } - Sub::QueryProposalResult(QueryProposalResult(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_proposal_result(&client, args).await; - } - Sub::QueryProtocolParameters(QueryProtocolParameters( - mut args, - )) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_protocol_parameters(&client, args).await; - } - Sub::QueryAccount(QueryAccount(args)) => { - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk(&mut ctx); - rpc::query_account(&client, args).await; - } - } - } - cli::NamadaClient::WithoutContext(cmd, global_args) => match cmd { - // Utils cmds - Utils::JoinNetwork(JoinNetwork(args)) => { - utils::join_network(global_args, args).await - } - Utils::FetchWasms(FetchWasms(args)) => { - utils::fetch_wasms(global_args, args).await - } - Utils::InitNetwork(InitNetwork(args)) => { - utils::init_network(global_args, args) - } - Utils::InitGenesisValidator(InitGenesisValidator(args)) => { - utils::init_genesis_validator(global_args, args) - } - Utils::PkToTmAddress(PkToTmAddress(args)) => { - utils::pk_to_tm_address(global_args, args) - } - Utils::DefaultBaseDir(DefaultBaseDir(args)) => { - utils::default_base_dir(global_args, args) - } - Utils::EpochSleep(EpochSleep(args)) => { - let mut ctx = cli::Context::new(global_args) - .expect("expected to construct a context"); - let ledger_address = args.ledger_address.clone(); - wait_until_node_is_synched(&ledger_address).await; - let client = HttpClient::new(ledger_address).unwrap(); - let args = args.to_sdk(&mut ctx); - rpc::epoch_sleep(&client, args).await; - } - }, - } - Ok(()) -} diff --git a/apps/src/bin/namada-client/main.rs b/apps/src/bin/namada-client/main.rs index a9e1fb4948..9b43ca8f91 100644 --- a/apps/src/bin/namada-client/main.rs +++ b/apps/src/bin/namada-client/main.rs @@ -1,5 +1,5 @@ use color_eyre::eyre::Result; -use namada_apps::cli::api::CliApi; +use namada_apps::cli::api::{CliApi, CliIo}; use namada_apps::facade::tendermint_rpc::HttpClient; use namada_apps::{cli, logging}; use tracing_subscriber::filter::LevelFilter; @@ -13,7 +13,7 @@ async fn main() -> Result<()> { let _log_guard = logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI - CliApi::<()>::handle_client_command::( + CliApi::::handle_client_command::( None, cli::namada_client_cli()?, ) diff --git a/apps/src/bin/namada-relayer/main.rs b/apps/src/bin/namada-relayer/main.rs index 52c15192dc..05d2620bcb 100644 --- a/apps/src/bin/namada-relayer/main.rs +++ b/apps/src/bin/namada-relayer/main.rs @@ -1,6 +1,6 @@ use color_eyre::eyre::Result; use namada::tendermint_rpc::HttpClient; -use namada_apps::cli::api::CliApi; +use namada_apps::cli::api::{CliApi, CliIo}; use namada_apps::{cli, logging}; use tracing_subscriber::filter::LevelFilter; @@ -14,5 +14,5 @@ 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).await } diff --git a/apps/src/bin/namada-wallet/main.rs b/apps/src/bin/namada-wallet/main.rs index 7459234c79..5e94831716 100644 --- a/apps/src/bin/namada-wallet/main.rs +++ b/apps/src/bin/namada-wallet/main.rs @@ -1,10 +1,10 @@ use color_eyre::eyre::Result; use namada_apps::cli; -use namada_apps::cli::api::CliApi; +use namada_apps::cli::api::{CliApi, CliIo}; 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) } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 7ef136bc98..1565e8ee21 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -15,10 +15,12 @@ pub mod wallet; use clap::{ArgGroup, ArgMatches, ColorChoice}; use color_eyre::eyre::Result; +use namada::types::io::DefaultIo; use utils::*; pub use utils::{dispatch_prompt, safe_exit, Cmd, TESTIN}; pub use self::context::Context; +use crate::cli::api::CliIo; include!("../../version.rs"); @@ -2585,6 +2587,14 @@ pub mod args { arg_default("gas-limit", DefaultFn(|| GasLimit::from(20_000))); pub const FEE_TOKEN: ArgDefaultFromCtx = arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".parse().unwrap())); + pub const FEE_PAYER: Arg = arg("fee-payer"); + pub const FEE_AMOUNT: ArgDefault = arg_default( + "fee-amount", + DefaultFn(|| token::DenominatedAmount { + amount: token::Amount::default(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }), + ); pub const GENESIS_PATH: Arg = arg("genesis-path"); pub const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); @@ -4233,14 +4243,6 @@ pub mod args { } } - impl CliToSdk> for QueryPgf { - fn to_sdk(self, ctx: &mut Context) -> QueryPgf { - QueryPgf:: { - query: self.query.to_sdk(ctx), - } - } - } - impl Args for QueryPgf { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -4253,6 +4255,14 @@ pub mod args { } } + impl CliToSdk> for QueryPgf { + fn to_sdk(self, ctx: &mut Context) -> QueryPgf { + QueryPgf:: { + query: self.query.to_sdk(ctx), + } + } + } + impl CliToSdk> for Withdraw { fn to_sdk(self, ctx: &mut Context) -> Withdraw { Withdraw:: { @@ -5653,7 +5663,7 @@ pub fn namada_client_cli() -> Result { let global_args = args::Global::parse(&matches); match cmd { cmds::NamadaClient::WithContext(sub_cmd) => { - let context = Context::new(global_args)?; + let context = Context::new::(global_args)?; Ok(NamadaClient::WithContext(Box::new((sub_cmd, context)))) } cmds::NamadaClient::WithoutContext(sub_cmd) => { @@ -5689,7 +5699,7 @@ pub fn namada_relayer_cli() -> Result { cmds::EthBridgePool::WithContext(sub_cmd), ) => { let global_args = args::Global::parse(&matches); - let context = Context::new(global_args)?; + let context = Context::new::(global_args)?; Ok(NamadaRelayer::EthBridgePoolWithCtx(Box::new(( sub_cmd, context, )))) diff --git a/apps/src/lib/cli/api.rs b/apps/src/lib/cli/api.rs index c22fe39fd3..2cf609f18e 100644 --- a/apps/src/lib/cli/api.rs +++ b/apps/src/lib/cli/api.rs @@ -4,6 +4,7 @@ use namada::ledger::queries::Client; use namada::ledger::rpc::wait_until_node_is_synched; use namada::tendermint_rpc::HttpClient; use namada::types::control_flow::Halt; +use namada::types::io::Io; use tendermint_config::net::Address as TendermintAddress; use crate::client::utils; @@ -12,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) -> Halt<()>; } #[async_trait::async_trait(?Send)] @@ -21,9 +22,14 @@ 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(self).await + async fn wait_until_node_is_synced(&self) -> Halt<()> { + wait_until_node_is_synched::<_, IO>(self).await } } -pub struct CliApi(PhantomData); +pub struct CliIo; + +#[async_trait::async_trait(?Send)] +impl Io for CliIo {} + +pub struct CliApi(PhantomData); diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 675c5b4aea..b89c4ae58a 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -3,6 +3,7 @@ use namada::ledger::eth_bridge::bridge_pool; use namada::ledger::tx::dump_tx; use namada::ledger::{signing, tx as sdk_tx}; use namada::types::control_flow::ProceedOrElse; +use namada::types::io::Io; use crate::cli; use crate::cli::api::{CliApi, CliClient}; @@ -14,7 +15,7 @@ fn error() -> Report { eyre!("Fatal error") } -impl CliApi { +impl CliApi { pub async fn handle_client_command( client: Option, cmd: cli::NamadaClient, @@ -35,20 +36,21 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run || args.tx.dry_run_wrapper; - tx::submit_custom(&client, &mut ctx, args).await?; + tx::submit_custom::<_, IO>(&client, &mut ctx, args) + .await?; if !dry_run { crate::wallet::save(&ctx.wallet) .unwrap_or_else(|err| eprintln!("{}", err)); } else { - println!( + IO::println( "Transaction dry run. No addresses have been \ - saved." + saved.", ) } } @@ -59,11 +61,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_transfer(&client, ctx, args).await?; + tx::submit_transfer::<_, IO>(&client, ctx, args) + .await?; } Sub::TxIbcTransfer(TxIbcTransfer(mut args)) => { let client = client.unwrap_or_else(|| { @@ -72,11 +75,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer(&client, ctx, args).await?; + tx::submit_ibc_transfer::<_, IO>(&client, ctx, args) + .await?; } Sub::TxUpdateAccount(TxUpdateAccount(mut args)) => { let client = client.unwrap_or_else(|| { @@ -85,12 +89,14 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_update_account(&client, &mut ctx, args) - .await?; + tx::submit_update_account::<_, IO>( + &client, &mut ctx, args, + ) + .await?; } Sub::TxInitAccount(TxInitAccount(mut args)) => { let client = client.unwrap_or_else(|| { @@ -99,21 +105,23 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run || args.tx.dry_run_wrapper; - tx::submit_init_account(&client, &mut ctx, args) - .await?; + tx::submit_init_account::<_, IO>( + &client, &mut ctx, args, + ) + .await?; if !dry_run { crate::wallet::save(&ctx.wallet) .unwrap_or_else(|err| eprintln!("{}", err)); } else { - println!( + IO::println( "Transaction dry run. No addresses have been \ - saved." + saved.", ) } } @@ -124,11 +132,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_init_validator(&client, ctx, args).await?; + tx::submit_init_validator::<_, IO>(&client, ctx, args) + .await?; } Sub::TxInitProposal(TxInitProposal(mut args)) => { let client = client.unwrap_or_else(|| { @@ -137,11 +146,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_init_proposal(&client, ctx, args).await?; + tx::submit_init_proposal::<_, IO>(&client, ctx, args) + .await?; } Sub::TxVoteProposal(TxVoteProposal(mut args)) => { let client = client.unwrap_or_else(|| { @@ -150,11 +160,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal(&client, ctx, args).await?; + tx::submit_vote_proposal::<_, IO>(&client, ctx, args) + .await?; } Sub::TxRevealPk(TxRevealPk(mut args)) => { let client = client.unwrap_or_else(|| { @@ -163,11 +174,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk(&client, &mut ctx, args).await?; + tx::submit_reveal_pk::<_, IO>(&client, &mut ctx, args) + .await?; } Sub::Bond(Bond(mut args)) => { let client = client.unwrap_or_else(|| { @@ -176,11 +188,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_bond(&client, &mut ctx, args).await?; + tx::submit_bond::<_, IO>(&client, &mut ctx, args) + .await?; } Sub::Unbond(Unbond(mut args)) => { let client = client.unwrap_or_else(|| { @@ -189,11 +202,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_unbond(&client, &mut ctx, args).await?; + tx::submit_unbond::<_, IO>(&client, &mut ctx, args) + .await?; } Sub::Withdraw(Withdraw(mut args)) => { let client = client.unwrap_or_else(|| { @@ -202,11 +216,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_withdraw(&client, ctx, args).await?; + tx::submit_withdraw::<_, IO>(&client, ctx, args) + .await?; } Sub::TxCommissionRateChange(TxCommissionRateChange( mut args, @@ -217,11 +232,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_validator_commission_change( + tx::submit_validator_commission_change::<_, IO>( &client, ctx, args, ) .await?; @@ -235,14 +250,14 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); let tx_args = args.tx.clone(); let default_signer = Some(args.sender.clone()); - let signing_data = tx::aux_signing_data( + let signing_data = tx::aux_signing_data::<_, IO>( &client, &mut ctx.wallet, &args.tx, @@ -252,7 +267,7 @@ impl CliApi { .await?; let (mut tx, _epoch) = - bridge_pool::build_bridge_pool_tx( + bridge_pool::build_bridge_pool_tx::<_, _, _, IO>( &client, &mut ctx.wallet, &mut ctx.shielded, @@ -261,7 +276,7 @@ impl CliApi { ) .await?; - signing::generate_test_vector( + signing::generate_test_vector::<_, _, IO>( &client, &mut ctx.wallet, &tx, @@ -269,9 +284,9 @@ impl CliApi { .await?; if args.tx.dump_tx { - dump_tx(&args.tx, tx); + dump_tx::(&args.tx, tx); } else { - tx::submit_reveal_aux( + tx::submit_reveal_aux::<_, IO>( &client, &mut ctx, tx_args.clone(), @@ -286,7 +301,7 @@ impl CliApi { signing_data, )?; - sdk_tx::process_tx( + sdk_tx::process_tx::<_, _, IO>( &client, &mut ctx.wallet, &tx_args, @@ -302,11 +317,14 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_unjail_validator(&client, ctx, args).await?; + tx::submit_unjail_validator::<_, IO>( + &client, ctx, args, + ) + .await?; } Sub::TxUpdateStewardCommission( TxUpdateStewardCommission(mut args), @@ -317,11 +335,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_update_steward_commission( + tx::submit_update_steward_commission::<_, IO>( &client, ctx, args, ) .await?; @@ -333,11 +351,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_resign_steward(&client, ctx, args).await?; + tx::submit_resign_steward::<_, IO>(&client, ctx, args) + .await?; } // Ledger queries Sub::QueryEpoch(QueryEpoch(mut args)) => { @@ -345,10 +364,10 @@ impl CliApi { C::from_tendermint_address(&mut args.ledger_address) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; - rpc::query_and_print_epoch(&client).await; + rpc::query_and_print_epoch::<_, IO>(&client).await; } Sub::QueryValidatorState(QueryValidatorState(mut args)) => { let client = client.unwrap_or_else(|| { @@ -357,11 +376,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_and_print_validator_state( + rpc::query_and_print_validator_state::<_, IO>( &client, &mut ctx.wallet, args, @@ -375,11 +394,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_transfers( + rpc::query_transfers::<_, _, IO>( &client, &mut ctx.wallet, &mut ctx.shielded, @@ -394,22 +413,26 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_conversions(&client, &mut ctx.wallet, args) - .await; + rpc::query_conversions::<_, IO>( + &client, + &mut ctx.wallet, + 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::() .await .proceed_or_else(error)?; - rpc::query_block(&client).await; + rpc::query_block::<_, IO>(&client).await; } Sub::QueryBalance(QueryBalance(mut args)) => { let client = client.unwrap_or_else(|| { @@ -418,11 +441,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_balance( + rpc::query_balance::<_, _, IO>( &client, &mut ctx.wallet, &mut ctx.shielded, @@ -437,13 +460,17 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_bonds(&client, &mut ctx.wallet, args) - .await - .expect("expected successful query of bonds"); + rpc::query_bonds::<_, IO>( + &client, + &mut ctx.wallet, + args, + ) + .await + .expect("expected successful query of bonds"); } Sub::QueryBondedStake(QueryBondedStake(mut args)) => { let client = client.unwrap_or_else(|| { @@ -452,11 +479,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_bonded_stake(&client, args).await; + rpc::query_bonded_stake::<_, IO>(&client, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(mut args)) => { let client = client.unwrap_or_else(|| { @@ -465,11 +492,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_and_print_commission_rate( + rpc::query_and_print_commission_rate::<_, IO>( &client, &mut ctx.wallet, args, @@ -483,12 +510,16 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_slashes(&client, &mut ctx.wallet, args) - .await; + rpc::query_slashes::<_, IO>( + &client, + &mut ctx.wallet, + args, + ) + .await; } Sub::QueryDelegations(QueryDelegations(mut args)) => { let client = client.unwrap_or_else(|| { @@ -497,12 +528,16 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_delegations(&client, &mut ctx.wallet, args) - .await; + rpc::query_delegations::<_, IO>( + &client, + &mut ctx.wallet, + args, + ) + .await; } Sub::QueryFindValidator(QueryFindValidator(mut args)) => { let client = client.unwrap_or_else(|| { @@ -511,11 +546,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_find_validator(&client, args).await; + rpc::query_find_validator::<_, IO>(&client, args).await; } Sub::QueryResult(QueryResult(mut args)) => { let client = client.unwrap_or_else(|| { @@ -524,11 +559,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_result(&client, args).await; + rpc::query_result::<_, IO>(&client, args).await; } Sub::QueryRawBytes(QueryRawBytes(mut args)) => { let client = client.unwrap_or_else(|| { @@ -537,13 +572,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_raw_bytes(&client, args).await; + rpc::query_raw_bytes::<_, IO>(&client, args).await; } - Sub::QueryProposal(QueryProposal(mut args)) => { let client = client.unwrap_or_else(|| { C::from_tendermint_address( @@ -551,11 +585,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_proposal(&client, args).await; + rpc::query_proposal::<_, IO>(&client, args).await; } Sub::QueryProposalResult(QueryProposalResult(mut args)) => { let client = client.unwrap_or_else(|| { @@ -564,11 +598,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_proposal_result(&client, args).await; + rpc::query_proposal_result::<_, IO>(&client, args) + .await; } Sub::QueryProtocolParameters(QueryProtocolParameters( mut args, @@ -579,11 +614,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_protocol_parameters(&client, args).await; + rpc::query_protocol_parameters::<_, IO>(&client, args) + .await; } Sub::QueryPgf(QueryPgf(mut args)) => { let client = client.unwrap_or_else(|| { @@ -592,11 +628,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_pgf(&client, args).await; + rpc::query_pgf::<_, IO>(&client, args).await; } Sub::QueryAccount(QueryAccount(mut args)) => { let client = client.unwrap_or_else(|| { @@ -605,11 +641,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_account(&client, args).await; + rpc::query_account::<_, IO>(&client, args).await; } Sub::SignTx(SignTx(mut args)) => { let client = client.unwrap_or_else(|| { @@ -618,11 +654,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::sign_tx(&client, &mut ctx, args).await?; + tx::sign_tx::<_, IO>(&client, &mut ctx, args).await?; } } } @@ -647,17 +683,17 @@ impl CliApi { utils::default_base_dir(global_args, args) } Utils::EpochSleep(EpochSleep(args)) => { - let mut ctx = cli::Context::new(global_args) + let mut ctx = cli::Context::new::(global_args) .expect("expected to construct a context"); let mut ledger_address = args.ledger_address.clone(); let client = C::from_tendermint_address(&mut ledger_address); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::epoch_sleep(&client, args).await; + rpc::epoch_sleep::<_, IO>(&client, args).await; } }, } diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 14a75ca460..1df122bc9c 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -11,6 +11,7 @@ use namada::ledger::wallet::Wallet; use namada::types::address::{Address, InternalAddress}; use namada::types::chain::ChainId; use namada::types::ethereum_events::EthAddress; +use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::*; @@ -83,7 +84,7 @@ pub struct Context { } impl Context { - pub fn new(global_args: args::Global) -> Result { + pub fn new(global_args: args::Global) -> Result { let global_config = read_or_try_new_global_config(&global_args); tracing::debug!("Chain ID: {}", global_config.default_chain_id); @@ -144,7 +145,7 @@ impl Context { wallet, global_config, config, - shielded: CLIShieldedUtils::new(chain_dir), + shielded: CLIShieldedUtils::new::(chain_dir), native_token, }) } diff --git a/apps/src/lib/cli/relayer.rs b/apps/src/lib/cli/relayer.rs index 242a9ff061..f0d38cce77 100644 --- a/apps/src/lib/cli/relayer.rs +++ b/apps/src/lib/cli/relayer.rs @@ -4,6 +4,7 @@ use color_eyre::eyre::{eyre, Report, Result}; use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::ledger::eth_bridge::{bridge_pool, validator_set}; use namada::types::control_flow::ProceedOrElse; +use namada::types::io::Io; use crate::cli; use crate::cli::api::{CliApi, CliClient}; @@ -14,7 +15,7 @@ fn error() -> Report { eyre!("Fatal error") } -impl CliApi { +impl CliApi { pub async fn handle_relayer_command( client: Option, cmd: cli::NamadaRelayer, @@ -35,11 +36,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - bridge_pool::recommend_batch(&client, args) + bridge_pool::recommend_batch::<_, IO>(&client, args) .await .proceed_or_else(error)?; } @@ -55,11 +56,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - bridge_pool::construct_proof(&client, args) + bridge_pool::construct_proof::<_, IO>(&client, args) .await .proceed_or_else(error)?; } @@ -70,7 +71,7 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let eth_client = Arc::new( @@ -78,7 +79,7 @@ impl CliApi { .unwrap(), ); let args = args.to_sdk_ctxless(); - bridge_pool::relay_bridge_pool_proof( + bridge_pool::relay_bridge_pool_proof::<_, _, IO>( eth_client, &client, args, ) .await @@ -91,10 +92,10 @@ impl CliApi { C::from_tendermint_address(&mut query.ledger_address) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; - bridge_pool::query_bridge_pool(&client).await; + bridge_pool::query_bridge_pool::<_, IO>(&client).await; } EthBridgePoolWithoutCtx::QuerySigned( QuerySignedBridgePool(mut query), @@ -103,10 +104,10 @@ impl CliApi { C::from_tendermint_address(&mut query.ledger_address) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; - bridge_pool::query_signed_bridge_pool(&client) + bridge_pool::query_signed_bridge_pool::<_, IO>(&client) .await .proceed_or_else(error)?; } @@ -117,10 +118,10 @@ impl CliApi { C::from_tendermint_address(&mut query.ledger_address) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; - bridge_pool::query_relay_progress(&client).await; + bridge_pool::query_relay_progress::<_, IO>(&client).await; } }, cli::NamadaRelayer::ValidatorSet(sub) => match sub { @@ -133,12 +134,14 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - validator_set::query_validator_set_args(&client, args) - .await; + validator_set::query_validator_set_args::<_, IO>( + &client, args, + ) + .await; } ValidatorSet::ValidatorSetProof(ValidatorSetProof( mut args, @@ -149,11 +152,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - validator_set::query_validator_set_update_proof( + validator_set::query_validator_set_update_proof::<_, IO>( &client, args, ) .await; @@ -167,7 +170,7 @@ impl CliApi { ) }); client - .wait_until_node_is_synced() + .wait_until_node_is_synced::() .await .proceed_or_else(error)?; let eth_client = Arc::new( @@ -175,7 +178,7 @@ impl CliApi { .unwrap(), ); let args = args.to_sdk_ctxless(); - validator_set::relay_validator_set_update( + validator_set::relay_validator_set_update::<_, _, IO>( eth_client, &client, args, ) .await diff --git a/apps/src/lib/cli/utils.rs b/apps/src/lib/cli/utils.rs index aed45507d9..4ae8e4adbc 100644 --- a/apps/src/lib/cli/utils.rs +++ b/apps/src/lib/cli/utils.rs @@ -10,6 +10,7 @@ use lazy_static::lazy_static; use super::args; use super::context::{Context, FromContext}; +use crate::cli::api::CliIo; // We only use static strings pub type App = clap::Command; @@ -24,7 +25,7 @@ pub trait Cmd: Sized { match Self::parse(&matches) { Some(cmd) => { let global_args = args::Global::parse(&matches); - let context = Context::new(global_args)?; + let context = Context::new::(global_args)?; Ok((cmd, context)) } None => { diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 7505c59efe..d72ee3ecd0 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -9,8 +9,10 @@ use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::ledger::masp::find_valid_diversifier; use namada::ledger::wallet::{DecryptionError, FindKeyError}; +use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; +use namada::{display, display_line, edisplay_line}; use rand_core::OsRng; use crate::cli; @@ -19,7 +21,7 @@ use crate::cli::args::CliToSdk; use crate::cli::{args, cmds, Context}; use crate::wallet::{read_and_confirm_encryption_password, CliWalletUtils}; -impl CliApi { +impl CliApi { pub fn handle_wallet_command( cmd: cmds::NamadaWallet, mut ctx: Context, @@ -27,57 +29,57 @@ impl CliApi { match cmd { cmds::NamadaWallet::Key(sub) => match sub { cmds::WalletKey::Restore(cmds::KeyRestore(args)) => { - key_and_address_restore(ctx, args) + key_and_address_restore::(ctx, args) } cmds::WalletKey::Gen(cmds::KeyGen(args)) => { - key_and_address_gen(ctx, args) + key_and_address_gen::(ctx, args) } cmds::WalletKey::Find(cmds::KeyFind(args)) => { - key_find(ctx, args) + key_find::(ctx, args) } cmds::WalletKey::List(cmds::KeyList(args)) => { - key_list(ctx, args) + key_list::(ctx, args) } cmds::WalletKey::Export(cmds::Export(args)) => { - key_export(ctx, args) + key_export::(ctx, args) } }, cmds::NamadaWallet::Address(sub) => match sub { cmds::WalletAddress::Gen(cmds::AddressGen(args)) => { - key_and_address_gen(ctx, args) + key_and_address_gen::(ctx, args) } cmds::WalletAddress::Restore(cmds::AddressRestore(args)) => { - key_and_address_restore(ctx, args) + key_and_address_restore::(ctx, args) } cmds::WalletAddress::Find(cmds::AddressOrAliasFind(args)) => { - address_or_alias_find(ctx, args) + address_or_alias_find::(ctx, args) } cmds::WalletAddress::List(cmds::AddressList) => { - address_list(ctx) + address_list::(ctx) } cmds::WalletAddress::Add(cmds::AddressAdd(args)) => { - address_add(ctx, args) + address_add::(ctx, args) } }, cmds::NamadaWallet::Masp(sub) => match sub { cmds::WalletMasp::GenSpendKey(cmds::MaspGenSpendKey(args)) => { - spending_key_gen(ctx, args) + spending_key_gen::(ctx, args) } cmds::WalletMasp::GenPayAddr(cmds::MaspGenPayAddr(args)) => { let args = args.to_sdk(&mut ctx); - payment_address_gen(ctx, args) + payment_address_gen::(ctx, args) } cmds::WalletMasp::AddAddrKey(cmds::MaspAddAddrKey(args)) => { - address_key_add(ctx, args) + address_key_add::(ctx, args) } cmds::WalletMasp::ListPayAddrs(cmds::MaspListPayAddrs) => { - payment_addresses_list(ctx) + payment_addresses_list::(ctx) } cmds::WalletMasp::ListKeys(cmds::MaspListKeys(args)) => { - spending_keys_list(ctx, args) + spending_keys_list::(ctx, args) } cmds::WalletMasp::FindAddrKey(cmds::MaspFindAddrKey(args)) => { - address_key_find(ctx, args) + address_key_find::(ctx, args) } }, } @@ -86,7 +88,7 @@ impl CliApi { } /// Find shielded address or key -fn address_key_find( +fn address_key_find( ctx: Context, args::AddrKeyFind { alias, @@ -97,21 +99,24 @@ fn address_key_find( let alias = alias.to_lowercase(); if let Ok(viewing_key) = wallet.find_viewing_key(&alias) { // Check if alias is a viewing key - println!("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) => println!("Spending key: {}", spending_key), + Ok(spending_key) => { + display_line!(IO, "Spending key: {}", spending_key) + } Err(FindKeyError::KeyNotFound) => {} - Err(err) => eprintln!("{}", 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 - println!("Payment address: {}", payment_addr); + display_line!(IO, "Payment address: {}", payment_addr); } else { // Otherwise alias cannot be referring to any shielded value - println!( + display_line!( + 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.", @@ -121,7 +126,7 @@ fn address_key_find( } /// List spending keys. -fn spending_keys_list( +fn spending_keys_list( ctx: Context, args::MaspKeysList { decrypt, @@ -132,32 +137,33 @@ fn spending_keys_list( let known_view_keys = wallet.get_viewing_keys(); let known_spend_keys = wallet.get_spending_keys(); if known_view_keys.is_empty() { - println!( + display_line!( + IO, "No known keys. Try `masp add --alias my-addr --value ...` to add \ - a new key to the wallet." + a new key to the wallet.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - writeln!(w, "Known keys:").unwrap(); + display_line!(IO, &mut w; "Known keys:").unwrap(); for (alias, key) in known_view_keys { - write!(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() { - writeln!(w, " (encrypted):") + display_line!(IO, &mut w; " (encrypted):") } else { - writeln!(w, " (not encrypted):") + display_line!(IO, &mut w; " (not encrypted):") } .unwrap(); } else { - writeln!(w, ":").unwrap(); + display_line!(IO, &mut w; ":").unwrap(); } // Always print the corresponding viewing key - writeln!(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 { @@ -166,8 +172,11 @@ fn spending_keys_list( // Here the spending key is unencrypted or successfully // decrypted Ok(spending_key) => { - writeln!(w, " Spending key: {}", spending_key) - .unwrap(); + display_line!(IO, + &mut w; + " Spending key: {}", spending_key, + ) + .unwrap(); } // Here the key is encrypted but decryption has not been // requested @@ -177,10 +186,10 @@ fn spending_keys_list( // Here the key is encrypted but incorrect password has // been provided Err(err) => { - writeln!( - w, - " Couldn't decrypt the spending key: {}", - err + display_line!(IO, + &mut w; + " Couldn't decrypt the spending key: {}", + err, ) .unwrap(); } @@ -192,26 +201,27 @@ fn spending_keys_list( } /// List payment addresses. -fn payment_addresses_list(ctx: Context) { +fn payment_addresses_list(ctx: Context) { let wallet = ctx.wallet; let known_addresses = wallet.get_payment_addrs(); if known_addresses.is_empty() { - println!( + display_line!( + IO, "No known payment addresses. Try `masp gen-addr --alias my-addr` \ - to generate a new payment address." + to generate a new payment address.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - writeln!(w, "Known payment addresses:").unwrap(); + display_line!(IO, &mut w; "Known payment addresses:").unwrap(); for (alias, address) in sorted(known_addresses) { - writeln!(w, " \"{}\": {}", alias, address).unwrap(); + display_line!(IO, &mut w; " \"{}\": {}", alias, address).unwrap(); } } } /// Generate a spending key. -fn spending_key_gen( +fn spending_key_gen( ctx: Context, args::MaspSpendKeyGen { alias, @@ -224,14 +234,15 @@ fn spending_key_gen( 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)); - println!( + display_line!( + IO, "Successfully added a spending key with alias: \"{}\"", alias ); } /// Generate a shielded payment address from the given key. -fn payment_address_gen( +fn payment_address_gen( ctx: Context, args::MaspPayAddrGen { alias, @@ -254,18 +265,19 @@ fn payment_address_gen( alias_force, ) .unwrap_or_else(|| { - eprintln!("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)); - println!( + display_line!( + 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( +fn address_key_add( mut ctx: Context, args::MaspAddrKeyAdd { alias, @@ -281,7 +293,7 @@ fn address_key_add( .wallet .insert_viewing_key(alias, viewing_key, alias_force) .unwrap_or_else(|| { - eprintln!("Viewing key not added"); + edisplay_line!(IO, "Viewing key not added"); cli::safe_exit(1); }); (alias, "viewing key") @@ -298,7 +310,7 @@ fn address_key_add( alias_force, ) .unwrap_or_else(|| { - eprintln!("Spending key not added"); + edisplay_line!(IO, "Spending key not added"); cli::safe_exit(1); }); (alias, "spending key") @@ -308,22 +320,24 @@ fn address_key_add( .wallet .insert_payment_addr(alias, payment_addr, alias_force) .unwrap_or_else(|| { - eprintln!("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)); - println!( + display_line!( + IO, "Successfully added a {} with the following alias to wallet: {}", - typ, alias, + typ, + alias, ); } /// Restore a keypair and an implicit address from the mnemonic code in the /// wallet. -fn key_and_address_restore( +fn key_and_address_restore( ctx: Context, args::KeyAndAddressRestore { scheme, @@ -345,15 +359,17 @@ fn key_and_address_restore( encryption_password, ) .unwrap_or_else(|err| { - eprintln!("{}", err); + edisplay_line!(IO, "{}", err); cli::safe_exit(1) }) .unwrap_or_else(|| { - println!("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| eprintln!("{}", err)); - println!( + crate::wallet::save(&wallet) + .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); + display_line!( + IO, "Successfully added a key and an address with alias: \"{}\"", alias ); @@ -361,7 +377,7 @@ 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( +fn key_and_address_gen( ctx: Context, args::KeyAndAddressGen { scheme, @@ -386,22 +402,24 @@ fn key_and_address_gen( derivation_path_and_mnemonic_rng, ) .unwrap_or_else(|err| { - eprintln!("{}", err); + edisplay_line!(IO, "{}", err); cli::safe_exit(1); }) .unwrap_or_else(|| { - println!("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| eprintln!("{}", err)); - println!( + crate::wallet::save(&wallet) + .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); + display_line!( + IO, "Successfully added a key and an address with alias: \"{}\"", alias ); } /// Find a keypair in the wallet store. -fn key_find( +fn key_find( ctx: Context, args::KeyFind { public_key, @@ -417,9 +435,10 @@ fn key_find( let alias = alias.or(value); match alias { None => { - eprintln!( + edisplay_line!( + IO, "An alias, public key or public key hash needs to be \ - supplied" + supplied", ); cli::safe_exit(1) } @@ -430,20 +449,20 @@ fn key_find( match found_keypair { Ok(keypair) => { let pkh: PublicKeyHash = (&keypair.ref_to()).into(); - println!("Public key hash: {}", pkh); - println!("Public key: {}", keypair.ref_to()); + display_line!(IO, "Public key hash: {}", pkh); + display_line!(IO, "Public key: {}", keypair.ref_to()); if unsafe_show_secret { - println!("Secret key: {}", keypair); + display_line!(IO, "Secret key: {}", keypair); } } Err(err) => { - eprintln!("{}", err); + edisplay_line!(IO, "{}", err); } } } /// List all known keys. -fn key_list( +fn key_list( ctx: Context, args::KeyList { decrypt, @@ -453,38 +472,54 @@ fn key_list( let wallet = ctx.wallet; let known_keys = wallet.get_keys(); if known_keys.is_empty() { - println!( + display_line!( + IO, "No known keys. Try `key gen --alias my-key` to generate a new \ - key." + key.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - writeln!(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" }; - writeln!(w, " Alias \"{}\" ({}):", alias, encrypted).unwrap(); + display_line!(IO, + &mut w; + " Alias \"{}\" ({}):", alias, encrypted, + ) + .unwrap(); if let Some(pkh) = pkh { - writeln!(w, " Public key hash: {}", pkh).unwrap(); + display_line!(IO, &mut w; " Public key hash: {}", pkh) + .unwrap(); } match stored_keypair.get::(decrypt, None) { Ok(keypair) => { - writeln!(w, " Public key: {}", keypair.ref_to()) - .unwrap(); + display_line!(IO, + &mut w; + " Public key: {}", keypair.ref_to(), + ) + .unwrap(); if unsafe_show_secret { - writeln!(w, " Secret key: {}", keypair).unwrap(); + display_line!(IO, + &mut w; + " Secret key: {}", keypair, + ) + .unwrap(); } } Err(DecryptionError::NotDecrypting) if !decrypt => { continue; } Err(err) => { - writeln!(w, " Couldn't decrypt the keypair: {}", err) - .unwrap(); + display_line!(IO, + &mut w; + " Couldn't decrypt the keypair: {}", err, + ) + .unwrap(); } } } @@ -492,7 +527,10 @@ fn key_list( } /// Export a keypair to a file. -fn key_export(ctx: Context, args::KeyExport { alias }: args::KeyExport) { +fn key_export( + ctx: Context, + args::KeyExport { alias }: args::KeyExport, +) { let mut wallet = ctx.wallet; wallet .find_key(alias.to_lowercase(), None) @@ -504,36 +542,40 @@ fn key_export(ctx: Context, args::KeyExport { alias }: args::KeyExport) { let mut file = File::create(&file_name).unwrap(); file.write_all(file_data.as_ref()).unwrap(); - println!("Exported to file {}", file_name); + display_line!(IO, "Exported to file {}", file_name); }) .unwrap_or_else(|err| { - eprintln!("{}", err); + edisplay_line!(IO, "{}", err); cli::safe_exit(1) }) } /// List all known addresses. -fn address_list(ctx: Context) { +fn address_list(ctx: Context) { let wallet = ctx.wallet; let known_addresses = wallet.get_addresses(); if known_addresses.is_empty() { - println!( + display_line!( + IO, "No known addresses. Try `address gen --alias my-addr` to \ - generate a new implicit address." + generate a new implicit address.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - writeln!(w, "Known addresses:").unwrap(); + display_line!(IO, &mut w; "Known addresses:").unwrap(); for (alias, address) in sorted(known_addresses) { - writeln!(w, " \"{}\": {}", alias, address.to_pretty_string()) - .unwrap(); + display_line!(IO, + &mut w; + " \"{}\": {}", alias, address.to_pretty_string(), + ) + .unwrap(); } } } /// Find address (alias) by its alias (address). -fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { +fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { let wallet = ctx.wallet; if args.address.is_some() && args.alias.is_some() { panic!( @@ -543,9 +585,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()) { - println!("Found address {}", address.to_pretty_string()); + display_line!(IO, "Found address {}", address.to_pretty_string()); } else { - println!( + display_line!( + IO, "No address with alias {} found. Use the command `address \ list` to see all the known addresses.", args.alias.unwrap().to_lowercase() @@ -553,9 +596,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()) { - println!("Found alias {}", alias); + display_line!(IO, "Found alias {}", alias); } else { - println!( + display_line!( + IO, "No alias with address {} found. Use the command `address \ list` to see all the known addresses.", args.address.unwrap() @@ -565,7 +609,7 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { } /// Add an address to the wallet. -fn address_add(ctx: Context, args: args::AddressAdd) { +fn address_add(ctx: Context, args: args::AddressAdd) { let mut wallet = ctx.wallet; if wallet .add_address( @@ -575,11 +619,13 @@ fn address_add(ctx: Context, args: args::AddressAdd) { ) .is_none() { - eprintln!("Address not added"); + edisplay_line!(IO, "Address not added"); cli::safe_exit(1); } - crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); - println!( + crate::wallet::save(&wallet) + .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); + display_line!( + 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 b1b46ef7fa..a1e97cfb23 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fs::{self, read_dir}; -use std::io::{self, Write}; +use std::io; use std::iter::Iterator; use std::str::FromStr; @@ -46,11 +46,13 @@ use namada::types::address::{masp, Address}; use namada::types::control_flow::ProceedOrElse; use namada::types::error::{is_pinned_error, Error, PinnedBalanceError}; use namada::types::hash::Hash; +use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{BlockHeight, BlockResults, Epoch, Key, KeySeg}; use namada::types::token::{Change, MaspDenom}; use namada::types::{error, storage, token}; +use namada::{display, display_line, edisplay_line}; use tokio::time::Instant; use crate::cli::{self, args}; @@ -63,12 +65,15 @@ use crate::wallet::CliWalletUtils; /// /// If a response is not delivered until `deadline`, we exit the cli with an /// error. -pub async fn query_tx_status( +pub async fn query_tx_status< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, status: namada::ledger::rpc::TxEventQuery<'_>, deadline: Instant, ) -> Event { - namada::ledger::rpc::query_tx_status(client, status, deadline) + namada::ledger::rpc::query_tx_status::<_, IO>(client, status, deadline) .await .proceed() } @@ -76,28 +81,32 @@ pub async fn query_tx_status( /// 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 = namada::ledger::rpc::query_epoch(client).await.unwrap(); - println!("Last committed epoch: {}", epoch); + display_line!(IO, "Last committed epoch: {}", epoch); epoch } /// Query the last committed block -pub async fn query_block( +pub async fn query_block( client: &C, ) { let block = namada::ledger::rpc::query_block(client).await.unwrap(); match block { Some(block) => { - println!( + display_line!( + IO, "Last committed block ID: {}, height: {}, time: {}", - block.hash, block.height, block.time + block.hash, + block.height, + block.time ); } None => { - println!("No block has been committed yet."); + display_line!(IO, "No block has been committed yet."); } } } @@ -116,6 +125,7 @@ pub async fn query_results( pub async fn query_transfers< C: namada::ledger::queries::Client + Sync, U: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -163,7 +173,7 @@ pub async fn query_transfers< // Realize the rewards that would have been attained upon the // transaction's reception let amt = shielded - .compute_exchanged_amount( + .compute_exchanged_amount::<_, IO>( client, amt, epoch, @@ -194,33 +204,43 @@ pub async fn query_transfers< if !relevant { continue; } - println!("Height: {}, Index: {}, Transparent Transfer:", height, idx); + display_line!( + IO, + "Height: {}, Index: {}, Transparent Transfer:", + height, + idx + ); // Display the transparent changes first for (account, MaspChange { ref asset, change }) in tfer_delta { if account != masp() { - print!(" {}:", account); + display!(IO, " {}:", account); let token_alias = wallet.lookup_alias(asset); let sign = match change.cmp(&Change::zero()) { Ordering::Greater => "+", Ordering::Less => "-", Ordering::Equal => "", }; - print!( + display!( + IO, " {}{} {}", sign, - format_denominated_amount(client, asset, change.into(),) - .await, + format_denominated_amount::<_, IO>( + client, + asset, + change.into(), + ) + .await, token_alias ); } - println!(); + display_line!(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) { - print!(" {}:", fvk_map[&account]); + display!(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()) { @@ -228,10 +248,11 @@ pub async fn query_transfers< Ordering::Less => "-", Ordering::Equal => "", }; - print!( + display!( + IO, " {}{} {}", sign, - format_denominated_amount( + format_denominated_amount::<_, IO>( client, &token_addr, val.into(), @@ -240,14 +261,17 @@ pub async fn query_transfers< token_alias, ); } - println!(); + display_line!(IO, ""); } } } } /// Query the raw bytes of given storage key -pub async fn query_raw_bytes( +pub async fn query_raw_bytes< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, args: args::QueryRawBytes, ) { @@ -257,9 +281,9 @@ pub async fn query_raw_bytes( .await, ); if !response.data.is_empty() { - println!("Found data: 0x{}", HEXLOWER.encode(&response.data)); + display_line!(IO, "Found data: 0x{}", HEXLOWER.encode(&response.data)); } else { - println!("No data found for key {}", args.storage_key); + display_line!(IO, "No data found for key {}", args.storage_key); } } @@ -267,6 +291,7 @@ pub async fn query_raw_bytes( pub async fn query_balance< C: namada::ledger::queries::Client + Sync, U: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -277,22 +302,35 @@ pub async fn query_balance< // the CLI arguments match &args.owner { Some(BalanceOwner::FullViewingKey(_viewing_key)) => { - query_shielded_balance(client, wallet, shielded, args).await + query_shielded_balance::<_, _, IO>(client, wallet, shielded, args) + .await } Some(BalanceOwner::Address(_owner)) => { - query_transparent_balance(client, wallet, args).await + query_transparent_balance::<_, IO>(client, wallet, args).await } Some(BalanceOwner::PaymentAddress(_owner)) => { - query_pinned_balance(client, wallet, shielded, args).await + query_pinned_balance::<_, _, IO>(client, wallet, shielded, args) + .await } None => { // Print pinned balance - query_pinned_balance(client, wallet, shielded, args.clone()).await; + query_pinned_balance::<_, _, IO>( + client, + wallet, + shielded, + args.clone(), + ) + .await; // Print shielded balance - query_shielded_balance(client, wallet, shielded, args.clone()) - .await; + query_shielded_balance::<_, _, IO>( + client, + wallet, + shielded, + args.clone(), + ) + .await; // Then print transparent balance - query_transparent_balance(client, wallet, args).await; + query_transparent_balance::<_, IO>(client, wallet, args).await; } }; } @@ -300,6 +338,7 @@ pub async fn query_balance< /// Query token balance(s) pub async fn query_transparent_balance< C: namada::ledger::queries::Client + Sync, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -319,14 +358,20 @@ pub async fn query_transparent_balance< .await { Ok(balance) => { - let balance = - format_denominated_amount(client, &token, balance) - .await; - println!("{}: {}", token_alias, balance); + let balance = format_denominated_amount::<_, IO>( + client, &token, balance, + ) + .await; + display_line!(IO, "{}: {}", token_alias, balance); } Err(e) => { - println!("Eror in querying: {e}"); - println!("No {} balance found for {}", token_alias, owner) + display_line!(IO, "Eror in querying: {e}"); + display_line!( + IO, + "No {} balance found for {}", + token_alias, + owner + ) } } } @@ -335,27 +380,37 @@ pub async fn query_transparent_balance< for (token_alias, token) in tokens { let balance = get_token_balance(client, &token, &owner).await; if !balance.is_zero() { - let balance = - format_denominated_amount(client, &token, balance) - .await; - println!("{}: {}", token_alias, balance); + let balance = format_denominated_amount::<_, IO>( + client, &token, balance, + ) + .await; + display_line!(IO, "{}: {}", token_alias, balance); } } } (Some(token), None) => { let prefix = token::balance_prefix(&token); let balances = - query_storage_prefix::(client, &prefix).await; - if let Some(balances) = balances { - print_balances(client, wallet, balances, Some(&token), None) + query_storage_prefix::(client, &prefix) .await; + if let Some(balances) = balances { + print_balances::<_, IO>( + client, + wallet, + balances, + Some(&token), + None, + ) + .await; } } (None, None) => { let balances = - query_storage_prefix::(client, &prefix).await; + query_storage_prefix::(client, &prefix) + .await; if let Some(balances) = balances { - print_balances(client, wallet, balances, None, None).await; + print_balances::<_, IO>(client, wallet, balances, None, None) + .await; } } } @@ -365,6 +420,7 @@ pub async fn query_transparent_balance< pub async fn query_pinned_balance< C: namada::ledger::queries::Client + Sync, U: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -398,7 +454,7 @@ pub async fn query_pinned_balance< // address for vk in &viewing_keys { balance = shielded - .compute_exchanged_pinned_balance(client, owner, vk) + .compute_exchanged_pinned_balance::<_, IO>(client, owner, vk) .await; if !is_pinned_error(&balance) { break; @@ -410,21 +466,22 @@ pub async fn query_pinned_balance< let fvk = match ExtendedViewingKey::from_str(vk_str.trim()) { Ok(fvk) => fvk, _ => { - eprintln!("Invalid viewing key entered"); + edisplay_line!(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(client, owner, &vk) + .compute_exchanged_pinned_balance::<_, IO>(client, owner, &vk) .await } // Now print out the received quantities according to CLI arguments match (balance, args.token.as_ref()) { (Err(Error::Pinned(PinnedBalanceError::InvalidViewingKey)), _) => { - println!( + display_line!( + IO, "Supplied viewing key cannot decode transactions to given \ payment address." ) @@ -433,10 +490,14 @@ pub async fn query_pinned_balance< Err(Error::Pinned(PinnedBalanceError::NoTransactionPinned)), _, ) => { - println!("Payment address {} has not yet been consumed.", owner) + display_line!( + IO, + "Payment address {} has not yet been consumed.", + owner + ) } (Err(other), _) => { - println!("Error in Querying Pinned balance {}", other) + display_line!(IO, "Error in Querying Pinned balance {}", other) } (Ok((balance, epoch)), Some(token)) => { let token_alias = wallet.lookup_alias(token); @@ -447,22 +508,29 @@ pub async fn query_pinned_balance< .unwrap_or_default(); if total_balance.is_zero() { - println!( + display_line!( + IO, "Payment address {} was consumed during epoch {}. \ Received no shielded {}", - owner, epoch, token_alias + owner, + epoch, + token_alias ); } else { - let formatted = format_denominated_amount( + let formatted = format_denominated_amount::<_, IO>( client, token, total_balance.into(), ) .await; - println!( + display_line!( + IO, "Payment address {} was consumed during epoch {}. \ Received {} {}", - owner, epoch, formatted, token_alias, + owner, + epoch, + formatted, + token_alias, ); } } @@ -474,14 +542,16 @@ pub async fn query_pinned_balance< .filter(|((token_epoch, _), _)| *token_epoch == epoch) { if !found_any { - println!( + display_line!( + IO, "Payment address {} was consumed during epoch {}. \ Received:", - owner, epoch + owner, + epoch ); found_any = true; } - let formatted = format_denominated_amount( + let formatted = format_denominated_amount::<_, IO>( client, token_addr, (*value).into(), @@ -491,13 +561,15 @@ pub async fn query_pinned_balance< .get(token_addr) .map(|a| a.to_string()) .unwrap_or_else(|| token_addr.to_string()); - println!(" {}: {}", token_alias, formatted,); + display_line!(IO, " {}: {}", token_alias, formatted,); } if !found_any { - println!( + display_line!( + IO, "Payment address {} was consumed during epoch {}. \ Received no shielded assets.", - owner, epoch + owner, + epoch ); } } @@ -505,7 +577,7 @@ pub async fn query_pinned_balance< } } -async fn print_balances( +async fn print_balances( client: &C, wallet: &Wallet, balances: impl Iterator, @@ -526,7 +598,8 @@ async fn print_balances( owner.clone(), format!( ": {}, owned by {}", - format_denominated_amount(client, tok, balance).await, + format_denominated_amount::<_, IO>(client, tok, balance) + .await, wallet.lookup_alias(owner) ), ), @@ -555,45 +628,53 @@ async fn print_balances( } _ => { let token_alias = wallet.lookup_alias(&t); - writeln!(w, "Token {}", token_alias).unwrap(); + display_line!(IO, &mut w; "Token {}", token_alias).unwrap(); print_token = Some(t); } } // Print the balance - writeln!(w, "{}", s).unwrap(); + display_line!(IO, &mut w; "{}", s).unwrap(); print_num += 1; } if print_num == 0 { match (token, target) { - (Some(_), Some(target)) | (None, Some(target)) => writeln!( - w, + (Some(_), Some(target)) | (None, Some(target)) => display_line!( + IO, + &mut w; "No balances owned by {}", wallet.lookup_alias(target) ) .unwrap(), (Some(token), None) => { let token_alias = wallet.lookup_alias(token); - writeln!(w, "No balances for token {}", token_alias).unwrap() + display_line!(IO, &mut w; "No balances for token {}", token_alias).unwrap() } - (None, None) => writeln!(w, "No balances").unwrap(), + (None, None) => display_line!(IO, &mut w; "No balances").unwrap(), } } } /// Query Proposals -pub async fn query_proposal( +pub async fn query_proposal< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, args: args::QueryProposal, ) { - let current_epoch = query_and_print_epoch(client).await; + let current_epoch = query_and_print_epoch::<_, IO>(client).await; if let Some(id) = args.proposal_id { let proposal = query_proposal_by_id(client, id).await.unwrap(); if let Some(proposal) = proposal { - println!("{}", proposal.to_string_with_status(current_epoch)); + display_line!( + IO, + "{}", + proposal.to_string_with_status(current_epoch) + ); } else { - eprintln!("No proposal found with id: {}", id); + edisplay_line!(IO, "No proposal found with id: {}", id); } } else { let last_proposal_id_key = governance_storage::get_counter_key(); @@ -608,14 +689,14 @@ pub async fn query_proposal( 0 }; - println!("id: {}", last_proposal_id); + display_line!(IO, "id: {}", last_proposal_id); for id in from_id..last_proposal_id { let proposal = query_proposal_by_id(client, id) .await .unwrap() .expect("Proposal should be written to storage."); - println!("{}", proposal); + display_line!(IO, "{}", proposal); } } } @@ -632,6 +713,7 @@ pub async fn query_proposal_by_id( pub async fn query_shielded_balance< C: namada::ledger::queries::Client + Sync, U: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -658,7 +740,7 @@ pub async fn query_shielded_balance< // 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(client).await; + let epoch = query_and_print_epoch::<_, IO>(client).await; // Map addresses to token names let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); match (args.token, owner.is_some()) { @@ -675,7 +757,11 @@ pub async fn query_shielded_balance< .expect("context should contain viewing key") } else { shielded - .compute_exchanged_balance(client, &viewing_key, epoch) + .compute_exchanged_balance::<_, IO>( + client, + &viewing_key, + epoch, + ) .await .unwrap() .expect("context should contain viewing key") @@ -688,15 +774,17 @@ pub async fn query_shielded_balance< .cloned() .unwrap_or_default(); if total_balance.is_zero() { - println!( + display_line!( + IO, "No shielded {} balance found for given key", token_alias ); } else { - println!( + display_line!( + IO, "{}: {}", token_alias, - format_denominated_amount( + format_denominated_amount::<_, IO>( client, &token, token::Amount::from(total_balance) @@ -720,7 +808,11 @@ pub async fn query_shielded_balance< .expect("context should contain viewing key") } else { shielded - .compute_exchanged_balance(client, &viewing_key, epoch) + .compute_exchanged_balance::<_, IO>( + client, + &viewing_key, + epoch, + ) .await .unwrap() .expect("context should contain viewing key") @@ -742,7 +834,8 @@ pub async fn query_shielded_balance< // remove this from here, should not be making the // hashtable creation any uglier if balances.is_empty() { - println!( + display_line!( + IO, "No shielded {} balance found for any wallet key", &token_addr ); @@ -758,14 +851,14 @@ pub async fn query_shielded_balance< .get(&token) .map(|a| a.to_string()) .unwrap_or_else(|| token.to_string()); - println!("Shielded Token {}:", alias); - let formatted = format_denominated_amount( + display_line!(IO, "Shielded Token {}:", alias); + let formatted = format_denominated_amount::<_, IO>( client, &token, token_balance.into(), ) .await; - println!(" {}, owned by {}", formatted, fvk); + display_line!(IO, " {}, owned by {}", formatted, fvk); } } // Here the user wants to know the balance for a specific token across @@ -781,10 +874,10 @@ pub async fn query_shielded_balance< ) .unwrap(); let token_alias = wallet.lookup_alias(&token); - println!("Shielded Token {}:", token_alias); + display_line!(IO, "Shielded Token {}:", token_alias); let mut found_any = false; let token_alias = wallet.lookup_alias(&token); - println!("Shielded Token {}:", token_alias,); + display_line!(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; @@ -796,7 +889,11 @@ pub async fn query_shielded_balance< .expect("context should contain viewing key") } else { shielded - .compute_exchanged_balance(client, &viewing_key, epoch) + .compute_exchanged_balance::<_, IO>( + client, + &viewing_key, + epoch, + ) .await .unwrap() .expect("context should contain viewing key") @@ -806,17 +903,18 @@ pub async fn query_shielded_balance< if !val.is_zero() { found_any = true; } - let formatted = format_denominated_amount( + let formatted = format_denominated_amount::<_, IO>( client, address, (*val).into(), ) .await; - println!(" {}, owned by {}", formatted, fvk); + display_line!(IO, " {}, owned by {}", formatted, fvk); } } if !found_any { - println!( + display_line!( + IO, "No shielded {} balance found for any wallet key", token_alias, ); @@ -834,15 +932,23 @@ pub async fn query_shielded_balance< .unwrap() .expect("context should contain viewing key"); // Print balances by human-readable token names - print_decoded_balance_with_epoch(client, wallet, balance).await; + print_decoded_balance_with_epoch::<_, IO>( + client, wallet, balance, + ) + .await; } else { let balance = shielded - .compute_exchanged_balance(client, &viewing_key, epoch) + .compute_exchanged_balance::<_, IO>( + client, + &viewing_key, + epoch, + ) .await .unwrap() .expect("context should contain viewing key"); // Print balances by human-readable token names - print_decoded_balance(client, wallet, balance, epoch).await; + print_decoded_balance::<_, IO>(client, wallet, balance, epoch) + .await; } } } @@ -850,6 +956,7 @@ pub async fn query_shielded_balance< pub async fn print_decoded_balance< C: namada::ledger::queries::Client + Sync, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -857,17 +964,22 @@ pub async fn print_decoded_balance< epoch: Epoch, ) { if decoded_balance.is_empty() { - println!("No shielded balance found for given key"); + display_line!(IO, "No shielded balance found for given key"); } else { for ((_, token_addr), amount) in decoded_balance .iter() .filter(|((token_epoch, _), _)| *token_epoch == epoch) { - println!( + display_line!( + IO, "{} : {}", wallet.lookup_alias(token_addr), - format_denominated_amount(client, token_addr, (*amount).into()) - .await, + format_denominated_amount::<_, IO>( + client, + token_addr, + (*amount).into() + ) + .await, ); } } @@ -875,6 +987,7 @@ 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, @@ -882,7 +995,7 @@ pub async fn print_decoded_balance_with_epoch< ) { let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); if decoded_balance.is_empty() { - println!("No shielded balance found for given key"); + display_line!(IO, "No shielded balance found for given key"); } for ((epoch, token_addr), value) in decoded_balance.iter() { let asset_value = (*value).into(); @@ -890,11 +1003,13 @@ pub async fn print_decoded_balance_with_epoch< .get(token_addr) .map(|a| a.to_string()) .unwrap_or_else(|| token_addr.to_string()); - println!( + display_line!( + IO, "{} | {} : {}", alias, epoch, - format_denominated_amount(client, token_addr, asset_value).await, + format_denominated_amount::<_, IO>(client, token_addr, asset_value) + .await, ); } } @@ -912,6 +1027,7 @@ pub async fn get_token_balance( pub async fn query_proposal_result< C: namada::ledger::queries::Client + Sync, + IO: Io, >( client: &C, args: args::QueryProposalResult, @@ -924,7 +1040,7 @@ pub async fn query_proposal_result< { proposal } else { - eprintln!("Proposal {} not found.", proposal_id); + edisplay_line!(IO, "Proposal {} not found.", proposal_id); return; }; @@ -946,8 +1062,8 @@ pub async fn query_proposal_result< let proposal_result = compute_proposal_result(votes, total_voting_power, tally_type); - println!("Proposal Id: {} ", proposal_id); - println!("{:4}{}", "", proposal_result); + display_line!(IO, "Proposal Id: {} ", proposal_id); + display_line!(IO, "{:4}{}", "", proposal_result); } else { let proposal_folder = args.proposal_folder.expect( "The argument --proposal-folder is required with --offline.", @@ -983,11 +1099,14 @@ pub async fn query_proposal_result< if proposal.is_ok() { proposal.unwrap() } else { - eprintln!("The offline proposal is not valid."); + edisplay_line!(IO, "The offline proposal is not valid."); return; } } else { - eprintln!("Couldn't find a file name offline_proposal_*.json."); + edisplay_line!( + IO, + "Couldn't find a file name offline_proposal_*.json." + ); return; }; @@ -1001,9 +1120,12 @@ pub async fn query_proposal_result< }) .collect::>(); - let proposal_votes = - compute_offline_proposal_votes(client, &proposal, votes.clone()) - .await; + let proposal_votes = compute_offline_proposal_votes::<_, IO>( + client, + &proposal, + votes.clone(), + ) + .await; let total_voting_power = get_total_staked_tokens(client, proposal.proposal.tally_epoch) .await; @@ -1014,30 +1136,33 @@ pub async fn query_proposal_result< TallyType::TwoThird, ); - println!("Proposal offline: {}", proposal.proposal.hash()); - println!("Parsed {} votes.", votes.len()); - println!("{:4}{}", "", proposal_result); + display_line!(IO, "Proposal offline: {}", proposal.proposal.hash()); + display_line!(IO, "Parsed {} votes.", votes.len()); + display_line!(IO, "{:4}{}", "", proposal_result); } } -pub async fn query_account( +pub async fn query_account< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, args: args::QueryAccount, ) { let account = rpc::get_account_info(client, &args.owner).await.unwrap(); if let Some(account) = account { - println!("Address: {}", account.address); - println!("Threshold: {}", account.threshold); - println!("Public keys:"); + display_line!(IO, "Address: {}", account.address); + display_line!(IO, "Threshold: {}", account.threshold); + display_line!(IO, "Public keys:"); for (public_key, _) in account.public_keys_map.pk_to_idx { - println!("- {}", public_key); + display_line!(IO, "- {}", public_key); } } else { - println!("No account exists for {}", args.owner); + display_line!(IO, "No account exists for {}", args.owner); } } -pub async fn query_pgf( +pub async fn query_pgf( client: &C, _args: args::QueryPgf, ) { @@ -1045,25 +1170,36 @@ pub async fn query_pgf( let fundings = query_pgf_fundings(client).await; match stewards.is_empty() { - true => println!("Pgf stewards: no stewards are currectly set."), + true => { + display_line!(IO, "Pgf stewards: no stewards are currectly set.") + } false => { - println!("Pgf stewards:"); + display_line!(IO, "Pgf stewards:"); for steward in stewards { - println!("{:4}- {}", "", steward.address); - println!("{:4} Reward distribution:", ""); + display_line!(IO, "{:4}- {}", "", steward.address); + display_line!(IO, "{:4} Reward distribution:", ""); for (address, percentage) in steward.reward_distribution { - println!("{:6}- {} to {}", "", percentage, address); + display_line!( + IO, + "{:6}- {} to {}", + "", + percentage, + address + ); } } } } match fundings.is_empty() { - true => println!("Pgf fundings: no fundings are currently set."), + true => { + display_line!(IO, "Pgf fundings: no fundings are currently set.") + } false => { - println!("Pgf fundings:"); + display_line!(IO, "Pgf fundings:"); for funding in fundings { - println!( + display_line!( + IO, "{:4}- {} for {}", "", funding.detail.target, @@ -1076,94 +1212,116 @@ pub async fn query_pgf( pub async fn query_protocol_parameters< C: namada::ledger::queries::Client + Sync, + IO: Io, >( client: &C, _args: args::QueryProtocolParameters, ) { let governance_parameters = query_governance_parameters(client).await; - println!("Governance Parameters\n"); - println!( + display_line!(IO, "Governance Parameters\n"); + display_line!( + IO, "{:4}Min. proposal fund: {}", "", governance_parameters.min_proposal_fund.to_string_native() ); - println!( + display_line!( + IO, "{:4}Max. proposal code size: {}", - "", governance_parameters.max_proposal_code_size + "", + governance_parameters.max_proposal_code_size ); - println!( + display_line!( + IO, "{:4}Min. proposal voting period: {}", - "", governance_parameters.min_proposal_voting_period + "", + governance_parameters.min_proposal_voting_period ); - println!( + display_line!( + IO, "{:4}Max. proposal period: {}", - "", governance_parameters.max_proposal_period + "", + governance_parameters.max_proposal_period ); - println!( + display_line!( + IO, "{:4}Max. proposal content size: {}", - "", governance_parameters.max_proposal_content_size + "", + governance_parameters.max_proposal_content_size ); - println!( + display_line!( + IO, "{:4}Min. proposal grace epochs: {}", - "", governance_parameters.min_proposal_grace_epochs + "", + governance_parameters.min_proposal_grace_epochs ); let pgf_parameters = query_pgf_parameters(client).await; - println!("Public Goods Funding Parameters\n"); - println!( + display_line!(IO, "Public Goods Funding Parameters\n"); + display_line!( + IO, "{:4}Pgf inflation rate: {}", - "", pgf_parameters.pgf_inflation_rate + "", + pgf_parameters.pgf_inflation_rate ); - println!( + display_line!( + IO, "{:4}Steward inflation rate: {}", - "", pgf_parameters.stewards_inflation_rate + "", + pgf_parameters.stewards_inflation_rate ); - println!("Protocol parameters"); + display_line!(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."); - println!( + display_line!( + IO, "{:4}Min. epoch duration: {}", - "", epoch_duration.min_duration + "", + epoch_duration.min_duration ); - println!( + display_line!( + IO, "{:4}Min. number of blocks: {}", - "", epoch_duration.min_num_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) .await .expect("Parameter should be defined."); - println!("{:4}Max. block duration: {}", "", max_block_duration); + display_line!(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) .await .expect("Parameter should be defined."); - println!("{:4}VP whitelist: {:?}", "", vp_whitelist); + display_line!(IO, "{:4}VP whitelist: {:?}", "", vp_whitelist); let key = param_storage::get_tx_whitelist_storage_key(); let tx_whitelist = query_storage_value::>(client, &key) .await .expect("Parameter should be defined."); - println!("{:4}Transactions whitelist: {:?}", "", tx_whitelist); + display_line!(IO, "{:4}Transactions whitelist: {:?}", "", tx_whitelist); let key = param_storage::get_max_block_gas_key(); let max_block_gas = query_storage_value::(client, &key) .await .expect("Parameter should be defined."); - println!("{:4}Max block gas: {:?}", "", max_block_gas); + display_line!(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."); - println!( + display_line!( + IO, "{:4}Fee unshielding gas limit: {:?}", - "", fee_unshielding_gas_limit + "", + fee_unshielding_gas_limit ); let key = param_storage::get_fee_unshielding_descriptions_limit_key(); @@ -1171,9 +1329,11 @@ pub async fn query_protocol_parameters< query_storage_value::(client, &key) .await .expect("Parameter should be defined."); - println!( + display_line!( + IO, "{:4}Fee unshielding descriptions limit: {:?}", - "", fee_unshielding_descriptions_limit + "", + fee_unshielding_descriptions_limit ); let key = param_storage::get_gas_cost_key(); @@ -1183,36 +1343,51 @@ pub async fn query_protocol_parameters< >(client, &key) .await .expect("Parameter should be defined."); - println!("{:4}Gas cost table:", ""); + display_line!(IO, "{:4}Gas cost table:", ""); for (token, gas_cost) in gas_cost_table { - println!("{:8}{}: {:?}", "", token, gas_cost); + display_line!(IO, "{:8}{}: {:?}", "", token, gas_cost); } - println!("PoS parameters"); + display_line!(IO, "PoS parameters"); let pos_params = query_pos_parameters(client).await; - println!( + display_line!( + IO, "{:4}Block proposer reward: {}", - "", pos_params.block_proposer_reward + "", + pos_params.block_proposer_reward ); - println!( + display_line!( + IO, "{:4}Block vote reward: {}", - "", pos_params.block_vote_reward + "", + pos_params.block_vote_reward ); - println!( + display_line!( + IO, "{:4}Duplicate vote minimum slash rate: {}", - "", pos_params.duplicate_vote_min_slash_rate + "", + pos_params.duplicate_vote_min_slash_rate ); - println!( + display_line!( + IO, "{:4}Light client attack minimum slash rate: {}", - "", pos_params.light_client_attack_min_slash_rate + "", + pos_params.light_client_attack_min_slash_rate ); - println!( + display_line!( + IO, "{:4}Max. validator slots: {}", - "", pos_params.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, + "{:4}Votes per token: {}", + "", + pos_params.tm_votes_per_token ); - println!("{:4}Pipeline length: {}", "", pos_params.pipeline_len); - println!("{:4}Unbonding length: {}", "", pos_params.unbonding_len); - println!("{:4}Votes per token: {}", "", pos_params.tm_votes_per_token); } pub async fn query_bond( @@ -1269,6 +1444,7 @@ pub async fn query_pgf_parameters( pub async fn query_and_print_unbonds< C: namada::ledger::queries::Client + Sync, + IO: Io, >( client: &C, source: &Address, @@ -1289,16 +1465,18 @@ pub async fn query_and_print_unbonds< } } if total_withdrawable != token::Amount::default() { - println!( + display_line!( + IO, "Total withdrawable now: {}.", total_withdrawable.to_string_native() ); } if !not_yet_withdrawable.is_empty() { - println!("Current epoch: {current_epoch}.") + display_line!(IO, "Current epoch: {current_epoch}."); } for (withdraw_epoch, amount) in not_yet_withdrawable { - println!( + display_line!( + IO, "Amount {} withdrawable starting from epoch {withdraw_epoch}.", amount.to_string_native(), ); @@ -1322,12 +1500,12 @@ pub async fn query_withdrawable_tokens< } /// Query PoS bond(s) and unbond(s) -pub async fn query_bonds( +pub async fn query_bonds( client: &C, _wallet: &mut Wallet, args: args::QueryBonds, ) -> std::io::Result<()> { - let epoch = query_and_print_epoch(client).await; + let epoch = query_and_print_epoch::<_, IO>(client).await; let source = args.owner; let validator = args.validator; @@ -1349,24 +1527,26 @@ pub async fn query_bonds( bond_id.source, bond_id.validator ) }; - writeln!(w, "{}:", bond_type)?; + display_line!(IO, &mut w; "{}:", bond_type)?; for bond in &details.data.bonds { - writeln!( - w, + display_line!( + IO, + &mut w; " Remaining active bond from epoch {}: Δ {}", bond.start, bond.amount.to_string_native() )?; } if details.bonds_total != token::Amount::zero() { - writeln!( - w, + display_line!( + IO, + &mut w; "Active (slashed) bonds total: {}", details.bonds_total_active().to_string_native() )?; } - writeln!(w, "Bonds total: {}", details.bonds_total.to_string_native())?; - writeln!(w)?; + display_line!(IO, &mut w; "Bonds total: {}", details.bonds_total.to_string_native())?; + display_line!(IO, &mut w; "")?; if !details.data.unbonds.is_empty() { let bond_type = if bond_id.source == bond_id.validator { @@ -1374,38 +1554,43 @@ pub async fn query_bonds( } else { format!("Unbonded delegations from {}", bond_id.source) }; - writeln!(w, "{}:", bond_type)?; + display_line!(IO, &mut w; "{}:", bond_type)?; for unbond in &details.data.unbonds { - writeln!( - w, + display_line!( + IO, + &mut w; " Withdrawable from epoch {} (active from {}): Δ {}", unbond.withdraw, unbond.start, unbond.amount.to_string_native() )?; } - writeln!( - w, + display_line!( + IO, + &mut w; "Unbonded total: {}", details.unbonds_total.to_string_native() )?; } - writeln!( - w, + display_line!( + IO, + &mut w; "Withdrawable total: {}", details.total_withdrawable.to_string_native() )?; - writeln!(w)?; + display_line!(IO, &mut w; "")?; } if bonds_and_unbonds.bonds_total != bonds_and_unbonds.bonds_total_slashed { - writeln!( - w, + display_line!( + IO, + &mut w; "All bonds total active: {}", bonds_and_unbonds.bonds_total_active().to_string_native() )?; } - writeln!( - w, + display_line!( + IO, + &mut w; "All bonds total: {}", bonds_and_unbonds.bonds_total.to_string_native() )?; @@ -1413,19 +1598,22 @@ pub async fn query_bonds( if bonds_and_unbonds.unbonds_total != bonds_and_unbonds.unbonds_total_slashed { - writeln!( - w, + display_line!( + IO, + &mut w; "All unbonds total active: {}", bonds_and_unbonds.unbonds_total_active().to_string_native() )?; } - writeln!( - w, + display_line!( + IO, + &mut w; "All unbonds total: {}", bonds_and_unbonds.unbonds_total.to_string_native() )?; - writeln!( - w, + display_line!( + IO, + &mut w; "All unbonds total withdrawable: {}", bonds_and_unbonds.total_withdrawable.to_string_native() )?; @@ -1433,13 +1621,16 @@ pub async fn query_bonds( } /// Query PoS bonded stake -pub async fn query_bonded_stake( +pub async fn query_bonded_stake< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, args: args::QueryBondedStake, ) { let epoch = match args.epoch { Some(epoch) => epoch, - None => query_and_print_epoch(client).await, + None => query_and_print_epoch::<_, IO>(client).await, }; match args.validator { @@ -1451,13 +1642,14 @@ pub async fn query_bonded_stake( Some(stake) => { // TODO: show if it's in consensus set, below capacity, or // below threshold set - println!( + display_line!( + IO, "Bonded stake of validator {validator}: {}", stake.to_string_native() ) } None => { - println!("No bonded stake found for {validator}") + display_line!(IO, "No bonded stake found for {validator}"); } } } @@ -1481,10 +1673,11 @@ pub async fn query_bonded_stake( let stdout = io::stdout(); let mut w = stdout.lock(); - writeln!(w, "Consensus validators:").unwrap(); + display_line!(IO, &mut w; "Consensus validators:").unwrap(); for val in consensus.into_iter().rev() { - writeln!( - w, + display_line!( + IO, + &mut w; " {}: {}", val.address.encode(), val.bonded_stake.to_string_native() @@ -1492,10 +1685,12 @@ pub async fn query_bonded_stake( .unwrap(); } if !below_capacity.is_empty() { - writeln!(w, "Below capacity validators:").unwrap(); + display_line!(IO, &mut w; "Below capacity validators:") + .unwrap(); for val in below_capacity.into_iter().rev() { - writeln!( - w, + display_line!( + IO, + &mut w; " {}: {}", val.address.encode(), val.bonded_stake.to_string_native() @@ -1507,7 +1702,8 @@ pub async fn query_bonded_stake( } let total_staked_tokens = get_total_staked_tokens(client, epoch).await; - println!( + display_line!( + IO, "Total bonded stake: {}", total_staked_tokens.to_string_native() ); @@ -1549,6 +1745,7 @@ 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, @@ -1561,22 +1758,32 @@ pub async fn query_and_print_validator_state< match state { Some(state) => match state { ValidatorState::Consensus => { - println!("Validator {validator} is in the consensus set") + display_line!( + IO, + "Validator {validator} is in the consensus set" + ) } ValidatorState::BelowCapacity => { - println!("Validator {validator} is in the below-capacity set") + display_line!( + IO, + "Validator {validator} is in the below-capacity set" + ) } ValidatorState::BelowThreshold => { - println!("Validator {validator} is in the below-threshold set") + display_line!( + IO, + "Validator {validator} is in the below-threshold set" + ) } ValidatorState::Inactive => { - println!("Validator {validator} is inactive") + display_line!(IO, "Validator {validator} is inactive") } ValidatorState::Jailed => { - println!("Validator {validator} is jailed") + display_line!(IO, "Validator {validator} is jailed") } }, - None => println!( + None => display_line!( + 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)" @@ -1587,6 +1794,7 @@ 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, @@ -1601,7 +1809,8 @@ pub async fn query_and_print_commission_rate< commission_rate: rate, max_commission_change_per_epoch: change, }) => { - println!( + display_line!( + IO, "Validator {} commission rate: {}, max change per epoch: {}", validator.encode(), rate, @@ -1609,7 +1818,8 @@ pub async fn query_and_print_commission_rate< ); } None => { - println!( + display_line!( + IO, "Address {} is not a validator (did not find commission rate \ and max change)", validator.encode(), @@ -1619,7 +1829,10 @@ pub async fn query_and_print_commission_rate< } /// Query PoS slashes -pub async fn query_slashes( +pub async fn query_slashes< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, _wallet: &mut Wallet, args: args::QuerySlashes, @@ -1632,12 +1845,13 @@ pub async fn query_slashes( RPC.vp().pos().validator_slashes(client, &validator).await, ); if !slashes.is_empty() { - println!("Processed slashes:"); + display_line!(IO, "Processed slashes:"); let stdout = io::stdout(); let mut w = stdout.lock(); for slash in slashes { - writeln!( - w, + display_line!( + IO, + &mut w; "Infraction epoch {}, block height {}, type {}, rate \ {}", slash.epoch, @@ -1648,7 +1862,8 @@ pub async fn query_slashes( .unwrap(); } } else { - println!( + display_line!( + IO, "No processed slashes found for {}", validator.encode() ) @@ -1664,14 +1879,15 @@ pub async fn query_slashes( >(RPC.vp().pos().enqueued_slashes(client).await); let enqueued_slashes = enqueued_slashes.get(&validator).cloned(); if let Some(enqueued) = enqueued_slashes { - println!("\nEnqueued slashes for future processing"); + display_line!(IO, "\nEnqueued slashes for future processing"); for (epoch, slashes) in enqueued { - println!("To be processed in epoch {}", epoch); + display_line!(IO, "To be processed in epoch {}", epoch); for slash in slashes { let stdout = io::stdout(); let mut w = stdout.lock(); - writeln!( - w, + display_line!( + IO, + &mut w; "Infraction epoch {}, block height {}, type {}", slash.epoch, slash.block_height, slash.r#type, ) @@ -1679,7 +1895,11 @@ pub async fn query_slashes( } } } else { - println!("No enqueued slashes found for {}", validator.encode()) + display_line!( + IO, + "No enqueued slashes found for {}", + validator.encode() + ) } } None => { @@ -1691,11 +1911,12 @@ pub async fn query_slashes( if !all_slashes.is_empty() { let stdout = io::stdout(); let mut w = stdout.lock(); - println!("Processed slashes:"); + display_line!(IO, "Processed slashes:"); for (validator, slashes) in all_slashes.into_iter() { for slash in slashes { - writeln!( - w, + display_line!( + IO, + &mut w; "Infraction epoch {}, block height {}, rate {}, \ type {}, validator {}", slash.epoch, @@ -1708,7 +1929,7 @@ pub async fn query_slashes( } } } else { - println!("No processed slashes found") + display_line!(IO, "No processed slashes found") } // Find enqueued slashes to be processed in the future for the given @@ -1721,15 +1942,20 @@ pub async fn query_slashes( HashMap>>, >(RPC.vp().pos().enqueued_slashes(client).await); if !enqueued_slashes.is_empty() { - println!("\nEnqueued slashes for future processing"); + display_line!(IO, "\nEnqueued slashes for future processing"); for (validator, slashes_by_epoch) in enqueued_slashes { for (epoch, slashes) in slashes_by_epoch { - println!("\nTo be processed in epoch {}", epoch); + display_line!( + IO, + "\nTo be processed in epoch {}", + epoch + ); for slash in slashes { let stdout = io::stdout(); let mut w = stdout.lock(); - writeln!( - w, + display_line!( + IO, + &mut w; "Infraction epoch {}, block height {}, type \ {}, validator {}", slash.epoch, @@ -1742,13 +1968,19 @@ pub async fn query_slashes( } } } else { - println!("\nNo enqueued slashes found for future processing") + display_line!( + IO, + "\nNo enqueued slashes found for future processing" + ) } } } } -pub async fn query_delegations( +pub async fn query_delegations< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, _wallet: &mut Wallet, args: args::QueryDelegations, @@ -1758,22 +1990,26 @@ pub async fn query_delegations( RPC.vp().pos().delegation_validators(client, &owner).await, ); if delegations.is_empty() { - println!("No delegations found"); + display_line!(IO, "No delegations found"); } else { - println!("Found delegations to:"); + display_line!(IO, "Found delegations to:"); for delegation in delegations { - println!(" {delegation}"); + display_line!(IO, " {delegation}"); } } } -pub async fn query_find_validator( +pub async fn query_find_validator< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, args: args::QueryFindValidator, ) { let args::QueryFindValidator { query: _, tm_addr } = args; if tm_addr.len() != 40 { - eprintln!( + edisplay_line!( + IO, "Expected 40 characters in Tendermint address, got {}", tm_addr.len() ); @@ -1784,15 +2020,20 @@ pub async fn query_find_validator( RPC.vp().pos().validator_by_tm_addr(client, &tm_addr).await, ); match validator { - Some(address) => println!("Found validator address \"{address}\"."), + Some(address) => { + display_line!(IO, "Found validator address \"{address}\".") + } None => { - println!("No validator with Tendermint address {tm_addr} found.") + display_line!( + IO, + "No validator with Tendermint address {tm_addr} found." + ) } } } /// Dry run a transaction -pub async fn dry_run_tx( +pub async fn dry_run_tx( client: &C, tx_bytes: Vec, ) -> Result<(), error::Error> @@ -1800,9 +2041,10 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - println!( + display_line!( + IO, "Dry-run result: {}", - namada::ledger::rpc::dry_run_tx(client, tx_bytes).await? + namada::ledger::rpc::dry_run_tx::<_, IO>(client, tx_bytes).await? ); Ok(()) } @@ -1859,7 +2101,10 @@ pub async fn known_address( } /// Query for all conversions. -pub async fn query_conversions( +pub async fn query_conversions< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, wallet: &mut Wallet, args: args::QueryConversions, @@ -1892,7 +2137,8 @@ pub async fn query_conversions( } conversions_found = true; // Print the asset to which the conversion applies - print!( + display!( + IO, "{}[{}]: ", tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), epoch, @@ -1904,7 +2150,8 @@ pub async fn query_conversions( // printing let ((addr, _), epoch, _, _) = &conv_state.assets[asset_type]; // Now print out this component of the conversion - print!( + display!( + IO, "{}{} {}[{}]", prefix, val, @@ -1915,10 +2162,13 @@ pub async fn query_conversions( prefix = " + "; } // Allowed conversions are always implicit equations - println!(" = 0"); + display_line!(IO, " = 0"); } if !conversions_found { - println!("No conversions found satisfying specified criteria."); + display_line!( + IO, + "No conversions found satisfying specified criteria." + ); } } @@ -1937,11 +2187,14 @@ pub async fn query_conversion( } /// Query a wasm code hash -pub async fn query_wasm_code_hash( +pub async fn query_wasm_code_hash< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, code_path: impl AsRef, ) -> Result { - namada::ledger::rpc::query_wasm_code_hash(client, code_path).await + namada::ledger::rpc::query_wasm_code_hash::<_, IO>(client, code_path).await } /// Query a storage value and decode it with [`BorshDeserialize`]. @@ -1975,6 +2228,7 @@ pub async fn query_storage_value_bytes< pub async fn query_storage_prefix< C: namada::ledger::queries::Client + Sync, T, + IO: Io, >( client: &C, key: &storage::Key, @@ -1982,7 +2236,7 @@ pub async fn query_storage_prefix< where T: BorshDeserialize, { - namada::ledger::rpc::query_storage_prefix(client, key) + namada::ledger::rpc::query_storage_prefix::<_, IO, _>(client, key) .await .unwrap() } @@ -2022,7 +2276,7 @@ pub async fn query_tx_response( /// Lookup the results of applying the specified transaction to the /// blockchain. -pub async fn query_result( +pub async fn query_result( client: &C, args: args::QueryResult, ) { @@ -2034,7 +2288,8 @@ pub async fn query_result( .await; match tx_response { Ok(result) => { - println!( + display_line!( + IO, "Transaction was applied with result: {}", serde_json::to_string_pretty(&result).unwrap() ) @@ -2047,13 +2302,14 @@ pub async fn query_result( ) .await; match tx_response { - Ok(result) => println!( + Ok(result) => display_line!( + IO, "Transaction was accepted with result: {}", serde_json::to_string_pretty(&result).unwrap() ), Err(err2) => { // Print the errors that caused the lookups to fail - eprintln!("{}\n{}", err1, err2); + edisplay_line!(IO, "{}\n{}", err1, err2); cli::safe_exit(1) } } @@ -2061,16 +2317,16 @@ pub async fn query_result( } } -pub async fn epoch_sleep( +pub async fn epoch_sleep( client: &C, _args: args::Query, ) { - let start_epoch = query_and_print_epoch(client).await; + let start_epoch = query_and_print_epoch::<_, IO>(client).await; loop { tokio::time::sleep(core::time::Duration::from_secs(1)).await; let current_epoch = query_epoch(client).await.unwrap(); if current_epoch > start_epoch { - println!("Reached epoch {}", current_epoch); + display_line!(IO, "Reached epoch {}", current_epoch); break; } } @@ -2172,6 +2428,7 @@ fn unwrap_client_response( pub async fn compute_offline_proposal_votes< C: namada::ledger::queries::Client + Sync, + IO: Io, >( client: &C, proposal: &OfflineSignedProposal, @@ -2219,7 +2476,8 @@ pub async fn compute_offline_proposal_votes< .insert(validator, delegator_stake); } } else { - println!( + display_line!( + 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 c3a46d3fd7..b0397f64d2 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -22,8 +22,10 @@ use namada::tendermint_rpc::HttpClient; use namada::types::address::{Address, ImplicitAddress}; use namada::types::dec::Dec; use namada::types::error; +use namada::types::io::Io; use namada::types::key::{self, *}; use namada::types::transaction::pos::InitValidator; +use namada::{display_line, edisplay_line}; use super::rpc; use crate::cli::{args, safe_exit, Context}; @@ -38,16 +40,24 @@ use crate::wallet::{ /// Wrapper around `signing::aux_signing_data` that stores the optional /// disposable address to the wallet -pub async fn aux_signing_data( +pub async fn aux_signing_data< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, wallet: &mut Wallet, args: &args::Tx, owner: &Option
, default_signer: Option
, ) -> Result { - let signing_data = - signing::aux_signing_data(client, wallet, args, owner, default_signer) - .await?; + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + wallet, + args, + owner, + default_signer, + ) + .await?; if args.disposable_signing_key { if !(args.dry_run || args.dry_run_wrapper) { @@ -58,7 +68,8 @@ pub async fn aux_signing_data( ) })?; } else { - println!( + display_line!( + IO, "Transaction dry run. The disposable address will not be \ saved to wallet." ) @@ -69,7 +80,10 @@ pub async fn aux_signing_data( } // Build a transaction to reveal the signer of the given transaction. -pub async fn submit_reveal_aux( +pub async fn submit_reveal_aux< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, ctx: &mut Context, args: args::Tx, @@ -87,11 +101,16 @@ pub async fn submit_reveal_aux( let public_key = key.ref_to(); if tx::is_reveal_pk_needed::(client, address, args.force).await? { - let signing_data = - aux_signing_data(client, &mut ctx.wallet, &args, &None, None) - .await?; + let signing_data = aux_signing_data::<_, IO>( + client, + &mut ctx.wallet, + &args, + &None, + None, + ) + .await?; - let (mut tx, _epoch) = tx::build_reveal_pk( + let (mut tx, _epoch) = tx::build_reveal_pk::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -102,27 +121,34 @@ pub async fn submit_reveal_aux( ) .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>( + client, + &mut ctx.wallet, + &tx, + ) + .await?; signing::sign_tx(&mut ctx.wallet, &args, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args, tx) + .await?; } } Ok(()) } -pub async fn submit_custom( +pub async fn submit_custom( client: &C, ctx: &mut Context, args: args::TxCustom, ) -> Result<(), error::Error> where + C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { let default_signer = Some(args.owner.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -131,9 +157,10 @@ where ) .await?; - submit_reveal_aux(client, ctx, args.tx.clone(), &args.owner).await?; + submit_reveal_aux::<_, IO>(client, ctx, args.tx.clone(), &args.owner) + .await?; - let (mut tx, _epoch) = tx::build_custom( + let (mut tx, _epoch) = tx::build_custom::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -142,19 +169,21 @@ where ) .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) + .await?; } Ok(()) } -pub async fn submit_update_account( +pub async fn submit_update_account( client: &C, ctx: &mut Context, args: args::TxUpdateAccount, @@ -164,7 +193,7 @@ where C::Error: std::fmt::Display, { let default_signer = Some(args.addr.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -173,7 +202,7 @@ where ) .await?; - let (mut tx, _epoch) = tx::build_update_account( + let (mut tx, _epoch) = tx::build_update_account::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -182,31 +211,39 @@ where ) .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) + .await?; } Ok(()) } -pub async fn submit_init_account( +pub async fn submit_init_account( client: &C, ctx: &mut Context, args: args::TxInitAccount, ) -> Result<(), error::Error> where + C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let signing_data = - aux_signing_data(client, &mut ctx.wallet, &args.tx, &None, None) - .await?; + let signing_data = aux_signing_data::<_, IO>( + client, + &mut ctx.wallet, + &args.tx, + &None, + None, + ) + .await?; - let (mut tx, _epoch) = tx::build_init_account( + let (mut tx, _epoch) = tx::build_init_account::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -215,21 +252,21 @@ where ) .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) + .await?; } Ok(()) } -pub async fn submit_init_validator< - C: namada::ledger::queries::Client + Sync, ->( +pub async fn submit_init_validator( client: &C, mut ctx: Context, args::TxInitValidator { @@ -247,7 +284,10 @@ pub async fn submit_init_validator< unsafe_dont_encrypt, tx_code_path: _, }: args::TxInitValidator, -) -> Result<(), error::Error> { +) -> Result<(), error::Error> +where + C: namada::ledger::queries::Client + Sync, +{ let tx_args = args::Tx { chain_id: tx_args .clone() @@ -281,12 +321,12 @@ pub async fn submit_init_validator< .map(|key| match key { common::SecretKey::Ed25519(_) => key, common::SecretKey::Secp256k1(_) => { - eprintln!("Consensus key can only be ed25519"); + edisplay_line!(IO, "Consensus key can only be ed25519"); safe_exit(1) } }) .unwrap_or_else(|| { - println!("Generating consensus key..."); + display_line!(IO, "Generating consensus key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); ctx.wallet @@ -307,12 +347,12 @@ pub async fn submit_init_validator< .map(|key| match key { common::SecretKey::Secp256k1(_) => key.ref_to(), common::SecretKey::Ed25519(_) => { - eprintln!("Eth cold key can only be secp256k1"); + edisplay_line!(IO, "Eth cold key can only be secp256k1"); safe_exit(1) } }) .unwrap_or_else(|| { - println!("Generating Eth cold key..."); + display_line!(IO, "Generating Eth cold key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); ctx.wallet @@ -334,12 +374,12 @@ pub async fn submit_init_validator< .map(|key| match key { common::SecretKey::Secp256k1(_) => key.ref_to(), common::SecretKey::Ed25519(_) => { - eprintln!("Eth hot key can only be secp256k1"); + edisplay_line!(IO, "Eth hot key can only be secp256k1"); safe_exit(1) } }) .unwrap_or_else(|| { - println!("Generating Eth hot key..."); + display_line!(IO, "Generating Eth hot key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); ctx.wallet @@ -358,7 +398,7 @@ pub async fn submit_init_validator< }); if protocol_key.is_none() { - println!("Generating protocol signing key..."); + display_line!(IO, "Generating protocol signing key..."); } // Generate the validator keys let validator_keys = gen_validator_keys( @@ -375,14 +415,17 @@ pub async fn submit_init_validator< .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::( + client, + validator_vp_code_path.to_str().unwrap(), + ) + .await + .unwrap(); // Validate the commission rate data if commission_rate > Dec::one() || commission_rate < Dec::zero() { - eprintln!( + edisplay_line!( + IO, "The validator commission rate must not exceed 1.0 or 100%, and \ it must be 0 or positive" ); @@ -393,7 +436,8 @@ pub async fn submit_init_validator< if max_commission_rate_change > Dec::one() || max_commission_rate_change < Dec::zero() { - eprintln!( + edisplay_line!( + IO, "The validator maximum change in commission rate per epoch must \ not exceed 1.0 or 100%" ); @@ -402,7 +446,7 @@ pub async fn submit_init_validator< } } let tx_code_hash = - query_wasm_code_hash(client, args::TX_INIT_VALIDATOR_WASM) + query_wasm_code_hash::<_, IO>(client, args::TX_INIT_VALIDATOR_WASM) .await .unwrap(); @@ -428,11 +472,16 @@ pub async fn submit_init_validator< tx.add_code_from_hash(tx_code_hash).add_data(data); - let signing_data = - aux_signing_data(client, &mut ctx.wallet, &tx_args, &None, None) - .await?; + let signing_data = signing::aux_signing_data::<_, _, IO>( + client, + &mut ctx.wallet, + &tx_args, + &None, + None, + ) + .await?; - tx::prepare_tx( + tx::prepare_tx::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -445,16 +494,18 @@ pub async fn submit_init_validator< ) .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if tx_args.dump_tx { - tx::dump_tx(&tx_args, tx); + tx::dump_tx::(&tx_args, tx); } else { signing::sign_tx(&mut ctx.wallet, &tx_args, &mut tx, signing_data)?; - let result = tx::process_tx(client, &mut ctx.wallet, &tx_args, tx) - .await? - .initialized_accounts(); + let result = + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &tx_args, tx) + .await? + .initialized_accounts(); if !tx_args.dry_run { let (validator_address_alias, validator_address) = match &result[..] @@ -466,12 +517,15 @@ pub async fn submit_init_validator< { (alias.clone(), validator_address.clone()) } else { - eprintln!("Expected one account to be created"); + edisplay_line!( + IO, + "Expected one account to be created" + ); safe_exit(1) } } _ => { - eprintln!("Expected one account to be created"); + edisplay_line!(IO, "Expected one account to be created"); safe_exit(1) } }; @@ -479,7 +533,7 @@ pub async fn submit_init_validator< ctx.wallet .add_validator_data(validator_address, validator_keys); crate::wallet::save(&ctx.wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); + .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); let tendermint_home = ctx.config.ledger.cometbft_dir(); tendermint_node::write_validator_key( @@ -505,24 +559,38 @@ pub async fn submit_init_validator< .await .expect("Pos parameter should be defined."); - println!(); - println!( + display_line!(IO, ""); + display_line!( + IO, "The validator's addresses and keys were stored in the wallet:" ); - println!(" Validator address \"{}\"", validator_address_alias); - println!(" Validator account key \"{}\"", validator_key_alias); - println!(" Consensus key \"{}\"", consensus_key_alias); - println!( + display_line!( + IO, + " Validator address \"{}\"", + validator_address_alias + ); + display_line!( + IO, + " Validator account key \"{}\"", + validator_key_alias + ); + display_line!(IO, " Consensus key \"{}\"", consensus_key_alias); + display_line!( + IO, "The ledger node has been setup to use this validator's \ address and consensus key." ); - println!( + display_line!( + 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 { - println!("Transaction dry run. No addresses have been saved."); + display_line!( + IO, + "Transaction dry run. No addresses have been saved." + ); } } Ok(()) @@ -541,7 +609,7 @@ pub struct CLIShieldedUtils { impl CLIShieldedUtils { /// Initialize a shielded transaction context that identifies notes /// decryptable by any viewing key in the given set - pub fn new(context_dir: PathBuf) -> masp::ShieldedContext { + pub fn new(context_dir: PathBuf) -> masp::ShieldedContext { // Make sure that MASP parameters are downloaded to enable MASP // transaction building and verification later on let params_dir = masp::get_params_dir(); @@ -552,10 +620,13 @@ impl CLIShieldedUtils { && convert_path.exists() && output_path.exists()) { - println!("MASP parameters not present, downloading..."); + display_line!(IO, "MASP parameters not present, downloading..."); masp_proofs::download_masp_parameters(None) .expect("MASP parameters not present or downloadable"); - println!("MASP parameter download complete, resuming execution..."); + display_line!( + IO, + "MASP parameter download complete, resuming execution..." + ); } // Finally initialize a shielded context with the supplied directory let utils = Self { context_dir }; @@ -636,14 +707,17 @@ impl masp::ShieldedUtils for CLIShieldedUtils { } } -pub async fn submit_transfer( +pub async fn submit_transfer< + C: namada::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, mut ctx: Context, args: args::TxTransfer, ) -> Result<(), error::Error> { for _ in 0..2 { let default_signer = Some(args.source.effective_address()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -652,7 +726,7 @@ pub async fn submit_transfer( ) .await?; - submit_reveal_aux( + submit_reveal_aux::<_, IO>( client, &mut ctx, args.tx.clone(), @@ -661,7 +735,7 @@ pub async fn submit_transfer( .await?; let arg = args.clone(); - let (mut tx, tx_epoch) = tx::build_transfer( + let (mut tx, tx_epoch) = tx::build_transfer::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -669,17 +743,24 @@ pub async fn submit_transfer( signing_data.fee_payer.clone(), ) .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); break; } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - let result = - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + let result = tx::process_tx::<_, _, IO>( + client, + &mut ctx.wallet, + &args.tx, + tx, + ) + .await?; - let submission_epoch = rpc::query_and_print_epoch(client).await; + let submission_epoch = + rpc::query_and_print_epoch::<_, IO>(client).await; match result { ProcessTxResponse::Applied(resp) if @@ -691,7 +772,7 @@ pub async fn submit_transfer( tx_epoch.unwrap() != submission_epoch => { // Then we probably straddled an epoch boundary. Let's retry... - eprintln!( + edisplay_line!(IO, "MASP transaction rejected and this may be due to the \ epoch changing. Attempting to resubmit transaction.", ); @@ -707,16 +788,17 @@ pub async fn submit_transfer( Ok(()) } -pub async fn submit_ibc_transfer( +pub async fn submit_ibc_transfer( client: &C, mut ctx: Context, args: args::TxIbcTransfer, ) -> Result<(), error::Error> where + C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { let default_signer = Some(args.source.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -725,9 +807,10 @@ where ) .await?; - submit_reveal_aux(client, &mut ctx, args.tx.clone(), &args.source).await?; + submit_reveal_aux::<_, IO>(client, &mut ctx, args.tx.clone(), &args.source) + .await?; - let (mut tx, _epoch) = tx::build_ibc_transfer( + let (mut tx, _epoch) = tx::build_ibc_transfer::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -735,27 +818,30 @@ where signing_data.fee_payer.clone(), ) .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) + .await?; } Ok(()) } -pub async fn submit_init_proposal( +pub async fn submit_init_proposal( client: &C, mut ctx: Context, args: args::InitProposal, ) -> Result<(), error::Error> where + C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let current_epoch = rpc::query_and_print_epoch(client).await; + let current_epoch = rpc::query_and_print_epoch::<_, IO>(client).await; let governance_parameters = rpc::query_governance_parameters(client).await; let ((mut tx_builder, _fee_unshield_epoch), signing_data) = if args @@ -771,7 +857,7 @@ where .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; let default_signer = Some(proposal.author.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -792,7 +878,7 @@ where ) })?; - println!("Proposal serialized to: {}", output_file_path); + display_line!(IO, "Proposal serialized to: {}", output_file_path); return Ok(()); } else if args.is_pgf_funding { let proposal = @@ -806,7 +892,7 @@ where .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -815,7 +901,7 @@ where ) .await?; - submit_reveal_aux( + submit_reveal_aux::<_, IO>( client, &mut ctx, args.tx.clone(), @@ -824,7 +910,7 @@ where .await?; ( - tx::build_pgf_funding_proposal( + tx::build_pgf_funding_proposal::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -858,7 +944,7 @@ where .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -867,7 +953,7 @@ where ) .await?; - submit_reveal_aux( + submit_reveal_aux::<_, IO>( client, &mut ctx, args.tx.clone(), @@ -876,7 +962,7 @@ where .await?; ( - tx::build_pgf_stewards_proposal( + tx::build_pgf_stewards_proposal::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -908,7 +994,7 @@ where .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -917,7 +1003,7 @@ where ) .await?; - submit_reveal_aux( + submit_reveal_aux::<_, IO>( client, &mut ctx, args.tx.clone(), @@ -926,7 +1012,7 @@ where .await?; ( - tx::build_default_proposal( + tx::build_default_proposal::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -938,10 +1024,15 @@ where signing_data, ) }; - signing::generate_test_vector(client, &mut ctx.wallet, &tx_builder).await?; + signing::generate_test_vector::<_, _, IO>( + client, + &mut ctx.wallet, + &tx_builder, + ) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx_builder); + tx::dump_tx::(&args.tx, tx_builder); } else { signing::sign_tx( &mut ctx.wallet, @@ -949,13 +1040,19 @@ where &mut tx_builder, signing_data, )?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + tx::process_tx::<_, _, IO>( + client, + &mut ctx.wallet, + &args.tx, + tx_builder, + ) + .await?; } Ok(()) } -pub async fn submit_vote_proposal( +pub async fn submit_vote_proposal( client: &C, mut ctx: Context, args: args::VoteProposal, @@ -964,10 +1061,10 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let current_epoch = rpc::query_and_print_epoch(client).await; + let current_epoch = rpc::query_and_print_epoch::<_, IO>(client).await; let default_signer = Some(args.voter.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -1015,10 +1112,10 @@ where .serialize(args.tx.output_folder) .expect("Should be able to serialize the offline proposal"); - println!("Proposal vote serialized to: {}", output_file_path); + display_line!(IO, "Proposal vote serialized to: {}", output_file_path); return Ok(()); } else { - tx::build_vote_proposal( + tx::build_vote_proposal::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -1028,10 +1125,15 @@ where ) .await? }; - signing::generate_test_vector(client, &mut ctx.wallet, &tx_builder).await?; + signing::generate_test_vector::<_, _, IO>( + client, + &mut ctx.wallet, + &tx_builder, + ) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx_builder); + tx::dump_tx::(&args.tx, tx_builder); } else { signing::sign_tx( &mut ctx.wallet, @@ -1039,13 +1141,19 @@ where &mut tx_builder, signing_data, )?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + tx::process_tx::<_, _, IO>( + client, + &mut ctx.wallet, + &args.tx, + tx_builder, + ) + .await?; } Ok(()) } -pub async fn sign_tx( +pub async fn sign_tx( client: &C, ctx: &mut Context, args::SignTx { @@ -1061,12 +1169,12 @@ where let tx = if let Ok(transaction) = Tx::deserialize(tx_data.as_ref()) { transaction } else { - eprintln!("Couldn't decode the transaction."); + edisplay_line!(IO, "Couldn't decode the transaction."); safe_exit(1) }; let default_signer = Some(owner.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &tx_args, @@ -1084,7 +1192,8 @@ where { Some(secret_key) } else { - eprintln!( + edisplay_line!( + IO, "Couldn't find the secret key for {}. Skipping signature \ generation.", public_key @@ -1118,7 +1227,8 @@ where &signature.serialize(), ) .expect("Signature should be deserializable."); - println!( + display_line!( + IO, "Signature for {} serialized at {}", &account_public_keys_map .get_public_key_from_index(signature.index) @@ -1130,20 +1240,27 @@ where Ok(()) } -pub async fn submit_reveal_pk( +pub async fn submit_reveal_pk( client: &C, ctx: &mut Context, args: args::RevealPk, ) -> Result<(), error::Error> where + C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - submit_reveal_aux(client, ctx, args.tx, &(&args.public_key).into()).await?; + submit_reveal_aux::<_, IO>( + client, + ctx, + args.tx, + &(&args.public_key).into(), + ) + .await?; Ok(()) } -pub async fn submit_bond( +pub async fn submit_bond( client: &C, ctx: &mut Context, args: args::Bond, @@ -1154,7 +1271,7 @@ where { let default_address = args.source.clone().unwrap_or(args.validator.clone()); let default_signer = Some(default_address.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -1163,9 +1280,10 @@ where ) .await?; - submit_reveal_aux(client, ctx, args.tx.clone(), &default_address).await?; + submit_reveal_aux::<_, IO>(client, ctx, args.tx.clone(), &default_address) + .await?; - let (mut tx, _fee_unshield_epoch) = tx::build_bond( + let (mut tx, _fee_unshield_epoch) = tx::build_bond::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -1173,30 +1291,33 @@ where signing_data.fee_payer.clone(), ) .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) + .await?; } Ok(()) } -pub async fn submit_unbond( +pub async fn submit_unbond( client: &C, ctx: &mut Context, args: args::Unbond, ) -> Result<(), error::Error> where + C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { let default_address = args.source.clone().unwrap_or(args.validator.clone()); let default_signer = Some(default_address.clone()); - let signing_data = aux_signing_data( + let signing_data = signing::aux_signing_data::<_, _, IO>( client, &mut ctx.wallet, &args.tx, @@ -1206,7 +1327,7 @@ where .await?; let (mut tx, _fee_unshield_epoch, latest_withdrawal_pre) = - tx::build_unbond( + tx::build_unbond::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -1214,32 +1335,36 @@ where signing_data.fee_payer.clone(), ) .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) + .await?; - tx::query_unbonds(client, args.clone(), latest_withdrawal_pre).await?; + tx::query_unbonds::<_, IO>(client, args.clone(), latest_withdrawal_pre) + .await?; } Ok(()) } -pub async fn submit_withdraw( +pub async fn submit_withdraw( client: &C, mut ctx: Context, args: args::Withdraw, ) -> Result<(), error::Error> where + C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { let default_address = args.source.clone().unwrap_or(args.validator.clone()); let default_signer = Some(default_address.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -1248,7 +1373,7 @@ where ) .await?; - let (mut tx, _fee_unshield_epoch) = tx::build_withdraw( + let (mut tx, _fee_unshield_epoch) = tx::build_withdraw::<_, _, _, IO>( client, &mut ctx.wallet, &mut ctx.shielded, @@ -1256,31 +1381,31 @@ where signing_data.fee_payer.clone(), ) .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) + .await?; } Ok(()) } -pub async fn submit_validator_commission_change< - C: namada::ledger::queries::Client + Sync, ->( +pub async fn submit_validator_commission_change( client: &C, mut ctx: Context, args: args::CommissionRateChange, ) -> Result<(), error::Error> where - C::Error: std::fmt::Display, + C: namada::ledger::queries::Client + Sync, { let default_signer = Some(args.validator.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -1289,22 +1414,25 @@ where ) .await?; - let (mut tx, _fee_unshield_epoch) = tx::build_validator_commission_change( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + let (mut tx, _fee_unshield_epoch) = + tx::build_validator_commission_change::<_, _, _, IO>( + client, + &mut ctx.wallet, + &mut ctx.shielded, + args.clone(), + signing_data.fee_payer.clone(), + ) + .await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) + .await?; } Ok(()) @@ -1312,6 +1440,7 @@ where pub async fn submit_unjail_validator< C: namada::ledger::queries::Client + Sync, + IO: Io, >( client: &C, mut ctx: Context, @@ -1321,7 +1450,7 @@ where C::Error: std::fmt::Display, { let default_signer = Some(args.validator.clone()); - let signing_data = aux_signing_data( + let signing_data = aux_signing_data::<_, IO>( client, &mut ctx.wallet, &args.tx, @@ -1330,22 +1459,25 @@ where ) .await?; - let (mut tx, _fee_unshield_epoch) = tx::build_unjail_validator( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + let (mut tx, _fee_unshield_epoch) = + tx::build_unjail_validator::<_, _, _, IO>( + client, + &mut ctx.wallet, + &mut ctx.shielded, + args.clone(), + signing_data.fee_payer.clone(), + ) + .await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) + .await?; } Ok(()) @@ -1353,6 +1485,7 @@ where pub async fn submit_update_steward_commission< C: namada::ledger::queries::Client + Sync, + IO: Io, >( client: &C, mut ctx: Context, @@ -1363,7 +1496,7 @@ where C::Error: std::fmt::Display, { let default_signer = Some(args.steward.clone()); - let signing_data = signing::aux_signing_data( + let signing_data = signing::aux_signing_data::<_, _, IO>( client, &mut ctx.wallet, &args.tx, @@ -1372,28 +1505,31 @@ where ) .await?; - let (mut tx, _fee_unshield_epoch) = tx::build_update_steward_commission( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - &signing_data.fee_payer, - ) - .await?; + let (mut tx, _fee_unshield_epoch) = + tx::build_update_steward_commission::<_, _, _, IO>( + client, + &mut ctx.wallet, + &mut ctx.shielded, + args.clone(), + &signing_data.fee_payer, + ) + .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) + .await?; } Ok(()) } -pub async fn submit_resign_steward( +pub async fn submit_resign_steward( client: &C, mut ctx: Context, args: args::ResignSteward, @@ -1403,7 +1539,7 @@ where C::Error: std::fmt::Display, { let default_signer = Some(args.steward.clone()); - let signing_data = signing::aux_signing_data( + let signing_data = signing::aux_signing_data::<_, _, IO>( client, &mut ctx.wallet, &args.tx, @@ -1412,45 +1548,49 @@ where ) .await?; - let (mut tx, _fee_unshield_epoch) = tx::build_resign_steward( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - &signing_data.fee_payer, - ) - .await?; + let (mut tx, _fee_unshield_epoch) = + tx::build_resign_steward::<_, _, _, IO>( + client, + &mut ctx.wallet, + &mut ctx.shielded, + args.clone(), + &signing_data.fee_payer, + ) + .await?; - signing::generate_test_vector(client, &mut ctx.wallet, &tx).await?; + signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) + .await?; if args.tx.dump_tx { - tx::dump_tx(&args.tx, tx); + tx::dump_tx::(&args.tx, tx); } else { signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) + .await?; } Ok(()) } /// Save accounts initialized from a tx into the wallet, if any. -pub async fn save_initialized_accounts( +pub async fn save_initialized_accounts( wallet: &mut Wallet, args: &args::Tx, initialized_accounts: Vec
, ) { - tx::save_initialized_accounts::(wallet, args, initialized_accounts).await + tx::save_initialized_accounts::(wallet, 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( +pub async fn broadcast_tx( rpc_cli: &HttpClient, to_broadcast: &TxBroadcastData, ) -> Result { - tx::broadcast_tx(rpc_cli, to_broadcast).await + tx::broadcast_tx::<_, IO>(rpc_cli, to_broadcast).await } /// Broadcast a transaction to be included in the blockchain. @@ -1461,9 +1601,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( +pub async fn submit_tx( client: &HttpClient, to_broadcast: TxBroadcastData, ) -> Result { - tx::submit_tx(client, to_broadcast).await + tx::submit_tx::<_, IO>(client, 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 c07735d0b9..68a346a4ed 100644 --- a/apps/src/lib/node/ledger/shell/testing/client.rs +++ b/apps/src/lib/node/ledger/shell/testing/client.rs @@ -3,6 +3,7 @@ use std::ops::ControlFlow; use clap::Command as App; use eyre::Report; use namada::types::control_flow::Halt; +use namada::types::io::{DefaultIo, Io}; use tendermint_config::net::Address as TendermintAddress; use super::node::MockNode; @@ -24,7 +25,7 @@ pub fn run( wasm_dir: Some(locked.wasm_dir.clone()), } }; - let ctx = Context::new(global.clone())?; + let ctx = Context::new::(global.clone())?; let rt = tokio::runtime::Runtime::new().unwrap(); match who { @@ -46,7 +47,10 @@ pub fn run( NamadaClient::WithoutContext(sub_cmd, global) } }; - rt.block_on(CliApi::<()>::handle_client_command(Some(node), cmd)) + rt.block_on(CliApi::::handle_client_command( + Some(node), + cmd, + )) } Bin::Wallet => { args.insert(0, "wallet"); @@ -56,7 +60,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) } Bin::Relayer => { args.insert(0, "relayer"); @@ -78,7 +82,10 @@ pub fn run( NamadaRelayer::ValidatorSet(sub_cmd) } }; - rt.block_on(CliApi::<()>::handle_relayer_command(Some(node), cmd)) + rt.block_on(CliApi::::handle_relayer_command( + Some(node), + cmd, + )) } } } @@ -89,7 +96,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) -> Halt<()> { ControlFlow::Continue(()) } } diff --git a/benches/lib.rs b/benches/lib.rs index 11042c84b3..d050372b09 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -78,6 +78,7 @@ use namada::tendermint::Hash; use namada::tendermint_rpc::{self}; use namada::types::address::InternalAddress; use namada::types::chain::ChainId; +use namada::types::io::DefaultIo; use namada::types::masp::{ ExtendedViewingKey, PaymentAddress, TransferSource, TransferTarget, }; @@ -662,12 +663,13 @@ 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( @@ -784,7 +786,10 @@ impl BenchShieldedCtx { )) .unwrap(); let shielded = async_runtime - .block_on(self.shielded.gen_shielded_transfer(&self.shell, args)) + .block_on( + self.shielded + .gen_shielded_transfer::<_, DefaultIo>(&self.shell, args), + ) .unwrap() .map( |ShieldedTransfer { diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs index 50d560c2b0..a73f5efd77 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/shared/src/ledger/eth_bridge.rs @@ -17,6 +17,8 @@ use crate::types::control_flow::time::{ Constant, Duration, Error as TimeoutError, Instant, LinearBackoff, Sleep, }; use crate::types::control_flow::{self, Halt, TryHalt}; +use crate::types::io::Io; +use crate::{display_line, edisplay_line}; const DEFAULT_BACKOFF: Duration = std::time::Duration::from_millis(500); const DEFAULT_CEILING: Duration = std::time::Duration::from_secs(30); @@ -98,7 +100,10 @@ pub struct BlockOnEthSync { } /// Block until Ethereum finishes synchronizing. -pub async fn block_on_eth_sync(client: &C, args: BlockOnEthSync) -> Halt<()> +pub async fn block_on_eth_sync( + client: &C, + args: BlockOnEthSync, +) -> Halt<()> where C: Middleware, { @@ -106,7 +111,7 @@ where deadline, delta_sleep, } = args; - tracing::info!("Attempting to synchronize with the Ethereum network"); + display_line!(IO, "Attempting to synchronize with the Ethereum network"); Sleep { strategy: LinearBackoff { delta: delta_sleep }, } @@ -122,15 +127,18 @@ where }) .await .try_halt(|_| { - tracing::error!("Timed out while waiting for Ethereum to synchronize"); + edisplay_line!( + IO, + "Timed out while waiting for Ethereum to synchronize" + ); })?; - tracing::info!("The Ethereum node is up to date"); + display_line!(IO, "The Ethereum node is up to date"); control_flow::proceed(()) } /// Check if Ethereum has finished synchronizing. In case it has /// not, perform `action`. -pub async fn eth_sync_or( +pub async fn eth_sync_or( client: &C, mut action: F, ) -> Halt> @@ -142,7 +150,8 @@ where .await .map(|status| status.is_synchronized()) .try_halt(|err| { - tracing::error!( + edisplay_line!( + IO, "An error occurred while fetching the Ethereum \ synchronization status: {err}" ); @@ -156,11 +165,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) -> Halt<()> where C: Middleware, { - eth_sync_or(client, || { + eth_sync_or::<_, _, _, IO>(client, || { 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 2b2220a5f9..31dd9aa3c6 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -3,7 +3,6 @@ use std::borrow::Cow; use std::cmp::Ordering; use std::collections::HashMap; -use std::io::Write; use std::sync::Arc; use borsh::BorshSerialize; @@ -37,15 +36,18 @@ use crate::types::eth_abi::Encode; use crate::types::eth_bridge_pool::{ GasFee, PendingTransfer, TransferToEthereum, TransferToEthereumKind, }; +use crate::types::io::Io; use crate::types::keccak::KeccakHash; use crate::types::token::{Amount, DenominatedAmount}; use crate::types::voting_power::FractionalVotingPower; +use crate::{display, display_line}; /// Craft a transaction that adds a transfer to the Ethereum bridge pool. pub async fn build_bridge_pool_tx< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -65,7 +67,7 @@ pub async fn build_bridge_pool_tx< wrapper_fee_payer: common::PublicKey, ) -> Result<(Tx, Option), Error> { let fee_payer = fee_payer.unwrap_or_else(|| sender.clone()); - let DenominatedAmount { amount, .. } = validate_amount( + let DenominatedAmount { amount, .. } = validate_amount::<_, IO>( client, amount, &wrapped_erc20s::token(&asset), @@ -75,7 +77,7 @@ pub async fn build_bridge_pool_tx< .map_err(|e| Error::Other(format!("Failed to validate amount. {}", e)))?; let DenominatedAmount { amount: fee_amount, .. - } = validate_amount(client, fee_amount, &fee_token, tx_args.force) + } = validate_amount::<_, IO>(client, fee_amount, &fee_token, tx_args.force) .await .map_err(|e| { Error::Other(format!( @@ -103,7 +105,7 @@ pub async fn build_bridge_pool_tx< }; let tx_code_hash = - query_wasm_code_hash(client, code_path.to_str().unwrap()) + query_wasm_code_hash::<_, IO>(client, code_path.to_str().unwrap()) .await .unwrap(); @@ -113,7 +115,7 @@ pub async fn build_bridge_pool_tx< // TODO(namada#1800): validate the tx on the client side - let epoch = prepare_tx::( + let epoch = prepare_tx::( client, wallet, shielded, @@ -138,7 +140,7 @@ struct BridgePoolResponse { /// Query the contents of the Ethereum bridge pool. /// Prints out a json payload. -pub async fn query_bridge_pool(client: &C) +pub async fn query_bridge_pool(client: &C) where C: Client + Sync, { @@ -153,19 +155,19 @@ where .map(|transfer| (transfer.keccak256().to_string(), transfer)) .collect(); if pool_contents.is_empty() { - println!("Bridge pool is empty."); + display_line!(IO, "Bridge pool is empty."); return; } let contents = BridgePoolResponse { bridge_pool_contents: pool_contents, }; - println!("{}", 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( +pub async fn query_signed_bridge_pool( client: &C, ) -> Halt> where @@ -182,13 +184,13 @@ where .map(|transfer| (transfer.keccak256().to_string(), transfer)) .collect(); if pool_contents.is_empty() { - println!("Bridge pool is empty."); + display_line!(IO, "Bridge pool is empty."); return control_flow::halt(); } let contents = BridgePoolResponse { bridge_pool_contents: pool_contents.clone(), }; - println!("{}", serde_json::to_string_pretty(&contents).unwrap()); + display_line!(IO, "{}", serde_json::to_string_pretty(&contents).unwrap()); control_flow::proceed(pool_contents) } @@ -197,7 +199,7 @@ where /// backing each `TransferToEthereum` event. /// /// Prints a json payload. -pub async fn query_relay_progress(client: &C) +pub async fn query_relay_progress(client: &C) where C: Client + Sync, { @@ -207,12 +209,12 @@ where .transfer_to_ethereum_progress(client) .await .unwrap(); - println!("{}", 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( +async fn construct_bridge_pool_proof( client: &C, args: GenBridgePoolProofReq<'_, '_>, ) -> Halt @@ -242,27 +244,29 @@ where let warning = "Warning".on_yellow(); let warning = warning.bold(); let warning = warning.blink(); - println!( + display_line!( + 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:?}", ); - print!("\nDo you wish to proceed? (y/n): "); - std::io::stdout().flush().unwrap(); + display!(IO, "\nDo you wish to proceed? (y/n): "); + IO::flush(); loop { - let mut buffer = String::new(); - let stdin = std::io::stdin(); - stdin.read_line(&mut buffer).try_halt(|e| { - println!("Encountered error reading from STDIN: {e:?}"); + let resp = IO::read().await.try_halt(|e| { + display_line!( + IO, + "Encountered error reading from STDIN: {e:?}" + ); })?; - match buffer.trim() { + match resp.trim() { "y" => break, "n" => return control_flow::halt(), _ => { - print!("Expected 'y' or 'n'. Please try again: "); - std::io::stdout().flush().unwrap(); + display!(IO, "Expected 'y' or 'n'. Please try again: "); + IO::flush(); } } } @@ -276,7 +280,7 @@ where .await; response.map(|response| response.data).try_halt(|e| { - println!("Encountered error constructing proof:\n{e}"); + display_line!(IO, "Encountered error constructing proof:\n{:?}", e); }) } @@ -292,7 +296,7 @@ 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( +pub async fn construct_proof( client: &C, args: args::BridgePoolProof, ) -> Halt<()> @@ -302,7 +306,7 @@ where let GenBridgePoolProofRsp { abi_encoded_proof: bp_proof_bytes, appendices, - } = construct_bridge_pool_proof( + } = construct_bridge_pool_proof::<_, IO>( client, GenBridgePoolProofReq { transfers: args.transfers.as_slice().into(), @@ -332,12 +336,12 @@ where .unwrap_or_default(), abi_encoded_proof: bp_proof_bytes, }; - println!("{}", 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( eth_client: Arc, nam_client: &C, args: args::RelayBridgePoolProof, @@ -350,7 +354,7 @@ where let _signal_receiver = args.safe_mode.then(install_shutdown_signal); if args.sync { - block_on_eth_sync( + block_on_eth_sync::<_, IO>( &*eth_client, BlockOnEthSync { deadline: Instant::now() + Duration::from_secs(60), @@ -359,13 +363,13 @@ where ) .await?; } else { - eth_sync_or_exit(&*eth_client).await?; + eth_sync_or_exit::<_, IO>(&*eth_client).await?; } let GenBridgePoolProofRsp { abi_encoded_proof: bp_proof, .. - } = construct_bridge_pool_proof( + } = construct_bridge_pool_proof::<_, IO>( nam_client, GenBridgePoolProofReq { transfers: Cow::Owned(args.transfers), @@ -385,7 +389,8 @@ where let error = "Error".on_red(); let error = error.bold(); let error = error.blink(); - println!( + 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 \ @@ -397,7 +402,11 @@ where let bp_proof: RelayProof = AbiDecode::decode(&bp_proof).try_halt(|error| { - println!("Unable to decode the generated proof: {:?}", error); + display_line!( + IO, + "Unable to decode the generated proof: {:?}", + error + ); })?; // NOTE: this operation costs no gas on Ethereum @@ -410,7 +419,8 @@ where let error = "Error".on_red(); let error = error.bold(); let error = error.blink(); - println!( + display_line!( + 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 \ @@ -423,7 +433,8 @@ where let error = "Error".on_red(); let error = error.bold(); let error = error.blink(); - println!( + display_line!( + 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!", @@ -450,7 +461,7 @@ where .await .unwrap(); - println!("{transf_result:?}"); + display_line!(IO, "{transf_result:?}"); control_flow::proceed(()) } @@ -461,11 +472,13 @@ mod recommendations { use namada_core::types::uint::{self, Uint, I256}; use super::*; + use crate::edisplay_line; use crate::eth_bridge::storage::bridge_pool::{ get_nonce_key, get_signed_root_key, }; use crate::eth_bridge::storage::proof::BridgePoolRootProof; use crate::types::ethereum_events::Uint as EthUint; + use crate::types::io::Io; use crate::types::storage::BlockHeight; use crate::types::vote_extensions::validator_set_update::{ EthAddrBook, VotingPowersMap, VotingPowersMapExt, @@ -547,7 +560,7 @@ 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( + pub async fn recommend_batch( client: &C, args: args::RecommendBatch, ) -> Halt<()> @@ -580,12 +593,15 @@ mod recommendations { ) .await .try_halt(|err| { - eprintln!("Failed to query Bridge pool proof: {err}"); + edisplay_line!( + IO, + "Failed to query Bridge pool proof: {err}" + ); })? .data, ) .try_halt(|err| { - eprintln!("Failed to decode Bridge pool proof: {err}"); + edisplay_line!(IO, "Failed to decode Bridge pool proof: {err}"); })?; // get the latest bridge pool nonce @@ -594,16 +610,20 @@ mod recommendations { .storage_value(client, None, None, false, &get_nonce_key()) .await .try_halt(|err| { - eprintln!("Failed to query Bridge pool nonce: {err}"); + edisplay_line!( + IO, + "Failed to query Bridge pool nonce: {err}" + ); })? .data, ) .try_halt(|err| { - eprintln!("Failed to decode Bridge pool nonce: {err}"); + edisplay_line!(IO, "Failed to decode Bridge pool nonce: {err}"); })?; if latest_bp_nonce != bp_root.data.1 { - eprintln!( + edisplay_line!( + IO, "The signed Bridge pool nonce is not up to date, repeat this \ query at a later time" ); @@ -627,17 +647,17 @@ mod recommendations { + valset_fee() * valset_size; // we don't recommend transfers that have already been relayed - let eligible = generate_eligible( + let eligible = generate_eligible::( &args.conversion_table, &in_progress, - query_signed_bridge_pool(client).await?, + query_signed_bridge_pool::<_, IO>(client).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::( eligible, &args.conversion_table, validator_gas, @@ -651,17 +671,22 @@ mod recommendations { net_profit, bridge_pool_gas_fees, }| { - println!("Recommended batch: {transfer_hashes:#?}"); - println!( + display_line!(IO, "Recommended batch: {transfer_hashes:#?}"); + display_line!( + IO, "Estimated Ethereum transaction gas (in gwei): \ {ethereum_gas_fees}", ); - println!("Estimated net profit (in gwei): {net_profit}"); - println!("Total fees: {bridge_pool_gas_fees:#?}"); + display_line!( + IO, + "Estimated net profit (in gwei): {net_profit}" + ); + display_line!(IO, "Total fees: {bridge_pool_gas_fees:#?}"); }, ) .unwrap_or_else(|| { - println!( + display_line!( + IO, "Unable to find a recommendation satisfying the input \ parameters." ); @@ -705,7 +730,7 @@ mod recommendations { } /// Generate eligible recommendations. - fn generate_eligible( + fn generate_eligible( conversion_table: &HashMap, in_progress: &BTreeSet, signed_pool: HashMap, @@ -721,21 +746,24 @@ mod recommendations { .get(&pending.gas_fee.token) .and_then(|entry| match entry.conversion_rate { r if r == 0.0f64 => { - eprintln!( + edisplay_line!( + IO, "{}: Ignoring null conversion rate", pending.gas_fee.token, ); None } r if r < 0.0f64 => { - eprintln!( + edisplay_line!( + IO, "{}: Ignoring negative conversion rate: {r:.1}", pending.gas_fee.token, ); None } r if r > 1e9 => { - eprintln!( + edisplay_line!( + IO, "{}: Ignoring high conversion rate: {r:.1} > \ 10^9", pending.gas_fee.token, @@ -785,7 +813,7 @@ mod recommendations { /// Generates the actual recommendation from restrictions given by the /// input parameters. - fn generate_recommendations( + fn generate_recommendations( contents: Vec, conversion_table: &HashMap, validator_gas: Uint, @@ -853,6 +881,11 @@ mod recommendations { bridge_pool_gas_fees: total_fees, }) } else { + display_line!( + IO, + "Unable to find a recommendation satisfying the input \ + parameters." + ); None }, ) @@ -882,6 +915,7 @@ mod recommendations { use super::*; use crate::types::control_flow::ProceedOrElse; + use crate::types::io::DefaultIo; /// An established user address for testing & development pub fn bertha_address() -> Address { @@ -987,8 +1021,12 @@ 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::( + &table, + &in_progress, + signed_pool, + ) + .proceed(); assert_eq!(eligible, expected); eligible } @@ -1078,7 +1116,7 @@ mod recommendations { let profitable = vec![transfer(100_000); 17]; let hash = profitable[0].keccak256().to_string(); let expected = vec![hash; 17]; - let recommendation = generate_recommendations( + let recommendation = generate_recommendations::( process_transfers(profitable), &Default::default(), Uint::from_u64(800_000), @@ -1097,7 +1135,7 @@ mod recommendations { let hash = transfers[0].keccak256().to_string(); transfers.push(transfer(0)); let expected: Vec<_> = vec![hash; 17]; - let recommendation = generate_recommendations( + let recommendation = generate_recommendations::( process_transfers(transfers), &Default::default(), Uint::from_u64(800_000), @@ -1115,7 +1153,7 @@ mod recommendations { let transfers = vec![transfer(75_000); 4]; let hash = transfers[0].keccak256().to_string(); let expected = vec![hash; 2]; - let recommendation = generate_recommendations( + let recommendation = generate_recommendations::( process_transfers(transfers), &Default::default(), Uint::from_u64(50_000), @@ -1137,7 +1175,7 @@ mod recommendations { .map(|t| t.keccak256().to_string()) .take(5) .collect(); - let recommendation = generate_recommendations( + let recommendation = generate_recommendations::( process_transfers(transfers), &Default::default(), Uint::from_u64(150_000), @@ -1156,7 +1194,7 @@ mod recommendations { let hash = transfers[0].keccak256().to_string(); let expected = vec![hash; 4]; transfers.extend([transfer(17_500), transfer(17_500)]); - let recommendation = generate_recommendations( + let recommendation = generate_recommendations::( process_transfers(transfers), &Default::default(), Uint::from_u64(150_000), @@ -1172,7 +1210,7 @@ mod recommendations { #[test] fn test_wholly_infeasible() { let transfers = vec![transfer(75_000); 4]; - let recommendation = generate_recommendations( + let recommendation = generate_recommendations::( process_transfers(transfers), &Default::default(), Uint::from_u64(300_000), @@ -1253,7 +1291,7 @@ mod recommendations { const VALIDATOR_GAS_FEE: Uint = Uint::from_u64(100_000); - let recommended_batch = generate_recommendations( + let recommended_batch = generate_recommendations::( eligible, &conversion_table, // gas spent by validator signature checks diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 7ff30ef07c..eabac48786 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -24,6 +24,8 @@ use crate::types::control_flow::time::{self, Duration, Instant}; use crate::types::control_flow::{ self, install_shutdown_signal, Halt, TryHalt, }; +use crate::types::io::{DefaultIo, Io}; +use crate::{display_line, edisplay_line}; /// Relayer related errors. #[derive(Debug, Default)] @@ -266,7 +268,7 @@ 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( +pub async fn query_validator_set_update_proof( client: &C, args: args::ValidatorSetProof, ) where @@ -285,11 +287,11 @@ pub async fn query_validator_set_update_proof( .await .unwrap(); - println!("0x{}", HEXLOWER.encode(encoded_proof.as_ref())); + display_line!(IO, "0x{}", HEXLOWER.encode(encoded_proof.as_ref())); } /// Query an ABI encoding of the validator set at a given epoch. -pub async fn query_validator_set_args( +pub async fn query_validator_set_args( client: &C, args: args::ConsensusValidatorSet, ) where @@ -308,11 +310,15 @@ pub async fn query_validator_set_args( .await .unwrap(); - println!("0x{}", HEXLOWER.encode(encoded_validator_set_args.as_ref())); + display_line!( + IO, + "0x{}", + HEXLOWER.encode(encoded_validator_set_args.as_ref()) + ); } /// 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( eth_client: Arc, nam_client: &C, args: args::ValidatorSetUpdateRelay, @@ -325,7 +331,7 @@ where let mut signal_receiver = args.safe_mode.then(install_shutdown_signal); if args.sync { - block_on_eth_sync( + block_on_eth_sync::<_, IO>( &*eth_client, BlockOnEthSync { deadline: Instant::now() + Duration::from_secs(60), @@ -334,7 +340,7 @@ where ) .await?; } else { - eth_sync_or_exit(&*eth_client).await?; + eth_sync_or_exit::<_, IO>(&*eth_client).await?; } if args.daemon { @@ -352,7 +358,10 @@ where nam_client, |relay_result| match relay_result { RelayResult::GovernanceCallError(reason) => { - tracing::error!(reason, "Calling Governance failed"); + edisplay_line!( + IO, + "Calling Governance failed due to: {reason}" + ); } RelayResult::NonceError { argument, contract } => { let whence = match argument.cmp(&contract) { @@ -360,22 +369,31 @@ where Ordering::Equal => "identical to", Ordering::Greater => "too far ahead of", }; - tracing::error!( - ?argument, - ?contract, - "Argument nonce is {whence} contract nonce" + edisplay_line!( + IO, + "Argument nonce <{argument}> is {whence} contract \ + nonce <{contract}>" ); } RelayResult::NoReceipt => { - tracing::warn!( + edisplay_line!( + IO, "No transfer receipt received from the Ethereum node" ); } RelayResult::Receipt { receipt } => { if receipt.is_successful() { - tracing::info!(?receipt, "Ethereum transfer succeeded"); + display_line!( + IO, + "Ethereum transfer succeeded: {:?}", + receipt + ); } else { - tracing::error!(?receipt, "Ethereum transfer failed"); + display_line!( + IO, + "Ethereum transfer failed: {:?}", + receipt + ); } } }, @@ -433,7 +451,9 @@ where time::sleep(sleep_for).await; let is_synchronizing = - eth_sync_or(&*eth_client, || ()).await.is_break(); + eth_sync_or::<_, _, _, DefaultIo>(&*eth_client, || ()) + .await + .is_break(); if is_synchronizing { tracing::debug!("The Ethereum node is synchronizing"); last_call_succeeded = false; diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 0ddb7cb6a0..873e1bfa93 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -71,6 +71,7 @@ use crate::types::address::{masp, Address}; use crate::types::error::{ EncodingError, Error, PinnedBalanceError, QueryError, }; +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; @@ -78,6 +79,7 @@ use crate::types::token::{ Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; use crate::types::transaction::{EllipticCurve, PairingEngine, WrapperTx}; +use crate::{display_line, edisplay_line}; /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. @@ -1025,7 +1027,7 @@ 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( &mut self, client: &C, vk: &ViewingKey, @@ -1035,7 +1037,7 @@ impl ShieldedContext { if let Some(balance) = self.compute_shielded_balance(client, vk).await? { let exchanged_amount = self - .compute_exchanged_amount( + .compute_exchanged_amount::<_, IO>( client, balance, target_epoch, @@ -1058,7 +1060,7 @@ impl ShieldedContext { /// the trace amount that could not be converted is moved from input to /// output. #[allow(clippy::too_many_arguments)] - async fn apply_conversion( + async fn apply_conversion( &mut self, client: &C, conv: AllowedConversion, @@ -1079,7 +1081,8 @@ impl ShieldedContext { make_asset_type(Some(asset_type.0), &asset_type.1, asset_type.2)?; let threshold = -conv[&masp_asset]; if threshold == 0 { - eprintln!( + edisplay_line!( + IO, "Asset threshold of selected conversion for asset type {} is \ 0, this is a bug, please report it.", masp_asset @@ -1109,7 +1112,7 @@ impl ShieldedContext { /// note of the conversions that were used. Note that this function does /// not assume that allowed conversions from the ledger are expressed in /// terms of the latest asset types. - pub async fn compute_exchanged_amount( + pub async fn compute_exchanged_amount( &mut self, client: &C, mut input: MaspAmount, @@ -1147,14 +1150,15 @@ impl ShieldedContext { if let (Some((conv, _wit, usage)), false) = (conversions.get_mut(&asset_type), at_target_asset_type) { - println!( + display_line!( + IO, "converting current asset type to latest asset type..." ); // Not at the target asset type, not at the latest asset // type. Apply conversion to get from // current asset type to the latest // asset type. - self.apply_conversion( + self.apply_conversion::<_, IO>( client, conv.clone(), (asset_epoch, token_addr.clone(), denom), @@ -1168,14 +1172,15 @@ impl ShieldedContext { conversions.get_mut(&target_asset_type), at_target_asset_type, ) { - println!( + display_line!( + IO, "converting latest asset type to target asset type..." ); // Not at the target asset type, yet at the latest asset // type. Apply inverse conversion to get // from latest asset type to the target // asset type. - self.apply_conversion( + self.apply_conversion::<_, IO>( client, conv.clone(), (asset_epoch, token_addr.clone(), denom), @@ -1210,7 +1215,7 @@ impl ShieldedContext { /// of the specified asset type. Return the total value accumulated plus /// notes and the corresponding diversifiers/merkle paths that were used to /// achieve the total value. - pub async fn collect_unspent_notes( + pub async fn collect_unspent_notes( &mut self, client: &C, vk: &ViewingKey, @@ -1256,7 +1261,7 @@ impl ShieldedContext { })?; let input = self.decode_all_amounts(client, pre_contr).await; let (contr, proposed_convs) = self - .compute_exchanged_amount( + .compute_exchanged_amount::<_, IO>( client, input, target_epoch, @@ -1286,7 +1291,7 @@ impl ShieldedContext { .path() .ok_or_else(|| { Error::Other(format!( - "Un able to get path: {}", + "Unable to get path: {}", line!() )) })?; @@ -1395,7 +1400,7 @@ 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( &mut self, client: &C, owner: PaymentAddress, @@ -1404,16 +1409,21 @@ impl ShieldedContext { // Obtain the balance that will be exchanged let (amt, ep) = Self::compute_pinned_balance(client, owner, viewing_key).await?; - println!("Pinned balance: {:?}", amt); + display_line!(IO, "Pinned balance: {:?}", amt); // Establish connection with which to do exchange rate queries let amount = self.decode_all_amounts(client, amt).await; - println!("Decoded pinned balance: {:?}", amount); + display_line!(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::<_, IO>( + client, + amount, + ep, + BTreeMap::new(), + ) .await? .0; - println!("Exchanged amount: {:?}", computed_amount); + display_line!(IO, "Exchanged amount: {:?}", computed_amount); Ok((self.decode_all_amounts(client, computed_amount).await, ep)) } @@ -1475,7 +1485,7 @@ impl ShieldedContext { /// understood that transparent account changes are effected only by the /// amounts and signatures specified by the containing Transfer object. #[cfg(feature = "masp-tx-gen")] - pub async fn gen_shielded_transfer( + pub async fn gen_shielded_transfer( &mut self, client: &C, args: args::TxTransfer, @@ -1545,7 +1555,7 @@ impl ShieldedContext { if let Some(sk) = spending_key { // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = self - .collect_unspent_notes( + .collect_unspent_notes::<_, IO>( client, &to_viewing_key(&sk).vk, I128Sum::from_sum(amount), diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index ddef3ecb20..99771e6d5b 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -39,15 +39,17 @@ use crate::tendermint_rpc::Order; use crate::types::control_flow::{time, Halt, TryHalt}; use crate::types::error::{EncodingError, Error, QueryError}; use crate::types::hash::Hash; +use crate::types::io::Io; use crate::types::key::common; use crate::types::storage::{BlockHeight, BlockResults, Epoch, PrefixValue}; use crate::types::{error, storage, token}; +use crate::{display_line, edisplay_line}; /// 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( +pub async fn query_tx_status( client: &C, status: TxEventQuery<'_>, deadline: time::Instant, @@ -88,7 +90,10 @@ where }) .await .try_halt(|_| { - eprintln!("Transaction status query deadline of {deadline:?} exceeded"); + edisplay_line!( + IO, + "Transaction status query deadline of {deadline:?} exceeded" + ); }) } @@ -266,7 +271,10 @@ pub async fn query_conversion( } /// Query a wasm code hash -pub async fn query_wasm_code_hash( +pub async fn query_wasm_code_hash< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, code_path: impl AsRef, ) -> Result { @@ -277,7 +285,8 @@ pub async fn query_wasm_code_hash( { Some(hash) => Ok(Hash::try_from(&hash[..]).expect("Invalid code hash")), None => { - eprintln!( + edisplay_line!( + IO, "The corresponding wasm code of the code path {} doesn't \ exist on chain.", code_path.as_ref(), @@ -351,7 +360,11 @@ 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( +pub async fn query_storage_prefix< + C: crate::ledger::queries::Client + Sync, + IO: Io, + T, +>( client: &C, key: &storage::Key, ) -> Result>, error::Error> @@ -368,9 +381,11 @@ where &value[..], ) { Err(err) => { - eprintln!( + edisplay_line!( + IO, "Skipping a value for key {}. Error in decoding: {}", - key, err + key, + err ); None } @@ -456,7 +471,7 @@ pub async fn query_tx_events( } /// Dry run a transaction -pub async fn dry_run_tx( +pub async fn dry_run_tx( client: &C, tx_bytes: Vec, ) -> Result { @@ -465,7 +480,7 @@ pub async fn dry_run_tx( RPC.shell().dry_run_tx(client, data, height, prove).await, )? .data; - println! {"Dry-run result: {}", result}; + display_line!(IO, "Dry-run result: {}", result); Ok(result) } @@ -737,8 +752,6 @@ pub async fn query_proposal_by_id( client: &C, proposal_id: u64, ) -> Result, Error> { - // let a = RPC.vp().gov().proposal_id(client, &proposal_id).await; - // println!("{:?}", a.err().unwrap()); convert_response::( RPC.vp().gov().proposal_id(client, &proposal_id).await, ) @@ -810,6 +823,7 @@ pub async fn get_public_key_at( /// Query a validator's unbonds for a given epoch pub async fn query_and_print_unbonds< C: crate::ledger::queries::Client + Sync, + IO: Io, >( client: &C, source: &Address, @@ -830,16 +844,18 @@ pub async fn query_and_print_unbonds< } } if total_withdrawable != token::Amount::default() { - println!( + display_line!( + IO, "Total withdrawable now: {}.", total_withdrawable.to_string_native() ); } if !not_yet_withdrawable.is_empty() { - println!("Current epoch: {current_epoch}.") + display_line!(IO, "Current epoch: {current_epoch}.") } for (withdraw_epoch, amount) in not_yet_withdrawable { - println!( + display_line!( + IO, "Amount {} withdrawable starting from epoch {withdraw_epoch}.", amount.to_string_native() ); @@ -929,6 +945,7 @@ pub async fn bonds_and_unbonds( .await, ) } + /// Get bonds and unbonds with all details (slashes and rewards, if any) /// grouped by their bond IDs, enriched with extra information calculated from /// the data. @@ -954,7 +971,10 @@ pub async fn enriched_bonds_and_unbonds< } /// Get the correct representation of the amount given the token type. -pub async fn validate_amount( +pub async fn validate_amount< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, amount: InputAmount, token: &Address, @@ -971,13 +991,15 @@ pub async fn validate_amount( Some(denom) => Ok(denom), None => { if force { - println!( + display_line!( + IO, "No denomination found for token: {token}, but --force \ was passed. Defaulting to the provided denomination." ); Ok(input_amount.denom) } else { - println!( + display_line!( + IO, "No denomination found for token: {token}, the input \ arguments could not be parsed." ); @@ -988,7 +1010,8 @@ pub async fn validate_amount( } }?; if denom < input_amount.denom && !force { - println!( + display_line!( + IO, "The input amount contained a higher precision than allowed by \ {token}." ); @@ -998,7 +1021,8 @@ pub async fn validate_amount( )))) } else { input_amount.increase_precision(denom).map_err(|_err| { - println!( + display_line!( + IO, "The amount provided requires more the 256 bits to represent." ); Error::from(QueryError::General( @@ -1011,7 +1035,7 @@ 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<()> +pub async fn wait_until_node_is_synched(client: &C) -> Halt<()> where C: crate::ledger::queries::Client + Sync, { @@ -1035,7 +1059,8 @@ where if is_at_least_height_one && !is_catching_up { return ControlFlow::Break(Ok(())); } - println!( + display_line!( + IO, " Waiting for {} ({}/{} tries)...", if is_at_least_height_one { "a first block" @@ -1049,7 +1074,11 @@ where ControlFlow::Continue(()) } Err(e) => { - eprintln!("Failed to query node status with error: {}", e); + edisplay_line!( + IO, + "Failed to query node status with error: {}", + e + ); ControlFlow::Break(Err(())) } } @@ -1057,7 +1086,10 @@ where .await // maybe time out .try_halt(|_| { - println!("Node is still catching up, wait for it to finish synching."); + display_line!( + IO, + "Node is still catching up, wait for it to finish synching." + ); })? // error querying rpc .try_halt(|_| ()) @@ -1067,6 +1099,7 @@ where /// correctly as a string. pub async fn format_denominated_amount< C: crate::ledger::queries::Client + Sync, + IO: Io, >( client: &C, token: &Address, @@ -1076,11 +1109,12 @@ pub async fn format_denominated_amount< RPC.vp().token().denomination(client, token).await, ) .unwrap_or_else(|t| { - println!("Error in querying for denomination: {t}"); + display_line!(IO, "Error in querying for denomination: {t}"); None }) .unwrap_or_else(|| { - println!( + display_line!( + IO, "No denomination found for token: {token}, defaulting to zero \ decimal places" ); diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index 1753c645d3..89a3b24edb 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -1,5 +1,4 @@ //! Functions to sign transactions - use std::collections::{BTreeMap, HashMap}; use std::path::PathBuf; @@ -42,6 +41,7 @@ use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::ledger::{args, rpc}; use crate::proto::{MaspBuilder, Section, Tx}; use crate::types::error::{EncodingError, Error, TxError}; +use crate::types::io::*; use crate::types::key::*; use crate::types::masp::{ExtendedViewingKey, PaymentAddress}; use crate::types::storage::Epoch; @@ -52,6 +52,7 @@ use crate::types::transaction::governance::{ }; use crate::types::transaction::pos::InitValidator; use crate::types::transaction::{Fee, TxType}; +use crate::{display_line, edisplay_line}; #[cfg(feature = "std")] /// Env. var specifying where to store signing test vectors @@ -80,6 +81,7 @@ pub struct SigningTxData { pub async fn find_pk< C: crate::ledger::queries::Client + Sync, U: WalletUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -88,7 +90,8 @@ pub async fn find_pk< ) -> Result { match addr { Address::Established(_) => { - println!( + display_line!( + IO, "Looking-up public key of {} from the ledger...", addr.encode() ); @@ -149,6 +152,7 @@ pub fn find_key_by_pk( pub async fn tx_signers< C: crate::ledger::queries::Client + Sync, U: WalletUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -171,7 +175,7 @@ pub async fn tx_signers< Some(signer) if signer == masp() => Ok(vec![masp_tx_key().ref_to()]), Some(signer) => Ok(vec![ - find_pk::(client, wallet, &signer, args.password.clone()) + find_pk::(client, wallet, &signer, args.password.clone()) .await?, ]), None => other_err( @@ -233,6 +237,7 @@ pub fn sign_tx( pub async fn aux_signing_data< C: crate::ledger::queries::Client + Sync, U: WalletUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -241,7 +246,8 @@ pub async fn aux_signing_data< default_signer: Option
, ) -> Result { let public_keys = if owner.is_some() || args.wrapper_fee_payer.is_none() { - tx_signers::(client, wallet, args, default_signer.clone()).await? + tx_signers::(client, wallet, args, default_signer.clone()) + .await? } else { vec![] }; @@ -294,7 +300,10 @@ pub async fn aux_signing_data< #[cfg(not(feature = "mainnet"))] /// Solve the PoW challenge if balance is insufficient to pay transaction fees /// or if solution is explicitly requested. -pub async fn solve_pow_challenge( +pub async fn solve_pow_challenge< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, args: &args::Tx, requires_pow: bool, @@ -308,8 +317,10 @@ pub async fn solve_pow_challenge( let err_msg = format!( "The wrapper transaction source doesn't have enough balance to \ pay fee {}, got {}.", - format_denominated_amount(client, &token_addr, total_fee).await, - format_denominated_amount(client, &token_addr, balance).await, + format_denominated_amount::<_, IO>(client, &token_addr, total_fee) + .await, + format_denominated_amount::<_, IO>(client, &token_addr, balance) + .await, ); if !args.force && cfg!(feature = "mainnet") { panic!("{}", err_msg); @@ -319,7 +330,10 @@ pub async fn solve_pow_challenge( // If the address derived from the keypair doesn't have enough balance // to pay for the fee, allow to find a PoW solution instead. if (requires_pow || !is_bal_sufficient) && !args.dump_tx { - println!("The transaction requires the completion of a PoW challenge."); + display_line!( + IO, + "The transaction requires the completion of a PoW challenge." + ); // Obtain a PoW challenge for faucet withdrawal let challenge = rpc::get_testnet_pow_challenge(client, source).await; @@ -333,7 +347,10 @@ pub async fn solve_pow_challenge( #[cfg(not(feature = "mainnet"))] /// Update the PoW challenge inside the given transaction -pub async fn update_pow_challenge( +pub async fn update_pow_challenge< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, args: &args::Tx, tx: &mut Tx, @@ -353,7 +370,8 @@ pub async fn update_pow_challenge( }) { Ok(amount) => amount, Err(_e) => { - eprintln!( + edisplay_line!( + IO, "Could not retrieve the gas cost for token {}", args.fee_token ); @@ -366,10 +384,14 @@ pub async fn update_pow_challenge( }; let fee_amount = match args.fee_amount { Some(amount) => { - let validated_fee_amount = - validate_amount(client, amount, &args.fee_token, args.force) - .await - .expect("Expected to be able to validate fee"); + let validated_fee_amount = validate_amount::<_, IO>( + client, + amount, + &args.fee_token, + args.force, + ) + .await + .expect("Expected to be able to validate fee"); let amount = Amount::from_uint(validated_fee_amount.amount, 0).unwrap(); @@ -378,7 +400,8 @@ pub async fn update_pow_challenge( amount } else if !args.force { // Update the fee amount if it's not enough - println!( + display_line!( + IO, "The provided gas price {} is less than the minimum \ amount required {}, changing it to match the minimum", amount.to_string_native(), @@ -400,7 +423,7 @@ pub async fn update_pow_challenge( .unwrap_or_default(); if let TxType::Wrapper(wrapper) = &mut tx.header.tx_type { - let pow_solution = solve_pow_challenge( + let pow_solution = solve_pow_challenge::<_, IO>( client, args, requires_pow, @@ -435,6 +458,7 @@ pub struct TxSourcePostBalance { pub async fn wrap_tx< C: crate::ledger::queries::Client + Sync, V: ShieldedUtils, + IO: Io, >( client: &C, shielded: &mut ShieldedContext, @@ -460,7 +484,8 @@ pub async fn wrap_tx< }) { Ok(amount) => amount, Err(_e) => { - eprintln!( + edisplay_line!( + IO, "Could not retrieve the gas cost for token {}", args.fee_token ); @@ -473,10 +498,14 @@ pub async fn wrap_tx< }; let fee_amount = match args.fee_amount { Some(amount) => { - let validated_fee_amount = - validate_amount(client, amount, &args.fee_token, args.force) - .await - .expect("Expected to be able to validate fee"); + let validated_fee_amount = validate_amount::<_, IO>( + client, + amount, + &args.fee_token, + args.force, + ) + .await + .expect("Expected to be able to validate fee"); let amount = Amount::from_uint(validated_fee_amount.amount, 0).unwrap(); @@ -485,7 +514,8 @@ pub async fn wrap_tx< amount } else if !args.force { // Update the fee amount if it's not enough - println!( + display_line!( + IO, "The provided gas price {} is less than the minimum \ amount required {}, changing it to match the minimum", amount.to_string_native(), @@ -546,7 +576,7 @@ pub async fn wrap_tx< }; match shielded - .gen_shielded_transfer(client, transfer_args) + .gen_shielded_transfer::<_, IO>(client, transfer_args) .await { Ok(Some(ShieldedTransfer { @@ -596,7 +626,7 @@ pub async fn wrap_tx< (Some(transaction), Some(unshielding_epoch)) } Ok(None) => { - eprintln!("Missing unshielding transaction"); + edisplay_line!(IO, "Missing unshielding transaction"); if !args.force && cfg!(feature = "mainnet") { panic!(); } @@ -604,7 +634,11 @@ pub async fn wrap_tx< (None, None) } Err(e) => { - eprintln!("Error in fee unshielding generation: {}", e); + edisplay_line!( + IO, + "Error in fee unshielding generation: {}", + e + ); if !args.force && cfg!(feature = "mainnet") { panic!(); } @@ -617,16 +651,20 @@ pub async fn wrap_tx< let err_msg = format!( "The wrapper transaction source doesn't have enough \ balance to pay fee {}, balance: {}.", - format_denominated_amount(client, &token_addr, total_fee) - .await, - format_denominated_amount( + format_denominated_amount::<_, IO>( + client, + &token_addr, + total_fee + ) + .await, + format_denominated_amount::<_, IO>( client, &token_addr, updated_balance ) .await, ); - eprintln!("{}", err_msg); + edisplay_line!(IO, "{}", err_msg); if !args.force && cfg!(feature = "mainnet") { panic!("{}", err_msg); } @@ -636,7 +674,8 @@ pub async fn wrap_tx< } _ => { if args.fee_unshield.is_some() { - println!( + display_line!( + IO, "Enough transparent balance to pay fees: the fee \ unshielding spending key will be ignored" ); @@ -654,7 +693,7 @@ pub async fn wrap_tx< }); #[cfg(not(feature = "mainnet"))] - let pow_solution = solve_pow_challenge( + let pow_solution = solve_pow_challenge::<_, IO>( client, args, requires_pow, @@ -717,7 +756,10 @@ fn make_ledger_amount_addr( /// Adds a Ledger output line describing a given transaction amount and asset /// type -async fn make_ledger_amount_asset( +async fn make_ledger_amount_asset< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, tokens: &HashMap, output: &mut Vec, @@ -729,7 +771,8 @@ 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::<_, IO>(client, token, amount.into()) + .await; if let Some(token) = tokens.get(token) { output .push( @@ -815,6 +858,7 @@ fn format_outputs(output: &mut Vec) { /// transactions pub async fn make_ledger_masp_endpoints< C: crate::ledger::queries::Client + Sync, + IO: Io, >( client: &C, tokens: &HashMap, @@ -838,7 +882,7 @@ pub async fn make_ledger_masp_endpoints< for sapling_input in builder.builder.sapling_inputs() { let vk = ExtendedViewingKey::from(*sapling_input.key()); output.push(format!("Sender : {}", vk)); - make_ledger_amount_asset( + make_ledger_amount_asset::<_, IO>( client, tokens, output, @@ -865,7 +909,7 @@ pub async fn make_ledger_masp_endpoints< for sapling_output in builder.builder.sapling_outputs() { let pa = PaymentAddress::from(sapling_output.address()); output.push(format!("Destination : {}", pa)); - make_ledger_amount_asset( + make_ledger_amount_asset::<_, IO>( client, tokens, output, @@ -893,6 +937,7 @@ pub async fn make_ledger_masp_endpoints< pub async fn generate_test_vector< C: crate::ledger::queries::Client + Sync, U: WalletUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -907,7 +952,8 @@ pub async fn generate_test_vector< // Contract the large data blobs in the transaction tx.wallet_filter(); // Convert the transaction to Ledger format - let decoding = to_ledger_vector(client, wallet, &tx).await?; + let decoding = + to_ledger_vector::<_, _, IO>(client, wallet, &tx).await?; let output = serde_json::to_string(&decoding) .map_err(|e| Error::from(EncodingError::Serde(e.to_string())))?; // Record the transaction at the identified path @@ -948,30 +994,36 @@ pub async fn generate_test_vector< pub async fn to_ledger_vector< C: crate::ledger::queries::Client + Sync, U: WalletUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, tx: &Tx, ) -> Result { let init_account_hash = - query_wasm_code_hash(client, TX_INIT_ACCOUNT_WASM).await?; + query_wasm_code_hash::<_, IO>(client, TX_INIT_ACCOUNT_WASM).await?; let init_validator_hash = - query_wasm_code_hash(client, TX_INIT_VALIDATOR_WASM).await?; + query_wasm_code_hash::<_, IO>(client, TX_INIT_VALIDATOR_WASM).await?; let init_proposal_hash = - query_wasm_code_hash(client, TX_INIT_PROPOSAL).await?; + query_wasm_code_hash::<_, IO>(client, TX_INIT_PROPOSAL).await?; let vote_proposal_hash = - query_wasm_code_hash(client, TX_VOTE_PROPOSAL).await?; - let reveal_pk_hash = query_wasm_code_hash(client, TX_REVEAL_PK).await?; + query_wasm_code_hash::<_, IO>(client, TX_VOTE_PROPOSAL).await?; + let reveal_pk_hash = + query_wasm_code_hash::<_, IO>(client, TX_REVEAL_PK).await?; let update_account_hash = - query_wasm_code_hash(client, TX_UPDATE_ACCOUNT_WASM).await?; - let transfer_hash = query_wasm_code_hash(client, TX_TRANSFER_WASM).await?; - let ibc_hash = query_wasm_code_hash(client, TX_IBC_WASM).await?; - let bond_hash = query_wasm_code_hash(client, TX_BOND_WASM).await?; - let unbond_hash = query_wasm_code_hash(client, TX_UNBOND_WASM).await?; - let withdraw_hash = query_wasm_code_hash(client, TX_WITHDRAW_WASM).await?; + query_wasm_code_hash::<_, IO>(client, TX_UPDATE_ACCOUNT_WASM).await?; + let transfer_hash = + query_wasm_code_hash::<_, IO>(client, TX_TRANSFER_WASM).await?; + let ibc_hash = query_wasm_code_hash::<_, IO>(client, TX_IBC_WASM).await?; + let bond_hash = query_wasm_code_hash::<_, IO>(client, TX_BOND_WASM).await?; + let unbond_hash = + query_wasm_code_hash::<_, IO>(client, TX_UNBOND_WASM).await?; + let withdraw_hash = + query_wasm_code_hash::<_, IO>(client, TX_WITHDRAW_WASM).await?; let change_commission_hash = - query_wasm_code_hash(client, TX_CHANGE_COMMISSION_WASM).await?; - let user_hash = query_wasm_code_hash(client, VP_USER_WASM).await?; + query_wasm_code_hash::<_, IO>(client, TX_CHANGE_COMMISSION_WASM) + .await?; + let user_hash = query_wasm_code_hash::<_, IO>(client, VP_USER_WASM).await?; // To facilitate lookups of human-readable token names let tokens: HashMap = wallet @@ -1264,7 +1316,7 @@ pub async fn to_ledger_vector< tv.name = "Transfer 0".to_string(); tv.output.push("Type : Transfer".to_string()); - make_ledger_masp_endpoints( + make_ledger_masp_endpoints::<_, IO>( client, &tokens, &mut tv.output, @@ -1273,7 +1325,7 @@ pub async fn to_ledger_vector< &asset_types, ) .await; - make_ledger_masp_endpoints( + make_ledger_masp_endpoints::<_, IO>( client, &tokens, &mut tv.output_expert, @@ -1446,13 +1498,13 @@ pub async fn to_ledger_vector< if let Some(wrapper) = tx.header.wrapper() { let gas_token = wrapper.fee.token.clone(); - let gas_limit = format_denominated_amount( + let gas_limit = format_denominated_amount::<_, IO>( client, &gas_token, Amount::from(wrapper.gas_limit), ) .await; - let fee_amount_per_gas_unit = format_denominated_amount( + let fee_amount_per_gas_unit = format_denominated_amount::<_, IO>( client, &gas_token, wrapper.fee.amount_per_gas_unit, diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 5ebc7f52bb..9a05d8802d 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -57,6 +57,7 @@ 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::error::{EncodingError, Error, QueryError, Result, TxError}; +use crate::types::io::Io; use crate::types::key::*; use crate::types::masp::TransferTarget; use crate::types::storage::Epoch; @@ -64,7 +65,7 @@ use crate::types::time::DateTimeUtc; use crate::types::transaction::account::{InitAccount, UpdateAccount}; use crate::types::transaction::{pos, TxType}; use crate::types::{storage, token}; -use crate::vm; +use crate::{display_line, edisplay_line, vm}; /// Initialize account transaction WASM pub const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; @@ -121,7 +122,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(args: &args::Tx, tx: Tx) { let tx_id = tx.header_hash(); let serialized_tx = tx.serialize(); match args.output_folder.to_owned() { @@ -131,14 +132,15 @@ pub fn dump_tx(args: &args::Tx, tx: Tx) { let out = File::create(&tx_path).unwrap(); serde_json::to_writer_pretty(out, &serialized_tx) .expect("Should be able to write to file."); - println!( + display_line!( + IO, "Transaction serialized to {}.", tx_path.to_string_lossy() ); } None => { - println!("Below the serialized transaction: \n"); - println!("{}", serialized_tx) + display_line!(IO, "Below the serialized transaction: \n"); + display_line!(IO, "{}", serialized_tx) } } } @@ -150,6 +152,7 @@ pub async fn prepare_tx< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, _wallet: &mut Wallet, @@ -163,7 +166,7 @@ pub async fn prepare_tx< if !args.dry_run { let epoch = rpc::query_epoch(client).await?; - Ok(signing::wrap_tx( + Ok(signing::wrap_tx::<_, _, IO>( client, shielded, tx, @@ -185,6 +188,7 @@ pub async fn prepare_tx< pub async fn process_tx< C: crate::ledger::queries::Client + Sync, U: WalletUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -202,7 +206,7 @@ pub async fn process_tx< // println!("HTTP request body: {}", request_body); if args.dry_run || args.dry_run_wrapper { - expect_dry_broadcast(TxBroadcastData::DryRun(tx), client).await + expect_dry_broadcast::<_, IO>(TxBroadcastData::DryRun(tx), client).await } else { // We use this to determine when the wrapper tx makes it on-chain let wrapper_hash = tx.header_hash().to_string(); @@ -222,13 +226,13 @@ pub async fn process_tx< // of masp epoch Either broadcast or submit transaction and // collect result into sum type if args.broadcast_only { - broadcast_tx(client, &to_broadcast) + broadcast_tx::<_, IO>(client, &to_broadcast) .await .map(ProcessTxResponse::Broadcast) } else { - match submit_tx(client, to_broadcast).await { + match submit_tx::<_, IO>(client, to_broadcast).await { Ok(x) => { - save_initialized_accounts::( + save_initialized_accounts::( wallet, args, x.initialized_accounts.clone(), @@ -268,6 +272,7 @@ pub async fn build_reveal_pk< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -277,11 +282,12 @@ pub async fn build_reveal_pk< public_key: &common::PublicKey, fee_payer: &common::PublicKey, ) -> Result<(Tx, Option)> { - println!( + display_line!( + IO, "Submitting a tx to reveal the public key for address {address}..." ); - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -299,7 +305,7 @@ pub async fn build_reveal_pk< /// 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( +pub async fn broadcast_tx( rpc_cli: &C, to_broadcast: &TxBroadcastData, ) -> Result { @@ -324,12 +330,20 @@ pub async fn broadcast_tx( lift_rpc_error(rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await)?; if response.code == 0.into() { - println!("Transaction added to mempool: {:?}", response); + display_line!(IO, "Transaction added to mempool: {:?}", response); // Print the transaction identifiers to enable the extraction of // acceptance/application results later { - println!("Wrapper transaction hash: {:?}", wrapper_tx_hash); - println!("Inner transaction hash: {:?}", decrypted_tx_hash); + display_line!( + IO, + "Wrapper transaction hash: {:?}", + wrapper_tx_hash + ); + display_line!( + IO, + "Inner transaction hash: {:?}", + decrypted_tx_hash + ); } Ok(response) } else { @@ -349,7 +363,7 @@ 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( +pub async fn submit_tx( client: &C, to_broadcast: TxBroadcastData, ) -> Result @@ -366,7 +380,7 @@ where }?; // Broadcast the supplied transaction - broadcast_tx(client, &to_broadcast).await?; + broadcast_tx::<_, IO>(client, &to_broadcast).await?; let deadline = time::Instant::now() + time::Duration::from_secs( @@ -382,17 +396,21 @@ where let parsed = { let wrapper_query = crate::ledger::rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); - let event = rpc::query_tx_status(client, wrapper_query, deadline) - .await - .proceed_or(TxError::AcceptTimeout)?; + let event = + rpc::query_tx_status::<_, IO>(client, 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| { Error::from(EncodingError::Serde(err.to_string())) }) }; - println!("Transaction accepted with result: {}", tx_to_str(&parsed)?); + display_line!( + IO, + "Transaction accepted with result: {}", + tx_to_str(&parsed)? + ); // The transaction is now on chain. We wait for it to be decrypted // and applied if parsed.code == 0.to_string() { @@ -400,11 +418,16 @@ where // payload makes its way onto the blockchain let decrypted_query = rpc::TxEventQuery::Applied(decrypted_hash.as_str()); - let event = rpc::query_tx_status(client, decrypted_query, deadline) - .await - .proceed_or(TxError::AppliedTimeout)?; + let event = rpc::query_tx_status::<_, IO>( + client, + decrypted_query, + deadline, + ) + .await + .proceed_or(TxError::AppliedTimeout)?; let parsed = TxResponse::from_event(event); - println!( + display_line!( + IO, "Transaction applied with result: {}", tx_to_str(&parsed)? ); @@ -441,7 +464,7 @@ pub fn decode_component( } /// Save accounts initialized from a tx into the wallet, if any. -pub async fn save_initialized_accounts( +pub async fn save_initialized_accounts( wallet: &mut Wallet, args: &args::Tx, initialized_accounts: Vec
, @@ -449,7 +472,8 @@ pub async fn save_initialized_accounts( let len = initialized_accounts.len(); if len != 0 { // Store newly initialized account addresses in the wallet - println!( + display_line!( + IO, "The transaction initialized {} new account{}", len, if len == 1 { "" } else { "s" } @@ -480,12 +504,16 @@ pub async fn save_initialized_accounts( ); match added { Some(new_alias) if new_alias != encoded => { - println!( + display_line!( + IO, "Added alias {} for address {}.", - new_alias, encoded + new_alias, + encoded ); } - _ => println!("No alias added for address {}.", encoded), + _ => { + display_line!(IO, "No alias added for address {}.", encoded) + } }; } } @@ -496,6 +524,7 @@ pub async fn build_validator_commission_change< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -515,7 +544,11 @@ pub async fn build_validator_commission_change< let validator = validator.clone(); if rpc::is_validator(client, &validator).await? { if rate < Dec::zero() || rate > Dec::one() { - eprintln!("Invalid new commission rate, received {}", rate); + edisplay_line!( + IO, + "Invalid new commission rate, received {}", + rate + ); return Err(Error::from(TxError::InvalidCommissionRate(rate))); } @@ -535,7 +568,8 @@ pub async fn build_validator_commission_change< if rate.abs_diff(&commission_rate) > max_commission_change_per_epoch { - eprintln!( + edisplay_line!( + IO, "New rate is too large of a change with respect to \ the predecessor epoch in which the rate will take \ effect." @@ -548,14 +582,14 @@ pub async fn build_validator_commission_change< } } None => { - eprintln!("Error retrieving from storage"); + edisplay_line!(IO, "Error retrieving from storage"); if !tx_args.force { return Err(Error::from(TxError::Retrieval)); } } } } else { - eprintln!("The given address {validator} is not a validator."); + edisplay_line!(IO, "The given address {validator} is not a validator."); if !tx_args.force { return Err(Error::from(TxError::InvalidValidatorAddress( validator, @@ -568,7 +602,7 @@ pub async fn build_validator_commission_change< new_rate: rate, }; - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -587,6 +621,7 @@ pub async fn build_update_steward_commission< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -600,7 +635,7 @@ pub async fn build_update_steward_commission< gas_payer: &common::PublicKey, ) -> Result<(Tx, Option)> { if !rpc::is_steward(client, &steward).await && !tx_args.force { - eprintln!("The given address {} is not a steward.", &steward); + edisplay_line!(IO, "The given address {} is not a steward.", &steward); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); }; @@ -608,7 +643,10 @@ pub async fn build_update_steward_commission< .map_err(|e| TxError::InvalidStewardCommission(e.to_string()))?; if !commission.is_valid() && !tx_args.force { - eprintln!("The sum of all percentage must not be greater than 1."); + edisplay_line!( + IO, + "The sum of all percentage must not be greater than 1." + ); return Err(Error::from(TxError::InvalidStewardCommission( "Commission sum is greater than 1.".to_string(), ))); @@ -619,7 +657,7 @@ pub async fn build_update_steward_commission< commission: commission.reward_distribution, }; - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -638,6 +676,7 @@ pub async fn build_resign_steward< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -650,11 +689,11 @@ pub async fn build_resign_steward< gas_payer: &common::PublicKey, ) -> Result<(Tx, Option)> { if !rpc::is_steward(client, &steward).await && !tx_args.force { - eprintln!("The given address {} is not a steward.", &steward); + edisplay_line!(IO, "The given address {} is not a steward.", &steward); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); }; - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -673,6 +712,7 @@ pub async fn build_unjail_validator< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -685,7 +725,11 @@ pub async fn build_unjail_validator< fee_payer: common::PublicKey, ) -> Result<(Tx, Option)> { if !rpc::is_validator(client, &validator).await? { - eprintln!("The given address {} is not a validator.", &validator); + edisplay_line!( + IO, + "The given address {} is not a validator.", + &validator + ); if !tx_args.force { return Err(Error::from(TxError::InvalidValidatorAddress( validator.clone(), @@ -706,7 +750,8 @@ pub async fn build_unjail_validator< )) })?; if validator_state_at_pipeline != ValidatorState::Jailed { - eprintln!( + edisplay_line!( + IO, "The given validator address {} is not jailed at the pipeline \ epoch when it would be restored to one of the validator sets.", &validator @@ -728,7 +773,8 @@ pub async fn build_unjail_validator< let eligible_epoch = last_slash_epoch + params.slash_processing_epoch_offset(); if current_epoch < eligible_epoch { - eprintln!( + edisplay_line!( + IO, "The given validator address {} is currently frozen and \ not yet eligible to be unjailed.", &validator @@ -754,7 +800,7 @@ pub async fn build_unjail_validator< Err(err) => return Err(err), } - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -773,6 +819,7 @@ pub async fn build_withdraw< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -787,9 +834,12 @@ pub async fn build_withdraw< ) -> Result<(Tx, Option)> { let epoch = rpc::query_epoch(client).await?; - let validator = - known_validator_or_err(validator.clone(), tx_args.force, client) - .await?; + let validator = known_validator_or_err::<_, IO>( + validator.clone(), + tx_args.force, + client, + ) + .await?; let source = source.clone(); @@ -804,26 +854,29 @@ pub async fn build_withdraw< .await?; if tokens.is_zero() { - eprintln!( + edisplay_line!( + IO, "There are no unbonded bonds ready to withdraw in the current \ epoch {}.", epoch ); - rpc::query_and_print_unbonds(client, &bond_source, &validator).await?; + rpc::query_and_print_unbonds::<_, IO>(client, &bond_source, &validator) + .await?; if !tx_args.force { return Err(Error::from(TxError::NoUnbondReady(epoch))); } } else { - println!( + display_line!( + IO, "Found {} tokens that can be withdrawn.", tokens.to_string_native() ); - println!("Submitting transaction to withdraw them..."); + display_line!(IO, "Submitting transaction to withdraw them..."); } let data = pos::Withdraw { validator, source }; - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -842,6 +895,7 @@ pub async fn build_unbond< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -860,18 +914,24 @@ pub async fn build_unbond< let bond_source = source.clone().unwrap_or_else(|| validator.clone()); if !tx_args.force { - known_validator_or_err(validator.clone(), tx_args.force, client) - .await?; + known_validator_or_err::<_, IO>( + validator.clone(), + tx_args.force, + client, + ) + .await?; let bond_amount = rpc::query_bond(client, &bond_source, &validator, None).await?; - println!( + display_line!( + IO, "Bond amount available for unbonding: {} NAM", bond_amount.to_string_native() ); if amount > bond_amount { - eprintln!( + edisplay_line!( + IO, "The total bonds of the source {} is lower than the amount to \ be unbonded. Amount to unbond is {} and the total bonds is \ {}.", @@ -906,7 +966,7 @@ pub async fn build_unbond< source: source.clone(), }; - let (tx, epoch) = build( + let (tx, epoch) = build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -922,7 +982,7 @@ pub async fn build_unbond< } /// Query the unbonds post-tx -pub async fn query_unbonds( +pub async fn query_unbonds( client: &C, args: args::Unbond, latest_withdrawal_pre: Option<(Epoch, token::Amount)>, @@ -951,7 +1011,8 @@ pub async fn query_unbonds( match latest_withdraw_epoch_post.cmp(&latest_withdraw_epoch_pre) { std::cmp::Ordering::Less => { if args.tx.force { - eprintln!( + edisplay_line!( + IO, "Unexpected behavior reading the unbonds data has \ occurred" ); @@ -960,7 +1021,8 @@ pub async fn query_unbonds( } } std::cmp::Ordering::Equal => { - println!( + display_line!( + IO, "Amount {} withdrawable starting from epoch {}", (latest_withdraw_amount_post - latest_withdraw_amount_pre) .to_string_native(), @@ -968,7 +1030,8 @@ pub async fn query_unbonds( ); } std::cmp::Ordering::Greater => { - println!( + display_line!( + IO, "Amount {} withdrawable starting from epoch {}", latest_withdraw_amount_post.to_string_native(), latest_withdraw_epoch_post, @@ -976,7 +1039,8 @@ pub async fn query_unbonds( } } } else { - println!( + display_line!( + IO, "Amount {} withdrawable starting from epoch {}", latest_withdraw_amount_post.to_string_native(), latest_withdraw_epoch_post, @@ -990,6 +1054,7 @@ pub async fn build_bond< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -1004,15 +1069,20 @@ pub async fn build_bond< }: args::Bond, fee_payer: common::PublicKey, ) -> Result<(Tx, Option)> { - let validator = - known_validator_or_err(validator.clone(), tx_args.force, client) - .await?; + let validator = known_validator_or_err::<_, IO>( + validator.clone(), + tx_args.force, + client, + ) + .await?; // Check that the source address exists on chain let source = match source.clone() { - Some(source) => source_exists_or_err(source, tx_args.force, client) - .await - .map(Some), + Some(source) => { + source_exists_or_err::<_, IO>(source, tx_args.force, client) + .await + .map(Some) + } None => Ok(source.clone()), }?; // Check bond's source (source for delegation or validator for self-bonds) @@ -1021,7 +1091,7 @@ pub async fn build_bond< let balance_key = token::balance_key(&native_token, bond_source); // TODO Should we state the same error message for the native token? - let post_balance = check_balance_too_low_err( + let post_balance = check_balance_too_low_err::<_, IO>( &native_token, bond_source, amount, @@ -1042,7 +1112,7 @@ pub async fn build_bond< source, }; - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -1061,6 +1131,7 @@ pub async fn build_default_proposal< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -1094,7 +1165,7 @@ pub async fn build_default_proposal< }; Ok(()) }; - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -1113,6 +1184,7 @@ pub async fn build_vote_proposal< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -1176,7 +1248,7 @@ pub async fn build_vote_proposal< delegations, }; - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -1195,6 +1267,7 @@ pub async fn build_pgf_funding_proposal< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -1220,7 +1293,7 @@ pub async fn build_pgf_funding_proposal< data.content = extra_section_hash; Ok(()) }; - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -1239,6 +1312,7 @@ pub async fn build_pgf_stewards_proposal< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -1265,7 +1339,7 @@ pub async fn build_pgf_stewards_proposal< Ok(()) }; - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -1284,6 +1358,7 @@ pub async fn build_ibc_transfer< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -1292,16 +1367,22 @@ pub async fn build_ibc_transfer< fee_payer: common::PublicKey, ) -> Result<(Tx, Option)> { // Check that the source address exists on chain - let source = - source_exists_or_err(args.source.clone(), args.tx.force, client) - .await?; + let source = source_exists_or_err::<_, IO>( + args.source.clone(), + args.tx.force, + client, + ) + .await?; // We cannot check the receiver // validate the amount given - let validated_amount = - validate_amount(client, args.amount, &args.token, args.tx.force) - .await - .expect("expected to validate amount"); + let validated_amount = validate_amount::<_, IO>( + client, + args.amount, + &args.token, + args.tx.force, + ) + .await?; if validated_amount.canonical().denom.0 != 0 { return Err(Error::Other(format!( "The amount for the IBC transfer should be an integer: {}", @@ -1312,7 +1393,7 @@ pub async fn build_ibc_transfer< // Check source balance let balance_key = token::balance_key(&args.token, &source); - let post_balance = check_balance_too_low_err( + let post_balance = check_balance_too_low_err::<_, IO>( &args.token, &source, validated_amount.amount, @@ -1327,10 +1408,12 @@ pub async fn build_ibc_transfer< token: args.token.clone(), }); - let tx_code_hash = - query_wasm_code_hash(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::<_, IO>( + client, + args.tx_code_path.to_str().unwrap(), + ) + .await + .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; let ibc_denom = match &args.token { Address::Internal(InternalAddress::IbcToken(hash)) => { @@ -1398,7 +1481,7 @@ pub async fn build_ibc_transfer< tx.add_code_from_hash(tx_code_hash) .add_serialized_data(data); - let epoch = prepare_tx::( + let epoch = prepare_tx::( client, wallet, shielded, @@ -1416,7 +1499,7 @@ pub async fn build_ibc_transfer< /// Abstraction for helping build transactions #[allow(clippy::too_many_arguments)] -pub async fn build( +pub async fn build( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -1432,8 +1515,9 @@ where D: BorshSerialize, U: WalletUtils, V: ShieldedUtils, + IO: Io, { - build_pow_flag( + build_pow_flag::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -1450,7 +1534,14 @@ where } #[allow(clippy::too_many_arguments)] -async fn build_pow_flag( +async fn build_pow_flag< + C: crate::ledger::queries::Client + Sync, + U, + V, + F, + D, + IO: Io, +>( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -1472,15 +1563,16 @@ where let mut tx_builder = Tx::new(chain_id, tx_args.expiration); - let tx_code_hash = query_wasm_code_hash(client, path.to_string_lossy()) - .await - .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; + let tx_code_hash = + query_wasm_code_hash::<_, IO>(client, path.to_string_lossy()) + .await + .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; on_tx(&mut tx_builder, &mut data)?; tx_builder.add_code_from_hash(tx_code_hash).add_data(data); - let epoch = prepare_tx::( + let epoch = prepare_tx::( client, wallet, shielded, @@ -1573,6 +1665,7 @@ pub async fn build_transfer< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -1585,18 +1678,21 @@ pub async fn build_transfer< let token = args.token.clone(); // Check that the source address exists on chain - source_exists_or_err(source.clone(), args.tx.force, client).await?; + source_exists_or_err::<_, IO>(source.clone(), args.tx.force, client) + .await?; // Check that the target address exists on chain - target_exists_or_err(target.clone(), args.tx.force, client).await?; + target_exists_or_err::<_, IO>(target.clone(), args.tx.force, client) + .await?; // Check source balance let balance_key = token::balance_key(&token, &source); // validate the amount given let validated_amount = - validate_amount(client, args.amount, &token, args.tx.force).await?; + validate_amount::<_, IO>(client, args.amount, &token, args.tx.force) + .await?; args.amount = InputAmount::Validated(validated_amount); - let post_balance = check_balance_too_low_err::( + let post_balance = check_balance_too_low_err::( &token, &source, validated_amount.amount, @@ -1635,7 +1731,9 @@ pub async fn build_transfer< let is_source_faucet = false; // Construct the shielded part of the transaction, if any - let stx_result = shielded.gen_shielded_transfer(client, args.clone()).await; + let stx_result = shielded + .gen_shielded_transfer::<_, IO>(client, args.clone()) + .await; let shielded_parts = match stx_result { Ok(stx) => Ok(stx), @@ -1703,7 +1801,7 @@ pub async fn build_transfer< }; Ok(()) }; - let (tx, unshielding_epoch) = build_pow_flag( + let (tx, unshielding_epoch) = build_pow_flag::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -1744,6 +1842,7 @@ pub async fn build_init_account< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -1757,7 +1856,8 @@ pub async fn build_init_account< }: args::TxInitAccount, fee_payer: &common::PublicKey, ) -> Result<(Tx, Option)> { - let vp_code_hash = query_wasm_code_hash_buf(client, &vp_code_path).await?; + let vp_code_hash = + query_wasm_code_hash_buf::<_, IO>(client, &vp_code_path).await?; let threshold = match threshold { Some(threshold) => threshold, @@ -1782,7 +1882,7 @@ pub async fn build_init_account< data.vp_code_hash = extra_section_hash; Ok(()) }; - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -1801,6 +1901,7 @@ pub async fn build_update_account< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -1826,7 +1927,8 @@ pub async fn build_update_account< let vp_code_hash = match vp_code_path { Some(code_path) => { - let vp_hash = query_wasm_code_hash_buf(client, &code_path).await?; + let vp_hash = + query_wasm_code_hash_buf::<_, IO>(client, &code_path).await?; Some(vp_hash) } None => None, @@ -1850,7 +1952,7 @@ pub async fn build_update_account< data.vp_code_hash = extra_section_hash; Ok(()) }; - build( + build::<_, _, _, _, _, IO>( client, wallet, shielded, @@ -1869,6 +1971,7 @@ pub async fn build_custom< C: crate::ledger::queries::Client + Sync, U: WalletUtils, V: ShieldedUtils, + IO: Io, >( client: &C, wallet: &mut Wallet, @@ -1887,7 +1990,7 @@ pub async fn build_custom< Error::Other("Invalid tx deserialization.".to_string()) })? } else { - let tx_code_hash = query_wasm_code_hash_buf( + let tx_code_hash = query_wasm_code_hash_buf::<_, IO>( client, &code_path .ok_or(Error::Other("No code path supplied".to_string()))?, @@ -1900,7 +2003,7 @@ pub async fn build_custom< tx }; - let epoch = prepare_tx::( + let epoch = prepare_tx::( client, wallet, shielded, @@ -1916,13 +2019,16 @@ pub async fn build_custom< Ok((tx, epoch)) } -async fn expect_dry_broadcast( +async fn expect_dry_broadcast< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( to_broadcast: TxBroadcastData, client: &C, ) -> Result { match to_broadcast { TxBroadcastData::DryRun(tx) => { - rpc::dry_run_tx(client, tx.to_bytes()).await?; + rpc::dry_run_tx::<_, IO>(client, tx.to_bytes()).await?; Ok(ProcessTxResponse::DryRun) } TxBroadcastData::Live { @@ -1940,7 +2046,10 @@ 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( +async fn known_validator_or_err< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( validator: Address, force: bool, client: &C, @@ -1949,7 +2058,8 @@ async fn known_validator_or_err( let is_validator = rpc::is_validator(client, &validator).await?; if !is_validator { if force { - eprintln!( + edisplay_line!( + IO, "The address {} doesn't belong to any known validator account.", validator ); @@ -1965,7 +2075,7 @@ async fn known_validator_or_err( /// general pattern for checking if an address exists on the chain, or /// throwing an error if it's not forced. Takes a generic error /// message and the error type. -async fn address_exists_or_err( +async fn address_exists_or_err( addr: Address, force: bool, client: &C, @@ -1979,7 +2089,7 @@ where let addr_exists = rpc::known_address::(client, &addr).await?; if !addr_exists { if force { - eprintln!("{}", message); + edisplay_line!(IO, "{}", message); Ok(addr) } else { Err(err(addr)) @@ -1992,14 +2102,17 @@ 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( +async fn source_exists_or_err< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( token: Address, force: bool, client: &C, ) -> 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::<_, _, IO>(token, force, client, message, |err| { Error::from(TxError::SourceDoesNotExist(err)) }) .await @@ -2008,14 +2121,17 @@ 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( +async fn target_exists_or_err< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( token: Address, force: bool, client: &C, ) -> 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::<_, _, IO>(token, force, client, message, |err| { Error::from(TxError::TargetLocationDoesNotExist(err)) }) .await @@ -2024,7 +2140,10 @@ 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( +async fn check_balance_too_low_err< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( token: &Address, source: &Address, amount: token::Amount, @@ -2039,14 +2158,21 @@ async fn check_balance_too_low_err( Some(diff) => Ok(diff), None => { if force { - eprintln!( + edisplay_line!( + 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::<_, IO>( + client, token, amount + ) + .await, + format_denominated_amount::<_, IO>( + client, token, balance + ) + .await, ); Ok(token::Amount::default()) } else { @@ -2063,9 +2189,11 @@ async fn check_balance_too_low_err( QueryError::General(_) | QueryError::NoSuchKey(_), )) => { if force { - eprintln!( + edisplay_line!( + IO, "No balance found for the source {} of token {}", - source, token + source, + token ); Ok(token::Amount::default()) } else { @@ -2082,10 +2210,17 @@ async fn check_balance_too_low_err( } #[allow(dead_code)] -fn validate_untrusted_code_err(vp_code: &Vec, force: bool) -> Result<()> { +fn validate_untrusted_code_err( + vp_code: &Vec, + force: bool, +) -> Result<()> { if let Err(err) = vm::validate_untrusted_wasm(vp_code) { if force { - eprintln!("Validity predicate code validation failed with {}", err); + edisplay_line!( + IO, + "Validity predicate code validation failed with {}", + err + ); Ok(()) } else { Err(Error::from(TxError::WasmValidationFailure(err))) @@ -2094,11 +2229,14 @@ fn validate_untrusted_code_err(vp_code: &Vec, force: bool) -> Result<()> { Ok(()) } } -async fn query_wasm_code_hash_buf( +async fn query_wasm_code_hash_buf< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( client: &C, path: &Path, ) -> Result { - query_wasm_code_hash(client, path.to_string_lossy()).await + query_wasm_code_hash::<_, IO>(client, path.to_string_lossy()).await } /// A helper for [`fn build`] that can be used for `on_tx` arg that does nothing diff --git a/shared/src/types/io.rs b/shared/src/types/io.rs new file mode 100644 index 0000000000..93d45a75ff --- /dev/null +++ b/shared/src/types/io.rs @@ -0,0 +1,123 @@ +//! Traits for implementing IO handlers. This is to enable +//! generic IO. The defaults are the obvious Rust native +//! functions. + +/// Rust native I/O handling. +pub struct DefaultIo; + +#[async_trait::async_trait(?Send)] +impl Io for DefaultIo {} + +#[async_trait::async_trait(?Send)] +#[allow(missing_docs)] +pub trait Io { + fn print(output: impl AsRef) { + print!("{}", output.as_ref()); + } + + fn flush() { + use std::io::Write; + std::io::stdout().flush().unwrap(); + } + + fn println(output: impl AsRef) { + println!("{}", output.as_ref()); + } + + fn write( + mut writer: W, + output: impl AsRef, + ) -> std::io::Result<()> { + write!(writer, "{}", output.as_ref()) + } + + fn writeln( + mut writer: W, + output: impl AsRef, + ) -> std::io::Result<()> { + writeln!(writer, "{}", output.as_ref()) + } + + fn eprintln(output: impl AsRef) { + eprintln!("{}", output.as_ref()); + } + + async fn read() -> tokio::io::Result { + read_aux(tokio::io::stdin()).await + } + + async fn prompt(question: impl AsRef) -> String { + prompt_aux(tokio::io::stdin(), tokio::io::stdout(), question.as_ref()) + .await + } +} + +/// A generic function for displaying a prompt to users and reading +/// in their response. +pub async fn prompt_aux( + mut reader: R, + mut writer: W, + question: &str, +) -> String +where + R: tokio::io::AsyncReadExt + Unpin, + W: tokio::io::AsyncWriteExt + Unpin, +{ + writer + .write_all(question.as_bytes()) + .await + .expect("Unable to write"); + writer.flush().await.unwrap(); + let mut s = String::new(); + reader.read_to_string(&mut s).await.expect("Unable to read"); + s +} + +/// A generic function for reading input from users +pub async fn read_aux(mut reader: R) -> tokio::io::Result +where + R: tokio::io::AsyncReadExt + Unpin, +{ + let mut s = String::new(); + reader.read_to_string(&mut s).await?; + Ok(s) +} + +/// Convenience macro for formatting arguments to +/// [`Io::print`] +#[macro_export] +macro_rules! display { + ($io:ty) => { + <$io>::print("") + }; + ($io:ty, $w:expr; $($args:tt)*) => { + <$io>::write($w, format_args!($($args)*).to_string()) + }; + ($io:ty,$($args:tt)*) => { + <$io>::print(format_args!($($args)*).to_string()) + }; +} + +/// Convenience macro for formatting arguments to +/// [`Io::println`] and [`Io::writeln`] +#[macro_export] +macro_rules! display_line { + ($io:ty) => { + <$io>::println("") + }; + ($io:ty, $w:expr; $($args:tt)*) => { + <$io>::writeln($w, format_args!($($args)*).to_string()) + }; + ($io:ty,$($args:tt)*) => { + <$io>::println(format_args!($($args)*).to_string()) + }; +} + +/// Convenience macro for formatting arguments to +/// [`Io::eprintln`] +#[macro_export] +macro_rules! edisplay_line { + ($io:ty,$($args:tt)*) => { + <$io>::eprintln(format_args!($($args)*).to_string()) + }; +} diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 026295d728..693c1bfbe0 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -3,6 +3,7 @@ pub mod control_flow; pub mod error; pub mod ibc; +pub mod io; pub mod key; pub use namada_core::types::{ diff --git a/test_fixtures/masp_proofs/0DAF8BDF2318129AC828A7149AC83E76506147445D4DC22D57CBC9869BCDDA80.bin b/test_fixtures/masp_proofs/0DAF8BDF2318129AC828A7149AC83E76506147445D4DC22D57CBC9869BCDDA80.bin new file mode 100644 index 0000000000..f20cf90f1a Binary files /dev/null and b/test_fixtures/masp_proofs/0DAF8BDF2318129AC828A7149AC83E76506147445D4DC22D57CBC9869BCDDA80.bin differ diff --git a/test_fixtures/masp_proofs/12C933751C24BDC39C9108F5AF5D4C1BF345378A4FB6BB0B179BA8BDB0D2A3C0.bin b/test_fixtures/masp_proofs/12C933751C24BDC39C9108F5AF5D4C1BF345378A4FB6BB0B179BA8BDB0D2A3C0.bin new file mode 100644 index 0000000000..e24feb560a Binary files /dev/null and b/test_fixtures/masp_proofs/12C933751C24BDC39C9108F5AF5D4C1BF345378A4FB6BB0B179BA8BDB0D2A3C0.bin differ diff --git a/test_fixtures/masp_proofs/5B99F3D7E0CE75AB1F4B737EC88B269A5436CD72AA758686960F409B04841707.bin b/test_fixtures/masp_proofs/5B99F3D7E0CE75AB1F4B737EC88B269A5436CD72AA758686960F409B04841707.bin new file mode 100644 index 0000000000..30b0e399a3 Binary files /dev/null and b/test_fixtures/masp_proofs/5B99F3D7E0CE75AB1F4B737EC88B269A5436CD72AA758686960F409B04841707.bin differ diff --git a/test_fixtures/masp_proofs/889C046FA76727BC97433503BB79BAC90BA1F01653EBCFDCF7CC8AAA1BBEE462.bin b/test_fixtures/masp_proofs/889C046FA76727BC97433503BB79BAC90BA1F01653EBCFDCF7CC8AAA1BBEE462.bin new file mode 100644 index 0000000000..525e1e63ee Binary files /dev/null and b/test_fixtures/masp_proofs/889C046FA76727BC97433503BB79BAC90BA1F01653EBCFDCF7CC8AAA1BBEE462.bin differ diff --git a/test_fixtures/masp_proofs/A9FA2730222946FA51E9D587544FDED28D5E7D3C6B52DCF38A5978CEA70D6FD3.bin b/test_fixtures/masp_proofs/A9FA2730222946FA51E9D587544FDED28D5E7D3C6B52DCF38A5978CEA70D6FD3.bin new file mode 100644 index 0000000000..634d326dcd Binary files /dev/null and b/test_fixtures/masp_proofs/A9FA2730222946FA51E9D587544FDED28D5E7D3C6B52DCF38A5978CEA70D6FD3.bin differ diff --git a/test_fixtures/masp_proofs/AC308C08512AF5DAA364B845D146763B3CE0BACFB7799C6744E50B9E7F43E961.bin b/test_fixtures/masp_proofs/AC308C08512AF5DAA364B845D146763B3CE0BACFB7799C6744E50B9E7F43E961.bin new file mode 100644 index 0000000000..b67b3c8cd0 Binary files /dev/null and b/test_fixtures/masp_proofs/AC308C08512AF5DAA364B845D146763B3CE0BACFB7799C6744E50B9E7F43E961.bin differ diff --git a/test_fixtures/masp_proofs/BE57BA4D8FB068F5A933E78DEF2989556FD771D368849D034E22923FD350EEEC.bin b/test_fixtures/masp_proofs/BE57BA4D8FB068F5A933E78DEF2989556FD771D368849D034E22923FD350EEEC.bin new file mode 100644 index 0000000000..c05883cbab Binary files /dev/null and b/test_fixtures/masp_proofs/BE57BA4D8FB068F5A933E78DEF2989556FD771D368849D034E22923FD350EEEC.bin differ diff --git a/test_fixtures/masp_proofs/E76E54B7526CD2B5423322FB711C0CA6AA6520A2AC8BC34A84358EA137F138D0.bin b/test_fixtures/masp_proofs/E76E54B7526CD2B5423322FB711C0CA6AA6520A2AC8BC34A84358EA137F138D0.bin new file mode 100644 index 0000000000..4df2e0be9f Binary files /dev/null and b/test_fixtures/masp_proofs/E76E54B7526CD2B5423322FB711C0CA6AA6520A2AC8BC34A84358EA137F138D0.bin differ diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index f2a6d20da3..88471bb374 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -20,6 +20,7 @@ use borsh::BorshSerialize; use color_eyre::eyre::Result; use data_encoding::HEXLOWER; use namada::types::address::Address; +use namada::types::io::DefaultIo; use namada::types::storage::Epoch; use namada::types::token; use namada_apps::client::tx::CLIShieldedUtils; @@ -738,7 +739,7 @@ fn ledger_txs_and_queries() -> Result<()> { #[test] fn masp_txs_and_queries() -> Result<()> { // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. @@ -886,7 +887,7 @@ fn masp_txs_and_queries() -> Result<()> { #[test] fn wrapper_disposable_signer() -> Result<()> { // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. diff --git a/tests/src/integration/masp.rs b/tests/src/integration/masp.rs index ac69f08a0d..02d6dabc8f 100644 --- a/tests/src/integration/masp.rs +++ b/tests/src/integration/masp.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use color_eyre::eyre::Result; use color_eyre::owo_colors::OwoColorize; +use namada::types::io::DefaultIo; use namada_apps::client::tx::CLIShieldedUtils; use namada_apps::node::ledger::shell::testing::client::run; use namada_apps::node::ledger::shell::testing::utils::Bin; @@ -29,7 +30,7 @@ fn masp_incentives() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. @@ -765,7 +766,7 @@ fn masp_pinned_txs() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); let mut node = setup::setup()?; // Wait till epoch boundary @@ -928,7 +929,7 @@ fn masp_txs_and_queries() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); enum Response { Ok(&'static str), @@ -1235,7 +1236,7 @@ fn wrapper_fee_unshielding() { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new(PathBuf::new()); + let _ = CLIShieldedUtils::new::(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. diff --git a/wasm/checksums.json b/wasm/checksums.json index 9c50dd46dc..7ddad4f479 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,23 +1,23 @@ { - "tx_bond.wasm": "tx_bond.8d1ddbeb397209c5efa22dd57fbdb31825d67c2942441cb2612583ec2593831a.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.41ada308019a6227a495d996f5d3248e3f8052fcadf8779ee2b2e293aa73ccd0.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.137f2871848970aa9cf1d3a92a1e1a6e7a48b0537632d838bbb4e69fd301f8c3.wasm", - "tx_ibc.wasm": "tx_ibc.af007e03e8de1f8c34eb928fcfe91fd44b05c0183ca1149c5b262c8f62fcdd36.wasm", - "tx_init_account.wasm": "tx_init_account.d527ea17b417fca1a72d6a26abc34219630efcad4701e629a89e026e06ee06c1.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.e605bb96ff8b6ad1e10491a81590d15ed792f87b0382d1faee9966cb25a09028.wasm", - "tx_init_validator.wasm": "tx_init_validator.91ce97ff0bfa49ce9baa7585ae7e2c0514e91a66c625502b4aced635da5b021a.wasm", - "tx_resign_steward.wasm": "tx_resign_steward.a34a5737653182ecb75f6fcbac5b74ef849eb29b0072a20e90897acc14d8886e.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.8ab38f516ac799dcb96ba372cd5e5defd381ddf9d69579ce1556d1721d34f668.wasm", - "tx_transfer.wasm": "tx_transfer.738ac69d4a4f3dfb154aeed6b806ef1042b1a707de98bf8c6cc5ad66d478f6d9.wasm", - "tx_unbond.wasm": "tx_unbond.0c90a1f9a95b7171e0ebdca8318c19ba45f358158aa68370f630f84025635c8f.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.310c196cb7b2d371bb74fe37ee1f2f7e233ead59477027891e4e28751b6bb3fe.wasm", - "tx_update_account.wasm": "tx_update_account.8f5934e4fcca4e7d3c58e1c0b8722ce0a948efa6b99e7801dd1c16f8ea22fb59.wasm", - "tx_update_steward_commission.wasm": "tx_update_steward_commission.3deda3d2d0fcce2e14c6a4d72931ea3a3713666c6eed5fd29a78e30d395b3cf5.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.33567afd0c59d5f2499a3cf4ebf3c254de1cae1d310d004b8e0e538f2fc8377a.wasm", - "tx_withdraw.wasm": "tx_withdraw.00e0a04e892eb67ac3c3c7a3162b12dc198506c1c466893923911c3ab471dc03.wasm", - "vp_implicit.wasm": "vp_implicit.0fc8114c5d87db4d818b4b38ded664c1ca1d3d42026e6b1328213c00e99f01eb.wasm", - "vp_masp.wasm": "vp_masp.8b01ab3c616342973fb30e37c8b9e4c83615f25b7cc6657f0360146266d36890.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.720d6808fa225cb3381ac0ff682e1c9136d8ef1193933ce30356ef2252fee001.wasm", - "vp_user.wasm": "vp_user.f93b90d5a0226c79159edd48f2801e7a12525751b937fda58525a8fc8b42d745.wasm", - "vp_validator.wasm": "vp_validator.3decad0fd761b928cdec3f89ed241fc218dd402be300f5edabf511677ae3d37d.wasm" + "tx_bond.wasm": "tx_bond.65775f3faf6f0ff1bbfa8bd7f171dd1d591d83536a4eac3016c2a460471b7fa7.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.2e664c7d28bf61b04a826a43532c35c3982e4f5eb9fbf5159888b4e6a1f540a6.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.c3f55f00b0a58b9f78703f8b96893839aa771610b670aeeef4e9db55a00ec1bc.wasm", + "tx_ibc.wasm": "tx_ibc.27f6f51add11129c6845000e76ad5fcef3151f3c3339464ee1bb367ea2654981.wasm", + "tx_init_account.wasm": "tx_init_account.6bd6d908fb6e88ea91777d59c30e4ed0de0cd682101098966703bed7d9fbf58b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d7b84245eab1529041367d7109594ca4496b6d7425cf4c9f4bfb69951cf2f5f3.wasm", + "tx_init_validator.wasm": "tx_init_validator.f8f5c1512b4f117ac4bd7f1860799265e1b671739ea3e712e85ed4c8063594d1.wasm", + "tx_resign_steward.wasm": "tx_resign_steward.3460f267f3bad1de285d2cd53f1eecda29b44eefb2f7dee0df9d059f778670c0.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.214dbad89fecd6897d41db962688fd5320ed50a805025eef857d47761fb30088.wasm", + "tx_transfer.wasm": "tx_transfer.e452dbee5ffadc6f4df60a7adbf638592b94da8fdbcf5767f09170b71dee68f0.wasm", + "tx_unbond.wasm": "tx_unbond.4082bf7da70105c1c248ee403762d2cacb1ce15bf1e7da7cd82e800fae7b05fb.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.1dfe798b2bffcfcafd0439cfea7b40ad7a3380dc02d424ecf07de35d6219f55e.wasm", + "tx_update_account.wasm": "tx_update_account.22ad3f7a3afafe8cb3aedd6d4fc210b93208221795fdd76626e803b972935176.wasm", + "tx_update_steward_commission.wasm": "tx_update_steward_commission.eb59360509115e7ec537ba49f7e4438a4522b1929c78d7b644af1025479594a3.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.ca1171723084845931b994535631b05a843b5db0d54b52a484653a932de704db.wasm", + "tx_withdraw.wasm": "tx_withdraw.2505b1c659e910b078f8c618c10659645e96c9ae0aac55c3adbdb05a7e7622d1.wasm", + "vp_implicit.wasm": "vp_implicit.d46e9d52909cd365203bedd007effd12fe17e50a5f25151d827e4cc05cee0cca.wasm", + "vp_masp.wasm": "vp_masp.0dda4af5fb350cb2d4c795c51abe150f0df772a4d335540846cc5dffd8880928.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.54a30792965ebdb8476f0cdcae4e374350653baa69dd1c0e2e1ffde4e496169c.wasm", + "vp_user.wasm": "vp_user.c5c935be6353c1ea08c012af3850dd7819ee58f81b2c8ffaf7826020b1fb41d6.wasm", + "vp_validator.wasm": "vp_validator.f91045a01992002574796e598c611ac71ad57bb17061467b049596cb0c756ecc.wasm" } \ No newline at end of file