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
extern crate alloc;
24

            
25
#[cfg(feature = "runtime-benchmarks")]
26
use tp_traits::BlockNumber;
27
use {
28
    cumulus_primitives_core::ParaId,
29
    frame_support::{
30
        pallet_prelude::*,
31
        sp_runtime::{traits::Zero, Saturating},
32
        traits::{
33
            tokens::ExistenceRequirement, Currency, EnsureOriginWithArg, OnUnbalanced,
34
            WithdrawReasons,
35
        },
36
    },
37
    frame_system::pallet_prelude::*,
38
    scale_info::prelude::vec::Vec,
39
    serde::{Deserialize, Serialize},
40
    sp_io::hashing::blake2_256,
41
    sp_runtime::{traits::TrailingZeroInput, DispatchError},
42
    tp_traits::{AuthorNotingHook, CollatorAssignmentHook, CollatorAssignmentTip},
43
};
44

            
45
#[cfg(any(test, feature = "runtime-benchmarks"))]
46
mod benchmarks;
47
#[cfg(test)]
48
mod mock;
49

            
50
#[cfg(test)]
51
mod tests;
52
pub mod weights;
53
pub use weights::WeightInfo;
54

            
55
pub use pallet::*;
56
use tp_traits::AuthorNotingInfo;
57

            
58
#[frame_support::pallet]
59
pub mod pallet {
60
    use super::*;
61

            
62
    #[pallet::config]
63
    pub trait Config: frame_system::Config {
64
        /// Handlers for fees
65
        type OnChargeForBlock: OnUnbalanced<NegativeImbalanceOf<Self>>;
66
        type OnChargeForCollatorAssignment: OnUnbalanced<NegativeImbalanceOf<Self>>;
67
        type OnChargeForCollatorAssignmentTip: OnUnbalanced<NegativeImbalanceOf<Self>>;
68

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

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

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

            
86
        type WeightInfo: WeightInfo;
87
    }
88

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

            
96
    #[pallet::pallet]
97
    pub struct Pallet<T>(PhantomData<T>);
98

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

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

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

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

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

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

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

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

            
185
113
            Self::deposit_event(Event::<T>::CreditsPurchased {
186
113
                para_id,
187
113
                payer: account,
188
113
                credit,
189
113
            });
190

            
191
113
            Ok(().into())
192
        }
193

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

            
206
52
            Self::set_free_block_production_credits(&para_id, free_block_credits);
207

            
208
52
            Ok(().into())
209
        }
210

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

            
223
            if given_free_credits {
224
                GivenFreeCredits::<T>::insert(para_id, ());
225
            } else {
226
                GivenFreeCredits::<T>::remove(para_id);
227
            }
228

            
229
            Ok(().into())
230
        }
231

            
232
        /// Call index to set the refund address for non-spent tokens
233
        #[pallet::call_index(3)]
234
        #[pallet::weight(T::WeightInfo::set_refund_address())]
235
        #[allow(clippy::useless_conversion)]
236
        pub fn set_refund_address(
237
            origin: OriginFor<T>,
238
            para_id: ParaId,
239
            refund_address: Option<T::AccountId>,
240
10
        ) -> DispatchResultWithPostInfo {
241
10
            T::ManagerOrigin::ensure_origin(origin, &para_id)?;
242

            
243
8
            RefundAddress::<T>::set(para_id, refund_address.clone());
244

            
245
8
            Self::deposit_event(Event::<T>::RefundAddressUpdated {
246
8
                para_id,
247
8
                refund_address,
248
8
            });
249

            
250
8
            Ok(().into())
251
        }
252

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

            
265
22
            Self::set_free_collator_assignment_credits(&para_id, free_collator_assignment_credits);
266

            
267
22
            Ok(().into())
268
        }
269

            
270
        /// Max core price for parathread in relay chain currency
271
        #[pallet::call_index(5)]
272
        #[pallet::weight(T::WeightInfo::set_max_core_price())]
273
        #[allow(clippy::useless_conversion)]
274
        pub fn set_max_core_price(
275
            origin: OriginFor<T>,
276
            para_id: ParaId,
277
            max_core_price: u128,
278
2
        ) -> DispatchResultWithPostInfo {
279
2
            T::ManagerOrigin::ensure_origin(origin, &para_id)?;
280

            
281
2
            MaxCorePrice::<T>::insert(para_id, max_core_price);
282

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

            
288
2
            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
        #[allow(clippy::useless_conversion)]
296
        pub fn set_max_tip(
297
            origin: OriginFor<T>,
298
            para_id: ParaId,
299
            max_tip: Option<BalanceOf<T>>,
300
42
        ) -> DispatchResultWithPostInfo {
301
42
            T::ManagerOrigin::ensure_origin(origin, &para_id)?;
302

            
303
42
            MaxTip::<T>::set(para_id, max_tip);
304

            
305
42
            Ok(().into())
306
        }
307
    }
