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
24960
#[frame_support::pallet]
59
pub mod pallet {
60
    use super::*;
61

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

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

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

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

            
88
        type WeightInfo: WeightInfo;
89
    }
90

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

            
98
69439
    #[pallet::pallet]
99
    pub struct Pallet<T>(PhantomData<T>);
100

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

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

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

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

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

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

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

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

            
187
179
            Self::deposit_event(Event::<T>::CreditsPurchased {
188
179
                para_id,
189
179
                payer: account,
190
179
                credit,
191
179
            });
192
179

            
193
179
            Ok(().into())
194
        }
195

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

            
208
100
            Self::set_free_block_production_credits(&para_id, free_block_credits);
209
100

            
210
100
            Ok(().into())
211
        }
212

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

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

            
231
18
            Ok(().into())
232
        }
233

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

            
245
20
            if let Some(refund_address) = refund_address.clone() {
246
15
                RefundAddress::<T>::insert(para_id, refund_address.clone());
247
19
            } else {
248
5
                RefundAddress::<T>::remove(para_id);
249
5
            }
250

            
251
20
            Self::deposit_event(Event::<T>::RefundAddressUpdated {
252
20
                para_id,
253
20
                refund_address,
254
20
            });
255
20

            
256
20
            Ok(().into())
257
        }
258

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

            
271
64
            Self::set_free_collator_assignment_credits(&para_id, free_collator_assignment_credits);
272
64

            
273
64
            Ok(().into())
274
        }
275

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

            
287
2
            MaxCorePrice::<T>::insert(para_id, max_core_price);
288
2

            
289
2
            Self::deposit_event(Event::<T>::MaxCorePriceUpdated {
290
2
                para_id,
291
2
                max_core_price,
292
2
            });
293
2

            
294
2
            Ok(().into())
295
        }
296

            
297
        /// Set the maximum tip a container chain is willing to pay to be assigned a collator on congestion.
298
        /// Can only be called by container chain manager.
299
        #[pallet::call_index(6)]
300
        #[pallet::weight(T::WeightInfo::set_max_tip())]
301
        #[allow(clippy::useless_conversion)]
302
        pub fn set_max_tip(
303
            origin: OriginFor<T>,
304
            para_id: ParaId,
305
            max_tip: Option<BalanceOf<T>>,
306
48
        ) -> DispatchResultWithPostInfo {
307
48
            T::ManagerOrigin::ensure_origin(origin, &para_id)?;
308

            
309
48
            if let Some(max_tip) = max_tip {
310
48
                MaxTip::<T>::insert(para_id, max_tip);
311
48
            } else {
312
                MaxTip::<T>::remove(para_id);
313
            }
314

            
315
48
            Ok(().into())
316
        }
317
    }
