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(distribution)!: custom fee allocation #2231

Merged
merged 5 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
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
9 changes: 7 additions & 2 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ import (
axelarnetKeeper "github.com/axelarnetwork/axelar-core/x/axelarnet/keeper"
axelarnetTypes "github.com/axelarnetwork/axelar-core/x/axelarnet/types"
axelarbankkeeper "github.com/axelarnetwork/axelar-core/x/bank/keeper"
axelardistr "github.com/axelarnetwork/axelar-core/x/distribution"
axelardistrkeeper "github.com/axelarnetwork/axelar-core/x/distribution/keeper"
"github.com/axelarnetwork/axelar-core/x/evm"
evmKeeper "github.com/axelarnetwork/axelar-core/x/evm/keeper"
evmTypes "github.com/axelarnetwork/axelar-core/x/evm/types"
Expand Down Expand Up @@ -238,6 +240,7 @@ func NewAxelarApp(
SetKeeper(keepers, initIBCKeeper(appCodec, keys, keepers))

// set up custom axelar keepers
SetKeeper(keepers, initAxelarDistributionKeeper(keepers))
SetKeeper(keepers, initAxelarnetKeeper(appCodec, keys, keepers))
SetKeeper(keepers, initEvmKeeper(appCodec, keys, keepers))
SetKeeper(keepers, initNexusKeeper(appCodec, keys, keepers))
Expand Down Expand Up @@ -521,6 +524,8 @@ func initAppModules(keepers *KeeperCache, bApp *bam.BaseApp, encodingConfig axel

appCodec := encodingConfig.Codec

distrAppModule := distr.NewAppModule(appCodec, *GetKeeper[distrkeeper.Keeper](keepers), GetKeeper[authkeeper.AccountKeeper](keepers), GetKeeper[bankkeeper.BaseKeeper](keepers), GetKeeper[stakingkeeper.Keeper](keepers))

appModules := []module.AppModule{
genutil.NewAppModule(GetKeeper[authkeeper.AccountKeeper](keepers), GetKeeper[stakingkeeper.Keeper](keepers), bApp.DeliverTx, encodingConfig.TxConfig),
auth.NewAppModule(appCodec, *GetKeeper[authkeeper.AccountKeeper](keepers), nil),
Expand All @@ -532,7 +537,7 @@ func initAppModules(keepers *KeeperCache, bApp *bam.BaseApp, encodingConfig axel
gov.NewAppModule(appCodec, *GetKeeper[govkeeper.Keeper](keepers), GetKeeper[authkeeper.AccountKeeper](keepers), GetKeeper[bankkeeper.BaseKeeper](keepers)),
mint.NewAppModule(appCodec, *GetKeeper[mintkeeper.Keeper](keepers), GetKeeper[authkeeper.AccountKeeper](keepers)),
slashing.NewAppModule(appCodec, *GetKeeper[slashingkeeper.Keeper](keepers), GetKeeper[authkeeper.AccountKeeper](keepers), GetKeeper[bankkeeper.BaseKeeper](keepers), GetKeeper[stakingkeeper.Keeper](keepers)),
distr.NewAppModule(appCodec, *GetKeeper[distrkeeper.Keeper](keepers), GetKeeper[authkeeper.AccountKeeper](keepers), GetKeeper[bankkeeper.BaseKeeper](keepers), GetKeeper[stakingkeeper.Keeper](keepers)),
axelardistr.NewAppModule(distrAppModule, *GetKeeper[axelardistrkeeper.Keeper](keepers)),
staking.NewAppModule(appCodec, *GetKeeper[stakingkeeper.Keeper](keepers), GetKeeper[authkeeper.AccountKeeper](keepers), GetKeeper[bankkeeper.BaseKeeper](keepers)),
upgrade.NewAppModule(*GetKeeper[upgradekeeper.Keeper](keepers)),
evidence.NewAppModule(*GetKeeper[evidencekeeper.Keeper](keepers)),
Expand Down Expand Up @@ -708,7 +713,7 @@ func initMessageAnteDecorators(encodingConfig axelarParams.EncodingConfig, keepe
func InitModuleAccountPermissions() map[string][]string {
return map[string][]string{
authtypes.FeeCollectorName: nil,
distrtypes.ModuleName: nil,
distrtypes.ModuleName: {authtypes.Minter, authtypes.Burner},
minttypes.ModuleName: {authtypes.Minter},
stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking},
stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking},
Expand Down
13 changes: 13 additions & 0 deletions app/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import (
axelarnetKeeper "github.com/axelarnetwork/axelar-core/x/axelarnet/keeper"
axelarnetTypes "github.com/axelarnetwork/axelar-core/x/axelarnet/types"
axelarbankkeeper "github.com/axelarnetwork/axelar-core/x/bank/keeper"
axelardistrkeeper "github.com/axelarnetwork/axelar-core/x/distribution/keeper"
evmKeeper "github.com/axelarnetwork/axelar-core/x/evm/keeper"
evmTypes "github.com/axelarnetwork/axelar-core/x/evm/types"
multisigKeeper "github.com/axelarnetwork/axelar-core/x/multisig/keeper"
Expand Down Expand Up @@ -424,6 +425,18 @@ func initDistributionKeeper(appCodec codec.Codec, keys map[string]*sdk.KVStoreKe
return &distrK
}

func initAxelarDistributionKeeper(keepers *KeeperCache) *axelardistrkeeper.Keeper {
axelardistrK := axelardistrkeeper.NewKeeper(
*GetKeeper[distrkeeper.Keeper](keepers),
GetKeeper[authkeeper.AccountKeeper](keepers),
GetKeeper[bankkeeper.BaseKeeper](keepers),
GetKeeper[stakingkeeper.Keeper](keepers),
authtypes.FeeCollectorName,
)

return &axelardistrK
}

func initMintKeeper(appCodec codec.Codec, keys map[string]*sdk.KVStoreKey, keepers *KeeperCache) *mintkeeper.Keeper {
mintK := mintkeeper.NewKeeper(
appCodec,
Expand Down
34 changes: 34 additions & 0 deletions docs/proto/proto-docs.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions proto/axelar/distribution/v1beta1/events.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
syntax = "proto3";
package axelar.distribution.v1beta1;

import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";

option go_package = "github.com/axelarnetwork/axelar-core/x/distribution/types";

message FeeBurnedEvent {
repeated cosmos.base.v1beta1.Coin coins = 2
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
}
1 change: 1 addition & 0 deletions utils/events/event_imports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
_ "github.com/axelarnetwork/axelar-core/x/ante/types"
_ "github.com/axelarnetwork/axelar-core/x/auxiliary/types"
_ "github.com/axelarnetwork/axelar-core/x/axelarnet/types"
_ "github.com/axelarnetwork/axelar-core/x/distribution/types"
_ "github.com/axelarnetwork/axelar-core/x/evm/types"
_ "github.com/axelarnetwork/axelar-core/x/multisig/types"
_ "github.com/axelarnetwork/axelar-core/x/nexus/types"
Expand Down
79 changes: 79 additions & 0 deletions x/distribution/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package keeper

import (
sdk "github.com/cosmos/cosmos-sdk/types"
distribution "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
distributionTypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
abci "github.com/tendermint/tendermint/abci/types"

"github.com/axelarnetwork/axelar-core/utils/events"
"github.com/axelarnetwork/axelar-core/x/distribution/types"
"github.com/axelarnetwork/utils/funcs"
"github.com/axelarnetwork/utils/slices"
)

// Keeper wraps the distribution keeper to customize fee allocation mechanism
type Keeper struct {
distribution.Keeper

authKeeper types.AccountKeeper
bankKeeper types.BankKeeper
stakingKeeper types.StakingKeeper
feeCollectorName string
}

func NewKeeper(
k distribution.Keeper, ak types.AccountKeeper, bk types.BankKeeper,
sk types.StakingKeeper, feeCollectorName string,
) Keeper {
return Keeper{
Keeper: k,
authKeeper: ak,
bankKeeper: bk,
stakingKeeper: sk,
feeCollectorName: feeCollectorName,
}
}

// AllocateTokens modifies the fee distribution by:
// - Allocating the community tax portion to the community pool
// - Burning all remaining tokens instead of distributing to validators
func (k Keeper) AllocateTokens(ctx sdk.Context, _, _ int64, _ sdk.ConsAddress, _ []abci.VoteInfo) {
// fetch and clear the collected fees for distribution, since this is
// called in BeginBlock, collected fees will be from the previous block
// (and distributed to the previous proposer)
feeCollector := k.authKeeper.GetModuleAccount(ctx, k.feeCollectorName)
feesCollectedInt := k.bankKeeper.GetAllBalances(ctx, feeCollector.GetAddress())
feesCollected := sdk.NewDecCoinsFromCoins(feesCollectedInt...)

// transfer collected fees to the distribution module account
err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName, distributionTypes.ModuleName, feesCollectedInt)
if err != nil {
panic(err)

Check warning on line 52 in x/distribution/keeper/keeper.go

View check run for this annotation

Codecov / codecov/patch

x/distribution/keeper/keeper.go#L52

Added line #L52 was not covered by tests
}

feePool := k.GetFeePool(ctx)

communityTaxRate := k.GetCommunityTax(ctx)
communityPoolAmount := feesCollected.MulDecTruncate(communityTaxRate)
remaining := feesCollected.Sub(communityPoolAmount)

// truncate the remaining coins, return remainder to community pool
feeToBurn, truncationRemainder := remaining.TruncateDecimal()
communityPoolAmount = communityPoolAmount.Add(truncationRemainder...)

// allocate community funding
feePool.CommunityPool = feePool.CommunityPool.Add(communityPoolAmount...)
k.SetFeePool(ctx, feePool)

// burn the rest
funcs.MustNoErr(k.bankKeeper.BurnCoins(ctx, distributionTypes.ModuleName, feeToBurn))
events.Emit(ctx, &types.FeeBurnedEvent{
Coins: feeToBurn,
})

// track cumulative burned fee
feeBurned := slices.Map(feeToBurn, types.WithBurnedPrefix)
funcs.MustNoErr(k.bankKeeper.MintCoins(ctx, distributionTypes.ModuleName, feeBurned))
funcs.MustNoErr(k.bankKeeper.SendCoinsFromModuleToAccount(ctx, distributionTypes.ModuleName, types.ZeroAddress, feeBurned))
}
132 changes: 132 additions & 0 deletions x/distribution/keeper/keeper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package keeper_test

import (
"testing"

"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
distribution "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/assert"
"github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

"github.com/axelarnetwork/axelar-core/app/params"
"github.com/axelarnetwork/axelar-core/testutils/fake"
"github.com/axelarnetwork/axelar-core/testutils/rand"
axelarnettypes "github.com/axelarnetwork/axelar-core/x/axelarnet/exported"
"github.com/axelarnetwork/axelar-core/x/distribution/keeper"
"github.com/axelarnetwork/axelar-core/x/distribution/types"
"github.com/axelarnetwork/axelar-core/x/distribution/types/mock"
"github.com/axelarnetwork/utils/funcs"
"github.com/axelarnetwork/utils/slices"
. "github.com/axelarnetwork/utils/test"
)

func TestAllocateTokens(t *testing.T) {
var (
k keeper.Keeper
accBalances map[string]sdk.Coins
bk *mock.BankKeeperMock
fee sdk.Coins
)

ctx := sdk.NewContext(fake.NewMultiStore(), tmproto.Header{}, false, log.TestingLogger())

fee = sdk.NewCoins(sdk.NewCoin(axelarnettypes.NativeAsset, sdk.NewInt(rand.PosI64())))
accBalances = map[string]sdk.Coins{
authtypes.NewModuleAddress(authtypes.FeeCollectorName).String(): fee,
}

Given("an axelar distribution keeper", func() {
encCfg := params.MakeEncodingConfig()
subspace := paramstypes.NewSubspace(encCfg.Codec, encCfg.Amino, sdk.NewKVStoreKey(distributiontypes.StoreKey), sdk.NewKVStoreKey("tKey"), distributiontypes.ModuleName)
ak := &mock.AccountKeeperMock{
GetModuleAccountFunc: func(ctx sdk.Context, name string) authtypes.ModuleAccountI {
return authtypes.NewEmptyModuleAccount(name)
},
GetModuleAddressFunc: func(name string) sdk.AccAddress {
return authtypes.NewModuleAddress(name)
},
}
bk = &mock.BankKeeperMock{
GetAllBalancesFunc: func(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
return accBalances[addr.String()]
},
SendCoinsFromModuleToModuleFunc: func(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error {
senderModule = authtypes.NewModuleAddress(senderModule).String()
recipientModule = authtypes.NewModuleAddress(recipientModule).String()

accBalances[senderModule] = accBalances[senderModule].Sub(amt)
accBalances[recipientModule] = accBalances[recipientModule].Add(amt...)

return nil
},
SendCoinsFromModuleToAccountFunc: func(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error {
senderModule = authtypes.NewModuleAddress(senderModule).String()

accBalances[senderModule] = accBalances[senderModule].Sub(amt)
accBalances[recipientAddr.String()] = accBalances[recipientAddr.String()].Add(amt...)

return nil
},
BurnCoinsFunc: func(ctx sdk.Context, name string, amt sdk.Coins) error {
acc := authtypes.NewModuleAddress(name).String()
accBalances[acc] = accBalances[acc].Sub(amt)

return nil
},
MintCoinsFunc: func(ctx sdk.Context, name string, amt sdk.Coins) error {
acc := authtypes.NewModuleAddress(name).String()
accBalances[acc] = accBalances[acc].Add(amt...)

return nil
},
}
sk := &mock.StakingKeeperMock{
ValidatorByConsAddrFunc: func(ctx sdk.Context, addr sdk.ConsAddress) stakingtypes.ValidatorI {
seed := []byte("key")
consKey := ed25519.GenPrivKeyFromSecret(seed).PubKey()
pk := secp256k1.GenPrivKeyFromSecret(seed)
valAddr := sdk.ValAddress(pk.PubKey().Address().Bytes())
return funcs.Must(stakingtypes.NewValidator(valAddr, consKey, stakingtypes.Description{}))
},
}

distriK := distribution.NewKeeper(encCfg.Codec, sdk.NewKVStoreKey(distributiontypes.StoreKey), subspace, ak, bk, sk, authtypes.FeeCollectorName, map[string]bool{})
k = keeper.NewKeeper(distriK, ak, bk, sk, authtypes.FeeCollectorName)
k.SetFeePool(ctx, distributiontypes.FeePool{CommunityPool: sdk.DecCoins{}})
k.SetParams(ctx, distributiontypes.DefaultParams())
}).
When("allocate token", func() {
k.AllocateTokens(ctx, 0, 1, sdk.ConsAddress{}, nil)
}).
Then("allocate to community pool and burn the rest", func(t *testing.T) {
assert.Len(t, bk.BurnCoinsCalls(), 1)

feeBurnedType := proto.MessageName(&types.FeeBurnedEvent{})
assert.Len(t, slices.Filter(ctx.EventManager().Events(), func(e sdk.Event) bool {
return e.Type == feeBurnedType
}), 1)

burned, tax := expectedBurnAndTax(ctx, k, fee)
expectedBurnedFee := sdk.NewCoins(slices.Map(burned, types.WithBurnedPrefix)...)

assert.Equal(t, expectedBurnedFee, accBalances[types.ZeroAddress.String()])
assert.Equal(t, k.GetFeePool(ctx).CommunityPool, tax)
}).
Run(t)
}

func expectedBurnAndTax(ctx sdk.Context, k keeper.Keeper, fee sdk.Coins) (sdk.Coins, sdk.DecCoins) {
feesDec := sdk.NewDecCoinsFromCoins(fee...)
tax := feesDec.MulDecTruncate(k.GetCommunityTax(ctx))
burnAmt, remainder := feesDec.Sub(tax).TruncateDecimal()

return burnAmt, tax.Add(remainder...)
}
24 changes: 24 additions & 0 deletions x/distribution/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package distribution

import (
"github.com/cosmos/cosmos-sdk/types/module"
distr "github.com/cosmos/cosmos-sdk/x/distribution"

"github.com/axelarnetwork/axelar-core/x/distribution/keeper"
)

var _ module.AppModule = AppModule{}

type AppModule struct {
distr.AppModule

keeper keeper.Keeper
}

// NewAppModule creates a new AppModule object
func NewAppModule(distrAppModule distr.AppModule, keeper keeper.Keeper) AppModule {
return AppModule{
AppModule: distrAppModule,
keeper: keeper,
}
}
Loading
Loading