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
    dp_core::ParaId,
36
    frame_support::{
37
        pallet_prelude::*,
38
        traits::{
39
            fungible::{Balanced, Credit, Inspect},
40
            tokens::{Fortitude, Precision, Preservation},
41
            Imbalance, OnUnbalanced,
42
        },
43
    },
44
    frame_system::pallet_prelude::*,
45
    sp_runtime::{
46
        traits::{Get, Saturating, Zero},
47
        Perbill,
48
    },
49
    tp_traits::{
50
        AuthorNotingHook, AuthorNotingInfo, DistributeRewards, GetCurrentContainerChains,
51
        MaybeSelfChainBlockAuthor,
52
    },
53
};
54

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

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

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

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

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

            
92
            // Get the number of chains at this block (tanssi + container chain blocks)
93
34864
            weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
94
34864
            let registered_para_ids = T::ContainerChains::current_container_chains();
95
34864

            
96
34864
            let mut number_of_chains: BalanceOf<T> = (registered_para_ids.len() as u32).into();
97
34864

            
98
34864
            // We only add 1 extra chain to number_of_chains if we are
99
34864
            // in a parachain context with an orchestrator configured.
100
34864
            if T::GetSelfChainBlockAuthor::get_block_author().is_some() {
101
27396
                number_of_chains.saturating_inc();
102
34864
            }
103

            
104
            // Only create new supply and rewards if number_of_chains is not zero.
105
34864
            if !number_of_chains.is_zero() {
106
                // Issue new supply
107
30554
                let new_supply =
108
30554
                    T::Currency::issue(T::InflationRate::get() * T::Currency::total_issuance());
109
30554

            
110
30554
                // Split staking reward portion
111
30554
                let total_rewards = T::RewardsPortion::get() * new_supply.peek();
112
30554
                let (rewards_credit, reminder_credit) = new_supply.split(total_rewards);
113
30554

            
114
30554
                let rewards_per_chain: BalanceOf<T> = rewards_credit
115
30554
                    .peek()
116
30554
                    .checked_div(&number_of_chains)
117
30554
                    .unwrap_or_else(|| {
118
                        log::error!("Rewards per chain is zero");
119
                        BalanceOf::<T>::zero()
120
30554
                    });
121
30554
                let (mut total_reminder, staking_rewards) = rewards_credit.split_merge(
122
30554
                    total_rewards % number_of_chains,
123
30554
                    (reminder_credit, CreditOf::<T>::zero()),
124
30554
                );
125

            
126
                // Deposit the new supply dedicated to rewards in the pending rewards account
127
                if let Err(undistributed_rewards) =
128
30554
                    T::Currency::resolve(&T::PendingRewardsAccount::get(), staking_rewards)
129
                {
130
                    total_reminder = total_reminder.merge(undistributed_rewards);
131
30554
                }
132

            
133
                // Keep track of chains to reward
134
30554
                ChainsToReward::<T>::put(ChainsToRewardValue {
135
30554
                    para_ids: registered_para_ids,
136
30554
                    rewards_per_chain,
137
30554
                });
138
30554

            
139
30554
                // Let the runtime handle the non-staking part
140
30554
                T::OnUnbalanced::on_unbalanced(not_distributed_rewards.merge(total_reminder));
141

            
142
                // We don't reward the orchestrator in solochain mode
143
30554
                if let Some(orchestrator_author) = T::GetSelfChainBlockAuthor::get_block_author() {
144
27396
                    weight.saturating_accrue(Self::reward_orchestrator_author(orchestrator_author));
145
30554
                }
146
4310
            }
147

            
148
34864
            weight
149
34864
        }
150
    }
151

            
152
    #[pallet::config]
