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