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
23109
#[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
1322
    #[pallet::error]
91
    pub enum Error<T> {
92
        InsufficientFundsToPurchaseCredits,
93
        InsufficientCredits,
94
        CreditPriceTooExpensive,
95
    }
96

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

            
100
588
    #[pallet::event]
101
28688
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
102
    pub enum Event<T: Config> {
103
1
        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
2
        MaxCorePriceUpdated {
130
            para_id: ParaId,
131
            max_core_price: Option<u128>,
132
        },
133
        CollatorAssignmentCreditsSet {
134
            para_id: ParaId,
135
            credits: u32,
136
        },
137
    }
138

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

            
143
16441
    #[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
852
    #[pallet::storage]
149
    pub type GivenFreeCredits<T: Config> = StorageMap<_, Blake2_128Concat, ParaId, (), OptionQuery>;
150

            
151
    /// Refund address
152
712
    #[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
703
    #[pallet::storage]
158
    pub type MaxCorePrice<T: Config> = StorageMap<_, Blake2_128Concat, ParaId, u128, OptionQuery>;
159

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

            
164
1290
    #[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
        pub fn purchase_credits(
172
            origin: OriginFor<T>,
173
            para_id: ParaId,
174
            credit: BalanceOf<T>,
175
149
        ) -> DispatchResultWithPostInfo {
176
149
            let account = ensure_signed(origin)?;
177
149
            let parachain_tank = Self::parachain_tank(para_id);
178
149
            T::Currency::transfer(
179
149
                &account,
180
149
                &parachain_tank,
181
149
                credit,
182
149
                ExistenceRequirement::KeepAlive,
183
149
            )?;
184

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

            
191
148
            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
        pub fn set_block_production_credits(
199
            origin: OriginFor<T>,
200
            para_id: ParaId,
201
            free_block_credits: BlockNumberFor<T>,
202
89
        ) -> DispatchResultWithPostInfo {
203
89
            ensure_root(origin)?;
204

            
205
88
            Self::set_free_block_production_credits(&para_id, free_block_credits);
206
88

            
207
88
            Ok(().into())
208
        }
209

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

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

            
227
18
            Ok(().into())
228
        }
229

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

            
240
16
            if let Some(refund_address) = refund_address.clone() {
241
15
                RefundAddress::<T>::insert(para_id, refund_address.clone());
242
15
            } else {
243
1
                RefundAddress::<T>::remove(para_id);
244
1
            }
245

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

            
251
16
            Ok(().into())
252
        }
253

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

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

            
267
60
            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
        pub fn set_max_core_price(
274
            origin: OriginFor<T>,
275
            para_id: ParaId,
276
            max_core_price: Option<u128>,
277
2
        ) -> DispatchResultWithPostInfo {
278
2
            T::ManagerOrigin::ensure_origin(origin, &para_id)?;
279

            
280
2
            if let Some(max_core_price) = max_core_price {
281
2
                MaxCorePrice::<T>::insert(para_id, max_core_price);
282
2
            } else {
283
                MaxCorePrice::<T>::remove(para_id);
284
            }
285

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

            
291
2
            Ok(().into())
292
        }
293

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

            
305
34
            if let Some(max_tip) = max_tip {
306
34
                MaxTip::<T>::insert(para_id, max_tip);
307
34
            } else {
308
                MaxTip::<T>::remove(para_id);
309
            }
310

            
311
34
            Ok(().into())
312
        }
313
    }
314

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

            
323
20964
            ensure!(
324
20964
                existing_credits >= 1u32.into(),
325
400
                Error::<T>::InsufficientCredits,
326
            );
327

            
328
20564
            let updated_credits = existing_credits.saturating_sub(1u32.into());
329
20564
            BlockProductionCredits::<T>::insert(para_id, updated_credits);
330
20564

            
331
20564
            Self::deposit_event(Event::<T>::BlockProductionCreditBurned {
332
20564
                para_id: *para_id,
333
20564
                credits_remaining: updated_credits,
334
20564
            });
335
20564

            
336
20564
            Ok(().into())
337
20964
        }
338

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

            
345
7696
            ensure!(existing_credits >= 1u32, Error::<T>::InsufficientCredits,);
346

            
347
7441
            let updated_credits = existing_credits.saturating_sub(1u32);
348
7441
            CollatorAssignmentCredits::<T>::insert(para_id, updated_credits);
349
7441

            
350
7441
            Self::deposit_event(Event::<T>::CollatorAssignmentCreditBurned {
351
7441
                para_id: *para_id,
352
7441
                credits_remaining: updated_credits,
353
7441
            });
354
7441

            
355
7441
            Ok(().into())
356
7696
        }
357

            
358
124
        pub fn give_free_credits(para_id: &ParaId) -> Weight {
359
124
            if GivenFreeCredits::<T>::contains_key(para_id) {
360
                // This para id has already received free credits
361
2
                return Weight::default();
362
122
            }
363
122

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

            
373
            // Set number of credits to FreeCollatorAssignmentCredits
374
122
            let collator_assignment_existing_credits =
375
122
                CollatorAssignmentCredits::<T>::get(para_id).unwrap_or(0u32);
376
122
            let collator_assignment_updated_credits = T::FreeCollatorAssignmentCredits::get();
377
122

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

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

            
390
122
            Weight::default()
391
124
        }
