-
Notifications
You must be signed in to change notification settings - Fork 711
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduces: Delegated Staking Pallet (#3904)
This is the second PR in preparation for #454. ## Also see - **Precursor** #3889. - **Follow up** #3905. Overall changes are documented here (lot more visual 😍): https://hackmd.io/@ak0n/454-np-governance ## Changes ### Delegation Interface Provides delegation primitives for staking. Introduces two new roles: - Agent: These are accounts who receive delegation from other accounts (delegators) and stakes on behalf of them. The funds are held in delegator accounts. - Delegator: Accounts who delegate their funds to an agent authorising them to use it for staking. Supports - A way for delegators to add or withdraw delegation to an agent. - A way for an agent to slash a delegator during a slashing event. ### Pallet Delegated Staking - Implements `DelegationInterface`. - Lazy slashing: Any slashes to an Agent is posted in a ledger but not immediately slashed. The agent can call `DelegationInterface::delegator_slash` to slash the member and clear the corresponding slash from its ledger. - Consumes `StakingInterface` to provide `CoreStaking` features. In reality, this will be `pallet-staking`. - Ensures bookkeeping for agent and delegator are correct but leaves the management of reward and slash logic upto the consumer of this pallet. - While it does not expose any calls yet, it is written with the intent of exposing these primitives via extrinsics. ## TODO - [x] Improve unit tests in the pallet. - [x] Separate slash reward perbill for rewarding the slash reporters? - [x] Review if we should add more events. --------- Co-authored-by: Kian Paimani <[email protected]> Co-authored-by: Gonçalo Pestana <[email protected]> Co-authored-by: georgepisaltu <[email protected]>
- Loading branch information
1 parent
e6d934c
commit 4d47b44
Showing
11 changed files
with
2,480 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 | ||
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json | ||
|
||
title: Introduce pallet-delegated-staking | ||
|
||
doc: | ||
- audience: Runtime Dev | ||
description: | | ||
Adds a new pallet `delegated-staking` that allows delegators to delegate their funds to agents who can stake | ||
these funds on behalf of them. This would be used by Nomination Pools to migrate into a delegation staking based | ||
pool. | ||
|
||
crates: | ||
- name: pallet-delegated-staking | ||
bump: patch | ||
- name: pallet-staking | ||
bump: patch | ||
- name: sp-staking | ||
bump: minor |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
[package] | ||
name = "pallet-delegated-staking" | ||
version = "1.0.0" | ||
authors.workspace = true | ||
edition.workspace = true | ||
license = "Apache-2.0" | ||
homepage = "https://substrate.io" | ||
repository.workspace = true | ||
description = "FRAME delegated staking pallet" | ||
|
||
[package.metadata.docs.rs] | ||
targets = ["x86_64-unknown-linux-gnu"] | ||
|
||
[dependencies] | ||
codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } | ||
frame-support = { path = "../support", default-features = false } | ||
frame-system = { path = "../system", default-features = false } | ||
scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } | ||
sp-std = { path = "../../primitives/std", default-features = false } | ||
sp-runtime = { path = "../../primitives/runtime", default-features = false } | ||
sp-staking = { path = "../../primitives/staking", default-features = false } | ||
|
||
[dev-dependencies] | ||
sp-core = { path = "../../primitives/core" } | ||
sp-io = { path = "../../primitives/io" } | ||
substrate-test-utils = { path = "../../test-utils" } | ||
sp-tracing = { path = "../../primitives/tracing" } | ||
pallet-staking = { path = "../staking" } | ||
pallet-balances = { path = "../balances" } | ||
pallet-timestamp = { path = "../timestamp" } | ||
pallet-staking-reward-curve = { path = "../staking/reward-curve" } | ||
frame-election-provider-support = { path = "../election-provider-support", default-features = false } | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"codec/std", | ||
"frame-election-provider-support/std", | ||
"frame-support/std", | ||
"frame-system/std", | ||
"pallet-balances/std", | ||
"pallet-staking/std", | ||
"pallet-timestamp/std", | ||
"scale-info/std", | ||
"sp-core/std", | ||
"sp-io/std", | ||
"sp-runtime/std", | ||
"sp-staking/std", | ||
"sp-std/std", | ||
] | ||
runtime-benchmarks = [ | ||
"frame-election-provider-support/runtime-benchmarks", | ||
"frame-support/runtime-benchmarks", | ||
"frame-system/runtime-benchmarks", | ||
"pallet-balances/runtime-benchmarks", | ||
"pallet-staking/runtime-benchmarks", | ||
"pallet-timestamp/runtime-benchmarks", | ||
"sp-runtime/runtime-benchmarks", | ||
"sp-staking/runtime-benchmarks", | ||
] | ||
try-runtime = [ | ||
"frame-election-provider-support/try-runtime", | ||
"frame-support/try-runtime", | ||
"frame-system/try-runtime", | ||
"pallet-balances/try-runtime", | ||
"pallet-staking/try-runtime", | ||
"pallet-timestamp/try-runtime", | ||
"sp-runtime/try-runtime", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
// This file is part of Substrate. | ||
|
||
// Copyright (C) Parity Technologies (UK) Ltd. | ||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 | ||
|
||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
|
||
// 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 General Public License for more details. | ||
|
||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
//! Implementations of public traits, namely [`DelegationInterface`] and [`OnStakingUpdate`]. | ||
|
||
use super::*; | ||
use sp_staking::{DelegationInterface, DelegationMigrator, OnStakingUpdate}; | ||
|
||
impl<T: Config> DelegationInterface for Pallet<T> { | ||
type Balance = BalanceOf<T>; | ||
type AccountId = T::AccountId; | ||
|
||
/// Effective balance of the `Agent` account. | ||
fn agent_balance(who: &Self::AccountId) -> Self::Balance { | ||
Agent::<T>::get(who) | ||
.map(|agent| agent.ledger.effective_balance()) | ||
.unwrap_or_default() | ||
} | ||
|
||
fn delegator_balance(delegator: &Self::AccountId) -> Self::Balance { | ||
Delegation::<T>::get(delegator).map(|d| d.amount).unwrap_or_default() | ||
} | ||
|
||
/// Delegate funds to an `Agent`. | ||
fn delegate( | ||
who: &Self::AccountId, | ||
agent: &Self::AccountId, | ||
reward_account: &Self::AccountId, | ||
amount: Self::Balance, | ||
) -> DispatchResult { | ||
Pallet::<T>::register_agent( | ||
RawOrigin::Signed(agent.clone()).into(), | ||
reward_account.clone(), | ||
)?; | ||
|
||
// Delegate the funds from who to the `Agent` account. | ||
Pallet::<T>::delegate_to_agent(RawOrigin::Signed(who.clone()).into(), agent.clone(), amount) | ||
} | ||
|
||
/// Add more delegation to the `Agent` account. | ||
fn delegate_extra( | ||
who: &Self::AccountId, | ||
agent: &Self::AccountId, | ||
amount: Self::Balance, | ||
) -> DispatchResult { | ||
Pallet::<T>::delegate_to_agent(RawOrigin::Signed(who.clone()).into(), agent.clone(), amount) | ||
} | ||
|
||
/// Withdraw delegation of `delegator` to `Agent`. | ||
/// | ||
/// If there are funds in `Agent` account that can be withdrawn, then those funds would be | ||
/// unlocked/released in the delegator's account. | ||
fn withdraw_delegation( | ||
delegator: &Self::AccountId, | ||
agent: &Self::AccountId, | ||
amount: Self::Balance, | ||
num_slashing_spans: u32, | ||
) -> DispatchResult { | ||
Pallet::<T>::release_delegation( | ||
RawOrigin::Signed(agent.clone()).into(), | ||
delegator.clone(), | ||
amount, | ||
num_slashing_spans, | ||
) | ||
} | ||
|
||
/// Returns true if the `Agent` have any slash pending to be applied. | ||
fn has_pending_slash(agent: &Self::AccountId) -> bool { | ||
Agent::<T>::get(agent) | ||
.map(|d| !d.ledger.pending_slash.is_zero()) | ||
.unwrap_or(false) | ||
} | ||
|
||
fn delegator_slash( | ||
agent: &Self::AccountId, | ||
delegator: &Self::AccountId, | ||
value: Self::Balance, | ||
maybe_reporter: Option<Self::AccountId>, | ||
) -> sp_runtime::DispatchResult { | ||
Pallet::<T>::do_slash(agent.clone(), delegator.clone(), value, maybe_reporter) | ||
} | ||
} | ||
|
||
impl<T: Config> DelegationMigrator for Pallet<T> { | ||
type Balance = BalanceOf<T>; | ||
type AccountId = T::AccountId; | ||
|
||
fn migrate_nominator_to_agent( | ||
agent: &Self::AccountId, | ||
reward_account: &Self::AccountId, | ||
) -> DispatchResult { | ||
Pallet::<T>::migrate_to_agent( | ||
RawOrigin::Signed(agent.clone()).into(), | ||
reward_account.clone(), | ||
) | ||
} | ||
|
||
fn migrate_delegation( | ||
agent: &Self::AccountId, | ||
delegator: &Self::AccountId, | ||
value: Self::Balance, | ||
) -> DispatchResult { | ||
Pallet::<T>::migrate_delegation( | ||
RawOrigin::Signed(agent.clone()).into(), | ||
delegator.clone(), | ||
value, | ||
) | ||
} | ||
} | ||
|
||
impl<T: Config> OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> { | ||
fn on_slash( | ||
who: &T::AccountId, | ||
_slashed_active: BalanceOf<T>, | ||
_slashed_unlocking: &sp_std::collections::btree_map::BTreeMap<EraIndex, BalanceOf<T>>, | ||
slashed_total: BalanceOf<T>, | ||
) { | ||
<Agents<T>>::mutate(who, |maybe_register| match maybe_register { | ||
// if existing agent, register the slashed amount as pending slash. | ||
Some(register) => register.pending_slash.saturating_accrue(slashed_total), | ||
None => { | ||
// nothing to do | ||
}, | ||
}); | ||
} | ||
|
||
fn on_withdraw(stash: &T::AccountId, amount: BalanceOf<T>) { | ||
// if there is a withdraw to the agent, then add it to the unclaimed withdrawals. | ||
let _ = Agent::<T>::get(stash) | ||
// can't do anything if there is an overflow error. Just raise a defensive error. | ||
.and_then(|agent| agent.add_unclaimed_withdraw(amount).defensive()) | ||
.map(|agent| agent.save()); | ||
} | ||
} |
Oops, something went wrong.