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
#![cfg_attr(not(feature = "std"), no_std)]
22
extern crate alloc;
23

            
24
pub use pallet::*;
25

            
26
#[cfg(test)]
27
mod mock;
28

            
29
#[cfg(test)]
30
mod tests;
31

            
32
#[cfg(feature = "runtime-benchmarks")]
33
use tp_traits::BlockNumber;
34
use {
35
    alloc::vec::Vec,
36
    dp_core::ParaId,
37
    frame_support::{
38
        pallet_prelude::*,
39
        traits::{
40
            fungible::{Balanced, Credit, Inspect},
41
            tokens::{Fortitude, Precision, Preservation},
42
            Imbalance, OnUnbalanced,
43
        },
44
    },
45
    frame_system::pallet_prelude::*,
46
    sp_runtime::{
47
        traits::{Get, Saturating, Zero},
48
        Perbill,
49
    },
50
    tp_traits::{
51
        AuthorNotingHook, AuthorNotingInfo, DistributeRewards, ForSession,
52
        GetContainerChainsWithCollators, MaybeSelfChainBlockAuthor,
53
    },
54
};
55

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

            
60
    pub type BalanceOf<T> =
61
        <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
62
    pub type CreditOf<T> = Credit<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
63

            
64
    /// Inflation rewards pallet.
65
    #[pallet::pallet]
66
    pub struct Pallet<T>(PhantomData<T>);
67

            
68
    #[pallet::hooks]
69
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
70
14401
        fn on_initialize(_: BlockNumberFor<T>) -> Weight {
71
14401
            let mut weight = T::DbWeight::get().reads(1);
72

            
73
            // Collect indistributed rewards, if any
74
            // Any parachain we have not rewarded is handled by onUnbalanced
75
14401
            let not_distributed_rewards =
76
14401
                if let Some(chains_to_reward) = ChainsToReward::<T>::take() {
77
                    // Collect and sum all undistributed rewards
78
8332
                    let rewards_not_distributed: BalanceOf<T> = chains_to_reward
79
8332
                        .rewards_per_chain
80
8332
                        .saturating_mul((chains_to_reward.para_ids.len() as u32).into());
81
8332
                    T::Currency::withdraw(
82
8332
                        &T::PendingRewardsAccount::get(),
83
8332
                        rewards_not_distributed,
84
8332
                        Precision::BestEffort,
85
8332
                        Preservation::Expendable,
86
8332
                        Fortitude::Force,
87
                    )
88
8332
                    .unwrap_or(CreditOf::<T>::zero())
89
                } else {
90
6069
                    CreditOf::<T>::zero()
91
                };
92

            
93
            // Get the number of chains at this block (tanssi + container chain blocks)
94
14401
            weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
95
14401
            let container_chains_to_check_unbounded: Vec<_> =
96
14401
                T::ContainerChains::container_chains_with_collators(ForSession::Current)
97
14401
                    .into_iter()
98
16154
                    .filter_map(|(para_id, collators)| (!collators.is_empty()).then_some(para_id))
99
14401
                    .collect();
100

            
101
            // Convert to BoundedVec. If the number of chains is greater than the limit, truncate
102
            // and emit a warning. This should never happen because we assume that
103
            // MaxContainerChains has the same value in all the pallets, but that's not enforced.
104
            // A better solution would be for container_chains_with_collators to return an already
105
            // bounded data structure.
106
14401
            let unbounded_len = container_chains_to_check_unbounded.len();
107
14401
            let container_chains_to_check =
108
14401
                BoundedVec::truncate_from(container_chains_to_check_unbounded);
109
14401
            if container_chains_to_check.len() != unbounded_len {
110
                log::warn!("inflation_rewards: got more chains than max. ")
111
14401
            }
112

            
113
14401
            let mut number_of_chains: BalanceOf<T> =
114
14401
                (container_chains_to_check.len() as u32).into();
115

            
116
            // We only add 1 extra chain to number_of_chains if we are
117
            // in a parachain context with an orchestrator configured.
118
14401
            if T::GetSelfChainBlockAuthor::get_block_author().is_some() {
119
6262
                number_of_chains.saturating_inc();
120
14401
            }
121

            
122
            // Only create new supply and rewards if number_of_chains is not zero.
123
14401
            if !number_of_chains.is_zero() {
124
                // Issue new supply
125
8804
                let new_supply =
126
8804
                    T::Currency::issue(T::InflationRate::get() * T::Currency::total_issuance());
127

            
128
                // Split staking reward portion
129
8804
                let total_rewards = T::RewardsPortion::get() * new_supply.peek();
130
8804
                let (rewards_credit, reminder_credit) = new_supply.split(total_rewards);
131

            
132
8804
                let rewards_per_chain: BalanceOf<T> = rewards_credit
133
8804
                    .peek()
134
8804
                    .checked_div(&number_of_chains)
135
8804
                    .unwrap_or_else(|| {
136
                        log::error!("Rewards per chain is zero");
137
                        BalanceOf::<T>::zero()
138
                    });
139
8804
                let (mut total_reminder, staking_rewards) = rewards_credit.split_merge(
140
8804
                    total_rewards % number_of_chains,
141
8804
                    (reminder_credit, CreditOf::<T>::zero()),
142
8804
                );
143

            
144
                // Deposit the new supply dedicated to rewards in the pending rewards account
145
                if let Err(undistributed_rewards) =
146
8804
                    T::Currency::resolve(&T::PendingRewardsAccount::get(), staking_rewards)
147
                {
148
                    total_reminder = total_reminder.merge(undistributed_rewards);
149
8804
                }
150

            
151
                // Keep track of chains to reward
152
8804
                ChainsToReward::<T>::put(ChainsToRewardValue {
153
8804
                    para_ids: container_chains_to_check,
154
8804
                    rewards_per_chain,
155
8804
                });
156

            
157
                // Let the runtime handle the non-staking part
158
8804
                T::OnUnbalanced::on_unbalanced(not_distributed_rewards.merge(total_reminder));
159

            
160
                // We don't reward the orchestrator in solochain mode
161
8804
                if let Some(orchestrator_author) = T::GetSelfChainBlockAuthor::get_block_author() {
162
6262
                    weight.saturating_accrue(Self::reward_orchestrator_author(orchestrator_author));
163
8804
                }
164
5597
            }
165

            
166
14401
            weight
167
14401
        }
