From d94ec0596180d1d15cc8a5057ab4a580cc24a8e3 Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Tue, 17 Dec 2024 16:34:12 -0600 Subject: [PATCH 01/17] [pos] Add epoch argument to vp::pos::rewards --- crates/apps_lib/src/cli.rs | 8 ++++++++ crates/apps_lib/src/client/rpc.rs | 17 +++++++++++++---- crates/sdk/src/args.rs | 5 +++++ crates/sdk/src/queries/vp/pos.rs | 18 +++++++++--------- crates/sdk/src/rpc.rs | 19 +++++++++++-------- 5 files changed, 46 insertions(+), 21 deletions(-) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index 90a1066b98..bb3a197a9a 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -7309,6 +7309,7 @@ pub mod args { query: self.query.to_sdk(ctx)?, validator: ctx.borrow_chain_or_exit().get(&self.validator), source: self.source.map(|x| ctx.borrow_chain_or_exit().get(&x)), + epoch: self.epoch, }) } } @@ -7318,10 +7319,12 @@ pub mod args { let query = Query::parse(matches); let source = SOURCE_OPT.parse(matches); let validator = VALIDATOR.parse(matches); + let epoch = EPOCH.parse(matches); Self { query, source, validator, + epoch, } } @@ -7336,6 +7339,10 @@ pub mod args { "Validator address for the rewards query." )), ) + .arg(EPOCH.def().help(wrap!( + "The epoch at which to query (corresponding to the last \ + committed block, if not specified)." + ))) } } @@ -7562,6 +7569,7 @@ pub mod args { type Address = WalletAddress; type BalanceOwner = WalletBalanceOwner; type BlockHeight = BlockHeight; + type Epoch = Epoch; type BpConversionTable = PathBuf; type ConfigRpcTendermintAddress = ConfigRpcAddress; type Data = PathBuf; diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index 22e1cafd62..b324bc7d69 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -1503,8 +1503,11 @@ pub async fn query_rewards( client: &C, source: &Option
, validator: &Address, + epoch: &Option, ) -> token::Amount { - unwrap_sdk_result(rpc::query_rewards(client, source, validator).await) + unwrap_sdk_result( + rpc::query_rewards(client, source, validator, epoch).await, + ) } /// Query token total supply. @@ -1920,12 +1923,18 @@ pub async fn query_and_print_rewards( context: &N, args: args::QueryRewards, ) { - let (source, validator) = (args.source, args.validator); + let (source, validator, epoch) = (args.source, args.validator, args.epoch); - let rewards = query_rewards(context.client(), &source, &validator).await; + let rewards = + query_rewards(context.client(), &source, &validator, &epoch).await; display_line!( context.io(), - "Current rewards available for claim: {} NAM", + "{}: {} NAM", + epoch + .map(|e| format!("Rewards for epoch {}", e)) + .unwrap_or_else( + || "Current rewards available for claim".to_string() + ), rewards.to_string_native() ); } diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index 754633a36c..9d8500171b 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -101,6 +101,8 @@ pub trait NamadaTypes: Clone + std::fmt::Debug { type MaspIndexerAddress: Clone + std::fmt::Debug; /// Represents a block height type BlockHeight: Clone + std::fmt::Debug; + /// Represents an epoch + type Epoch: Clone + std::fmt::Debug; } /// The concrete types being used in Namada SDK @@ -123,6 +125,7 @@ impl NamadaTypes for SdkTypes { type Address = Address; type BalanceOwner = namada_core::masp::BalanceOwner; type BlockHeight = namada_core::chain::BlockHeight; + type Epoch = namada_core::chain::Epoch; type BpConversionTable = HashMap; type ConfigRpcTendermintAddress = tendermint_rpc::Url; type Data = Vec; @@ -2545,6 +2548,8 @@ pub struct QueryRewards { pub source: Option, /// Address of the validator pub validator: C::Address, + /// Epoch in which to find rewards + pub epoch: Option, } /// Query PoS delegations diff --git a/crates/sdk/src/queries/vp/pos.rs b/crates/sdk/src/queries/vp/pos.rs index 5fcf4e06fd..0403850591 100644 --- a/crates/sdk/src/queries/vp/pos.rs +++ b/crates/sdk/src/queries/vp/pos.rs @@ -103,7 +103,7 @@ router! {POS, ( "bond" / [source: Address] / [validator: Address] / [epoch: opt Epoch] ) -> token::Amount = bond, - ( "rewards" / [validator: Address] / [source: opt Address] ) + ( "rewards" / [validator: Address] / [source: opt Address] / [epoch: opt Epoch] ) -> token::Amount = rewards, ( "bond_with_slashing" / [source: Address] / [validator: Address] / [epoch: opt Epoch] ) @@ -588,17 +588,19 @@ fn rewards( ctx: RequestCtx<'_, D, H, V, T>, validator: Address, source: Option
, + epoch: Option, ) -> namada_storage::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let current_epoch = ctx.state.in_mem().last_epoch; + let epoch = epoch.unwrap_or(ctx.state.in_mem().last_epoch); + query_reward_tokens::<_, governance::Store<_>>( ctx.state, source.as_ref(), &validator, - current_epoch, + epoch, ) } @@ -861,11 +863,9 @@ mod test { }; let result = POS.handle(ctx, &request); assert!(result.is_err()); - assert!( - result - .unwrap_err() - .to_string() - .contains("Invalid Tendermint address") - ) + assert!(result + .unwrap_err() + .to_string() + .contains("Invalid Tendermint address")) } } diff --git a/crates/sdk/src/rpc.rs b/crates/sdk/src/rpc.rs index 362048a564..c7e0ef1466 100644 --- a/crates/sdk/src/rpc.rs +++ b/crates/sdk/src/rpc.rs @@ -872,9 +872,13 @@ pub async fn query_rewards( client: &C, source: &Option
, validator: &Address, + epoch: &Option, ) -> Result { convert_response::( - RPC.vp().pos().rewards(client, validator, source).await, + RPC.vp() + .pos() + .rewards(client, validator, source, epoch) + .await, ) } @@ -1682,16 +1686,15 @@ pub async fn osmosis_denom_from_namada_denom( if nam_denom.trace_path.is_empty() { // Namada native asset - let address = nam_denom - .base_denom - .as_str() - .parse::
() - .map_err(|err| { - Error::Encode(EncodingError::Decoding(format!( + let address = + nam_denom.base_denom.as_str().parse::
().map_err( + |err| { + Error::Encode(EncodingError::Decoding(format!( "Failed to parse base denom {} as Namada address: {err}", nam_denom.base_denom ))) - })?; + }, + )?; // validate that the base denom is not another ibc token if matches!(&address, Address::Internal(InternalAddress::IbcToken(_))) { From abcdefa9a20bd960b9d6d4f5f68beb5b0d4a56ca Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Wed, 18 Dec 2024 18:15:12 -0600 Subject: [PATCH 02/17] [pos] when querying rewards by epoch, use the historical rewards_counter value --- crates/sdk/src/queries/vp/pos.rs | 69 ++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/crates/sdk/src/queries/vp/pos.rs b/crates/sdk/src/queries/vp/pos.rs index 0403850591..23e71314c3 100644 --- a/crates/sdk/src/queries/vp/pos.rs +++ b/crates/sdk/src/queries/vp/pos.rs @@ -13,6 +13,7 @@ use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::queries::{ find_delegation_validators, find_delegations, }; +use namada_proof_of_stake::rewards::read_rewards_counter; use namada_proof_of_stake::slashing::{ find_all_enqueued_slashes, find_all_slashes, }; @@ -34,9 +35,9 @@ use namada_proof_of_stake::types::{ WeightedValidator, }; use namada_proof_of_stake::{bond_amount, query_reward_tokens}; -use namada_state::{DBIter, KeySeg, StorageHasher, DB}; +use namada_state::{DBIter, KeySeg, StorageHasher, StorageRead, DB}; use namada_storage::collections::lazy_map; -use namada_storage::OptionExt; +use namada_storage::{OptionExt, ResultExt}; use crate::governance; use crate::queries::types::RequestCtx; @@ -594,14 +595,68 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let epoch = epoch.unwrap_or(ctx.state.in_mem().last_epoch); - - query_reward_tokens::<_, governance::Store<_>>( + let reward_tokens = query_reward_tokens::<_, governance::Store<_>>( ctx.state, source.as_ref(), &validator, - epoch, - ) + epoch.unwrap_or(ctx.state.in_mem().last_epoch), + )?; + + match epoch { + None => Ok(reward_tokens), + Some(epoch) => { + // When querying by epoch, since query_reward_tokens includes rewards_counter not + // based on epoch, we need to subtract it and instead add the rewards_counter from + // the height of the epoch we are querying. + let source = source.unwrap_or_else(|| validator.clone()); + let rewards_counter_last_epoch = + read_rewards_counter(ctx.state, &source, &validator)?; + + let rewards_counter_at_epoch = { + // Choose the height at the end of the epoch in order to maximize the chance of + // finding the storage without it being pruned. + let height = if epoch == ctx.state.in_mem().last_epoch { + ctx.state.in_mem().get_last_block_height() + } else { + ctx.state + .get_epoch_start_height( + epoch.checked_add(1).unwrap_or_default(), + )? + .ok_or(namada_storage::Error::new_const( + "Epoch not found", + ))? + .checked_sub(1) + .unwrap_or_default() + }; + + let storage_key = + namada_proof_of_stake::storage_key::rewards_counter_key( + &source, &validator, + ); + + let storage_value = ctx + .state + .db_read_with_height(&storage_key, height) + .into_storage_result()?; + + storage_value + .0 + .map(|bytes| { + token::Amount::try_from_slice(&bytes) + .into_storage_result() + }) + .transpose()? + .unwrap_or_default() + }; + + // Add before subtracting because Amounts are unsigned + checked!( + reward_tokens + rewards_counter_at_epoch + - rewards_counter_last_epoch + ) + .into_storage_result() + } + } } fn bonds_and_unbonds( From e1cf91934b9675fe6e9bb375ac78213cc74f5bc8 Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Wed, 18 Dec 2024 22:40:38 -0600 Subject: [PATCH 03/17] refactor: use existing storage_value query function for looking up past rewards_counter --- crates/sdk/src/queries/shell.rs | 2 +- crates/sdk/src/queries/vp/pos.rs | 83 ++++++++++++++++++-------------- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/crates/sdk/src/queries/shell.rs b/crates/sdk/src/queries/shell.rs index f515296404..0d6abd13f7 100644 --- a/crates/sdk/src/queries/shell.rs +++ b/crates/sdk/src/queries/shell.rs @@ -429,7 +429,7 @@ where /// borsh-encoded types, it is safe to check `data.is_empty()` to see if the /// value was found, except for unit - see `fn query_storage_value` in /// `apps/src/lib/client/rpc.rs` for unit type handling via `storage_has_key`. -fn storage_value( +pub fn storage_value( ctx: RequestCtx<'_, D, H, V, T>, request: &RequestQuery, storage_key: storage::Key, diff --git a/crates/sdk/src/queries/vp/pos.rs b/crates/sdk/src/queries/vp/pos.rs index 23e71314c3..77691e4404 100644 --- a/crates/sdk/src/queries/vp/pos.rs +++ b/crates/sdk/src/queries/vp/pos.rs @@ -41,6 +41,7 @@ use namada_storage::{OptionExt, ResultExt}; use crate::governance; use crate::queries::types::RequestCtx; +use crate::queries::{shell, RequestQuery}; // PoS validity predicate queries router! {POS, @@ -612,42 +613,8 @@ where let rewards_counter_last_epoch = read_rewards_counter(ctx.state, &source, &validator)?; - let rewards_counter_at_epoch = { - // Choose the height at the end of the epoch in order to maximize the chance of - // finding the storage without it being pruned. - let height = if epoch == ctx.state.in_mem().last_epoch { - ctx.state.in_mem().get_last_block_height() - } else { - ctx.state - .get_epoch_start_height( - epoch.checked_add(1).unwrap_or_default(), - )? - .ok_or(namada_storage::Error::new_const( - "Epoch not found", - ))? - .checked_sub(1) - .unwrap_or_default() - }; - - let storage_key = - namada_proof_of_stake::storage_key::rewards_counter_key( - &source, &validator, - ); - - let storage_value = ctx - .state - .db_read_with_height(&storage_key, height) - .into_storage_result()?; - - storage_value - .0 - .map(|bytes| { - token::Amount::try_from_slice(&bytes) - .into_storage_result() - }) - .transpose()? - .unwrap_or_default() - }; + let rewards_counter_at_epoch = + get_rewards_counter_at_epoch(ctx, &source, &validator, epoch)?; // Add before subtracting because Amounts are unsigned checked!( @@ -659,6 +626,50 @@ where } } +fn get_rewards_counter_at_epoch( + ctx: RequestCtx<'_, D, H, V, T>, + source: &Address, + validator: &Address, + epoch: Epoch, +) -> namada_storage::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + // Do this first so that we return an error if an invalid epoch is requested + let queried_height = ctx + .state + .get_epoch_start_height(epoch)? + .ok_or(namada_storage::Error::new_const("Epoch not found"))?; + + let storage_key = namada_proof_of_stake::storage_key::rewards_counter_key( + source, validator, + ); + + // Shortcut: avoid costly lookup of non-existent storage key in history + // by first checking to see if it currently exists in memory before + // querying by height. + // TODO: this might not be valid if rewards have been claimed in the current epoch? + // (because the rewards_counter would be removed from storage until the next epoch) + if !ctx.state.has_key(&storage_key)? { + return Ok(token::Amount::from(0)); + } + + let query = RequestQuery { + height: queried_height.try_into().into_storage_result()?, + prove: false, + data: Default::default(), + path: Default::default(), + }; + + let value = shell::storage_value(ctx, &query, storage_key)?; + if value.data.is_empty() { + Ok(token::Amount::from(0)) + } else { + token::Amount::try_from_slice(&value.data).into_storage_result() + } +} + fn bonds_and_unbonds( ctx: RequestCtx<'_, D, H, V, T>, source: Option
, From a6257eb3a6420e114ddec44da198a72bc45fa457 Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Wed, 18 Dec 2024 23:03:41 -0600 Subject: [PATCH 04/17] cargo fmt --- crates/apps_lib/src/cli.rs | 2 +- crates/sdk/src/args.rs | 2 +- crates/sdk/src/queries/vp/pos.rs | 20 ++++++++++++-------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index bb3a197a9a..605d026846 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -7569,12 +7569,12 @@ pub mod args { type Address = WalletAddress; type BalanceOwner = WalletBalanceOwner; type BlockHeight = BlockHeight; - type Epoch = Epoch; type BpConversionTable = PathBuf; type ConfigRpcTendermintAddress = ConfigRpcAddress; type Data = PathBuf; type DatedSpendingKey = WalletDatedSpendingKey; type DatedViewingKey = WalletDatedViewingKey; + type Epoch = Epoch; type EthereumAddress = String; type Keypair = WalletKeypair; type MaspIndexerAddress = String; diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index 9d8500171b..8fc34f905f 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -125,12 +125,12 @@ impl NamadaTypes for SdkTypes { type Address = Address; type BalanceOwner = namada_core::masp::BalanceOwner; type BlockHeight = namada_core::chain::BlockHeight; - type Epoch = namada_core::chain::Epoch; type BpConversionTable = HashMap; type ConfigRpcTendermintAddress = tendermint_rpc::Url; type Data = Vec; type DatedSpendingKey = DatedSpendingKey; type DatedViewingKey = DatedViewingKey; + type Epoch = namada_core::chain::Epoch; type EthereumAddress = (); type Keypair = namada_core::key::common::SecretKey; type MaspIndexerAddress = String; diff --git a/crates/sdk/src/queries/vp/pos.rs b/crates/sdk/src/queries/vp/pos.rs index 77691e4404..07f3d908e2 100644 --- a/crates/sdk/src/queries/vp/pos.rs +++ b/crates/sdk/src/queries/vp/pos.rs @@ -606,8 +606,9 @@ where match epoch { None => Ok(reward_tokens), Some(epoch) => { - // When querying by epoch, since query_reward_tokens includes rewards_counter not - // based on epoch, we need to subtract it and instead add the rewards_counter from + // When querying by epoch, since query_reward_tokens includes + // rewards_counter not based on epoch, we need to + // subtract it and instead add the rewards_counter from // the height of the epoch we are querying. let source = source.unwrap_or_else(|| validator.clone()); let rewards_counter_last_epoch = @@ -649,8 +650,9 @@ where // Shortcut: avoid costly lookup of non-existent storage key in history // by first checking to see if it currently exists in memory before // querying by height. - // TODO: this might not be valid if rewards have been claimed in the current epoch? - // (because the rewards_counter would be removed from storage until the next epoch) + // TODO: this might not be valid if rewards have been claimed in the current + // epoch? (because the rewards_counter would be removed from + // storage until the next epoch) if !ctx.state.has_key(&storage_key)? { return Ok(token::Amount::from(0)); } @@ -929,9 +931,11 @@ mod test { }; let result = POS.handle(ctx, &request); assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("Invalid Tendermint address")) + assert!( + result + .unwrap_err() + .to_string() + .contains("Invalid Tendermint address") + ) } } From bd78cbab294b811f0f739423c41e11d21bb596ec Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Wed, 18 Dec 2024 23:07:45 -0600 Subject: [PATCH 05/17] slight wording change: for -> at to make it more clear that rewards are as of beginning of epoch --- crates/apps_lib/src/client/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index b324bc7d69..5338376f44 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -1931,7 +1931,7 @@ pub async fn query_and_print_rewards( context.io(), "{}: {} NAM", epoch - .map(|e| format!("Rewards for epoch {}", e)) + .map(|e| format!("Rewards at epoch {}", e)) .unwrap_or_else( || "Current rewards available for claim".to_string() ), From 1f5dd5ab29b4b90fa235a4d24b491a0786752b81 Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Wed, 18 Dec 2024 23:19:11 -0600 Subject: [PATCH 06/17] refactor: Amount::from(0) -> Amount::zero() --- crates/sdk/src/queries/vp/pos.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/sdk/src/queries/vp/pos.rs b/crates/sdk/src/queries/vp/pos.rs index 07f3d908e2..05e6f545c1 100644 --- a/crates/sdk/src/queries/vp/pos.rs +++ b/crates/sdk/src/queries/vp/pos.rs @@ -654,7 +654,7 @@ where // epoch? (because the rewards_counter would be removed from // storage until the next epoch) if !ctx.state.has_key(&storage_key)? { - return Ok(token::Amount::from(0)); + return Ok(token::Amount::zero()); } let query = RequestQuery { @@ -666,7 +666,7 @@ where let value = shell::storage_value(ctx, &query, storage_key)?; if value.data.is_empty() { - Ok(token::Amount::from(0)) + Ok(token::Amount::zero()) } else { token::Amount::try_from_slice(&value.data).into_storage_result() } From 7937b91f6b7b2186c95f7065f22c14b6b98848fe Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Wed, 18 Dec 2024 23:30:00 -0600 Subject: [PATCH 07/17] changelog: add --- .changelog/unreleased/SDK/4196-rewards-at-past-epoch.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/SDK/4196-rewards-at-past-epoch.md diff --git a/.changelog/unreleased/SDK/4196-rewards-at-past-epoch.md b/.changelog/unreleased/SDK/4196-rewards-at-past-epoch.md new file mode 100644 index 0000000000..d312fb757f --- /dev/null +++ b/.changelog/unreleased/SDK/4196-rewards-at-past-epoch.md @@ -0,0 +1,2 @@ +- Allow querying PoS rewards at a specified epoch + ([\#4196](https://github.com/anoma/namada/pull/4196)) \ No newline at end of file From cb29c9d3974e601202ad871337f1e5ab03ebb518 Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Thu, 23 Jan 2025 16:05:25 -0600 Subject: [PATCH 08/17] [vscode] Add namada_sdk unit test debug launch definition --- .vscode/launch.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 7c13fc3f35..f8984e9e0d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -229,6 +229,25 @@ "args": [], "cwd": "${workspaceFolder}" }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'namada_sdk'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=namada_sdk" + ], + "filter": { + "name": "namada_sdk", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, { "type": "lldb", "request": "launch", From 3c47541591b8e401432101c1ed84703f8d36bf9a Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Thu, 23 Jan 2025 17:07:27 -0600 Subject: [PATCH 09/17] enhancement: implement read_subspace_val_with_height for mockdb --- crates/storage/src/mockdb.rs | 39 +++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/crates/storage/src/mockdb.rs b/crates/storage/src/mockdb.rs index 272e177923..6b0161b4e8 100644 --- a/crates/storage/src/mockdb.rs +++ b/crates/storage/src/mockdb.rs @@ -381,14 +381,39 @@ impl DB for MockDB { fn read_subspace_val_with_height( &self, key: &Key, - _height: BlockHeight, - _last_height: BlockHeight, + height: BlockHeight, + last_height: BlockHeight, ) -> Result>> { - tracing::warn!( - "read_subspace_val_with_height is not implemented, will read \ - subspace value from latest height" - ); - self.read_subspace_val(key) + if height == last_height { + self.read_subspace_val(key) + } else { + // Quick-n-dirty implementation for reading subspace value at height: + // - See if there are any diffs between height+1..last_height. + // - If so, the first one will provide the value we want as its old value. + // - If not, we can just read the value at the latest height. + for h in (height.0 + 1)..=last_height.0 { + let old_diff = self.read_diffs_val(key, h.into(), true)?; + let new_diff = self.read_diffs_val(key, h.into(), false)?; + + match (old_diff, new_diff) { + (Some(old_diff), Some(_)) | (Some(old_diff), None) => { + // If there is an old diff, it contains the value at the requested height. + return Ok(Some(old_diff)); + } + (None, Some(_)) => { + // If there is a new diff but no old diff, there was + // no value at the requested height. + return Ok(None); + } + (None, None) => { + // If there are no diffs, keep looking. + continue; + } + } + } + + self.read_subspace_val(key) + } } fn write_subspace_val( From 8cb563ff13cece635d8eb4fe7c757c970b497d88 Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Thu, 23 Jan 2025 12:15:04 -0600 Subject: [PATCH 10/17] [pos] Add unit test for vp::pos::rewards query with epochs --- Cargo.lock | 1 + crates/sdk/Cargo.toml | 1 + crates/sdk/src/queries/vp/pos.rs | 316 ++++++++++++++++++++++++++++++- 3 files changed, 311 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 77940026d3..8b63dded37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5541,6 +5541,7 @@ dependencies = [ "namada_state", "namada_storage", "namada_token", + "namada_trans_token", "namada_tx", "namada_vm", "namada_vote_ext", diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index f81e052373..204c3c340f 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -162,6 +162,7 @@ namada_proof_of_stake = { path = "../proof_of_stake", default-features = false, namada_state = { path = "../state", features = ["testing"] } namada_storage = { path = "../storage", features = ["testing"] } namada_token = { path = "../token", features = ["testing", "masp"] } +namada_trans_token = { path = "../trans_token" } namada_tx = { path = "../tx", features = ["testing"]} namada_vm = { path = "../vm" } namada_vote_ext = { path = "../vote_ext" } diff --git a/crates/sdk/src/queries/vp/pos.rs b/crates/sdk/src/queries/vp/pos.rs index 05e6f545c1..135a13b8a4 100644 --- a/crates/sdk/src/queries/vp/pos.rs +++ b/crates/sdk/src/queries/vp/pos.rs @@ -904,7 +904,11 @@ fn enrich_bonds_and_unbonds( mod test { use super::*; use crate::queries::testing::TestClient; - use crate::queries::{RequestCtx, RequestQuery, Router}; + use crate::queries::{RequestCtx, RequestQuery, Router, RPC}; + use namada_core::chain::Epoch; + use namada_core::token; + use namada_core::{address, storage}; + use namada_state::StorageWrite; #[tokio::test] async fn test_validator_by_tm_addr_sanitized_input() { @@ -931,11 +935,309 @@ mod test { }; let result = POS.handle(ctx, &request); assert!(result.is_err()); - assert!( - result - .unwrap_err() - .to_string() - .contains("Invalid Tendermint address") - ) + assert!(result + .unwrap_err() + .to_string() + .contains("Invalid Tendermint address")) + } + + // Helpers for test_rewards_query + mod helpers { + use super::*; + + pub fn init_validator( + client: &mut TestClient, + ) -> (Address, namada_proof_of_stake::PosParams) { + let genesis_validator = + namada_proof_of_stake::test_utils::get_dummy_genesis_validator( + ); + let validator_address = genesis_validator.address.clone(); + + let params = + namada_proof_of_stake::test_utils::test_init_genesis::< + _, + namada_parameters::Store<_>, + governance::Store<_>, + namada_trans_token::Store<_>, + >( + &mut client.state, + namada_proof_of_stake::OwnedPosParams::default(), + std::iter::once(genesis_validator), + Epoch(0), + ) + .expect("Test initialization failed"); + + (validator_address, params) + } + + pub fn setup_delegator( + client: &mut TestClient, + validator_address: &Address, + bond_amount: token::Amount, + ) -> Address { + let delegator = address::testing::established_address_2(); + + // Credit tokens to delegator + let native_token = client.state.get_native_token().unwrap(); + StorageWrite::write( + &mut client.state, + &storage::Key::from( + namada_trans_token::storage_key::balance_key( + &native_token, + &delegator, + ), + ), + bond_amount, + ) + .expect("Credit tokens failed"); + + // Bond tokens from delegator to validator + namada_proof_of_stake::bond_tokens::< + _, + governance::Store<_>, + namada_trans_token::Store<_>, + >( + &mut client.state, + Some(&delegator), + validator_address, + bond_amount, + Epoch(1), + Some(0), + ) + .expect("Bonding tokens failed"); + + delegator + } + + pub fn init_state( + client: &mut TestClient, + bond_amount: token::Amount, + ) -> (Address, Address, token::Amount) { + let (validator, _params) = init_validator(client); + let delegator = setup_delegator(client, &validator, bond_amount); + + // Initialize the predecessor epochs + client + .state + .in_mem_mut() + .block + .pred_epochs + .new_epoch(0.into()); + + (validator, delegator, bond_amount) + } + + pub fn advance_epoch( + client: &mut TestClient, + validator_delegator: &(Address, Address), + reward: &(Option, Option), + ) -> Epoch { + let (validator, delegator) = validator_delegator; + let (validator_reward, delegator_reward) = reward; + let current_epoch = client.state.in_mem().last_epoch; + let next_epoch = current_epoch.next(); + let height = client.state.in_mem().block.height; + + // Advance block height and epoch + let next_height = height + 1; + client + .state + .in_mem_mut() + .begin_block(next_height) + .expect("Test failed"); + client.state.in_mem_mut().block.epoch = next_epoch; + client.state.in_mem_mut().block.height = next_height; + client + .state + .in_mem_mut() + .block + .pred_epochs + .new_epoch(next_height); + + // Add rewards + if let Some(rewards_amount) = delegator_reward { + namada_proof_of_stake::rewards::add_rewards_to_counter( + &mut client.state, + delegator, + validator, + *rewards_amount, + ) + .expect("Adding delegator rewards failed"); + } + + if let Some(rewards_amount) = validator_reward { + namada_proof_of_stake::rewards::add_rewards_to_counter( + &mut client.state, + validator, + validator, + *rewards_amount, + ) + .expect("Adding validator rewards failed"); + } + + client.state.commit_block().expect("Test failed"); + + next_epoch + } + } + + #[tokio::test] + async fn test_rewards_query() { + // Initialize test client + let mut client = TestClient::new(RPC); + + // We will be reusing this route frequently, so alias it here + let pos = RPC.vp().pos(); + + // Set up validator + let (validator, delegator, _params) = + helpers::init_state(&mut client, token::Amount::native_whole(100)); + + let bond = (validator.clone(), delegator.clone()); + let reward = (None, None); + + // Advance to next epoch without rewards + let epoch = helpers::advance_epoch(&mut client, &bond, &reward); + assert_eq!(epoch, Epoch(1)); + + // Test querying rewards (should be 0) + let result = pos + .rewards(&client, &validator, &Some(delegator.clone()), &None) + .await + .expect("Rewards query failed"); + assert_eq!(result, token::Amount::zero()); + + let result = pos + .rewards( + &client, + &validator, + &Some(delegator.clone()), + &Some(epoch), + ) + .await + .expect("Rewards query failed"); + assert_eq!(result, token::Amount::zero()); + + let result = pos + .rewards(&client, &validator, &None, &None) + .await + .expect("Rewards query failed"); + assert_eq!(result, token::Amount::zero()); + + // Advance to next epoch with some rewards + let val_reward_epoch_2 = token::Amount::native_whole(5); + let del_reward_epoch_2 = token::Amount::native_whole(7); + let reward_epoch_2 = + (Some(val_reward_epoch_2), Some(del_reward_epoch_2)); + let epoch = helpers::advance_epoch(&mut client, &bond, &reward_epoch_2); + assert_eq!(epoch, Epoch(2)); + + // Query latest rewards for delegator + let result = pos + .rewards(&client, &validator, &Some(delegator.clone()), &None) + .await + .expect("Rewards query failed"); + assert_eq!(result, del_reward_epoch_2); + + // Query latest rewards for validator + let result = pos + .rewards(&client, &validator, &None, &None) + .await + .expect("Rewards query failed"); + assert_eq!(result, val_reward_epoch_2); + + // Query delegator rewards at specific epoch + let result = pos + .rewards( + &client, + &validator, + &Some(delegator.clone()), + &Some(epoch), + ) + .await + .expect("Rewards query failed"); + assert_eq!(result, del_reward_epoch_2); + + // Query validator rewards at specific epoch + let result = pos + .rewards(&client, &validator, &None, &Some(epoch)) + .await + .expect("Rewards query failed"); + assert_eq!(result, val_reward_epoch_2); + + // Ensure no rewards at previous epoch + let result = pos + .rewards( + &client, + &validator, + &Some(delegator.clone()), + &epoch.prev(), + ) + .await + .expect("Rewards query failed"); + assert_eq!(result, token::Amount::zero()); + + let result = pos + .rewards(&client, &validator, &None, &epoch.prev()) + .await + .expect("Rewards query failed"); + assert_eq!(result, token::Amount::zero()); + + // Advance to another epoch with more rewards + let val_reward_epoch_3 = token::Amount::native_whole(9); + let del_reward_epoch_3 = token::Amount::native_whole(11); + let reward_epoch_3 = + (Some(val_reward_epoch_3), Some(del_reward_epoch_3)); + let epoch = helpers::advance_epoch(&mut client, &bond, &reward_epoch_3); + assert_eq!(epoch, Epoch(3)); + + // Query latest rewards + let result = pos + .rewards(&client, &validator, &Some(delegator.clone()), &None) + .await + .expect("Rewards query failed"); + assert_eq!(result, del_reward_epoch_3 + del_reward_epoch_2); + + let result = pos + .rewards(&client, &validator, &None, &None) + .await + .expect("Rewards query failed"); + assert_eq!(result, val_reward_epoch_3 + val_reward_epoch_2); + + // Query rewards at specific epoch + let result = pos + .rewards( + &client, + &validator, + &Some(delegator.clone()), + &Some(epoch), + ) + .await + .expect("Rewards query failed"); + assert_eq!(result, del_reward_epoch_3 + del_reward_epoch_2); + + let result = pos + .rewards(&client, &validator, &None, &Some(epoch)) + .await + .expect("Rewards query failed"); + assert_eq!(result, val_reward_epoch_3 + val_reward_epoch_2); + + // Query at previous epoch + let result = pos + .rewards( + &client, + &validator, + &Some(delegator.clone()), + &epoch.prev(), + ) + .await + .expect("Rewards query failed"); + assert_eq!(result, del_reward_epoch_2); + + let result = pos + .rewards(&client, &validator, &None, &epoch.prev()) + .await + .expect("Rewards query failed"); + assert_eq!(result, val_reward_epoch_2); } } From c988a40842cb5a831f4cddbc327264d9d5e8dbb3 Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Tue, 28 Jan 2025 11:21:54 -0600 Subject: [PATCH 11/17] fix: address review suggestions --- Cargo.lock | 1 - crates/apps_lib/src/cli.rs | 1 - crates/sdk/Cargo.toml | 1 - crates/sdk/src/args.rs | 5 +---- crates/sdk/src/queries/vp/pos.rs | 12 +++++------- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b63dded37..77940026d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5541,7 +5541,6 @@ dependencies = [ "namada_state", "namada_storage", "namada_token", - "namada_trans_token", "namada_tx", "namada_vm", "namada_vote_ext", diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index 605d026846..8120e07d9f 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -7574,7 +7574,6 @@ pub mod args { type Data = PathBuf; type DatedSpendingKey = WalletDatedSpendingKey; type DatedViewingKey = WalletDatedViewingKey; - type Epoch = Epoch; type EthereumAddress = String; type Keypair = WalletKeypair; type MaspIndexerAddress = String; diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 204c3c340f..f81e052373 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -162,7 +162,6 @@ namada_proof_of_stake = { path = "../proof_of_stake", default-features = false, namada_state = { path = "../state", features = ["testing"] } namada_storage = { path = "../storage", features = ["testing"] } namada_token = { path = "../token", features = ["testing", "masp"] } -namada_trans_token = { path = "../trans_token" } namada_tx = { path = "../tx", features = ["testing"]} namada_vm = { path = "../vm" } namada_vote_ext = { path = "../vote_ext" } diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index 8fc34f905f..3777654e1d 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -101,8 +101,6 @@ pub trait NamadaTypes: Clone + std::fmt::Debug { type MaspIndexerAddress: Clone + std::fmt::Debug; /// Represents a block height type BlockHeight: Clone + std::fmt::Debug; - /// Represents an epoch - type Epoch: Clone + std::fmt::Debug; } /// The concrete types being used in Namada SDK @@ -130,7 +128,6 @@ impl NamadaTypes for SdkTypes { type Data = Vec; type DatedSpendingKey = DatedSpendingKey; type DatedViewingKey = DatedViewingKey; - type Epoch = namada_core::chain::Epoch; type EthereumAddress = (); type Keypair = namada_core::key::common::SecretKey; type MaspIndexerAddress = String; @@ -2549,7 +2546,7 @@ pub struct QueryRewards { /// Address of the validator pub validator: C::Address, /// Epoch in which to find rewards - pub epoch: Option, + pub epoch: Option, } /// Query PoS delegations diff --git a/crates/sdk/src/queries/vp/pos.rs b/crates/sdk/src/queries/vp/pos.rs index 135a13b8a4..5f3609bb84 100644 --- a/crates/sdk/src/queries/vp/pos.rs +++ b/crates/sdk/src/queries/vp/pos.rs @@ -958,7 +958,7 @@ mod test { _, namada_parameters::Store<_>, governance::Store<_>, - namada_trans_token::Store<_>, + namada_token::Store<_>, >( &mut client.state, namada_proof_of_stake::OwnedPosParams::default(), @@ -981,11 +981,9 @@ mod test { let native_token = client.state.get_native_token().unwrap(); StorageWrite::write( &mut client.state, - &storage::Key::from( - namada_trans_token::storage_key::balance_key( - &native_token, - &delegator, - ), + &namada_token::storage_key::balance_key( + &native_token, + &delegator, ), bond_amount, ) @@ -995,7 +993,7 @@ mod test { namada_proof_of_stake::bond_tokens::< _, governance::Store<_>, - namada_trans_token::Store<_>, + namada_token::Store<_>, >( &mut client.state, Some(&delegator), From ec6763b9122620aa989f5c2d1ea8126f846a0bc0 Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Tue, 28 Jan 2025 11:22:32 -0600 Subject: [PATCH 12/17] fix: improve documentation for --epoch option in rewards command to clarify that it only returns accumulated rewards available to claim --- crates/apps_lib/src/cli.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index 8120e07d9f..f7f4d3d3c5 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -2049,7 +2049,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about(wrap!( - "Query the latest rewards available to claim for a given \ + "Query the rewards available to claim for a given \ delegation (or self-bond)." )) .add_args::>() @@ -7341,7 +7341,11 @@ pub mod args { ) .arg(EPOCH.def().help(wrap!( "The epoch at which to query (corresponding to the last \ - committed block, if not specified)." + committed block, if not specified). \ + \ + Note: when querying by epoch, this returns the accumulated \ + rewards that were available to claim at the start of the \ + epoch." ))) } } From aacd109411a11bb257de8b94a27a0d1fca98f570 Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Fri, 31 Jan 2025 15:37:59 -0600 Subject: [PATCH 13/17] Check for a last reward claim in addition to whether the key is in the state before invoking the shortcut --- crates/sdk/src/queries/vp/pos.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/sdk/src/queries/vp/pos.rs b/crates/sdk/src/queries/vp/pos.rs index 5f3609bb84..5edb78443c 100644 --- a/crates/sdk/src/queries/vp/pos.rs +++ b/crates/sdk/src/queries/vp/pos.rs @@ -18,8 +18,8 @@ use namada_proof_of_stake::slashing::{ find_all_enqueued_slashes, find_all_slashes, }; use namada_proof_of_stake::storage::{ - bond_handle, get_consensus_key, liveness_sum_missed_votes_handle, - read_all_validator_addresses, + bond_handle, get_consensus_key, get_last_reward_claim_epoch, + liveness_sum_missed_votes_handle, read_all_validator_addresses, read_below_capacity_validator_set_addresses_with_stake, read_consensus_validator_set_addresses, read_consensus_validator_set_addresses_with_stake, read_pos_params, @@ -648,12 +648,11 @@ where ); // Shortcut: avoid costly lookup of non-existent storage key in history - // by first checking to see if it currently exists in memory before - // querying by height. - // TODO: this might not be valid if rewards have been claimed in the current - // epoch? (because the rewards_counter would be removed from - // storage until the next epoch) - if !ctx.state.has_key(&storage_key)? { + // by first checking to see if it currently exists in memory or has ever + // been claimed before querying by height. + if !ctx.state.has_key(&storage_key)? + && get_last_reward_claim_epoch(ctx.state, source, validator)?.is_none() + { return Ok(token::Amount::zero()); } From 03db9deeeac7c1991d45353cb42bb537af7d5599 Mon Sep 17 00:00:00 2001 From: Joel Nordell Date: Fri, 31 Jan 2025 16:42:51 -0600 Subject: [PATCH 14/17] pos: extend test case to also cover what happens when rewards are claimed --- crates/sdk/src/queries/vp/pos.rs | 66 +++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/crates/sdk/src/queries/vp/pos.rs b/crates/sdk/src/queries/vp/pos.rs index 5edb78443c..8ef299276f 100644 --- a/crates/sdk/src/queries/vp/pos.rs +++ b/crates/sdk/src/queries/vp/pos.rs @@ -904,9 +904,9 @@ mod test { use super::*; use crate::queries::testing::TestClient; use crate::queries::{RequestCtx, RequestQuery, Router, RPC}; + use namada_core::address; use namada_core::chain::Epoch; use namada_core::token; - use namada_core::{address, storage}; use namada_state::StorageWrite; #[tokio::test] @@ -1236,5 +1236,69 @@ mod test { .await .expect("Rewards query failed"); assert_eq!(result, val_reward_epoch_2); + + // Simulate rewards claim, then query again at previous epoch + let height = client.state.in_mem().block.height; + client + .state + .in_mem_mut() + .begin_block(height + 1) + .expect("Test failed"); + client.state.in_mem_mut().block.height = height + 1; + + let claimed = namada_proof_of_stake::claim_reward_tokens::< + _, + governance::Store<_>, + namada_token::Store<_>, + >( + &mut client.state, Some(&delegator), &validator, epoch + ) + .expect("Claiming rewards failed"); + + assert_eq!(claimed, del_reward_epoch_3 + del_reward_epoch_2); + + let claimed_validator = + namada_proof_of_stake::claim_reward_tokens::< + _, + governance::Store<_>, + namada_token::Store<_>, + >(&mut client.state, None, &validator, epoch) + .expect("Claiming validator rewards failed"); + + assert_eq!(claimed_validator, val_reward_epoch_3 + val_reward_epoch_2); + + // Commit the block + client.state.commit_block().expect("Test failed"); + + // Expect rewards to now report 0 when not specifying epcoh + let result = pos + .rewards(&client, &validator, &Some(delegator.clone()), &None) + .await + .expect("Rewards query failed"); + assert_eq!(result, token::Amount::zero()); + + let result = pos + .rewards(&client, &validator, &None, &None) + .await + .expect("Rewards query failed"); + assert_eq!(result, token::Amount::zero()); + + // But when querying at the current epoch, the claimable rewards should still be reported + let result = pos + .rewards( + &client, + &validator, + &Some(delegator.clone()), + &Some(epoch), + ) + .await + .expect("Rewards query failed"); + assert_eq!(result, del_reward_epoch_3 + del_reward_epoch_2); + + let result = pos + .rewards(&client, &validator, &None, &Some(epoch)) + .await + .expect("Rewards query failed"); + assert_eq!(result, val_reward_epoch_3 + val_reward_epoch_2); } } From 011209724845480611e8436609560ec740fbedb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 3 Feb 2025 11:57:43 +0100 Subject: [PATCH 15/17] make fmt --- crates/apps_lib/src/cli.rs | 7 +++---- crates/sdk/src/queries/vp/pos.rs | 21 ++++++++++++--------- crates/sdk/src/rpc.rs | 13 +++++++------ crates/storage/src/mockdb.rs | 9 ++++++--- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index f7f4d3d3c5..467081c4de 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -7342,10 +7342,9 @@ pub mod args { .arg(EPOCH.def().help(wrap!( "The epoch at which to query (corresponding to the last \ committed block, if not specified). \ - \ - Note: when querying by epoch, this returns the accumulated \ - rewards that were available to claim at the start of the \ - epoch." + Note: when querying by epoch, this returns the \ + accumulated rewards that were available to claim at the \ + start of the epoch." ))) } } diff --git a/crates/sdk/src/queries/vp/pos.rs b/crates/sdk/src/queries/vp/pos.rs index 8ef299276f..71f1e6542c 100644 --- a/crates/sdk/src/queries/vp/pos.rs +++ b/crates/sdk/src/queries/vp/pos.rs @@ -901,13 +901,13 @@ fn enrich_bonds_and_unbonds( #[cfg(test)] mod test { + use namada_core::chain::Epoch; + use namada_core::{address, token}; + use namada_state::StorageWrite; + use super::*; use crate::queries::testing::TestClient; use crate::queries::{RequestCtx, RequestQuery, Router, RPC}; - use namada_core::address; - use namada_core::chain::Epoch; - use namada_core::token; - use namada_state::StorageWrite; #[tokio::test] async fn test_validator_by_tm_addr_sanitized_input() { @@ -934,10 +934,12 @@ mod test { }; let result = POS.handle(ctx, &request); assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("Invalid Tendermint address")) + assert!( + result + .unwrap_err() + .to_string() + .contains("Invalid Tendermint address") + ) } // Helpers for test_rewards_query @@ -1283,7 +1285,8 @@ mod test { .expect("Rewards query failed"); assert_eq!(result, token::Amount::zero()); - // But when querying at the current epoch, the claimable rewards should still be reported + // But when querying at the current epoch, the claimable rewards should + // still be reported let result = pos .rewards( &client, diff --git a/crates/sdk/src/rpc.rs b/crates/sdk/src/rpc.rs index c7e0ef1466..32ac8cc79b 100644 --- a/crates/sdk/src/rpc.rs +++ b/crates/sdk/src/rpc.rs @@ -1686,15 +1686,16 @@ pub async fn osmosis_denom_from_namada_denom( if nam_denom.trace_path.is_empty() { // Namada native asset - let address = - nam_denom.base_denom.as_str().parse::
().map_err( - |err| { - Error::Encode(EncodingError::Decoding(format!( + let address = nam_denom + .base_denom + .as_str() + .parse::
() + .map_err(|err| { + Error::Encode(EncodingError::Decoding(format!( "Failed to parse base denom {} as Namada address: {err}", nam_denom.base_denom ))) - }, - )?; + })?; // validate that the base denom is not another ibc token if matches!(&address, Address::Internal(InternalAddress::IbcToken(_))) { diff --git a/crates/storage/src/mockdb.rs b/crates/storage/src/mockdb.rs index 6b0161b4e8..badd81d685 100644 --- a/crates/storage/src/mockdb.rs +++ b/crates/storage/src/mockdb.rs @@ -387,9 +387,11 @@ impl DB for MockDB { if height == last_height { self.read_subspace_val(key) } else { - // Quick-n-dirty implementation for reading subspace value at height: + // Quick-n-dirty implementation for reading subspace value at + // height: // - See if there are any diffs between height+1..last_height. - // - If so, the first one will provide the value we want as its old value. + // - If so, the first one will provide the value we want as its old + // value. // - If not, we can just read the value at the latest height. for h in (height.0 + 1)..=last_height.0 { let old_diff = self.read_diffs_val(key, h.into(), true)?; @@ -397,7 +399,8 @@ impl DB for MockDB { match (old_diff, new_diff) { (Some(old_diff), Some(_)) | (Some(old_diff), None) => { - // If there is an old diff, it contains the value at the requested height. + // If there is an old diff, it contains the value at the + // requested height. return Ok(Some(old_diff)); } (None, Some(_)) => { From 0ff63c71f5be7cd26b11df6599960802e29ce53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 3 Feb 2025 13:09:15 +0100 Subject: [PATCH 16/17] fixup! make fmt --- crates/apps_lib/src/cli.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index 467081c4de..c1e032fe09 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -7341,10 +7341,9 @@ pub mod args { ) .arg(EPOCH.def().help(wrap!( "The epoch at which to query (corresponding to the last \ - committed block, if not specified). \ - Note: when querying by epoch, this returns the \ - accumulated rewards that were available to claim at the \ - start of the epoch." + committed block, if not specified). Note: when querying \ + by epoch, this returns the accumulated rewards that were \ + available to claim at the start of the epoch." ))) } } From 3a0b4be148fc2a4398cfe061cc1016e7fef5eca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 3 Feb 2025 13:26:27 +0100 Subject: [PATCH 17/17] deliberatly empty