Skip to content

Commit

Permalink
"add optin mapping to test"
Browse files Browse the repository at this point in the history
  • Loading branch information
sainoe committed Feb 9, 2024
1 parent 2a0fa7a commit 4031025
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 14 deletions.
100 changes: 100 additions & 0 deletions tests/integration/distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"strings"

"cosmossdk.io/math"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
Expand Down Expand Up @@ -601,6 +602,105 @@ func (s *CCVTestSuite) TestIBCTransferMiddleware() {
}
}

// TestAllocateTokens is a happy-path test of the consumer rewards pool allocation
// to opted-in validators and the community pool
func (s *CCVTestSuite) TestAllocateTokens() {
// set up channel and delegate some tokens in order for validator set update to be sent to the consumer chain
s.SetupAllCCVChannels()
providerKeeper := s.providerApp.GetProviderKeeper()
bankKeeper := s.providerApp.GetTestBankKeeper()
distributionKeeper := s.providerApp.GetTestDistributionKeeper()

totalRewards := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))}
providerKeeper.SetConsumerRewardDenom(s.providerCtx(), sdk.DefaultBondDenom) // register a consumer reward denom

// fund consumer rewards pool
bankKeeper.SendCoinsFromAccountToModule(
s.providerCtx(),
s.providerChain.SenderAccount.GetAddress(),
providertypes.ConsumerRewardsPool,
totalRewards,
)

// Allocate rewards evenly between consumers
rewardsPerConsumer := totalRewards.QuoInt(math.NewInt(int64(len(s.consumerBundles))))
for chainID := range s.consumerBundles {
// update consumer allocation
providerKeeper.SetConsumerRewardsAllocation(
s.providerCtx(),
chainID,
providertypes.ConsumerRewardsAllocation{
Rewards: sdk.NewDecCoinsFromCoins(rewardsPerConsumer...),
},
)

// opt in all validators for the chain
for _, val := range s.providerChain.Vals.Validators {
providerKeeper.SetOptedIn(
s.providerCtx(),
chainID,
providertypes.NewProviderConsAddress(sdk.ConsAddress(val.Address)),
uint64(s.providerCtx().BlockHeight()),
)
}
}

Check warning

Code scanning / CodeQL

Iteration over map Warning test

Iteration over map may be a possible source of non-determinism

// Iterate over the validators and
// store their current voting power and outstanding rewards
lastValOutRewards := map[string]sdk.DecCoins{}
votes := []abci.VoteInfo{}
for _, val := range s.providerChain.Vals.Validators {
votes = append(votes,
abci.VoteInfo{
Validator: abci.Validator{Address: val.Address, Power: val.VotingPower},
SignedLastBlock: true,
},
)

valRewards := distributionKeeper.GetValidatorOutstandingRewards(s.providerCtx(), sdk.ValAddress(val.Address))
lastValOutRewards[sdk.ValAddress(val.Address).String()] = valRewards.Rewards
}

// store community pool balance
lastCommPool := distributionKeeper.GetFeePoolCommunityCoins(s.providerCtx())

// execute BeginBlock to trigger the token allocation
providerKeeper.BeginBlockRD(
s.providerCtx(),
abci.RequestBeginBlock{
LastCommitInfo: abci.CommitInfo{
Votes: votes,
},
},
)

valNum := len(s.providerChain.Vals.Validators)
consuNum := len(s.consumerBundles)

// compute the expected validators token allocation by subtracting the community tax
rewardsPerConsumerDec := sdk.NewDecCoinsFromCoins(rewardsPerConsumer...)
communityTax := distributionKeeper.GetCommunityTax(s.providerCtx())
validatorsExpRewards := rewardsPerConsumerDec.
MulDecTruncate(math.LegacyOneDec().Sub(communityTax)).
// multiply by the number of consumers since all the validators opted in
MulDec(sdk.NewDec(int64(consuNum)))
perValExpReward := validatorsExpRewards.QuoDec(sdk.NewDec(int64(valNum)))

// verify the validator tokens allocation
// note all validators have the same voting power to keep things simple
for _, val := range s.providerChain.Vals.Validators {
valReward := distributionKeeper.GetValidatorOutstandingRewards(s.providerCtx(), sdk.ValAddress(val.Address))
s.Require().True(valReward.Rewards.IsEqual(
lastValOutRewards[sdk.ValAddress(val.Address).String()].Add(perValExpReward...),
))
}

commPoolExpRewards := sdk.NewDecCoinsFromCoins(totalRewards...).Sub(validatorsExpRewards)
currCommPool := distributionKeeper.GetFeePoolCommunityCoins(s.providerCtx())

