From 0e3fbc17dc96bbc98f32aa2de24cd39e654a3574 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 22 Aug 2024 00:12:54 -0700 Subject: [PATCH 1/6] add queries for total supply --- crates/apps_lib/src/cli.rs | 117 +++++++++++++++++++++++++++++ crates/apps_lib/src/cli/client.rs | 23 ++++++ crates/apps_lib/src/client/rpc.rs | 26 +++++++ crates/sdk/src/args.rs | 16 ++++ crates/sdk/src/queries/vp/token.rs | 29 ++++--- crates/sdk/src/rpc.rs | 9 +++ 6 files changed, 209 insertions(+), 11 deletions(-) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index c1b4c68534..e1d16d09b2 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -295,6 +295,8 @@ pub mod cmds { .subcommand(QueryCommissionRate::def().display_order(5)) .subcommand(QueryRewards::def().display_order(5)) .subcommand(QueryMetaData::def().display_order(5)) + .subcommand(QueryTotalSupply::def().display_order(5)) + .subcommand(QueryEffNativeSupply::def().display_order(5)) // Actions .subcommand(SignTx::def().display_order(6)) .subcommand(ShieldedSync::def().display_order(6)) @@ -366,6 +368,10 @@ pub mod cmds { let query_rewards = Self::parse_with_ctx(matches, QueryRewards); let query_delegations = Self::parse_with_ctx(matches, QueryDelegations); + let query_total_supply = + Self::parse_with_ctx(matches, QueryTotalSupply); + let query_native_supply = + Self::parse_with_ctx(matches, QueryEffNativeSupply); let query_find_validator = Self::parse_with_ctx(matches, QueryFindValidator); let query_result = Self::parse_with_ctx(matches, QueryResult); @@ -440,6 +446,8 @@ pub mod cmds { .or(query_validator_state) .or(query_commission) .or(query_metadata) + .or(query_total_supply) + .or(query_native_supply) .or(query_account) .or(sign_tx) .or(shielded_sync) @@ -523,6 +531,8 @@ pub mod cmds { QueryMetaData(QueryMetaData), QuerySlashes(QuerySlashes), QueryDelegations(QueryDelegations), + QueryTotalSupply(QueryTotalSupply), + QueryEffNativeSupply(QueryEffNativeSupply), QueryFindValidator(QueryFindValidator), QueryRawBytes(QueryRawBytes), QueryProposal(QueryProposal), @@ -2052,6 +2062,60 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryTotalSupply(pub args::QueryTotalSupply); + + impl SubCmd for QueryTotalSupply { + const CMD: &'static str = "total-supply"; + + fn parse(matches: &ArgMatches) -> Option + where + Self: Sized, + { + matches.subcommand_matches(Self::CMD).map(|matches| { + QueryTotalSupply(args::QueryTotalSupply::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about(wrap!( + "Query the total supply in the network of the given \ + token. For the native token, this will query the raw \ + total supply and not the effective total supply." + )) + .add_args::>() + } + } + + #[derive(Clone, Debug)] + pub struct QueryEffNativeSupply( + pub args::QueryEffNativeSupply, + ); + + impl SubCmd for QueryEffNativeSupply { + const CMD: &'static str = "native-supply"; + + fn parse(matches: &ArgMatches) -> Option + where + Self: Sized, + { + matches.subcommand_matches(Self::CMD).map(|matches| { + QueryEffNativeSupply(args::QueryEffNativeSupply::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about(wrap!( + "Query the total supply in the network of the given \ + token. For the native token, this will query the raw \ + total supply and not the effective total supply." + )) + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct QueryFindValidator(pub args::QueryFindValidator); @@ -6957,6 +7021,59 @@ pub mod args { } } + impl Args for QueryTotalSupply { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + let token = TOKEN.parse(matches); + Self { query, token } + } + + fn def(app: App) -> App { + app.add_args::>() + .arg(TOKEN.def().help(wrap!("The token address."))) + } + } + + impl CliToSdk> for QueryTotalSupply { + type Error = std::convert::Infallible; + + fn to_sdk( + self, + ctx: &mut Context, + ) -> Result, Self::Error> { + Ok(QueryTotalSupply:: { + query: self.query.to_sdk(ctx)?, + token: ctx.borrow_chain_or_exit().get(&self.token), + }) + } + } + + impl Args for QueryEffNativeSupply { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + Self { query } + } + + fn def(app: App) -> App { + app + } + } + + impl CliToSdk> + for QueryEffNativeSupply + { + type Error = std::convert::Infallible; + + fn to_sdk( + self, + ctx: &mut Context, + ) -> Result, Self::Error> { + Ok(QueryEffNativeSupply:: { + query: self.query.to_sdk(ctx)?, + }) + } + } + impl Args for QueryFindValidator { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); diff --git a/crates/apps_lib/src/cli/client.rs b/crates/apps_lib/src/cli/client.rs index 1d9ce01203..6724f24a73 100644 --- a/crates/apps_lib/src/cli/client.rs +++ b/crates/apps_lib/src/cli/client.rs @@ -637,6 +637,29 @@ impl CliApi { let namada = ctx.to_sdk(client, io); rpc::query_delegations(&namada, args).await; } + Sub::QueryTotalSupply(QueryTotalSupply(args)) => { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let ledger_address = + chain_ctx.get(&args.query.ledger_address); + let client = client.unwrap_or_else(|| { + C::from_tendermint_address(&ledger_address) + }); + client.wait_until_node_is_synced(&io).await?; + let args = args.to_sdk(&mut ctx)?; + let namada = ctx.to_sdk(client, io); + rpc::query_total_supply(&namada, args).await; + } + Sub::QueryEffNativeSupply(QueryEffNativeSupply(args)) => { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let ledger_address = + chain_ctx.get(&args.query.ledger_address); + let client = client.unwrap_or_else(|| { + C::from_tendermint_address(&ledger_address) + }); + client.wait_until_node_is_synced(&io).await?; + let namada = ctx.to_sdk(client, io); + rpc::query_effective_native_supply(&namada).await; + } Sub::QueryFindValidator(QueryFindValidator(args)) => { let chain_ctx = ctx.borrow_mut_chain_or_exit(); let ledger_address = diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index ec48adc9ba..8994386afe 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -1176,6 +1176,32 @@ pub async fn query_rewards( ) } +/// Query token total supply. +pub async fn query_total_supply( + context: &N, + args: args::QueryTotalSupply, +) { + let token = args.token; + let supply = unwrap_client_response::( + RPC.vp() + .token() + .total_supply(context.client(), &token) + .await, + ); + display_line!(context.io(), "Total supply of {token}: {supply}"); +} + +/// Query the effective total supply of the native token +pub async fn query_effective_native_supply(context: &N) { + let native_supply = unwrap_client_response::( + RPC.vp() + .token() + .effective_native_supply(context.client()) + .await, + ); + display_line!(context.io(), "{native_supply} NAM"); +} + /// Query a validator's state information pub async fn query_and_print_validator_state( context: &impl Namada, diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index 00bf1675d8..5949a16ba4 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -2220,6 +2220,22 @@ pub struct QueryDelegations { pub owner: C::Address, } +/// Query token total supply +#[derive(Clone, Debug)] +pub struct QueryTotalSupply { + /// Common query args + pub query: Query, + /// Token address + pub token: C::Address, +} + +/// Query effective native supply +#[derive(Clone, Debug)] +pub struct QueryEffNativeSupply { + /// Common query args + pub query: Query, +} + /// Query PoS to find a validator #[derive(Clone, Debug)] pub struct QueryFindValidator { diff --git a/crates/sdk/src/queries/vp/token.rs b/crates/sdk/src/queries/vp/token.rs index 7bae2619a0..3904f1cd85 100644 --- a/crates/sdk/src/queries/vp/token.rs +++ b/crates/sdk/src/queries/vp/token.rs @@ -10,38 +10,45 @@ use namada_token::{ use crate::queries::RequestCtx; router! {TOKEN, - ( "denomination" / [addr: Address] ) -> Option = denomination, - ( "total_supply" / [addr: Address] ) -> token::Amount = total_supply, + ( "denomination" / [token: Address] ) -> Option = denomination, + ( "total_supply" / [token: Address] ) -> token::Amount = total_supply, + ( "effective_native_supply" ) -> token::Amount = effective_native_supply, } /// Get the number of decimal places (in base 10) for a /// token specified by `addr`. fn denomination( ctx: RequestCtx<'_, D, H, V, T>, - addr: Address, + token: Address, ) -> namada_storage::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - read_denom(ctx.state, &addr) + read_denom(ctx.state, &token) } /// Get the total supply for a token address fn total_supply( ctx: RequestCtx<'_, D, H, V, T>, - addr: Address, + token: Address, ) -> namada_storage::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let native_token = ctx.state.in_mem().native_token.clone(); - if addr == native_token { - get_effective_total_native_supply(ctx.state) - } else { - read_total_supply(ctx.state, &addr) - } + read_total_supply(ctx.state, &token) +} + +/// Get the effective total supply of the native token +fn effective_native_supply( + ctx: RequestCtx<'_, D, H, V, T>, +) -> namada_storage::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + get_effective_total_native_supply(ctx.state) } pub mod client_only_methods { diff --git a/crates/sdk/src/rpc.rs b/crates/sdk/src/rpc.rs index 44a2ab554a..96e82e7686 100644 --- a/crates/sdk/src/rpc.rs +++ b/crates/sdk/src/rpc.rs @@ -224,6 +224,15 @@ pub async fn get_token_total_supply( convert_response::(RPC.vp().token().total_supply(client, token).await) } +/// Query the effective total supply of the native token +pub async fn get_effective_native_supply( + client: &C, +) -> Result { + convert_response::( + RPC.vp().token().effective_native_supply(client).await, + ) +} + /// Check if the given address is a known validator. pub async fn is_validator( client: &C, From 46524b8b6efd2274f34188635f2bcc8ff7d507c7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 23 Aug 2024 14:57:28 -0700 Subject: [PATCH 2/6] fix some logging and CLI functionality --- crates/apps_lib/src/cli.rs | 9 +++++---- crates/apps_lib/src/client/rpc.rs | 7 +++++-- crates/tests/src/e2e/setup.rs | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index e1d16d09b2..6781374b97 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -2108,9 +2108,10 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about(wrap!( - "Query the total supply in the network of the given \ - token. For the native token, this will query the raw \ - total supply and not the effective total supply." + "Query the effective total circulating supply of the \ + native token NAM. This excludes illquid NAM tokens held \ + in places such as the PGF account. This is the token \ + amount used in inflation calculations." )) .add_args::>() } @@ -7055,7 +7056,7 @@ pub mod args { } fn def(app: App) -> App { - app + app.add_args::>() } } diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index 8994386afe..5173e0d35e 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -1188,7 +1188,10 @@ pub async fn query_total_supply( .total_supply(context.client(), &token) .await, ); - display_line!(context.io(), "Total supply of {token}: {supply}"); + display_line!( + context.io(), + "Total supply of token {token}: {supply} raw units" + ); } /// Query the effective total supply of the native token @@ -1199,7 +1202,7 @@ pub async fn query_effective_native_supply(context: &N) { .effective_native_supply(context.client()) .await, ); - display_line!(context.io(), "{native_supply} NAM"); + display_line!(context.io(), "nam: {}", native_supply.to_string_native()); } /// Query a validator's state information diff --git a/crates/tests/src/e2e/setup.rs b/crates/tests/src/e2e/setup.rs index e3fa45c778..0e5f6db696 100644 --- a/crates/tests/src/e2e/setup.rs +++ b/crates/tests/src/e2e/setup.rs @@ -1469,6 +1469,7 @@ pub mod constants { // Native VP aliases pub const GOVERNANCE_ADDRESS: &str = "governance"; pub const MASP: &str = "masp"; + pub const PGF_ADDRESS: &str = "pgf"; // Fungible token addresses pub const NAM: &str = "NAM"; From 97bb9f3a67dbc8cd1edf8375102264d63030a117 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 23 Aug 2024 14:57:34 -0700 Subject: [PATCH 3/6] testing --- crates/tests/src/integration/ledger_tests.rs | 41 +++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/crates/tests/src/integration/ledger_tests.rs b/crates/tests/src/integration/ledger_tests.rs index fdd8d7233c..b4172a77e0 100644 --- a/crates/tests/src/integration/ledger_tests.rs +++ b/crates/tests/src/integration/ledger_tests.rs @@ -32,7 +32,8 @@ use test_log::test; use crate::e2e::ledger_tests::prepare_proposal_data; use crate::e2e::setup::constants::{ ALBERT, ALBERT_KEY, APFEL, BERTHA, BERTHA_KEY, BTC, CHRISTEL, CHRISTEL_KEY, - DAEWON, DOT, ESTER, ETH, GOVERNANCE_ADDRESS, KARTOFFEL, NAM, SCHNITZEL, + DAEWON, DOT, ESTER, ETH, GOVERNANCE_ADDRESS, KARTOFFEL, NAM, PGF_ADDRESS, + SCHNITZEL, }; use crate::e2e::setup::{apply_use_device, ensure_hot_key}; use crate::integration::helpers::{ @@ -1155,6 +1156,44 @@ fn pgf_governance_proposal() -> Result<()> { ); assert!(captured.contains("Pgf fundings: no fundings are currently set.")); + // 7.1 Query total NAM supply and PGF balance + let query_balance_args = vec![ + "balance", + "--owner", + PGF_ADDRESS, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 13.785266")); + + let query_total_supply_args = vec![ + "total-supply", + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_total_supply_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains( + "token tnam1q9kn74xfzytqkqyycfrhycr8ajam8ny935cge0z5: 114400023904507 \ + raw units" + )); + + let query_native_supply_args = + vec!["native-supply", "--ledger-address", &validator_one_rpc]; + let captured = CapturedOutput::of(|| { + run(&node, Bin::Client, query_native_supply_args) + }); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 114400010.119241")); + // 8. Submit proposal funding let albert = defaults::albert_address(); let bertha = defaults::bertha_address(); From f8c679abe897e8908bf5a394651efe46bd9aedd1 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 23 Aug 2024 14:58:46 -0700 Subject: [PATCH 4/6] changelog: add #3691 --- .changelog/unreleased/improvements/3691-cli-commands.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/3691-cli-commands.md diff --git a/.changelog/unreleased/improvements/3691-cli-commands.md b/.changelog/unreleased/improvements/3691-cli-commands.md new file mode 100644 index 0000000000..50d93a4961 --- /dev/null +++ b/.changelog/unreleased/improvements/3691-cli-commands.md @@ -0,0 +1,3 @@ +- Include some CLI commands for querying the total supply of any token + and the effective total circulating supply of the native token. + ([\#3691](https://github.com/anoma/namada/pull/3691)) \ No newline at end of file From 526c26105300f81099ea0d5fe926e2fedc0aaaeb Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 26 Aug 2024 16:31:22 -0700 Subject: [PATCH 5/6] format denominated amount --- crates/apps_lib/src/client/rpc.rs | 13 +++++++++++-- crates/tests/src/integration/ledger_tests.rs | 3 +-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index 5173e0d35e..ab280fb4ff 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -36,7 +36,8 @@ use namada_sdk::proof_of_stake::types::{ use namada_sdk::proof_of_stake::PosParams; use namada_sdk::queries::{Client, RPC}; use namada_sdk::rpc::{ - self, enriched_bonds_and_unbonds, query_epoch, TxResponse, + self, enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, + TxResponse, }; use namada_sdk::storage::BlockResults; use namada_sdk::tendermint_rpc::endpoint::status; @@ -1188,9 +1189,17 @@ pub async fn query_total_supply( .total_supply(context.client(), &token) .await, ); + let amount_str = format_denominated_amount( + context.client(), + context.io(), + &token, + supply, + ) + .await; display_line!( context.io(), - "Total supply of token {token}: {supply} raw units" + "Total supply of token {token}: {}", + amount_str ); } diff --git a/crates/tests/src/integration/ledger_tests.rs b/crates/tests/src/integration/ledger_tests.rs index b4172a77e0..65c0862061 100644 --- a/crates/tests/src/integration/ledger_tests.rs +++ b/crates/tests/src/integration/ledger_tests.rs @@ -1182,8 +1182,7 @@ fn pgf_governance_proposal() -> Result<()> { CapturedOutput::of(|| run(&node, Bin::Client, query_total_supply_args)); assert_matches!(captured.result, Ok(_)); assert!(captured.contains( - "token tnam1q9kn74xfzytqkqyycfrhycr8ajam8ny935cge0z5: 114400023904507 \ - raw units" + "token tnam1q9kn74xfzytqkqyycfrhycr8ajam8ny935cge0z5: 114400023.904507" )); let query_native_supply_args = From f57a1a9a0e2cfbf988b48a72218ae3aced0d3195 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 27 Aug 2024 17:01:04 -0700 Subject: [PATCH 6/6] better error handling for sdk query in client --- crates/apps_lib/src/client/rpc.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index ab280fb4ff..a642bd4856 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -1183,11 +1183,8 @@ pub async fn query_total_supply( args: args::QueryTotalSupply, ) { let token = args.token; - let supply = unwrap_client_response::( - RPC.vp() - .token() - .total_supply(context.client(), &token) - .await, + let supply = unwrap_sdk_result( + rpc::get_token_total_supply(context.client(), &token).await, ); let amount_str = format_denominated_amount( context.client(), @@ -2046,6 +2043,14 @@ fn unwrap_client_response( }) } +/// A helper to unwrap an SDK query result. Will shut down process on error. +fn unwrap_sdk_result(response: Result) -> T { + response.unwrap_or_else(|err| { + eprintln!("Error in the query: {:?}", err); + cli::safe_exit(1) + }) +} + pub async fn compute_proposal_votes( client: &C, proposal_id: u64,