318

            
319
    impl<T: Config> Pallet<T> {
320
        /// Burn a credit for the given para. Deducts one credit if possible, errors otherwise.
321
22804
        pub fn burn_block_production_free_credit_for_para(
322
22804
            para_id: &ParaId,
323
22804
        ) -> DispatchResultWithPostInfo {
324
22804
            let existing_credits =
325
22804
                BlockProductionCredits::<T>::get(para_id).unwrap_or(BlockNumberFor::<T>::zero());
326
22804

            
327
22804
            ensure!(
328
22804
                existing_credits >= 1u32.into(),
329
403
                Error::<T>::InsufficientCredits,
330
            );
331

            
332
22401
            let updated_credits = existing_credits.saturating_sub(1u32.into());
333
22401
            BlockProductionCredits::<T>::insert(para_id, updated_credits);
334
22401

            
335
22401
            Self::deposit_event(Event::<T>::BlockProductionCreditBurned {
336
22401
                para_id: *para_id,
337
22401
                credits_remaining: updated_credits,
338
22401
            });
339
22401

            
340
22401
            Ok(().into())
341
22804
        }
342

            
343
        /// Burn a credit for the given para. Deducts one credit if possible, errors otherwise.
344
9501
        pub fn burn_collator_assignment_free_credit_for_para(
345
9501
            para_id: &ParaId,
346
9501
        ) -> DispatchResultWithPostInfo {
347
9501
            let existing_credits = CollatorAssignmentCredits::<T>::get(para_id).unwrap_or(0u32);
348
9501

            
349
9501
            ensure!(existing_credits >= 1u32, Error::<T>::InsufficientCredits,);
350

            
351
9231
            let updated_credits = existing_credits.saturating_sub(1u32);
352
9231
            CollatorAssignmentCredits::<T>::insert(para_id, updated_credits);
353
9231

            
354
9231
            Self::deposit_event(Event::<T>::CollatorAssignmentCreditBurned {
355
9231
                para_id: *para_id,
356
9231
                credits_remaining: updated_credits,
357
9231
            });
358
9231

            
359
9231
            Ok(().into())
360
9501
        }
361

            
362
219
        pub fn give_free_credits(para_id: &ParaId) -> Weight {
363
219
            if GivenFreeCredits::<T>::contains_key(para_id) {
364
                // This para id has already received free credits
365
2
                return Weight::default();
366
217
            }
367
217

            
368
217
            // Set number of credits to FreeBlockProductionCredits
369
217
            let block_production_existing_credits =
370
217
                BlockProductionCredits::<T>::get(para_id).unwrap_or(BlockNumberFor::<T>::zero());
371
217
            let block_production_updated_credits = T::FreeBlockProductionCredits::get();
372
217
            // Do not update credits if for some reason this para id had more
373
217
            if block_production_existing_credits < block_production_updated_credits {
374
217
                Self::set_free_block_production_credits(para_id, block_production_updated_credits);
375
217
            }
376

            
377
            // Set number of credits to FreeCollatorAssignmentCredits
378
217
            let collator_assignment_existing_credits =
379
217
                CollatorAssignmentCredits::<T>::get(para_id).unwrap_or(0u32);
380
217
            let collator_assignment_updated_credits = T::FreeCollatorAssignmentCredits::get();
381
217

            
382
217
            // Do not update credits if for some reason this para id had more
383
217
            if collator_assignment_existing_credits < collator_assignment_updated_credits {
384
217
                Self::set_free_collator_assignment_credits(
385
217
                    para_id,
386
217
                    collator_assignment_updated_credits,
387
217
                );
388
217
            }
389

            
390
            // We only allow to call this function once per para id, even if it didn't actually
391
            // receive all the free credits
392
217
            GivenFreeCredits::<T>::insert(para_id, ());
393
217

            
394
217
            Weight::default()
395
219
        }
396

            
397
285
        pub fn set_free_collator_assignment_credits(
398
285
            para_id: &ParaId,
399
285
            free_collator_assignment_credits: u32,
400
285
        ) {
401
285
            if free_collator_assignment_credits.is_zero() {
402
46
                CollatorAssignmentCredits::<T>::remove(para_id);
403
239
            } else {
404
239
                CollatorAssignmentCredits::<T>::insert(para_id, free_collator_assignment_credits);
405
239
            }
406

            
407
285
            Self::deposit_event(Event::<T>::CollatorAssignmentCreditsSet {
408
285
                para_id: *para_id,
409
285
                credits: free_collator_assignment_credits,
410
285
            });
411
285
        }
412

            
413
317
        pub fn set_free_block_production_credits(
414
317
            para_id: &ParaId,
415
317
            free_collator_block_production_credits: BlockNumberFor<T>,
416
317
        ) {
417
317
            if free_collator_block_production_credits.is_zero() {
418
75
                BlockProductionCredits::<T>::remove(para_id);
419
242
            } else {
420
242
                BlockProductionCredits::<T>::insert(
421
242
                    para_id,
422
242
                    free_collator_block_production_credits,
423
242
                );
424
242
            }
425

            
426
317
            Self::deposit_event(Event::<T>::BlockProductionCreditsSet {
427
317
                para_id: *para_id,
428
317
                credits: free_collator_block_production_credits,
429
317
            });
430
317
        }
431

            
432
6147
        pub fn charge_tip(para_id: &ParaId, tip: &BalanceOf<T>) -> Result<(), DispatchError> {
433
6147
            // Only charge the tip to the paras that had a max tip set
434
6147
            // (aka were willing to tip for being assigned a collator)
435
6147
            if MaxTip::<T>::get(para_id).is_some() {
436
184
                let tip_imbalance = T::Currency::withdraw(
437
184
                    &Self::parachain_tank(*para_id),
438
184
                    *tip,
439
184
                    WithdrawReasons::TIP,
440
184
                    ExistenceRequirement::KeepAlive,
441
184
                )?;
442

            
443
168
                Self::deposit_event(Event::<T>::CollatorAssignmentTipCollected {
444
168
                    para_id: *para_id,
445
168
                    payer: Self::parachain_tank(*para_id),
446
168
                    tip: *tip,
447
168
                });
448
168
                T::OnChargeForCollatorAssignmentTip::on_unbalanced(tip_imbalance);
449
5963
            }
450
6131
            Ok(())
451
6147
        }
452

            
453
        pub fn free_block_production_credits(para_id: ParaId) -> Option<BlockNumberFor<T>> {
454
            BlockProductionCredits::<T>::get(para_id)
455
        }
456

            
457
        pub fn free_collator_assignment_credits(para_id: ParaId) -> Option<u32> {
458
            CollatorAssignmentCredits::<T>::get(para_id)
459
        }
460

            
461
        pub fn given_free_credits(para_id: ParaId) -> Option<()> {
462
            GivenFreeCredits::<T>::get(para_id)
463
        }
464

            
465
        pub fn refund_address(para_id: ParaId) -> Option<T::AccountId> {
466
            RefundAddress::<T>::get(para_id)
467
        }
468

            
469
        pub fn max_tip(para_id: ParaId) -> Option<BalanceOf<T>> {
470
            MaxTip::<T>::get(para_id)
471
        }
472
    }