s.Require().True(currCommPool.IsEqual(lastCommPool.Add(commPoolExpRewards...)))
}

// getEscrowBalance gets the current balances in the escrow account holding the transferred tokens to the provider
func (s *CCVTestSuite) getEscrowBalance() sdk.Coins {
consumerBankKeeper := s.consumerApp.GetTestBankKeeper()
Expand Down
4 changes: 4 additions & 0 deletions testutil/integration/debug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,7 @@ func TestSlashRetries(t *testing.T) {
func TestIBCTransferMiddleware(t *testing.T) {
runCCVTestByName(t, "TestIBCTransferMiddleware")
}

func TestAllocateTokens(t *testing.T) {
runCCVTestByName(t, "TestAllocateTokens")
}
35 changes: 21 additions & 14 deletions x/ccv/provider/keeper/distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

// BeginBlockRD executes BeginBlock logic for the Reward Distribution sub-protocol.
func (k Keeper) BeginBlockRD(ctx sdk.Context, req abci.RequestBeginBlock) {
// transfers all whitelisted consumer rewards to the fee collector address

// determine the total power signing the block
var previousTotalPower int64
Expand Down Expand Up @@ -125,34 +124,43 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, totalPreviousPower int64, bonded
}

// TODO: define which validators earned the tokens, i.e. already opted in for 1000 blocks
// AllocateTokensToConsumerValidators allocates the consumer rewards pool to validator
// according to their voting power
func (k Keeper) AllocateTokensToConsumerValidators(
ctx sdk.Context,
chainID string,
totalPreviousPower int64,
bondedVotes []abci.VoteInfo,
fees sdk.DecCoins,
) (totalReward sdk.DecCoins) {
for _, vote := range bondedVotes {
valConsAddr := vote.Validator.Address
tokens sdk.DecCoins,
) sdk.DecCoins {
optedInVals := map[string]struct{}{}
for _, val := range k.GetOptedIn(ctx, chainID) {
optedInVals[val.ProviderAddr.Address.String()] = struct{}{}
}

if _, found := k.GetValidatorConsumerPubKey(ctx, chainID, types.NewProviderConsAddress(valConsAddr)); !found {
totalReward := sdk.DecCoins{}

for _, vote := range bondedVotes {
consAddr := sdk.ConsAddress(vote.Validator.Address)
if _, ok := optedInVals[consAddr.String()]; !ok {
continue
}

// TODO: Consider micro-slashing for missing votes.
//
// Ref: https://github.com/cosmos/cosmos-sdk/issues/2525#issuecomment-430838701
powerFraction := math.LegacyNewDec(vote.Validator.Power).QuoTruncate(math.LegacyNewDec(totalPreviousPower))
feeFraction := fees.MulDecTruncate(powerFraction)
tokensFraction := tokens.MulDecTruncate(powerFraction)

k.distributionKeeper.AllocateTokensToValidator(
ctx,
k.stakingKeeper.ValidatorByConsAddr(ctx, valConsAddr),
feeFraction,
k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr),
tokensFraction,
)
totalReward = totalReward.Add(feeFraction...)
totalReward = totalReward.Add(tokensFraction...)
}

return
return totalReward
}

// TransferConsumerRewardsToDistributionModule transfers the collected rewards of the given consumer chain
Expand All @@ -161,7 +169,6 @@ func (k Keeper) TransferConsumerRewardsToDistributionModule(
ctx sdk.Context,
chainID string,
) (sdk.Coins, error) {

// Get coins of the consumer reward pool
pool := k.GetConsumerRewardsAllocation(ctx, chainID)

Expand Down Expand Up @@ -200,15 +207,15 @@ func (k Keeper) TransferConsumerRewardsToDistributionModule(

// consumer reward pools getter and setter

// get the consumer reward pool distribution info
// GetConsumerRewardsAllocation the onsumer rewards allocation for the given chain ID
func (k Keeper) GetConsumerRewardsAllocation(ctx sdk.Context, chainID string) (pool types.ConsumerRewardsAllocation) {
store := ctx.KVStore(k.storeKey)
b := store.Get(types.ConsumerRewardPoolKey(chainID))
k.cdc.MustUnmarshal(b, &pool)
return
}

// set the consumer reward pool distribution info
// SetConsumerRewardsAllocation sets the consumer rewards allocation for the given chain ID
func (k Keeper) SetConsumerRewardsAllocation(ctx sdk.Context, chainID string, pool types.ConsumerRewardsAllocation) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshal(&pool)
Expand Down

0 comments on commit 4031025

Please sign in to comment.