153
    pub trait Config: frame_system::Config {
154
        /// Overarching event type.
155
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
156

            
157
        type Currency: Inspect<Self::AccountId> + Balanced<Self::AccountId>;
158

            
159
        type ContainerChains: GetCurrentContainerChains;
160

            
161
        /// Get block author for self chain
162
        type GetSelfChainBlockAuthor: MaybeSelfChainBlockAuthor<Self::AccountId>;
163

            
164
        /// Inflation rate per orchestrator block (proportion of the total issuance)
165
        #[pallet::constant]
166
        type InflationRate: Get<Perbill>;
167

            
168
        /// What to do with the new supply not dedicated to staking
169
        type OnUnbalanced: OnUnbalanced<CreditOf<Self>>;
170

            
171
        /// The account that will store rewards waiting to be paid out
172
        #[pallet::constant]
173
        type PendingRewardsAccount: Get<Self::AccountId>;
174

            
175
        /// Staking rewards distribution implementation
176
        type StakingRewardsDistributor: DistributeRewards<Self::AccountId, CreditOf<Self>>;
177

            
178
        /// Proportion of the new supply dedicated to staking
179
        #[pallet::constant]
180
        type RewardsPortion: Get<Perbill>;
181
    }
182

            
183
624
    #[pallet::event]
184
50162
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
185
    pub enum Event<T: Config> {
186
        /// Rewarding orchestrator author
187
        RewardedOrchestrator {
188
            account_id: T::AccountId,
189
            balance: BalanceOf<T>,
190
        },
191
        /// Rewarding container author
192
        RewardedContainer {
193
            account_id: T::AccountId,
194
            para_id: ParaId,
195
            balance: BalanceOf<T>,
196
        },
197
    }
198

            
199
    /// Container chains to reward per block
200
276960
    #[pallet::storage]
201
    pub(super) type ChainsToReward<T: Config> =
202
        StorageValue<_, ChainsToRewardValue<T>, OptionQuery>;
203

            
204
    #[derive(
205
1248
        Clone, Encode, Decode, PartialEq, sp_core::RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
206
    )]
207
    #[scale_info(skip_type_params(T))]
208
    pub struct ChainsToRewardValue<T: Config> {
209
        pub para_ids: BoundedVec<
210
            ParaId,
211
            <T::ContainerChains as GetCurrentContainerChains>::MaxContainerChains,
212
        >,
213
        pub rewards_per_chain: BalanceOf<T>,
214
    }
215

            
216
    impl<T: Config> Pallet<T> {
217
27396
        fn reward_orchestrator_author(orchestrator_author: T::AccountId) -> Weight {
218
27396
            let mut total_weight = T::DbWeight::get().reads(1);
219
27396
            if let Some(chains_to_reward) = ChainsToReward::<T>::get() {
220
27396
                total_weight.saturating_accrue(T::DbWeight::get().reads(1));
221
27396
                match T::StakingRewardsDistributor::distribute_rewards(
222
27396
                    orchestrator_author.clone(),
223
27396
                    T::Currency::withdraw(
224
27396
                        &T::PendingRewardsAccount::get(),
225
27396
                        chains_to_reward.rewards_per_chain,
226
27396
                        Precision::BestEffort,
227
27396
                        Preservation::Expendable,
228
27396
                        Fortitude::Force,
229
27396
                    )
230
27396
                    .unwrap_or(CreditOf::<T>::zero()),
231
27396
                ) {
232
27363
                    Ok(frame_support::dispatch::PostDispatchInfo { actual_weight, .. }) => {
233
27363
                        Self::deposit_event(Event::RewardedOrchestrator {
234
27363
                            account_id: orchestrator_author,
235
27363
                            balance: chains_to_reward.rewards_per_chain,
236
27363
                        });
237

            
238
27363
                        if let Some(weight) = actual_weight {
239
27353
                            total_weight.saturating_accrue(weight)
240
10
                        }
241
                    }
242
33
                    Err(e) => {
243
33
                        log::debug!("Fail to distribute rewards: {:?}", e)
244
                    }
245
                }
246
            } else {
247
                panic!("ChainsToReward not filled");
248
            }
249
27396
            total_weight
250
27396
        }
251

            
252
        pub fn container_chains_to_reward() -> Option<ChainsToRewardValue<T>> {
253
            ChainsToReward::<T>::get()
254
        }
255
    }