473

            
474
    #[pallet::genesis_config]
475
    pub struct GenesisConfig<T: Config> {
476
        pub para_id_credits: Vec<FreeCreditGenesisParams<BlockNumberFor<T>>>,
477
    }
478

            
479
    impl<T: Config> Default for GenesisConfig<T> {
480
133
        fn default() -> Self {
481
133
            Self {
482
133
                para_id_credits: Default::default(),
483
133
            }
484
133
        }
485
    }
486

            
487
538
    #[pallet::genesis_build]
488
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
489
1319
        fn build(&self) {
490
1787
            for para_id_credits in &self.para_id_credits {
491
468
                BlockProductionCredits::<T>::insert(
492
468
                    para_id_credits.para_id,
493
468
                    para_id_credits.block_production_credits,
494
468
                );
495
468
                CollatorAssignmentCredits::<T>::insert(
496
468
                    para_id_credits.para_id,
497
468
                    para_id_credits.collator_assignment_credits,
498
468
                );
499
468
            }
500
1319
        }
501
    }
502
}
503

            
504
// Params to be set in genesis
505
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Serialize, Deserialize)]
506
pub struct FreeCreditGenesisParams<BlockProductCredits> {
507
    pub para_id: ParaId,
508
    pub block_production_credits: BlockProductCredits,
509
    pub collator_assignment_credits: u32,
510
}
511
impl<BlockProductCredits> From<(ParaId, BlockProductCredits, u32)>
512
    for FreeCreditGenesisParams<BlockProductCredits>
513
{
514
696
    fn from(value: (ParaId, BlockProductCredits, u32)) -> Self {
515
696
        Self {
516
696
            para_id: value.0,
517
696
            block_production_credits: value.1,
518
696
            collator_assignment_credits: value.2,
519
696
        }
520
696
    }
521
}
522

            
523
/// Balance used by this pallet
524
pub type BalanceOf<T> =
525
    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
