Skip to content

CIP-31: updates according to the implemention #290

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

Merged
merged 5 commits into from
Apr 8, 2025
Merged
Changes from all commits
Commits
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
189 changes: 127 additions & 62 deletions cips/cip-031.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

| cip | 31 |
|----------------|-------------------------------------------------------------------------------------------------------------------------|
| title | Incorporate staking rewards into vesting account schedules |
Expand All @@ -17,7 +16,7 @@

*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 accounts existing lockup constraints while still providing incentive to secure the network through staking.
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.

## Motivation

Expand All @@ -33,115 +32,181 @@ By integrating staking rewards directly into the lockup schedule, this proposal

### Core Mechanism

1. **Account Type Verification:**
#### Account Type Verification

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:
- **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.
- 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.

#### Reward Claim Paths

2. **Reward Claim Paths:**
The CIP covers multiple paths where rewards might be integrated:

- **Direct Reward Claim:**
- **Direct Reward Claim:**
The user invokes a reward withdrawal message and the lockup schedule is updated.

- **Redelegation and Unbonding:**
- **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](https://github.com/celestiaorg/CIPs/pull/251) is accepted this path is no longer available.*
- *Note: IF [CIP-30](https://github.com/celestiaorg/CIPs/pull/251) is accepted this path is no longer available.*

- **Validator Commission:**
- **Validator Commission:**
Commission rewards are split from staking rewards: commissions go directly to the account (unlocked) while staking rewards follow the lockup schedule.

3. **Token State Management:**
#### Token State Management

Tokens are tracked in four states:

- Locked & Unstaked
- Locked & Staked
- Unlocked & Unstaked
- Unlocked & Staked
- Locked & Unstaked
- Locked & Staked
- Unlocked & Unstaked
- Unlocked & Staked

4. **Mathematical Model & Example:**
For a lockup schedule of 100 tokens over 365 days (≈0.274 tokens/day), once 30% of the tokens have unlocked, claiming a reward of about 0.657 tokens immediately unlocks 30% (≈0.197 tokens) while the remaining 70% (≈0.460 tokens) is added to the locked balance. This brings the locked amount to roughly 70.460 tokens, which will continue unlocking over the remaining 70% of the period (approximately 255.5 days), yielding an updated daily unlock rate of about 0.276 tokens/day.
#### Example Implementations

*NOTE: reward calculation is arbitrary, it is not based on Celestia's inflation.*
##### Continuous Vesting Accounts

5. **Error Handling:**
- Ensure sufficient balance checks before processing rewards.
- Validate that the lockup period is still active.
- Separate commission transfers from reward lockup updates.
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.

### Module Interaction
If the account holder claims a staking reward of 0.657 tokens at this point:

The following sequence summarizes the module interactions:
1. The reward is added to the *original* vesting amount: `New Total Original Amount = 100 + 0.657 = 100.657 tokens`.
2. 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`.
3. 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).
4. The total amount now unlocked is 30.1971 tokens.
5. The remaining locked amount is recalculated: `New Remaining Locked = 100.657 (New Total) - 30.1971 (New Unlocked) = 70.4599 tokens`.
6. This new remaining locked amount (70.4599 tokens) will unlock over the remaining 70% of the period (255.5 days).
7. The daily unlock rate for the remainder of the period increases slightly: `New Daily Rate = 70.4599 / 255.5 ≈ 0.2758 tokens/day`.

```mermaid
sequenceDiagram
participant DM as Distribution Module
participant AK as Account Keeper
participant LM as Lockup Module
participant BM as Bank Module
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.

DM->>AK: Get Receiving Account
AK-->>DM: Return Account
##### Delayed Vesting Accounts

rect rgb(0, 0, 139)
Note over DM: Account Type Check
DM->>DM: Is Lockup Account?
end
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.

alt Is Lockup Account
DM->>LM: Get Lockup Type
DM->>LM: Calculate New Lockup Schedule
LM->>LM: Update Lockup Parameters
LM->>BM: Update Account Balance
LM-->>DM: Return Success
else Not Lockup Account
DM->>BM: Process Normal Reward Transfer
end
1. The reward is added to the `OriginalVesting` amount tracked by the account: `New Total Original Amount = 1000 + 50 = 1050 tokens`.
2. Unlike continuous vesting, no portion of this reward unlocks immediately, regardless of the time elapsed.
3. The account continues to hold 0 unlocked tokens.
4. When the `EndTime` (day 730) is reached, the *entire* 1050 tokens (original 1000 + 50 reward) unlock at once.

BM-->>DM: Return Status
The claimed rewards simply increase the total balance that will become available at the predefined unlock date.

##### Periodic Vesting Accounts

```
Periodic account creation will be removed, and update schedule will be a noop

##### Permanent Vesting Accounts

Permanent account creation will be removed, and update schedule will be a noop

#### Module Interaction

The following sequence summarizes the module interactions:

```mermaid
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
```

### Commission Cap

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.

# Parameters
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.

## Parameters

When upgrading to v4 we propose introducing a migration that will set the Parameter in [`x/Staking`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.50.x/x/staking/types/staking.pb.go#L934) to 25% disallowing new validators from creating validators with \>25% commission rates.

# Backwards Compatibility
## Backwards Compatibility

All existing lockup accounts will be subject to the locking system. This change will take effect in v4, since this is a larger upgrade there is not a need for backwards compatible systems.
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.

# Test Cases
## Test Cases

The implementation should include test cases covering:

- **Basic Reward Claim:**
- Claim rewards multiple times and verify that the lockup schedule updates correctly.
- **Redelegation with Rewards:**
- Ensure the hook triggers before redelegation and that rewards are claimed and added to lockup.
- **Unbonding with Rewards:**
- Verify that the lockup schedule is updated correctly prior to unbonding.
- **Validator Commission Path:**
- Confirm that commission rewards are processed normally (non-locked) while staking rewards are added to the lockup schedule.

# Reference Implementation
## Reference Implementation

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.

```diff
// 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 <https://github.com/01builders/cosmos-sdk/pull/10>.

# Security Considerations
## Security Considerations

- **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.
Expand Down