cip | 31 |
---|---|
title | Incorporate staking rewards into vesting account schedules |
description | Dynamically update vesting schedules to include newly earned staking rewards |
author | Dean Eigenmann (@decanus), Marko Baricevic (@tac0turtle) |
discussions-to | Forum Discussion |
status | Last Call |
last-call-deadline | 2025-04-02 |
type | Standards Track |
category | Core |
created | 2025-02-07 |
Note: in the software lockup accounts are referred to vesting accounts, for this document we will refer to them as lockup accounts.
This CIP proposes integrating staking rewards directly into lockup accounts. When a lockup account receives rewards from staking, the system adds the reward amount to the lockup balance and recalculates the daily unlock rate over the remaining lockup period. This mechanism ensures that reward tokens remain subject to an account's existing lockup constraints while still providing incentive to secure the network through staking.
The default design of the lockup accounts in most Proof-of-Stake blockchains is that locked tokens can be staked and earn rewards, but these rewards are not subject to the lockup schedule. This has led to recipients of locked tokens being able to sell staking rewards independently of their unlock schedule, arguably in tension with the purpose of lockup accounts.
By integrating staking rewards directly into the lockup schedule, this proposal incentivizes locked tokens to secure the network while ensuring that staking rewards are locked. Summarizing the motivations:
- Consistency: Ensures that all tokens under a lockup account—whether from the initial locked amount or subsequently earned rewards—are subject to the same unlock schedule.
- Security: Prevents immediate liquidity from staking rewards without removing incentive to secure the network.
- Simplicity: Leverages existing modules (distribution, lockup, bank) with minimal disruption by adding hooks and type checks.
When staking rewards are distributed (e.g., via MsgWithdrawDelegatorReward
), the distribution module will first check if the receiving account is a lockup account.
- If it is:
- Verify that it is of a modifiable lockup type.
- Calculate the new lockup amount:
New Lockup Amount = Original Lockup Amount + Claimed Rewards
- Determine the new unlock rate by dividing the updated lockup amount by the remaining lockup period.
- Update the lockup schedule and the account balance accordingly.
- If it is not a lockup account:
- Process the reward as a normal reward transfer.
The CIP covers multiple paths where rewards might be integrated:
-
Direct Reward Claim: The user invokes a reward withdrawal message and the lockup schedule is updated.
-
Redelegation and Unbonding: Hooks in the staking module ensure rewards are claimed and added to the lockup schedule prior to processing redelegation or unbonding.
- Note: IF CIP-30 is accepted this path is no longer available.
-
Validator Commission: Commission rewards are split from staking rewards: commissions go directly to the account (unlocked) while staking rewards follow the lockup schedule.
-
Non Locked Coins Rewards Within the Cosmos SDK rewards can be a variety of tokens not only the staking token. Secondly a lockup account can have multiple tokens too. When UpdateSchedule is called, we will only append the tokens included in the vesting schedule. Example: If the rewards for a delegator are (
100 tia, 100foo
) and the lockup schedule only includestia
we will only lock up thetia
and allow thefoo
token to be freely transferred out of the account.
Tokens are tracked in four states:
- Locked & Unstaked
- Locked & Staked
- Unlocked & Unstaked
- Unlocked & Staked
Consider a lockup schedule initially granting 100 tokens over 365 days (unlocking ≈0.274 tokens/day). Assume 30% of the lockup period has passed (109.5 days), meaning 30 tokens have already unlocked, and 70 tokens remain locked.
If the account holder claims a staking reward of 0.657 tokens at this point:
- The reward is added to the original vesting amount:
New Total Original Amount = 100 + 0.657 = 100.657 tokens
. - The system recalculates the amount that should be unlocked after 30% of the period based on this new total:
Target Unlocked Amount = 0.30 * 100.657 = 30.1971 tokens
. - The difference between the target unlocked amount and the amount already unlocked is made available immediately:
Immediate Unlock = 30.1971 - 30 = 0.1971 tokens
. This represents 30% of the claimed reward (0.30 * 0.657). - The total amount now unlocked is 30.1971 tokens.
- The remaining locked amount is recalculated:
New Remaining Locked = 100.657 (New Total) - 30.1971 (New Unlocked) = 70.4599 tokens
. - This new remaining locked amount (70.4599 tokens) will unlock over the remaining 70% of the period (255.5 days).
- The daily unlock rate for the remainder of the period increases slightly:
New Daily Rate = 70.4599 / 255.5 ≈ 0.2758 tokens/day
.
A portion of the reward corresponding to the elapsed vesting duration (30% in this case) unlocks immediately, while the rest (70%) is added to the future vesting schedule.
For a DelayedVestingAccount
, the entire principal vests at a single point in the future (EndTime
).
Consider an account set up with 1000 tokens scheduled to unlock entirely after 730 days (2 years).
Assume at day 200, the account holder claims a staking reward of 50 tokens.
- The reward is added to the
OriginalVesting
amount tracked by the account:New Total Original Amount = 1000 + 50 = 1050 tokens
. - Unlike continuous vesting, no portion of this reward unlocks immediately, regardless of the time elapsed.
- The account continues to hold 0 unlocked tokens.
- When the
EndTime
(day 730) is reached, the entire 1050 tokens (original 1000 + 50 reward) unlock at once.
The claimed rewards simply increase the total balance that will become available at the predefined unlock date.
Periodic account creation will be removed, and update schedule will be a noop
Permanent account creation will be removed, and update schedule will be a noop
The following sequence summarizes the module interactions:
sequenceDiagram
participant DM as Distribution Module
participant AK as Account Keeper
participant LM as Lockup Module
participant BM as Bank Module
DM->>AK: Get Receiving Account
AK-->>DM: Return Account
rect rgb(0, 0, 139)
Note over DM: Account Type Check
DM->>DM: Is Lockup Account?
end
alt Is Lockup Account
DM->>LM: Get Lockup Type
LM->>LM: Update Lockup Amount
LM-->>DM: Return Success
else Not Lockup Account
DM->>BM: Process Normal Reward Transfer
end
BM-->>DM: Return Status
As part of this CIP we also propose capping the validator commission at 25%. A commission cap is required to avoid circumvention of this CIP by large token holders, who could spin up a new validator with 100% commission, where such commission would be fully unlocked.
The commission cap will also be applied to validators who were created prior to the upgrade but are unbonded. This prevents new validators from being created today in order to avoid the cap in the future.
Existing validators with greater than 25% commission will be allowed to keep their commission rate, but when modifying their commission rate they will be required to set it to 25% or lower.
When upgrading to v4 we propose introducing a migration that will set the Parameter in x/Staking
to 25% disallowing new validators from creating validators with >25% commission rates.
The continuous and delayed vesting account types will be updated to implement CIP-31. Specifically, they will implement the UpdateSchedule
function so that the original vesting amount can account for staking rewards. The periodic and permanent account types will not support CIP-31. They will be disallowed from being created.
The implementation should include test cases covering:
- Basic Reward Claim:
- Claim rewards multiple times and verify that the lockup schedule updates correctly.
- Validator Commission Path:
- Confirm that commission rewards are processed normally (non-locked) while staking rewards are added to the lockup schedule.
The VestingAccount
interface will be updated to account for an UpdateSchedule method which includes the amount to update the schedule with and the blocktime at which it occured. We must pass in the blocktime in order to understand if the account is fully vested or not.
// VestingAccount defines an account type that vests coins via a vesting schedule.
type VestingAccount interface {
sdk.AccountI
// LockedCoins returns the set of coins that are not spendable (i.e. locked),
// defined as the vesting coins that are not delegated.
//
// To get spendable coins of a vesting account, first the total balance must
// be retrieved and the locked tokens can be subtracted from the total balance.
// Note, the spendable balance can be negative.
LockedCoins(blockTime time.Time) sdk.Coins
// TrackDelegation performs internal vesting accounting necessary when
// delegating from a vesting account. It accepts the current block time, the
// delegation amount and balance of all coins whose denomination exists in
// the account's original vesting balance.
TrackDelegation(blockTime time.Time, balance, amount sdk.Coins)
// TrackUndelegation performs internal vesting accounting necessary when a
// vesting account performs an undelegation.
TrackUndelegation(amount sdk.Coins)
GetVestedCoins(blockTime time.Time) sdk.Coins
GetVestingCoins(blockTime time.Time) sdk.Coins
GetStartTime() int64
GetEndTime() int64
GetOriginalVesting() sdk.Coins
GetDelegatedFree() sdk.Coins
GetDelegatedVesting() sdk.Coins
+ // UpdateSchedule updates the original vesting amount for the account.
+ UpdateSchedule(blockTime time.Time, amount sdk.Coins) error
}
A reference implementation will be provided in the Celestia codebase under the distribution and lockup modules. It includes the modifications to the reward claim functions, the new lockup schedule recalculation logic, and updated tests demonstrating correct behavior for all outlined paths. The PR can be found at 01builders/cosmos-sdk#10.
-
Integrity of Lockup Schedules: The recalculation of lockup schedules must be atomic and resistant to race conditions. Any failure in updating the lockup schedule should not lead to inconsistencies in account balances.
-
Separation of Rewards: The design ensures that validator commissions are kept separate from staking rewards that enter lockup, preventing accidental over-lockup.
-
Error Handling: Adequate checks are in place for balance sufficiency, lockup period validity, and correct module interactions to prevent unexpected token inflation or loss.
Copyright and related rights waived via CC0.