168
    }
169

            
170
    #[pallet::config]
171
    pub trait Config: frame_system::Config {
172
        type Currency: Inspect<Self::AccountId> + Balanced<Self::AccountId>;
173

            
174
        /// Get container chains with collators. The number of chains returned affects inflation:
175
        /// we mint tokens to reward the collator of each chain.
176
        type ContainerChains: GetContainerChainsWithCollators<Self::AccountId>;
177

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

            
181
        /// Get block author for self chain
182
        type GetSelfChainBlockAuthor: MaybeSelfChainBlockAuthor<Self::AccountId>;
183

            
184
        /// Inflation rate per orchestrator block (proportion of the total issuance)
185
        #[pallet::constant]
186
        type InflationRate: Get<Perbill>;
187

            
188
        /// What to do with the new supply not dedicated to staking
189
        type OnUnbalanced: OnUnbalanced<CreditOf<Self>>;
190

            
191
        /// The account that will store rewards waiting to be paid out
192
        #[pallet::constant]
193
        type PendingRewardsAccount: Get<Self::AccountId>;
194

            
195
        /// Staking rewards distribution implementation
196
        type StakingRewardsDistributor: DistributeRewards<Self::AccountId, CreditOf<Self>>;
197

            
198
        /// Proportion of the new supply dedicated to staking
199
        #[pallet::constant]
200
        type RewardsPortion: Get<Perbill>;
201
    }
202

            
203
    #[pallet::event]
204
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
205
    pub enum Event<T: Config> {
206
        /// Rewarding orchestrator author
207
        RewardedOrchestrator {
208
            account_id: T::AccountId,
209
            balance: BalanceOf<T>,
210
        },
211
        /// Rewarding container author
212
        RewardedContainer {
213
            account_id: T::AccountId,
214
            para_id: ParaId,
215
            balance: BalanceOf<T>,
216
        },
217
    }
218

            
219
    /// Container chains to reward per block
220
    #[pallet::storage]
221
    pub(super) type ChainsToReward<T: Config> =
222
        StorageValue<_, ChainsToRewardValue<T>, OptionQuery>;
223

            
224
    #[derive(
225
        Clone, Encode, Decode, PartialEq, sp_core::RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
226
    )]
227
    #[scale_info(skip_type_params(T))]
228
    pub struct ChainsToRewardValue<T: Config> {
229
        pub para_ids: BoundedVec<ParaId, <T as Config>::MaxContainerChains>,
230
        pub rewards_per_chain: BalanceOf<T>,
231
    }
