Skip to content

Commit

Permalink
feeburn module
Browse files Browse the repository at this point in the history
  • Loading branch information
expertdicer committed Nov 2, 2023
1 parent e9592d3 commit cab04ab
Show file tree
Hide file tree
Showing 32 changed files with 2,966 additions and 0 deletions.
11 changes: 11 additions & 0 deletions x/feeburn/ante/expected_keepers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ante

import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// BankKeeper defines the contract needed for supply related APIs (noalias)
type BankKeeper interface {
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
}
155 changes: 155 additions & 0 deletions x/feeburn/ante/fee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package ante

import (
"fmt"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/types"

feeburnkeeper "github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/feeburn/keeper"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
)

// DeductFeeDecorator deducts fees from the first signer of the tx
// If the first signer does not have the funds to pay for the fees, return with InsufficientFunds error
// Call next AnteHandler if fees successfully deducted
// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator
type DeductFeeDecorator struct {
accountKeeper ante.AccountKeeper
bankKeeper BankKeeper
feegrantKeeper ante.FeegrantKeeper
txFeeChecker ante.TxFeeChecker
feeburnKeeper feeburnkeeper.Keeper
}

func NewDeductFeeDecorator(ak ante.AccountKeeper, bk BankKeeper, fk ante.FeegrantKeeper, tfc ante.TxFeeChecker, fbk feeburnkeeper.Keeper) DeductFeeDecorator {
if tfc == nil {
tfc = checkTxFeeWithValidatorMinGasPrices
}

return DeductFeeDecorator{
accountKeeper: ak,
bankKeeper: bk,
feegrantKeeper: fk,
txFeeChecker: tfc,
feeburnKeeper: fbk,
}
}

func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}

if !simulate && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 {
return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas")
}

var (
priority int64
err error
)

fee := feeTx.GetFee()
if !simulate {
fee, priority, err = dfd.txFeeChecker(ctx, tx)
if err != nil {
return ctx, err
}
}
if err := dfd.checkDeductFee(ctx, tx, fee); err != nil {
return ctx, err
}

newCtx := ctx.WithPriority(priority)

return next(newCtx, tx, simulate)
}

func (dfd DeductFeeDecorator) checkDeductFee(ctx sdk.Context, sdkTx sdk.Tx, fee sdk.Coins) error {
feeTx, ok := sdkTx.(sdk.FeeTx)
if !ok {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}

if addr := dfd.accountKeeper.GetModuleAddress(types.FeeCollectorName); addr == nil {
return fmt.Errorf("fee collector module account (%s) has not been set", types.FeeCollectorName)
}

feePayer := feeTx.FeePayer()
feeGranter := feeTx.FeeGranter()
deductFeesFrom := feePayer

// if feegranter set deduct fee from feegranter account.
// this works with only when feegrant enabled.
if feeGranter != nil {
if dfd.feegrantKeeper == nil {
return sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled")
} else if !feeGranter.Equals(feePayer) {
err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, sdkTx.GetMsgs())
if err != nil {
return errorsmod.Wrapf(err, "%s does not allow to pay fees for %s", feeGranter, feePayer)
}
}

deductFeesFrom = feeGranter
}

deductFeesFromAcc := dfd.accountKeeper.GetAccount(ctx, deductFeesFrom)
if deductFeesFromAcc == nil {
return sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom)
}

// deduct the fees
if !fee.IsZero() {
feeBurnPercent, ok := sdk.NewIntFromString(dfd.feeburnKeeper.GetTxFeeBurnPercent(ctx))
if !ok {
return sdkerrors.ErrInvalidType
}
err := DeductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, fee, feeBurnPercent)
if err != nil {
return err
}
}

events := sdk.Events{
sdk.NewEvent(
sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()),
sdk.NewAttribute(sdk.AttributeKeyFeePayer, deductFeesFrom.String()),
),
}
ctx.EventManager().EmitEvents(events)

return nil
}

// DeductFees deducts fees from the given account.
func DeductFees(bankKeeper BankKeeper, ctx sdk.Context, acc types.AccountI, fees sdk.Coins, bp sdkmath.Int) error {
if !fees.IsValid() {
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}

// Calculate burning amounts by given percentage and fee amounts
burningFees := sdk.Coins{}
for _, fee := range fees {
burningAmount := fee.Amount.Mul(bp).Quo(sdk.NewInt(100))
burningFees = burningFees.Add(sdk.NewCoin(fee.Denom, burningAmount))
}

err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees)
if err != nil {
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error())
}

err = bankKeeper.BurnCoins(ctx, types.FeeCollectorName, burningFees)
if err != nil {
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error())
}

return nil
}
67 changes: 67 additions & 0 deletions x/feeburn/ante/validator_tx_fee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package ante

import (
"math"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per
// unit of gas is fixed and set by each validator, can the tx priority is computed from the gas price.
func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return nil, 0, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}

