|
1 | 1 | use crate::traits::AssetDepositLimiter; |
2 | 2 | use crate::types::LockdownStatus; |
3 | | -use crate::{AssetLockdownState, Config, Event, Pallet}; |
| 3 | +use crate::{AssetLockdownState, Config, Pallet}; |
4 | 4 | use frame_support::traits::Get; |
5 | 5 | use frame_system::pallet_prelude::BlockNumberFor; |
6 | 6 | use orml_traits::currency::OnDeposit; |
7 | | -use orml_traits::{GetByKey, Handler, Happened}; |
| 7 | +use orml_traits::GetByKey; |
8 | 8 | use sp_runtime::{SaturatedConversion, Saturating}; |
9 | 9 | use sp_std::marker::PhantomData; |
10 | 10 |
|
11 | | -//TODO: check every he usage of saturaring |
| 11 | +#[derive(Debug)] |
| 12 | +enum DepositAction<Balance> { |
| 13 | + InitialDeposit, |
| 14 | + LockdownActive, |
| 15 | + LockdownExpired, |
| 16 | + PeriodExpired, |
| 17 | + WithinPeriod { last_issuance: Balance }, |
| 18 | +} |
12 | 19 |
|
13 | | -//TODO: add prop tests, also for save deposit, so only the claimed and specified amount is returned |
| 20 | +struct DepositContext<T: Config> { |
| 21 | + action: DepositAction<T::Balance>, |
| 22 | + limit: T::Balance, |
| 23 | + lockdown_until: BlockNumberFor<T>, |
| 24 | + asset_issuance: T::Balance, |
| 25 | +} |
14 | 26 |
|
15 | | -//TODO: integration tests |
16 | | -// when other parachain is hacked, we should be able to lock down the asset issuance |
17 | | -// sending VDOT to us and exchange it |
18 | | -// Other parachain can send us VDOT, we exchange it for DOT and lock down the issuance |
19 | | -// example: byfrist xcm transfer to our asset crossing, so we should mint it, but we should not alllow |
20 | | -// Other test: replicate the problem we had last week, where you could mint any amount of sharetoken in stablepool |
21 | | -// --- we set limit for sharetoken in asset registry, when add liquidty, but when this cross this, it should reserve |
| 27 | +pub struct IssuanceIncreaseFuse<T: Config>(PhantomData<T>); |
22 | 28 |
|
23 | | -//TODO: SET GLOBAL LIMIT TO 1 DAY |
| 29 | +impl<T: Config> IssuanceIncreaseFuse<T> { |
| 30 | + /// Check if the given amount can be minted for the asset |
| 31 | + pub fn can_mint(currency_id: T::AssetId, amount: T::Balance) -> bool { |
| 32 | + let Some(context) = Self::get_context(currency_id) else { |
| 33 | + return true; |
| 34 | + }; |
24 | 35 |
|
25 | | -pub struct IssuanceIncreaseFuse<T: Config>(PhantomData<T>); |
| 36 | + match &context.action { |
| 37 | + DepositAction::LockdownActive => false, |
| 38 | + DepositAction::InitialDeposit | DepositAction::LockdownExpired | DepositAction::PeriodExpired => { |
| 39 | + amount <= context.limit |
| 40 | + } |
| 41 | + DepositAction::WithinPeriod { last_issuance } => { |
| 42 | + let issuance_increase_in_period = context.asset_issuance.saturating_sub(*last_issuance); |
| 43 | + issuance_increase_in_period <= context.limit |
| 44 | + } |
| 45 | + } |
| 46 | + } |
26 | 47 |
|
27 | | -impl<T: Config> OnDeposit<T::AccountId, T::AssetId, T::Balance> for IssuanceIncreaseFuse<T> { |
28 | | - fn on_deposit(currency_id: T::AssetId, who: &T::AccountId, amount: T::Balance) -> sp_runtime::DispatchResult { |
| 48 | + fn get_context(currency_id: T::AssetId) -> Option<DepositContext<T>> { |
29 | 49 | let period = <T::DepositLimiter as AssetDepositLimiter<T::AccountId, T::AssetId, T::Balance>>::Period::get(); |
30 | 50 | if period == 0u128 { |
31 | | - // no limit |
32 | | - return Ok(()); |
| 51 | + return None; |
33 | 52 | } |
34 | 53 |
|
35 | 54 | let Some(limit) = |
36 | 55 | <T::DepositLimiter as AssetDepositLimiter<T::AccountId, T::AssetId, T::Balance>>::DepositLimit::get( |
37 | 56 | ¤cy_id, |
38 | 57 | ) |
39 | 58 | else { |
40 | | - return Ok(()); |
| 59 | + return None; |
41 | 60 | }; |
42 | 61 |
|
43 | 62 | let current_block = <frame_system::Pallet<T>>::block_number(); |
44 | | - let lockdown_until: BlockNumberFor<T> = current_block.saturating_add(period.saturated_into()); |
| 63 | + let lockdown_until = current_block.saturating_add(period.saturated_into()); |
45 | 64 | let asset_issuance = |
46 | 65 | <T::DepositLimiter as AssetDepositLimiter<T::AccountId, T::AssetId, T::Balance>>::Issuance::get( |
47 | 66 | ¤cy_id, |
48 | 67 | ); |
49 | 68 |
|
50 | | - match AssetLockdownState::<T>::get(currency_id) { |
51 | | - None => { |
52 | | - // This happens only once - to set the initial state |
53 | | - // Still check if this new deposit does not exceed the limit |
54 | | - // Set the issuance without this amount because we want the amount to count towards the limit per period. |
55 | | - if amount > limit { |
56 | | - let to_lock = amount.saturating_sub(limit); |
57 | | - Pallet::<T>::do_lock_deposit(&who, currency_id, to_lock)?; |
58 | | - Pallet::<T>::do_lockdown_asset(currency_id, lockdown_until)?; |
59 | | - } else { |
60 | | - Pallet::<T>::do_reset_deposit_limits(currency_id, amount)?; |
61 | | - } |
62 | | - } |
63 | | - Some(LockdownStatus::Locked(until)) if until > current_block => { |
64 | | - // Asset in lockdown |
65 | | - Pallet::<T>::do_lock_deposit(&who, currency_id, amount)?; |
66 | | - } |
67 | | - Some(LockdownStatus::Locked(_)) => { |
68 | | - // Lockdown expired |
69 | | - // Check if this new deposit does not exceed the limit and lock it down again if it does. |
70 | | - if amount > limit { |
71 | | - let to_lock = amount.saturating_sub(limit); |
72 | | - Pallet::<T>::do_lock_deposit(&who, currency_id, to_lock)?; |
73 | | - Pallet::<T>::do_lockdown_asset(currency_id, lockdown_until)?; |
| 69 | + let state = AssetLockdownState::<T>::get(currency_id); |
| 70 | + let action = Self::classify_state(state, current_block, period); |
| 71 | + |
| 72 | + Some(DepositContext { |
| 73 | + action, |
| 74 | + limit, |
| 75 | + lockdown_until, |
| 76 | + asset_issuance, |
| 77 | + }) |
| 78 | + } |
| 79 | + |
| 80 | + fn classify_state( |
| 81 | + state: Option<LockdownStatus<BlockNumberFor<T>, T::Balance>>, |
| 82 | + current_block: BlockNumberFor<T>, |
| 83 | + period: u128, |
| 84 | + ) -> DepositAction<T::Balance> { |
| 85 | + match state { |
| 86 | + None => DepositAction::InitialDeposit, |
| 87 | + Some(LockdownStatus::Locked(until)) if until > current_block => DepositAction::LockdownActive, |
| 88 | + Some(LockdownStatus::Locked(_)) => DepositAction::LockdownExpired, |
| 89 | + Some(LockdownStatus::Unlocked((last_reset_at, last_issuance))) => { |
| 90 | + if last_reset_at.saturating_add(period.saturated_into()) <= current_block { |
| 91 | + DepositAction::PeriodExpired |
74 | 92 | } else { |
75 | | - Pallet::<T>::do_lift_lockdown(currency_id, amount)?; |
| 93 | + DepositAction::WithinPeriod { last_issuance } |
76 | 94 | } |
77 | 95 | } |
78 | | - Some(LockdownStatus::Unlocked((last_reset_at, _))) |
79 | | - if last_reset_at.saturating_add(period.saturated_into()) <= current_block => |
80 | | - { |
81 | | - // The period is over, so we can reset the limit. |
82 | | - // But first, we must check if this new deposit does not exceed the limit. |
83 | | - if amount > limit { |
84 | | - let to_lock = amount.saturating_sub(limit); |
85 | | - Pallet::<T>::do_lock_deposit(&who, currency_id, to_lock)?; |
86 | | - Pallet::<T>::do_lockdown_asset(currency_id, lockdown_until)?; |
87 | | - } else { |
88 | | - Pallet::<T>::do_reset_deposit_limits(currency_id, amount)?; |
89 | | - } |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + fn process_deposit_action( |
| 100 | + context: &DepositContext<T>, |
| 101 | + who: &T::AccountId, |
| 102 | + currency_id: T::AssetId, |
| 103 | + amount: T::Balance, |
| 104 | + ) -> sp_runtime::DispatchResult { |
| 105 | + match &context.action { |
| 106 | + DepositAction::InitialDeposit | DepositAction::PeriodExpired => { |
| 107 | + Self::handle_limit_reset(context, who, currency_id, amount) |
90 | 108 | } |
91 | | - Some(LockdownStatus::Unlocked((_, last_issuance))) => { |
92 | | - // If the period is not over, we check the limit by comparing issuance increase. |
93 | | - let issuance_increase_in_period = asset_issuance.saturating_sub(last_issuance); |
94 | | - if issuance_increase_in_period > limit { |
95 | | - // We should lock only the excess, not all new deposit |
96 | | - // Formula: to_lock = current_issuance - (last_issuance + limit) |
97 | | - let to_lock = asset_issuance.saturating_sub(last_issuance.saturating_add(limit)); |
98 | | - debug_assert!(to_lock <= amount); |
99 | | - Pallet::<T>::do_lock_deposit(&who, currency_id, to_lock)?; |
100 | | - Pallet::<T>::do_lockdown_asset(currency_id, lockdown_until)?; |
101 | | - } |
| 109 | + DepositAction::LockdownExpired => Self::handle_lockdown_expired(context, who, currency_id, amount), |
| 110 | + DepositAction::LockdownActive => Pallet::<T>::do_lock_deposit(who, currency_id, amount), |
| 111 | + DepositAction::WithinPeriod { last_issuance } => { |
| 112 | + Self::handle_within_period(context, who, currency_id, amount, *last_issuance) |
102 | 113 | } |
103 | | - }; |
| 114 | + } |
| 115 | + } |
104 | 116 |
|
| 117 | + fn handle_limit_reset( |
| 118 | + context: &DepositContext<T>, |
| 119 | + who: &T::AccountId, |
| 120 | + currency_id: T::AssetId, |
| 121 | + amount: T::Balance, |
| 122 | + ) -> sp_runtime::DispatchResult { |
| 123 | + if amount > context.limit { |
| 124 | + let to_lock = amount.saturating_sub(context.limit); |
| 125 | + Pallet::<T>::do_lock_deposit(who, currency_id, to_lock)?; |
| 126 | + Pallet::<T>::do_lockdown_asset(currency_id, context.lockdown_until)?; |
| 127 | + } else { |
| 128 | + Pallet::<T>::do_reset_deposit_limits(currency_id, amount)?; |
| 129 | + } |
| 130 | + Ok(()) |
| 131 | + } |
| 132 | + |
| 133 | + fn handle_lockdown_expired( |
| 134 | + context: &DepositContext<T>, |
| 135 | + who: &T::AccountId, |
| 136 | + currency_id: T::AssetId, |
| 137 | + amount: T::Balance, |
| 138 | + ) -> sp_runtime::DispatchResult { |
| 139 | + if amount > context.limit { |
| 140 | + let to_lock = amount.saturating_sub(context.limit); |
| 141 | + Pallet::<T>::do_lock_deposit(who, currency_id, to_lock)?; |
| 142 | + Pallet::<T>::do_lockdown_asset(currency_id, context.lockdown_until)?; |
| 143 | + } else { |
| 144 | + Pallet::<T>::do_lift_lockdown(currency_id, amount)?; |
| 145 | + } |
105 | 146 | Ok(()) |
106 | 147 | } |
| 148 | + |
| 149 | + fn handle_within_period( |
| 150 | + context: &DepositContext<T>, |
| 151 | + who: &T::AccountId, |
| 152 | + currency_id: T::AssetId, |
| 153 | + _amount: T::Balance, |
| 154 | + last_issuance: T::Balance, |
| 155 | + ) -> sp_runtime::DispatchResult { |
| 156 | + let issuance_increase_in_period = context.asset_issuance.saturating_sub(last_issuance); |
| 157 | + if issuance_increase_in_period > context.limit { |
| 158 | + let to_lock = context |
| 159 | + .asset_issuance |
| 160 | + .saturating_sub(last_issuance.saturating_add(context.limit)); |
| 161 | + Pallet::<T>::do_lock_deposit(who, currency_id, to_lock)?; |
| 162 | + Pallet::<T>::do_lockdown_asset(currency_id, context.lockdown_until)?; |
| 163 | + } |
| 164 | + Ok(()) |
| 165 | + } |
| 166 | +} |
| 167 | + |
| 168 | +impl<T: Config> OnDeposit<T::AccountId, T::AssetId, T::Balance> for IssuanceIncreaseFuse<T> { |
| 169 | + fn on_deposit(currency_id: T::AssetId, who: &T::AccountId, amount: T::Balance) -> sp_runtime::DispatchResult { |
| 170 | + let Some(context) = Self::get_context(currency_id) else { |
| 171 | + return Ok(()); |
| 172 | + }; |
| 173 | + |
| 174 | + Self::process_deposit_action(&context, who, currency_id, amount) |
| 175 | + } |
107 | 176 | } |
0 commit comments