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
//! # Inflation Rewards Pallet
18
//!
19
//! This pallet handle native token inflation and rewards distribution.
20
//!
21
//! Inflation is minted once in `on_initialize`, using
22
//! `T::Currency::issue(T::InflationRate::get() * T::Currency::total_issuance());`.
23
//!
24
//! Note that this is `Perbill` `Mul`, so it rounds to the nearest integer.
25
//! Search for `rational_mul_correction` in polkadot-sdk for more info.
26
//!
27
//! Then, the inflation is split into 2 parts based on `T::RewardsPortion::get()`.
28
//! The rewards portion is transferred first to `T::PendingRewardsAccount` in `on_initialize`,
29
//! and then to each collator in `on_container_authors_noted`. If a chain doesn't produce blocks,
30
//! the reward stays in that account and is transferred to `T::OnUnbalanced` on the next block
31
//! `on_initialize`.
32
//!
33
//! The `T::OnUnbalanced` handles inflation that doesn't go to block rewards, this is usually the
34
//! parachain bond account.
35

            
36
#![cfg_attr(not(feature = "std"), no_std)]
37
extern crate alloc;
38

            
39
pub use pallet::*;
40

            
41
#[cfg(test)]
42
mod mock;
43

            
44
#[cfg(test)]
45
mod tests;
46

            
47
#[cfg(feature = "runtime-benchmarks")]
48
use tp_traits::BlockNumber;
49
use {
50
    alloc::vec::Vec,
51
    dp_core::ParaId,
52
    frame_support::{
53
        pallet_prelude::*,
54
        traits::{
55
            fungible::{Balanced, Credit, Inspect},
56
            tokens::{Fortitude, Precision, Preservation},
57
            Imbalance, OnUnbalanced,
58
        },
59
    },
60
    frame_system::pallet_prelude::*,
61
    sp_runtime::{
62
        traits::{Get, Saturating, Zero},
63
        Perbill,
64
    },
65
    tp_traits::{
66
        AuthorNotingHook, AuthorNotingInfo, DistributeRewards, ForSession,
67
        GetContainerChainsWithCollators, MaybeSelfChainBlockAuthor,
68
    },
69
};
70

            
71
#[frame_support::pallet]
72
pub mod pallet {
73
    use super::*;
74

            
75
    pub type BalanceOf<T> =
76
        <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
77
    pub type CreditOf<T> = Credit<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
78

            
79
    /// Inflation rewards pallet.
80
    #[pallet::pallet]
81
    pub struct Pallet<T>(PhantomData<T>);
82

            
83
    #[pallet::hooks]
84
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
85
16957
        fn on_initialize(_: BlockNumberFor<T>) -> Weight {
86
16957
            let mut weight = T::DbWeight::get().reads(1);
87

            
88
            // Collect indistributed rewards, if any
89
            // Any parachain we have not rewarded is handled by onUnbalanced
90
16957
            let not_distributed_rewards =
91
16957
                if let Some(chains_to_reward) = ChainsToReward::<T>::take() {
92
                    // Collect and sum all undistributed rewards
93
8332
                    let rewards_not_distributed: BalanceOf<T> = chains_to_reward
94
8332
                        .rewards_per_chain
95
8332
                        .saturating_mul((chains_to_reward.para_ids.len() as u32).into());
96
8332
                    T::Currency::withdraw(
97
8332
                        &T::PendingRewardsAccount::get(),
98
8332
                        rewards_not_distributed,
99
8332
                        Precision::BestEffort,
100
8332
                        Preservation::Expendable,
101
8332
                        Fortitude::Force,
102
                    )
103
8332
                    .unwrap_or_else(|_e| {
104
                        log::debug!("failed to withdraw from PendingRewardsAccount");
105

            
106
                        CreditOf::<T>::zero()
107
                    })
108
                } else {
109
8625
                    CreditOf::<T>::zero()
110
                };
111

            
112
            // Get the number of chains at this block (container chain blocks)
113
16957
            weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
114
16957
            let container_chains_to_check_unbounded: Vec<_> =
115
16957
                T::ContainerChains::container_chains_with_collators(ForSession::Current)
116
16957
                    .into_iter()
117
18710
                    .filter_map(|(para_id, collators)| (!collators.is_empty()).then_some(para_id))
118
16957
                    .collect();
119

            
120
            // Convert to BoundedBTreeSet. If the number of chains is greater than the limit, truncate
121
            // and emit a warning. This should never happen because we assume that
122
            // MaxContainerChains has the same value in all the pallets, but that's not enforced.
123
            // A better solution would be for container_chains_with_collators to return an already
124
            // bounded data structure.
125
16957
            let unbounded_len = container_chains_to_check_unbounded.len();
126
16957
            let container_chains_to_check = bounded_vec_into_bounded_btree_set(
127
16957
                BoundedVec::truncate_from(container_chains_to_check_unbounded),
128
            );
129
16957
            if container_chains_to_check.len() != unbounded_len {
130
                log::warn!("inflation_rewards: got more chains than max");
131
16957
            }
132

            
133
16957
            let mut number_of_chains: BalanceOf<T> =
134
16957
                (container_chains_to_check.len() as u32).into();
135

            
136
            // We only add 1 extra chain to number_of_chains if we are
137
            // in a parachain context with an orchestrator configured.
138
16957
            if T::GetSelfChainBlockAuthor::get_block_author().is_some() {
139
6262
                number_of_chains.saturating_inc();
140
16957
            }
141

            
142
            // Only create new supply and rewards if number_of_chains is not zero.
143
16957
            if !number_of_chains.is_zero() {
144
                // Issue new supply
145
8806
                let new_supply =
146
8806
                    T::Currency::issue(T::InflationRate::get() * T::Currency::total_issuance());
147

            
148
                // Split staking reward portion
149
8806
                let total_rewards = T::RewardsPortion::get() * new_supply.peek();
150
8806
                let (rewards_credit, reminder_credit) = new_supply.split(total_rewards);
151

            
152
8806
                let rewards_per_chain: BalanceOf<T> = rewards_credit
153
8806
                    .peek()
154
8806
                    .checked_div(&number_of_chains)
155
8806
                    .unwrap_or_else(|| {
156
                        // This is unreachable because we checked `number_of_chains.is_zero()` above
157
                        log::error!("Rewards per chain is zero");
158
                        BalanceOf::<T>::zero()
159
                    });
160
                // rewards_credit must be a multiple of number_of_chains, because the reward is split
161
                // evenly between all chains. So take the remainder (total_rewards % number_of_chains)
162
                // and move it to total_remainder, like this:
163
                // total_remainder = reminder_credit + (total_rewards % number_of_chains)
164
                // staking_rewards = rewards_credit - (total_rewards % number_of_chains)
165
                // This guarantees that `staking_rewards - rewards_per_chain * number_of_chains == 0`
166
8806
                let (mut total_remainder, staking_rewards) = rewards_credit.split_merge(
167
8806
                    total_rewards % number_of_chains,
168
8806
                    (reminder_credit, CreditOf::<T>::zero()),
169
8806
                );
170

            
171
                // Deposit the new supply dedicated to rewards in the pending rewards account
172
                if let Err(undistributed_rewards) =
173
8806
                    T::Currency::resolve(&T::PendingRewardsAccount::get(), staking_rewards)
174
                {
175
                    // This error can happen if `PendingRewardsAccount` has 0 balance and
176
                    // `staking_rewards` is less than existential deposit. In that case, we won't
177
                    // be able to reward collators, and the reward will go to `OnUnbalanced`.
178
                    total_remainder = total_remainder.merge(undistributed_rewards);
179
8806
                }
180

            
181
                // Keep track of chains to reward
182
8806
                ChainsToReward::<T>::put(ChainsToRewardValue {
183
8806
                    para_ids: container_chains_to_check,
184
8806
                    rewards_per_chain,
185
8806
                });
186

            
187
                // Let the runtime handle the non-staking part, plus the non distributed rewards
188
                // from the previous block, plus the dust from total_rewards % number_of_chains.
189
8806
                T::OnUnbalanced::on_unbalanced(not_distributed_rewards.merge(total_remainder));
190

            
191
                // We don't reward the orchestrator in solochain mode
192
8806
                if let Some(orchestrator_author) = T::GetSelfChainBlockAuthor::get_block_author() {
193
6262
                    // Container chain authors get rewarded later in inherent, but orchestrator
194
6262
                    // author is rewarded now.
195
6262
                    weight.saturating_accrue(Self::reward_orchestrator_author(orchestrator_author));
196
8806
                }
197
8151
            }
198

            
199
16957
            weight
200
16957
        }
201
    }
