Skip to content

Commit

Permalink
added delegation credit to rewards distribution
Browse files Browse the repository at this point in the history
  • Loading branch information
omerlavanet committed Nov 26, 2024
1 parent 8ba7e34 commit 7c3f1a7
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 30 deletions.
3 changes: 2 additions & 1 deletion 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 @@ -72,7 +73,7 @@ func DualstakingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) {
)

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

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

Expand Down
65 changes: 48 additions & 17 deletions x/dualstaking/keeper/delegate_credit.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,58 @@ package keeper
// tracking the list of providers for a delegator, indexed by the delegator.

import (
"fmt"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/lavanet/lava/v4/x/dualstaking/types"
)

const monthHours = 720 // 30 days * 24 hours

// calculate the delegation credit based on the timestamps, and the amounts of delegations
// amounts and credits represent daily value, rounded down
// can be used to calculate the credit for distribution or update the credit fields in the delegation
func (k Keeper) CalculateCredit(ctx sdk.Context, delegation types.Delegation) (credit sdk.Coin, creditTimestampRet int64) {
// Calculate the credit for the delegation
currentAmount := delegation.Amount
creditAmount := delegation.Credit
// handle uninitialized amounts
if creditAmount.IsNil() {
creditAmount = sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), sdk.ZeroInt())
}
if currentAmount.IsNil() {
// this should never happen, but we handle it just in case
currentAmount = sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), sdk.ZeroInt())
}
currentTimestamp := ctx.BlockTime().UTC()
delegationTimestap := time.Unix(delegation.Timestamp, 0)
delegationTimestamp := time.Unix(delegation.Timestamp, 0)
creditTimestamp := time.Unix(delegation.CreditTimestamp, 0)
// we normalize dates before we start the calculation
// maximum scope is 30 days, we start with the delegation truncation then the credit
monthAgo := currentTimestamp.AddDate(0, 0, -30)
if monthAgo.Unix() > delegationTimestap.Unix() {
delegationTimestap = monthAgo
// set no credit if the delegation is older than 30 days
if monthAgo.Unix() >= delegationTimestamp.Unix() {
// in the case the delegation wasn't changed for 30 days or more we truncate the timestamp to 30 days ago
// and disable the credit for older dates since they are irrelevant
delegationTimestamp = monthAgo
creditTimestamp = delegationTimestamp
creditAmount = sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), sdk.ZeroInt())
} else if monthAgo.Unix() > creditTimestamp.Unix() {
// delegation is less than 30 days, but credit might be older, so truncate it to 30 days
creditTimestamp = monthAgo
}

creditDelta := int64(0) // hours
if !creditAmount.IsNil() && !creditAmount.IsZero() {
if creditTimestamp.Before(delegationTimestap) {
creditDelta = int64(delegationTimestap.Sub(creditTimestamp).Hours())
}
} else {
// handle uninitialized credit
creditAmount = sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), sdk.ZeroInt())
if delegation.CreditTimestamp == 0 || creditAmount.IsZero() {
// in case credit was never set, we set it to the delegation timestamp
creditTimestamp = delegationTimestamp
} else if creditTimestamp.Before(delegationTimestamp) {
// calculate the credit delta in hours
creditDelta = int64(delegationTimestamp.Sub(creditTimestamp).Hours())
}

amountDelta := int64(0) // hours
if !currentAmount.IsZero() {
if delegationTimestap.Before(currentTimestamp) {
amountDelta = int64(currentTimestamp.Sub(delegationTimestap).Hours())
}
if !currentAmount.IsZero() && delegationTimestamp.Before(currentTimestamp) {
amountDelta = int64(currentTimestamp.Sub(delegationTimestamp).Hours())
}

// creditDelta is the weight of the history and amountDelta is the weight of the current amount
Expand All @@ -67,7 +78,27 @@ func (k Keeper) CalculateCredit(ctx sdk.Context, delegation types.Delegation) (c
if totalDelta == 0 {
return sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), sdk.ZeroInt()), currentTimestamp.Unix()
}
fmt.Println("creditDelta", creditDelta, "amountDelta", amountDelta, "totalDelta", totalDelta, "creditAmount", creditAmount.Amount, "currentAmount", currentAmount.Amount)
credit = sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), currentAmount.Amount.MulRaw(amountDelta).Add(creditAmount.Amount.MulRaw(creditDelta)).QuoRaw(totalDelta))
return credit, creditTimestamp.Unix()
}

