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: CNS-daily restaking credit #1794

Merged
merged 26 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8c31c08
added credit and credit timestamp to delegations
omerlavanet Nov 25, 2024
8ba7e34
fix nil deref and edge cases where delegation is in the future
omerlavanet Nov 25, 2024
7c3f1a7
added delegation credit to rewards distribution
omerlavanet Nov 26, 2024
2b56061
fix delegate set to account for existing delegation
omerlavanet Nov 26, 2024
c2fc7bc
wip on tests
omerlavanet Nov 26, 2024
212c6c4
unitests wip
omerlavanet Nov 28, 2024
f986516
normalized provider credit too if he wasnt staked for a month, finish…
omerlavanet Nov 28, 2024
00dae9f
lint
omerlavanet Nov 28, 2024
3bee5ed
adding unitests
omerlavanet Nov 28, 2024
fe504f3
fix ctx
omerlavanet Nov 28, 2024
fa2c139
finished delegation set with credit unitests
omerlavanet Nov 28, 2024
610fe47
wip fixing tests
omerlavanet Nov 28, 2024
3b4d5b0
fix a test
omerlavanet Nov 28, 2024
71c8ae2
fix last test
omerlavanet Nov 28, 2024
5a2803e
add unitest - wip
omerlavanet Dec 1, 2024
88b0444
push unitests wip
omerlavanet Dec 2, 2024
8ad8b1a
finished delegation partial test
omerlavanet Dec 3, 2024
131ea6c
adapted new sub tests to trigger only on the new flow
omerlavanet Dec 3, 2024
b90718b
Merge remote-tracking branch 'origin/main' into CNS-daily-delegation-…
omerlavanet Dec 3, 2024
8f3ee81
fix date add bug using months instead of days
omerlavanet Dec 3, 2024
b7d4c6a
add migrator
omerlavanet Dec 4, 2024
cb0c408
Merge branch 'main' into CNS-daily-delegation-limit
omerlavanet Dec 4, 2024
c7127a6
PR comments
omerlavanet Dec 8, 2024
4e98ed4
Merge branch 'main' into CNS-daily-delegation-limit
omerlavanet Dec 8, 2024
e1084a6
Merge branch 'main' into CNS-daily-delegation-limit
omerlavanet Dec 8, 2024
0843e5f
Merge branch 'main' into CNS-daily-delegation-limit
Yaroms Dec 15, 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
4 changes: 3 additions & 1 deletion proto/lavanet/lava/dualstaking/delegate.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ message Delegation {
string provider = 1; // provider receives the delegated funds
string delegator = 3; // delegator that owns the delegated funds
cosmos.base.v1beta1.Coin amount = 4 [(gogoproto.nullable) = false];
int64 timestamp = 5; // Unix timestamp of the delegation (+ month)
int64 timestamp = 5; // Unix timestamp of the last change
cosmos.base.v1beta1.Coin credit = 6 [(gogoproto.nullable) = false]; // amount of credit earned by the delegation over the period
int64 credit_timestamp = 7; // Unix timestamp of the delegation credit latest calculation capped at 30d
}

message Delegator {
Expand Down
8 changes: 8 additions & 0 deletions testutil/common/tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,14 @@ func (ts *Tester) AdvanceMonthsFrom(from time.Time, months int) *Tester {
return ts
}

func (ts *Tester) AdvanceTimeHours(timeDelta time.Duration) *Tester {
endTime := ts.BlockTime().Add(timeDelta)
for ts.BlockTime().Before(endTime) {
ts.AdvanceBlock(time.Hour)
}
return ts
}

func (ts *Tester) BondDenom() string {
return ts.Keepers.StakingKeeper.BondDenom(sdk.UnwrapSDKContext(ts.Ctx))
}
Expand Down
5 changes: 3 additions & 2 deletions testutil/keeper/dualstaking.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"testing"
"time"

tmdb "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/libs/log"
Expand Down Expand Up @@ -64,15 +65,15 @@ func DualstakingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) {
memStoreKey,
paramsSubspace,
&mockBankKeeper{},
nil,
&mockStakingKeeperEmpty{},
&mockAccountKeeper{},
epochstorageKeeper,
speckeeper.NewKeeper(cdc, nil, nil, paramsSubspaceSpec, nil),
fixationkeeper.NewKeeper(cdc, tsKeeper, epochstorageKeeper.BlocksToSaveRaw),
)

ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger())

ctx = ctx.WithBlockTime(time.Now().UTC())
// Initialize params
k.SetParams(ctx, types.DefaultParams())

Expand Down
56 changes: 56 additions & 0 deletions testutil/keeper/mock_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"fmt"
"time"

"cosmossdk.io/math"
tenderminttypes "github.com/cometbft/cometbft/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

