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

Feat/separate unlock chunks #1753 #1763

Merged
merged 29 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
03ef368
new struct and basic migration stubs, compiling
shannonwells Nov 3, 2023
eb36287
linting
shannonwells Nov 3, 2023
085bfd8
stake, unstake, withdraw_unstake passing tests except 1
shannonwells Nov 3, 2023
5379dd6
Fix bug in StakingAccountDetails withdraw
shannonwells Nov 7, 2023
ec9ea57
correctly set locked token amounts with unstake_unlocks plus staking …
shannonwells Nov 7, 2023
80aec25
updates after rebase
shannonwells Nov 7, 2023
e85b2ca
* Rename StakingAccountDetailsV2 to just StakingDetails
shannonwells Nov 9, 2023
275a69a
migration test finished and passing
shannonwells Nov 10, 2023
1f6470a
try-runtime working on rococo, tests passing
shannonwells Nov 13, 2023
f48813d
fix warnings
shannonwells Nov 13, 2023
baad12b
design doc reflects rename
shannonwells Nov 14, 2023
a079cc4
design doc reflects rename in capacity.md
shannonwells Nov 14, 2023
1fe2961
please the linter
shannonwells Nov 14, 2023
d992c68
cleanup
shannonwells Nov 14, 2023
4185a5a
Run benchmarks [run-benchmarks capacity]
shannonwells Nov 14, 2023
a872e2d
address PR comments
shannonwells Nov 15, 2023
70285d1
updates after rebase
shannonwells Nov 15, 2023
fdae443
fill unlock chunks for unstake benchmark
shannonwells Nov 15, 2023
ec39db6
Run benchmarks [run-benchmarks capacity]
shannonwells Nov 15, 2023
67d8caf
* Add a test for unstake when there is no staker-target relationship
shannonwells Nov 16, 2023
1c18d9a
Run benchmarks [run-benchmarks capacity]
shannonwells Nov 17, 2023
c710c09
Update weights for PR #1763
do-not-reply Nov 17, 2023
bca93ba
bump spec version
shannonwells Nov 17, 2023
2324c2a
respond to PR review comments
shannonwells Nov 30, 2023
495f095
Run benchmarks [run-benchmarks capacity]
shannonwells Nov 30, 2023
9b1f0a7
add a bit about how the current version works
shannonwells Nov 30, 2023
3ccc599
fix comments
shannonwells Nov 30, 2023
2b6f740
Update weights for PR #1763
do-not-reply Nov 30, 2023
f0675d1
remove try_mutate, fix set_staking_account
shannonwells Dec 1, 2023
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
4 changes: 2 additions & 2 deletions designdocs/capacity.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ Storage for keeping records of staking accounting.
/// Storage for keeping a ledger of staked token amounts for accounts.
#[pallet::storage]
pub type StakingAccountLedger<T: Config> =
StorageMap<_, Twox64Concat, T::AccountId, StakingAccountDetails<T::Balance>>;
StorageMap<_, Twox64Concat, T::AccountId, StakingDetails<T::Balance>>;

```

Expand Down Expand Up @@ -355,7 +355,7 @@ The type used for storing information about staking details.

```rust