feeCoins := feeTx.GetFee()
gas := feeTx.GetGas()

// Ensure that the provided fees meet a minimum threshold for the validator,
// if this is a CheckTx. This is only for local mempool purposes, and thus
// is only ran on check tx.
if ctx.IsCheckTx() {
minGasPrices := ctx.MinGasPrices()
if !minGasPrices.IsZero() {
requiredFees := make(sdk.Coins, len(minGasPrices))

// Determine the required fees by multiplying each required minimum gas
// price by the gas limit, where fee = ceil(minGasPrice * gasLimit).
glDec := sdkmath.LegacyNewDec(int64(gas))
for i, gp := range minGasPrices {
fee := gp.Amount.Mul(glDec)
requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
}

if !feeCoins.IsAnyGTE(requiredFees) {
return nil, 0, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees)
}
}
}

priority := getTxPriority(feeCoins, int64(gas))
return feeCoins, priority, nil
}

// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the gas price
// provided in a transaction.
// NOTE: This implementation should be used with a great consideration as it opens potential attack vectors
// where txs with multiple coins could not be prioritize as expected.
func getTxPriority(fee sdk.Coins, gas int64) int64 {
var priority int64
for _, c := range fee {
p := int64(math.MaxInt64)
gasPrice := c.Amount.QuoRaw(gas)
if gasPrice.IsInt64() {
p = gasPrice.Int64()
}
if priority == 0 || p < priority {
priority = p
}
}

return priority
}
30 changes: 30 additions & 0 deletions x/feeburn/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cli

import (
"fmt"
// "strings"

"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
// "github.com/cosmos/cosmos-sdk/client/flags"
// sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/feeburn/types"
)

// GetQueryCmd returns the cli query commands for this module
func GetQueryCmd(_ string) *cobra.Command {
// Group feeburn queries under a subcommand
cmd := &cobra.Command{
Use: types.ModuleName,
Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName),
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}

cmd.AddCommand(CmdQueryParams())

return cmd
}
34 changes: 34 additions & 0 deletions x/feeburn/client/cli/query_params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cli

import (
"context"

"github.com/ChihuahuaChain/chihuahua/x/feeburn/types"

Check failure on line 6 in x/feeburn/client/cli/query_params.go

View workflow job for this annotation

GitHub Actions / test

no required module provides package github.com/ChihuahuaChain/chihuahua/x/feeburn/types; to add it:

Check failure on line 6 in x/feeburn/client/cli/query_params.go

View workflow job for this annotation

GitHub Actions / build

no required module provides package github.com/ChihuahuaChain/chihuahua/x/feeburn/types; to add it:
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/spf13/cobra"
)

func CmdQueryParams() *cobra.Command {
cmd := &cobra.Command{
Use: "params",
Short: "shows the parameters of the module",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)

queryClient := types.NewQueryClient(clientCtx)

res, err := queryClient.Params(context.Background(), &types.QueryParamsRequest{})
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
24 changes: 24 additions & 0 deletions x/feeburn/client/cli/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cli

import (
"fmt"

"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
// "github.com/cosmos/cosmos-sdk/client/flags"
"github.com/ChihuahuaChain/chihuahua/x/feeburn/types"
)

// GetTxCmd returns the transaction commands for this module
func GetTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: types.ModuleName,
Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName),
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}

return cmd
}
24 changes: 24 additions & 0 deletions x/feeburn/genesis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package feeburn

import (
errorsmod "cosmossdk.io/errors"
"github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/feeburn/keeper"
"github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/feeburn/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// InitGenesis initializes the module's state from a provided genesis state.
func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) {
err := k.SetParams(ctx, genState.Params)
if err != nil {
panic(errorsmod.Wrapf(err, "error setting params"))
}
}

// ExportGenesis returns the module's exported genesis
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState {
genesis := types.DefaultGenesis()
genesis.Params = k.GetParams(ctx)

return genesis
}
26 changes: 26 additions & 0 deletions x/feeburn/genesis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package feeburn_test

import (
"testing"

keepertest "github.com/ChihuahuaChain/chihuahua/testutil/keeper"
"github.com/ChihuahuaChain/chihuahua/testutil/nullify"

"github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/feeburn"
"github.com/White-Whale-Defi-Platform/migaloo-chain/v3/x/feeburn/types"
"github.com/stretchr/testify/require"
)

func TestGenesis(t *testing.T) {
genesisState := types.GenesisState{
Params: types.DefaultParams(),
}

k, ctx := keepertest.FeeburnKeeper(t)
feeburn.InitGenesis(ctx, *k, genesisState)
got := feeburn.ExportGenesis(ctx, *k)
require.NotNil(t, got)

nullify.Fill(&genesisState)
nullify.Fill(got)
}
Loading

0 comments on commit cab04ab

Please sign in to comment.