256
}
257

            
258
// This function should only be used to **reward** a container author.
259
// There will be no additional check other than checking if we have already
260
// rewarded this author for **in this tanssi block**
261
// Any additional check should be done in the calling function
262
impl<T: Config> AuthorNotingHook<T::AccountId> for Pallet<T> {
263
22677
    fn on_container_authors_noted(info: &[AuthorNotingInfo<T::AccountId>]) -> Weight {
264
22677
        let mut total_weight = T::DbWeight::get().reads_writes(1, 0);
265
        // We take chains to reward, to see what containers are left to reward
266
22677
        if let Some(mut container_chains_to_reward) = ChainsToReward::<T>::get() {
267
45477
            for info in info {
268
22800
                let author = &info.author;
269
22800
                let para_id = info.para_id;
270

            
271
                // If we find the index is because we still have not rewarded it
272
22800
                if let Ok(index) = container_chains_to_reward.para_ids.binary_search(&para_id) {
273
                    // we distribute rewards to the author
274
22799
                    match T::StakingRewardsDistributor::distribute_rewards(
275
22799
                        author.clone(),
276
22799
                        T::Currency::withdraw(
277
22799
                            &T::PendingRewardsAccount::get(),
278
22799
                            container_chains_to_reward.rewards_per_chain,
279
22799
                            Precision::BestEffort,
280
22799
                            Preservation::Expendable,
281
22799
                            Fortitude::Force,
282
22799
                        )
283
22799
                        .unwrap_or(CreditOf::<T>::zero()),
284
22799
                    ) {
285
22799
                        Ok(frame_support::dispatch::PostDispatchInfo { actual_weight, .. }) => {
286
22799
                            Self::deposit_event(Event::RewardedContainer {
287
22799
                                account_id: author.clone(),
288
22799
                                balance: container_chains_to_reward.rewards_per_chain,
289
22799
                                para_id,
290
22799
                            });
291
22799
                            if let Some(weight) = actual_weight {
292
22796
                                total_weight.saturating_accrue(weight)
293
3
                            }
294
                        }
295
                        Err(e) => {
296
                            log::warn!("Fail to distribute rewards: {:?}", e)
297
                        }
298
                    }
299
                    // we remove the para id from container-chains to reward
300
                    // this makes sure we dont reward it twice in the same block
301
22799
                    container_chains_to_reward.para_ids.remove(index);
302
1
                }
303
            }
304

            
305
22677
            total_weight.saturating_accrue(T::DbWeight::get().writes(1));
306
22677
            // Keep track of chains to reward
307
22677
            ChainsToReward::<T>::put(container_chains_to_reward);
308
        } else {
309
            // TODO: why would ChainsToReward ever be None?
310
            log::warn!("ChainsToReward is None");
311
        }
312

            
313
22677
        total_weight
314
22677
    }
315

            
316
    #[cfg(feature = "runtime-benchmarks")]
317
    fn prepare_worst_case_for_bench(_a: &T::AccountId, _b: BlockNumber, para_id: ParaId) {
318
        // arbitrary amount to perform rewarding
319
        // we mint twice as much to the rewards account to make it possible
320
        let reward_amount = 1_000_000_000u32;
321
        let mint = reward_amount.saturating_mul(2);
322

            
323
        T::Currency::resolve(
324
            &T::PendingRewardsAccount::get(),
325
            T::Currency::issue(BalanceOf::<T>::from(mint)),
326
        )
327
        .expect("to mint tokens");
328

            
329
        ChainsToReward::<T>::put(ChainsToRewardValue {
330
            para_ids: alloc::vec![para_id].try_into().expect("to be in bound"),
331
            rewards_per_chain: BalanceOf::<T>::from(reward_amount),
332
        });
333
    }
334
}