pub struct StakingAccountDetails<Balance, BlockNumber> {
pub struct StakingDetails<Balance, BlockNumber> {
/// The amount a Staker has staked, minus the sum of all tokens in `unlocking`.
pub active: Balance,
/// The total amount of tokens in `active` and `unlocking`
Expand Down
35 changes: 15 additions & 20 deletions designdocs/capacity_staking_rewards_implementation.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
# Capacity Staking Rewards Implementation

## Overview
Staking Capacity for rewards is a new feature which allows token holders to stake FRQCY and split the staking
rewards with a Provider they choose. The Provider receives a small reward in Capacity
(which is periodically replenished), and the staker receives a periodic return in FRQCY token.
The amount of Capacity that the Provider would receive in such case is a fraction of what they would get from a
`MaximumCapacity` stake.
This document describes a new type of staking which allows token holders to stake FRQCY and split staking rewards with a Provider the staker chooses.

Currently, when staking token for Capacity, the only choice is to assign all the generated Capacity to the designated target.
The target, who must be a Provider, may then spend this Capacity to pay for specific transactions. This is called **Maximized Capacity** staking.

In this new type of staking, called **Provider Boosting**, the Provider receives a reward in Capacity and the staker receives a periodic return in FRQCY token.
The amount of Capacity that the Provider would receive in such case is a less than what they would get from a `MaximumCapacity` stake.

The period of Capacity replenishment - the `Epoch` - and the period of token reward - the `RewardEra`- are different.
Epochs much necessarily be much shorter than rewards because Capacity replenishment needs to be multiple times a day to meet the needs of a high traffic network, and to allow Providers the ability to delay transactions to a time of day with lower network activity if necessary.
Reward eras need to be on a much longer scale, such as every two weeks, because there are potentially orders of magnitude more stakers, and calculating rewards is computationally more intensive than updating Capacity balances for the comparatively few Providers.
In addition, this lets the chain to store Reward history for much longer rather than forcing people to have to take steps to claim rewards.

### Diagram
This illustrates roughly (and not to scale) how Provider Boost staking works. Just like the current staking behavior, now called Maximized staking, The Capacity generated by staking is added to the Provider's Capacity ledger immediately so it can be used right away. The amount staked is locked in Alice's account, preventing transfer.
This illustrates roughly -- not to scale and **NOT reflecting actual reward amounts** -- how Provider Boost staking is expected to work. Just like the current staking behavior, now called Maximium staking, The Capacity generated by staking is added to the Provider's Capacity ledger immediately so it can be used right away. The amount staked is locked in Alice's account, preventing transfer.
shannonwells marked this conversation as resolved.
Show resolved Hide resolved

Provider Boost token rewards are earned only for token staked for a complete Reward Era. So Alice does not begin earning rewards until Reward Era 5 in the diagram, and this means Alice must wait until Reward Era 6 to claim rewards for Reward Era 5. Unclaimed reward amounts are actually not minted or transferred until they are claimed, and may also not be calculated until then, depending on the economic model.

This process will be described in more detail in the Economic Model Design Document.

### NOTE: Actual reward amounts are TBD; amounts are for illustration purposes only
![Provider boosted staking](https://github.com/LibertyDSNP/frequency/assets/502640/ffb632f2-79c2-4a09-a906-e4de02e4f348)

The proposed feature is a design for staking FRQCY token in exchange for Capacity and/or FRQCY.
Expand Down Expand Up @@ -50,23 +53,15 @@ It does not give regard to what the economic model actually is, since that is ye

## Staking Token Rewards

### StakingAccountDetails updates
### StakingAccountDetails --> StakingDetails
New fields are added. The field **`last_rewarded_at`** is to keep track of the last time rewards were claimed for this Staking Account.
MaximumCapacity staking accounts MUST always have the value `None` for `last_rewarded_at`.
Finally, `stake_change_unlocking`, is added, which stores an `UnlockChunk` when a staking account has changed.
targets for some amount of funds. This is to prevent retarget spamming.

This will be a V2 of this storage and original StakingAccountDetails will need to be migrated.
This is a second version of this storage, to replace StakingAccountDetails, and StakingAccountDetails data will need to be migrated.
```rust
pub struct StakingAccountDetailsV2<T: Config> {
pub struct StakingDetails<T: Config> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnlockChunk should likely be in here, but not a blocking change.

pub active: BalanceOf<T>,
pub total: BalanceOf<T>,
pub unlocking: BoundedVec<UnlockChunk<BalanceOf<T>, T::EpochNumber>, T::MaxUnlockingChunks>,
/// The number of the last StakingEra that this account's rewards were claimed.
pub last_rewards_claimed_at: Option<T::RewardEra>, // NEW None means never rewarded, Some(RewardEra) means last rewarded RewardEra.
/// staking amounts that have been retargeted are prevented from being retargeted again for the
/// configured Thawing Period number of blocks.
pub stake_change_unlocking: BoundedVec<UnlockChunk<BalanceOf<T>, T::RewardEra>, T::MaxUnlockingChunks>, // NEW
}
```

Expand Down Expand Up @@ -150,7 +145,7 @@ pub struct StakingRewardClaim<T: Config> {
/// How much is claimed, in token
pub claimed_reward: Balance,
/// The end state of the staking account if the operations are valid
pub staking_account_end_state: StakingAccountDetails,
pub staking_account_end_state: StakingDetails,
/// The starting era for the claimed reward period, inclusive
pub from_era: T::RewardEra,
/// The ending era for the claimed reward period, inclusive
Expand Down Expand Up @@ -264,7 +259,7 @@ calculate rewards on chain at all.

Regardless, on success, the claimed rewards are minted and transferred as locked token to the origin, with the existing
unstaking thaw period for withdrawal (which simply unlocks thawed token amounts as before).
There is no chunk added; instead the existing unstaking thaw period is applied to last_rewards_claimed_at in StakingAccountDetails.
There is no chunk added; instead the existing unstaking thaw period is applied to last_rewards_claimed_at in StakingDetails.

Forcing stakers to wait a thaw period for every claim is an incentive to claim rewards sooner than later, leveling out
possible inflationary effects and helping prevent unclaimed rewards from expiring.
Expand Down Expand Up @@ -336,7 +331,7 @@ No more than `T::MaxUnlockingChunks` staking amounts may be retargeted within th
Each call creates one chunk. Emits a `StakingTargetChanged` event with the parameters of the extrinsic.
```rust
/// Sets the target of the staking capacity to a new target.
/// This adds a chunk to `StakingAccountDetails.stake_change_unlocking chunks`, up to `T::MaxUnlockingChunks`.
/// This adds a chunk to `StakingDetails.stake_change_unlocking chunks`, up to `T::MaxUnlockingChunks`.
/// The staked amount and Capacity generated by `amount` originally targeted to the `from` MSA Id is reassigned to the `to` MSA Id.
/// Does not affect unstaking process or additional stake amounts.
/// Changing a staking target to a Provider when Origin has nothing staked them will retain the staking type.
Expand Down
34 changes: 21 additions & 13 deletions pallets/capacity/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::*;
use crate::Pallet as Capacity;

use frame_benchmarking::{account, benchmarks, whitelist_account};
use frame_support::{assert_ok, traits::Currency};
use frame_support::{assert_ok, traits::Currency, BoundedVec};
use frame_system::RawOrigin;
use parity_scale_codec::alloc::vec::Vec;

Expand Down Expand Up @@ -56,21 +56,19 @@ benchmarks! {

withdraw_unstaked {
let caller: T::AccountId = create_funded_account::<T>("account", SEED, 5u32);
let amount: BalanceOf<T> = T::MinimumStakingAmount::get();

let mut staking_account = StakingAccountDetails::<T>::default();
staking_account.deposit(500u32.into());

// set new unlock chunks using tuples of (value, thaw_at)
let new_unlocks: Vec<(u32, u32)> = Vec::from([(50u32, 3u32), (50u32, 5u32)]);
assert_eq!(true, staking_account.set_unlock_chunks(&new_unlocks));
let mut unlocking: UnlockChunkList<T> = BoundedVec::default();
for _i in 0..T::MaxUnlockingChunks::get() {
let unlock_chunk: UnlockChunk<BalanceOf<T>, T::EpochNumber> = UnlockChunk { value: 1u32.into(), thaw_at: 3u32.into() };
assert_ok!(unlocking.try_push(unlock_chunk));
}
UnstakeUnlocks::<T>::set(&caller, Some(unlocking));

Capacity::<T>::set_staking_account(&caller.clone(), &staking_account);
CurrentEpoch::<T>::set(T::EpochNumber::from(5u32));

}: _ (RawOrigin::Signed(caller.clone()))
verify {
assert_last_event::<T>(Event::<T>::StakeWithdrawn {account: caller, amount: 100u32.into() }.into());
let total = T::MaxUnlockingChunks::get();
assert_last_event::<T>(Event::<T>::StakeWithdrawn {account: caller, amount: total.into() }.into());
}

on_initialize {
Expand All @@ -91,18 +89,28 @@ benchmarks! {
let target = 1;
let block_number = 4u32;

let mut staking_account = StakingAccountDetails::<T>::default();
let mut staking_account = StakingDetails::<T>::default();
let mut target_details = StakingTargetDetails::<BalanceOf<T>>::default();
let mut capacity_details = CapacityDetails::<BalanceOf<T>, <T as Config>::EpochNumber>::default();

staking_account.deposit(staking_amount);
target_details.deposit(staking_amount, capacity_amount);
capacity_details.deposit(&staking_amount, &capacity_amount);

Capacity::<T>::set_staking_account(&caller.clone(), &staking_account);
let _ = Capacity::<T>::set_staking_account_and_lock(&caller.clone(), &staking_account);
Capacity::<T>::set_target_details_for(&caller.clone(), target, target_details);
Capacity::<T>::set_capacity_for(target, capacity_details);

// fill up unlock chunks to max bound - 1
let count = T::MaxUnlockingChunks::get()-1;
let mut unlocking: UnlockChunkList<T> = BoundedVec::default();
for _i in 0..count {
let unlock_chunk: UnlockChunk<BalanceOf<T>, T::EpochNumber> = UnlockChunk { value: 1u32.into(), thaw_at: 3u32.into() };
assert_ok!(unlocking.try_push(unlock_chunk));
}
UnstakeUnlocks::<T>::set(&caller, Some(unlocking));


}: _ (RawOrigin::Signed(caller.clone()), target, unstaking_amount.into())
verify {
assert_last_event::<T>(Event::<T>::UnStaked {account: caller, target: target, amount: unstaking_amount.into(), capacity: Capacity::<T>::calculate_capacity_reduction(unstaking_amount.into(), staking_amount, capacity_amount) }.into());
Expand Down
Loading
Loading