526

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

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

            
545
impl<T: Config> AuthorNotingHook<T::AccountId> for Pallet<T> {
546
    // This hook is called when pallet_author_noting sees that the block number of a container chain has increased.
547
    // Currently we always charge 1 credit, even if a container chain produced more that 1 block in between tanssi
548
    // blocks.
549
22677
    fn on_container_authors_noted(infos: &[AuthorNotingInfo<T::AccountId>]) -> Weight {
550
45477
        for info in infos {
551
22800
            let para_id = info.para_id;
552
22800
            if Pallet::<T>::burn_block_production_free_credit_for_para(&para_id).is_err() {
553
400
                let (amount_to_charge, _weight) =
554
400
                    T::ProvideBlockProductionCost::block_cost(&para_id);
555
400

            
556
400
                match T::Currency::withdraw(
557
400
                    &Self::parachain_tank(para_id),
558
400
                    amount_to_charge,
559
400
                    WithdrawReasons::FEE,
560
400
                    ExistenceRequirement::KeepAlive,
561
400
                ) {
562
320
                    Err(e) => log::warn!(
563
318
                        "Failed to withdraw block production payment for container chain {}: {:?}",
564
318
                        u32::from(para_id),
565
                        e
566
                    ),
567
80
                    Ok(imbalance) => {
568
80
                        T::OnChargeForBlock::on_unbalanced(imbalance);
569
80
                    }
570
                }
571
22400
            }
572
        }
573

            
574
22677
        T::WeightInfo::on_container_authors_noted(infos.len() as u32)
575
22677
    }
576

            
577
    #[cfg(feature = "runtime-benchmarks")]
578
    fn prepare_worst_case_for_bench(
579
        _author: &T::AccountId,
580
        _block_number: BlockNumber,
581
        para_id: ParaId,
582
    ) {
583
        let (amount_to_charge, _weight) = T::ProvideBlockProductionCost::block_cost(&para_id);
584
        // mint large amount (bigger than ED) to ensure withdraw will not fail.
585
        let mint = BalanceOf::<T>::from(2_000_000_000u32).saturating_add(amount_to_charge);
586

            
587
        // mint twice more to not have ED issues
588
        T::Currency::resolve_creating(&Self::parachain_tank(para_id), T::Currency::issue(mint));
589
    }
590
}
591

            
592
impl<T: Config> CollatorAssignmentHook<BalanceOf<T>> for Pallet<T> {
593
    // is_parathread parameter for future use to apply different logic
594
3
    fn on_collators_assigned(
595
3
        para_id: ParaId,
596
3
        maybe_tip: Option<&BalanceOf<T>>,
597
3
        _is_parathread: bool,
598
3
    ) -> Result<Weight, DispatchError> {
599
        // Withdraw assignment fee
600
2
        let maybe_assignment_imbalance =
601
3
            if Pallet::<T>::burn_collator_assignment_free_credit_for_para(&para_id).is_err() {
602
3
                let (amount_to_charge, _weight) =
603
3
                    T::ProvideCollatorAssignmentCost::collator_assignment_cost(&para_id);
604
3
                Some(T::Currency::withdraw(
605
3
                    &Self::parachain_tank(para_id),
606
3
                    amount_to_charge,
607
3
                    WithdrawReasons::FEE,
608
3
                    ExistenceRequirement::KeepAlive,
609
3
                )?)
610
            } else {
611
                None
612
            };
613

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

            
646
1
        if let Some(assignment_imbalance) = maybe_assignment_imbalance {
647
1
            T::OnChargeForCollatorAssignment::on_unbalanced(assignment_imbalance);
648
1
        }
649

            
650
1
        Ok(T::WeightInfo::on_collators_assigned())
651
3
    }
652
}
653

            
654
impl<T: Config> CollatorAssignmentTip<BalanceOf<T>> for Pallet<T> {
655
7602
    fn get_para_tip(para_id: ParaId) -> Option<BalanceOf<T>> {
656
7602
        MaxTip::<T>::get(para_id)
657
7602
    }
658
}
659

            
660
impl<T: Config> Pallet<T> {
661
    /// Derive a derivative account ID from the paraId.
662
10653
    pub fn parachain_tank(para_id: ParaId) -> T::AccountId {
663
10653
        let entropy = (b"modlpy/serpayment", para_id).using_encoded(blake2_256);
664
10653
        Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
665
10653
            .expect("infinite length input; no invalid inputs for type; qed")
666
10653
    }
667

            
668
    /// Hook to perform things on deregister
669
97
    pub fn para_deregistered(para_id: ParaId) {
670
97
        // Drain the para-id account from tokens
671
97
        let parachain_tank_balance = T::Currency::total_balance(&Self::parachain_tank(para_id));
672
97
        if !parachain_tank_balance.is_zero() {
673
14
            if let Ok(imbalance) = T::Currency::withdraw(
674
14
                &Self::parachain_tank(para_id),
675
14
                parachain_tank_balance,
676
14
                WithdrawReasons::FEE,
677
14
                ExistenceRequirement::AllowDeath,
678
14
            ) {
679
14
                if let Some(address) = RefundAddress::<T>::get(para_id) {
680
7
                    T::Currency::resolve_creating(&address, imbalance);
681
7
                } else {
682
7
                    // Burn for now, we might be able to pass something to do with this
683
7
                    drop(imbalance);
684
7
                }
685
            }
686
83
        }
687

            
688
        // Clean refund addres
689
97
        RefundAddress::<T>::remove(para_id);
690
97

            
691
97
        // Clean credits
692
97
        BlockProductionCredits::<T>::remove(para_id);
693
97
        CollatorAssignmentCredits::<T>::remove(para_id);
694
97
        MaxTip::<T>::remove(para_id);
695
97
        MaxCorePrice::<T>::remove(para_id);
696
97
    }
697
}