diff --git a/.changelog/unreleased/bug-fixes/3375-masp-total-rewards.md b/.changelog/unreleased/bug-fixes/3375-masp-total-rewards.md new file mode 100644 index 0000000000..05acb7961b --- /dev/null +++ b/.changelog/unreleased/bug-fixes/3375-masp-total-rewards.md @@ -0,0 +1,2 @@ +- Update native token total supply with MASP rewards. + ([\#3375](https://github.com/anoma/namada/pull/3375)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/3375-masp-total-rewards.md b/.changelog/unreleased/improvements/3375-masp-total-rewards.md new file mode 100644 index 0000000000..e5f18700c0 --- /dev/null +++ b/.changelog/unreleased/improvements/3375-masp-total-rewards.md @@ -0,0 +1,2 @@ +- Store total MASP rewards and print them in the conversions query. + ([\#3375](https://github.com/anoma/namada/pull/3375)) \ No newline at end of file diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index 941fa83444..e2359a7015 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -1707,6 +1707,19 @@ pub async fn query_conversions( ) { // The chosen token type of the conversions let target_token = args.token; + + if target_token.as_ref().is_none() { + // Query and print the total rewards first + let total_rewards = rpc::query_masp_total_rewards(context.client()) + .await + .expect("MASP total rewards should be present"); + display!( + context.io(), + "Total rewards of native token minted for shielded pool: {}", + total_rewards.to_string_native() + ); + } + // To facilitate human readable token addresses let tokens = context .wallet() diff --git a/crates/sdk/src/queries/vp/token.rs b/crates/sdk/src/queries/vp/token.rs index 18e994ebc9..89d8a15e55 100644 --- a/crates/sdk/src/queries/vp/token.rs +++ b/crates/sdk/src/queries/vp/token.rs @@ -49,7 +49,7 @@ pub mod client_only_methods { use borsh::BorshDeserialize; use namada_core::address::Address; use namada_core::token; - use namada_token::storage_key::balance_key; + use namada_token::storage_key::{balance_key, masp_total_rewards}; use super::Token; use crate::queries::{Client, RPC}; @@ -79,5 +79,28 @@ pub mod client_only_methods { }; Ok(balance) } + + /// Get the total rewards minted by MASP. + pub async fn masp_total_rewards( + &self, + client: &CLIENT, + ) -> Result::Error> + where + CLIENT: Client + Sync, + { + let total_rewards_key = masp_total_rewards(); + let response = RPC + .shell() + .storage_value(client, None, None, false, &total_rewards_key) + .await?; + + let tokens = if response.data.is_empty() { + token::Amount::zero() + } else { + token::Amount::try_from_slice(&response.data) + .unwrap_or_default() + }; + Ok(tokens) + } } } diff --git a/crates/sdk/src/rpc.rs b/crates/sdk/src/rpc.rs index 873addfd36..7523227ce1 100644 --- a/crates/sdk/src/rpc.rs +++ b/crates/sdk/src/rpc.rs @@ -347,6 +347,13 @@ pub async fn query_conversions( convert_response::(RPC.shell().read_conversions(client).await) } +/// Query the total rewards minted by MASP +pub async fn query_masp_total_rewards( + client: &C, +) -> Result { + convert_response::(RPC.vp().token().masp_total_rewards(client).await) +} + /// Query to read the tokens that earn masp rewards. pub async fn query_masp_reward_tokens( client: &C, diff --git a/crates/shielded_token/src/conversion.rs b/crates/shielded_token/src/conversion.rs index a8e009ac78..e53c72814a 100644 --- a/crates/shielded_token/src/conversion.rs +++ b/crates/shielded_token/src/conversion.rs @@ -254,13 +254,14 @@ where use namada_parameters as parameters; use namada_storage::conversion_state::ConversionLeaf; use namada_storage::{Error, OptionExt, ResultExt}; - use namada_trans_token::storage_key::balance_key; use namada_trans_token::{MaspDigitPos, NATIVE_MAX_DECIMAL_PLACES}; use rayon::iter::{ IndexedParallelIterator, IntoParallelIterator, ParallelIterator, }; use rayon::prelude::ParallelSlice; + use crate::mint_rewards; + // The derived conversions will be placed in MASP address space let masp_addr = MASP; @@ -566,12 +567,8 @@ where // Update the MASP's transparent reward token balance to ensure that it // is sufficiently backed to redeem rewards - let reward_key = balance_key(&native_token, &masp_addr); - let addr_bal: Amount = storage.read(&reward_key)?.unwrap_or_default(); - let new_bal = addr_bal - .checked_add(total_reward) - .ok_or_else(|| Error::new_const("Balance with reward overflow"))?; - storage.write(&reward_key, new_bal)?; + mint_rewards(storage, total_reward)?; + // Try to distribute Merkle tree construction as evenly as possible // across multiple cores // Merkle trees must have exactly 2^n leaves to be mergeable diff --git a/crates/shielded_token/src/storage.rs b/crates/shielded_token/src/storage.rs index 0b91da75a9..bddfcb7731 100644 --- a/crates/shielded_token/src/storage.rs +++ b/crates/shielded_token/src/storage.rs @@ -1,10 +1,11 @@ -use namada_core::address::Address; +use namada_core::address::{self, Address}; use namada_core::arith::checked; use namada_core::token; use namada_core::token::Amount; use namada_core::uint::Uint; use namada_storage as storage; use namada_storage::{StorageRead, StorageWrite}; +use namada_trans_token::credit_tokens; use storage::ResultExt; use crate::storage_key::*; @@ -40,3 +41,31 @@ where storage.write(&masp_locked_amount_target_key(address), raw_target)?; Ok(()) } + +/// Mint MASP rewards tokens and increment the stored total rewards. +pub fn mint_rewards( + storage: &mut S, + amount: token::Amount, +) -> storage::Result<()> +where + S: StorageRead + StorageWrite, +{ + let native_token = storage.get_native_token()?; + credit_tokens(storage, &native_token, &address::MASP, amount)?; + + let total_rewards_key = masp_total_rewards(); + let mut total_rewards = read_total_rewards(storage)?; + checked!(total_rewards += amount)?; + storage.write(&total_rewards_key, total_rewards) +} + +/// Read the total rewards minted by MASP. +pub fn read_total_rewards(storage: &S) -> storage::Result +where + S: StorageRead, +{ + let total_rewards_key = masp_total_rewards(); + let total_rewards: token::Amount = + storage.read(&total_rewards_key)?.unwrap_or_default(); + Ok(total_rewards) +} diff --git a/crates/shielded_token/src/storage_key.rs b/crates/shielded_token/src/storage_key.rs index f584ab7fff..df558b2e9e 100644 --- a/crates/shielded_token/src/storage_key.rs +++ b/crates/shielded_token/src/storage_key.rs @@ -32,6 +32,8 @@ pub const MASP_KD_GAIN_KEY: &str = "derivative_gain"; pub const MASP_LOCKED_AMOUNT_TARGET_KEY: &str = "locked_amount_target"; /// The key for the max reward rate for a given asset pub const MASP_MAX_REWARD_RATE_KEY: &str = "max_reward_rate"; +/// The key for the total inflation rewards minted by MASP +pub const MASP_TOTAL_REWARDS: &str = "max_total_rewards"; /// Obtain the nominal proportional key for the given token pub fn masp_kp_gain_key(token_addr: &Address) -> storage::Key { @@ -163,3 +165,10 @@ pub fn masp_assets_hash_key() -> storage::Key { .push(&MASP_ASSETS_HASH_KEY.to_owned()) .expect("Cannot obtain a storage key") } + +/// The max reward rate key for the given token +pub fn masp_total_rewards() -> storage::Key { + storage::Key::from(address::MASP.to_db_key()) + .push(&MASP_TOTAL_REWARDS.to_owned()) + .expect("Cannot obtain a storage key") +}