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

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

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

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

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

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

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

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

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

            
87
        type WeightInfo: WeightInfo;
88
    }
89

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
339
22165
            Ok(().into())
340
22568
        }
341

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

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

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

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

            
358
9047
            Ok(().into())
359
9317
        }
360

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
573
22436
        T::WeightInfo::on_container_authors_noted(infos.len() as u32)
574
22436
    }
575

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

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

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

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

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

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

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

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

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

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

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