From 615ebc8e7dbf3531c446f60ec7ac77676aef3b63 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Mon, 9 Oct 2023 15:25:56 +0200 Subject: [PATCH] SDK can now query for the native token address from the network. Also added null IO implementation. --- apps/src/bin/namada-client/main.rs | 2 +- apps/src/bin/namada-relayer/main.rs | 3 +- apps/src/bin/namada-wallet/main.rs | 2 +- apps/src/lib/cli/api.rs | 4 +- apps/src/lib/cli/client.rs | 7 +- apps/src/lib/cli/context.rs | 8 +- apps/src/lib/cli/relayer.rs | 4 +- apps/src/lib/cli/wallet.rs | 2 +- apps/src/lib/client/tx.rs | 4 +- .../lib/node/ledger/shell/testing/client.rs | 6 +- benches/lib.rs | 3 +- shared/src/ledger/mod.rs | 126 ++++++++++-------- shared/src/ledger/queries/shell.rs | 12 ++ shared/src/sdk/args.rs | 4 +- shared/src/sdk/rpc.rs | 7 + shared/src/types/io.rs | 60 ++++++++- 16 files changed, 167 insertions(+), 87 deletions(-) diff --git a/apps/src/bin/namada-client/main.rs b/apps/src/bin/namada-client/main.rs index 167674f65e..770dcf5367 100644 --- a/apps/src/bin/namada-client/main.rs +++ b/apps/src/bin/namada-client/main.rs @@ -13,7 +13,7 @@ async fn main() -> Result<()> { let _log_guard = logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI - CliApi::::handle_client_command::( + CliApi::handle_client_command::( None, cli::namada_client_cli()?, &CliIo, diff --git a/apps/src/bin/namada-relayer/main.rs b/apps/src/bin/namada-relayer/main.rs index ef5e05f913..f9d98a2a4e 100644 --- a/apps/src/bin/namada-relayer/main.rs +++ b/apps/src/bin/namada-relayer/main.rs @@ -14,6 +14,5 @@ async fn main() -> Result<()> { let cmd = cli::namada_relayer_cli()?; // run the CLI - CliApi::::handle_relayer_command::(None, cmd, &CliIo) - .await + CliApi::handle_relayer_command::(None, cmd, &CliIo).await } diff --git a/apps/src/bin/namada-wallet/main.rs b/apps/src/bin/namada-wallet/main.rs index 987e9d2699..30d4a64156 100644 --- a/apps/src/bin/namada-wallet/main.rs +++ b/apps/src/bin/namada-wallet/main.rs @@ -6,5 +6,5 @@ pub fn main() -> Result<()> { color_eyre::install()?; let (cmd, ctx) = cli::namada_wallet_cli()?; // run the CLI - CliApi::::handle_wallet_command(cmd, ctx, &CliIo) + CliApi::handle_wallet_command(cmd, ctx, &CliIo) } diff --git a/apps/src/lib/cli/api.rs b/apps/src/lib/cli/api.rs index 052a834f55..1b6851f3a9 100644 --- a/apps/src/lib/cli/api.rs +++ b/apps/src/lib/cli/api.rs @@ -1,5 +1,3 @@ -use std::marker::PhantomData; - use namada::sdk::queries::Client; use namada::sdk::rpc::wait_until_node_is_synched; use namada::tendermint_rpc::HttpClient; @@ -32,4 +30,4 @@ pub struct CliIo; #[async_trait::async_trait(?Send)] impl Io for CliIo {} -pub struct CliApi(PhantomData); +pub struct CliApi; diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index ac1ca1e34d..a342e9ef25 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -15,8 +15,8 @@ fn error() -> Report { eyre!("Fatal error") } -impl CliApi { - pub async fn handle_client_command( +impl CliApi { + pub async fn handle_client_command( client: Option, cmd: cli::NamadaClient, io: &IO, @@ -139,11 +139,12 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - let namada = NamadaImpl::new( + let namada = NamadaImpl::native_new( &client, &mut ctx.wallet, &mut ctx.shielded, io, + ctx.native_token, ); tx::submit_init_validator( &namada, diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 4772ef98b9..f6c3399baf 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -161,7 +161,13 @@ impl Context { C: namada::ledger::queries::Client + Sync, IO: Io, { - NamadaImpl::new(client, &mut self.wallet, &mut self.shielded, io) + NamadaImpl::native_new( + client, + &mut self.wallet, + &mut self.shielded, + io, + self.native_token.clone(), + ) } /// Parse and/or look-up the value from the context. diff --git a/apps/src/lib/cli/relayer.rs b/apps/src/lib/cli/relayer.rs index d94fd5a09d..aadf2d3bda 100644 --- a/apps/src/lib/cli/relayer.rs +++ b/apps/src/lib/cli/relayer.rs @@ -15,11 +15,11 @@ fn error() -> Report { eyre!("Fatal error") } -impl CliApi { +impl CliApi { pub async fn handle_relayer_command( client: Option, cmd: cli::NamadaRelayer, - io: &IO, + io: &impl Io, ) -> Result<()> where C: CliClient, diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 5dc223cd64..6247145b84 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -25,7 +25,7 @@ use crate::cli::args::CliToSdk; use crate::cli::{args, cmds, Context}; use crate::wallet::{read_and_confirm_encryption_password, CliWalletUtils}; -impl CliApi { +impl CliApi { pub fn handle_wallet_command( cmd: cmds::NamadaWallet, mut ctx: Context, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 22f0c1b1b4..53b2232f64 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -641,7 +641,7 @@ where })?; let author_balance = rpc::get_token_balance( namada.client(), - &namada.native_token().await, + &namada.native_token(), &proposal.proposal.author, ) .await; @@ -665,7 +665,7 @@ where })?; let author_balane = rpc::get_token_balance( namada.client(), - &namada.native_token().await, + &namada.native_token(), &proposal.proposal.author, ) .await; diff --git a/apps/src/lib/node/ledger/shell/testing/client.rs b/apps/src/lib/node/ledger/shell/testing/client.rs index 504bdc7e5b..7649156b8e 100644 --- a/apps/src/lib/node/ledger/shell/testing/client.rs +++ b/apps/src/lib/node/ledger/shell/testing/client.rs @@ -47,7 +47,7 @@ pub fn run( NamadaClient::WithoutContext(sub_cmd, global) } }; - rt.block_on(CliApi::::handle_client_command( + rt.block_on(CliApi::handle_client_command( Some(node), cmd, &TestingIo, @@ -61,7 +61,7 @@ pub fn run( let cmd = cmds::NamadaWallet::parse(&matches) .expect("Could not parse wallet command"); - CliApi::::handle_wallet_command(cmd, ctx, &TestingIo) + CliApi::handle_wallet_command(cmd, ctx, &TestingIo) } Bin::Relayer => { args.insert(0, "relayer"); @@ -83,7 +83,7 @@ pub fn run( NamadaRelayer::ValidatorSet(sub_cmd) } }; - rt.block_on(CliApi::::handle_relayer_command( + rt.block_on(CliApi::handle_relayer_command( Some(node), cmd, &TestingIo, diff --git a/benches/lib.rs b/benches/lib.rs index b5036d5f66..f0cba69475 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -810,11 +810,12 @@ impl BenchShieldedCtx { &[], )) .unwrap(); - let namada = NamadaImpl::new( + let namada = NamadaImpl::native_new( &self.shell, &mut self.wallet, &mut self.shielded, &StdIo, + self.shell.wl_storage.storage.native_token.clone(), ); let shielded = async_runtime .block_on( diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 5536f46b63..aecde2d930 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -28,6 +28,7 @@ use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use crate::proto::Tx; use crate::sdk::args::{self, InputAmount, SdkTypes}; use crate::sdk::masp::{ShieldedContext, ShieldedUtils}; +use crate::sdk::rpc::query_native_token; use crate::sdk::signing::{self, SigningTxData}; use crate::sdk::tx::{ self, ProcessTxResponse, TX_BOND_WASM, TX_BRIDGE_POOL_WASM, @@ -86,10 +87,10 @@ pub trait Namada<'a>: Sized { ) -> RwLockWriteGuard<&'a mut ShieldedContext>; /// Return the native token - async fn native_token(&self) -> Address; + fn native_token(&self) -> Address; /// Make a tx builder using no arguments - async fn tx_builder(&self) -> args::Tx { + fn tx_builder(&self) -> args::Tx { args::Tx { dry_run: false, dry_run_wrapper: false, @@ -102,7 +103,7 @@ pub trait Namada<'a>: Sized { wallet_alias_force: false, fee_amount: None, wrapper_fee_payer: None, - fee_token: self.native_token().await, + fee_token: self.native_token(), fee_unshield: None, gas_limit: GasLimit::from(20_000), expiration: None, @@ -117,7 +118,7 @@ pub trait Namada<'a>: Sized { } /// Make a TxTransfer builder from the given minimum set of arguments - async fn new_transfer( + fn new_transfer( &self, source: TransferSource, target: TransferTarget, @@ -130,24 +131,21 @@ pub trait Namada<'a>: Sized { token, amount, tx_code_path: PathBuf::from(TX_TRANSFER_WASM), - tx: self.tx_builder().await, - native_token: self.native_token().await, + tx: self.tx_builder(), + native_token: self.native_token(), } } /// Make a RevealPK builder from the given minimum set of arguments - async fn new_reveal_pk( - &self, - public_key: common::PublicKey, - ) -> args::RevealPk { + fn new_reveal_pk(&self, public_key: common::PublicKey) -> args::RevealPk { args::RevealPk { public_key, - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a Bond builder from the given minimum set of arguments - async fn new_bond( + fn new_bond( &self, validator: Address, amount: token::Amount, @@ -156,14 +154,14 @@ pub trait Namada<'a>: Sized { validator, amount, source: None, - tx: self.tx_builder().await, - native_token: self.native_token().await, + tx: self.tx_builder(), + native_token: self.native_token(), tx_code_path: PathBuf::from(TX_BOND_WASM), } } /// Make a Unbond builder from the given minimum set of arguments - async fn new_unbond( + fn new_unbond( &self, validator: Address, amount: token::Amount, @@ -172,13 +170,13 @@ pub trait Namada<'a>: Sized { validator, amount, source: None, - tx: self.tx_builder().await, + tx: self.tx_builder(), tx_code_path: PathBuf::from(TX_UNBOND_WASM), } } /// Make a TxIbcTransfer builder from the given minimum set of arguments - async fn new_ibc_transfer( + fn new_ibc_transfer( &self, source: Address, receiver: String, @@ -196,41 +194,38 @@ pub trait Namada<'a>: Sized { timeout_height: None, timeout_sec_offset: None, memo: None, - tx: self.tx_builder().await, + tx: self.tx_builder(), tx_code_path: PathBuf::from(TX_IBC_WASM), } } /// Make a InitProposal builder from the given minimum set of arguments - async fn new_init_proposal( - &self, - proposal_data: Vec, - ) -> args::InitProposal { + fn new_init_proposal(&self, proposal_data: Vec) -> args::InitProposal { args::InitProposal { proposal_data, - native_token: self.native_token().await, + native_token: self.native_token(), is_offline: false, is_pgf_stewards: false, is_pgf_funding: false, tx_code_path: PathBuf::from(TX_INIT_PROPOSAL), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a TxUpdateAccount builder from the given minimum set of arguments - async fn new_update_account(&self, addr: Address) -> args::TxUpdateAccount { + fn new_update_account(&self, addr: Address) -> args::TxUpdateAccount { args::TxUpdateAccount { addr, vp_code_path: None, public_keys: vec![], threshold: None, tx_code_path: PathBuf::from(TX_UPDATE_ACCOUNT_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a VoteProposal builder from the given minimum set of arguments - async fn new_vote_prposal( + fn new_vote_prposal( &self, vote: String, voter: Address, @@ -242,13 +237,13 @@ pub trait Namada<'a>: Sized { is_offline: false, proposal_data: None, tx_code_path: PathBuf::from(TX_VOTE_PROPOSAL), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a CommissionRateChange builder from the given minimum set of /// arguments - async fn new_change_commission_rate( + fn new_change_commission_rate( &self, rate: Dec, validator: Address, @@ -257,12 +252,12 @@ pub trait Namada<'a>: Sized { rate, validator, tx_code_path: PathBuf::from(TX_CHANGE_COMMISSION_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a TxInitValidator builder from the given minimum set of arguments - async fn new_init_validator( + fn new_init_validator( &self, commission_rate: Dec, max_commission_rate_change: Dec, @@ -280,34 +275,34 @@ pub trait Namada<'a>: Sized { validator_vp_code_path: PathBuf::from(VP_USER_WASM), unsafe_dont_encrypt: false, tx_code_path: PathBuf::from(TX_INIT_VALIDATOR_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a TxUnjailValidator builder from the given minimum set of arguments - async fn new_unjail_validator( + fn new_unjail_validator( &self, validator: Address, ) -> args::TxUnjailValidator { args::TxUnjailValidator { validator, tx_code_path: PathBuf::from(TX_UNJAIL_VALIDATOR_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a Withdraw builder from the given minimum set of arguments - async fn new_withdraw(&self, validator: Address) -> args::Withdraw { + fn new_withdraw(&self, validator: Address) -> args::Withdraw { args::Withdraw { validator, source: None, tx_code_path: PathBuf::from(TX_WITHDRAW_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a Withdraw builder from the given minimum set of arguments - async fn new_add_erc20_transfer( + fn new_add_erc20_transfer( &self, sender: Address, recipient: EthAddress, @@ -324,28 +319,25 @@ pub trait Namada<'a>: Sized { denom: NATIVE_MAX_DECIMAL_PLACES.into(), }), fee_payer: None, - fee_token: self.native_token().await, + fee_token: self.native_token(), nut: false, code_path: PathBuf::from(TX_BRIDGE_POOL_WASM), - tx: self.tx_builder().await, + tx: self.tx_builder(), } } /// Make a ResignSteward builder from the given minimum set of arguments - async fn new_resign_steward( - &self, - steward: Address, - ) -> args::ResignSteward { + fn new_resign_steward(&self, steward: Address) -> args::ResignSteward { args::ResignSteward { steward, - tx: self.tx_builder().await, + tx: self.tx_builder(), tx_code_path: PathBuf::from(TX_RESIGN_STEWARD), } } /// Make a UpdateStewardCommission builder from the given minimum set of /// arguments - async fn new_update_steward_rewards( + fn new_update_steward_rewards( &self, steward: Address, commission: Vec, @@ -353,16 +345,16 @@ pub trait Namada<'a>: Sized { args::UpdateStewardCommission { steward, commission, - tx: self.tx_builder().await, + tx: self.tx_builder(), tx_code_path: PathBuf::from(TX_UPDATE_STEWARD_COMMISSION), } } /// Make a TxCustom builder from the given minimum set of arguments - async fn new_custom(&self, owner: Address) -> args::TxCustom { + fn new_custom(&self, owner: Address) -> args::TxCustom { args::TxCustom { owner, - tx: self.tx_builder().await, + tx: self.tx_builder(), code_path: None, data_path: None, serialized_tx: None, @@ -405,13 +397,12 @@ where pub shielded: RwLock<&'a mut ShieldedContext>, /// Captures the input/output streams used by this object pub io: &'a I, + /// The address of the native token + native_token: Address, /// The default builder for a Tx prototype: args::Tx, } -/// The Namada token -pub const NAM: &str = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5"; - impl<'a, C, U, V, I> NamadaImpl<'a, C, U, V, I> where C: crate::ledger::queries::Client + Sync, @@ -419,18 +410,20 @@ where V: ShieldedUtils, I: Io, { - /// Construct a new Namada context - pub fn new( + /// Construct a new Namada context with the given native token address + pub fn native_new( client: &'a C, wallet: &'a mut Wallet, shielded: &'a mut ShieldedContext, io: &'a I, + native_token: Address, ) -> Self { - Self { + NamadaImpl { client, wallet: RwLock::new(wallet), shielded: RwLock::new(shielded), io, + native_token: native_token.clone(), prototype: args::Tx { dry_run: false, dry_run_wrapper: false, @@ -443,7 +436,7 @@ where wallet_alias_force: false, fee_amount: None, wrapper_fee_payer: None, - fee_token: Address::from_str(NAM).unwrap(), + fee_token: native_token, fee_unshield: None, gas_limit: GasLimit::from(20_000), expiration: None, @@ -457,6 +450,23 @@ where }, } } + + /// Construct a new Namada context looking up the native token address + pub async fn new( + client: &'a C, + wallet: &'a mut Wallet, + shielded: &'a mut ShieldedContext, + io: &'a I, + ) -> crate::sdk::error::Result> { + let native_token = query_native_token(client).await?; + Ok(NamadaImpl::native_new( + client, + wallet, + shielded, + io, + native_token, + )) + } } #[async_trait::async_trait(?Send)] @@ -473,12 +483,12 @@ where type WalletUtils = U; /// Obtain the prototypical Tx builder - async fn tx_builder(&self) -> args::Tx { + fn tx_builder(&self) -> args::Tx { self.prototype.clone() } - async fn native_token(&self) -> Address { - Address::from_str(NAM).unwrap() + fn native_token(&self) -> Address { + self.native_token.clone() } fn io(&self) -> &'a Self::Io { diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index a766846916..a9f272839f 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -43,6 +43,9 @@ router! {SHELL, // Epoch of the last committed block ( "epoch" ) -> Epoch = epoch, + // The address of the native token + ( "native_token" ) -> Address = native_token, + // Epoch of the input block height ( "epoch_at_height" / [height: BlockHeight]) -> Option = epoch_at_height, @@ -288,6 +291,15 @@ where Ok(data) } +fn native_token(ctx: RequestCtx<'_, D, H>) -> storage_api::Result
+where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let data = ctx.wl_storage.storage.native_token.clone(); + Ok(data) +} + fn epoch_at_height( ctx: RequestCtx<'_, D, H>, height: BlockHeight, diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs index 0853dc251b..d7556d6321 100644 --- a/shared/src/sdk/args.rs +++ b/shared/src/sdk/args.rs @@ -530,7 +530,7 @@ impl InitProposal { e.to_string(), ) })?; - let nam_address = context.native_token().await; + let nam_address = context.native_token(); let author_balance = rpc::get_token_balance( context.client(), &nam_address, @@ -558,7 +558,7 @@ impl InitProposal { e.to_string(), ) })?; - let nam_address = context.native_token().await; + let nam_address = context.native_token(); let author_balance = rpc::get_token_balance( context.client(), &nam_address, diff --git a/shared/src/sdk/rpc.rs b/shared/src/sdk/rpc.rs index eb8ebd8a11..e7da6dcbae 100644 --- a/shared/src/sdk/rpc.rs +++ b/shared/src/sdk/rpc.rs @@ -103,6 +103,13 @@ pub async fn query_epoch( convert_response::(RPC.shell().epoch(client).await) } +/// Query the address of the native token +pub async fn query_native_token( + client: &C, +) -> Result { + convert_response::(RPC.shell().native_token(client).await) +} + /// Query the epoch of the given block height, if it exists. /// Will return none if the input block height is greater than /// the latest committed block height. diff --git a/shared/src/types/io.rs b/shared/src/types/io.rs index f100ca8433..248f6f91d9 100644 --- a/shared/src/types/io.rs +++ b/shared/src/types/io.rs @@ -2,28 +2,26 @@ //! generic IO. The defaults are the obvious Rust native //! functions. -/// Rust native I/O handling. -pub struct StdIo; - +/// A trait that abstracts out I/O operations #[async_trait::async_trait(?Send)] -impl Io for StdIo {} - -#[async_trait::async_trait(?Send)] -#[allow(missing_docs)] pub trait Io { + /// Print the given string fn print(&self, output: impl AsRef) { print!("{}", output.as_ref()); } + /// Flush the output fn flush(&self) { use std::io::Write; std::io::stdout().flush().unwrap(); } + /// Print the given string with a newline fn println(&self, output: impl AsRef) { println!("{}", output.as_ref()); } + /// Print the given string into the given Writer fn write( &self, mut writer: W, @@ -32,6 +30,7 @@ pub trait Io { write!(writer, "{}", output.as_ref()) } + /// Print the given string into the given Writer and terminate with newline fn writeln( &self, mut writer: W, @@ -40,10 +39,12 @@ pub trait Io { writeln!(writer, "{}", output.as_ref()) } + /// Print the given error string fn eprintln(&self, output: impl AsRef) { eprintln!("{}", output.as_ref()); } + /// Read a string from input async fn read(&self) -> std::io::Result { #[cfg(not(target_family = "wasm"))] { @@ -55,6 +56,7 @@ pub trait Io { } } + /// Display the given prompt and return the string input async fn prompt(&self, question: impl AsRef) -> String { #[cfg(not(target_family = "wasm"))] { @@ -76,6 +78,50 @@ pub trait Io { } } +/// Rust native I/O handling. +pub struct StdIo; + +#[async_trait::async_trait(?Send)] +impl Io for StdIo {} + +/// Ignores all I/O operations. +pub struct NullIo; + +#[async_trait::async_trait(?Send)] +impl Io for NullIo { + fn print(&self, _output: impl AsRef) {} + + fn flush(&self) {} + + fn println(&self, _output: impl AsRef) {} + + fn write( + &self, + mut _writer: W, + _output: impl AsRef, + ) -> std::io::Result<()> { + Ok(()) + } + + fn writeln( + &self, + mut _writer: W, + _output: impl AsRef, + ) -> std::io::Result<()> { + Ok(()) + } + + fn eprintln(&self, _output: impl AsRef) {} + + async fn read(&self) -> std::io::Result { + panic!("Unsupported operation") + } + + async fn prompt(&self, _question: impl AsRef) -> String { + panic!("Unsupported operation") + } +} + /// A generic function for displaying a prompt to users and reading /// in their response. #[cfg(not(target_family = "wasm"))]