// account keeper mock
Expand Down Expand Up @@ -36,6 +38,60 @@ func (k mockAccountKeeper) SetModuleAccount(sdk.Context, authtypes.ModuleAccount
// mock bank keeper
var balance map[string]sdk.Coins = make(map[string]sdk.Coins)

type mockStakingKeeperEmpty struct{}

func (k mockStakingKeeperEmpty) ValidatorByConsAddr(sdk.Context, sdk.ConsAddress) stakingtypes.ValidatorI {
return nil
}

func (k mockStakingKeeperEmpty) UnbondingTime(ctx sdk.Context) time.Duration {
return time.Duration(0)
}

func (k mockStakingKeeperEmpty) GetAllDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress) []stakingtypes.Delegation {
return nil
}

func (k mockStakingKeeperEmpty) GetDelegatorValidator(ctx sdk.Context, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) (validator stakingtypes.Validator, err error) {
return stakingtypes.Validator{}, nil
}

func (k mockStakingKeeperEmpty) GetDelegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (delegation stakingtypes.Delegation, found bool) {
return stakingtypes.Delegation{}, false
}

func (k mockStakingKeeperEmpty) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool) {
return stakingtypes.Validator{}, false
}

func (k mockStakingKeeperEmpty) GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) (delegations []stakingtypes.Delegation) {
return []stakingtypes.Delegation{}
}

func (k mockStakingKeeperEmpty) BondDenom(ctx sdk.Context) string {
return "ulava"
}

func (k mockStakingKeeperEmpty) ValidateUnbondAmount(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt math.Int) (shares sdk.Dec, err error) {
return sdk.Dec{}, nil
}

func (k mockStakingKeeperEmpty) Undelegate(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) (time.Time, error) {
return time.Time{}, nil
}

func (k mockStakingKeeperEmpty) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt math.Int, tokenSrc stakingtypes.BondStatus, validator stakingtypes.Validator, subtractAccount bool) (newShares sdk.Dec, err error) {
return sdk.Dec{}, nil
}

func (k mockStakingKeeperEmpty) GetBondedValidatorsByPower(ctx sdk.Context) []stakingtypes.Validator {
return []stakingtypes.Validator{}
}

func (k mockStakingKeeperEmpty) GetAllValidators(ctx sdk.Context) (validators []stakingtypes.Validator) {
return []stakingtypes.Validator{}
}

type mockBankKeeper struct{}

