Skip to content

Commit

Permalink
trigger distribution on slash event, calculate stakers portion by onl…
Browse files Browse the repository at this point in the history
…y dogfood powers
  • Loading branch information
leonz789 committed Dec 6, 2024
1 parent 00e4684 commit 21bab9d
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 29 deletions.
1 change: 1 addition & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,7 @@ func NewExocoreApp(

// set the hooks at the end, after all modules are instantiated.
(&app.OperatorKeeper).SetHooks(
app.DistrKeeper.OperatorHooks(),
app.StakingKeeper.OperatorHooks(),

Check failure on line 781 in app/app.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

too many arguments in call to (&app.OperatorKeeper).SetHooks

Check failure on line 781 in app/app.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

too many arguments in call to (&app.OperatorKeeper).SetHooks

Check failure on line 781 in app/app.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

too many arguments in call to (&app.OperatorKeeper).SetHooks

Check failure on line 781 in app/app.go

View workflow job for this annotation

GitHub Actions / test-unit-cover

too many arguments in call to (&app.OperatorKeeper).SetHooks

Check failure on line 781 in app/app.go

View workflow job for this annotation

GitHub Actions / test-unit-cover

too many arguments in call to (&app.OperatorKeeper).SetHooks

Check failure on line 781 in app/app.go

View workflow job for this annotation

GitHub Actions / test-unit-cover

too many arguments in call to (&app.OperatorKeeper).SetHooks

Check failure on line 781 in app/app.go

View workflow job for this annotation

GitHub Actions / test-unit-cover

too many arguments in call to (&app.OperatorKeeper).SetHooks

Check failure on line 781 in app/app.go

View workflow job for this annotation

GitHub Actions / test-unit-cover

too many arguments in call to (&app.OperatorKeeper).SetHooks

Check failure on line 781 in app/app.go

View workflow job for this annotation

GitHub Actions / build

too many arguments in call to (&app.OperatorKeeper).SetHooks
)

Expand Down
78 changes: 53 additions & 25 deletions x/feedistribution/keeper/allocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,57 @@ import (
"sort"

"cosmossdk.io/math"
sdkmath "cosmossdk.io/math"
avstypes "github.com/ExocoreNetwork/exocore/x/avs/types"
"github.com/ExocoreNetwork/exocore/x/feedistribution/types"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

// Based on the epoch, AllocateTokens performs reward and fee distribution to all validators.
func (k Keeper) AllocateTokens(ctx sdk.Context, totalPreviousPower int64) error {
// AllocateTokens performs reward and fee distribution to all validators.
// 1. afterSlash distributed accumlated fees till now in current epoch and the portion of minted coins
// corresponding to the time passed in current epoch
// 2. afterEpoch distributes all left coins including both fees and minted coins
//
// CONTRACT: before we adopt f1 like mechnisam to deal with precisely distribution,
// we need to set the epochIdentify the same to both dogfood and exomint
func (k Keeper) AllocateTokens(ctx sdk.Context, isSlash bool) error {
logger := k.Logger()
feeCollector := k.authKeeper.GetModuleAccount(ctx, k.feeCollectorName)
feesCollectedInt := k.bankKeeper.GetAllBalances(ctx, feeCollector.GetAddress())
feesCollected := sdk.NewDecCoinsFromCoins(feesCollectedInt...)
// if this is triggered by slash instead of epochEnd, we need to calculated the amount of minted coins
// corresponding to passed time of current epoch
if isSlash {
mintParams := k.mintKeeper.GetParams(ctx)
mintedCoin := sdk.NewCoin(
mintParams.MintDenom, mintParams.EpochReward,
)

mintedCoinDec := sdk.NewDecCoinFromCoin(mintedCoin)
// we only distribute fees (excluding minted coins) from current epoch
// if the minted coins of current epoch had been allocated, we calculate the corresponding portion of minted tokens
if feesCollectedInt.AmountOf(mintParams.MintDenom).GTE(mintParams.EpochReward) {
epochInfo, found := k.epochsKeeper.GetEpochInfo(ctx, mintParams.EpochIdentifier)
if !found {
// skip the calculation and distribute no minted coins out, the remaining will be handled at the end of the epoch
feesCollected.Sub(sdk.DecCoins{mintedCoinDec})
logger.Error("Failed to find epoch info")
} else {
passedDuration := sdkmath.LegacyNewDec(int64(ctx.BlockTime().Sub(epochInfo.StartTime)))
epochDuration := sdkmath.LegacyNewDec(int64(epochInfo.Duration))
mintedCoinDec.Amount.MulMut(sdkmath.LegacyOneDec().Sub(passedDuration.QuoTruncate(epochDuration)))
feesCollected.Sub(sdk.DecCoins{mintedCoinDec})
}
}
}

// transfer collected fees to the distribution module account
// transfer collected fees including minted coins to the distribution module account
if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName, types.ModuleName, feesCollectedInt); err != nil {
return err
}

totalPreviousPower := k.StakingKeeper.GetLastTotalPower(ctx).Int64()
feePool := k.GetFeePool(ctx)
if totalPreviousPower == 0 {
feePool.CommunityPool = feePool.CommunityPool.Add(feesCollected...)
Expand Down Expand Up @@ -114,33 +147,28 @@ func (k Keeper) AllocateTokensToValidator(ctx sdk.Context, val stakingtypes.Vali
func (k Keeper) AllocateTokensToStakers(ctx sdk.Context, operatorAddress sdk.AccAddress, rewardToAllStakers sdk.DecCoins, feePool *types.FeePool) {
logger := k.Logger()
logger.Info("AllocateTokensToStakers", "operatorAddress", operatorAddress.String())
avsList, err := k.StakingKeeper.GetOptedInAVSForOperator(ctx, operatorAddress.String())
if err != nil {
logger.Debug("avs address lists not found; skipping")
return
}
stakersPowerMap, curTotalStakersPowers := make(map[string]math.LegacyDec), math.LegacyNewDec(0)
globalStakerAddressList := make([]string, 0)
for _, avsAddress := range avsList {
avsAssets, err := k.StakingKeeper.GetAVSSupportedAssets(ctx, avsAddress)
isAvs, avsAddress := k.avsKeeper.IsAVSByChainID(ctx, ctx.ChainID())
if !isAvs {
logger.Error("Skipping distribution for due to fail to generate avsAddr from chainID", "chainID", ctx.ChainID())
return
}

assetIDs := k.StakingKeeper.GetAssetIDs(ctx)
for _, assetID := range assetIDs {
stakerList, err := k.StakingKeeper.GetStakersByOperator(ctx, operatorAddress.String(), assetID)
if err != nil {
logger.Debug("avs address lists not found; skipping")
logger.Debug("staker lists not found; skipping")
continue
}
for assetID := range avsAssets {
stakerList, err := k.StakingKeeper.GetStakersByOperator(ctx, operatorAddress.String(), assetID)
if err != nil {
logger.Debug("staker lists not found; skipping")
continue
}
for _, staker := range stakerList.Stakers {
if curStakerPower, err := k.StakingKeeper.CalculateUSDValueForStaker(ctx, staker, avsAddress, operatorAddress.Bytes()); err != nil {
logger.Error("curStakerPower error", "error", err)
} else {
stakersPowerMap[staker] = curStakerPower
globalStakerAddressList = append(globalStakerAddressList, staker)
curTotalStakersPowers = curTotalStakersPowers.Add(curStakerPower)
}
for _, staker := range stakerList.Stakers {
if curStakerPower, err := k.StakingKeeper.CalculateUSDValueForStaker(ctx, staker, avsAddress, operatorAddress.Bytes()); err != nil {
logger.Error("curStakerPower error", "error", err)
} else {
stakersPowerMap[staker] = curStakerPower
globalStakerAddressList = append(globalStakerAddressList, staker)
curTotalStakersPowers = curTotalStakersPowers.Add(curStakerPower)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@ func (wrapper EpochsHooksWrapper) AfterEpochEnd(ctx sdk.Context, epochIdentifier
expEpochID := wrapper.keeper.GetParams(ctx).EpochIdentifier
if strings.Compare(epochIdentifier, expEpochID) == 0 {
// the minted coins generated by minting module will do the token allocation and distribution here
previousTotalPower := wrapper.keeper.StakingKeeper.GetLastTotalPower(ctx)
// previousTotalPower := wrapper.keeper.StakingKeeper.GetLastTotalPower(ctx)
logger := wrapper.keeper.Logger()
logger.Info(
"AfterEpochEnd of distribution",
)
err := wrapper.keeper.AllocateTokens(ctx, previousTotalPower.Int64())
if err != nil {
// at the end of an epoch we distribute all minted tokens for one epoch and left fees
// here we have the temporary approach that we allocate minited tokens for next epoch then we can correctly
// distribute those tokens based on potential slash event. And that should be ok since the amount of minited
// token for each epoch is the same, so we just missed the very first epoch's minting.
if err := wrapper.keeper.AllocateTokens(ctx, false); err != nil {
logger.Error("failed to allocate tokens", "err", err)
return
}
}
}
54 changes: 54 additions & 0 deletions x/feedistribution/keeper/impl_operator_hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package keeper

import (
keytypes "github.com/ExocoreNetwork/exocore/types/keys"
operatortypes "github.com/ExocoreNetwork/exocore/x/operator/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

type OperatorHooksWrapper struct {
keeper *Keeper
}

var _ operatortypes.OperatorHooks = OperatorHooksWrapper{}

func (k *Keeper) OperatorHooks() OperatorHooksWrapper {
return OperatorHooksWrapper{k}
}

// AfterOperatorKeySet is the implementation of the operator hooks.
// CONTRACT: an operator cannot set their key if they are already in the process of removing it.
func (wrapper OperatorHooksWrapper) AfterOperatorKeySet(
sdk.Context, sdk.AccAddress, string, keytypes.WrappedConsKey,
) {
}

// AfterOperatorKeyReplaced is the implementation of the operator hooks.
// CONTRACT: key replacement is not allowed if the operator is in the process of removing their
// key.
// CONTRACT: key replacement from newKey to oldKey is not allowed, after a replacement from
// oldKey to newKey.
func (wrapper OperatorHooksWrapper) AfterOperatorKeyReplaced(
_ sdk.Context, _ sdk.AccAddress, _ keytypes.WrappedConsKey,
_ keytypes.WrappedConsKey, _ string,
) {
}

// AfterOperatorKeyRemovalInitiated is the implementation of the operator hooks.
func (wrapper OperatorHooksWrapper) AfterOperatorKeyRemovalInitiated(
_ sdk.Context, _ sdk.AccAddress, _ string, _ keytypes.WrappedConsKey,
) {
}

func (wrapper OperatorHooksWrapper) AfterSlash(
ctx sdk.Context, _ sdk.AccAddress, _ []string,
) {
logger := wrapper.keeper.Logger()
logger.Info(
"AfterSlash of distribution",
)
// When distribution triggered by slash, we only distribute fee collected until now from last distribution
if err := wrapper.keeper.AllocateTokens(ctx, true); err != nil {
logger.Error("failed to allocate tokens", "err", err)
}
}
6 changes: 6 additions & 0 deletions x/feedistribution/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type (
authKeeper types.AccountKeeper
bankKeeper types.BankKeeper
epochsKeeper types.EpochsKeeper
mintKeeper types.MintKeeper
avsKeeper types.AVSKeeper

feeCollectorName string

Expand All @@ -40,6 +42,8 @@ func NewKeeper(
accountKeeper types.AccountKeeper,
stakingkeeper stakingkeeper.Keeper,
epochKeeper types.EpochsKeeper,
mintKeeper types.MintKeeper,
avsKeeper types.AVSKeeper,
) Keeper {
// ensure distribution module account is set
if addr := accountKeeper.GetModuleAddress(types.ModuleName); addr == nil {
Expand All @@ -60,6 +64,8 @@ func NewKeeper(
epochsKeeper: epochKeeper,
feeCollectorName: feeCollectorName,
StakingKeeper: stakingkeeper,
mintKeeper: mintKeeper,
avsKeeper: avsKeeper,
}

return *k
Expand Down
9 changes: 9 additions & 0 deletions x/feedistribution/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

epochsTypes "github.com/ExocoreNetwork/exocore/x/epochs/types"

minttypes "github.com/ExocoreNetwork/exocore/x/exomint/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/types"
)
Expand Down Expand Up @@ -54,3 +55,11 @@ type PoolKeeper interface {
GetCommunityPool(ctx context.Context) (sdk.Coins, error)
SetToDistribute(ctx context.Context, amount sdk.Coins, addr string) error
}

type MintKeeper interface {
GetParams(ctx sdk.Context) minttypes.Params
}

type AVSKeeper interface {
IsAVSByChainID(ctx sdk.Context, chainID string) (bool, string)
}

0 comments on commit 21bab9d

Please sign in to comment.