implement block emissions

This commit is contained in:
akildemir
2024-04-09 11:14:07 +03:00
parent 8be0538eba
commit 1cd9868433
15 changed files with 545 additions and 6 deletions

25
Cargo.lock generated
View File

@@ -7487,6 +7487,30 @@ dependencies = [
"chrono", "chrono",
] ]
[[package]]
name = "serai-emissions-pallet"
version = "0.1.0"
dependencies = [
"frame-support",
"frame-system",
"pallet-babe",
"parity-scale-codec",
"scale-info",
"serai-coins-pallet",
"serai-dex-pallet",
"serai-emissions-primitives",
"serai-primitives",
"serai-validator-sets-pallet",
"serai-validator-sets-primitives",
"sp-runtime",
"sp-session",
"sp-std",
]
[[package]]
name = "serai-emissions-primitives"
version = "0.1.0"
[[package]] [[package]]
name = "serai-env" name = "serai-env"
version = "0.1.0" version = "0.1.0"
@@ -7809,6 +7833,7 @@ dependencies = [
"scale-info", "scale-info",
"serai-coins-pallet", "serai-coins-pallet",
"serai-dex-pallet", "serai-dex-pallet",
"serai-emissions-pallet",
"serai-genesis-liquidity-pallet", "serai-genesis-liquidity-pallet",
"serai-in-instructions-pallet", "serai-in-instructions-pallet",
"serai-primitives", "serai-primitives",

View File

@@ -0,0 +1,14 @@
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Call {
// This call is just a place holder so that abi works as expected.
empty_call,
}
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Event {
empty_event,
}

View File

@@ -12,6 +12,7 @@ pub mod in_instructions;
pub mod signals; pub mod signals;
pub mod genesis_liquidity; pub mod genesis_liquidity;
pub mod emissions;
pub mod babe; pub mod babe;
pub mod grandpa; pub mod grandpa;
@@ -26,8 +27,9 @@ pub enum Call {
Coins(coins::Call), Coins(coins::Call),
LiquidityTokens(coins::Call), LiquidityTokens(coins::Call),
Dex(dex::Call), Dex(dex::Call),
GenesisLiquidity(genesis_liquidity::Call),
ValidatorSets(validator_sets::Call), ValidatorSets(validator_sets::Call),
GenesisLiquidity(genesis_liquidity::Call),
Emissions(emissions::Call),
InInstructions(in_instructions::Call), InInstructions(in_instructions::Call),
Signals(signals::Call), Signals(signals::Call),
Babe(babe::Call), Babe(babe::Call),
@@ -48,8 +50,9 @@ pub enum Event {
Coins(coins::Event), Coins(coins::Event),
LiquidityTokens(coins::Event), LiquidityTokens(coins::Event),
Dex(dex::Event), Dex(dex::Event),
GenesisLiquidity(genesis_liquidity::Event),
ValidatorSets(validator_sets::Event), ValidatorSets(validator_sets::Event),
GenesisLiquidity(genesis_liquidity::Event),
Emissions(emissions::Event),
InInstructions(in_instructions::Event), InInstructions(in_instructions::Event),
Signals(signals::Event), Signals(signals::Event),
Babe, Babe,

View File

@@ -194,6 +194,11 @@ pub mod pallet {
#[pallet::getter(fn security_oracle_value)] #[pallet::getter(fn security_oracle_value)]
pub type SecurityOracleValue<T: Config> = StorageMap<_, Identity, Coin, Amount, OptionQuery>; pub type SecurityOracleValue<T: Config> = StorageMap<_, Identity, Coin, Amount, OptionQuery>;
/// Current length of the `SpotPrices` map.
#[pallet::storage]
#[pallet::getter(fn swap_volume)]
pub type SwapVolume<T: Config> = StorageMap<_, Identity, Coin, u64, OptionQuery>;
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
fn restore_median( fn restore_median(
coin: Coin, coin: Coin,
@@ -890,6 +895,20 @@ pub mod pallet {
} }
i += 1; i += 1;
} }
// update the volume, SRI amount is always at index 1 if the path len is 2 or 3.
// path len 1 is not allowed and 3 is already the maximum.
let swap_volume = amounts.get(1).ok_or(Error::<T>::CorrespondenceError)?;
let existing = SwapVolume::<T>::get(coin1).unwrap_or(0);
let new_volume = existing.saturating_add(*swap_volume);
SwapVolume::<T>::set(coin1, Some(new_volume));
// if we did 2 pools, update the volume for second coin as well.
if u32::try_from(path.len()).unwrap() == T::MaxSwapPathLength::get() {
let existing = SwapVolume::<T>::get(path.last().unwrap()).unwrap_or(0);
let new_volume = existing.saturating_add(*swap_volume);
SwapVolume::<T>::set(path.last().unwrap(), Some(new_volume));
}
Self::deposit_event(Event::SwapExecuted { Self::deposit_event(Event::SwapExecuted {
who: sender, who: sender,
send_to, send_to,

View File

@@ -0,0 +1,65 @@
[package]
name = "serai-emissions-pallet"
version = "0.1.0"
description = "Emissions pallet for Serai"
license = "AGPL-3.0-only"
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/emissions/pallet"
authors = ["Akil Demir <aeg_asd@hotmail.com>"]
edition = "2021"
rust-version = "1.77"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.cargo-machete]
ignored = ["scale", "scale-info"]
[lints]
workspace = true
[dependencies]
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2", default-features = false, features = ["derive"] }
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
sp-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
pallet-babe = { git = "https://github.com/serai-dex/substrate", default-features = false }
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false }
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false }
serai-primitives = { path = "../../primitives", default-features = false }
serai-validator-sets-primitives = { path = "../../validator-sets/primitives", default-features = false }
emissions-primitives = { package = "serai-emissions-primitives", path = "../primitives", default-features = false }
[features]
std = [
"scale/std",
"scale-info/std",
"frame-system/std",
"frame-support/std",
"sp-std/std",
"sp-session/std",
"sp-runtime/std",
"pallet-babe/std",
"coins-pallet/std",
"validator-sets-pallet/std",
"dex-pallet/std",
"serai-primitives/std",
"emissions-primitives/std",
]
default = ["std"]

View File

@@ -0,0 +1,15 @@
AGPL-3.0-only license
Copyright (c) 2024 Luke Parker
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License Version 3 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@@ -0,0 +1,306 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[allow(clippy::cast_possible_truncation, clippy::no_effect_underscore_binding)]
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_system::pallet_prelude::*;
use frame_support::{pallet_prelude::*, sp_runtime::SaturatedConversion};
use sp_std::{vec, vec::Vec, collections::btree_map::BTreeMap};
use sp_session::ShouldEndSession;
use sp_runtime;
use coins_pallet::{Config as CoinsConfig, Pallet as Coins, AllowMint};
use dex_pallet::{Config as DexConfig, Pallet as Dex};
use validator_sets_pallet::{Pallet as ValidatorSets, Config as ValidatorSetsConfig};
use pallet_babe::{Pallet as Babe, Config as BabeConfig};
use serai_primitives::{NetworkId, NETWORKS, *};
use serai_validator_sets_primitives::MAX_KEY_SHARES_PER_SET;
use emissions_primitives::*;
#[pallet::config]
pub trait Config:
frame_system::Config<AccountId = PublicKey>
+ ValidatorSetsConfig
+ BabeConfig
+ CoinsConfig
+ DexConfig
{
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
#[pallet::genesis_config]
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub struct GenesisConfig<T: Config> {
/// Networks to spawn Serai with.
pub networks: Vec<NetworkId>,
/// List of participants to place in the initial validator sets.
pub participants: Vec<T::AccountId>,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig { networks: Default::default(), participants: Default::default() }
}
}
#[pallet::error]
pub enum Error<T> {
GenesisPeriodEnded,
AmountOverflowed,
NotEnoughLiquidity,
CanOnlyRemoveFullAmount,
}
#[pallet::event]
pub enum Event<T: Config> {}
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::storage]
#[pallet::getter(fn participants)]
pub(crate) type Participants<T: Config> = StorageMap<
_,
Identity,
NetworkId,
BoundedVec<(PublicKey, u64), ConstU32<{ MAX_KEY_SHARES_PER_SET }>>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn epoch_begin_block)]
pub(crate) type EpochBeginBlock<T: Config> = StorageMap<_, Identity, u64, u64, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn economic_security_reached)]
pub(crate) type EconomicSecurityReached<T: Config> =
StorageMap<_, Identity, NetworkId, BlockNumberFor<T>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn last_swap_volume)]
pub(crate) type LastSwapVolume<T: Config> = StorageMap<_, Identity, NetworkId, u64, OptionQuery>;
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
for id in self.networks.clone() {
let mut participants = vec![];
for p in self.participants.clone() {
participants.push((p, 0u64));
}
Participants::<T>::set(id, Some(participants.try_into().unwrap()));
}
EpochBeginBlock::<T>::set(0, 0);
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
/// Since we are on `on_finalize`, session should have already rotated.
/// We can distribute the rewards for the last set.
fn on_finalize(n: BlockNumberFor<T>) {
// we accept we reached economic security once we can mint smallest amount of a network's coin
for coin in COINS {
let existing = EconomicSecurityReached::<T>::get(coin.network());
if existing == 0u32.into() &&
<T as CoinsConfig>::AllowMint::is_allowed(&Balance { coin, amount: Amount(1) })
{
EconomicSecurityReached::<T>::set(coin.network(), n);
}
}
// emissions start only after genesis period and happens once per epoch
// so we don't do anything before that time.
if !(n >= BLOCKS_PER_MONTH.into() && T::ShouldEndSession::should_end_session(n)) {
return;
}
// figure out the amount of blocks in the last epoch
// TODO: we use epoch index here but should we use SessionIndex since this is how we decide
// whether time to distribute the rewards or not? Because apparently epochs != Sessions
// since we can skip some epochs if the chain is offline more than epoch duration??
let epoch = Babe::<T>::current_epoch().epoch_index - 1;
let block_count = n.saturated_into::<u64>() - Self::epoch_begin_block(epoch);
// get total reward for this epoch
let pre_ec_security = Self::pre_ec_security();
let mut distances = BTreeMap::new();
let mut total_distance: u64 = 0;
let reward_this_epoch = if Self::initial_period(n) {
// rewards are fixed for initial period
block_count * INITIAL_REWARD_PER_BLOCK
} else if pre_ec_security {
// calculate distance to economic security per network
let mut total_required: u64 = 0;
let mut total_current: u64 = 0;
for n in NETWORKS {
if n == NetworkId::Serai {
continue;
}
let required = ValidatorSets::<T>::required_stake_for_network(n);
let mut current = ValidatorSets::<T>::total_allocated_stake(n).unwrap_or(Amount(0)).0;
if current > required {
current = required;
}
distances.insert(n, required - current);
total_required = total_required.saturating_add(required);
total_current = total_current.saturating_add(current);
}
total_distance = total_required.saturating_sub(total_current);
// add serai network portion(20%)
let new_total_distance =
total_distance.saturating_mul(10) / (10 - SERAI_VALIDATORS_DESIRED_PERCENTAGE);
distances.insert(NetworkId::Serai, new_total_distance - total_distance);
total_distance = new_total_distance;
// rewards for pre-economic security is
// (STAKE_REQUIRED - CURRENT_STAKE) / blocks_until(SECURE_BY).
let block_reward = total_distance / Self::blocks_until(SECURE_BY);
block_count * block_reward
} else {
// post ec security
block_count * REWARD_PER_BLOCK
};
// get swap volumes
let mut volume_per_network: BTreeMap<NetworkId, u64> = BTreeMap::new();
for c in COINS {
// this should return 0 for SRI and so it shouldn't affect the total volume.
let current_volume = Dex::<T>::swap_volume(c).unwrap_or(0);
volume_per_network.insert(
c.network(),
(*volume_per_network.get(&c.network()).unwrap_or(&0)).saturating_add(current_volume),
);
}
// map current volumes to epoch volumes
let mut total_volume = 0u64;
for (n, vol) in &mut volume_per_network {
let last_volume = Self::last_swap_volume(n).unwrap_or(0);
let vol_this_epoch = vol.saturating_sub(last_volume);
// update the current volume
LastSwapVolume::<T>::set(n, Some(*vol));
total_volume = total_volume.saturating_add(vol_this_epoch);
*vol = vol_this_epoch;
}
// map epoch ec-security-distance/volume to rewards
let rewards_per_network = distances
.into_iter()
.map(|(n, distance)| {
let reward = if pre_ec_security {
// calculate how much each network gets based on distance to ec-security
reward_this_epoch.saturating_mul(distance) / total_distance
} else {
// 20% of the reward goes to the Serai network and rest is distributed among others
// based on swap-volume.
if n == NetworkId::Serai {
reward_this_epoch / 5
} else {
let reward = reward_this_epoch - (reward_this_epoch / 5);
reward.saturating_mul(*volume_per_network.get(&n).unwrap_or(&0)) / total_volume
}
};
(n, reward)
})
.collect::<BTreeMap<NetworkId, u64>>();
// distribute the rewards within the network
for (n, reward) in rewards_per_network {
// calculate pool vs validator share
let capacity = ValidatorSets::<T>::total_allocated_stake(n).unwrap_or(Amount(0)).0;
let required = ValidatorSets::<T>::required_stake_for_network(n);
let unused_capacity = capacity.saturating_sub(required);
let distribution = unused_capacity.saturating_mul(ACCURACY_MULTIPLIER) / capacity;
let total = DESIRED_DISTRIBUTION.saturating_add(distribution);
let validators_reward = DESIRED_DISTRIBUTION.saturating_mul(reward) / total;
let pool_reward = total - validators_reward;
// distribute validators rewards
Self::distribute_to_validators(n, validators_reward);
// send the rest to the pool
let coin_count = u64::try_from(n.coins().len()).unwrap();
for c in n.coins() {
// TODO: we just print a warning here instead of unwrap?
// assumes reward is equally distributed between network coins.
Coins::<T>::mint(
Dex::<T>::get_pool_account(*c),
Balance { coin: Coin::Serai, amount: Amount(pool_reward / coin_count) },
)
.unwrap();
}
}
// set the begin block and participants
EpochBeginBlock::<T>::set(epoch, n.saturated_into::<u64>());
for n in NETWORKS {
// TODO: `participants_for_latest_decided_set` returns keys with key shares but we
// store keys with actual stake amounts. Pr https://github.com/serai-dex/serai/pull/518
// supposed to change that and so this pr relies and that pr.
Participants::<T>::set(n, ValidatorSets::<T>::participants_for_latest_decided_set(n));
}
}
}
impl<T: Config> Pallet<T> {
fn blocks_until(block: u64) -> u64 {
let current = <frame_system::Pallet<T>>::block_number().saturated_into::<u64>();
block.saturating_sub(current)
}
fn initial_period(n: BlockNumberFor<T>) -> bool {
n >= BLOCKS_PER_MONTH.into() && n < (3 * BLOCKS_PER_MONTH).into()
}
/// Returns true if any of the external networks haven't reached economic security yet.
fn pre_ec_security() -> bool {
for n in NETWORKS {
if n == NetworkId::Serai {
continue;
}
if Self::economic_security_reached(n) == 0u32.into() {
return true;
}
}
false
}
fn distribute_to_validators(n: NetworkId, reward: u64) {
// distribute among network's set based on
// -> (key shares * stake per share) + ((stake % stake per share) / 2)
let stake_per_share = ValidatorSets::<T>::allocation_per_key_share(n).unwrap().0;
let mut scores = vec![];
let mut total_score = 0u64;
for (p, amount) in Self::participants(n).unwrap() {
let remainder = amount % stake_per_share;
let score = (amount - remainder) + (remainder / 2);
total_score = total_score.saturating_add(score);
scores.push((p, score));
}
// stake the rewards
for (p, score) in scores {
let p_reward = reward.saturating_mul(score) / total_score;
// TODO: print a warning here?
let _ = ValidatorSets::<T>::deposit_stake(n, p, Amount(p_reward));
}
}
}
}
pub use pallet::*;