232

            
233
    impl<T: Config> Pallet<T> {
234
6262
        fn reward_orchestrator_author(orchestrator_author: T::AccountId) -> Weight {
235
6262
            let mut total_weight = T::DbWeight::get().reads(1);
236
6262
            if let Some(chains_to_reward) = ChainsToReward::<T>::get() {
237
6262
                total_weight.saturating_accrue(T::DbWeight::get().reads(1));
238
6262
                match T::StakingRewardsDistributor::distribute_rewards(
239
6262
                    orchestrator_author.clone(),
240
6262
                    T::Currency::withdraw(
241
6262
                        &T::PendingRewardsAccount::get(),
242
6262
                        chains_to_reward.rewards_per_chain,
243
6262
                        Precision::BestEffort,
244
6262
                        Preservation::Expendable,
245
6262
                        Fortitude::Force,
246
6262
                    )
247
6262
                    .unwrap_or(CreditOf::<T>::zero()),
248
6262
                ) {
249
6196
                    Ok(frame_support::dispatch::PostDispatchInfo { actual_weight, .. }) => {
250
6196
                        Self::deposit_event(Event::RewardedOrchestrator {
251
6196
                            account_id: orchestrator_author,
252
6196
                            balance: chains_to_reward.rewards_per_chain,
253
6196
                        });
254

            
255
6196
                        if let Some(weight) = actual_weight {
256
6186
                            total_weight.saturating_accrue(weight)
257
10
                        }
258
                    }
259
66
                    Err(e) => {
260
66
                        log::debug!("Fail to distribute rewards: {:?}", e)
261
                    }
262
                }
263
            } else {
264
                panic!("ChainsToReward not filled");
265
            }
266
6262
            total_weight
267
6262
        }
268

            
269
        pub fn container_chains_to_reward() -> Option<ChainsToRewardValue<T>> {
270
            ChainsToReward::<T>::get()
271
        }
272
    }
273
}
274

            
275
// This function should only be used to **reward** a container author.
276
// There will be no additional check other than checking if we have already
277
// rewarded this author for **in this tanssi block**
278
// Any additional check should be done in the calling function
279
impl<T: Config> AuthorNotingHook<T::AccountId> for Pallet<T> {
280
135
    fn on_container_authors_noted(info: &[AuthorNotingInfo<T::AccountId>]) -> Weight {
281
135
        let mut total_weight = T::DbWeight::get().reads_writes(1, 0);
282
        // We take chains to reward, to see what containers are left to reward
283
135
        if let Some(mut container_chains_to_reward) = ChainsToReward::<T>::get() {
284
282
            for info in info {
285
147
                let author = &info.author;
286
147
                let para_id = info.para_id;
287

            
288
                // If we find the index is because we still have not rewarded it
289
147
                if let Ok(index) = container_chains_to_reward.para_ids.binary_search(&para_id) {
290
                    // we distribute rewards to the author
291
146
                    match T::StakingRewardsDistributor::distribute_rewards(
292
146
                        author.clone(),
293
146
                        T::Currency::withdraw(
294
146
                            &T::PendingRewardsAccount::get(),
295
146
                            container_chains_to_reward.rewards_per_chain,
296
146
                            Precision::BestEffort,
297
146
                            Preservation::Expendable,
298
146
                            Fortitude::Force,
299
146
                        )
300
146
                        .unwrap_or(CreditOf::<T>::zero()),
301
146
                    ) {
302
146
                        Ok(frame_support::dispatch::PostDispatchInfo { actual_weight, .. }) => {
303
146
                            Self::deposit_event(Event::RewardedContainer {
304
146
                                account_id: author.clone(),
305
146
                                balance: container_chains_to_reward.rewards_per_chain,
306
146
                                para_id,
307
146
                            });
308
146
                            if let Some(weight) = actual_weight {
309
143
                                total_weight.saturating_accrue(weight)
310
3
                            }
311
                        }
312
                        Err(e) => {
313
                            log::warn!("Fail to distribute rewards: {:?}", e)
314
                        }
315
                    }
316
                    // we remove the para id from container-chains to reward
317
                    // this makes sure we dont reward it twice in the same block
318
146
                    container_chains_to_reward.para_ids.remove(index);
319
1
                }
320
            }
321

            
322
135
            total_weight.saturating_accrue(T::DbWeight::get().writes(1));
323
            // Keep track of chains to reward
324
135
            ChainsToReward::<T>::put(container_chains_to_reward);
325
        } else {
326
            // TODO: why would ChainsToReward ever be None?
327
            log::warn!("ChainsToReward is None");
328
        }
329

            
330
135
        total_weight
331
135
    }
332

            
333
    #[cfg(feature = "runtime-benchmarks")]
334
    fn prepare_worst_case_for_bench(_a: &T::AccountId, _b: BlockNumber, para_id: ParaId) {
335
        // arbitrary amount to perform rewarding
336
        // we mint twice as much to the rewards account to make it possible
337
        let reward_amount = 1_000_000_000u32;
338
        let mint = reward_amount.saturating_mul(2);
339

            
340
        T::Currency::resolve(
341
            &T::PendingRewardsAccount::get(),
342
            T::Currency::issue(BalanceOf::<T>::from(mint)),
343
        )
344
        .expect("to mint tokens");
345

            
346
        ChainsToReward::<T>::put(ChainsToRewardValue {
347
            para_ids: alloc::vec![para_id].try_into().expect("to be in bound"),
348
            rewards_per_chain: BalanceOf::<T>::from(reward_amount),
349
        });
350
    }
351
}