1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
//! # Services Payment pallet
18
//!
19
//! This pallet allows for block creation services to be paid for by a
20
//! containerChain.
21

            
22
#![cfg_attr(not(feature = "std"), no_std)]
23

            
24
use {
25
    cumulus_primitives_core::ParaId,
26
    frame_support::{
27
        pallet_prelude::*,
28
        sp_runtime::{traits::Zero, Saturating},
29
        traits::{
30
            tokens::ExistenceRequirement, Currency, EnsureOriginWithArg, OnUnbalanced,
31
            WithdrawReasons,
32
        },
33
    },
34
    frame_system::pallet_prelude::*,
35
    scale_info::prelude::vec::Vec,
36
    serde::{Deserialize, Serialize},
37
    sp_io::hashing::blake2_256,
38
    sp_runtime::{traits::TrailingZeroInput, DispatchError},
39
    tp_traits::{AuthorNotingHook, BlockNumber, CollatorAssignmentHook, CollatorAssignmentTip},
40
};
41

            
42
#[cfg(any(test, feature = "runtime-benchmarks"))]
43
mod benchmarks;
44
#[cfg(test)]
45
mod mock;
46

            
47
#[cfg(test)]
48
mod tests;
49
pub mod weights;
50
pub use weights::WeightInfo;
51

            
52
pub use pallet::*;
53

            
54
1086
#[frame_support::pallet]
55
pub mod pallet {
56
    use super::*;
57

            
58
    #[pallet::config]
59
    pub trait Config: frame_system::Config {
60
        /// The overarching event type.
61
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
62
        /// Handlers for fees
63
        type OnChargeForBlock: OnUnbalanced<NegativeImbalanceOf<Self>>;
64
        type OnChargeForCollatorAssignment: OnUnbalanced<NegativeImbalanceOf<Self>>;
65
        type OnChargeForCollatorAssignmentTip: OnUnbalanced<NegativeImbalanceOf<Self>>;
66

            
67
        /// Currency type for fee payment
68
        type Currency: Currency<Self::AccountId>;
69
        /// Provider of a block cost which can adjust from block to block
70
        type ProvideBlockProductionCost: ProvideBlockProductionCost<Self>;
71
        /// Provider of a block cost which can adjust from block to block
72
        type ProvideCollatorAssignmentCost: ProvideCollatorAssignmentCost<Self>;
73

            
74
        /// The maximum number of block production credits that can be accumulated
75
        #[pallet::constant]
76
        type FreeBlockProductionCredits: Get<BlockNumberFor<Self>>;
77

            
78
        /// The maximum number of collator assigment production credits that can be accumulated
79
        #[pallet::constant]
80
        type FreeCollatorAssignmentCredits: Get<u32>;
81
        /// Owner of the container chain, can call some only-owner methods
82
        type ManagerOrigin: EnsureOriginWithArg<Self::RuntimeOrigin, ParaId>;
83

            
84
        type WeightInfo: WeightInfo;
85
    }
86

            
87
    #[pallet::error]
88
    pub enum Error<T> {
89
        InsufficientFundsToPurchaseCredits,
90
        InsufficientCredits,
91
        CreditPriceTooExpensive,
92
    }
93

            
94
515102
    #[pallet::pallet]
95
    pub struct Pallet<T>(PhantomData<T>);
96

            
97
    #[pallet::event]
98
234
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
99
    pub enum Event<T: Config> {
100
        CreditsPurchased {
101
            para_id: ParaId,
102
            payer: T::AccountId,
103
            credit: BalanceOf<T>,
104
        },
105
        BlockProductionCreditBurned {
106
            para_id: ParaId,
107
            credits_remaining: BlockNumberFor<T>,
108
        },
109
        CollatorAssignmentCreditBurned {
110
            para_id: ParaId,
111
            credits_remaining: u32,
112
        },
113
        CollatorAssignmentTipCollected {
114
            para_id: ParaId,
115
            payer: T::AccountId,
116
            tip: BalanceOf<T>,
117
        },
118
        BlockProductionCreditsSet {
119
            para_id: ParaId,
120
            credits: BlockNumberFor<T>,
121
        },
122
        RefundAddressUpdated {
123
            para_id: ParaId,
124
            refund_address: Option<T::AccountId>,
125
        },
126
        MaxCorePriceUpdated {
127
            para_id: ParaId,
128
            max_core_price: Option<u128>,
129
        },
130
        CollatorAssignmentCreditsSet {
131
            para_id: ParaId,
132
            credits: u32,
133
        },
134
    }
135

            
136
    #[pallet::storage]
137
    pub type BlockProductionCredits<T: Config> =
138
        StorageMap<_, Blake2_128Concat, ParaId, BlockNumberFor<T>, OptionQuery>;
139

            
140
    #[pallet::storage]
141
    pub type CollatorAssignmentCredits<T: Config> =
142
        StorageMap<_, Blake2_128Concat, ParaId, u32, OptionQuery>;
143

            
144
    /// List of para ids that have already been given free credits
145
    #[pallet::storage]
146
    pub type GivenFreeCredits<T: Config> = StorageMap<_, Blake2_128Concat, ParaId, (), OptionQuery>;
147

            
148
    /// Refund address
149
    #[pallet::storage]
150
    pub type RefundAddress<T: Config> =
151
        StorageMap<_, Blake2_128Concat, ParaId, T::AccountId, OptionQuery>;
152

            
153
    /// Max core price for parathread in relay chain currency
154
    #[pallet::storage]
155
    pub type MaxCorePrice<T: Config> = StorageMap<_, Blake2_128Concat, ParaId, u128, OptionQuery>;
156

            
157
    /// Max tip for collator assignment on congestion
158
    #[pallet::storage]
159
    pub type MaxTip<T: Config> = StorageMap<_, Blake2_128Concat, ParaId, BalanceOf<T>, OptionQuery>;
160

            
161
6009
    #[pallet::call]
162
    impl<T: Config> Pallet<T>
163
    where
164
        BlockNumberFor<T>: Into<BalanceOf<T>>,
165
    {
166
        #[pallet::call_index(0)]
167
        #[pallet::weight(T::WeightInfo::purchase_credits())]
168
        pub fn purchase_credits(
169
            origin: OriginFor<T>,
170
            para_id: ParaId,
171
            credit: BalanceOf<T>,
172
696
        ) -> DispatchResultWithPostInfo {
173
696
            let account = ensure_signed(origin)?;
174
696
            let parachain_tank = Self::parachain_tank(para_id);
175
696
            T::Currency::transfer(
176
696
                &account,
177
696
                &parachain_tank,
178
696
                credit,
179
696
                ExistenceRequirement::KeepAlive,
180
696
            )?;
181

            
182
234
            Self::deposit_event(Event::<T>::CreditsPurchased {
183
234
                para_id,
184
234
                payer: account,
185
234
                credit,
186
234
            });
187
234

            
188
234
            Ok(().into())
189
        }
190

            
191
        /// Set the number of block production credits for this para_id without paying for them.
192
        /// Can only be called by root.
193
        #[pallet::call_index(1)]
194
        #[pallet::weight(T::WeightInfo::set_block_production_credits())]
195
        pub fn set_block_production_credits(
196
            origin: OriginFor<T>,
197
            para_id: ParaId,
198
            free_block_credits: BlockNumberFor<T>,
199
99
        ) -> DispatchResultWithPostInfo {
200
99
            ensure_root(origin)?;
201

            
202
            Self::set_free_block_production_credits(&para_id, free_block_credits);
203

            
204
            Ok(().into())
205
        }
206

            
207
        /// Helper to set and cleanup the `GivenFreeCredits` storage.
208
        /// Can only be called by root.
209
        #[pallet::call_index(2)]
210
        #[pallet::weight(T::WeightInfo::set_given_free_credits())]
211
        pub fn set_given_free_credits(
212
            origin: OriginFor<T>,
213
            para_id: ParaId,
214
            given_free_credits: bool,
215
60
        ) -> DispatchResultWithPostInfo {
216
60
            ensure_root(origin)?;
217

            
218
            if given_free_credits {
219
                GivenFreeCredits::<T>::insert(para_id, ());
220
            } else {
221
                GivenFreeCredits::<T>::remove(para_id);
222
            }
223

            
224
            Ok(().into())
225
        }
226

            
227
        /// Call index to set the refund address for non-spent tokens
228
        #[pallet::call_index(3)]
229
        #[pallet::weight(T::WeightInfo::set_refund_address())]
230
        pub fn set_refund_address(
231
            origin: OriginFor<T>,
232
            para_id: ParaId,
233
            refund_address: Option<T::AccountId>,
234
36
        ) -> DispatchResultWithPostInfo {
235
36
            T::ManagerOrigin::ensure_origin(origin, &para_id)?;
236

            
237
            if let Some(refund_address) = refund_address.clone() {
238
                RefundAddress::<T>::insert(para_id, refund_address.clone());
239
            } else {
240
                RefundAddress::<T>::remove(para_id);
241
            }
242

            
243
            Self::deposit_event(Event::<T>::RefundAddressUpdated {
244
                para_id,
245
                refund_address,
246
            });
247

            
248
            Ok(().into())
249
        }
250

            
251
        /// Set the number of block production credits for this para_id without paying for them.
252
        /// Can only be called by root.
253
        #[pallet::call_index(4)]
254
        #[pallet::weight(T::WeightInfo::set_block_production_credits())]
255
        pub fn set_collator_assignment_credits(
256
            origin: OriginFor<T>,
257
            para_id: ParaId,
258
            free_collator_assignment_credits: u32,
259
33
        ) -> DispatchResultWithPostInfo {
260
33
            ensure_root(origin)?;
261

            
262
            Self::set_free_collator_assignment_credits(&para_id, free_collator_assignment_credits);
263

            
264
            Ok(().into())
265
        }
266

            
267
        /// Max core price for parathread in relay chain currency
268
        #[pallet::call_index(5)]
269
        #[pallet::weight(T::WeightInfo::set_max_core_price())]
270
        pub fn set_max_core_price(
271
            origin: OriginFor<T>,
272
            para_id: ParaId,
273
            max_core_price: Option<u128>,
274
66
        ) -> DispatchResultWithPostInfo {
275
66
            T::ManagerOrigin::ensure_origin(origin, &para_id)?;
276

            
277
            if let Some(max_core_price) = max_core_price {
278
                MaxCorePrice::<T>::insert(para_id, max_core_price);
279
            } else {
280
                MaxCorePrice::<T>::remove(para_id);
281
            }
282

            
283
            Self::deposit_event(Event::<T>::MaxCorePriceUpdated {
284
                para_id,
285
                max_core_price,
286
            });
287

            
288
            Ok(().into())
289
        }
290

            
291
        /// Set the maximum tip a container chain is willing to pay to be assigned a collator on congestion.
292
        /// Can only be called by container chain manager.
293
        #[pallet::call_index(6)]
294
        #[pallet::weight(T::WeightInfo::set_max_tip())]
295
        pub fn set_max_tip(
296
            origin: OriginFor<T>,
297
            para_id: ParaId,
298
            max_tip: Option<BalanceOf<T>>,
299
90
        ) -> DispatchResultWithPostInfo {
300
90
            T::ManagerOrigin::ensure_origin(origin, &para_id)?;
301

            
302
            if let Some(max_tip) = max_tip {
303
                MaxTip::<T>::insert(para_id, max_tip);
304
            } else {
305
                MaxTip::<T>::remove(para_id);
306
            }
307

            
308
            Ok(().into())
309
        }
310
    }
311

            
312
    impl<T: Config> Pallet<T> {
313
        /// Burn a credit for the given para. Deducts one credit if possible, errors otherwise.
314
        pub fn burn_block_production_free_credit_for_para(
315
            para_id: &ParaId,
316
        ) -> DispatchResultWithPostInfo {
317
            let existing_credits =
318
                BlockProductionCredits::<T>::get(para_id).unwrap_or(BlockNumberFor::<T>::zero());
319

            
320
            ensure!(
321
                existing_credits >= 1u32.into(),
322
                Error::<T>::InsufficientCredits,
323
            );
324

            
325
            let updated_credits = existing_credits.saturating_sub(1u32.into());
326
            BlockProductionCredits::<T>::insert(para_id, updated_credits);
327

            
328
            Self::deposit_event(Event::<T>::BlockProductionCreditBurned {
329
                para_id: *para_id,
330
                credits_remaining: updated_credits,
331
            });
332

            
333
            Ok(().into())
334
        }
335

            
336
        /// Burn a credit for the given para. Deducts one credit if possible, errors otherwise.
337
        pub fn burn_collator_assignment_free_credit_for_para(
338
            para_id: &ParaId,
339
        ) -> DispatchResultWithPostInfo {
340
            let existing_credits = CollatorAssignmentCredits::<T>::get(para_id).unwrap_or(0u32);
341

            
342
            ensure!(existing_credits >= 1u32, Error::<T>::InsufficientCredits,);
343

            
344
            let updated_credits = existing_credits.saturating_sub(1u32);
345
            CollatorAssignmentCredits::<T>::insert(para_id, updated_credits);
346

            
347
            Self::deposit_event(Event::<T>::CollatorAssignmentCreditBurned {
348
                para_id: *para_id,
349
                credits_remaining: updated_credits,
350
            });
351

            
352
            Ok(().into())
353
        }
354

            
355
        pub fn give_free_credits(para_id: &ParaId) -> Weight {
356
            if GivenFreeCredits::<T>::contains_key(para_id) {
357
                // This para id has already received free credits
358
                return Weight::default();
359
            }
360

            
361
            // Set number of credits to FreeBlockProductionCredits
362
            let block_production_existing_credits =
363
                BlockProductionCredits::<T>::get(para_id).unwrap_or(BlockNumberFor::<T>::zero());
364
            let block_production_updated_credits = T::FreeBlockProductionCredits::get();
365
            // Do not update credits if for some reason this para id had more
366
            if block_production_existing_credits < block_production_updated_credits {
367
                Self::set_free_block_production_credits(para_id, block_production_updated_credits);
368
            }
369

            
370
            // Set number of credits to FreeCollatorAssignmentCredits
371
            let collator_assignment_existing_credits =
372
                CollatorAssignmentCredits::<T>::get(para_id).unwrap_or(0u32);
373
            let collator_assignment_updated_credits = T::FreeCollatorAssignmentCredits::get();
374

            
375
            // Do not update credits if for some reason this para id had more
376
            if collator_assignment_existing_credits < collator_assignment_updated_credits {
377
                Self::set_free_collator_assignment_credits(
378
                    para_id,
379
                    collator_assignment_updated_credits,
380
                );
381
            }
382

            
383
            // We only allow to call this function once per para id, even if it didn't actually
384
            // receive all the free credits
385
            GivenFreeCredits::<T>::insert(para_id, ());
386

            
387
            Weight::default()
388
        }
389

            
390
        pub fn set_free_collator_assignment_credits(
391
            para_id: &ParaId,
392
            free_collator_assignment_credits: u32,
393
        ) {
394
            if free_collator_assignment_credits.is_zero() {
395
                CollatorAssignmentCredits::<T>::remove(para_id);
396
            } else {
397
                CollatorAssignmentCredits::<T>::insert(para_id, free_collator_assignment_credits);
398
            }
399

            
400
            Self::deposit_event(Event::<T>::CollatorAssignmentCreditsSet {
401
                para_id: *para_id,
402
                credits: free_collator_assignment_credits,
403
            });
404
        }
405

            
406
        pub fn set_free_block_production_credits(
407
            para_id: &ParaId,
408
            free_collator_block_production_credits: BlockNumberFor<T>,
409
        ) {
410
            if free_collator_block_production_credits.is_zero() {
411
                BlockProductionCredits::<T>::remove(para_id);
412
            } else {
413
                BlockProductionCredits::<T>::insert(
414
                    para_id,
415
                    free_collator_block_production_credits,
416
                );
417
            }
418

            
419
            Self::deposit_event(Event::<T>::BlockProductionCreditsSet {
420
                para_id: *para_id,
421
                credits: free_collator_block_production_credits,
422
            });
423
        }
424

            
425
        pub fn free_block_production_credits(para_id: ParaId) -> Option<BlockNumberFor<T>> {
426
            BlockProductionCredits::<T>::get(para_id)
427
        }
428

            
429
        pub fn free_collator_assignment_credits(para_id: ParaId) -> Option<u32> {
430
            CollatorAssignmentCredits::<T>::get(para_id)
431
        }
432

            
433
        pub fn given_free_credits(para_id: ParaId) -> Option<()> {
434
            GivenFreeCredits::<T>::get(para_id)
435
        }
436

            
437
        pub fn refund_address(para_id: ParaId) -> Option<T::AccountId> {
438
            RefundAddress::<T>::get(para_id)
439
        }
440

            
441
        pub fn max_tip(para_id: ParaId) -> Option<BalanceOf<T>> {
442
            MaxTip::<T>::get(para_id)
443
        }
444
    }
445

            
446
    #[pallet::genesis_config]
447
    pub struct GenesisConfig<T: Config> {
448
        pub para_id_credits: Vec<FreeCreditGenesisParams<BlockNumberFor<T>>>,
449
    }
450

            
451
    impl<T: Config> Default for GenesisConfig<T> {
452
        fn default() -> Self {
453
            Self {
454
                para_id_credits: Default::default(),
455
            }
456
        }
457
    }
458

            
459
    #[pallet::genesis_build]
460
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
461
3
        fn build(&self) {
462
3
            for para_id_credits in &self.para_id_credits {
463
                BlockProductionCredits::<T>::insert(
464
                    para_id_credits.para_id,
465
                    para_id_credits.block_production_credits,
466
                );
467
                CollatorAssignmentCredits::<T>::insert(
468
                    para_id_credits.para_id,
469
                    para_id_credits.collator_assignment_credits,
470
                );
471
            }
472
3
        }
473
    }