View File

@@ -0,0 +1,22 @@
[package]
name = "serai-emissions-primitives"
version = "0.1.0"
description = "Serai emissions primitives"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/emissions/primitives"
authors = ["Akil Demir <aeg_asd@hotmail.com>"]
edition = "2021"
rust-version = "1.77"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
[dependencies]
[features]
std = []
default = ["std"]

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Luke Parker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,24 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
/// Amount of blocks in 30 days for 6s per block.
pub const BLOCKS_PER_MONTH: u32 = 10 * 60 * 24 * 30;
/// INITIAL_REWARD = 100,000 SRI / BLOCKS_PER_DAY for 60 days
pub const INITIAL_REWARD_PER_BLOCK: u64 = 100_000 * 10u64.pow(8) / ((BLOCKS_PER_MONTH as u64) / 30);
/// REWARD = 20M SRI / BLOCKS_PER_YEAR
pub const REWARD_PER_BLOCK: u64 = 20_000_000 * 10u64.pow(8) / ((BLOCKS_PER_MONTH as u64) * 12);
/// 20% of all stake desired to be for Serai network(2/10)
pub const SERAI_VALIDATORS_DESIRED_PERCENTAGE: u64 = 2;
/// Desired unused capacity ratio for a network assuming capacity is 10,000.
pub const DESIRED_DISTRIBUTION: u64 = 1_000;
/// Percentage scale for the validator vs. pool reward distribution.
pub const ACCURACY_MULTIPLIER: u64 = 10_000;
/// The block to target for economic security
pub const SECURE_BY: u64 = (BLOCKS_PER_MONTH as u64) * 12;

