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
            RefundAddress::<T>::set(para_id, refund_address.clone());
246
20

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

            
252
20
            Ok(().into())
253
        }
254

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

            
267
64
            Self::set_free_collator_assignment_credits(&para_id, free_collator_assignment_credits);
268
64

            
269
64
            Ok(().into())
270
        }
271

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

            
283
2
            MaxCorePrice::<T>::insert(para_id, max_core_price);
284
2

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

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

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

            
305
48
            MaxTip::<T>::set(para_id, max_tip);
306
48

            
307
48
            Ok(().into())
308
        }
309
    }
310

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

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

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

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

            
332
22401
            Ok(().into())
333
22804
        }
334

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

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

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

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

            
351
9231
            Ok(().into())
352
9501
        }
353

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

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

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

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

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

            
386
217
            Weight::default()
387
219
        }
388

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

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

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

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

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

            
435
168
                Self::deposit_event(Event::<T>::CollatorAssignmentTipCollected {
436
168
                    para_id: *para_id,
437
168
                    payer: Self::parachain_tank(*para_id),
438
168
                    tip: *tip,
439
168
                });
440
168
                T::OnChargeForCollatorAssignmentTip::on_unbalanced(tip_imbalance);
441
5963
            }
442
6131
            Ok(())
443
6147
        }
444

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
566
22677
        T::WeightInfo::on_container_authors_noted(infos.len() as u32)
567
22677
    }
568

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

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

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

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

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

            
642
1
        Ok(T::WeightInfo::on_collators_assigned())
643
3
    }
644
}
645

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

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

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

            
680
        // Clean refund addres
681
97
        RefundAddress::<T>::remove(para_id);
682
97

            
683
97
        // Clean credits
684
97
        BlockProductionCredits::<T>::remove(para_id);
685
97
        CollatorAssignmentCredits::<T>::remove(para_id);
686
97
        MaxTip::<T>::remove(para_id);
687
97
        MaxCorePrice::<T>::remove(para_id);
688
97
    }
689
}