func init_balance() {
Expand Down
53 changes: 36 additions & 17 deletions x/dualstaking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ This document specifies the dualstaking module of Lava Protocol.

In the Lava blockchain there are two kinds of staking users, the first ones are validators, legacy to cosmos, the second ones are providers.
Validators play a role in the consensus mechanism, while providers offer services to consumers and compete with other providers by staking tokens.
Since a lot of tokens are expected to be staked by providers, to enhance the security of the chain, Lava lets providers to participate in the consensus via dualstaking.
Since a lot of tokens are expected to be staked by providers, to enhance the security of the chain, Lava lets providers to participate in the consensus via dualstaking.
Dualstaking makes this happen by "duplicating" delegations, for each validator delegation a parallel provider delegation will be created for the delegator, As a result, providers gain power in the consensus, influencing governance and block creation.


## Contents

* [Concepts](#concepts)
* [Delegation](#delegation)
* [Empty Provider](#empty-provider)
* [Dualstaking](#dualstaking)
* [Validator Delegation](#validator-delegation)
* [Validator Unbonding](#validator-unbonding)
* [Validator Slashing](#validator-slashing)
* [Provider Delegation](#provider-delegation)
* [Provider Unbonding](#provider-unbonding)
* [Hooks](#hooks)
* [RedelegateFlag](#redelegateflag)
* [Rewards](#rewards)
* [Delegation](#delegation)
* [Empty Provider](#empty-provider)
* [Dualstaking](#dualstaking)
* [Validator Delegation](#validator-delegation)
* [Validator Unbonding](#validator-unbonding)
* [Validator Slashing](#validator-slashing)
* [Provider Delegation](#provider-delegation)
* [Provider Unbonding](#provider-unbonding)
* [Hooks](#hooks)
* [RedelegateFlag](#redelegateflag)
* [Credit](#credit)
* [Parameters](#parameters)
* [Queries](#queries)
* [Transactions](#transactions)
Expand All @@ -38,7 +38,7 @@ When a provider stakes tokens, they create a self-delegation entry. Whenever a p

### Empty Provider

The empty provider is a place holder for provider delegations that are issued by the staking module.
The empty provider is a place holder for provider delegations that are issued by the staking module.
To support the functionality of the legacy Staking module, when a user delegates to a validator (it can't define the provider to delegate to in the legacy message), the dual staking module will delegate the same ammount to the empty provider.
The user can than choose to redelegate from the empty provider to an actual provider.

Expand Down Expand Up @@ -84,16 +84,35 @@ The following are use cases of the dualstaking module:
### Hooks

Dual staking module uses [staking hooks](keeper/hooks.go) to achieve its functionality.

1. AfterDelegationModified: this hook is called whenever a delegation is changed, whether it is created, or modified (NOT when completly removed). it calculates the difference in providers and validators stake to determine the action of the user (delegation or unbonding) depending on who is higher and than does the same with provider delegation.
* If provider delegations > validator delegations: user unbonded, uniform unbond from providers delegations (priority to empty provider).
* If provider delegations < validator delegations: user delegation, delegate to the empty provider.
2. BeforeDelegationRemoved: this hook is called when a delegation to a validator is removed (unbonding of all the tokens). uniform unbond from providers delegations
3. BeforeValidatorSlashed: this hook is called when a validator is being slashed. to make sure the balance between validator and provider delegation is kept it uniform unbond from providers delegations the slashed amount.
3. BeforeValidatorSlashed: this hook is called when a validator is being slashed. to make sure the balance between validator and provider delegation is kept it uniform unbond from providers delegations the slashed amount.

### RedelegateFlag

To prevent the dual staking module from taking action in the case of validator redelegation, we utilize the [antehandler](ante/ante_handler.go). When a redelegation message is being processed, the RedelegateFlag is set to true, and the hooks will disregard any delegation changes. It is important to note that the RedelegateFlag is stored in memory and not in the chain’s state.

### Credit

Credit Mechanism Overview
The credit mechanism ensures fair reward distribution to delegators based on both the amount and duration of their delegation. It calculates rewards proportionally to the effective stake over time.

Key Components

Credit: Effective delegation for a delegator, adjusted for staking duration.
CreditTimestamp: Last update time for the credit, enabling accurate reward calculations.
How It Works

Credit is calculated when a delegation is made or modified, based on the current amount and elapsed time.
Rewards are normalized over a 30-day period for consistency.
Example
Alice delegates 100 tokens for a full month, earning a credit of 100 tokens. Bob delegates 200 tokens for half a month, also earning a credit of 100 tokens. With a total reward pool of 500 tokens, both receive 250 tokens, reflecting their credit-adjusted stakes.

If Alice increases her delegation to 150 tokens mid-month, her credit is updated to reflect rewards earned so far, and future rewards are calculated on the new amount. This ensures fair distribution based on both delegation amount and duration.

## Parameters

The dualstaking parameters:
Expand Down Expand Up @@ -128,19 +147,19 @@ The Dualstaking module supports the following transactions:
| `unbond` | validator-addr (string) provider-addr (string) amount (coin) | undong from validator and provider the given amount |
| `claim-rewards` | optional: provider-addr (string)| claim the rewards from a given provider or all rewards |


## Proposals

The Dualstaking module does not have proposals.

### Events

The Dualstaking module has the following events:

| Event | When it happens |
| ---------- | --------------- |
| `delegate_to_provider` | a successful provider delegation |
| `unbond_from_provider` | a successful provider delegation unbond |
| `redelegate_between_providers` | a successful provider redelegation|
| `delegator_claim_rewards` | a successful provider delegator reward claim|
| `contributor_rewards` | spec contributor got new rewards|
| `validator_slash` | validator slashed happened, providers slashed accordingly|
| `validator_slash` | validator slashed happened, providers slashed accordingly|
17 changes: 13 additions & 4 deletions x/dualstaking/keeper/delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,14 @@ import (
// and updates the (epochstorage) stake-entry.
func (k Keeper) increaseDelegation(ctx sdk.Context, delegator, provider string, amount sdk.Coin, stake bool) error {
// get, update the delegation entry
delegation, err := k.delegations.Get(ctx, types.DelegationKey(provider, delegator))
if err != nil {
delegation, found := k.GetDelegation(ctx, provider, delegator)
if !found {
// new delegation (i.e. not increase of existing one)
delegation = types.NewDelegation(delegator, provider, ctx.BlockTime(), k.stakingKeeper.BondDenom(ctx))
}

delegation.AddAmount(amount)

err = k.delegations.Set(ctx, types.DelegationKey(provider, delegator), delegation)
err := k.SetDelegation(ctx, delegation)
if err != nil {
return err
}
Expand Down Expand Up @@ -364,7 +363,17 @@ func (k Keeper) GetAllDelegations(ctx sdk.Context) ([]types.Delegation, error) {
return iter.Values()
}

// this function overwrites the time tag with the ctx time upon writing the delegation
func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) error {
delegation.Timestamp = ctx.BlockTime().UTC().Unix()
existingDelegation, found := k.GetDelegation(ctx, delegation.Provider, delegation.Delegator)
if !found {
return k.delegations.Set(ctx, types.DelegationKey(delegation.Provider, delegation.Delegator), delegation)
}
// calculate credit based on the existing delegation before changes
credit, creditTimestamp := k.CalculateCredit(ctx, existingDelegation)
delegation.Credit = credit
delegation.CreditTimestamp = creditTimestamp
return k.delegations.Set(ctx, types.DelegationKey(delegation.Provider, delegation.Delegator), delegation)
}

Expand Down
Loading
Loading