Skip to content

Commit

Permalink
feat(distribution)!: custom fee allocation (#2231)
Browse files Browse the repository at this point in the history
  • Loading branch information
haiyizxx authored Jan 31, 2025
1 parent ef91531 commit 6563117
Show file tree
Hide file tree
Showing 12 changed files with 2,199 additions and 2 deletions.
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)
}

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

0 comments on commit 6563117

Please sign in to comment.