308

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

            
317
145
            ensure!(
318
145
                existing_credits >= 1u32.into(),
319
46
                Error::<T>::InsufficientCredits,
320
            );
321

            
322
99
            let updated_credits = existing_credits.saturating_sub(1u32.into());
323
99
            BlockProductionCredits::<T>::insert(para_id, updated_credits);
324

            
325
99
            Self::deposit_event(Event::<T>::BlockProductionCreditBurned {
326
99
                para_id: *para_id,
327
99
                credits_remaining: updated_credits,
328
99
            });
329

            
330
99
            Ok(().into())
331
145
        }
332

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

            
339
9009
            ensure!(existing_credits >= 1u32, Error::<T>::InsufficientCredits,);
340

            
341
8808
            let updated_credits = existing_credits.saturating_sub(1u32);
342
8808
            CollatorAssignmentCredits::<T>::insert(para_id, updated_credits);
343

            
344
8808
            Self::deposit_event(Event::<T>::CollatorAssignmentCreditBurned {
345
8808
                para_id: *para_id,
346
8808
                credits_remaining: updated_credits,
347
8808
            });
348

            
349
8808
            Ok(().into())
350
9009
        }
351

            
352
396
        pub fn give_free_credits(para_id: &ParaId) -> Weight {
353
396
            if GivenFreeCredits::<T>::contains_key(para_id) {
354
                // This para id has already received free credits
355
11
                return Weight::default();
356
385
            }
357

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

            
367
            // Set number of credits to FreeCollatorAssignmentCredits
368
385
            let collator_assignment_existing_credits =
369
385
                CollatorAssignmentCredits::<T>::get(para_id).unwrap_or(0u32);
370
385
            let collator_assignment_updated_credits = T::FreeCollatorAssignmentCredits::get();
371

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

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

            
384
385
            Weight::default()
385
396
        }
386

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

            
397
411
            Self::deposit_event(Event::<T>::CollatorAssignmentCreditsSet {
398
411
                para_id: *para_id,
399
411
                credits: free_collator_assignment_credits,
400
411
            });
401
411
        }
402

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

            
416
437
            Self::deposit_event(Event::<T>::BlockProductionCreditsSet {
417
437
                para_id: *para_id,
418
437
                credits: free_collator_block_production_credits,
419
437
            });
420
437
        }
421

            
422
6207
        pub fn charge_tip(para_id: &ParaId, tip: &BalanceOf<T>) -> Result<(), DispatchError> {
423
            // Only charge the tip to the paras that had a max tip set
424
            // (aka were willing to tip for being assigned a collator)
425
6207
            if MaxTip::<T>::get(para_id).is_some() {
426
307
                let tip_imbalance = T::Currency::withdraw(
427
307
                    &Self::parachain_tank(*para_id),
428
307
                    *tip,
429
                    WithdrawReasons::TIP,
430
307
                    ExistenceRequirement::KeepAlive,
431
13
                )?;
432

            
433
294
                Self::deposit_event(Event::<T>::CollatorAssignmentTipCollected {
434
294
                    para_id: *para_id,
435
294
                    payer: Self::parachain_tank(*para_id),
436
294
                    tip: *tip,
437
294
                });
438
294
                T::OnChargeForCollatorAssignmentTip::on_unbalanced(tip_imbalance);
439
5900
            }
440
6194
            Ok(())
441
6207
        }
442

            
443
        pub fn free_block_production_credits(para_id: ParaId) -> Option<BlockNumberFor<T>> {
444
            BlockProductionCredits::<T>::get(para_id)
445
        }
446

            
447
        pub fn free_collator_assignment_credits(para_id: ParaId) -> Option<u32> {
448
            CollatorAssignmentCredits::<T>::get(para_id)
449
        }
450

            
451
        pub fn given_free_credits(para_id: ParaId) -> Option<()> {
452
            GivenFreeCredits::<T>::get(para_id)
453
        }
454

            
455
        pub fn refund_address(para_id: ParaId) -> Option<T::AccountId> {
456
            RefundAddress::<T>::get(para_id)
457
        }
458

            
459
        pub fn max_tip(para_id: ParaId) -> Option<BalanceOf<T>> {
460
            MaxTip::<T>::get(para_id)
461
        }
462
    }
463

            
464
    #[pallet::genesis_config]
465
    pub struct GenesisConfig<T: Config> {
466
        pub para_id_credits: Vec<FreeCreditGenesisParams<BlockNumberFor<T>>>,
467
    }