View File

@@ -7,6 +7,7 @@ use sc_service::ChainType;
use serai_runtime::{ use serai_runtime::{
primitives::*, WASM_BINARY, BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig, SystemConfig, primitives::*, WASM_BINARY, BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig, SystemConfig,
CoinsConfig, DexConfig, ValidatorSetsConfig, SignalsConfig, BabeConfig, GrandpaConfig, CoinsConfig, DexConfig, ValidatorSetsConfig, SignalsConfig, BabeConfig, GrandpaConfig,
EmissionsConfig,
}; };
pub type ChainSpec = sc_service::GenericChainSpec<RuntimeGenesisConfig>; pub type ChainSpec = sc_service::GenericChainSpec<RuntimeGenesisConfig>;
@@ -59,6 +60,10 @@ fn testnet_genesis(
.collect(), .collect(),
participants: validators.clone(), participants: validators.clone(),
}, },
emissions: EmissionsConfig {
networks: serai_runtime::primitives::NETWORKS.to_vec(),
participants: validators.clone(),
},
signals: SignalsConfig::default(), signals: SignalsConfig::default(),
babe: BabeConfig { babe: BabeConfig {
authorities: validators.iter().map(|validator| ((*validator).into(), 1)).collect(), authorities: validators.iter().map(|validator| ((*validator).into(), 1)).collect(),

View File

@@ -15,7 +15,9 @@ use sp_core::{ConstU32, bounded::BoundedVec};
use crate::{borsh_serialize_bounded_vec, borsh_deserialize_bounded_vec}; use crate::{borsh_serialize_bounded_vec, borsh_deserialize_bounded_vec};
/// The type used to identify networks. /// The type used to identify networks.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[derive(
Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, PartialOrd, Ord, MaxEncodedLen, TypeInfo,
)]
#[cfg_attr(feature = "std", derive(Zeroize))] #[cfg_attr(feature = "std", derive(Zeroize))]
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))] #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

