astar_primitives/
dapp_staking.rs

1// This file is part of Astar.
2
3// Copyright (C) Stake Technologies Pte.Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later
5
6// Astar is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// Astar is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with Astar. If not, see <http://www.gnu.org/licenses/>.
18
19use super::{Balance, BlockNumber};
20
21use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
22
23use frame_support::pallet_prelude::{RuntimeDebug, Weight};
24use sp_arithmetic::ArithmeticError;
25use sp_core::{DecodeWithMemTracking, H160};
26use sp_runtime::traits::Zero;
27use sp_std::hash::Hash;
28
29/// Era number type
30pub type EraNumber = u32;
31/// Period number type
32pub type PeriodNumber = u32;
33/// Dapp Id type
34pub type DAppId = u16;
35/// Tier Id type
36pub type TierId = u8;
37/// Tier Rank type
38pub type Rank = u8;
39
40/// Configuration for cycles, periods, subperiods & eras.
41///
42/// * `cycle` - Time unit similar to 'year' in the real world. Consists of one or more periods. At the beginning of each cycle, inflation is recalculated.
43/// * `period` - Period consists of two distinct subperiods: `Voting` & `Build&Earn`. They are integral parts of dApp staking.
44///              Length is expressed in standard eras or just _eras_.
45/// * `era` - Era is the basic time unit in the dApp staking protocol. At the end of each era, reward pools for stakers & dApps are calculated.
46///           Era length is expressed in blocks.
47pub trait CycleConfiguration {
48    /// How many different periods are there in a cycle (a 'year').
49    ///
50    /// This value has to be at least 1.
51    fn periods_per_cycle() -> PeriodNumber;
52
53    /// For how many standard era lengths does the voting subperiod last.
54    ///
55    /// This value has to be at least 1.
56    fn eras_per_voting_subperiod() -> EraNumber;
57
58    /// How many standard eras are there in the build&earn subperiod.
59    ///
60    /// This value has to be at least 1.
61    fn eras_per_build_and_earn_subperiod() -> EraNumber;
62
63    /// How many blocks are there per standard era.
64    ///
65    /// This value has to be at least 1.
66    fn blocks_per_era() -> BlockNumber;
67
68    /// For how many standard era lengths does the period last.
69    fn period_in_era_lengths() -> EraNumber {
70        Self::eras_per_voting_subperiod().saturating_add(Self::eras_per_build_and_earn_subperiod())
71    }
72
73    /// For how many standard era lengths does the cycle (a 'year') last.
74    fn cycle_in_era_lengths() -> EraNumber {
75        Self::period_in_era_lengths().saturating_mul(Self::periods_per_cycle())
76    }
77
78    /// How many blocks are there per cycle (a 'year').
79    fn blocks_per_cycle() -> BlockNumber {
80        Self::blocks_per_era().saturating_mul(Self::cycle_in_era_lengths())
81    }
82
83    /// For how many standard era lengths do all the build&earn subperiods in a cycle last.
84    fn build_and_earn_eras_per_cycle() -> EraNumber {
85        Self::eras_per_build_and_earn_subperiod().saturating_mul(Self::periods_per_cycle())
86    }
87
88    /// How many distinct eras are there in a single period.
89    fn eras_per_period() -> EraNumber {
90        Self::eras_per_build_and_earn_subperiod().saturating_add(1)
91    }
92
93    /// How many distinct eras are there in a cycle.
94    fn eras_per_cycle() -> EraNumber {
95        Self::eras_per_period().saturating_mul(Self::periods_per_cycle())
96    }
97}
98
99/// Trait for observers (listeners) of various events related to dApp staking protocol.
100pub trait Observer {
101    /// Called in the block right before the next era starts.
102    ///
103    /// Returns the weight consumed by the call.
104    ///
105    /// # Arguments
106    /// * `next_era` - Era number of the next era.
107    fn block_before_new_era(_next_era: EraNumber) -> Weight {
108        Weight::zero()
109    }
110}
111
112impl Observer for () {}
113
114/// Interface for staking reward handler.
115///
116/// Provides reward pool values for stakers - normal & bonus rewards, as well as dApp reward pool.
117/// Also provides a safe function for paying out rewards.
118pub trait StakingRewardHandler<AccountId> {
119    /// Returns the staker reward pool & dApp reward pool for an era.
120    ///
121    /// The total staker reward pool is dynamic and depends on the total value staked.
122    fn staker_and_dapp_reward_pools(total_value_staked: Balance) -> (Balance, Balance);
123
124    /// Returns the bonus reward pool for a period.
125    fn bonus_reward_pool() -> Balance;
126
127    /// Attempts to pay out the rewards to the beneficiary.
128    fn payout_reward(beneficiary: &AccountId, reward: Balance) -> Result<(), ()>;
129}
130
131/// Trait defining the interface for dApp staking `smart contract types` handler.
132///
133/// It can be used to create a representation of the specified smart contract instance type.
134pub trait SmartContractHandle<AccountId> {
135    /// Create a new smart contract representation for the specified EVM address.
136    fn evm(address: H160) -> Self;
137    /// Create a new smart contract representation for the specified Wasm address.
138    fn wasm(address: AccountId) -> Self;
139}
140
141/// Multi-VM pointer to smart contract instance.
142#[derive(
143    PartialEq,
144    Eq,
145    Copy,
146    Clone,
147    Encode,
148    Decode,
149    DecodeWithMemTracking,
150    RuntimeDebug,
151    MaxEncodedLen,
152    Hash,
153    scale_info::TypeInfo,
154)]
155pub enum SmartContract<AccountId> {
156    /// EVM smart contract instance.
157    Evm(H160),
158    /// Wasm smart contract instance.
159    Wasm(AccountId),
160}
161
162impl<AccountId> SmartContractHandle<AccountId> for SmartContract<AccountId> {
163    fn evm(address: H160) -> Self {
164        Self::Evm(address)
165    }
166
167    fn wasm(address: AccountId) -> Self {
168        Self::Wasm(address)
169    }
170}
171
172/// Used to check whether an account is allowed to participate in dApp staking or not.
173pub trait AccountCheck<AccountId> {
174    /// `true` if the account is allowed to stake, `false` otherwise.
175    fn allowed_to_stake(account: &AccountId) -> bool;
176}
177
178impl<AccountId> AccountCheck<AccountId> for () {
179    fn allowed_to_stake(_account: &AccountId) -> bool {
180        true
181    }
182}
183
184/// Fixed number of tier slots used by dApp-staking recalculation.
185pub const FIXED_NUMBER_OF_TIER_SLOTS: u16 = 16;
186
187/// Standard tier slots arguments.
188/// Decided for Astar, during the Tokenomics 3.0 revamp.
189pub const FIXED_TIER_SLOTS_ARGS: (u64, u64) = (0, FIXED_NUMBER_OF_TIER_SLOTS as u64);
190
191/// RankedTier is wrapper around u8 to hold both tier and rank. u8 has 2 bytes (8bits) and they're using in this order `0xrank_tier`.
192/// First 4 bits are used to hold rank and second 4 bits are used to hold tier.
193/// i.e: 0xa1 will hold rank: 10 and tier: 1 (0xa1 & 0xf == 1; 0xa1 >> 4 == 10;)
194#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, MaxEncodedLen, scale_info::TypeInfo)]
195pub struct RankedTier(u8);
196
197impl RankedTier {
198    pub const MAX_RANK: u8 = 10;
199
200    /// Create new encoded RankedTier from tier and rank.
201    /// Returns Err(ArithmeticError::Overflow) if max value is not respected.
202    pub fn new(tier: TierId, rank: Rank) -> Result<Self, ArithmeticError> {
203        if rank > Self::MAX_RANK || tier > 0xf {
204            return Err(ArithmeticError::Overflow);
205        }
206        Ok(Self(rank << 4 | tier & 0x0f))
207    }
208
209    /// Create new encoded RankedTier from tier and rank with saturation.
210    pub fn new_saturated(tier: TierId, rank: Rank) -> Self {
211        Self(rank.min(Self::MAX_RANK) << 4 | tier.min(0xf) & 0x0f)
212    }
213
214    #[inline(always)]
215    pub fn tier(&self) -> TierId {
216        self.0 & 0x0f
217    }
218
219    #[inline(always)]
220    pub fn rank(&self) -> Rank {
221        (self.0 >> 4).min(Self::MAX_RANK)
222    }
223
224    #[inline(always)]
225    pub fn deconstruct(&self) -> (TierId, Rank) {
226        (self.tier(), self.rank())
227    }
228}
229
230impl core::fmt::Debug for RankedTier {
231    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
232        f.debug_struct("RankedTier")
233            .field("tier", &self.tier())
234            .field("rank", &self.rank())
235            .finish()
236    }
237}
238
239impl RankedTier {
240    /// Find rank based on lower/upper bounds and staked amount.
241    /// Delta between upper and lower bound is divided in 10 and will increase rank
242    /// by one for each threshold staked amount will reach.
243    /// i.e. find_rank(10, 20, 10) -> 0
244    /// i.e. find_rank(10, 20, 15) -> 5
245    /// i.e. find_rank(10, 20, 20) -> 10
246    pub fn find_rank(lower_bound: Balance, upper_bound: Balance, stake_amount: Balance) -> Rank {
247        if upper_bound.is_zero() {
248            return 0;
249        }
250        let rank_threshold = upper_bound
251            .saturating_sub(lower_bound)
252            .saturating_div(RankedTier::MAX_RANK.into());
253        if rank_threshold.is_zero() {
254            0
255        } else {
256            <Balance as TryInto<u8>>::try_into(
257                stake_amount
258                    .saturating_sub(lower_bound)
259                    .saturating_div(rank_threshold),
260            )
261            .unwrap_or_default()
262            .min(RankedTier::MAX_RANK)
263        }
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    #[test]
272    fn tier_and_rank() {
273        let t = RankedTier::new(0, 0).unwrap();
274        assert_eq!(t.deconstruct(), (0, 0));
275
276        let t = RankedTier::new(15, 10).unwrap();
277        assert_eq!(t.deconstruct(), (15, 10));
278
279        assert_eq!(RankedTier::new(16, 10), Err(ArithmeticError::Overflow));
280        assert_eq!(RankedTier::new(15, 11), Err(ArithmeticError::Overflow));
281
282        let t = RankedTier::new_saturated(0, 0);
283        assert_eq!(t.deconstruct(), (0, 0));
284
285        let t = RankedTier::new_saturated(1, 1);
286        assert_eq!(t.deconstruct(), (1, 1));
287
288        let t = RankedTier::new_saturated(3, 15);
289        assert_eq!(t.deconstruct(), (3, 10));
290
291        // max value for tier and rank
292        let t = RankedTier::new_saturated(16, 16);
293        assert_eq!(t.deconstruct(), (15, 10));
294    }
295
296    #[test]
297    fn find_rank() {
298        assert_eq!(RankedTier::find_rank(0, 0, 0), 0);
299        assert_eq!(RankedTier::find_rank(0, 100, 9), 0);
300        assert_eq!(RankedTier::find_rank(0, 100, 10), 1);
301        assert_eq!(RankedTier::find_rank(0, 100, 49), 4);
302        assert_eq!(RankedTier::find_rank(0, 100, 50), 5);
303        assert_eq!(RankedTier::find_rank(0, 100, 51), 5);
304        assert_eq!(RankedTier::find_rank(0, 100, 101), 10);
305
306        assert_eq!(RankedTier::find_rank(100, 100, 100), 0);
307        assert_eq!(RankedTier::find_rank(200, 100, 100), 0);
308    }
309}