202

            
203
    #[pallet::config]
204
    pub trait Config: frame_system::Config {
205
        type Currency: Inspect<Self::AccountId> + Balanced<Self::AccountId>;
206

            
207
        /// Get container chains with collators. The number of chains returned affects inflation:
208
        /// we mint tokens to reward the collator of each chain.
209
        type ContainerChains: GetContainerChainsWithCollators<Self::AccountId>;
210

            
211
        /// Hard limit on number of container chains with collators. Used to define bounded storage.
212
        type MaxContainerChains: Get<u32>;
213

            
214
        /// Get block author for self chain
215
        type GetSelfChainBlockAuthor: MaybeSelfChainBlockAuthor<Self::AccountId>;
216

            
217
        /// Inflation rate per orchestrator block (proportion of the total issuance)
218
        #[pallet::constant]
219
        type InflationRate: Get<Perbill>;
220

            
221
        /// What to do with the new supply not dedicated to staking
222
        type OnUnbalanced: OnUnbalanced<CreditOf<Self>>;
223

            
224
        /// The account that will store rewards waiting to be paid out
225
        #[pallet::constant]
226
        type PendingRewardsAccount: Get<Self::AccountId>;
227

            
228
        /// Staking rewards distribution implementation
229
        type StakingRewardsDistributor: DistributeRewards<Self::AccountId, CreditOf<Self>>;
230

            
231
        /// Proportion of the new supply dedicated to staking
232
        #[pallet::constant]
233
        type RewardsPortion: Get<Perbill>;
234
    }
