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

            
23
pub use pallet::*;
24

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
147
34362
            weight
148
34362
        }
149
    }
150

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

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

            
158
        type ContainerChains: GetCurrentContainerChains;
159

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
312
22436
        total_weight
313
22436
    }
314

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

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

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