1#[cfg(any(feature = "multicore", test))]
4use std::collections::BTreeMap;
5
6#[cfg(any(feature = "multicore", test))]
7use masp_primitives::asset_type::AssetType;
8#[cfg(any(feature = "multicore", test))]
9use masp_primitives::convert::{AllowedConversion, UncheckedAllowedConversion};
10#[cfg(any(feature = "multicore", test))]
11use masp_primitives::transaction::components::I128Sum as MaspAmount;
12use namada_controller::PDController;
13use namada_core::address::{Address, MASP};
14#[cfg(any(feature = "multicore", test))]
15use namada_core::arith::CheckedAdd;
16use namada_core::arith::checked;
17#[cfg(any(feature = "multicore", test))]
18use namada_core::borsh::BorshSerializeExt;
19use namada_core::dec::Dec;
20#[cfg(any(feature = "multicore", test))]
21use namada_core::hash::Hash;
22use namada_core::masp::Precision;
23#[cfg(any(feature = "multicore", test))]
24use namada_core::masp::{MaspEpoch, encode_asset_type};
25#[cfg(any(feature = "multicore", test))]
26use namada_core::token::MaspDigitPos;
27use namada_core::token::{Amount, DenominatedAmount, Denomination};
28use namada_core::uint::Uint;
29#[cfg(any(feature = "multicore", test))]
30use namada_state::iter_prefix_with_filter_map;
31use namada_systems::{parameters, trans_token};
32
33#[cfg(any(feature = "multicore", test))]
34use crate::storage_key::{
35 is_masp_conversion_key, is_masp_scheduled_reward_precision_key,
36 masp_assets_hash_key, masp_conversion_key_prefix,
37 masp_scheduled_base_native_precision_key,
38 masp_scheduled_reward_precision_key_prefix, masp_token_map_key,
39};
40use crate::storage_key::{
41 masp_base_native_precision_key, masp_kd_gain_key, masp_kp_gain_key,
42 masp_last_inflation_key, masp_last_locked_amount_key,
43 masp_locked_amount_target_key, masp_max_reward_rate_key,
44 masp_reward_precision_key,
45};
46#[cfg(any(feature = "multicore", test))]
47use crate::{ConversionLeaf, Error, OptionExt, ResultExt};
48use crate::{Result, StorageRead, StorageWrite, WithConversionState};
49
50#[allow(clippy::too_many_arguments)]
52pub fn compute_inflation(
53 locked_amount: Uint,
54 total_native_amount: Uint,
55 max_reward_rate: Dec,
56 last_inflation_amount: Uint,
57 p_gain_nom: Dec,
58 d_gain_nom: Dec,
59 epochs_per_year: u64,
60 target_amount: Dec,
61 last_amount: Dec,
62) -> Uint {
63 let controller = PDController::new(
64 total_native_amount,
65 max_reward_rate,
66 last_inflation_amount,
67 p_gain_nom,
68 d_gain_nom,
69 epochs_per_year,
70 target_amount,
71 last_amount,
72 );
73
74 let metric = Dec::try_from(locked_amount)
75 .expect("Should not fail to convert Uint to Dec");
76 let control_coeff = max_reward_rate
77 .checked_div(controller.get_epochs_per_year())
78 .expect("Control coefficient overflow");
79
80 tracing::debug!(
81 "Shielded token inflation inputs: {controller:#?}, metric: {metric}, \
82 coefficient: {control_coeff}"
83 );
84 controller
85 .compute_inflation(control_coeff, metric)
86 .expect("Inflation calculation overflow")
87}
88
89#[deprecated = "Token precisions are now read from storage instead of being \
91 inferred from their denominations."]
92fn infer_token_precision<S, TransToken>(
93 storage: &mut S,
94 addr: &Address,
95) -> Result<Precision>
96where
97 S: StorageWrite + StorageRead + WithConversionState,
98 TransToken: trans_token::Read<S>,
99{
100 let denomination = TransToken::read_denom(storage, addr)?
105 .expect("failed to read token denomination");
106 let precision_denom =
107 u32::from(denomination.0).saturating_sub(3).clamp(0, 38);
108 let reward_precision = checked!(10u128 ^ precision_denom)?;
109 Ok(reward_precision)
110}
111
112fn read_base_native_precision<S, TransToken>(
115 storage: &mut S,
116 native_token: &Address,
117) -> Result<Precision>
118where
119 S: StorageWrite + StorageRead + WithConversionState,
120 TransToken: trans_token::Keys + trans_token::Read<S>,
121{
122 let base_native_precision_key = masp_base_native_precision_key();
123 storage.read(&base_native_precision_key)?.map_or_else(
124 || -> Result<Precision> {
125 #[allow(deprecated)]
126 let precision =
127 infer_token_precision::<_, TransToken>(storage, native_token)?;
128 storage.write(&base_native_precision_key, precision)?;
129 Ok(precision)
130 },
131 Ok,
132 )
133}
134
135pub fn calculate_masp_rewards_precision<S, TransToken>(
138 storage: &mut S,
139 addr: &Address,
140) -> Result<Precision>
141where
142 S: StorageWrite + StorageRead + WithConversionState,
143 TransToken: trans_token::Keys + trans_token::Read<S>,
144{
145 let reward_precision_key = masp_reward_precision_key::<TransToken>(addr);
155 let reward_precision: Precision =
157 storage.read(&reward_precision_key)?.map_or_else(
158 || -> Result<Precision> {
159 let native_token = storage.get_native_token()?;
160 #[allow(deprecated)]
161 let prec = match storage.conversion_state().current_precision {
162 Some(native_precision) if *addr == native_token => {
165 native_precision
166 }
167 None if *addr == native_token => {
171 read_base_native_precision::<_, TransToken>(
172 storage, addr,
173 )?
174 }
175 _ => infer_token_precision::<_, TransToken>(storage, addr)?,
178 };
179 storage.write(&reward_precision_key, prec)?;
184 Ok(prec)
185 },
186 Ok,
187 )?;
188
189 Ok(reward_precision)
190}
191
192fn get_masp_dated_balance<S, TransToken>(
195 storage: &mut S,
196 token: &Address,
197) -> Result<Amount>
198where
199 S: StorageWrite + StorageRead,
200 TransToken: trans_token::Keys + trans_token::Read<S>,
201{
202 use crate::read_undated_balance;
203 let masp_addr = MASP;
204
205 let total_tokens_in_masp =
207 TransToken::read_balance(storage, token, &masp_addr)?;
208 let masp_undated_balance = read_undated_balance(storage, token)?;
211 Ok(checked!(total_tokens_in_masp - masp_undated_balance)?)
212}
213
214pub fn calculate_masp_rewards<S, TransToken>(
217 storage: &mut S,
218 token: &Address,
219 denomination: Denomination,
220 precision: Precision,
221 masp_epochs_per_year: u64,
222) -> Result<(u128, Precision)>
223where
224 S: StorageWrite + StorageRead,
225 TransToken: trans_token::Keys + trans_token::Read<S>,
226{
227 let masp_addr = MASP;
228
229 let total_native_tokens =
233 TransToken::get_effective_total_native_supply(storage)?;
234
235 let total_tokens_in_masp =
237 TransToken::read_balance(storage, token, &masp_addr)?;
238
239 let last_inflation: Amount = storage
241 .read(&masp_last_inflation_key::<TransToken>(token))?
242 .expect("failure to read last inflation");
243
244 let last_locked_amount: Amount = storage
245 .read(&masp_last_locked_amount_key::<TransToken>(token))?
246 .expect("failure to read last inflation");
247
248 let max_reward_rate: Dec = storage
250 .read(&masp_max_reward_rate_key::<TransToken>(token))?
251 .expect("max reward should properly decode");
252
253 let kp_gain_nom: Dec = storage
254 .read(&masp_kp_gain_key::<TransToken>(token))?
255 .expect("kp_gain_nom reward should properly decode");
256
257 let kd_gain_nom: Dec = storage
258 .read(&masp_kd_gain_key::<TransToken>(token))?
259 .expect("kd_gain_nom reward should properly decode");
260
261 let target_locked_amount: Amount = storage
262 .read(&masp_locked_amount_target_key::<TransToken>(token))?
263 .expect("locked ratio target should properly decode");
264
265 let target_locked_dec = Dec::try_from(target_locked_amount.raw_amount())
266 .expect("Should not fail to convert Uint to Dec");
267 let last_locked_dec = Dec::try_from(last_locked_amount.raw_amount())
268 .expect("Should not fail to convert Uint to Dec");
269
270 let inflation = compute_inflation(
272 total_tokens_in_masp.raw_amount(),
273 total_native_tokens.raw_amount(),
274 max_reward_rate,
275 last_inflation.raw_amount(),
276 kp_gain_nom,
277 kd_gain_nom,
278 masp_epochs_per_year,
279 target_locked_dec,
280 last_locked_dec,
281 );
282
283 let rewardable_tokens_in_masp =
285 get_masp_dated_balance::<S, TransToken>(storage, token)?;
286
287 let noterized_inflation = if rewardable_tokens_in_masp.is_zero() {
292 0u128
293 } else {
294 inflation
295 .checked_mul_div(
296 Uint::from(precision),
297 rewardable_tokens_in_masp.raw_amount(),
298 )
299 .and_then(|x| x.0.try_into().ok())
300 .unwrap_or_else(|| {
301 tracing::warn!(
302 "MASP inflation for {} assumed to be 0 because the \
303 computed value is too large. Please check the inflation \
304 parameters.",
305 *token
306 );
307 0u128
308 })
309 };
310 let inflation_amount = Amount::from_uint(
311 checked!(
312 rewardable_tokens_in_masp.raw_amount() / precision.into()
313 * Uint::from(noterized_inflation)
314 )?,
315 0,
316 )
317 .unwrap();
318 let denom_amount = DenominatedAmount::new(inflation_amount, denomination);
319 tracing::info!("MASP inflation for {token} is {denom_amount}");
320
321 tracing::debug!(
322 "Controller, call: total_in_masp {:?}, total_native_tokens {:?}, \
323 locked_target_amount {:?}, last_locked_amount {:?}, max_reward_rate \
324 {:?}, last_inflation {:?}, kp_gain_nom {:?}, kd_gain_nom {:?}, \
325 epochs_per_year {:?}",
326 total_tokens_in_masp,
327 total_native_tokens,
328 target_locked_amount,
329 last_locked_amount,
330 max_reward_rate,
331 last_inflation,
332 kp_gain_nom,
333 kd_gain_nom,
334 masp_epochs_per_year,
335 );
336 tracing::debug!("Token address: {:?}", token);
337 tracing::debug!("inflation from the pd controller {:?}", inflation);
338 tracing::debug!("total in the masp {:?}", total_tokens_in_masp);
339 tracing::debug!("precision {}", precision);
340 tracing::debug!("Noterized inflation: {}", noterized_inflation);
341
342 storage.write(
347 &masp_last_inflation_key::<TransToken>(token),
348 inflation_amount,
349 )?;
350
351 storage.write(
352 &masp_last_locked_amount_key::<TransToken>(token),
353 total_tokens_in_masp,
354 )?;
355
356 Ok((noterized_inflation, precision))
357}
358
359#[cfg(any(feature = "multicore", test))]
363fn update_native_conversions<S, TransToken>(
364 storage: &mut S,
365 token: &Address,
366 current_precision: &mut u128,
367 masp_epochs_per_year: u64,
368 masp_epoch: MaspEpoch,
369 current_convs: &mut BTreeMap<
370 (Address, Denomination, MaspDigitPos),
371 AllowedConversion,
372 >,
373) -> Result<(Denomination, (u128, u128))>
374where
375 S: StorageWrite + StorageRead + WithConversionState,
376 TransToken:
377 trans_token::Keys + trans_token::Read<S> + trans_token::Write<S>,
378{
379 let prev_masp_epoch =
380 masp_epoch.prev().ok_or_err_msg("MASP epoch underflow")?;
381 let denom = TransToken::read_denom(storage, token)?
382 .expect("failed to read token denomination");
383 let (reward, _precision) = calculate_masp_rewards::<S, TransToken>(
384 storage,
385 token,
386 denom,
387 *current_precision,
388 masp_epochs_per_year,
389 )?;
390 let current_precision_uint = Uint::from(*current_precision);
394 let reward = Uint::from(reward);
395 let new_precision = checked!(current_precision_uint + reward)?;
396 let new_precision = u128::try_from(new_precision).unwrap_or_else(|_| {
397 tracing::warn!(
398 "MASP precision for the native token {} is kept the same as in \
399 the last epoch because the computed value is too large. Please \
400 check the inflation parameters.",
401 token
402 );
403 *current_precision
404 });
405 for digit in MaspDigitPos::iter() {
406 let old_asset = encode_asset_type(
410 token.clone(),
411 denom,
412 digit,
413 Some(prev_masp_epoch),
414 )
415 .into_storage_result()?;
416 let new_asset =
417 encode_asset_type(token.clone(), denom, digit, Some(masp_epoch))
418 .into_storage_result()?;
419 let cur_conv = MaspAmount::from_pair(
423 old_asset,
424 i128::try_from(*current_precision)
425 .ok()
426 .and_then(i128::checked_neg)
427 .ok_or_err_msg("Current inflation overflow")?,
428 );
429 let new_conv = MaspAmount::from_pair(
430 new_asset,
431 i128::try_from(new_precision).into_storage_result()?,
432 );
433 current_convs.insert(
434 (token.clone(), denom, digit),
435 checked!(cur_conv + &new_conv)?.into(),
436 );
437 storage.conversion_state_mut().assets.insert(
439 old_asset,
440 ConversionLeaf {
441 token: token.clone(),
442 denom,
443 digit_pos: digit,
444 epoch: prev_masp_epoch,
445 conversion: MaspAmount::zero().into(),
446 leaf_pos: 0,
447 },
448 );
449 }
450 let reward_frac = (
452 new_precision
453 .checked_sub(*current_precision)
454 .unwrap_or_default(),
455 *current_precision,
456 );
457 let reward_precision_key = masp_reward_precision_key::<TransToken>(token);
459 storage.write(&reward_precision_key, new_precision)?;
460 *current_precision = new_precision;
461 Ok((denom, reward_frac))
462}
463
464#[cfg(any(feature = "multicore", test))]
468#[allow(clippy::too_many_arguments)]
469fn update_non_native_conversions<S, TransToken>(
470 storage: &mut S,
471 token: &Address,
472 base_native_precision: u128,
473 current_native_precision: u128,
474 masp_epochs_per_year: u64,
475 masp_epoch: MaspEpoch,
476 reward_assets: [AssetType; 4],
477 total_reward: &mut Amount,
478 current_convs: &mut BTreeMap<
479 (Address, Denomination, MaspDigitPos),
480 AllowedConversion,
481 >,
482) -> Result<Denomination>
483where
484 S: StorageWrite + StorageRead + WithConversionState,
485 TransToken:
486 trans_token::Keys + trans_token::Read<S> + trans_token::Write<S>,
487{
488 let prev_masp_epoch =
489 masp_epoch.prev().ok_or_err_msg("MASP epoch underflow")?;
490 let denom = TransToken::read_denom(storage, token)?
491 .expect("failed to read token denomination");
492 let precision =
493 calculate_masp_rewards_precision::<S, TransToken>(storage, token)?;
494 let (reward, precision) = calculate_masp_rewards::<S, TransToken>(
495 storage,
496 token,
497 denom,
498 precision,
499 masp_epochs_per_year,
500 )?;
501 let reward_uint = Uint::from(reward);
504 let base_native_precision_uint = Uint::from(base_native_precision);
505 let current_native_precision_uint = Uint::from(current_native_precision);
506 let real_reward = checked!(
507 (reward_uint * base_native_precision_uint)
508 / current_native_precision_uint
509 )?
510 .try_into()
511 .unwrap_or_else(|_| {
512 tracing::warn!(
513 "MASP reward for {} assumed to be 0 because the computed value is \
514 too large. Please check the inflation parameters.",
515 token
516 );
517 0u128
518 });
519 let precision_i128 = i128::try_from(precision).into_storage_result()?;
523 let real_reward_i128 = i128::try_from(real_reward).into_storage_result()?;
524 for digit in MaspDigitPos::iter() {
525 let old_asset = encode_asset_type(
529 token.clone(),
530 denom,
531 digit,
532 Some(prev_masp_epoch),
533 )
534 .into_storage_result()?;
535 let new_asset =
536 encode_asset_type(token.clone(), denom, digit, Some(masp_epoch))
537 .into_storage_result()?;
538
539 current_convs.insert(
540 (token.clone(), denom, digit),
541 checked!(
542 MaspAmount::from_pair(old_asset, -precision_i128)
543 + &MaspAmount::from_pair(new_asset, precision_i128)
544 + &MaspAmount::from_pair(
545 reward_assets[digit as usize],
546 real_reward_i128,
547 )
548 )?
549 .into(),
550 );
551 storage.conversion_state_mut().assets.insert(
553 old_asset,
554 ConversionLeaf {
555 token: token.clone(),
556 denom,
557 digit_pos: digit,
558 epoch: prev_masp_epoch,
559 conversion: MaspAmount::zero().into(),
560 leaf_pos: 0,
561 },
562 );
563 }
564 let addr_bal = get_masp_dated_balance::<S, TransToken>(storage, token)?;
566 *total_reward = total_reward
569 .checked_add(
570 addr_bal
571 .u128_eucl_div_rem((real_reward, precision))
572 .ok_or_else(|| {
573 Error::new_const("Total reward calculation overflow")
574 })?
575 .0,
576 )
577 .ok_or_else(|| Error::new_const("Total reward overflow"))?;
578 Ok(denom)
579}
580
581#[cfg(any(feature = "multicore", test))]
582fn apply_stored_conversion_updates<S, TransToken>(
585 storage: &mut S,
586 ep: &MaspEpoch,
587) -> Result<()>
588where
589 S: StorageWrite + StorageRead + WithConversionState,
590 TransToken:
591 trans_token::Keys + trans_token::Read<S> + trans_token::Write<S>,
592{
593 let scheduled_base_precision_key =
595 masp_scheduled_base_native_precision_key(ep);
596 if let Some(precision) = storage.read(&scheduled_base_precision_key)? {
597 let base_precision_key = masp_base_native_precision_key();
598 storage.write::<Precision>(&base_precision_key, precision)?;
599 }
600
601 let scheduled_reward_precision_key_prefix =
602 masp_scheduled_reward_precision_key_prefix(ep);
603 let mut precision_updates = BTreeMap::<_, Precision>::new();
604 for prec_result in iter_prefix_with_filter_map(
606 storage,
607 &scheduled_reward_precision_key_prefix,
608 is_masp_scheduled_reward_precision_key,
609 )? {
610 match prec_result {
611 Ok(((_ep, addr), precision)) => {
612 precision_updates.insert(addr, precision);
613 }
614 Err(err) => {
615 tracing::warn!("Encountered malformed precision: {}", err);
616 continue;
617 }
618 }
619 }
620 for (addr, precision) in precision_updates {
622 let reward_precision_key =
624 masp_reward_precision_key::<TransToken>(&addr);
625 storage.write(&reward_precision_key, precision)?;
626 }
627 let conversion_key_prefix = masp_conversion_key_prefix(ep);
628 let mut conversion_updates =
629 BTreeMap::<_, UncheckedAllowedConversion>::new();
630 for conv_result in iter_prefix_with_filter_map(
632 storage,
633 &conversion_key_prefix,
634 is_masp_conversion_key,
635 )? {
636 match conv_result {
637 Ok(((_ep, asset_type), conv)) => {
638 conversion_updates.insert(asset_type, conv);
639 }
640 Err(err) => {
641 tracing::warn!("Encountered malformed conversion: {}", err);
642 continue;
643 }
644 }
645 }
646 let assets = &mut storage.conversion_state_mut().assets;
648 for (asset_type, conv) in conversion_updates {
649 let Some(leaf) = assets.get_mut(&asset_type) else {
650 tracing::warn!(
651 "Encountered non-existent asset type: {}",
652 asset_type
653 );
654 continue;
655 };
656 leaf.conversion = conv.0;
657 }
658 storage.delete_prefix(&conversion_key_prefix)?;
660 storage.delete_prefix(&scheduled_reward_precision_key_prefix)?;
661 storage.delete(&scheduled_base_precision_key)?;
662 Ok(())
663}
664
665#[cfg(any(feature = "multicore", test))]
666pub fn update_allowed_conversions<S, Params, TransToken>(
668 storage: &mut S,
669) -> Result<()>
670where
671 S: StorageWrite + StorageRead + WithConversionState,
672 Params: parameters::Read<S>,
673 TransToken:
674 trans_token::Keys + trans_token::Read<S> + trans_token::Write<S>,
675{
676 use std::cmp::Ordering;
677
678 use masp_primitives::bls12_381;
679 use masp_primitives::ff::PrimeField;
680 use masp_primitives::merkle_tree::FrozenCommitmentTree;
681 use masp_primitives::sapling::Node;
682 use namada_core::masp::encode_reward_asset_types;
683 use namada_core::token::NATIVE_MAX_DECIMAL_PLACES;
684 use rayon::iter::{
685 IndexedParallelIterator, IntoParallelIterator, ParallelIterator,
686 };
687 use rayon::prelude::ParallelSlice;
688
689 use crate::mint_rewards;
690
691 let masp_epoch_multiplier = Params::masp_epoch_multiplier(storage)?;
693 let masp_epoch = MaspEpoch::try_from_epoch(
694 storage.get_block_epoch()?,
695 masp_epoch_multiplier,
696 )
697 .map_err(Error::new_const)?;
698 let Some(prev_masp_epoch) = masp_epoch.prev() else {
699 return Ok(());
700 };
701 apply_stored_conversion_updates::<_, TransToken>(
702 storage,
703 &prev_masp_epoch,
704 )?;
705 let token_map_key = masp_token_map_key();
706 let token_map: namada_core::masp::TokenMap =
707 storage.read(&token_map_key)?.unwrap_or_default();
708 let mut masp_reward_keys: Vec<_> = token_map.values().cloned().collect();
709 let mut masp_reward_denoms = BTreeMap::new();
710 let native_token = storage.get_native_token()?;
713 masp_reward_keys.sort_unstable_by(|x, y| {
714 if (*x == native_token) == (*y == native_token) {
715 Ordering::Equal
716 } else if *x == native_token {
717 Ordering::Less
718 } else {
719 Ordering::Greater
720 }
721 });
722 let mut total_deflated_reward = Amount::zero();
724
725 let reward_assets =
726 encode_reward_asset_types(&native_token).into_storage_result()?;
727 let mut current_convs = BTreeMap::<
729 (Address, Denomination, MaspDigitPos),
730 AllowedConversion,
731 >::new();
732 let base_native_precision =
734 read_base_native_precision::<_, TransToken>(storage, &native_token)?;
735 let mut current_native_precision = calculate_masp_rewards_precision::<
737 S,
738 TransToken,
739 >(storage, &native_token)?;
740
741 let epochs_per_year = Params::epochs_per_year(storage)?;
743 let masp_epochs_per_year =
744 checked!(epochs_per_year / masp_epoch_multiplier)?;
745 let mut native_reward_frac = None;
746 for token in &masp_reward_keys {
747 if *token == native_token {
750 let (denom, frac) = update_native_conversions::<_, TransToken>(
751 storage,
752 token,
753 &mut current_native_precision,
754 masp_epochs_per_year,
755 masp_epoch,
756 &mut current_convs,
757 )?;
758 masp_reward_denoms.insert(token.clone(), denom);
759 native_reward_frac = Some(frac);
760 } else {
761 let denom = update_non_native_conversions::<_, TransToken>(
762 storage,
763 token,
764 base_native_precision,
765 current_native_precision,
766 masp_epochs_per_year,
767 masp_epoch,
768 reward_assets,
769 &mut total_deflated_reward,
770 &mut current_convs,
771 )?;
772 masp_reward_denoms.insert(token.clone(), denom);
773 }
774 }
775 let non_native_reward = total_deflated_reward
777 .raw_amount()
778 .checked_mul_div(
779 current_native_precision.into(),
780 base_native_precision.into(),
781 )
782 .ok_or_else(|| Error::new_const("Total reward calculation overflow"))?;
783 let mut total_reward = Amount::from(non_native_reward.0);
786 if let Some(native_reward_frac) = native_reward_frac {
789 let addr_bal = TransToken::read_balance(storage, &native_token, &MASP)?;
791 let native_reward = addr_bal
794 .raw_amount()
795 .checked_mul_div(
796 native_reward_frac.0.into(),
797 native_reward_frac.1.into(),
798 )
799 .ok_or_else(|| Error::new_const("Three digit reward overflow"))?;
800 checked!(total_reward += native_reward.0.into())?;
802
803 let base_native_precision = Uint::from(base_native_precision);
804 let numerator = checked!(
809 native_reward.1 * base_native_precision
810 + Uint::from(native_reward_frac.1) * non_native_reward.1
811 )?;
812 let denominator =
813 checked!(base_native_precision * Uint::from(native_reward_frac.1))?;
814 if numerator >= denominator {
818 checked!(total_reward += 1.into())?;
819 }
820 }
821
822 let num_threads = rayon::current_num_threads();
825 let assets: Vec<_> = storage
827 .conversion_state_mut()
828 .assets
829 .values_mut()
830 .enumerate()
831 .collect();
832
833 #[allow(clippy::arithmetic_side_effects)]
834 let notes_per_thread_max = assets.len().div_ceil(num_threads);
835 #[allow(clippy::arithmetic_side_effects)]
837 let notes_per_thread_min = assets.len() / num_threads;
838
839 let conv_notes: Vec<Node> = assets
841 .into_par_iter()
842 .with_min_len(notes_per_thread_min)
843 .with_max_len(notes_per_thread_max)
844 .map(|(idx, leaf)| {
845 let cur_conv_key = (leaf.token.clone(), leaf.denom, leaf.digit_pos);
847 if let Some(current_conv) = current_convs.get(&cur_conv_key) {
848 #[allow(clippy::arithmetic_side_effects)]
850 {
851 leaf.conversion += current_conv.clone();
852 }
853 }
854 leaf.leaf_pos = idx;
856 Node::new(leaf.conversion.cmu().to_repr())
859 })
860 .collect();
861
862 mint_rewards::<S, TransToken>(storage, total_reward)?;
865
866 let mut notes_per_thread_rounded = 1;
870
871 #[allow(clippy::arithmetic_side_effects)]
873 while notes_per_thread_max > notes_per_thread_rounded * 4 {
874 notes_per_thread_rounded *= 2;
875 }
876 let tree_parts: Vec<_> = conv_notes
878 .par_chunks(notes_per_thread_rounded)
879 .map(FrozenCommitmentTree::new)
880 .collect();
881
882 storage.conversion_state_mut().tree =
885 FrozenCommitmentTree::merge(&tree_parts);
886 storage.write(
888 &crate::storage_key::masp_convert_anchor_key(),
889 namada_core::hash::Hash(
890 bls12_381::Scalar::from(storage.conversion_state().tree.root())
891 .to_bytes(),
892 ),
893 )?;
894
895 if !masp_reward_keys.contains(&native_token) {
896 masp_reward_denoms
899 .insert(native_token.clone(), NATIVE_MAX_DECIMAL_PLACES.into());
900 }
901 for (addr, denom) in masp_reward_denoms {
904 for digit in MaspDigitPos::iter() {
905 let new_asset =
908 encode_asset_type(addr.clone(), denom, digit, Some(masp_epoch))
909 .into_storage_result()?;
910 let tree_size = storage.conversion_state().tree.size();
911 storage.conversion_state_mut().assets.insert(
912 new_asset,
913 ConversionLeaf {
914 token: addr.clone(),
915 denom,
916 digit_pos: digit,
917 epoch: masp_epoch,
918 conversion: MaspAmount::zero().into(),
919 leaf_pos: tree_size,
920 },
921 );
922 }
923 }
924 let assets_hash =
926 Hash::sha256(storage.conversion_state().assets.serialize_to_vec());
927 storage.write(&masp_assets_hash_key(), assets_hash)?;
928
929 Ok(())
930}
931
932#[cfg(not(any(feature = "multicore", test)))]
934pub fn update_allowed_conversions<S, Params, TransToken>(
936 _storage: &mut S,
937) -> Result<()>
938where
939 S: StorageWrite + StorageRead + WithConversionState,
940 Params: parameters::Read<S>,
941 TransToken: trans_token::Keys,
942{
943 Ok(())
944}
945
946#[allow(clippy::arithmetic_side_effects)]
947#[cfg(test)]
948mod tests {
949 use std::str::FromStr;
950
951 use namada_core::address;
952 use namada_core::collections::HashMap;
953 use namada_core::dec::testing::arb_non_negative_dec;
954 use namada_core::token::testing::arb_amount;
955 use namada_state::testing::TestStorage;
956 use namada_trans_token::storage_key::{balance_key, minted_balance_key};
957 use namada_trans_token::write_denom;
958 use proptest::prelude::*;
959 use proptest::test_runner::Config;
960 use test_log::test;
961
962 use super::*;
963 use crate::ShieldedParams;
964
965 proptest! {
966 #![proptest_config(Config {
967 cases: 10,
968 .. Config::default()
969 })]
970 #[test]
971 fn test_updated_allowed_conversions(
972 initial_balance in arb_amount(),
973 masp_locked_ratio in arb_non_negative_dec(),
974 ) {
975 test_updated_allowed_conversions_aux(initial_balance, masp_locked_ratio)
976 }
977 }
978
979 fn test_updated_allowed_conversions_aux(
980 initial_balance: Amount,
981 masp_locked_ratio: Dec,
982 ) {
983 const ROUNDS: usize = 10;
984
985 let mut s = TestStorage::default();
986 {
988 namada_parameters::init_test_storage(&mut s).unwrap();
990
991 let token_params = ShieldedParams {
993 max_reward_rate: Dec::from_str("0.1").unwrap(),
994 kp_gain_nom: Dec::from_str("0.1").unwrap(),
995 kd_gain_nom: Dec::from_str("0.1").unwrap(),
996 locked_amount_target: 10_000_u64,
997 };
998
999 for (token_addr, (alias, denom)) in tokens() {
1000 namada_trans_token::write_params(&mut s, &token_addr).unwrap();
1001 crate::write_params::<_, namada_trans_token::Store<()>>(
1002 &token_params,
1003 &mut s,
1004 &token_addr,
1005 &denom,
1006 )
1007 .unwrap();
1008
1009 write_denom(&mut s, &token_addr, denom).unwrap();
1010
1011 let total_token_balance = initial_balance;
1013 s.write(&minted_balance_key(&token_addr), total_token_balance)
1014 .unwrap();
1015
1016 s.write(
1018 &balance_key(&token_addr, &address::MASP),
1019 masp_locked_ratio * total_token_balance,
1020 )
1021 .unwrap();
1022
1023 let token_map_key = masp_token_map_key();
1025 let mut token_map: namada_core::masp::TokenMap =
1026 s.read(&token_map_key).unwrap().unwrap_or_default();
1027 token_map.insert(alias.to_string(), token_addr.clone());
1028 s.write(&token_map_key, token_map).unwrap();
1029 }
1030 }
1031
1032 for i in 0..ROUNDS {
1033 println!("Round {i}");
1034 update_allowed_conversions::<
1035 _,
1036 namada_parameters::Store<_>,
1037 namada_trans_token::Store<_>,
1038 >(&mut s)
1039 .unwrap();
1040 println!();
1041 println!();
1042 }
1043 }
1044
1045 pub fn tokens() -> HashMap<Address, (&'static str, Denomination)> {
1046 vec![
1047 (address::testing::nam(), ("nam", 6.into())),
1048 (address::testing::btc(), ("btc", 8.into())),
1049 (address::testing::eth(), ("eth", 18.into())),
1050 (address::testing::dot(), ("dot", 10.into())),
1051 (address::testing::schnitzel(), ("schnitzel", 6.into())),
1052 (address::testing::apfel(), ("apfel", 6.into())),
1053 (address::testing::kartoffel(), ("kartoffel", 6.into())),
1054 ]
1055 .into_iter()
1056 .collect()
1057 }
1058
1059 #[test]
1060 fn test_masp_inflation_playground() {
1061 let denom = Uint::from(1_000_000); let total_tokens = 10_000_000_000_u64; let mut total_tokens = Uint::from(total_tokens) * denom;
1064 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
1067 * Dec::try_from(locked_tokens_target).unwrap())
1068 .to_uint()
1069 .unwrap();
1070 let epochs_per_year = 730_u64; let max_reward_rate = Dec::from_str("0.01").unwrap(); let mut last_inflation_amount = Uint::zero();
1073 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;
1077 let mut locked_tokens_last = init_locked_tokens;
1078
1079 let num_rounds = 10;
1080 println!();
1081
1082 for round in 0..num_rounds {
1083 let inflation = compute_inflation(
1084 locked_amount,
1085 total_tokens,
1086 max_reward_rate,
1087 last_inflation_amount,
1088 p_gain_nom,
1089 d_gain_nom,
1090 epochs_per_year,
1091 Dec::try_from(locked_tokens_target).unwrap(),
1092 Dec::try_from(locked_tokens_last).unwrap(),
1093 );
1094
1095 let rate = Dec::try_from(inflation).unwrap()
1096 * Dec::from(epochs_per_year)
1097 / Dec::try_from(total_tokens).unwrap();
1098
1099 println!(
1100 "Round {round}: Locked amount: {locked_amount}, inflation \
1101 rate: {rate} -- (raw infl: {inflation})",
1102 );
1103 last_inflation_amount = inflation;
1106 total_tokens += inflation;
1107 locked_tokens_last = locked_amount;
1108
1109 let change_staked_tokens = Uint::from(2) * locked_tokens_target;
1110 locked_amount += change_staked_tokens;
1111 }
1112 }
1113}