474
}
475

            
476
// Params to be set in genesis
477
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Serialize, Deserialize)]
478
pub struct FreeCreditGenesisParams<BlockProductCredits> {
479
    pub para_id: ParaId,
480
    pub block_production_credits: BlockProductCredits,
481
    pub collator_assignment_credits: u32,
482
}
483
impl<BlockProductCredits> From<(ParaId, BlockProductCredits, u32)>
484
    for FreeCreditGenesisParams<BlockProductCredits>
485
{
486
    fn from(value: (ParaId, BlockProductCredits, u32)) -> Self {
487
        Self {
488
            para_id: value.0,
489
            block_production_credits: value.1,
490
            collator_assignment_credits: value.2,
491
        }
492
    }
493
}
494

            
495
/// Balance used by this pallet
496
pub type BalanceOf<T> =
497
    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
498

            
499
pub type CurrencyOf<T> = <T as Config>::Currency;
500
/// Type alias to conveniently refer to the `Currency::NegativeImbalance` associated type.
501
pub type NegativeImbalanceOf<T> =
502
    <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
503
/// Handler for fee charging. This will be invoked when fees need to be deducted from the fee
504
/// account for a given paraId.
505

            
506
/// Returns the cost for a given block credit at the current time. This can be a complex operation,
507
/// so it also returns the weight it consumes. (TODO: or just rely on benchmarking)
508
pub trait ProvideBlockProductionCost<T: Config> {
509
    fn block_cost(para_id: &ParaId) -> (BalanceOf<T>, Weight);
510
}
511

            
512
/// Returns the cost for a given block credit at the current time. This can be a complex operation,
513
/// so it also returns the weight it consumes. (TODO: or just rely on benchmarking)
514
pub trait ProvideCollatorAssignmentCost<T: Config> {
515
    fn collator_assignment_cost(para_id: &ParaId) -> (BalanceOf<T>, Weight);
516
}
517

            
518
impl<T: Config> AuthorNotingHook<T::AccountId> for Pallet<T> {
519
    // This hook is called when pallet_author_noting sees that the block number of a container chain has increased.
520
    // Currently we always charge 1 credit, even if a container chain produced more that 1 block in between tanssi
521
    // blocks.
522
    fn on_container_author_noted(
523
        _author: &T::AccountId,
524
        _block_number: BlockNumber,
525
        para_id: ParaId,
526
    ) -> Weight {
527
        if Pallet::<T>::burn_block_production_free_credit_for_para(&para_id).is_err() {
528
            let (amount_to_charge, _weight) = T::ProvideBlockProductionCost::block_cost(&para_id);
529
            match T::Currency::withdraw(
530
                &Self::parachain_tank(para_id),
531
                amount_to_charge,
532
                WithdrawReasons::FEE,
533
                ExistenceRequirement::KeepAlive,
534
            ) {
535
                Err(e) => log::warn!(
536
                    "Failed to withdraw block production payment for container chain {}: {:?}",
537
                    u32::from(para_id),
538
                    e
539
                ),
540
                Ok(imbalance) => {
541
                    T::OnChargeForBlock::on_unbalanced(imbalance);
542
                }
543
            }
544
        }
545

            
546
        T::WeightInfo::on_container_author_noted()
547
    }
548
}
549

            
550
impl<T: Config> CollatorAssignmentHook<BalanceOf<T>> for Pallet<T> {
551
    // is_parathread parameter for future use to apply different logic
552
    fn on_collators_assigned(
553
        para_id: ParaId,
554
        maybe_tip: Option<&BalanceOf<T>>,
555
        _is_parathread: bool,
556
    ) -> Result<Weight, DispatchError> {
557
        // Withdraw assignment fee
558
        let maybe_assignment_imbalance =
559
            if Pallet::<T>::burn_collator_assignment_free_credit_for_para(&para_id).is_err() {
560
                let (amount_to_charge, _weight) =
561
                    T::ProvideCollatorAssignmentCost::collator_assignment_cost(&para_id);
562
                Some(T::Currency::withdraw(
563
                    &Self::parachain_tank(para_id),
564
                    amount_to_charge,
565
                    WithdrawReasons::FEE,
566
                    ExistenceRequirement::KeepAlive,
567
                )?)
568
            } else {
569
                None
570
            };
571

            
572
        if let Some(&tip) = maybe_tip {
573
            // Only charge the tip to the paras that had a max tip set
574
            // (aka were willing to tip for being assigned a collator)
575
            if MaxTip::<T>::get(para_id).is_some() {
576
                match T::Currency::withdraw(
577
                    &Self::parachain_tank(para_id),
578
                    tip,
579
                    WithdrawReasons::TIP,
580
                    ExistenceRequirement::KeepAlive,
581
                ) {
582
                    Err(e) => {
583
                        // Return assignment imbalance to tank on error
584
                        if let Some(assignment_imbalance) = maybe_assignment_imbalance {
585
                            T::Currency::resolve_creating(
586
                                &Self::parachain_tank(para_id),
587
                                assignment_imbalance,
588
                            );
589
                        }
590
                        return Err(e);
591
                    }
592
                    Ok(tip_imbalance) => {
593
                        Self::deposit_event(Event::<T>::CollatorAssignmentTipCollected {
594
                            para_id,
595
                            payer: Self::parachain_tank(para_id),
596
                            tip,
597
                        });
598
                        T::OnChargeForCollatorAssignmentTip::on_unbalanced(tip_imbalance);
599
                    }
600
                }
601
            }
602
        }
603

            
604
        if let Some(assignment_imbalance) = maybe_assignment_imbalance {
605
            T::OnChargeForCollatorAssignment::on_unbalanced(assignment_imbalance);
606
        }
607

            
608
        Ok(T::WeightInfo::on_collators_assigned())
609
    }
610
}
611

            
612
impl<T: Config> CollatorAssignmentTip<BalanceOf<T>> for Pallet<T> {
613
    fn get_para_tip(para_id: ParaId) -> Option<BalanceOf<T>> {
614
        MaxTip::<T>::get(para_id)
615
    }
616
}
617

            
618
impl<T: Config> Pallet<T> {
619
    /// Derive a derivative account ID from the paraId.
620
696
    pub fn parachain_tank(para_id: ParaId) -> T::AccountId {
621
696
        let entropy = (b"modlpy/serpayment", para_id).using_encoded(blake2_256);
622
696
        Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
623
696
            .expect("infinite length input; no invalid inputs for type; qed")
624
696
    }
625

            
626
    /// Hook to perform things on deregister
627
    pub fn para_deregistered(para_id: ParaId) {
628
        // Drain the para-id account from tokens
629
        let parachain_tank_balance = T::Currency::total_balance(&Self::parachain_tank(para_id));
630
        if !parachain_tank_balance.is_zero() {
631
            if let Ok(imbalance) = T::Currency::withdraw(
632
                &Self::parachain_tank(para_id),
633
                parachain_tank_balance,
634
                WithdrawReasons::FEE,
635
                ExistenceRequirement::AllowDeath,
636
            ) {
637
                if let Some(address) = RefundAddress::<T>::get(para_id) {
638
                    T::Currency::resolve_creating(&address, imbalance);
639
                } else {
640
                    // Burn for now, we might be able to pass something to do with this
641
                    drop(imbalance);
642
                }
643
            }
644
        }
645

            
646
        // Clean refund addres
647
        RefundAddress::<T>::remove(para_id);
648

            
649
        // Clean credits
650
        BlockProductionCredits::<T>::remove(para_id);
651
        CollatorAssignmentCredits::<T>::remove(para_id);
652
        MaxTip::<T>::remove(para_id);
653
        MaxCorePrice::<T>::remove(para_id);
654
    }
655
}