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

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

            
100
618
    #[pallet::event]
101
32188
    #[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
4
        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: u128,
132
        },
133
        CollatorAssignmentCreditsSet {
134
            para_id: ParaId,
135
            credits: u32,
136
        },
137
    }
138

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

            
143
20055
    #[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
20395
    #[pallet::storage]
162
    pub type MaxTip<T: Config> = StorageMap<_, Blake2_128Concat, ParaId, BalanceOf<T>, OptionQuery>;
163

            
164
1686
    #[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
180
        ) -> DispatchResultWithPostInfo {
176
180
            let account = ensure_signed(origin)?;
177
180
            let parachain_tank = Self::parachain_tank(para_id);
178
180
            T::Currency::transfer(
179
180
                &account,
180
180
                &parachain_tank,
181
180
                credit,
182
180
                ExistenceRequirement::KeepAlive,
183
180
            )?;
184

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

            
191
179
            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
101
        ) -> DispatchResultWithPostInfo {
203
101
            ensure_root(origin)?;
204

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

            
207
100
            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
28
        ) -> DispatchResultWithPostInfo {
238
28
            T::ManagerOrigin::ensure_origin(origin, &para_id)?;
239

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

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

            
251
20
            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
64
        ) -> DispatchResultWithPostInfo {
263
64
            ensure_root(origin)?;
264

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

            
267
64
            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: u128,
277
2
        ) -> DispatchResultWithPostInfo {
278
2
            T::ManagerOrigin::ensure_origin(origin, &para_id)?;
279

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

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

            
287
2
            Ok(().into())
288
        }
289

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

            
301
48
            if let Some(max_tip) = max_tip {
302
48
                MaxTip::<T>::insert(para_id, max_tip);
303
48
            } else {
304
                MaxTip::<T>::remove(para_id);
305
            }
306

            
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
22568
        pub fn burn_block_production_free_credit_for_para(
314
22568
            para_id: &ParaId,
315
22568
        ) -> DispatchResultWithPostInfo {
316
22568
            let existing_credits =
317
22568
                BlockProductionCredits::<T>::get(para_id).unwrap_or(BlockNumberFor::<T>::zero());
318
22568

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

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

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

            
332
22165
            Ok(().into())
333
22568
        }
334

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

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

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

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

            
351
9051
            Ok(().into())
352
9321
        }
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
6039
        pub fn charge_tip(para_id: &ParaId, tip: &BalanceOf<T>) -> Result<(), DispatchError> {
425
6039
            // Only charge the tip to the paras that had a max tip set
426
6039
            // (aka were willing to tip for being assigned a collator)
427
6039
            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
5855
            }
442
6023
            Ok(())
443
6039
        }
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
1774
            for para_id_credits in &self.para_id_credits {
483
456
                BlockProductionCredits::<T>::insert(
484
456
                    para_id_credits.para_id,
485
456
                    para_id_credits.block_production_credits,
486
456
                );
487
456
                CollatorAssignmentCredits::<T>::insert(
488
456
                    para_id_credits.para_id,
489
456
                    para_id_credits.collator_assignment_credits,
490
456
                );
491
456
            }
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
684
    fn from(value: (ParaId, BlockProductCredits, u32)) -> Self {
507
684
        Self {
508
684
            para_id: value.0,
509
684
            block_production_credits: value.1,
510
684
            collator_assignment_credits: value.2,
511
684
        }
512
684
    }
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

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

            
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 ProvideCollatorAssignmentCost<T: Config> {
535
    fn collator_assignment_cost(para_id: &ParaId) -> (BalanceOf<T>, Weight);
536
}
537

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

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

            
567
22436
        T::WeightInfo::on_container_authors_noted(infos.len() as u32)
568
22436
    }
569

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

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

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

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

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

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

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

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

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

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

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