Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NODE-149, feat: staking update #96

Open
wants to merge 59 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
a0aa1e0
NODE-106, deps: `polkadot-sdk@stable2407` migration (#67)
dnjscksdn98 Oct 28, 2024
63ca760
fix: replace deprecated function
dnjscksdn98 Oct 28, 2024
a00afa4
fix: resolve old syntax
dnjscksdn98 Nov 29, 2024
ab87802
feat: include system vault address
dnjscksdn98 Nov 29, 2024
b576236
deps: update cargo
dnjscksdn98 Nov 29, 2024
1e0aed5
chore: update package.json
dnjscksdn98 Nov 29, 2024
477c4a0
chore: remove passed storage migrations
dnjscksdn98 Dec 2, 2024
04b35ec
deps: update cargo
dnjscksdn98 Dec 2, 2024
a8b2100
feat: update cargo - Cancun support
dnjscksdn98 Dec 3, 2024
c456931
deps: update cargo (bifrost-frontier)
dnjscksdn98 Dec 4, 2024
46903b2
(rollback): include system vault address
dnjscksdn98 Dec 5, 2024
ef7272a
Merge branch 'main' into pre-v2.0.1
dnjscksdn98 Dec 9, 2024
42ba7ed
NODE-149, feat: update bond_less workflow
dnjscksdn98 Oct 30, 2024
bb0bc10
NODE-149, feat: update bondless & revoke workflow
dnjscksdn98 Nov 2, 2024
d3d8ac6
NODE-149, feat: update cancel_pending_request for Leave
dnjscksdn98 Nov 4, 2024
d442552
NODE-149, feat: allow reward payout on Decrease
dnjscksdn98 Nov 4, 2024
fc1e7ca
NODE-149, feat: split workflow for bond more
dnjscksdn98 Nov 4, 2024
7c4fc67
NODE-149, feat: handle unstaking nominations unreserves
dnjscksdn98 Nov 5, 2024
c0f07ba
NODE-149, feat: update unstaking nominations workflow
dnjscksdn98 Nov 5, 2024
c35a081
NODE-149, feat: unreserve pending requests on kickout
dnjscksdn98 Nov 5, 2024
6bc4655
NODE-149, chore: remove unnecessary flag
dnjscksdn98 Nov 6, 2024
2824132
NODE-149, chore: fix docstring
dnjscksdn98 Nov 6, 2024
52864be
NODE-149, feat: enable multiple decrease requests
dnjscksdn98 Nov 15, 2024
84492ce
NODE-149, chore: update extrinsic doc strings
dnjscksdn98 Nov 15, 2024
8324572
NODE-149, chore: update variable name
dnjscksdn98 Nov 15, 2024
c63f743
NODE-149, chore: update doc string
dnjscksdn98 Nov 15, 2024
88ca335
NODE-149, chore: update doc string - handle_validator_reward_payout
dnjscksdn98 Nov 15, 2024
b0284da
NODE-149, fix: add missing replacements
dnjscksdn98 Nov 15, 2024
562f379
NODE-149, chore: update doc strings - add_bottom_nomination
dnjscksdn98 Nov 15, 2024
42b434b
NODE-149, chore: update doc strings - increase_nomination
dnjscksdn98 Nov 15, 2024
aba13af
NODE-149, chore: update doc strings - schedule_decrease_nomination
dnjscksdn98 Nov 15, 2024
528402b
NODE-149, chore: update doc strings - schedule_revoke
dnjscksdn98 Nov 15, 2024
b03e3e6
NODE-149, chore: update doc strings - execute_pending_request
dnjscksdn98 Nov 15, 2024
cf101b3
NODE-149, chore: update doc strings - cancel_pending_request
dnjscksdn98 Nov 15, 2024
386870f
NODE-149, fix: remove unused revocations_count
dnjscksdn98 Nov 15, 2024
4fdda38
NODE-149, fix: update some comments
dnjscksdn98 Nov 15, 2024
7ba10e2
NODE-149, fix: add missing logic
dnjscksdn98 Nov 18, 2024
0102b75
NODE-149, fix: add missing currency reservation
dnjscksdn98 Nov 18, 2024
21961fb
NODE-149, fix: do not check NominatorState on UnstakingNominations
dnjscksdn98 Nov 18, 2024
f583327
NODE-149, fix: bring back NominatorState check with pattern check
dnjscksdn98 Nov 18, 2024
03411b6
NODE-149, fix: update return_stake NominatorState removal
dnjscksdn98 Nov 19, 2024
90f9d19
NODE-149, chore: update staking config
dnjscksdn98 Nov 19, 2024
208e2a7
NODE-149, chore: update staking precompile interface
dnjscksdn98 Nov 19, 2024
bed8a42
NODE-149, test: update staking test codes
dnjscksdn98 Nov 19, 2024
0d6e7b8
NODE-149, chore: update docstring
dnjscksdn98 Nov 20, 2024
a6696ff
NODE-149, chore: update nominator_requests interface
dnjscksdn98 Nov 20, 2024
d7cbab8
NODE-149, fix: allow revoking last nomination
dnjscksdn98 Nov 21, 2024
e8336b9
NODE-149, fix: remove unused function
dnjscksdn98 Nov 21, 2024
3f1067e
NODE-149, test: update staking test codes
dnjscksdn98 Nov 19, 2024
d615c4e
NODE-149, chore: update cargo (bifrost-frontier)
dnjscksdn98 Dec 5, 2024
c3045bb
NODE-149, feat: add nomination decrease limitations
dnjscksdn98 Dec 6, 2024
86ed739
Merge branch 'main' into NODE-149-staking-update
dnjscksdn98 Dec 10, 2024
e7d7da5
NODE-149, fix: remove redundant type casting
dnjscksdn98 Dec 10, 2024
a19bee3
NODE-158, fix: update runtime api to stable version (#103)
dnjscksdn98 Dec 13, 2024
f51eff7
NODE-106, deps: `polkadot-sdk@stable2407` migration (#67)
dnjscksdn98 Oct 28, 2024
ae6da4d
NODE-149, test: update staking test codes
dnjscksdn98 Nov 19, 2024
0fb8fae
NODE-149, chore: update cargo (bifrost-frontier)
dnjscksdn98 Dec 5, 2024
420acb4
NODE-149, feat: init bfc-staking v6 storage migration
dnjscksdn98 Dec 19, 2024
51449b4
NODE-149, fix: move NominationChange::Leave to the end
dnjscksdn98 Dec 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,225 changes: 769 additions & 456 deletions Cargo.lock

Large diffs are not rendered by default.

607 changes: 333 additions & 274 deletions pallets/bfc-staking/src/lib.rs

Large diffs are not rendered by default.

207 changes: 207 additions & 0 deletions pallets/bfc-staking/src/migrations.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,213 @@
use super::*;
use crate::set::OrderedSet;

pub mod v6 {
use frame_support::traits::OnRuntimeUpgrade;

use super::*;

pub struct MigrateToV6<T>(PhantomData<T>);

#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
/// The change request for a specific nomination.
pub struct OldNominationRequest<AccountId, Balance> {
/// The validator who owns this nomination
pub validator: AccountId,
/// The total unbonding amount of this request
pub amount: Balance,
/// The unbonding amount for each round.
/// `Decrease` requests are allowed to be pending for multiple rounds.
pub when_executable: RoundIndex,
/// The requested unbonding action
pub action: NominationChange,
}

#[derive(Clone, Encode, PartialEq, Decode, RuntimeDebug, TypeInfo)]
/// Pending requests to mutate nominations for each nominator
pub struct OldPendingNominationRequests<AccountId, Balance> {
/// Number of pending revocations (necessary for determining whether revoke is exit)
pub revocations_count: u32,
/// Map from validator -> Request (enforces at most 1 pending request per nomination)
pub requests: BTreeMap<AccountId, OldNominationRequest<AccountId, Balance>>,
/// Total amount of pending requests.
pub less_total: Balance,
}

#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
/// Nominator state
pub struct OldNominator<AccountId, Balance> {
/// Nominator account
pub id: AccountId,
/// Current state of all nominations
pub nominations: BTreeMap<AccountId, Balance>,
/// Initial state of all nominations
pub initial_nominations: BTreeMap<AccountId, Balance>,
/// Total balance locked for this nominator
pub total: Balance,
/// Requests to change nominations (decrease, revoke, and leave)
pub requests: OldPendingNominationRequests<AccountId, Balance>,
/// Status for this nominator
pub status: NominatorStatus,
/// The destination for round rewards
pub reward_dst: RewardDestination,
/// The total amount of awarded tokens to this nominator
pub awarded_tokens: Balance,
/// The amount of awarded tokens to this nominator per candidate
pub awarded_tokens_per_candidate: BTreeMap<AccountId, Balance>,
}

impl<T: Config> OnRuntimeUpgrade for MigrateToV6<T> {
fn on_runtime_upgrade() -> Weight {
let mut weight = Weight::zero();

let current = Pallet::<T>::in_code_storage_version();
let onchain = Pallet::<T>::on_chain_storage_version();

if current == 6 && onchain == 5 {
// 1. create `UnstakingNominations` for each validator
for (who, _) in CandidateInfo::<T>::iter() {
<UnstakingNominations<T>>::insert(&who, Nominations::default());
}

// 2. translate `NominatorState` & add requests to `UnstakingNominations`
NominatorState::<T>::translate(
|_, old: OldNominator<T::AccountId, BalanceOf<T>>| {
// 2.1. remove `requests.revocations_count` field
// 2.2. update `requests.requests.when_executable` from `RoundIndex` to `BTreeMap<RoundIndex, Balance>`
let mut new_requests: BTreeMap<
T::AccountId,
NominationRequest<T::AccountId, BalanceOf<T>>,
> = old.requests
.requests
.into_iter()
.map(|(validator, request)| {
(
validator.clone(),
NominationRequest {
validator,
amount: request.amount,
when_executable: BTreeMap::from([(
request.when_executable,
request.amount,
)]),
action: request.action,
},
)
})
.collect();

// 2.3. add leave requests to `requests.requests`
match old.status {
NominatorStatus::Leaving(round_index) => {
for (validator, amount) in old.nominations.clone() {
new_requests.insert(
validator.clone(),
NominationRequest {
validator,
amount,
when_executable: BTreeMap::from([(
round_index,
amount,
)]),
action: NominationChange::Leave,
},
);
}
},
_ => (),
}

// 2.4. decrease `nominations`, `total`, `requests.less_total`
let mut new_nominations = old.nominations.clone();
let mut new_total = old.total.clone();
let mut new_less_total = BalanceOf::<T>::zero();
for (validator, request) in new_requests.iter() {
if let Some(amount) = new_nominations.remove(validator) {
new_nominations.insert(
validator.clone(),
amount.saturating_sub(request.amount),
);
new_total = new_total.saturating_sub(request.amount);
new_less_total = new_less_total.saturating_add(request.amount);

// 2.5. add to `UnstakingNominations`
let _ = Pallet::<T>::add_to_unstaking_nominations(
validator.clone(),
Bond { owner: old.id.clone(), amount: request.amount },
);

// 2.6. decrease `Total`
<Total<T>>::mutate(|total| {
*total = total.saturating_sub(request.amount);
});

// 2.7. decrease `CandidateInfo.voting_power`
let mut candidate_info =
CandidateInfo::<T>::get(&validator).expect("CandidateInfo DNE");
candidate_info.voting_power = candidate_info
.clone()
.voting_power
.saturating_sub(request.amount);

// 2.8. decrease or remove from `TopNominations` | `BottomNominations`
// this will reorganize the following fields
// - `lowest_top_nomination_amount`
// - `highest_bottom_nomination_amount`
// - `lowest_bottom_nomination_amount`
// - `top_capacity`
// - `bottom_capacity`
// - `CandidatePool`
match request.action {
NominationChange::Decrease => {
candidate_info
.decrease_nomination::<T>(
&validator,
old.id.clone(),
amount,
request.amount,
)
.expect("decrease_nomination failed");
},
NominationChange::Revoke | NominationChange::Leave => {
candidate_info
.rm_nomination_if_exists::<T>(
&validator,
old.id.clone(),
request.amount,
)
.expect("rm_nomination_if_exists failed");
},
}
<CandidateInfo<T>>::insert(&validator, candidate_info);
}
}

Some(Nominator {
id: old.id,
nominations: new_nominations,
initial_nominations: old.initial_nominations.clone(),
total: new_total,
requests: PendingNominationRequests {
requests: new_requests,
less_total: new_less_total,
},
status: old.status,
reward_dst: old.reward_dst,
awarded_tokens: old.awarded_tokens,
awarded_tokens_per_candidate: old.awarded_tokens_per_candidate,
})
},
);
log!(info, "bfc-staking storage migration v6 completed successfully ✅");
} else {
log!(warn, "Skipping bfc-staking storage migration v6 💤");
weight = weight.saturating_add(T::DbWeight::get().reads(1));
}
weight
}
}
}

pub mod v5 {
use frame_support::traits::OnRuntimeUpgrade;

Expand Down
90 changes: 72 additions & 18 deletions pallets/bfc-staking/src/pallet/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use sp_std::{collections::btree_set::BTreeSet, vec, vec::Vec};

use frame_support::{
pallet_prelude::*,
traits::{Currency, EstimateNextSessionRotation, Get, Imbalance, ReservableCurrency},
traits::{Currency, EstimateNextSessionRotation, Get, Imbalance},
weights::Weight,
BoundedBTreeSet,
};
Expand Down Expand Up @@ -77,6 +77,62 @@ impl<T: Config> Pallet<T> {
return commission_sets.into_iter().any(|c| c.who == *who);
}

/// Adds a new nomination to the unstaking nominations for the given candidate.
/// If the nomination already exists, it will increase the amount
pub fn add_to_unstaking_nominations(
candidate: T::AccountId,
nomination: Bond<T::AccountId, BalanceOf<T>>,
) -> DispatchResult {
let mut unstaking_nominations =
<UnstakingNominations<T>>::get(&candidate).ok_or(Error::<T>::UnstakingNominationDNE)?;
let mut is_exist = false;
for n in &mut unstaking_nominations.nominations {
if n.owner == nomination.owner {
n.amount = n.amount.saturating_add(nomination.amount);
is_exist = true;
break;
}
}
if !is_exist {
unstaking_nominations.nominations.push(nomination.clone());
}
unstaking_nominations.total = unstaking_nominations.total.saturating_add(nomination.amount);
unstaking_nominations.sort_greatest_to_least();
<UnstakingNominations<T>>::insert(&candidate, unstaking_nominations);
Ok(())
}

/// Removes a nomination from the unstaking nominations for the given candidate.
/// If the nomination amount is zero, it will remove the nomination
pub fn remove_unstaking_nomination(
candidate: T::AccountId,
nomination: Bond<T::AccountId, BalanceOf<T>>,
) -> DispatchResult {
let mut unstaking_nominations =
<UnstakingNominations<T>>::get(&candidate).ok_or(Error::<T>::UnstakingNominationDNE)?;
unstaking_nominations.nominations = unstaking_nominations
.nominations
.clone()
.into_iter()
.filter_map(|n| {
if n.owner == nomination.owner {
let amount = n.amount.saturating_sub(nomination.amount);
if amount.is_zero() {
None
} else {
Some(Bond { owner: n.owner, amount })
}
} else {
Some(n)
}
})
.collect();
unstaking_nominations.total = unstaking_nominations.total.saturating_sub(nomination.amount);
unstaking_nominations.sort_greatest_to_least();
<UnstakingNominations<T>>::insert(&candidate, unstaking_nominations);
Ok(())
}

/// Adds a new controller set request. The state reflection will be applied in the next round.
pub fn add_to_controller_sets(
stash: T::AccountId,
Expand Down Expand Up @@ -256,17 +312,10 @@ impl<T: Config> Pallet<T> {
) -> DispatchResult {
let mut state = CandidateInfo::<T>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
state.rm_nomination_if_exists::<T>(&candidate, nominator.clone(), amount)?;
T::Currency::unreserve(&nominator, amount);
let new_total_locked = Total::<T>::get().saturating_sub(amount);
<Total<T>>::put(new_total_locked);
let new_total = state.voting_power;
<CandidateInfo<T>>::insert(&candidate, state);
Self::deposit_event(Event::NominatorLeftCandidate {
nominator,
candidate,
unstaked_amount: amount,
total_candidate_staked: new_total,
<Total<T>>::mutate(|total| {
*total = total.saturating_sub(amount);
});
<CandidateInfo<T>>::insert(&candidate, state);
Ok(())
}

Expand Down Expand Up @@ -331,12 +380,8 @@ impl<T: Config> Pallet<T> {
reward: BalanceOf<T>,
) -> Result<(), DispatchError> {
if let Some(mut nominator_state) = NominatorState::<T>::get(&nominator) {
// the nominator must be active (not leaving)
// and not revoking/decreasing the current validator
if nominator_state.is_active()
&& !nominator_state.is_revoking(&controller)
&& !nominator_state.is_decreasing(&controller)
{
// the nominator must not be leaving and not revoking the current validator
if nominator_state.is_active() && !nominator_state.is_revoking(&controller) {
// mint rewards to the nominator account
Self::mint_reward(reward, nominator.clone());

Expand All @@ -347,7 +392,7 @@ impl<T: Config> Pallet<T> {
nominator_state.increment_awarded_tokens(&controller, reward);
// auto-compound nomination
if nominator_state
.increase_nomination::<T>(controller.clone(), reward)
.increase_nomination::<T>(controller.clone(), reward, true)
.is_ok()
{
<NominatorState<T>>::insert(&nominator, nominator_state);
Expand Down Expand Up @@ -459,6 +504,15 @@ impl<T: Config> Pallet<T> {
);
<BottomNominations<T>>::insert(&c.new, bottom_nominations);
}
// replace `UnstakingNominations`
if let Some(unstaking_nominations) = <UnstakingNominations<T>>::take(&c.old) {
Self::replace_nominator_nominations(
&unstaking_nominations.nominators(),
&c.old,
&c.new,
);
<UnstakingNominations<T>>::insert(&c.new, unstaking_nominations);
}
// replace `AwardedPts`
let points = <AwardedPts<T>>::take(now, &c.old);
<AwardedPts<T>>::insert(now, &c.new, points);
Expand Down
Loading