View File

@@ -60,6 +60,7 @@ dex-pallet = { package = "serai-dex-pallet", path = "../dex/pallet", default-fea
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets/pallet", default-features = false } validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets/pallet", default-features = false }
genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../genesis-liquidity/pallet", default-features = false } genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../genesis-liquidity/pallet", default-features = false }
emissions-pallet = { package = "serai-emissions-pallet", path = "../emissions/pallet", default-features = false }
in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions/pallet", default-features = false } in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions/pallet", default-features = false }
@@ -114,6 +115,7 @@ std = [
"validator-sets-pallet/std", "validator-sets-pallet/std",
"genesis-liquidity-pallet/std", "genesis-liquidity-pallet/std",
"emissions-pallet/std",
"in-instructions-pallet/std", "in-instructions-pallet/std",

View File

@@ -33,6 +33,7 @@ pub use pallet_babe as babe;
pub use pallet_grandpa as grandpa; pub use pallet_grandpa as grandpa;
pub use genesis_liquidity_pallet as genesis_liquidity; pub use genesis_liquidity_pallet as genesis_liquidity;
pub use emissions_pallet as emissions;
// Actually used by the runtime // Actually used by the runtime
use sp_core::OpaqueMetadata; use sp_core::OpaqueMetadata;
@@ -294,6 +295,10 @@ impl genesis_liquidity::Config for Runtime {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
} }
impl emissions::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
}
// for publishing equivocation evidences. // for publishing equivocation evidences.
impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
where where
@@ -368,9 +373,10 @@ construct_runtime!(
Coins: coins, Coins: coins,
LiquidityTokens: coins::<Instance1>::{Pallet, Call, Storage, Event<T>}, LiquidityTokens: coins::<Instance1>::{Pallet, Call, Storage, Event<T>},
Dex: dex, Dex: dex,
GenesisLiquidity: genesis_liquidity,
ValidatorSets: validator_sets, ValidatorSets: validator_sets,
GenesisLiquidity: genesis_liquidity,
Emissions: emissions,
InInstructions: in_instructions, InInstructions: in_instructions,

View File

@@ -489,11 +489,12 @@ pub mod pallet {
network: NetworkId, network: NetworkId,
account: T::AccountId, account: T::AccountId,
amount: Amount, amount: Amount,
block_reward: bool,
) -> DispatchResult { ) -> DispatchResult {
let old_allocation = Self::allocation((network, account)).unwrap_or(Amount(0)).0; let old_allocation = Self::allocation((network, account)).unwrap_or(Amount(0)).0;
let new_allocation = old_allocation + amount.0; let new_allocation = old_allocation + amount.0;
let allocation_per_key_share = Self::allocation_per_key_share(network).unwrap().0; let allocation_per_key_share = Self::allocation_per_key_share(network).unwrap().0;
if new_allocation < allocation_per_key_share { if new_allocation < allocation_per_key_share && !block_reward {
Err(Error::<T>::InsufficientAllocation)?; Err(Error::<T>::InsufficientAllocation)?;
} }
@@ -796,6 +797,15 @@ pub mod pallet {
total_required total_required
} }
pub fn deposit_stake(
network: NetworkId,
account: T::AccountId,
amount: Amount,
) -> DispatchResult {
// TODO: make the increase_allocation public instead?
Self::increase_allocation(network, account, amount, true)
}
fn can_slash_serai_validator(validator: Public) -> bool { fn can_slash_serai_validator(validator: Public) -> bool {
// Checks if they're active or actively deallocating (letting us still slash them) // Checks if they're active or actively deallocating (letting us still slash them)
// We could check if they're upcoming/still allocating, yet that'd mean the equivocation is // We could check if they're upcoming/still allocating, yet that'd mean the equivocation is
@@ -936,7 +946,7 @@ pub mod pallet {
Self::account(), Self::account(),
Balance { coin: Coin::Serai, amount }, Balance { coin: Coin::Serai, amount },
)?; )?;
Self::increase_allocation(network, validator, amount) Self::increase_allocation(network, validator, amount, false)
} }
#[pallet::call_index(3)] #[pallet::call_index(3)]