468

            
469
    impl<T: Config> Default for GenesisConfig<T> {
470
173
        fn default() -> Self {
471
173
            Self {
472
173
                para_id_credits: Default::default(),
473
173
            }
474
173
        }
475
    }
476

            
477
    #[pallet::genesis_build]
478
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
479
1913
        fn build(&self) {
480
2509
            for para_id_credits in &self.para_id_credits {
481
596
                BlockProductionCredits::<T>::insert(
482
596
                    para_id_credits.para_id,
483
596
                    para_id_credits.block_production_credits,
484
596
                );
485
596
                CollatorAssignmentCredits::<T>::insert(
486
596
                    para_id_credits.para_id,
487
596
                    para_id_credits.collator_assignment_credits,
488
596
                );
489
596
            }
490
1913
        }
491
    }
492
}
493

            
494
// Params to be set in genesis
495
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Serialize, Deserialize)]
496
pub struct FreeCreditGenesisParams<BlockProductCredits> {
497
    pub para_id: ParaId,
498
    pub block_production_credits: BlockProductCredits,
499
    pub collator_assignment_credits: u32,
500
}
501
impl<BlockProductCredits> From<(ParaId, BlockProductCredits, u32)>
502
    for FreeCreditGenesisParams<BlockProductCredits>
503
{
504
824
    fn from(value: (ParaId, BlockProductCredits, u32)) -> Self {
505
824
        Self {
506
824
            para_id: value.0,
507
824
            block_production_credits: value.1,
508
824
            collator_assignment_credits: value.2,
509
824
        }
510
824
    }
511
}
512

            
513
/// Balance used by this pallet
514
pub type BalanceOf<T> =
515
    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
516

            
517
pub type CurrencyOf<T> = <T as Config>::Currency;
518
/// Type alias to conveniently refer to the `Currency::NegativeImbalance` associated type.
519
pub type NegativeImbalanceOf<T> =
520
    <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