// this function takes the delegation and returns it's credit within the last 30 days
func (k Keeper) CalculateMonthlyCredit(ctx sdk.Context, delegation types.Delegation) (credit sdk.Coin) {
credit, creditTimeEpoch := k.CalculateCredit(ctx, delegation)
if credit.IsNil() || credit.IsZero() || creditTimeEpoch <= 0 {
return sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), sdk.ZeroInt())
}
creditTimestamp := time.Unix(creditTimeEpoch, 0)
timeStampDiff := ctx.BlockTime().UTC().Sub(creditTimestamp).Hours()
if timeStampDiff <= 0 {
// no positive credit
return sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), sdk.ZeroInt())
}
// make sure we never increase the credit
if timeStampDiff > monthHours {
timeStampDiff = monthHours
}
// normalize credit to 30 days
credit.Amount = credit.Amount.MulRaw(int64(timeStampDiff)).QuoRaw(monthHours)
return credit
}
118 changes: 118 additions & 0 deletions x/dualstaking/keeper/delegate_credit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package keeper_test

import (
"testing"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
keepertest "github.com/lavanet/lava/v4/testutil/keeper"
commontypes "github.com/lavanet/lava/v4/utils/common/types"
"github.com/lavanet/lava/v4/x/dualstaking/keeper"
"github.com/lavanet/lava/v4/x/dualstaking/types"
"github.com/stretchr/testify/require"
)

func SetDelegationMock(k keeper.Keeper, ctx sdk.Context, delegation types.Delegation) (delegationRet types.Delegation) {
credit, creditTimestamp := k.CalculateCredit(ctx, delegation)
delegation.Credit = credit
delegation.CreditTimestamp = creditTimestamp
return delegation
}

func TestCalculateCredit(t *testing.T) {
k, ctx := keepertest.DualstakingKeeper(t)
bondDenom := commontypes.TokenDenom
timeNow := ctx.BlockTime()
tests := []struct {
name string
delegation types.Delegation
expectedCredit sdk.Coin
}{
{
name: "initial delegation",
delegation: types.Delegation{
Amount: sdk.NewCoin(bondDenom, sdk.NewInt(1000)),
Credit: sdk.NewCoin(bondDenom, sdk.ZeroInt()),
Timestamp: timeNow.Add(-time.Hour * 24 * 10).Unix(),
CreditTimestamp: 0,
},
expectedCredit: sdk.NewCoin(bondDenom, sdk.NewInt(1000)),
},
{
name: "delegation with existing credit",
delegation: types.Delegation{
Amount: sdk.NewCoin(bondDenom, sdk.NewInt(2000)),
Credit: sdk.NewCoin(bondDenom, sdk.NewInt(1000)),
Timestamp: timeNow.Add(-time.Hour * 24 * 5).Unix(),
CreditTimestamp: timeNow.Add(-time.Hour * 24 * 10).Unix(),
},
expectedCredit: sdk.NewCoin(bondDenom, sdk.NewInt(1500)),
},
{
name: "delegation older than 30 days",
delegation: types.Delegation{
Amount: sdk.NewCoin(bondDenom, sdk.NewInt(3000)),
Credit: sdk.NewCoin(bondDenom, sdk.NewInt(1500)),
Timestamp: timeNow.Add(-time.Hour * 24 * 40).Unix(),
CreditTimestamp: timeNow.Add(-time.Hour * 24 * 35).Unix(),
},
expectedCredit: sdk.NewCoin(bondDenom, sdk.NewInt(3000)),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
credit, _ := k.CalculateCredit(ctx, tt.delegation)
require.Equal(t, tt.expectedCredit, credit)
})
}
}

