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;
10#[cfg(any(feature = "multicore", test))]
11use masp_primitives::transaction::components::I128Sum as MaspAmount;
12use namada_controller::PDController;
13use namada_core::address::{Address, MASP};
14use namada_core::arith::checked;
15#[cfg(any(feature = "multicore", test))]
16use namada_core::arith::CheckedAdd;
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;
22#[cfg(any(feature = "multicore", test))]
23use namada_core::masp::encode_asset_type;
24#[cfg(any(feature = "multicore", test))]
25use namada_core::masp::MaspEpoch;
26#[cfg(any(feature = "multicore", test))]
27use namada_core::token::MaspDigitPos;
28use namada_core::token::{Amount, DenominatedAmount, Denomination};
29use namada_core::uint::Uint;
30use namada_systems::{parameters, trans_token};
31
32#[cfg(any(feature = "multicore", test))]
33use crate::storage_key::{masp_assets_hash_key, masp_token_map_key};
34use crate::storage_key::{
35 masp_kd_gain_key, masp_kp_gain_key, masp_last_inflation_key,
36 masp_last_locked_amount_key, masp_locked_amount_target_key,
37 masp_max_reward_rate_key,
38};
39#[cfg(any(feature = "multicore", test))]
40use crate::{ConversionLeaf, Error, OptionExt, ResultExt};
41use crate::{Result, StorageRead, StorageWrite, WithConversionState};
42
43#[allow(clippy::too_many_arguments)]
45pub fn compute_inflation(
46 locked_amount: Uint,
47 total_native_amount: Uint,
48 max_reward_rate: Dec,
49 last_inflation_amount: Uint,
50 p_gain_nom: Dec,
51 d_gain_nom: Dec,
52 epochs_per_year: u64,
53 target_amount: Dec,
54 last_amount: Dec,
55) -> Uint {
56 let controller = PDController::new(
57 total_native_amount,
58 max_reward_rate,
59 last_inflation_amount,
60 p_gain_nom,
61 d_gain_nom,
62 epochs_per_year,
63 target_amount,
64 last_amount,
65 );
66
67 let metric = Dec::try_from(locked_amount)
68 .expect("Should not fail to convert Uint to Dec");
69 let control_coeff = max_reward_rate
70 .checked_div(controller.get_epochs_per_year())
71 .expect("Control coefficient overflow");
72
73 tracing::debug!(
74 "Shielded token inflation inputs: {controller:#?}, metric: {metric}, \
75 coefficient: {control_coeff}"
76 );
77 controller
78 .compute_inflation(control_coeff, metric)
79 .expect("Inflation calculation overflow")
80}
81
82pub fn calculate_masp_rewards_precision<S, TransToken>(
85 storage: &mut S,
86 addr: &Address,
87) -> Result<(u128, Denomination)>
88where
89 S: StorageWrite + StorageRead,
90 TransToken: trans_token::Read<S>,
91{
92 let denomination = TransToken::read_denom(storage, addr)?
93 .expect("failed to read token denomination");
94 let precision_denom = std::cmp::max(u32::from(denomination.0), 3)
102 .checked_sub(3)
103 .expect("Cannot underflow");
104 Ok((checked!(10u128 ^ precision_denom)?, denomination))
105}
106
107pub fn calculate_masp_rewards<S, TransToken>(
110 storage: &mut S,
111 token: &Address,
112 denomination: Denomination,
113 precision: u128,
114 masp_epochs_per_year: u64,
115) -> Result<(u128, u128)>
116where
117 S: StorageWrite + StorageRead,
118 TransToken: trans_token::Keys + trans_token::Read<S>,
119{
120 let masp_addr = MASP;
121
122 let total_native_tokens =
126 TransToken::get_effective_total_native_supply(storage)?;
127
128 let total_tokens_in_masp =
130 TransToken::read_balance(storage, token, &masp_addr)?;
131
132 let last_inflation: Amount = storage
134 .read(&masp_last_inflation_key::<TransToken>(token))?
135 .expect("failure to read last inflation");
136
137 let last_locked_amount: Amount = storage
138 .read(&masp_last_locked_amount_key::<TransToken>(token))?
139 .expect("failure to read last inflation");
140
141 let max_reward_rate: Dec = storage
143 .read(&masp_max_reward_rate_key::<TransToken>(token))?
144 .expect("max reward should properly decode");
145
146 let kp_gain_nom: Dec = storage
147 .read(&masp_kp_gain_key::<TransToken>(token))?
148 .expect("kp_gain_nom reward should properly decode");
149
150 let kd_gain_nom: Dec = storage
151 .read(&masp_kd_gain_key::<TransToken>(token))?
152 .expect("kd_gain_nom reward should properly decode");
153
154 let target_locked_amount: Amount = storage
155 .read(&masp_locked_amount_target_key::<TransToken>(token))?
156 .expect("locked ratio target should properly decode");
157
158 let target_locked_dec = Dec::try_from(target_locked_amount.raw_amount())
159 .expect("Should not fail to convert Uint to Dec");
160 let last_locked_dec = Dec::try_from(last_locked_amount.raw_amount())
161 .expect("Should not fail to convert Uint to Dec");
162
163 let inflation = compute_inflation(
165 total_tokens_in_masp.raw_amount(),
166 total_native_tokens.raw_amount(),
167 max_reward_rate,
168 last_inflation.raw_amount(),
169 kp_gain_nom,
170 kd_gain_nom,
171 masp_epochs_per_year,
172 target_locked_dec,
173 last_locked_dec,
174 );
175
176 let noterized_inflation = if total_tokens_in_masp.is_zero() {
181 0u128
182 } else {
183 inflation
184 .checked_mul_div(
185 Uint::from(precision),
186 total_tokens_in_masp.raw_amount(),
187 )
188 .and_then(|x| x.0.try_into().ok())
189 .unwrap_or_else(|| {
190 tracing::warn!(
191 "MASP inflation for {} assumed to be 0 because the \
192 computed value is too large. Please check the inflation \
193 parameters.",
194 *token
195 );
196 0u128
197 })
198 };
199 let inflation_amount = Amount::from_uint(
200 checked!(
201 total_tokens_in_masp.raw_amount() / precision.into()
202 * Uint::from(noterized_inflation)
203 )?,
204 0,
205 )
206 .unwrap();
207 let denom_amount = DenominatedAmount::new(inflation_amount, denomination);
208 tracing::info!("MASP inflation for {token} is {denom_amount}");
209
210 tracing::debug!(
211 "Controller, call: total_in_masp {:?}, total_native_tokens {:?}, \
212 locked_target_amount {:?}, last_locked_amount {:?}, max_reward_rate \
213 {:?}, last_inflation {:?}, kp_gain_nom {:?}, kd_gain_nom {:?}, \
214 epochs_per_year {:?}",
215 total_tokens_in_masp,
216 total_native_tokens,
217 target_locked_amount,
218 last_locked_amount,
219 max_reward_rate,
220 last_inflation,
221 kp_gain_nom,
222 kd_gain_nom,
223 masp_epochs_per_year,
224 );
225 tracing::debug!("Token address: {:?}", token);
226 tracing::debug!("inflation from the pd controller {:?}", inflation);
227 tracing::debug!("total in the masp {:?}", total_tokens_in_masp);
228 tracing::debug!("precision {}", precision);
229 tracing::debug!("Noterized inflation: {}", noterized_inflation);
230
231 storage.write(
236 &masp_last_inflation_key::<TransToken>(token),
237 inflation_amount,
238 )?;
239
240 storage.write(
241 &masp_last_locked_amount_key::<TransToken>(token),
242 total_tokens_in_masp,
243 )?;
244
245 Ok((noterized_inflation, precision))
246}
247
248#[cfg(any(feature = "multicore", test))]
252fn update_native_conversions<S, TransToken>(
253 storage: &mut S,
254 token: &Address,
255 normed_inflation: u128,
256 masp_epochs_per_year: u64,
257 masp_epoch: MaspEpoch,
258 total_reward: &mut Amount,
259 current_convs: &mut BTreeMap<
260 (Address, Denomination, MaspDigitPos),
261 AllowedConversion,
262 >,
263) -> Result<Denomination>
264where
265 S: StorageWrite + StorageRead + WithConversionState,
266 TransToken:
267 trans_token::Keys + trans_token::Read<S> + trans_token::Write<S>,
268{
269 let prev_masp_epoch =
270 masp_epoch.prev().ok_or_err_msg("MASP epoch underflow")?;
271 let denom = TransToken::read_denom(storage, token)?
272 .expect("failed to read token denomination");
273 let (reward, _precision) = calculate_masp_rewards::<S, TransToken>(
274 storage,
275 token,
276 denom,
277 normed_inflation,
278 masp_epochs_per_year,
279 )?;
280 let inflation_uint = Uint::from(normed_inflation);
284 let reward = Uint::from(reward);
285 let new_normed_inflation = checked!(inflation_uint + reward)?;
286 let new_normed_inflation = u128::try_from(new_normed_inflation)
287 .unwrap_or_else(|_| {
288 tracing::warn!(
289 "MASP inflation for the native token {} is kept the same as \
290 in the last epoch because the computed value is too large. \
291 Please check the inflation parameters.",
292 token
293 );
294 normed_inflation
295 });
296 for digit in MaspDigitPos::iter() {
297 let old_asset = encode_asset_type(
301 token.clone(),
302 denom,
303 digit,
304 Some(prev_masp_epoch),
305 )
306 .into_storage_result()?;
307 let new_asset =
308 encode_asset_type(token.clone(), denom, digit, Some(masp_epoch))
309 .into_storage_result()?;
310 let cur_conv = MaspAmount::from_pair(
314 old_asset,
315 i128::try_from(normed_inflation)
316 .ok()
317 .and_then(i128::checked_neg)
318 .ok_or_err_msg("Current inflation overflow")?,
319 );
320 let new_conv = MaspAmount::from_pair(
321 new_asset,
322 i128::try_from(new_normed_inflation).into_storage_result()?,
323 );
324 current_convs.insert(
325 (token.clone(), denom, digit),
326 checked!(cur_conv + &new_conv)?.into(),
327 );
328 storage.conversion_state_mut().assets.insert(
330 old_asset,
331 ConversionLeaf {
332 token: token.clone(),
333 denom,
334 digit_pos: digit,
335 epoch: prev_masp_epoch,
336 conversion: MaspAmount::zero().into(),
337 leaf_pos: 0,
338 },
339 );
340 }
341 let addr_bal = TransToken::read_balance(storage, token, &MASP)?;
343 let native_reward = addr_bal
346 .u128_eucl_div_rem((new_normed_inflation, normed_inflation))
347 .ok_or_else(|| Error::new_const("Three digit reward overflow"))?;
348 *total_reward = total_reward
349 .checked_add(
350 native_reward
351 .0
352 .checked_add(native_reward.1)
353 .unwrap_or(Amount::max())
354 .checked_sub(addr_bal)
355 .unwrap_or_default(),
356 )
357 .ok_or_else(|| Error::new_const("Three digit total reward overflow"))?;
358 let _ = storage
360 .conversion_state_mut()
361 .normed_inflation
362 .insert(new_normed_inflation);
363 Ok(denom)
364}
365
366#[cfg(any(feature = "multicore", test))]
370#[allow(clippy::too_many_arguments)]
371fn update_non_native_conversions<S, TransToken>(
372 storage: &mut S,
373 token: &Address,
374 ref_inflation: u128,
375 normed_inflation: u128,
376 masp_epochs_per_year: u64,
377 masp_epoch: MaspEpoch,
378 reward_assets: [AssetType; 4],
379 total_reward: &mut Amount,
380 current_convs: &mut BTreeMap<
381 (Address, Denomination, MaspDigitPos),
382 AllowedConversion,
383 >,
384) -> Result<Denomination>
385where
386 S: StorageWrite + StorageRead + WithConversionState,
387 TransToken:
388 trans_token::Keys + trans_token::Read<S> + trans_token::Write<S>,
389{
390 let prev_masp_epoch =
391 masp_epoch.prev().ok_or_err_msg("MASP epoch underflow")?;
392 let (precision, denom) =
393 calculate_masp_rewards_precision::<S, TransToken>(storage, token)?;
394 let (reward, precision) = calculate_masp_rewards::<S, TransToken>(
395 storage,
396 token,
397 denom,
398 precision,
399 masp_epochs_per_year,
400 )?;
401 let reward_uint = Uint::from(reward);
404 let ref_inflation_uint = Uint::from(ref_inflation);
405 let inflation_uint = Uint::from(normed_inflation);
406 let real_reward =
407 checked!((reward_uint * ref_inflation_uint) / inflation_uint)?
408 .try_into()
409 .unwrap_or_else(|_| {
410 tracing::warn!(
411 "MASP reward for {} assumed to be 0 because the computed \
412 value is too large. Please check the inflation \
413 parameters.",
414 token
415 );
416 0u128
417 });
418 let precision_i128 = i128::try_from(precision).into_storage_result()?;
422 let real_reward_i128 = i128::try_from(real_reward).into_storage_result()?;
423 for digit in MaspDigitPos::iter() {
424 let old_asset = encode_asset_type(
428 token.clone(),
429 denom,
430 digit,
431 Some(prev_masp_epoch),
432 )
433 .into_storage_result()?;
434 let new_asset =
435 encode_asset_type(token.clone(), denom, digit, Some(masp_epoch))
436 .into_storage_result()?;
437
438 current_convs.insert(
439 (token.clone(), denom, digit),
440 checked!(
441 MaspAmount::from_pair(old_asset, -precision_i128)
442 + &MaspAmount::from_pair(new_asset, precision_i128)
443 + &MaspAmount::from_pair(
444 reward_assets[digit as usize],
445 real_reward_i128,
446 )
447 )?
448 .into(),
449 );
450 storage.conversion_state_mut().assets.insert(
452 old_asset,
453 ConversionLeaf {
454 token: token.clone(),
455 denom,
456 digit_pos: digit,
457 epoch: prev_masp_epoch,
458 conversion: MaspAmount::zero().into(),
459 leaf_pos: 0,
460 },
461 );
462 }
463 let addr_bal = TransToken::read_balance(storage, token, &MASP)?;
465 *total_reward = total_reward
468 .checked_add(
469 addr_bal
470 .u128_eucl_div_rem((reward, precision))
471 .ok_or_else(|| {
472 Error::new_const("Total reward calculation overflow")
473 })?
474 .0,
475 )
476 .ok_or_else(|| Error::new_const("Total reward overflow"))?;
477 Ok(denom)
478}
479
480#[cfg(any(feature = "multicore", test))]
481pub fn update_allowed_conversions<S, Params, TransToken>(
483 storage: &mut S,
484) -> Result<()>
485where
486 S: StorageWrite + StorageRead + WithConversionState,
487 Params: parameters::Read<S>,
488 TransToken:
489 trans_token::Keys + trans_token::Read<S> + trans_token::Write<S>,
490{
491 use std::cmp::Ordering;
492
493 use masp_primitives::bls12_381;
494 use masp_primitives::ff::PrimeField;
495 use masp_primitives::merkle_tree::FrozenCommitmentTree;
496 use masp_primitives::sapling::Node;
497 use namada_core::masp::encode_reward_asset_types;
498 use namada_core::token::NATIVE_MAX_DECIMAL_PLACES;
499 use rayon::iter::{
500 IndexedParallelIterator, IntoParallelIterator, ParallelIterator,
501 };
502 use rayon::prelude::ParallelSlice;
503
504 use crate::mint_rewards;
505
506 let token_map_key = masp_token_map_key();
507 let token_map: namada_core::masp::TokenMap =
508 storage.read(&token_map_key)?.unwrap_or_default();
509 let mut masp_reward_keys: Vec<_> = token_map.values().cloned().collect();
510 let mut masp_reward_denoms = BTreeMap::new();
511 let native_token = storage.get_native_token()?;
514 masp_reward_keys.sort_unstable_by(|x, y| {
515 if (*x == native_token) == (*y == native_token) {
516 Ordering::Equal
517 } else if *x == native_token {
518 Ordering::Less
519 } else {
520 Ordering::Greater
521 }
522 });
523 let mut total_reward = Amount::zero();
525
526 let reward_assets =
531 encode_reward_asset_types(&native_token).into_storage_result()?;
532 let mut current_convs = BTreeMap::<
534 (Address, Denomination, MaspDigitPos),
535 AllowedConversion,
536 >::new();
537 let ref_inflation = calculate_masp_rewards_precision::<S, TransToken>(
539 storage,
540 &native_token,
541 )?
542 .0;
543
544 let masp_epoch_multiplier = Params::masp_epoch_multiplier(storage)?;
546 let masp_epoch = MaspEpoch::try_from_epoch(
547 storage.get_block_epoch()?,
548 masp_epoch_multiplier,
549 )
550 .map_err(Error::new_const)?;
551 if masp_epoch.prev().is_none() {
552 return Ok(());
553 }
554 let epochs_per_year = Params::epochs_per_year(storage)?;
555 let masp_epochs_per_year =
556 checked!(epochs_per_year / masp_epoch_multiplier)?;
557 for token in &masp_reward_keys {
558 let normed_inflation = *storage
560 .conversion_state_mut()
561 .normed_inflation
562 .get_or_insert(ref_inflation);
563
564 let denom = if *token == native_token {
567 update_native_conversions::<_, TransToken>(
568 storage,
569 token,
570 normed_inflation,
571 masp_epochs_per_year,
572 masp_epoch,
573 &mut total_reward,
574 &mut current_convs,
575 )
576 } else {
577 update_non_native_conversions::<_, TransToken>(
578 storage,
579 token,
580 ref_inflation,
581 normed_inflation,
582 masp_epochs_per_year,
583 masp_epoch,
584 reward_assets,
585 &mut total_reward,
586 &mut current_convs,
587 )
588 }?;
589 masp_reward_denoms.insert(token.clone(), denom);
590 }
591
592 let num_threads = rayon::current_num_threads();
595 let assets: Vec<_> = storage
597 .conversion_state_mut()
598 .assets
599 .values_mut()
600 .enumerate()
601 .collect();
602
603 #[allow(clippy::arithmetic_side_effects)]
604 let notes_per_thread_max = (assets.len() + num_threads - 1) / num_threads;
605 #[allow(clippy::arithmetic_side_effects)]
607 let notes_per_thread_min = assets.len() / num_threads;
608
609 let conv_notes: Vec<Node> = assets
611 .into_par_iter()
612 .with_min_len(notes_per_thread_min)
613 .with_max_len(notes_per_thread_max)
614 .map(|(idx, leaf)| {
615 let cur_conv_key = (leaf.token.clone(), leaf.denom, leaf.digit_pos);
617 if let Some(current_conv) = current_convs.get(&cur_conv_key) {
618 #[allow(clippy::arithmetic_side_effects)]
620 {
621 leaf.conversion += current_conv.clone();
622 }
623 }
624 leaf.leaf_pos = idx;
626 Node::new(leaf.conversion.cmu().to_repr())
629 })
630 .collect();
631
632 mint_rewards::<S, TransToken>(storage, total_reward)?;
635
636 let mut notes_per_thread_rounded = 1;
640
641 #[allow(clippy::arithmetic_side_effects)]
643 while notes_per_thread_max > notes_per_thread_rounded * 4 {
644 notes_per_thread_rounded *= 2;
645 }
646 let tree_parts: Vec<_> = conv_notes
648 .par_chunks(notes_per_thread_rounded)
649 .map(FrozenCommitmentTree::new)
650 .collect();
651
652 storage.conversion_state_mut().tree =
655 FrozenCommitmentTree::merge(&tree_parts);
656 storage.write(
658 &crate::storage_key::masp_convert_anchor_key(),
659 namada_core::hash::Hash(
660 bls12_381::Scalar::from(storage.conversion_state().tree.root())
661 .to_bytes(),
662 ),
663 )?;
664
665 if !masp_reward_keys.contains(&native_token) {
666 masp_reward_denoms
669 .insert(native_token.clone(), NATIVE_MAX_DECIMAL_PLACES.into());
670 }
671 for (addr, denom) in masp_reward_denoms {
674 for digit in MaspDigitPos::iter() {
675 let new_asset =
678 encode_asset_type(addr.clone(), denom, digit, Some(masp_epoch))
679 .into_storage_result()?;
680 let tree_size = storage.conversion_state().tree.size();
681 storage.conversion_state_mut().assets.insert(
682 new_asset,
683 ConversionLeaf {
684 token: addr.clone(),
685 denom,
686 digit_pos: digit,
687 epoch: masp_epoch,
688 conversion: MaspAmount::zero().into(),
689 leaf_pos: tree_size,
690 },
691 );
692 }
693 }
694 let assets_hash =
696 Hash::sha256(storage.conversion_state().assets.serialize_to_vec());
697 storage.write(&masp_assets_hash_key(), assets_hash)?;
698
699 Ok(())
700}
701
702#[cfg(not(any(feature = "multicore", test)))]
704pub fn update_allowed_conversions<S, Params, TransToken>(
706 _storage: &mut S,
707) -> Result<()>
708where
709 S: StorageWrite + StorageRead + WithConversionState,
710 Params: parameters::Read<S>,
711 TransToken: trans_token::Keys,
712{
713 Ok(())
714}
715
716#[allow(clippy::arithmetic_side_effects)]
717#[cfg(test)]
718mod tests {
719 use std::str::FromStr;
720
721 use namada_core::address;
722 use namada_core::collections::HashMap;
723 use namada_core::dec::testing::arb_non_negative_dec;
724 use namada_core::token::testing::arb_amount;
725 use namada_state::testing::TestStorage;
726 use namada_trans_token::storage_key::{balance_key, minted_balance_key};
727 use namada_trans_token::write_denom;
728 use proptest::prelude::*;
729 use proptest::test_runner::Config;
730 use test_log::test;
731
732 use super::*;
733 use crate::ShieldedParams;
734
735 proptest! {
736 #![proptest_config(Config {
737 cases: 10,
738 .. Config::default()
739 })]
740 #[test]
741 fn test_updated_allowed_conversions(
742 initial_balance in arb_amount(),
743 masp_locked_ratio in arb_non_negative_dec(),
744 ) {
745 test_updated_allowed_conversions_aux(initial_balance, masp_locked_ratio)
746 }
747 }
748
749 fn test_updated_allowed_conversions_aux(
750 initial_balance: Amount,
751 masp_locked_ratio: Dec,
752 ) {
753 const ROUNDS: usize = 10;
754
755 let mut s = TestStorage::default();
756 {
758 namada_parameters::init_test_storage(&mut s).unwrap();
760
761 let token_params = ShieldedParams {
763 max_reward_rate: Dec::from_str("0.1").unwrap(),
764 kp_gain_nom: Dec::from_str("0.1").unwrap(),
765 kd_gain_nom: Dec::from_str("0.1").unwrap(),
766 locked_amount_target: 10_000_u64,
767 };
768
769 for (token_addr, (alias, denom)) in tokens() {
770 namada_trans_token::write_params(&mut s, &token_addr).unwrap();
771 crate::write_params::<_, namada_trans_token::Store<()>>(
772 &token_params,
773 &mut s,
774 &token_addr,
775 &denom,
776 )
777 .unwrap();
778
779 write_denom(&mut s, &token_addr, denom).unwrap();
780
781 let total_token_balance = initial_balance;
783 s.write(&minted_balance_key(&token_addr), total_token_balance)
784 .unwrap();
785
786 s.write(
788 &balance_key(&token_addr, &address::MASP),
789 masp_locked_ratio * total_token_balance,
790 )
791 .unwrap();
792
793 let token_map_key = masp_token_map_key();
795 let mut token_map: namada_core::masp::TokenMap =
796 s.read(&token_map_key).unwrap().unwrap_or_default();
797 token_map.insert(alias.to_string(), token_addr.clone());
798 s.write(&token_map_key, token_map).unwrap();
799 }
800 }
801
802 for i in 0..ROUNDS {
803 println!("Round {i}");
804 update_allowed_conversions::<
805 _,
806 namada_parameters::Store<_>,
807 namada_trans_token::Store<_>,
808 >(&mut s)
809 .unwrap();
810 println!();
811 println!();
812 }
813 }
814
815 pub fn tokens() -> HashMap<Address, (&'static str, Denomination)> {
816 vec![
817 (address::testing::nam(), ("nam", 6.into())),
818 (address::testing::btc(), ("btc", 8.into())),
819 (address::testing::eth(), ("eth", 18.into())),
820 (address::testing::dot(), ("dot", 10.into())),
821 (address::testing::schnitzel(), ("schnitzel", 6.into())),
822 (address::testing::apfel(), ("apfel", 6.into())),
823 (address::testing::kartoffel(), ("kartoffel", 6.into())),
824 ]
825 .into_iter()
826 .collect()
827 }
828
829 #[test]
830 fn test_masp_inflation_playground() {
831 let denom = Uint::from(1_000_000); let total_tokens = 10_000_000_000_u64; let mut total_tokens = Uint::from(total_tokens) * denom;
834 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
837 * Dec::try_from(locked_tokens_target).unwrap())
838 .to_uint()
839 .unwrap();
840 let epochs_per_year = 730_u64; let max_reward_rate = Dec::from_str("0.01").unwrap(); let mut last_inflation_amount = Uint::zero();
843 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;
847 let mut locked_tokens_last = init_locked_tokens;
848
849 let num_rounds = 10;
850 println!();
851
852 for round in 0..num_rounds {
853 let inflation = compute_inflation(
854 locked_amount,
855 total_tokens,
856 max_reward_rate,
857 last_inflation_amount,
858 p_gain_nom,
859 d_gain_nom,
860 epochs_per_year,
861 Dec::try_from(locked_tokens_target).unwrap(),
862 Dec::try_from(locked_tokens_last).unwrap(),
863 );
864
865 let rate = Dec::try_from(inflation).unwrap()
866 * Dec::from(epochs_per_year)
867 / Dec::try_from(total_tokens).unwrap();
868
869 println!(
870 "Round {round}: Locked amount: {locked_amount}, inflation \
871 rate: {rate} -- (raw infl: {inflation})",
872 );
873 last_inflation_amount = inflation;
876 total_tokens += inflation;
877 locked_tokens_last = locked_amount;
878
879 let change_staked_tokens = Uint::from(2) * locked_tokens_target;
880 locked_amount += change_staked_tokens;
881 }
882 }
883}