521
/// Handler for fee charging. This will be invoked when fees need to be deducted from the fee
522
/// account for a given paraId.
523
/// Returns the cost for a given block credit at the current time. This can be a complex operation,
524
/// so it also returns the weight it consumes. (TODO: or just rely on benchmarking)
525
pub trait ProvideBlockProductionCost<T: Config> {
526
    fn block_cost(para_id: &ParaId) -> (BalanceOf<T>, Weight);
527
}
528

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

            
535
impl<T: Config> AuthorNotingHook<T::AccountId> for Pallet<T> {
536
    // This hook is called when pallet_author_noting sees that the block number of a container chain has increased.
537
    // Currently we always charge 1 credit, even if a container chain produced more that 1 block in between tanssi
538
    // blocks.
539
129
    fn on_container_authors_noted(infos: &[AuthorNotingInfo<T::AccountId>]) -> Weight {
540
270
        for info in infos {
541
141
            let para_id = info.para_id;
542
141
            if Pallet::<T>::burn_block_production_free_credit_for_para(&para_id).is_err() {
543
43
                let (amount_to_charge, _weight) =
544
43
                    T::ProvideBlockProductionCost::block_cost(&para_id);
545

            
546
43
                match T::Currency::withdraw(
547
43
                    &Self::parachain_tank(para_id),
548
43
                    amount_to_charge,
549
43
                    WithdrawReasons::FEE,
550
43
                    ExistenceRequirement::KeepAlive,
551
43
                ) {
552
2
                    Err(e) => log::warn!(
553
                        "Failed to withdraw block production payment for container chain {}: {:?}",
554
                        u32::from(para_id),
555
                        e
556
                    ),
557
41
                    Ok(imbalance) => {
558
41
                        T::OnChargeForBlock::on_unbalanced(imbalance);
559
41
                    }
560
                }
561
98
            }
562
        }
563

            
564
129
        T::WeightInfo::on_container_authors_noted(infos.len() as u32)
565
129
    }
566

            
567
    #[cfg(feature = "runtime-benchmarks")]
568
    fn prepare_worst_case_for_bench(
569
        _author: &T::AccountId,
570
        _block_number: BlockNumber,
571
        para_id: ParaId,
572
    ) {
573
        let (amount_to_charge, _weight) = T::ProvideBlockProductionCost::block_cost(&para_id);
574
        // mint large amount (bigger than ED) to ensure withdraw will not fail.
575
        let mint = BalanceOf::<T>::from(2_000_000_000u32).saturating_add(amount_to_charge);
576

            
577
        // mint twice more to not have ED issues
578
        T::Currency::resolve_creating(&Self::parachain_tank(para_id), T::Currency::issue(mint));
579
    }
580
}
581

            
582
impl<T: Config> CollatorAssignmentHook<BalanceOf<T>> for Pallet<T> {
583
    // is_parathread parameter for future use to apply different logic
584
3
    fn on_collators_assigned(
585
3
        para_id: ParaId,
586
3
        maybe_tip: Option<&BalanceOf<T>>,
587
3
        _is_parathread: bool,
588
3
    ) -> Result<Weight, DispatchError> {
589
        // Withdraw assignment fee
590
2
        let maybe_assignment_imbalance =
591
3
            if Pallet::<T>::burn_collator_assignment_free_credit_for_para(&para_id).is_err() {
592
3
                let (amount_to_charge, _weight) =
593
3
                    T::ProvideCollatorAssignmentCost::collator_assignment_cost(&para_id);
594
3
                Some(T::Currency::withdraw(
595
3
                    &Self::parachain_tank(para_id),
596
3
                    amount_to_charge,
597
                    WithdrawReasons::FEE,
598
3
                    ExistenceRequirement::KeepAlive,
599
1
                )?)
600
            } else {
601
                None
602
            };
603

            
604
2
        if let Some(&tip) = maybe_tip {
605
            // Only charge the tip to the paras that had a max tip set
606
            // (aka were willing to tip for being assigned a collator)
607
2
            if MaxTip::<T>::get(para_id).is_some() {
608
2
                match T::Currency::withdraw(
609
2
                    &Self::parachain_tank(para_id),
610
2
                    tip,
611
2
                    WithdrawReasons::TIP,
612
2
                    ExistenceRequirement::KeepAlive,
613
2
                ) {
614
1
                    Err(e) => {
615
                        // Return assignment imbalance to tank on error
616
1
                        if let Some(assignment_imbalance) = maybe_assignment_imbalance {
617
1
                            T::Currency::resolve_creating(
618
1
                                &Self::parachain_tank(para_id),
619
1
                                assignment_imbalance,
620
1
                            );
621
1
                        }
622
1
                        return Err(e);
623
                    }
624
1
                    Ok(tip_imbalance) => {
625
1
                        Self::deposit_event(Event::<T>::CollatorAssignmentTipCollected {
626
1
                            para_id,
627
1
                            payer: Self::parachain_tank(para_id),
628
1
                            tip,
629
1
                        });
630
1
                        T::OnChargeForCollatorAssignmentTip::on_unbalanced(tip_imbalance);
631
1
                    }
632
                }
633
            }
634
        }
635

            
636
1
        if let Some(assignment_imbalance) = maybe_assignment_imbalance {
637
1
            T::OnChargeForCollatorAssignment::on_unbalanced(assignment_imbalance);
638
1
        }
639

            
640
1
        Ok(T::WeightInfo::on_collators_assigned())
641
3
    }
642
}
643

            
644
impl<T: Config> CollatorAssignmentTip<BalanceOf<T>> for Pallet<T> {
645
7203
    fn get_para_tip(para_id: ParaId) -> Option<BalanceOf<T>> {
646
7203
        MaxTip::<T>::get(para_id)
647
7203
    }
648
}
649

            
650
impl<T: Config> Pallet<T> {
651
    /// Derive a derivative account ID from the paraId.
652
9990
    pub fn parachain_tank(para_id: ParaId) -> T::AccountId {
653
9990
        let entropy = (b"modlpy/serpayment", para_id).using_encoded(blake2_256);
654
9990
        Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
655
9990
            .expect("infinite length input; no invalid inputs for type; qed")
656
9990
    }
657

            
658
    /// Hook to perform things on deregister
659
85
    pub fn para_deregistered(para_id: ParaId) {
660
        // Drain the para-id account from tokens
661
85
        let parachain_tank_balance = T::Currency::total_balance(&Self::parachain_tank(para_id));
662
85
        if !parachain_tank_balance.is_zero() {
663
2
            if let Ok(imbalance) = T::Currency::withdraw(
664
2
                &Self::parachain_tank(para_id),
665
2
                parachain_tank_balance,
666
2
                WithdrawReasons::FEE,
667
2
                ExistenceRequirement::AllowDeath,
668
2
            ) {
669
2
                if let Some(address) = RefundAddress::<T>::get(para_id) {
670
1
                    T::Currency::resolve_creating(&address, imbalance);
671
1
                } else {
672
1
                    // Burn for now, we might be able to pass something to do with this
673
1
                    drop(imbalance);
674
1
                }
675
            }
676
83
        }
677

            
678
        // Clean refund addres
679
85
        RefundAddress::<T>::remove(para_id);
680

            
681
        // Clean credits
682
85
        BlockProductionCredits::<T>::remove(para_id);
683
85
        CollatorAssignmentCredits::<T>::remove(para_id);
684
85
        MaxTip::<T>::remove(para_id);
685
85
        MaxCorePrice::<T>::remove(para_id);
686
85
    }
687
}