235

            
236
    #[pallet::event]
237
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
238
    pub enum Event<T: Config> {
239
        /// Rewarding orchestrator author
240
        RewardedOrchestrator {
241
            account_id: T::AccountId,
242
            balance: BalanceOf<T>,
243
        },
244
        /// Rewarding container author
245
        RewardedContainer {
246
            account_id: T::AccountId,
247
            para_id: ParaId,
248
            balance: BalanceOf<T>,
249
        },
250
    }
251

            
252
    /// Container chains to reward per block.
253
    /// This gets initialized to the list of chains that should be producing blocks.
254
    /// Then, in the `set_latest_author_data` inherent, the chains that actually have produced
255
    /// blocks are rewarded and removed from this list, in the `on_container_authors_noted` hook.
256
    /// Chains that have not produced blocks stay in this list, and their rewards get accumulated as
257
    /// `not_distributed_rewards` and handled by `OnUnbalanced` in the next block `on_initialize`.
258
    #[pallet::storage]
259
    pub(super) type ChainsToReward<T: Config> =
260
        StorageValue<_, ChainsToRewardValue<T>, OptionQuery>;
261

            
262
    #[derive(
263
        Clone, Encode, Decode, PartialEq, sp_core::RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
264
    )]
265
    #[scale_info(skip_type_params(T))]
266
    pub struct ChainsToRewardValue<T: Config> {
267
        pub para_ids: BoundedBTreeSet<ParaId, <T as Config>::MaxContainerChains>,
268
        pub rewards_per_chain: BalanceOf<T>,
269
    }
