mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Remove the staking pallet for validator-sets alone
The staking pallet is an indirection which offered no practical benefit yet increased the overhead of every call.
This commit is contained in:
18
Cargo.lock
generated
18
Cargo.lock
generated
@@ -8567,7 +8567,6 @@ dependencies = [
|
|||||||
"serai-in-instructions-pallet",
|
"serai-in-instructions-pallet",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-signals-pallet",
|
"serai-signals-pallet",
|
||||||
"serai-staking-pallet",
|
|
||||||
"serai-validator-sets-pallet",
|
"serai-validator-sets-pallet",
|
||||||
"sp-api",
|
"sp-api",
|
||||||
"sp-authority-discovery",
|
"sp-authority-discovery",
|
||||||
@@ -8599,22 +8598,6 @@ dependencies = [
|
|||||||
"sp-io",
|
"sp-io",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serai-staking-pallet"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"frame-support",
|
|
||||||
"frame-system",
|
|
||||||
"pallet-session",
|
|
||||||
"parity-scale-codec",
|
|
||||||
"scale-info",
|
|
||||||
"serai-coins-pallet",
|
|
||||||
"serai-primitives",
|
|
||||||
"serai-validator-sets-pallet",
|
|
||||||
"sp-runtime",
|
|
||||||
"sp-std",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-validator-sets-pallet"
|
name = "serai-validator-sets-pallet"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -8624,6 +8607,7 @@ dependencies = [
|
|||||||
"pallet-session",
|
"pallet-session",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
|
"serai-coins-pallet",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-validator-sets-primitives",
|
"serai-validator-sets-primitives",
|
||||||
"sp-application-crypto",
|
"sp-application-crypto",
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ members = [
|
|||||||
"substrate/validator-sets/primitives",
|
"substrate/validator-sets/primitives",
|
||||||
"substrate/validator-sets/pallet",
|
"substrate/validator-sets/pallet",
|
||||||
|
|
||||||
"substrate/staking/pallet",
|
|
||||||
|
|
||||||
"substrate/signals/pallet",
|
"substrate/signals/pallet",
|
||||||
|
|
||||||
"substrate/runtime",
|
"substrate/runtime",
|
||||||
|
|||||||
@@ -60,8 +60,6 @@ exceptions = [
|
|||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-validator-sets-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-validator-sets-pallet" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-staking-pallet" },
|
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-signals-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-signals-pallet" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-runtime" },
|
{ allow = ["AGPL-3.0"], name = "serai-runtime" },
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", d
|
|||||||
coins-pallet = { package = "serai-coins-pallet", path = "../coins/pallet", 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 }
|
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets/pallet", default-features = false }
|
||||||
staking-pallet = { package = "serai-staking-pallet", path = "../staking/pallet", default-features = false }
|
|
||||||
pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-session = { git = "https://github.com/serai-dex/substrate", 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 }
|
||||||
@@ -103,7 +102,6 @@ std = [
|
|||||||
"coins-pallet/std",
|
"coins-pallet/std",
|
||||||
|
|
||||||
"validator-sets-pallet/std",
|
"validator-sets-pallet/std",
|
||||||
"staking-pallet/std",
|
|
||||||
"pallet-session/std",
|
"pallet-session/std",
|
||||||
|
|
||||||
"in-instructions-pallet/std",
|
"in-instructions-pallet/std",
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ pub use pallet_transaction_payment as transaction_payment;
|
|||||||
|
|
||||||
pub use coins_pallet as coins;
|
pub use coins_pallet as coins;
|
||||||
|
|
||||||
pub use staking_pallet as staking;
|
|
||||||
pub use validator_sets_pallet as validator_sets;
|
pub use validator_sets_pallet as validator_sets;
|
||||||
pub use pallet_session as session;
|
pub use pallet_session as session;
|
||||||
|
|
||||||
@@ -169,7 +168,6 @@ impl Contains<RuntimeCall> for CallFilter {
|
|||||||
// All of these pallets are our own, and all of their written calls are intended to be called
|
// All of these pallets are our own, and all of their written calls are intended to be called
|
||||||
RuntimeCall::Coins(call) => !matches!(call, coins::Call::__Ignore(_, _)),
|
RuntimeCall::Coins(call) => !matches!(call, coins::Call::__Ignore(_, _)),
|
||||||
RuntimeCall::ValidatorSets(call) => !matches!(call, validator_sets::Call::__Ignore(_, _)),
|
RuntimeCall::ValidatorSets(call) => !matches!(call, validator_sets::Call::__Ignore(_, _)),
|
||||||
RuntimeCall::Staking(call) => !matches!(call, staking::Call::__Ignore(_, _)),
|
|
||||||
RuntimeCall::InInstructions(call) => !matches!(call, in_instructions::Call::__Ignore(_, _)),
|
RuntimeCall::InInstructions(call) => !matches!(call, in_instructions::Call::__Ignore(_, _)),
|
||||||
RuntimeCall::Signals(call) => !matches!(call, signals::Call::__Ignore(_, _)),
|
RuntimeCall::Signals(call) => !matches!(call, signals::Call::__Ignore(_, _)),
|
||||||
|
|
||||||
@@ -248,9 +246,6 @@ impl coins::Config for Runtime {
|
|||||||
impl validator_sets::Config for Runtime {
|
impl validator_sets::Config for Runtime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
}
|
}
|
||||||
impl staking::Config for Runtime {
|
|
||||||
type RuntimeEvent = RuntimeEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct IdentityValidatorIdOf;
|
pub struct IdentityValidatorIdOf;
|
||||||
impl Convert<PublicKey, Option<PublicKey>> for IdentityValidatorIdOf {
|
impl Convert<PublicKey, Option<PublicKey>> for IdentityValidatorIdOf {
|
||||||
@@ -265,7 +260,7 @@ impl session::Config for Runtime {
|
|||||||
type ValidatorIdOf = IdentityValidatorIdOf;
|
type ValidatorIdOf = IdentityValidatorIdOf;
|
||||||
type ShouldEndSession = Babe;
|
type ShouldEndSession = Babe;
|
||||||
type NextSessionRotation = Babe;
|
type NextSessionRotation = Babe;
|
||||||
type SessionManager = Staking;
|
type SessionManager = ValidatorSets;
|
||||||
type SessionHandler = <SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
type SessionHandler = <SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||||
type Keys = SessionKeys;
|
type Keys = SessionKeys;
|
||||||
type WeightInfo = session::weights::SubstrateWeight<Runtime>;
|
type WeightInfo = session::weights::SubstrateWeight<Runtime>;
|
||||||
@@ -349,7 +344,6 @@ construct_runtime!(
|
|||||||
Coins: coins,
|
Coins: coins,
|
||||||
|
|
||||||
ValidatorSets: validator_sets,
|
ValidatorSets: validator_sets,
|
||||||
Staking: staking,
|
|
||||||
Session: session,
|
Session: session,
|
||||||
|
|
||||||
InInstructions: in_instructions,
|
InInstructions: in_instructions,
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "serai-staking-pallet"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Staking pallet for Serai"
|
|
||||||
license = "AGPL-3.0-only"
|
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/staking/pallet"
|
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
all-features = true
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
parity-scale-codec = { version = "3", default-features = false, features = ["derive"] }
|
|
||||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
|
||||||
|
|
||||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
|
||||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
|
||||||
|
|
||||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
|
||||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
|
||||||
|
|
||||||
serai-primitives = { path = "../../primitives", 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 }
|
|
||||||
|
|
||||||
pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
std = [
|
|
||||||
"frame-system/std",
|
|
||||||
"frame-support/std",
|
|
||||||
|
|
||||||
"sp-std/std",
|
|
||||||
"sp-runtime/std",
|
|
||||||
|
|
||||||
"serai-primitives/std",
|
|
||||||
|
|
||||||
"coins-pallet/std",
|
|
||||||
|
|
||||||
"validator-sets-pallet/std",
|
|
||||||
|
|
||||||
"pallet-session/std",
|
|
||||||
]
|
|
||||||
|
|
||||||
runtime-benchmarks = [
|
|
||||||
"frame-system/runtime-benchmarks",
|
|
||||||
"frame-support/runtime-benchmarks",
|
|
||||||
]
|
|
||||||
|
|
||||||
default = ["std"]
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
AGPL-3.0-only license
|
|
||||||
|
|
||||||
Copyright (c) 2022-2023 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/>.
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
|
||||||
|
|
||||||
#[frame_support::pallet]
|
|
||||||
pub mod pallet {
|
|
||||||
use sp_runtime::traits::TrailingZeroInput;
|
|
||||||
use sp_std::vec::Vec;
|
|
||||||
|
|
||||||
use frame_system::pallet_prelude::*;
|
|
||||||
use frame_support::pallet_prelude::*;
|
|
||||||
|
|
||||||
use serai_primitives::*;
|
|
||||||
|
|
||||||
use coins_pallet::{Config as CoinsConfig, Pallet as Coins};
|
|
||||||
|
|
||||||
use validator_sets_pallet::{
|
|
||||||
primitives::{Session, ValidatorSet},
|
|
||||||
Config as VsConfig, Pallet as VsPallet,
|
|
||||||
};
|
|
||||||
use pallet_session::{Config as SessionConfig, SessionManager};
|
|
||||||
|
|
||||||
#[pallet::error]
|
|
||||||
pub enum Error<T> {
|
|
||||||
StakeUnavilable,
|
|
||||||
NoDeallocation,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pallet::event]
|
|
||||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
|
||||||
pub enum Event<T: Config> {
|
|
||||||
Staked {
|
|
||||||
validator: T::AccountId,
|
|
||||||
amount: Amount,
|
|
||||||
},
|
|
||||||
Unstaked {
|
|
||||||
validator: T::AccountId,
|
|
||||||
amount: Amount,
|
|
||||||
},
|
|
||||||
ImmediateDeallocation {
|
|
||||||
validator: T::AccountId,
|
|
||||||
network: NetworkId,
|
|
||||||
amount: Amount,
|
|
||||||
},
|
|
||||||
DeallocationClaimed {
|
|
||||||
validator: T::AccountId,
|
|
||||||
network: NetworkId,
|
|
||||||
session: Session,
|
|
||||||
amount: Amount,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pallet::config]
|
|
||||||
pub trait Config:
|
|
||||||
frame_system::Config + CoinsConfig + VsConfig + SessionConfig<ValidatorId = PublicKey>
|
|
||||||
{
|
|
||||||
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pallet::pallet]
|
|
||||||
pub struct Pallet<T>(PhantomData<T>);
|
|
||||||
|
|
||||||
/// The amount of funds this account has staked.
|
|
||||||
#[pallet::storage]
|
|
||||||
#[pallet::getter(fn staked)]
|
|
||||||
pub type Staked<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>;
|
|
||||||
|
|
||||||
/// The amount of stake this account has allocated to validator sets.
|
|
||||||
#[pallet::storage]
|
|
||||||
#[pallet::getter(fn allocated)]
|
|
||||||
pub type Allocated<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>;
|
|
||||||
|
|
||||||
impl<T: Config> Pallet<T> {
|
|
||||||
fn account() -> T::AccountId {
|
|
||||||
// Substrate has a pattern of using simply using 8-bytes (as a PalletId) directly as an
|
|
||||||
// AccountId. This replicates its internals to remove the 8-byte limit
|
|
||||||
T::AccountId::decode(&mut TrailingZeroInput::new(b"staking")).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_stake(account: T::AccountId, amount: u64) {
|
|
||||||
Staked::<T>::mutate(account, |staked| *staked += amount);
|
|
||||||
Self::deposit_event(Event::Staked { validator: account, amount: Amount(amount) });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_stake(account: T::AccountId, amount: u64) -> Result<(), Error<T>> {
|
|
||||||
Staked::<T>::mutate(account, |staked| {
|
|
||||||
let available = *staked - Self::allocated(account);
|
|
||||||
if available < amount {
|
|
||||||
Err(Error::<T>::StakeUnavilable)?;
|
|
||||||
}
|
|
||||||
*staked -= amount;
|
|
||||||
Self::deposit_event(Event::Unstaked { validator: account, amount: Amount(amount) });
|
|
||||||
Ok::<_, Error<T>>(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allocate_internal(account: &T::AccountId, amount: u64) -> Result<(), Error<T>> {
|
|
||||||
Allocated::<T>::try_mutate(account, |allocated| {
|
|
||||||
let available = Self::staked(account) - *allocated;
|
|
||||||
if available < amount {
|
|
||||||
Err(Error::<T>::StakeUnavilable)?;
|
|
||||||
}
|
|
||||||
*allocated += amount;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deallocate_internal(account: &T::AccountId, amount: u64) -> Result<(), Error<T>> {
|
|
||||||
Allocated::<T>::try_mutate(account, |allocated| {
|
|
||||||
if *allocated < amount {
|
|
||||||
Err(Error::<T>::StakeUnavilable)?;
|
|
||||||
}
|
|
||||||
*allocated -= amount;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pallet::call]
|
|
||||||
impl<T: Config> Pallet<T> {
|
|
||||||
/// Stake funds from this account.
|
|
||||||
#[pallet::call_index(0)]
|
|
||||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
|
||||||
pub fn stake(origin: OriginFor<T>, #[pallet::compact] amount: u64) -> DispatchResult {
|
|
||||||
let signer = ensure_signed(origin)?;
|
|
||||||
let balance = Balance { coin: Coin::Serai, amount: Amount(amount) };
|
|
||||||
Coins::<T>::transfer_internal(signer, Self::account(), balance)?;
|
|
||||||
Self::add_stake(signer, amount);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unstake funds from this account. Only unallocated funds may be unstaked.
|
|
||||||
#[pallet::call_index(1)]
|
|
||||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
|
||||||
pub fn unstake(origin: OriginFor<T>, #[pallet::compact] amount: u64) -> DispatchResult {
|
|
||||||
let signer = ensure_signed(origin)?;
|
|
||||||
Self::remove_stake(signer, amount)?;
|
|
||||||
let balance = Balance { coin: Coin::Serai, amount: Amount(amount) };
|
|
||||||
Coins::<T>::transfer_internal(Self::account(), signer, balance)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocate `amount` to a given validator set.
|
|
||||||
#[pallet::call_index(2)]
|
|
||||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
|
||||||
pub fn allocate(
|
|
||||||
origin: OriginFor<T>,
|
|
||||||
network: NetworkId,
|
|
||||||
#[pallet::compact] amount: u64,
|
|
||||||
) -> DispatchResult {
|
|
||||||
let account = ensure_signed(origin)?;
|
|
||||||
|
|
||||||
// add to amount allocated
|
|
||||||
Self::allocate_internal(&account, amount)?;
|
|
||||||
// This does not emit an event as the validator-sets pallet will
|
|
||||||
|
|
||||||
// increase allocation for participant in validator set
|
|
||||||
VsPallet::<T>::increase_allocation(network, account, Amount(amount))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deallocate `amount` from a given validator set.
|
|
||||||
#[pallet::call_index(3)]
|
|
||||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
|
||||||
pub fn deallocate(
|
|
||||||
origin: OriginFor<T>,
|
|
||||||
network: NetworkId,
|
|
||||||
#[pallet::compact] amount: u64,
|
|
||||||
) -> DispatchResult {
|
|
||||||
let account = ensure_signed(origin)?;
|
|
||||||
|
|
||||||
// decrease allocation in validator set
|
|
||||||
let can_immediately_deallocate =
|
|
||||||
VsPallet::<T>::decrease_allocation(network, account, Amount(amount))?;
|
|
||||||
if can_immediately_deallocate {
|
|
||||||
Self::deallocate_internal(&account, amount)?;
|
|
||||||
Self::deposit_event(Event::ImmediateDeallocation {
|
|
||||||
validator: account,
|
|
||||||
network,
|
|
||||||
amount: Amount(amount),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pallet::call_index(4)]
|
|
||||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
|
||||||
pub fn claim_deallocation(
|
|
||||||
origin: OriginFor<T>,
|
|
||||||
network: NetworkId,
|
|
||||||
session: Session,
|
|
||||||
) -> DispatchResult {
|
|
||||||
let account = ensure_signed(origin)?;
|
|
||||||
let Some(amount) = VsPallet::<T>::take_deallocatable_amount(network, session, account) else {
|
|
||||||
Err(Error::<T>::NoDeallocation)?
|
|
||||||
};
|
|
||||||
Self::deallocate_internal(&account, amount.0)?;
|
|
||||||
Self::deposit_event(Event::DeallocationClaimed {
|
|
||||||
validator: account,
|
|
||||||
network,
|
|
||||||
session,
|
|
||||||
amount,
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call order is end_session(i - 1) -> start_session(i) -> new_session(i + 1)
|
|
||||||
// new_session(i + 1) is called immediately after start_session(i)
|
|
||||||
// then we wait until the session ends then get a call to end_session(i) and so on.
|
|
||||||
impl<T: Config> SessionManager<T::ValidatorId> for Pallet<T> {
|
|
||||||
fn new_session(_new_index: u32) -> Option<Vec<T::ValidatorId>> {
|
|
||||||
VsPallet::<T>::new_session();
|
|
||||||
// TODO: Where do we return their stake?
|
|
||||||
Some(VsPallet::<T>::select_validators(NetworkId::Serai))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_session_genesis(_: u32) -> Option<Vec<T::ValidatorId>> {
|
|
||||||
// TODO: Because we don't call new_session here, we don't emit NewSet { Serai, session: 1 }
|
|
||||||
Some(VsPallet::<T>::select_validators(NetworkId::Serai))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end_session(end_index: u32) {
|
|
||||||
VsPallet::<T>::retire_set(ValidatorSet {
|
|
||||||
network: NetworkId::Serai,
|
|
||||||
session: Session(end_index),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_session(_start_index: u32) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use pallet::*;
|
|
||||||
@@ -29,6 +29,8 @@ pallet-session = { git = "https://github.com/serai-dex/substrate", default-featu
|
|||||||
serai-primitives = { path = "../../primitives", default-features = false }
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../primitives", default-features = false }
|
validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../primitives", default-features = false }
|
||||||
|
|
||||||
|
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
"scale/std",
|
"scale/std",
|
||||||
@@ -47,6 +49,8 @@ std = [
|
|||||||
|
|
||||||
"serai-primitives/std",
|
"serai-primitives/std",
|
||||||
"validator-sets-primitives/std",
|
"validator-sets-primitives/std",
|
||||||
|
|
||||||
|
"coins-pallet/std",
|
||||||
]
|
]
|
||||||
|
|
||||||
runtime-benchmarks = [
|
runtime-benchmarks = [
|
||||||
|
|||||||
@@ -16,9 +16,12 @@ pub mod pallet {
|
|||||||
pub use validator_sets_primitives as primitives;
|
pub use validator_sets_primitives as primitives;
|
||||||
use primitives::*;
|
use primitives::*;
|
||||||
|
|
||||||
|
use coins_pallet::Pallet as Coins;
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
pub trait Config:
|
pub trait Config:
|
||||||
frame_system::Config<AccountId = Public>
|
frame_system::Config<AccountId = Public>
|
||||||
|
+ coins_pallet::Config
|
||||||
+ pallet_session::Config<ValidatorId = Public>
|
+ pallet_session::Config<ValidatorId = Public>
|
||||||
+ TypeInfo
|
+ TypeInfo
|
||||||
{
|
{
|
||||||
@@ -245,37 +248,6 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> Pallet<T> {
|
|
||||||
fn is_bft(network: NetworkId) -> bool {
|
|
||||||
let allocation_per_key_share = AllocationPerKeyShare::<T>::get(network).unwrap().0;
|
|
||||||
|
|
||||||
let mut validators_len = 0;
|
|
||||||
let mut top = None;
|
|
||||||
let mut key_shares = 0;
|
|
||||||
for (_, amount) in SortedAllocationsIter::<T>::new(network) {
|
|
||||||
validators_len += 1;
|
|
||||||
|
|
||||||
key_shares += amount.0 / allocation_per_key_share;
|
|
||||||
if top.is_none() {
|
|
||||||
top = Some(key_shares);
|
|
||||||
}
|
|
||||||
|
|
||||||
if key_shares > u64::from(MAX_KEY_SHARES_PER_SET) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(top) = top else { return false };
|
|
||||||
|
|
||||||
// key_shares may be over MAX_KEY_SHARES_PER_SET, which will cause an off-chain reduction of
|
|
||||||
// each validator's key shares until their sum is MAX_KEY_SHARES_PER_SET
|
|
||||||
// post_amortization_key_shares_for_top_validator yields what the top validator's key shares
|
|
||||||
// would be after such a reduction, letting us evaluate this correctly
|
|
||||||
let top = post_amortization_key_shares_for_top_validator(validators_len, top, key_shares);
|
|
||||||
(top * 3) < key_shares.min(MAX_KEY_SHARES_PER_SET.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pending deallocations, keyed by the Session they become unlocked on.
|
/// Pending deallocations, keyed by the Session they become unlocked on.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
type PendingDeallocations<T: Config> =
|
type PendingDeallocations<T: Config> =
|
||||||
@@ -312,6 +284,11 @@ pub mod pallet {
|
|||||||
amount: Amount,
|
amount: Amount,
|
||||||
delayed_until: Option<Session>,
|
delayed_until: Option<Session>,
|
||||||
},
|
},
|
||||||
|
DeallocationClaimed {
|
||||||
|
validator: T::AccountId,
|
||||||
|
network: NetworkId,
|
||||||
|
session: Session,
|
||||||
|
},
|
||||||
SetRetired {
|
SetRetired {
|
||||||
set: ValidatorSet,
|
set: ValidatorSet,
|
||||||
},
|
},
|
||||||
@@ -383,6 +360,8 @@ pub mod pallet {
|
|||||||
DeallocationWouldRemoveParticipant,
|
DeallocationWouldRemoveParticipant,
|
||||||
/// Deallocation would cause the validator set to no longer achieve fault tolerance.
|
/// Deallocation would cause the validator set to no longer achieve fault tolerance.
|
||||||
DeallocationWouldRemoveFaultTolerance,
|
DeallocationWouldRemoveFaultTolerance,
|
||||||
|
/// Deallocation to be claimed doesn't exist.
|
||||||
|
NonExistentDeallocation,
|
||||||
/// Validator Set already generated keys.
|
/// Validator Set already generated keys.
|
||||||
AlreadyGeneratedKeys,
|
AlreadyGeneratedKeys,
|
||||||
/// An invalid MuSig signature was provided.
|
/// An invalid MuSig signature was provided.
|
||||||
@@ -426,78 +405,41 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::call]
|
|
||||||
impl<T: Config> Pallet<T> {
|
impl<T: Config> Pallet<T> {
|
||||||
#[pallet::call_index(0)]
|
fn account() -> T::AccountId {
|
||||||
#[pallet::weight(0)] // TODO
|
system_address(b"validator-sets").into()
|
||||||
pub fn set_keys(
|
}
|
||||||
origin: OriginFor<T>,
|
|
||||||
network: NetworkId,
|
|
||||||
key_pair: KeyPair,
|
|
||||||
signature: Signature,
|
|
||||||
) -> DispatchResult {
|
|
||||||
ensure_none(origin)?;
|
|
||||||
|
|
||||||
// signature isn't checked as this is an unsigned transaction, and validate_unsigned
|
fn is_bft(network: NetworkId) -> bool {
|
||||||
// (called by pre_dispatch) checks it
|
let allocation_per_key_share = AllocationPerKeyShare::<T>::get(network).unwrap().0;
|
||||||
let _ = signature;
|
|
||||||
|
|
||||||
let session = Session(pallet_session::Pallet::<T>::current_index());
|
let mut validators_len = 0;
|
||||||
|
let mut top = None;
|
||||||
|
let mut key_shares = 0;
|
||||||
|
for (_, amount) in SortedAllocationsIter::<T>::new(network) {
|
||||||
|
validators_len += 1;
|
||||||
|
|
||||||
let set = ValidatorSet { session, network };
|
key_shares += amount.0 / allocation_per_key_share;
|
||||||
|
if top.is_none() {
|
||||||
|
top = Some(key_shares);
|
||||||
|
}
|
||||||
|
|
||||||
Keys::<T>::set(set, Some(key_pair.clone()));
|
if key_shares > u64::from(MAX_KEY_SHARES_PER_SET) {
|
||||||
Self::deposit_event(Event::KeyGen { set, key_pair });
|
break;
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::validate_unsigned]
|
let Some(top) = top else { return false };
|
||||||
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
|
||||||
type Call = Call<T>;
|
|
||||||
|
|
||||||
fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
// key_shares may be over MAX_KEY_SHARES_PER_SET, which will cause an off-chain reduction of
|
||||||
// Match to be exhaustive
|
// each validator's key shares until their sum is MAX_KEY_SHARES_PER_SET
|
||||||
let (network, key_pair, signature) = match call {
|
// post_amortization_key_shares_for_top_validator yields what the top validator's key shares
|
||||||
Call::set_keys { network, ref key_pair, ref signature } => (network, key_pair, signature),
|
// would be after such a reduction, letting us evaluate this correctly
|
||||||
Call::__Ignore(_, _) => unreachable!(),
|
let top = post_amortization_key_shares_for_top_validator(validators_len, top, key_shares);
|
||||||
};
|
(top * 3) < key_shares.min(MAX_KEY_SHARES_PER_SET.into())
|
||||||
|
|
||||||
let session = Session(pallet_session::Pallet::<T>::current_index());
|
|
||||||
|
|
||||||
let set = ValidatorSet { session, network: *network };
|
|
||||||
match Self::verify_signature(set, key_pair, signature) {
|
|
||||||
Err(Error::AlreadyGeneratedKeys) => Err(InvalidTransaction::Stale)?,
|
|
||||||
Err(Error::NonExistentValidatorSet) |
|
|
||||||
Err(Error::InsufficientAllocation) |
|
|
||||||
Err(Error::NotEnoughAllocated) |
|
|
||||||
Err(Error::AllocationWouldRemoveFaultTolerance) |
|
|
||||||
Err(Error::DeallocationWouldRemoveParticipant) |
|
|
||||||
Err(Error::DeallocationWouldRemoveFaultTolerance) |
|
|
||||||
Err(Error::NonExistentValidator) |
|
|
||||||
Err(Error::BadSignature) => Err(InvalidTransaction::BadProof)?,
|
|
||||||
Err(Error::__Ignore(_, _)) => unreachable!(),
|
|
||||||
Ok(()) => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidTransaction::with_tag_prefix("validator-sets")
|
fn increase_allocation(
|
||||||
.and_provides(set)
|
|
||||||
// Set a 10 block longevity, though this should be included in the next block
|
|
||||||
.longevity(10)
|
|
||||||
.propagate(true)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicitly provide a pre-dispatch which calls validate_unsigned
|
|
||||||
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
|
|
||||||
Self::validate_unsigned(TransactionSource::InBlock, call).map(|_| ()).map_err(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Config> Pallet<T> {
|
|
||||||
#[frame_support::transactional]
|
|
||||||
pub fn increase_allocation(
|
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
account: T::AccountId,
|
account: T::AccountId,
|
||||||
amount: Amount,
|
amount: Amount,
|
||||||
@@ -548,8 +490,7 @@ pub mod pallet {
|
|||||||
/// doesn't become used (preventing deallocation).
|
/// doesn't become used (preventing deallocation).
|
||||||
///
|
///
|
||||||
/// Returns if the amount is immediately eligible for deallocation.
|
/// Returns if the amount is immediately eligible for deallocation.
|
||||||
#[frame_support::transactional]
|
fn decrease_allocation(
|
||||||
pub fn decrease_allocation(
|
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
account: T::AccountId,
|
account: T::AccountId,
|
||||||
amount: Amount,
|
amount: Amount,
|
||||||
@@ -609,7 +550,7 @@ pub mod pallet {
|
|||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set it to PendingDeallocations, letting the staking pallet release it on a future session
|
// Set it to PendingDeallocations, letting it be released upon a future session
|
||||||
// This unwrap should be fine as this account is active, meaning a session has occurred
|
// This unwrap should be fine as this account is active, meaning a session has occurred
|
||||||
let mut to_unlock_on = Self::session(network).unwrap();
|
let mut to_unlock_on = Self::session(network).unwrap();
|
||||||
if network == NetworkId::Serai {
|
if network == NetworkId::Serai {
|
||||||
@@ -665,7 +606,7 @@ pub mod pallet {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_session() {
|
fn new_session() {
|
||||||
for network in serai_primitives::NETWORKS {
|
for network in serai_primitives::NETWORKS {
|
||||||
// If this network hasn't started sessions yet, don't start one now
|
// If this network hasn't started sessions yet, don't start one now
|
||||||
let Some(current_session) = Self::session(network) else { continue };
|
let Some(current_session) = Self::session(network) else { continue };
|
||||||
@@ -677,10 +618,6 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_validators(network: NetworkId) -> Vec<Public> {
|
|
||||||
Self::participants(network).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn retire_set(set: ValidatorSet) {
|
pub fn retire_set(set: ValidatorSet) {
|
||||||
MuSigKeys::<T>::remove(set);
|
MuSigKeys::<T>::remove(set);
|
||||||
Keys::<T>::remove(set);
|
Keys::<T>::remove(set);
|
||||||
@@ -690,7 +627,7 @@ pub mod pallet {
|
|||||||
/// Take the amount deallocatable.
|
/// Take the amount deallocatable.
|
||||||
///
|
///
|
||||||
/// `session` refers to the Session the stake becomes deallocatable on.
|
/// `session` refers to the Session the stake becomes deallocatable on.
|
||||||
pub fn take_deallocatable_amount(
|
fn take_deallocatable_amount(
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
session: Session,
|
session: Session,
|
||||||
key: Public,
|
key: Public,
|
||||||
@@ -702,6 +639,154 @@ pub mod pallet {
|
|||||||
PendingDeallocations::<T>::take((network, session, key))
|
PendingDeallocations::<T>::take((network, session, key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pallet::call]
|
||||||
|
impl<T: Config> Pallet<T> {
|
||||||
|
#[pallet::call_index(0)]
|
||||||
|
#[pallet::weight(0)] // TODO
|
||||||
|
pub fn set_keys(
|
||||||
|
origin: OriginFor<T>,
|
||||||
|
network: NetworkId,
|
||||||
|
key_pair: KeyPair,
|
||||||
|
signature: Signature,
|
||||||
|
) -> DispatchResult {
|
||||||
|
ensure_none(origin)?;
|
||||||
|
|
||||||
|
// signature isn't checked as this is an unsigned transaction, and validate_unsigned
|
||||||
|
// (called by pre_dispatch) checks it
|
||||||
|
let _ = signature;
|
||||||
|
|
||||||
|
let session = Session(pallet_session::Pallet::<T>::current_index());
|
||||||
|
|
||||||
|
let set = ValidatorSet { session, network };
|
||||||
|
|
||||||
|
Keys::<T>::set(set, Some(key_pair.clone()));
|
||||||
|
Self::deposit_event(Event::KeyGen { set, key_pair });
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::call_index(1)]
|
||||||
|
#[pallet::weight(0)] // TODO
|
||||||
|
pub fn allocate(origin: OriginFor<T>, network: NetworkId, amount: Amount) -> DispatchResult {
|
||||||
|
let validator = ensure_signed(origin)?;
|
||||||
|
Coins::<T>::transfer_internal(
|
||||||
|
validator,
|
||||||
|
Self::account(),
|
||||||
|
Balance { coin: Coin::Serai, amount },
|
||||||
|
)?;
|
||||||
|
Self::increase_allocation(network, validator, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::call_index(2)]
|
||||||
|
#[pallet::weight(0)] // TODO
|
||||||
|
pub fn deallocate(origin: OriginFor<T>, network: NetworkId, amount: Amount) -> DispatchResult {
|
||||||
|
let account = ensure_signed(origin)?;
|
||||||
|
|
||||||
|
let can_immediately_deallocate = Self::decrease_allocation(network, account, amount)?;
|
||||||
|
if can_immediately_deallocate {
|
||||||
|
Coins::<T>::transfer_internal(
|
||||||
|
Self::account(),
|
||||||
|
account,
|
||||||
|
Balance { coin: Coin::Serai, amount },
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::call_index(3)]
|
||||||
|
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
||||||
|
pub fn claim_deallocation(
|
||||||
|
origin: OriginFor<T>,
|
||||||
|
network: NetworkId,
|
||||||
|
session: Session,
|
||||||
|
) -> DispatchResult {
|
||||||
|
let account = ensure_signed(origin)?;
|
||||||
|
let Some(amount) = Self::take_deallocatable_amount(network, session, account) else {
|
||||||
|
Err(Error::<T>::NonExistentDeallocation)?
|
||||||
|
};
|
||||||
|
Coins::<T>::transfer_internal(
|
||||||
|
Self::account(),
|
||||||
|
account,
|
||||||
|
Balance { coin: Coin::Serai, amount },
|
||||||
|
)?;
|
||||||
|
Self::deposit_event(Event::DeallocationClaimed {
|
||||||
|
validator: account,
|
||||||
|
network,
|
||||||
|
session,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::validate_unsigned]
|
||||||
|
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
||||||
|
type Call = Call<T>;
|
||||||
|
|
||||||
|
fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
||||||
|
// Match to be exhaustive
|
||||||
|
let (network, key_pair, signature) = match call {
|
||||||
|
Call::set_keys { network, ref key_pair, ref signature } => (network, key_pair, signature),
|
||||||
|
Call::allocate { .. } | Call::deallocate { .. } | Call::claim_deallocation { .. } => {
|
||||||
|
Err(InvalidTransaction::Call)?
|
||||||
|
}
|
||||||
|
Call::__Ignore(_, _) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let session = Session(pallet_session::Pallet::<T>::current_index());
|
||||||
|
|
||||||
|
let set = ValidatorSet { session, network: *network };
|
||||||
|
match Self::verify_signature(set, key_pair, signature) {
|
||||||
|
Err(Error::AlreadyGeneratedKeys) => Err(InvalidTransaction::Stale)?,
|
||||||
|
Err(Error::NonExistentValidatorSet) |
|
||||||
|
Err(Error::InsufficientAllocation) |
|
||||||
|
Err(Error::NotEnoughAllocated) |
|
||||||
|
Err(Error::AllocationWouldRemoveFaultTolerance) |
|
||||||
|
Err(Error::DeallocationWouldRemoveParticipant) |
|
||||||
|
Err(Error::DeallocationWouldRemoveFaultTolerance) |
|
||||||
|
Err(Error::NonExistentDeallocation) |
|
||||||
|
Err(Error::NonExistentValidator) |
|
||||||
|
Err(Error::BadSignature) => Err(InvalidTransaction::BadProof)?,
|
||||||
|
Err(Error::__Ignore(_, _)) => unreachable!(),
|
||||||
|
Ok(()) => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidTransaction::with_tag_prefix("validator-sets")
|
||||||
|
.and_provides(set)
|
||||||
|
// Set a 10 block longevity, though this should be included in the next block
|
||||||
|
.longevity(10)
|
||||||
|
.propagate(true)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly provide a pre-dispatch which calls validate_unsigned
|
||||||
|
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
|
||||||
|
Self::validate_unsigned(TransactionSource::InBlock, call).map(|_| ()).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call order is end_session(i - 1) -> start_session(i) -> new_session(i + 1)
|
||||||
|
// new_session(i + 1) is called immediately after start_session(i)
|
||||||
|
// then we wait until the session ends then get a call to end_session(i) and so on.
|
||||||
|
impl<T: Config> pallet_session::SessionManager<T::ValidatorId> for Pallet<T> {
|
||||||
|
fn new_session(_new_index: u32) -> Option<Vec<T::ValidatorId>> {
|
||||||
|
Self::new_session();
|
||||||
|
// TODO: Where do we return their stake?
|
||||||
|
Some(Self::participants(NetworkId::Serai).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_session_genesis(_: u32) -> Option<Vec<T::ValidatorId>> {
|
||||||
|
// TODO: Because we don't call new_session here, we don't emit NewSet { Serai, session: 1 }
|
||||||
|
Some(Self::participants(NetworkId::Serai).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_session(end_index: u32) {
|
||||||
|
Self::retire_set(ValidatorSet { network: NetworkId::Serai, session: Session(end_index) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_session(_start_index: u32) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use pallet::*;
|
pub use pallet::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user