func TestCalculateMonthlyCredit(t *testing.T) {
k, ctx := keepertest.DualstakingKeeper(t)
bondDenom := commontypes.TokenDenom
timeNow := ctx.BlockTime()
tests := []struct {
name string
delegation types.Delegation
expectedCredit sdk.Coin
}{
{
name: "initial delegation",
delegation: types.Delegation{
Amount: sdk.NewCoin(bondDenom, sdk.NewInt(1000)),
Credit: sdk.NewCoin(bondDenom, sdk.ZeroInt()),
Timestamp: timeNow.Add(-time.Hour * 24 * 10).Unix(),
CreditTimestamp: 0,
},
expectedCredit: sdk.NewCoin(bondDenom, sdk.NewInt(1000)),
},
{
name: "delegation with existing credit",
delegation: types.Delegation{
Amount: sdk.NewCoin(bondDenom, sdk.NewInt(2000)),
Credit: sdk.NewCoin(bondDenom, sdk.NewInt(1000)),
Timestamp: timeNow.Add(-time.Hour * 24 * 5).Unix(),
CreditTimestamp: timeNow.Add(-time.Hour * 24 * 10).Unix(),
},
expectedCredit: sdk.NewCoin(bondDenom, sdk.NewInt(1500)),
},
{
name: "delegation older than 30 days",
delegation: types.Delegation{
Amount: sdk.NewCoin(bondDenom, sdk.NewInt(3000)),
Credit: sdk.NewCoin(bondDenom, sdk.NewInt(1500)),
Timestamp: timeNow.Add(-time.Hour * 24 * 40).Unix(),
CreditTimestamp: timeNow.Add(-time.Hour * 24 * 35).Unix(),
},
expectedCredit: sdk.NewCoin(bondDenom, sdk.NewInt(3000)),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
credit := k.CalculateMonthlyCredit(ctx, tt.delegation)
require.Equal(t, tt.expectedCredit, credit)
})
}
}
19 changes: 11 additions & 8 deletions x/dualstaking/keeper/delegator_reward.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (k Keeper) GetAllDelegatorReward(ctx sdk.Context) (list []types.DelegatorRe
// CalcRewards calculates the provider reward and the total reward for delegators
// providerReward = totalReward * ((effectiveDelegations*commission + providerStake) / effectiveStake)
// delegatorsReward = totalReward - providerReward
func (k Keeper) CalcRewards(ctx sdk.Context, totalReward sdk.Coins, totalDelegations math.Int, selfDelegation types.Delegation, delegations []types.Delegation, commission uint64) (providerReward sdk.Coins, delegatorsReward sdk.Coins) {
func (k Keeper) CalcRewards(ctx sdk.Context, totalReward sdk.Coins, totalDelegations math.Int, selfDelegation types.Delegation, commission uint64) (providerReward sdk.Coins, delegatorsReward sdk.Coins) {
zeroCoins := sdk.NewCoins()
totalDelegationsWithSelf := totalDelegations.Add(selfDelegation.Amount.Amount)

Expand Down Expand Up @@ -175,16 +175,19 @@ func (k Keeper) RewardProvidersAndDelegators(ctx sdk.Context, provider string, c
totalDelegations := sdk.ZeroInt()
var selfdelegation types.Delegation
// fetch relevant delegations (those who are passed the first week of delegation), self delegation and sum the total delegations
for _, d := range delegations {
if d.Delegator == metadata.Vault {
selfdelegation = d
} else if d.IsFirstWeekPassed(ctx.BlockTime().UTC().Unix()) {
relevantDelegations = append(relevantDelegations, d)
totalDelegations = totalDelegations.Add(d.Amount.Amount)
for _, delegation := range delegations {
if delegation.Delegator == metadata.Vault {
selfdelegation = delegation
} else {
credit := k.CalculateMonthlyCredit(ctx, delegation)
// modify the delegation for reward calculation based on the time it was staked
delegation.Amount = credit
relevantDelegations = append(relevantDelegations, delegation)
totalDelegations = totalDelegations.Add(delegation.Amount.Amount)
}
}

providerReward, delegatorsReward := k.CalcRewards(ctx, totalReward.Sub(contributorReward...), totalDelegations, selfdelegation, relevantDelegations, metadata.DelegateCommission)
providerReward, delegatorsReward := k.CalcRewards(ctx, totalReward.Sub(contributorReward...), totalDelegations, selfdelegation, metadata.DelegateCommission)

leftoverRewards := k.updateDelegatorsReward(ctx, totalDelegations, relevantDelegations, delegatorsReward, senderModule, calcOnlyDelegators)
fullProviderReward := providerReward.Add(leftoverRewards...)
Expand Down
2 changes: 1 addition & 1 deletion x/dualstaking/types/delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func NewDelegation(delegator, provider string, blockTime time.Time, tokenDenom s
Delegator: delegator,
Provider: provider,
Amount: sdk.NewCoin(tokenDenom, sdk.ZeroInt()),
Timestamp: blockTime.AddDate(0, 0, 7).UTC().Unix(),
Timestamp: blockTime.UTC().Unix(),
}
}

Expand Down
4 changes: 1 addition & 3 deletions x/pairing/keeper/delegator_rewards_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,19 +209,17 @@ func TestProviderRewardWithCommission(t *testing.T) {
currentTimestamp := ts.Ctx.BlockTime().UTC().Unix()
totalReward := sdk.NewCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(int64(relayCuSum))))

relevantDelegations := []dualstakingtypes.Delegation{}
totalDelegations := sdk.ZeroInt()
var selfdelegation dualstakingtypes.Delegation
for _, d := range res.Delegations {
if d.Delegator != stakeEntry.Vault {
selfdelegation = d
} else if d.IsFirstWeekPassed(currentTimestamp) {
relevantDelegations = append(relevantDelegations, d)
totalDelegations = totalDelegations.Add(d.Amount.Amount)
}
}

providerReward, _ := ts.Keepers.Dualstaking.CalcRewards(ts.Ctx, totalReward, totalDelegations, selfdelegation, relevantDelegations, stakeEntry.DelegateCommission)
providerReward, _ := ts.Keepers.Dualstaking.CalcRewards(ts.Ctx, totalReward, totalDelegations, selfdelegation, stakeEntry.DelegateCommission)

require.True(t, totalReward.IsEqual(providerReward))

Expand Down

0 comments on commit 7c3f1a7

Please sign in to comment.