1use alloc::boxed::Box;
20use core::marker::PhantomData;
21use pezframe_support::traits::{
22 fungible, fungibles, tokens::imbalance::ResolveTo, Contains, ContainsPair, Currency, Defensive,
23 Get, Imbalance, OnUnbalanced, OriginTrait,
24};
25use pezpallet_asset_tx_payment::HandleCredit;
26use pezpallet_collator_selection::StakingPotAccountId;
27use pezsp_runtime::traits::Zero;
28use xcm::latest::{
29 Asset, AssetId, Fungibility, Fungibility::Fungible, Junction, Junctions::Here, Location,
30 Parent, WeightLimit,
31};
32use xcm_executor::traits::ConvertLocation;
33
34pub type AccountIdOf<T> = <T as pezframe_system::Config>::AccountId;
36
37pub type NegativeImbalance<T> = <pezpallet_balances::Pezpallet<T> as Currency<
39 <T as pezframe_system::Config>::AccountId,
40>>::NegativeImbalance;
41
42#[deprecated(
44 note = "ToStakingPot is deprecated and will be removed after March 2024. Please use pezframe_support::traits::tokens::imbalance::ResolveTo instead."
45)]
46pub struct ToStakingPot<R>(PhantomData<R>);
47#[allow(deprecated)]
48impl<R> OnUnbalanced<NegativeImbalance<R>> for ToStakingPot<R>
49where
50 R: pezpallet_balances::Config + pezpallet_collator_selection::Config,
51 AccountIdOf<R>: From<pezkuwi_primitives::AccountId> + Into<pezkuwi_primitives::AccountId>,
52 <R as pezframe_system::Config>::RuntimeEvent: From<pezpallet_balances::Event<R>>,
53{
54 fn on_nonzero_unbalanced(amount: NegativeImbalance<R>) {
55 let staking_pot = <pezpallet_collator_selection::Pezpallet<R>>::account_id();
56 <pezpallet_balances::Pezpallet<R>>::resolve_creating(&staking_pot, amount);
58 }
59}
60
61pub struct DealWithFees<R>(PhantomData<R>);
64impl<R> OnUnbalanced<fungible::Credit<R::AccountId, pezpallet_balances::Pezpallet<R>>>
65 for DealWithFees<R>
66where
67 R: pezpallet_balances::Config + pezpallet_collator_selection::Config,
68 AccountIdOf<R>: From<pezkuwi_primitives::AccountId> + Into<pezkuwi_primitives::AccountId>,
69 <R as pezframe_system::Config>::RuntimeEvent: From<pezpallet_balances::Event<R>>,
70{
71 fn on_unbalanceds(
72 mut fees_then_tips: impl Iterator<
73 Item = fungible::Credit<R::AccountId, pezpallet_balances::Pezpallet<R>>,
74 >,
75 ) {
76 if let Some(mut fees) = fees_then_tips.next() {
77 if let Some(tips) = fees_then_tips.next() {
78 tips.merge_into(&mut fees);
79 }
80 ResolveTo::<StakingPotAccountId<R>, pezpallet_balances::Pezpallet<R>>::on_unbalanced(
81 fees,
82 )
83 }
84 }
85}
86
87pub struct AssetsToBlockAuthor<R, I>(PhantomData<(R, I)>);
90impl<R, I> HandleCredit<AccountIdOf<R>, pezpallet_assets::Pezpallet<R, I>>
91 for AssetsToBlockAuthor<R, I>
92where
93 I: 'static,
94 R: pezpallet_authorship::Config + pezpallet_assets::Config<I>,
95 AccountIdOf<R>: From<pezkuwi_primitives::AccountId> + Into<pezkuwi_primitives::AccountId>,
96{
97 fn handle_credit(credit: fungibles::Credit<AccountIdOf<R>, pezpallet_assets::Pezpallet<R, I>>) {
98 use pezframe_support::traits::fungibles::Balanced;
99 if let Some(author) = pezpallet_authorship::Pezpallet::<R>::author() {
100 let _ = pezpallet_assets::Pezpallet::<R, I>::resolve(&author, credit).defensive();
102 }
103 }
104}
105
106pub struct NonZeroIssuance<AccountId, Assets>(PhantomData<(AccountId, Assets)>);
108impl<AccountId, Assets> Contains<<Assets as fungibles::Inspect<AccountId>>::AssetId>
109 for NonZeroIssuance<AccountId, Assets>
110where
111 Assets: fungibles::Inspect<AccountId>,
112{
113 fn contains(id: &<Assets as fungibles::Inspect<AccountId>>::AssetId) -> bool {
114 !Assets::total_issuance(id.clone()).is_zero()
115 }
116}
117
118pub struct AssetExists<AccountId, Assets>(PhantomData<(AccountId, Assets)>);
120impl<AccountId, Assets> Contains<<Assets as fungibles::Inspect<AccountId>>::AssetId>
121 for AssetExists<AccountId, Assets>
122where
123 Assets: fungibles::Inspect<AccountId>,
124{
125 fn contains(id: &<Assets as fungibles::Inspect<AccountId>>::AssetId) -> bool {
126 Assets::asset_exists(id.clone())
127 }
128}
129
130pub struct AssetsFrom<T>(PhantomData<T>);
132impl<T: Get<Location>> ContainsPair<Asset, Location> for AssetsFrom<T> {
133 fn contains(asset: &Asset, origin: &Location) -> bool {
134 let loc = T::get();
135 &loc == origin
136 && matches!(asset, Asset { id: AssetId(asset_loc), fun: Fungible(_a) }
137 if asset_loc.match_and_split(&loc).is_some())
138 }
139}
140
141pub type BalanceOf<T> = <pezpallet_balances::Pezpallet<T> as Currency<
143 <T as pezframe_system::Config>::AccountId,
144>>::Balance;
145
146pub struct ToParentTreasury<TreasuryAccount, AccountIdConverter, T>(
149 PhantomData<(TreasuryAccount, AccountIdConverter, T)>,
150);
151
152impl<TreasuryAccount, AccountIdConverter, T> OnUnbalanced<NegativeImbalance<T>>
153 for ToParentTreasury<TreasuryAccount, AccountIdConverter, T>
154where
155 T: pezpallet_balances::Config + pezpallet_xcm::Config + pezframe_system::Config,
156 <<T as pezframe_system::Config>::RuntimeOrigin as OriginTrait>::AccountId: From<AccountIdOf<T>>,
157 [u8; 32]: From<<T as pezframe_system::Config>::AccountId>,
158 TreasuryAccount: Get<AccountIdOf<T>>,
159 AccountIdConverter: ConvertLocation<AccountIdOf<T>>,
160 BalanceOf<T>: Into<Fungibility>,
161{
162 fn on_unbalanced(amount: NegativeImbalance<T>) {
163 let amount = match amount.drop_zero() {
164 Ok(..) => return,
165 Err(amount) => amount,
166 };
167 let imbalance = amount.peek();
168 let root_location: Location = Here.into();
169 let root_account: AccountIdOf<T> =
170 match AccountIdConverter::convert_location(&root_location) {
171 Some(a) => a,
172 None => {
173 tracing::warn!(target: "xcm::on_unbalanced", "Failed to convert root origin into account id");
174 return;
175 },
176 };
177 let treasury_account: AccountIdOf<T> = TreasuryAccount::get();
178
179 <pezpallet_balances::Pezpallet<T>>::resolve_creating(&root_account, amount);
180
181 let result = <pezpallet_xcm::Pezpallet<T>>::limited_teleport_assets(
182 <<T as pezframe_system::Config>::RuntimeOrigin>::root(),
183 Box::new(Parent.into()),
184 Box::new(
185 Junction::AccountId32 { network: None, id: treasury_account.into() }
186 .into_location()
187 .into(),
188 ),
189 Box::new((Parent, imbalance).into()),
190 Box::new(Parent.into()),
191 WeightLimit::Unlimited,
192 );
193
194 if let Err(err) = result {
195 tracing::warn!(target: "xcm::on_unbalanced", error=?err, "Failed to teleport slashed assets");
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use pezframe_support::{
204 derive_impl, parameter_types,
205 traits::{ConstU32, FindAuthor, ValidatorRegistration},
206 PalletId,
207 };
208 use pezframe_system::{limits, EnsureRoot};
209 use pezkuwi_primitives::AccountId;
210 use pezpallet_collator_selection::IdentityCollator;
211 use pezsp_core::H256;
212 use pezsp_runtime::{
213 traits::{BlakeTwo256, IdentityLookup},
214 BuildStorage, Perbill,
215 };
216 use xcm::prelude::*;
217
218 type Block = pezframe_system::mocking::MockBlock<Test>;
219 const TEST_ACCOUNT: AccountId = AccountId::new([1; 32]);
220
221 pezframe_support::construct_runtime!(
222 pub enum Test
223 {
224 System: pezframe_system::{Pezpallet, Call, Config<T>, Storage, Event<T>},
225 Balances: pezpallet_balances::{Pezpallet, Call, Storage, Config<T>, Event<T>},
226 CollatorSelection: pezpallet_collator_selection::{Pezpallet, Call, Storage, Event<T>},
227 }
228 );
229
230 parameter_types! {
231 pub BlockLength: limits::BlockLength = limits::BlockLength::max(2 * 1024);
232 pub const AvailableBlockRatio: Perbill = Perbill::one();
233 }
234
235 #[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
236 impl pezframe_system::Config for Test {
237 type BaseCallFilter = pezframe_support::traits::Everything;
238 type RuntimeOrigin = RuntimeOrigin;
239 type Nonce = u64;
240 type RuntimeCall = RuntimeCall;
241 type Hash = H256;
242 type Hashing = BlakeTwo256;
243 type AccountId = AccountId;
244 type Lookup = IdentityLookup<Self::AccountId>;
245 type Block = Block;
246 type RuntimeEvent = RuntimeEvent;
247 type BlockLength = BlockLength;
248 type BlockWeights = ();
249 type DbWeight = ();
250 type Version = ();
251 type PalletInfo = PalletInfo;
252 type AccountData = pezpallet_balances::AccountData<u64>;
253 type OnNewAccount = ();
254 type OnKilledAccount = ();
255 type SystemWeightInfo = ();
256 type SS58Prefix = ();
257 type OnSetCode = ();
258 type MaxConsumers = pezframe_support::traits::ConstU32<16>;
259 }
260
261 #[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
262 impl pezpallet_balances::Config for Test {
263 type AccountStore = System;
264 }
265
266 pub struct OneAuthor;
267 impl FindAuthor<AccountId> for OneAuthor {
268 fn find_author<'a, I>(_: I) -> Option<AccountId>
269 where
270 I: 'a,
271 {
272 Some(TEST_ACCOUNT)
273 }
274 }
275
276 pub struct IsRegistered;
277 impl ValidatorRegistration<AccountId> for IsRegistered {
278 fn is_registered(_id: &AccountId) -> bool {
279 true
280 }
281 }
282
283 parameter_types! {
284 pub const PotId: PalletId = PalletId(*b"PotStake");
285 }
286
287 impl pezpallet_collator_selection::Config for Test {
288 type RuntimeEvent = RuntimeEvent;
289 type Currency = Balances;
290 type UpdateOrigin = EnsureRoot<AccountId>;
291 type PotId = PotId;
292 type MaxCandidates = ConstU32<20>;
293 type MinEligibleCollators = ConstU32<1>;
294 type MaxInvulnerables = ConstU32<20>;
295 type ValidatorId = <Self as pezframe_system::Config>::AccountId;
296 type ValidatorIdOf = IdentityCollator;
297 type ValidatorRegistration = IsRegistered;
298 type KickThreshold = ();
299 type WeightInfo = ();
300 }
301
302 impl pezpallet_authorship::Config for Test {
303 type FindAuthor = OneAuthor;
304 type EventHandler = ();
305 }
306
307 pub fn new_test_ext() -> pezsp_io::TestExternalities {
308 let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
309 pezpallet_balances::GenesisConfig::<Test>::default()
311 .assimilate_storage(&mut t)
312 .unwrap();
313 t.into()
314 }
315
316 #[test]
317 fn test_fees_and_tip_split() {
318 new_test_ext().execute_with(|| {
319 let fee =
320 <pezpallet_balances::Pezpallet<Test> as pezframe_support::traits::fungible::Balanced<
321 AccountId,
322 >>::issue(10);
323 let tip =
324 <pezpallet_balances::Pezpallet<Test> as pezframe_support::traits::fungible::Balanced<
325 AccountId,
326 >>::issue(20);
327
328 assert_eq!(Balances::free_balance(TEST_ACCOUNT), 0);
329
330 DealWithFees::on_unbalanceds(vec![fee, tip].into_iter());
331
332 assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 30);
334 });
335 }
336
337 #[test]
338 fn assets_from_filters_correctly() {
339 parameter_types! {
340 pub SomeSiblingTeyrchain: Location = (Parent, Teyrchain(1234)).into();
341 }
342
343 let asset_location = SomeSiblingTeyrchain::get()
344 .pushed_with_interior(GeneralIndex(42))
345 .expect("location will only have 2 junctions; qed");
346 let asset = Asset { id: AssetId(asset_location), fun: 1_000_000u128.into() };
347 assert!(
348 AssetsFrom::<SomeSiblingTeyrchain>::contains(&asset, &SomeSiblingTeyrchain::get()),
349 "AssetsFrom should allow assets from any of its interior locations"
350 );
351 }
352}