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
//! This pallet keep tracks of the validators reward points.
18
//! Storage will be cleared after a period of time.
19

            
20
#![cfg_attr(not(feature = "std"), no_std)]
21

            
22
#[cfg(test)]
23
mod mock;
24

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

            
28
#[cfg(feature = "runtime-benchmarks")]
29
mod benchmarking;
30

            
31
pub mod weights;
32

            
33
pub use pallet::*;
34

            
35
use {
36
    frame_support::traits::{
37
        fungible::{self, Mutate},
38
        Defensive, Get, ValidatorSet,
39
    },
40
    parity_scale_codec::Encode,
41
    polkadot_primitives::ValidatorIndex,
42
    runtime_parachains::session_info,
43
    snowbridge_core::{ChannelId, TokenId},
44
    snowbridge_outbound_queue_merkle_tree::{merkle_proof, merkle_root, verify_proof, MerkleProof},
45
    sp_core::H256,
46
    sp_runtime::traits::{Hash, MaybeEquivalence, Zero},
47
    sp_staking::SessionIndex,
48
    sp_std::{collections::btree_set::BTreeSet, vec::Vec},
49
    tp_bridge::{Command, DeliverMessage, Message, TicketInfo, ValidateMessage},
50
    tp_traits::ExternalIndexProvider,
51
    xcm::prelude::*,
52
};
53

            
54
/// Utils needed to generate/verify merkle roots/proofs inside this pallet.
55
#[derive(Debug, PartialEq, Eq, Clone)]
56
pub struct EraRewardsUtils {
57
    pub rewards_merkle_root: H256,
58
    pub leaves: Vec<H256>,
59
    pub leaf_index: Option<u64>,
60
    pub total_points: u128,
61
}
62

            
63
1
#[frame_support::pallet]
64
pub mod pallet {
65
    pub use crate::weights::WeightInfo;
66
    use {
67
        super::*, frame_support::pallet_prelude::*, sp_std::collections::btree_map::BTreeMap,
68
        tp_traits::EraIndexProvider,
69
    };
70

            
71
    /// The current storage version.
72
    const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
73

            
74
    pub type RewardPoints = u32;
75
    pub type EraIndex = u32;
76

            
77
    #[pallet::config]
78
    pub trait Config: frame_system::Config {
79
        /// Overarching event type.
80
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
81

            
82
        /// How to fetch the current era info.
83
        type EraIndexProvider: EraIndexProvider;
84

            
85
        /// For how many eras points are kept in storage.
86
        #[pallet::constant]
87
        type HistoryDepth: Get<EraIndex>;
88

            
89
        /// The amount of era points given by backing a candidate that is included.
90
        #[pallet::constant]
91
        type BackingPoints: Get<u32>;
92

            
93
        /// The amount of era points given by dispute voting on a candidate.
94
        #[pallet::constant]
95
        type DisputeStatementPoints: Get<u32>;
96

            
97
        /// Provider to know how may tokens were inflated (added) in a specific era.
98
        type EraInflationProvider: Get<u128>;
99

            
100
        /// Provider to retrieve the current external index indetifying the validators
101
        type ExternalIndexProvider: ExternalIndexProvider;
102

            
103
        type GetWhitelistedValidators: Get<Vec<Self::AccountId>>;
104

            
105
        /// Hashing tool used to generate/verify merkle roots and proofs.
106
        type Hashing: Hash<Output = H256>;
107

            
108
        /// Validate a message that will be sent to Ethereum.
109
        type ValidateMessage: ValidateMessage;
110

            
111
        /// Send a message to Ethereum. Needs to be validated first.
112
        type OutboundQueue: DeliverMessage<
113
            Ticket = <<Self as pallet::Config>::ValidateMessage as ValidateMessage>::Ticket,
114
        >;
115

            
116
        /// Currency the rewards are minted in
117
        type Currency: fungible::Inspect<Self::AccountId, Balance: From<u128>>
118
            + fungible::Mutate<Self::AccountId>;
119

            
120
        /// Ethereum Sovereign Account where rewards will be minted
121
        type RewardsEthereumSovereignAccount: Get<Self::AccountId>;
122

            
123
        /// Token Location from the external chain's point of view.
124
        type TokenLocationReanchored: Get<Location>;
125

            
126
        /// How to convert from a given Location to a specific TokenId.
127
        type TokenIdFromLocation: MaybeEquivalence<TokenId, Location>;
128

            
129
        /// The weight information of this pallet.
130
        type WeightInfo: WeightInfo;
131

            
132
        #[cfg(feature = "runtime-benchmarks")]
133
        type BenchmarkHelper: tp_bridge::TokenChannelSetterBenchmarkHelperTrait;
134
    }
135

            
136
4
    #[pallet::pallet]
137
    #[pallet::storage_version(STORAGE_VERSION)]
138
    pub struct Pallet<T>(_);
139

            
140
    #[pallet::event]
141
8
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
142
    pub enum Event<T: Config> {
143
7
        /// The rewards message was sent correctly.
144
        RewardsMessageSent {
145
            message_id: H256,
146
            rewards_command: Command,
147
        },
148
    }
149

            
150
    /// Keep tracks of distributed points per validator and total.
151
    #[derive(RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo)]
152
    pub struct EraRewardPoints<AccountId> {
153
        pub total: RewardPoints,
154
        pub individual: BTreeMap<AccountId, RewardPoints>,
155
    }
156

            
157
    impl<AccountId> Default for EraRewardPoints<AccountId> {
158
26
        fn default() -> Self {
159
26
            EraRewardPoints {
160
26
                total: Default::default(),
161
26
                individual: BTreeMap::new(),
162
26
            }
163
26
        }
164
    }
165

            
166
    /// Store reward points per era.
167
    /// Note: EraRewardPoints is actually bounded by the amount of validators.
168
294
    #[pallet::storage]
169
    #[pallet::unbounded]
170
    pub type RewardPointsForEra<T: Config> =
171
        StorageMap<_, Twox64Concat, EraIndex, EraRewardPoints<T::AccountId>, ValueQuery>;
172

            
173
    impl<T: Config> Pallet<T> {
174
        /// Reward validators. Does not check if the validators are valid, caller needs to make sure of that.
175
236
        pub fn reward_by_ids(points: impl IntoIterator<Item = (T::AccountId, RewardPoints)>) {
176
236
            let active_era = T::EraIndexProvider::active_era();
177
236

            
178
236
            RewardPointsForEra::<T>::mutate(active_era.index, |era_rewards| {
179
250
                for (validator, points) in points.into_iter() {
180
244
                    *era_rewards.individual.entry(validator).or_default() += points;
181
244
                    era_rewards.total += points;
182
244
                }
183
236
            })
184
236
        }
185

            
186
        // Helper function used to generate the following utils:
187
        //  - rewards_merkle_root: merkle root corresponding [(validatorId, rewardPoints)]
188
        //      for the era_index specified.
189
        //  - leaves: that were used to generate the previous merkle root.
190
        //  - leaf_index: index of the validatorId's leaf in the previous leaves array (if any).
191
        //  - total_points: number of total points of the era_index specified.
192
29
        pub fn generate_era_rewards_utils(
193
29
            era_index: EraIndex,
194
29
            maybe_account_id_check: Option<T::AccountId>,
195
29
        ) -> Option<EraRewardsUtils> {
196
29
            let era_rewards = RewardPointsForEra::<T>::get(&era_index);
197
29
            let total_points: u128 = era_rewards.total as u128;
198
29
            let mut leaves = Vec::with_capacity(era_rewards.individual.len());
199
29
            let mut leaf_index = None;
200

            
201
29
            if let Some(account) = &maybe_account_id_check {
202
12
                if !era_rewards.individual.contains_key(account) {
203
9
                    log::error!(
204
9
                        target: "ext_validators_rewards",
205
9
                        "AccountId {:?} not found for era {:?}!",
206
                        account,
207
                        era_index
208
                    );
209
9
                    return None;
210
3
                }
211
17
            }
212

            
213
34
            for (index, (account_id, reward_points)) in era_rewards.individual.iter().enumerate() {
214
34
                let encoded = (account_id, reward_points).encode();
215
34
                let hashed = <T as Config>::Hashing::hash(&encoded);
216
34
                leaves.push(hashed);
217

            
218
34
                if let Some(ref check_account_id) = maybe_account_id_check {
219
5
                    if account_id == check_account_id {
220
3
                        leaf_index = Some(index as u64);
221
3
                    }
222
29
                }
223
            }
224

            
225
20
            let rewards_merkle_root =
226
20
                merkle_root::<<T as Config>::Hashing, _>(leaves.iter().cloned());
227
20

            
228
20
            Some(EraRewardsUtils {
229
20
                rewards_merkle_root,
230
20
                leaves,
231
20
                leaf_index,
232
20
                total_points,
233
20
            })
234
29
        }
235

            
236
12
        pub fn generate_rewards_merkle_proof(
237
12
            account_id: T::AccountId,
238
12
            era_index: EraIndex,
239
12
        ) -> Option<MerkleProof> {
240
12
            let utils = Self::generate_era_rewards_utils(era_index, Some(account_id))?;
241
3
            utils.leaf_index.map(|index| {
242
3
                merkle_proof::<<T as Config>::Hashing, _>(utils.leaves.into_iter(), index)
243
3
            })
244
12
        }
245

            
246
3
        pub fn verify_rewards_merkle_proof(merkle_proof: MerkleProof) -> bool {
247
3
            verify_proof::<<T as Config>::Hashing, _, _>(
248
3
                &merkle_proof.root,
249
3
                merkle_proof.proof,
250
3
                merkle_proof.number_of_leaves,
251
3
                merkle_proof.leaf_index,
252
3
                merkle_proof.leaf,
253
3
            )
254
3
        }
255
    }
256

            
257
    impl<T: Config> tp_traits::OnEraStart for Pallet<T> {
258
246
        fn on_era_start(era_index: EraIndex, _session_start: u32, _external_idx: u64) {
259
246
            let Some(era_index_to_delete) = era_index.checked_sub(T::HistoryDepth::get()) else {
260
244
                return;
261
            };
262

            
263
2
            RewardPointsForEra::<T>::remove(era_index_to_delete);
264
246
        }
265
    }
266

            
267
    impl<T: Config> tp_traits::OnEraEnd for Pallet<T> {
268
86
        fn on_era_end(era_index: EraIndex) {
269
86
            // Will send a ReportRewards message to Ethereum unless:
270
86
            // - the reward token is misconfigured
271
86
            // - the tokens inflation is 0 (misconfigured inflation)
272
86
            // - the total points is 0 (no rewards to distribute)
273
86
            // - it fails to mint the tokens in the Ethereum Sovereign Account
274
86
            // - the generated message doesn't pass validation
275
86

            
276
86
            let token_location = T::TokenLocationReanchored::get();
277
86
            let token_id = T::TokenIdFromLocation::convert_back(&token_location);
278

            
279
86
            let Some(token_id) = token_id else {
280
74
                log::error!(target: "ext_validators_rewards", "no token id found for location {:?}", token_location);
281
74
                return;
282
            };
283

            
284
12
            let Some(utils) = Self::generate_era_rewards_utils(era_index, None) else {
285
                // Unreachable, this should never happen as we are sending
286
                // None as the second param in Self::generate_era_rewards_utils.
287
                log::error!(
288
                    target: "ext_validators_rewards",
289
                    "Outbound message not sent for era {:?}!",
290
                    era_index
291
                );
292
                return;
293
            };
294

            
295
12
            let tokens_inflated = T::EraInflationProvider::get();
296
12

            
297
12
            if tokens_inflated.is_zero() {
298
1
                log::error!(target: "ext_validators_rewards", "Not sending message because tokens_inflated is 0");
299
1
                return;
300
11
            }
301
11

            
302
11
            if utils.total_points.is_zero() {
303
3
                log::error!(target: "ext_validators_rewards", "Not sending message because total_points is 0");
304
3
                return;
305
8
            }
306
8

            
307
8
            let ethereum_sovereign_account = T::RewardsEthereumSovereignAccount::get();
308
            if let Err(err) =
309
8
                T::Currency::mint_into(&ethereum_sovereign_account, tokens_inflated.into())
310
            {
311
                log::error!(target: "ext_validators_rewards", "Failed to mint inflation into Ethereum Soverein Account: {err:?}");
312
                log::error!(target: "ext_validators_rewards", "Not sending message since there are no rewards to distribute");
313
                return;
314
8
            }
315
8

            
316
8
            let command = Command::ReportRewards {
317
8
                external_idx: T::ExternalIndexProvider::get_external_index(),
318
8
                era_index,
319
8
                total_points: utils.total_points,
320
8
                tokens_inflated,
321
8
                rewards_merkle_root: utils.rewards_merkle_root,
322
8
                token_id,
323
8
            };
324
8

            
325
8
            let channel_id: ChannelId = snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL;
326
8

            
327
8
            let outbound_message = Message {
328
8
                id: None,
329
8
                channel_id,
330
8
                command: command.clone(),
331
8
            };
332
8

            
333
8
            // Validate and deliver the message
334
8
            match T::ValidateMessage::validate(&outbound_message) {
335
8
                Ok((ticket, _fee)) => {
336
8
                    let message_id = ticket.message_id();
337
8
                    if let Err(err) = T::OutboundQueue::deliver(ticket) {
338
                        log::error!(target: "ext_validators_rewards", "OutboundQueue delivery of message failed. {err:?}");
339
8
                    } else {
340
8
                        Self::deposit_event(Event::RewardsMessageSent {
341
8
                            message_id,
342
8
                            rewards_command: command,
343
8
                        });
344
8
                    }
345
                }
346
                Err(err) => {
347
                    log::error!(target: "ext_validators_rewards", "OutboundQueue validation of message failed. {err:?}");
348
                }
349
            }
350

            
351
8
            frame_system::Pallet::<T>::register_extra_weight_unchecked(
352
8
                T::WeightInfo::on_era_end(),
353
8
                DispatchClass::Mandatory,
354
8
            );
355
86
        }
356
    }
357
}
358

            
359
/// Rewards validators for participating in parachains with era points in pallet-staking.
360
pub struct RewardValidatorsWithEraPoints<C>(core::marker::PhantomData<C>);
361

            
362
impl<C> RewardValidatorsWithEraPoints<C>
363
where
364
    C: pallet::Config + session_info::Config,