270

            
271
    impl<T: Config> Pallet<T> {
272
6262
        fn reward_orchestrator_author(orchestrator_author: T::AccountId) -> Weight {
273
6262
            let mut total_weight = T::DbWeight::get().reads(1);
274
6262
            if let Some(chains_to_reward) = ChainsToReward::<T>::get() {
275
6262
                total_weight.saturating_accrue(T::DbWeight::get().reads(1));
276
6262
                let actual_reward = T::Currency::withdraw(
277
6262
                    &T::PendingRewardsAccount::get(),
278
6262
                    chains_to_reward.rewards_per_chain,
279
6262
                    Precision::BestEffort,
280
6262
                    Preservation::Expendable,
281
6262
                    Fortitude::Force,
282
                )
283
6262
                .unwrap_or_else(|_e| {
284
                    log::debug!("failed to withdraw from PendingRewardsAccount");
285

            
286
                    CreditOf::<T>::zero()
287
                });
288
6262
                let actual_reward_for_event = actual_reward.peek();
289
6262
                if actual_reward_for_event != chains_to_reward.rewards_per_chain {
290
                    log::warn!("collator reward different than expected");
291
6262
                }
292
6262
                match T::StakingRewardsDistributor::distribute_rewards(
293
6262
                    orchestrator_author.clone(),
294
6262
                    actual_reward,
295
6262
                ) {
296
6196
                    Ok(frame_support::dispatch::PostDispatchInfo { actual_weight, .. }) => {
297
6196
                        Self::deposit_event(Event::RewardedOrchestrator {
298
6196
                            account_id: orchestrator_author,
299
6196
                            balance: actual_reward_for_event,
300
6196
                        });
301

            
302
6196
                        if let Some(weight) = actual_weight {
303
6186
                            total_weight.saturating_accrue(weight)
304
10
                        }
305
                    }
306
66
                    Err(e) => {
307
66
                        log::debug!("Fail to distribute rewards: {:?}", e)
308
                    }
309
                }
310
            } else {
311
                panic!("ChainsToReward not filled");
312
            }
313
6262
            total_weight
314
6262
        }
315

            
316
        pub fn container_chains_to_reward() -> Option<ChainsToRewardValue<T>> {
317
            ChainsToReward::<T>::get()
318
        }
319
    }
