#[cfg(any(feature = "multicore", test))]
use std::collections::BTreeMap;
#[cfg(any(feature = "multicore", test))]
use masp_primitives::asset_type::AssetType;
#[cfg(any(feature = "multicore", test))]
use masp_primitives::convert::{AllowedConversion, UncheckedAllowedConversion};
#[cfg(any(feature = "multicore", test))]
use masp_primitives::transaction::components::I128Sum as MaspAmount;
use namada_controller::PDController;
use namada_core::address::{Address, MASP};
#[cfg(any(feature = "multicore", test))]
use namada_core::arith::CheckedAdd;
use namada_core::arith::checked;
#[cfg(any(feature = "multicore", test))]
use namada_core::borsh::BorshSerializeExt;
use namada_core::dec::Dec;
#[cfg(any(feature = "multicore", test))]
use namada_core::hash::Hash;
use namada_core::masp::Precision;
#[cfg(any(feature = "multicore", test))]
use namada_core::masp::{MaspEpoch, encode_asset_type};
#[cfg(any(feature = "multicore", test))]
use namada_core::token::MaspDigitPos;
use namada_core::token::{Amount, DenominatedAmount, Denomination};
use namada_core::uint::Uint;
#[cfg(any(feature = "multicore", test))]
use namada_state::iter_prefix_with_filter_map;
use namada_systems::{parameters, trans_token};
#[cfg(any(feature = "multicore", test))]
use crate::storage_key::{
is_masp_conversion_key, is_masp_scheduled_reward_precision_key,
masp_assets_hash_key, masp_conversion_key_prefix,
masp_scheduled_base_native_precision_key,
masp_scheduled_reward_precision_key_prefix, masp_token_map_key,
};
use crate::storage_key::{
masp_base_native_precision_key, masp_kd_gain_key, masp_kp_gain_key,
masp_last_inflation_key, masp_last_locked_amount_key,
masp_locked_amount_target_key, masp_max_reward_rate_key,
masp_reward_precision_key,
};
#[cfg(any(feature = "multicore", test))]
use crate::{ConversionLeaf, Error, OptionExt, ResultExt};
use crate::{Result, StorageRead, StorageWrite, WithConversionState};
#[allow(clippy::too_many_arguments)]
pub fn compute_inflation(
locked_amount: Uint,
total_native_amount: Uint,
max_reward_rate: Dec,
last_inflation_amount: Uint,
p_gain_nom: Dec,
d_gain_nom: Dec,
epochs_per_year: u64,
target_amount: Dec,
last_amount: Dec,
) -> Uint {
let controller = PDController::new(
total_native_amount,
max_reward_rate,
last_inflation_amount,
p_gain_nom,
d_gain_nom,
epochs_per_year,
target_amount,
last_amount,
);
let metric = Dec::try_from(locked_amount)
.expect("Should not fail to convert Uint to Dec");
let control_coeff = max_reward_rate
.checked_div(controller.get_epochs_per_year())
.expect("Control coefficient overflow");
tracing::debug!(
"Shielded token inflation inputs: {controller:#?}, metric: {metric}, \
coefficient: {control_coeff}"
);
controller
.compute_inflation(control_coeff, metric)
.expect("Inflation calculation overflow")
}
#[deprecated = "Token precisions are now read from storage instead of being \
inferred from their denominations."]
fn infer_token_precision<S, TransToken>(
storage: &mut S,
addr: &Address,
) -> Result<Precision>
where
S: StorageWrite + StorageRead + WithConversionState,
TransToken: trans_token::Read<S>,
{
let denomination = TransToken::read_denom(storage, addr)?
.expect("failed to read token denomination");
let precision_denom =
u32::from(denomination.0).saturating_sub(3).clamp(0, 38);
let reward_precision = checked!(10u128 ^ precision_denom)?;
Ok(reward_precision)
}
fn read_base_native_precision<S, TransToken>(
storage: &mut S,
native_token: &Address,
) -> Result<Precision>
where
S: StorageWrite + StorageRead + WithConversionState,
TransToken: trans_token::Keys + trans_token::Read<S>,
{
let base_native_precision_key = masp_base_native_precision_key();
storage.read(&base_native_precision_key)?.map_or_else(
|| -> Result<Precision> {
#[allow(deprecated)]
let precision =
infer_token_precision::<_, TransToken>(storage, native_token)?;
storage.write(&base_native_precision_key, precision)?;
Ok(precision)
},
Ok,
)
}
pub fn calculate_masp_rewards_precision<S, TransToken>(
storage: &mut S,
addr: &Address,
) -> Result<Precision>
where
S: StorageWrite + StorageRead + WithConversionState,
TransToken: trans_token::Keys + trans_token::Read<S>,
{
let reward_precision_key = masp_reward_precision_key::<TransToken>(addr);
let reward_precision: Precision =
storage.read(&reward_precision_key)?.map_or_else(
|| -> Result<Precision> {
let native_token = storage.get_native_token()?;
#[allow(deprecated)]
let prec = match storage.conversion_state().current_precision {
Some(native_precision) if *addr == native_token => {
native_precision
}
None if *addr == native_token => {
read_base_native_precision::<_, TransToken>(
storage, addr,
)?
}
_ => infer_token_precision::<_, TransToken>(storage, addr)?,
};
storage.write(&reward_precision_key, prec)?;
Ok(prec)
},
Ok,
)?;
Ok(reward_precision)
}
fn get_masp_dated_balance<S, TransToken>(
storage: &mut S,
token: &Address,
) -> Result<Amount>
where
S: StorageWrite + StorageRead,
TransToken: trans_token::Keys + trans_token::Read<S>,
{
use crate::read_undated_balance;
let masp_addr = MASP;
let total_tokens_in_masp =
TransToken::read_balance(storage, token, &masp_addr)?;
let masp_undated_balance = read_undated_balance(storage, token)?;
Ok(checked!(total_tokens_in_masp - masp_undated_balance)?)
}
pub fn calculate_masp_rewards<S, TransToken>(
storage: &mut S,
token: &Address,
denomination: Denomination,
precision: Precision,
masp_epochs_per_year: u64,
) -> Result<(u128, Precision)>
where
S: StorageWrite + StorageRead,
TransToken: trans_token::Keys + trans_token::Read<S>,
{
let masp_addr = MASP;
let total_native_tokens =
TransToken::get_effective_total_native_supply(storage)?;
let total_tokens_in_masp =
TransToken::read_balance(storage, token, &masp_addr)?;
let last_inflation: Amount = storage
.read(&masp_last_inflation_key::<TransToken>(token))?
.expect("failure to read last inflation");
let last_locked_amount: Amount = storage
.read(&masp_last_locked_amount_key::<TransToken>(token))?
.expect("failure to read last inflation");
let max_reward_rate: Dec = storage
.read(&masp_max_reward_rate_key::<TransToken>(token))?
.expect("max reward should properly decode");
let kp_gain_nom: Dec = storage
.read(&masp_kp_gain_key::<TransToken>(token))?
.expect("kp_gain_nom reward should properly decode");
let kd_gain_nom: Dec = storage
.read(&masp_kd_gain_key::<TransToken>(token))?
.expect("kd_gain_nom reward should properly decode");
let target_locked_amount: Amount = storage
.read(&masp_locked_amount_target_key::<TransToken>(token))?
.expect("locked ratio target should properly decode");
let target_locked_dec = Dec::try_from(target_locked_amount.raw_amount())
.expect("Should not fail to convert Uint to Dec");
let last_locked_dec = Dec::try_from(last_locked_amount.raw_amount())
.expect("Should not fail to convert Uint to Dec");
let inflation = compute_inflation(
total_tokens_in_masp.raw_amount(),
total_native_tokens.raw_amount(),
max_reward_rate,
last_inflation.raw_amount(),
kp_gain_nom,
kd_gain_nom,
masp_epochs_per_year,
target_locked_dec,
last_locked_dec,
);
let rewardable_tokens_in_masp =
get_masp_dated_balance::<S, TransToken>(storage, token)?;
let noterized_inflation = if rewardable_tokens_in_masp.is_zero() {
0u128
} else {
inflation
.checked_mul_div(
Uint::from(precision),
rewardable_tokens_in_masp.raw_amount(),
)
.and_then(|x| x.0.try_into().ok())
.unwrap_or_else(|| {
tracing::warn!(
"MASP inflation for {} assumed to be 0 because the \
computed value is too large. Please check the inflation \
parameters.",
*token
);
0u128
})
};
let inflation_amount = Amount::from_uint(
checked!(
rewardable_tokens_in_masp.raw_amount() / precision.into()
* Uint::from(noterized_inflation)
)?,
0,
)
.unwrap();
let denom_amount = DenominatedAmount::new(inflation_amount, denomination);
tracing::info!("MASP inflation for {token} is {denom_amount}");
tracing::debug!(
"Controller, call: total_in_masp {:?}, total_native_tokens {:?}, \
locked_target_amount {:?}, last_locked_amount {:?}, max_reward_rate \
{:?}, last_inflation {:?}, kp_gain_nom {:?}, kd_gain_nom {:?}, \
epochs_per_year {:?}",
total_tokens_in_masp,
total_native_tokens,
target_locked_amount,
last_locked_amount,
max_reward_rate,
last_inflation,
kp_gain_nom,
kd_gain_nom,
masp_epochs_per_year,
);
tracing::debug!("Token address: {:?}", token);
tracing::debug!("inflation from the pd controller {:?}", inflation);
tracing::debug!("total in the masp {:?}", total_tokens_in_masp);
tracing::debug!("precision {}", precision);
tracing::debug!("Noterized inflation: {}", noterized_inflation);
storage.write(
&masp_last_inflation_key::<TransToken>(token),
inflation_amount,
)?;
storage.write(
&masp_last_locked_amount_key::<TransToken>(token),
total_tokens_in_masp,
)?;
Ok((noterized_inflation, precision))
}
#[cfg(any(feature = "multicore", test))]
fn update_native_conversions<S, TransToken>(
storage: &mut S,
token: &Address,
current_precision: &mut u128,
masp_epochs_per_year: u64,
masp_epoch: MaspEpoch,
current_convs: &mut BTreeMap<
(Address, Denomination, MaspDigitPos),
AllowedConversion,
>,
) -> Result<(Denomination, (u128, u128))>
where
S: StorageWrite + StorageRead + WithConversionState,
TransToken:
trans_token::Keys + trans_token::Read<S> + trans_token::Write<S>,
{
let prev_masp_epoch =
masp_epoch.prev().ok_or_err_msg("MASP epoch underflow")?;
let denom = TransToken::read_denom(storage, token)?
.expect("failed to read token denomination");
let (reward, _precision) = calculate_masp_rewards::<S, TransToken>(
storage,
token,
denom,
*current_precision,
masp_epochs_per_year,
)?;
let current_precision_uint = Uint::from(*current_precision);
let reward = Uint::from(reward);
let new_precision = checked!(current_precision_uint + reward)?;
let new_precision = u128::try_from(new_precision).unwrap_or_else(|_| {
tracing::warn!(
"MASP precision for the native token {} is kept the same as in \
the last epoch because the computed value is too large. Please \
check the inflation parameters.",
token
);
*current_precision
});
for digit in MaspDigitPos::iter() {
let old_asset = encode_asset_type(
token.clone(),
denom,
digit,
Some(prev_masp_epoch),
)
.into_storage_result()?;
let new_asset =
encode_asset_type(token.clone(), denom, digit, Some(masp_epoch))
.into_storage_result()?;
let cur_conv = MaspAmount::from_pair(
old_asset,
i128::try_from(*current_precision)
.ok()
.and_then(i128::checked_neg)
.ok_or_err_msg("Current inflation overflow")?,
);
let new_conv = MaspAmount::from_pair(
new_asset,
i128::try_from(new_precision).into_storage_result()?,
);
current_convs.insert(
(token.clone(), denom, digit),
checked!(cur_conv + &new_conv)?.into(),
);
storage.conversion_state_mut().assets.insert(
old_asset,
ConversionLeaf {
token: token.clone(),
denom,
digit_pos: digit,
epoch: prev_masp_epoch,
conversion: MaspAmount::zero().into(),
leaf_pos: 0,
},
);
}
let reward_frac = (
new_precision
.checked_sub(*current_precision)
.unwrap_or_default(),
*current_precision,
);
let reward_precision_key = masp_reward_precision_key::<TransToken>(token);
storage.write(&reward_precision_key, new_precision)?;
*current_precision = new_precision;
Ok((denom, reward_frac))
}
#[cfg(any(feature = "multicore", test))]
#[allow(clippy::too_many_arguments)]
fn update_non_native_conversions<S, TransToken>(
storage: &mut S,
token: &Address,
base_native_precision: u128,
current_native_precision: u128,
masp_epochs_per_year: u64,
masp_epoch: MaspEpoch,
reward_assets: [AssetType; 4],
total_reward: &mut Amount,
current_convs: &mut BTreeMap<
(Address, Denomination, MaspDigitPos),
AllowedConversion,
>,
) -> Result<Denomination>
where
S: StorageWrite + StorageRead + WithConversionState,
TransToken:
trans_token::Keys + trans_token::Read<S> + trans_token::Write<S>,
{
let prev_masp_epoch =
masp_epoch.prev().ok_or_err_msg("MASP epoch underflow")?;
let denom = TransToken::read_denom(storage, token)?
.expect("failed to read token denomination");
let precision =
calculate_masp_rewards_precision::<S, TransToken>(storage, token)?;
let (reward, precision) = calculate_masp_rewards::<S, TransToken>(
storage,
token,
denom,
precision,
masp_epochs_per_year,
)?;
let reward_uint = Uint::from(reward);
let base_native_precision_uint = Uint::from(base_native_precision);
let current_native_precision_uint = Uint::from(current_native_precision);
let real_reward = checked!(
(reward_uint * base_native_precision_uint)
/ current_native_precision_uint
)?
.try_into()
.unwrap_or_else(|_| {
tracing::warn!(
"MASP reward for {} assumed to be 0 because the computed value is \
too large. Please check the inflation parameters.",
token
);
0u128
});
let precision_i128 = i128::try_from(precision).into_storage_result()?;
let real_reward_i128 = i128::try_from(real_reward).into_storage_result()?;
for digit in MaspDigitPos::iter() {
let old_asset = encode_asset_type(
token.clone(),
denom,
digit,
Some(prev_masp_epoch),
)
.into_storage_result()?;
let new_asset =
encode_asset_type(token.clone(), denom, digit, Some(masp_epoch))
.into_storage_result()?;
current_convs.insert(
(token.clone(), denom, digit),
checked!(
MaspAmount::from_pair(old_asset, -precision_i128)
+ &MaspAmount::from_pair(new_asset, precision_i128)
+ &MaspAmount::from_pair(
reward_assets[digit as usize],
real_reward_i128,
)
)?
.into(),
);
storage.conversion_state_mut().assets.insert(
old_asset,
ConversionLeaf {
token: token.clone(),
denom,
digit_pos: digit,
epoch: prev_masp_epoch,
conversion: MaspAmount::zero().into(),
leaf_pos: 0,
},
);
}
let addr_bal = get_masp_dated_balance::<S, TransToken>(storage, token)?;
*total_reward = total_reward
.checked_add(
addr_bal
.u128_eucl_div_rem((real_reward, precision))
.ok_or_else(|| {
Error::new_const("Total reward calculation overflow")
})?
.0,
)
.ok_or_else(|| Error::new_const("Total reward overflow"))?;
Ok(denom)
}
#[cfg(any(feature = "multicore", test))]
fn apply_stored_conversion_updates<S, TransToken>(
storage: &mut S,
ep: &MaspEpoch,
) -> Result<()>
where
S: StorageWrite + StorageRead + WithConversionState,
TransToken:
trans_token::Keys + trans_token::Read<S> + trans_token::Write<S>,
{
let scheduled_base_precision_key =
masp_scheduled_base_native_precision_key(ep);
if let Some(precision) = storage.read(&scheduled_base_precision_key)? {
let base_precision_key = masp_base_native_precision_key();
storage.write::<Precision>(&base_precision_key, precision)?;
}
let scheduled_reward_precision_key_prefix =
masp_scheduled_reward_precision_key_prefix(ep);
let mut precision_updates = BTreeMap::<_, Precision>::new();
for prec_result in iter_prefix_with_filter_map(
storage,
&scheduled_reward_precision_key_prefix,
is_masp_scheduled_reward_precision_key,
)? {
match prec_result {
Ok(((_ep, addr), precision)) => {
precision_updates.insert(addr, precision);
}
Err(err) => {
tracing::warn!("Encountered malformed precision: {}", err);
continue;
}
}
}
for (addr, precision) in precision_updates {
let reward_precision_key =
masp_reward_precision_key::<TransToken>(&addr);
storage.write(&reward_precision_key, precision)?;
}
let conversion_key_prefix = masp_conversion_key_prefix(ep);
let mut conversion_updates =
BTreeMap::<_, UncheckedAllowedConversion>::new();
for conv_result in iter_prefix_with_filter_map(
storage,
&conversion_key_prefix,
is_masp_conversion_key,
)? {
match conv_result {
Ok(((_ep, asset_type), conv)) => {
conversion_updates.insert(asset_type, conv);
}
Err(err) => {
tracing::warn!("Encountered malformed conversion: {}", err);
continue;
}
}
}
let assets = &mut storage.conversion_state_mut().assets;
for (asset_type, conv) in conversion_updates {
let Some(leaf) = assets.get_mut(&asset_type) else {
tracing::warn!(
"Encountered non-existent asset type: {}",
asset_type
);
continue;
};
leaf.conversion = conv.0;
}
storage.delete_prefix(&conversion_key_prefix)?;
storage.delete_prefix(&scheduled_reward_precision_key_prefix)?;
storage.delete(&scheduled_base_precision_key)?;
Ok(())
}
#[cfg(any(feature = "multicore", test))]
pub fn update_allowed_conversions<S, Params, TransToken>(
storage: &mut S,
) -> Result<()>
where
S: StorageWrite + StorageRead + WithConversionState,
Params: parameters::Read<S>,
TransToken:
trans_token::Keys + trans_token::Read<S> + trans_token::Write<S>,
{
use std::cmp::Ordering;
use masp_primitives::bls12_381;
use masp_primitives::ff::PrimeField;
use masp_primitives::merkle_tree::FrozenCommitmentTree;
use masp_primitives::sapling::Node;
use namada_core::masp::encode_reward_asset_types;
use namada_core::token::NATIVE_MAX_DECIMAL_PLACES;
use rayon::iter::{
IndexedParallelIterator, IntoParallelIterator, ParallelIterator,
};
use rayon::prelude::ParallelSlice;
use crate::mint_rewards;
let masp_epoch_multiplier = Params::masp_epoch_multiplier(storage)?;
let masp_epoch = MaspEpoch::try_from_epoch(
storage.get_block_epoch()?,
masp_epoch_multiplier,
)
.map_err(Error::new_const)?;
let Some(prev_masp_epoch) = masp_epoch.prev() else {
return Ok(());
};
apply_stored_conversion_updates::<_, TransToken>(
storage,
&prev_masp_epoch,
)?;
let token_map_key = masp_token_map_key();
let token_map: namada_core::masp::TokenMap =
storage.read(&token_map_key)?.unwrap_or_default();
let mut masp_reward_keys: Vec<_> = token_map.values().cloned().collect();
let mut masp_reward_denoms = BTreeMap::new();
let native_token = storage.get_native_token()?;
masp_reward_keys.sort_unstable_by(|x, y| {
if (*x == native_token) == (*y == native_token) {
Ordering::Equal
} else if *x == native_token {
Ordering::Less
} else {
Ordering::Greater
}
});
let mut total_deflated_reward = Amount::zero();
let reward_assets =
encode_reward_asset_types(&native_token).into_storage_result()?;
let mut current_convs = BTreeMap::<
(Address, Denomination, MaspDigitPos),
AllowedConversion,
>::new();
let base_native_precision =
read_base_native_precision::<_, TransToken>(storage, &native_token)?;
let mut current_native_precision = calculate_masp_rewards_precision::<
S,
TransToken,
>(storage, &native_token)?;
let epochs_per_year = Params::epochs_per_year(storage)?;
let masp_epochs_per_year =
checked!(epochs_per_year / masp_epoch_multiplier)?;
let mut native_reward_frac = None;
for token in &masp_reward_keys {
if *token == native_token {
let (denom, frac) = update_native_conversions::<_, TransToken>(
storage,
token,
&mut current_native_precision,
masp_epochs_per_year,
masp_epoch,
&mut current_convs,
)?;
masp_reward_denoms.insert(token.clone(), denom);
native_reward_frac = Some(frac);
} else {
let denom = update_non_native_conversions::<_, TransToken>(
storage,
token,
base_native_precision,
current_native_precision,
masp_epochs_per_year,
masp_epoch,
reward_assets,
&mut total_deflated_reward,
&mut current_convs,
)?;
masp_reward_denoms.insert(token.clone(), denom);
}
}
let non_native_reward = total_deflated_reward
.raw_amount()
.checked_mul_div(
current_native_precision.into(),
base_native_precision.into(),
)
.ok_or_else(|| Error::new_const("Total reward calculation overflow"))?;
let mut total_reward = Amount::from(non_native_reward.0);
if let Some(native_reward_frac) = native_reward_frac {
let addr_bal = TransToken::read_balance(storage, &native_token, &MASP)?;
let native_reward = addr_bal
.raw_amount()
.checked_mul_div(
native_reward_frac.0.into(),
native_reward_frac.1.into(),
)
.ok_or_else(|| Error::new_const("Three digit reward overflow"))?;
checked!(total_reward += native_reward.0.into())?;
let base_native_precision = Uint::from(base_native_precision);
let numerator = checked!(
native_reward.1 * base_native_precision
+ Uint::from(native_reward_frac.1) * non_native_reward.1
)?;
let denominator =
checked!(base_native_precision * Uint::from(native_reward_frac.1))?;
if numerator >= denominator {
checked!(total_reward += 1.into())?;
}
}
let num_threads = rayon::current_num_threads();
let assets: Vec<_> = storage
.conversion_state_mut()
.assets
.values_mut()
.enumerate()
.collect();
#[allow(clippy::arithmetic_side_effects)]
let notes_per_thread_max = assets.len().div_ceil(num_threads);
#[allow(clippy::arithmetic_side_effects)]
let notes_per_thread_min = assets.len() / num_threads;
let conv_notes: Vec<Node> = assets
.into_par_iter()
.with_min_len(notes_per_thread_min)
.with_max_len(notes_per_thread_max)
.map(|(idx, leaf)| {
let cur_conv_key = (leaf.token.clone(), leaf.denom, leaf.digit_pos);
if let Some(current_conv) = current_convs.get(&cur_conv_key) {
#[allow(clippy::arithmetic_side_effects)]
{
leaf.conversion += current_conv.clone();
}
}
leaf.leaf_pos = idx;
Node::new(leaf.conversion.cmu().to_repr())
})
.collect();
mint_rewards::<S, TransToken>(storage, total_reward)?;
let mut notes_per_thread_rounded = 1;
#[allow(clippy::arithmetic_side_effects)]
while notes_per_thread_max > notes_per_thread_rounded * 4 {
notes_per_thread_rounded *= 2;
}
let tree_parts: Vec<_> = conv_notes
.par_chunks(notes_per_thread_rounded)
.map(FrozenCommitmentTree::new)
.collect();
storage.conversion_state_mut().tree =
FrozenCommitmentTree::merge(&tree_parts);
storage.write(
&crate::storage_key::masp_convert_anchor_key(),
namada_core::hash::Hash(
bls12_381::Scalar::from(storage.conversion_state().tree.root())
.to_bytes(),
),
)?;
if !masp_reward_keys.contains(&native_token) {
masp_reward_denoms
.insert(native_token.clone(), NATIVE_MAX_DECIMAL_PLACES.into());
}
for (addr, denom) in masp_reward_denoms {
for digit in MaspDigitPos::iter() {
let new_asset =
encode_asset_type(addr.clone(), denom, digit, Some(masp_epoch))
.into_storage_result()?;
let tree_size = storage.conversion_state().tree.size();
storage.conversion_state_mut().assets.insert(
new_asset,
ConversionLeaf {
token: addr.clone(),
denom,
digit_pos: digit,
epoch: masp_epoch,
conversion: MaspAmount::zero().into(),
leaf_pos: tree_size,
},
);
}
}
let assets_hash =
Hash::sha256(storage.conversion_state().assets.serialize_to_vec());
storage.write(&masp_assets_hash_key(), assets_hash)?;
Ok(())
}
#[cfg(not(any(feature = "multicore", test)))]
pub fn update_allowed_conversions<S, Params, TransToken>(
_storage: &mut S,
) -> Result<()>
where
S: StorageWrite + StorageRead + WithConversionState,
Params: parameters::Read<S>,
TransToken: trans_token::Keys,
{
Ok(())
}
#[allow(clippy::arithmetic_side_effects)]
#[cfg(test)]
mod tests {
use std::str::FromStr;
use namada_core::address;
use namada_core::collections::HashMap;
use namada_core::dec::testing::arb_non_negative_dec;
use namada_core::token::testing::arb_amount;
use namada_state::testing::TestStorage;
use namada_trans_token::storage_key::{balance_key, minted_balance_key};
use namada_trans_token::write_denom;
use proptest::prelude::*;
use proptest::test_runner::Config;
use test_log::test;
use super::*;
use crate::ShieldedParams;
proptest! {
#![proptest_config(Config {
cases: 10,
.. Config::default()
})]
#[test]
fn test_updated_allowed_conversions(
initial_balance in arb_amount(),
masp_locked_ratio in arb_non_negative_dec(),
) {
test_updated_allowed_conversions_aux(initial_balance, masp_locked_ratio)
}
}
fn test_updated_allowed_conversions_aux(
initial_balance: Amount,
masp_locked_ratio: Dec,
) {
const ROUNDS: usize = 10;
let mut s = TestStorage::default();
{
namada_parameters::init_test_storage(&mut s).unwrap();
let token_params = ShieldedParams {
max_reward_rate: Dec::from_str("0.1").unwrap(),
kp_gain_nom: Dec::from_str("0.1").unwrap(),
kd_gain_nom: Dec::from_str("0.1").unwrap(),
locked_amount_target: 10_000_u64,
};
for (token_addr, (alias, denom)) in tokens() {
namada_trans_token::write_params(&mut s, &token_addr).unwrap();
crate::write_params::<_, namada_trans_token::Store<()>>(
&token_params,
&mut s,
&token_addr,
&denom,
)
.unwrap();
write_denom(&mut s, &token_addr, denom).unwrap();
let total_token_balance = initial_balance;
s.write(&minted_balance_key(&token_addr), total_token_balance)
.unwrap();
s.write(
&balance_key(&token_addr, &address::MASP),
masp_locked_ratio * total_token_balance,
)
.unwrap();
let token_map_key = masp_token_map_key();
let mut token_map: namada_core::masp::TokenMap =
s.read(&token_map_key).unwrap().unwrap_or_default();
token_map.insert(alias.to_string(), token_addr.clone());
s.write(&token_map_key, token_map).unwrap();
}
}
for i in 0..ROUNDS {
println!("Round {i}");
update_allowed_conversions::<
_,
namada_parameters::Store<_>,
namada_trans_token::Store<_>,
>(&mut s)
.unwrap();
println!();
println!();
}
}
pub fn tokens() -> HashMap<Address, (&'static str, Denomination)> {
vec![
(address::testing::nam(), ("nam", 6.into())),
(address::testing::btc(), ("btc", 8.into())),
(address::testing::eth(), ("eth", 18.into())),
(address::testing::dot(), ("dot", 10.into())),
(address::testing::schnitzel(), ("schnitzel", 6.into())),
(address::testing::apfel(), ("apfel", 6.into())),
(address::testing::kartoffel(), ("kartoffel", 6.into())),
]
.into_iter()
.collect()
}
#[test]
fn test_masp_inflation_playground() {
let denom = Uint::from(1_000_000); let total_tokens = 10_000_000_000_u64; let mut total_tokens = Uint::from(total_tokens) * denom;
let locked_tokens_target = Uint::from(500_000) * denom; let init_locked_ratio = Dec::from_str("0.1").unwrap(); let init_locked_tokens = (init_locked_ratio
* Dec::try_from(locked_tokens_target).unwrap())
.to_uint()
.unwrap();
let epochs_per_year = 730_u64; let max_reward_rate = Dec::from_str("0.01").unwrap(); let mut last_inflation_amount = Uint::zero();
let p_gain_nom = Dec::from_str("25000").unwrap(); let d_gain_nom = Dec::from_str("25000").unwrap();
let mut locked_amount = init_locked_tokens;
let mut locked_tokens_last = init_locked_tokens;
let num_rounds = 10;
println!();
for round in 0..num_rounds {
let inflation = compute_inflation(
locked_amount,
total_tokens,
max_reward_rate,
last_inflation_amount,
p_gain_nom,
d_gain_nom,
epochs_per_year,
Dec::try_from(locked_tokens_target).unwrap(),
Dec::try_from(locked_tokens_last).unwrap(),
);
let rate = Dec::try_from(inflation).unwrap()
* Dec::from(epochs_per_year)
/ Dec::try_from(total_tokens).unwrap();
println!(
"Round {round}: Locked amount: {locked_amount}, inflation \
rate: {rate} -- (raw infl: {inflation})",
);
last_inflation_amount = inflation;
total_tokens += inflation;
locked_tokens_last = locked_amount;
let change_staked_tokens = Uint::from(2) * locked_tokens_target;
locked_amount += change_staked_tokens;
}
}
}