365
    C::ValidatorSet: ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
366
{
367
    /// Reward validators in session with points, but only if they are in the active set.
368
9
    fn reward_only_active(
369
9
        session_index: SessionIndex,
370
9
        indices: impl IntoIterator<Item = ValidatorIndex>,
371
9
        points: u32,
372
9
    ) {
373
9
        let validators = session_info::AccountKeys::<C>::get(&session_index);
374
9
        let validators = match validators
375
9
            .defensive_proof("account_keys are present for dispute_period sessions")
376
        {
377
9
            Some(validators) => validators,
378
            None => return,
379
        };
380
        // limit rewards to the active validator set
381
9
        let mut active_set: BTreeSet<_> = C::ValidatorSet::validators().into_iter().collect();
382
9

            
383
9
        // Remove whitelisted validators, we don't want to reward them
384
9
        let whitelisted_validators = C::GetWhitelistedValidators::get();
385
21
        for validator in whitelisted_validators {
386
12
            active_set.remove(&validator);
387
12
        }
388

            
389
9
        let rewards = indices
390
9
            .into_iter()
391
9
            .filter_map(|i| validators.get(i.0 as usize).cloned())
392
9
            .filter(|v| active_set.contains(v))
393
9
            .map(|v| (v, points));
394
9

            
395
9
        pallet::Pallet::<C>::reward_by_ids(rewards);
396
9
    }
397
}
398

            
399
impl<C> runtime_parachains::inclusion::RewardValidators for RewardValidatorsWithEraPoints<C>
400
where
401
    C: pallet::Config + runtime_parachains::shared::Config + session_info::Config,
402
    C::ValidatorSet: ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
403
{
404
9
    fn reward_backing(indices: impl IntoIterator<Item = ValidatorIndex>) {
405
9
        let session_index = runtime_parachains::shared::CurrentSessionIndex::<C>::get();
406
9
        Self::reward_only_active(session_index, indices, C::BackingPoints::get());
407
9
    }
408

            
409
    fn reward_bitfields(_validators: impl IntoIterator<Item = ValidatorIndex>) {}
410
}
411

            
412
impl<C> runtime_parachains::disputes::RewardValidators for RewardValidatorsWithEraPoints<C>
413
where
414
    C: pallet::Config + session_info::Config,
415
    C::ValidatorSet: ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
416
{
417
    fn reward_dispute_statement(
418
        session: SessionIndex,
419
        validators: impl IntoIterator<Item = ValidatorIndex>,
420
    ) {
421
        Self::reward_only_active(session, validators, C::DisputeStatementPoints::get());
422
    }
423
}