320
}
321

            
322
// This function should only be used to **reward** a container author.
323
// There will be no additional check other than checking if we have already
324
// rewarded this author for **in this tanssi block**
325
// Any additional check should be done in the calling function
326
impl<T: Config> AuthorNotingHook<T::AccountId> for Pallet<T> {
327
135
    fn on_container_authors_noted(info: &[AuthorNotingInfo<T::AccountId>]) -> Weight {
328
135
        if info.is_empty() {
329
            return Weight::zero();
330
135
        }
331
135
        let mut total_weight = T::DbWeight::get().reads_writes(1, 0);
332
        // We take chains to reward, to see what containers are left to reward
333
135
        if let Some(mut container_chains_to_reward) = ChainsToReward::<T>::get() {
334
282
            for info in info {
335
147
                let author = &info.author;
336
147
                let para_id = info.para_id;
337

            
338
                // If we find the para id is because we still have not rewarded it
339
                // this makes sure we dont reward it twice in the same block
340
147
                if container_chains_to_reward.para_ids.remove(&para_id) {
341
                    // we distribute rewards to the author
342
146
                    let actual_reward = T::Currency::withdraw(
343
146
                        &T::PendingRewardsAccount::get(),
344
146
                        container_chains_to_reward.rewards_per_chain,
345
146
                        Precision::BestEffort,
346
146
                        Preservation::Expendable,
347
146
                        Fortitude::Force,
348
                    )
349
146
                    .unwrap_or_else(|_e| {
350
                        log::debug!("failed to withdraw from PendingRewardsAccount");
351

            
352
                        CreditOf::<T>::zero()
353
                    });
354
146
                    let actual_reward_for_event = actual_reward.peek();
355
146
                    if actual_reward_for_event != container_chains_to_reward.rewards_per_chain {
356
                        log::warn!("collator reward different than expected");
357
146
                    }
358
146
                    match T::StakingRewardsDistributor::distribute_rewards(
359
146
                        author.clone(),
360
146
                        actual_reward,
361
146
                    ) {
362
146
                        Ok(frame_support::dispatch::PostDispatchInfo { actual_weight, .. }) => {
363
146
                            Self::deposit_event(Event::RewardedContainer {
364
146
                                account_id: author.clone(),
365
146
                                balance: actual_reward_for_event,
366
146
                                para_id,
367
146
                            });
368
146
                            if let Some(weight) = actual_weight {
369
143
                                total_weight.saturating_accrue(weight)
370
3
                            }
371
                        }
372
                        Err(e) => {
373
                            log::warn!("Fail to distribute rewards: {:?}", e)
374
                        }
375
                    }
376
                } else {
377
                    // para id not found in list, either
378
                    // * tried to reward a chain that doesn't have any collators assigned
379
                    // * tried to reward the same chain twice in the same block
380
1
                    log::warn!("ChainsToReward: para id not found");
381
                }
382
            }
383

            
384
135
            total_weight.saturating_accrue(T::DbWeight::get().writes(1));
385
            // Keep track of chains to reward
386
135
            ChainsToReward::<T>::put(container_chains_to_reward);
387
        } else {
388
            // Unreachable because we always write ChainsToReward in on_initialize
389
            log::warn!("ChainsToReward is None");
390
        }
391

            
392
135
        total_weight
393
135
    }
394

            
395
    #[cfg(feature = "runtime-benchmarks")]
396
    fn prepare_worst_case_for_bench(a: &T::AccountId, _b: BlockNumber, para_id: ParaId) {
397
        // arbitrary amount to perform rewarding
398
        // we mint twice as much to the rewards account to make it possible
399
        let reward_amount = 1_000_000_000u32;
400
        let mint = reward_amount.saturating_mul(2);
401

            
402
        T::Currency::resolve(
403
            &T::PendingRewardsAccount::get(),
404
            T::Currency::issue(BalanceOf::<T>::from(mint)),
405
        )
406
        .expect("to mint tokens");
407

            
408
        // TODO: this API doesn't make sense, we want to add a para id to the list of chains to
409
        // reward. So change `prepare_worst_case_for_bench` into something that takes a list
410
        let old_para_ids: alloc::vec::Vec<_> = ChainsToReward::<T>::get()
411
            .map(|x| x.para_ids.into_iter().collect())
412
            .unwrap_or_default();
413
        ChainsToReward::<T>::put(ChainsToRewardValue {
414
            para_ids: alloc::collections::BTreeSet::from_iter(
415
                old_para_ids.into_iter().chain([para_id]),
416
            )
417
            .try_into()
418
            .expect("to be in bound"),
419
            rewards_per_chain: BalanceOf::<T>::from(reward_amount),
420
        });
421

            
422
        T::StakingRewardsDistributor::prepare_worst_case_for_bench(a);
423
    }
424

            
425
    #[cfg(feature = "runtime-benchmarks")]
426
    fn bench_advance_block() {
427
        T::StakingRewardsDistributor::bench_advance_block()
428
    }
429

            
430
    #[cfg(feature = "runtime-benchmarks")]
431
    fn bench_execute_pending() {
432
        T::StakingRewardsDistributor::bench_execute_pending()
433
    }
434
}
435

            
436
/// Convert a `BoundedVec` into a `BoundedBTreeSet` with the same bound.
437
// TODO: upstream this into BoundedVec crate
438
16957
fn bounded_vec_into_bounded_btree_set<T: core::cmp::Ord + core::fmt::Debug, S: Get<u32>>(
439
16957
    x: BoundedVec<T, S>,
440
16957
) -> BoundedBTreeSet<T, S> {
441
16957
    let mut set = BoundedBTreeSet::default();
442

            
443
25282
    for item in x {
444
8325
        set.try_insert(item)
445
8325
            .expect("insert must have enough space because vec and set use the same type as bound");
446
8325
    }
447

            
448
16957
    set
449
16957
}