392

            
393
186
        pub fn set_free_collator_assignment_credits(
394
186
            para_id: &ParaId,
395
186
            free_collator_assignment_credits: u32,
396
186
        ) {
397
186
            if free_collator_assignment_credits.is_zero() {
398
43
                CollatorAssignmentCredits::<T>::remove(para_id);
399
143
            } else {
400
143
                CollatorAssignmentCredits::<T>::insert(para_id, free_collator_assignment_credits);
401
143
            }
402

            
403
186
            Self::deposit_event(Event::<T>::CollatorAssignmentCreditsSet {
404
186
                para_id: *para_id,
405
186
                credits: free_collator_assignment_credits,
406
186
            });
407
186
        }
408

            
409
210
        pub fn set_free_block_production_credits(
410
210
            para_id: &ParaId,
411
210
            free_collator_block_production_credits: BlockNumberFor<T>,
412
210
        ) {
413
210
            if free_collator_block_production_credits.is_zero() {
414
67
                BlockProductionCredits::<T>::remove(para_id);
415
143
            } else {
416
143
                BlockProductionCredits::<T>::insert(
417
143
                    para_id,
418
143
                    free_collator_block_production_credits,
419
143
                );
420
143
            }
421

            
422
210
            Self::deposit_event(Event::<T>::BlockProductionCreditsSet {
423
210
                para_id: *para_id,
424
210
                credits: free_collator_block_production_credits,
425
210
            });
426
210
        }
427

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

            
439
120
                Self::deposit_event(Event::<T>::CollatorAssignmentTipCollected {
440
120
                    para_id: *para_id,
441
120
                    payer: Self::parachain_tank(*para_id),
442
120
                    tip: *tip,
443
120
                });
444
120
                T::OnChargeForCollatorAssignmentTip::on_unbalanced(tip_imbalance);
445
4922
            }
446
5042
            Ok(())
447
5057
        }
448

            
449
        pub fn free_block_production_credits(para_id: ParaId) -> Option<BlockNumberFor<T>> {
450
            BlockProductionCredits::<T>::get(para_id)
451
        }
452

            
453
        pub fn free_collator_assignment_credits(para_id: ParaId) -> Option<u32> {
454
            CollatorAssignmentCredits::<T>::get(para_id)
455
        }
456

            
457
        pub fn given_free_credits(para_id: ParaId) -> Option<()> {
458
            GivenFreeCredits::<T>::get(para_id)
459
        }
460

            
461
        pub fn refund_address(para_id: ParaId) -> Option<T::AccountId> {
462
            RefundAddress::<T>::get(para_id)
463
        }
464

            
465
        pub fn max_tip(para_id: ParaId) -> Option<BalanceOf<T>> {
466
            MaxTip::<T>::get(para_id)
467
        }
468
    }
469

            
470
    #[pallet::genesis_config]
471
    pub struct GenesisConfig<T: Config> {
472
        pub para_id_credits: Vec<FreeCreditGenesisParams<BlockNumberFor<T>>>,
473
    }
474

            
475
    impl<T: Config> Default for GenesisConfig<T> {
476
4
        fn default() -> Self {
477
4
            Self {
478
4
                para_id_credits: Default::default(),
479
4
            }
480
4
        }
481
    }
482

            
483
419
    #[pallet::genesis_build]
484
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
485
423
        fn build(&self) {
486
732
            for para_id_credits in &self.para_id_credits {
487
309
                BlockProductionCredits::<T>::insert(
488
309
                    para_id_credits.para_id,
489
309
                    para_id_credits.block_production_credits,
490
309
                );
491
309
                CollatorAssignmentCredits::<T>::insert(
492
309
                    para_id_credits.para_id,
493
309
                    para_id_credits.collator_assignment_credits,
494
309
                );
495
309
            }
496
423
        }
497
    }
498
}
499

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

            
519
/// Balance used by this pallet
520
pub type BalanceOf<T> =
521
    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
522

            
523
pub type CurrencyOf<T> = <T as Config>::Currency;
524
/// Type alias to conveniently refer to the `Currency::NegativeImbalance` associated type.
525
pub type NegativeImbalanceOf<T> =
526
    <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
527
/// Handler for fee charging. This will be invoked when fees need to be deducted from the fee
528
/// account for a given paraId.
529

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

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

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

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

            
571
20816
        T::WeightInfo::on_container_authors_noted(infos.len() as u32)
572
20816
    }
573

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

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

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

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

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

            
647
1
        Ok(T::WeightInfo::on_collators_assigned())
648
3
    }
649
}
650

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

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

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

            
685
        // Clean refund addres
686
89
        RefundAddress::<T>::remove(para_id);
687
89

            
688
89
        // Clean credits
689
89
        BlockProductionCredits::<T>::remove(para_id);
690
89
        CollatorAssignmentCredits::<T>::remove(para_id);
691
89
        MaxTip::<T>::remove(para_id);
692
89
        MaxCorePrice::<T>::remove(para_id);
693
89
    }
694
}