Skip to content

Commit

Permalink
feat: integrate x/feemarket (#250)
Browse files Browse the repository at this point in the history
* integrate feemarket

* lint

* prepare add feemarket upgrade handler

* remove feeabs decorator

* refactor and add unit tests

* extract concrete errors types

* remove todo

* fix wrong if condition

* use correct error type in test

* updgrade

---------

Co-authored-by: Jacob Gadikian <[email protected]>
  • Loading branch information
tuantran1702 and faddat authored Jul 5, 2024
1 parent d3f8d2e commit 709efaa
Show file tree
Hide file tree
Showing 12 changed files with 730 additions and 185 deletions.
80 changes: 0 additions & 80 deletions app/ante.go

This file was deleted.

187 changes: 187 additions & 0 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package ante

import (
ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante"
"github.com/cosmos/ibc-go/v8/modules/core/keeper"
feeabskeeper "github.com/osmosis-labs/fee-abstraction/v8/x/feeabs/keeper"
feeabstypes "github.com/osmosis-labs/fee-abstraction/v8/x/feeabs/types"
feemarketante "github.com/skip-mev/feemarket/x/feemarket/ante"
feemarkettypes "github.com/skip-mev/feemarket/x/feemarket/types"

corestoretypes "cosmossdk.io/core/store"
circuitante "cosmossdk.io/x/circuit/ante"
circuitkeeper "cosmossdk.io/x/circuit/keeper"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/ante"

wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmTypes "github.com/CosmWasm/wasmd/x/wasm/types"
)

// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC
// channel keeper.
type HandlerOptions struct {
ante.HandlerOptions

IBCKeeper *keeper.Keeper
WasmConfig *wasmTypes.WasmConfig
WasmKeeper *wasmkeeper.Keeper
TXCounterStoreService corestoretypes.KVStoreService
CircuitKeeper *circuitkeeper.Keeper
FeeAbskeeper feeabskeeper.Keeper
FeeMarketKeeper feemarketante.FeeMarketKeeper
AccountKeeper feemarketante.AccountKeeper
}

// NewAnteHandler constructor
func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
if options.AccountKeeper == nil {
return nil, ErrMissingAccountKeeper
}
if options.BankKeeper == nil {
return nil, ErrMissingBankKeeper
}
if options.SignModeHandler == nil {
return nil, ErrMissingSignModeHandler
}
if options.WasmConfig == nil {
return nil, ErrMissingWasmConfig
}
if options.TXCounterStoreService == nil {
return nil, ErrMissingWasmStoreService
}
if options.CircuitKeeper == nil {
return nil, ErrMissingCircuitKeeper
}

anteDecorators := []sdk.AnteDecorator{
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit), // after setup context to enforce limits early
wasmkeeper.NewCountTXDecorator(options.TXCounterStoreService),
wasmkeeper.NewGasRegisterDecorator(options.WasmKeeper.GetGasRegister()),
circuitante.NewCircuitBreakerDecorator(options.CircuitKeeper),
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
feemarketante.NewFeeMarketCheckDecorator( // fee market check replaces fee deduct decorator
options.FeeMarketKeeper,
ante.NewDeductFeeDecorator(
options.AccountKeeper,
options.BankKeeper,
options.FeegrantKeeper,
options.TxFeeChecker,
),
), // fees are deducted in the fee market deduct post handler
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewRedundantRelayDecorator(options.IBCKeeper),
}

return sdk.ChainAnteDecorators(anteDecorators...), nil
}

// DenomResolverImpl is Eve's implementation of x/feemarket's DenomResolver
type DenomResolverImpl struct {
FeeabsKeeper feeabskeeper.Keeper
StakingKeeper feeabstypes.StakingKeeper
}

var _ feemarkettypes.DenomResolver = &DenomResolverImpl{}

// ConvertToDenom converts any given coin to the native denom of the chain or the other way around.
// Return error if neither of coin.Denom and denom is the native denom of the chain.
// If the denom is the bond denom, convert `coin` to the native denom. return error if coin.Denom is not in the allowed list
// If the denom is not the bond denom, convert the `coin` to the given denom. return error if denom is not in the allowed list
func (r *DenomResolverImpl) ConvertToDenom(ctx sdk.Context, coin sdk.DecCoin, denom string) (sdk.DecCoin, error) {
bondDenom, err := r.StakingKeeper.BondDenom(ctx)
if err != nil {
return sdk.DecCoin{}, err
}
if denom != bondDenom && coin.Denom != bondDenom {
return sdk.DecCoin{}, ErrNeitherNativeDenom(coin.Denom, denom)
}
var amount sdk.Coins
var hostZoneConfig feeabstypes.HostChainFeeAbsConfig
var found bool

if denom == bondDenom {
hostZoneConfig, found = r.FeeabsKeeper.GetHostZoneConfig(ctx, coin.Denom)
if !found {
return sdk.DecCoin{}, ErrDenomNotRegistered(coin.Denom)
}
amount, err = r.getIBCCoinFromNative(ctx, sdk.NewCoins(sdk.NewCoin(coin.Denom, coin.Amount.TruncateInt())), hostZoneConfig)
} else if coin.Denom == bondDenom {
hostZoneConfig, found := r.FeeabsKeeper.GetHostZoneConfig(ctx, denom)
if !found {
return sdk.DecCoin{}, ErrDenomNotRegistered(denom)
}
amount, err = r.FeeabsKeeper.CalculateNativeFromIBCCoins(ctx, sdk.NewCoins(sdk.NewCoin(denom, coin.Amount.TruncateInt())), hostZoneConfig)
}

if err != nil {
return sdk.DecCoin{}, err
}
return sdk.NewDecCoinFromDec(denom, amount[0].Amount.ToLegacyDec()), nil
}

// extra denoms should be all denoms that have been registered via governance(host zone)
func (r *DenomResolverImpl) ExtraDenoms(ctx sdk.Context) ([]string, error) {
allHostZoneConfigs, err := r.FeeabsKeeper.GetAllHostZoneConfig(ctx)
if err != nil {
return nil, err
}
denoms := make([]string, 0, len(allHostZoneConfigs))
for _, hostZoneConfig := range allHostZoneConfigs {
denoms = append(denoms, hostZoneConfig.IbcDenom)
}
return denoms, nil
}

// //////////////////////////////////////
// Helper functions for DenomResolver //
// //////////////////////////////////////

func (r *DenomResolverImpl) getIBCCoinFromNative(ctx sdk.Context, nativeCoins sdk.Coins, chainConfig feeabstypes.HostChainFeeAbsConfig) (coins sdk.Coins, err error) {
if len(nativeCoins) != 1 {
return sdk.Coins{}, ErrExpectedOneCoin(len(nativeCoins))
}

nativeCoin := nativeCoins[0]

twapRate, err := r.FeeabsKeeper.GetTwapRate(ctx, chainConfig.IbcDenom)
if err != nil {
return sdk.Coins{}, err
}

// Divide native amount by twap rate to get IBC amount
ibcAmount := nativeCoin.Amount.ToLegacyDec().Quo(twapRate).RoundInt()
ibcCoin := sdk.NewCoin(chainConfig.IbcDenom, ibcAmount)

// Verify the resulting IBC coin
err = r.verifyIBCCoins(ctx, sdk.NewCoins(ibcCoin))
if err != nil {
return sdk.Coins{}, err
}

return sdk.NewCoins(ibcCoin), nil
}

// return err if IBC token isn't in allowed_list
func (r *DenomResolverImpl) verifyIBCCoins(ctx sdk.Context, ibcCoins sdk.Coins) error {
if ibcCoins.Len() != 1 {
return feeabstypes.ErrInvalidIBCFees
}

ibcDenom := ibcCoins[0].Denom
if r.FeeabsKeeper.HasHostZoneConfig(ctx, ibcDenom) {
return nil
}
return feeabstypes.ErrUnsupportedDenom.Wrapf("unsupported denom: %s", ibcDenom)
}
114 changes: 114 additions & 0 deletions app/ante/ante_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package ante

import (
"testing"

"github.com/osmosis-labs/fee-abstraction/v8/x/feeabs/types"
feemarketante "github.com/skip-mev/feemarket/x/feemarket/ante"
feemarkettypes "github.com/skip-mev/feemarket/x/feemarket/types"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"

"cosmossdk.io/errors"
math "cosmossdk.io/math"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

func TestMempoolDecorator(t *testing.T) {
gasLimit := uint64(200000)
minGasPrice := sdk.NewDecCoinsFromCoins(sdk.NewInt64Coin("ueve", feemarkettypes.DefaultMinBaseGasPrice.TruncateInt64()))
validFeeAmount := feemarkettypes.DefaultMinBaseGasPrice.MulInt64(int64(gasLimit))
validFee := sdk.NewCoins(sdk.NewCoin("ueve", validFeeAmount.TruncateInt()))
validIbcFee := sdk.NewCoins(sdk.NewCoin("ibcfee", validFeeAmount.TruncateInt()))
// mockHostZoneConfig is used to mock the host zone config, with ibcfee as the ibc fee denom to be used as alternative fee
mockHostZoneConfig := types.HostChainFeeAbsConfig{
IbcDenom: "ibcfee",
OsmosisPoolTokenDenomIn: "osmosis",
PoolId: 1,
Status: types.HostChainFeeAbsStatus_UPDATED,
}
testCases := []struct {
name string
feeAmount sdk.Coins
malleate func(*AnteTestSuite)
expErr error
}{
{
"empty fee, should fail",
sdk.Coins{},
func(suite *AnteTestSuite) {
},
errors.Wrapf(feemarkettypes.ErrNoFeeCoins, "%s", "got length 0"),
},
{
"valid native fee, should pass",
validFee,
func(suite *AnteTestSuite) {},
nil,
},
{
"valid ibc fee, should pass",
validIbcFee,
func(suite *AnteTestSuite) {
err := suite.feeabsKeeper.SetHostZoneConfig(suite.ctx, mockHostZoneConfig)
require.NoError(t, err)
suite.feeabsKeeper.SetTwapRate(suite.ctx, "ibcfee", math.LegacyNewDec(1))
suite.stakingKeeper.EXPECT().BondDenom(gomock.Any()).Return("ueve", nil).AnyTimes()
},
nil,
},
{
"not enough ibc fee, should fail",
validIbcFee.Sub(sdk.NewCoin("ibcfee", math.NewInt(1))),
func(suite *AnteTestSuite) {
err := suite.feeabsKeeper.SetHostZoneConfig(suite.ctx, mockHostZoneConfig)
require.NoError(t, err)
suite.feeabsKeeper.SetTwapRate(suite.ctx, "ibcfee", math.LegacyNewDec(1))
suite.stakingKeeper.EXPECT().BondDenom(gomock.Any()).Return("ueve", nil).AnyTimes()
},
sdkerrors.ErrInsufficientFee,
},
{
"fee in unsupported denom, should fail",
sdk.NewCoins(sdk.NewCoin("unsupported", validFeeAmount.TruncateInt())),
func(suite *AnteTestSuite) {
suite.stakingKeeper.EXPECT().BondDenom(gomock.Any()).Return("ueve", nil).AnyTimes()
},
ErrDenomNotRegistered("unsupported"),
},
{
"multiple fee denoms, only one supported, should pass",
sdk.NewCoins(validFee[0], sdk.NewCoin("unsupported", math.NewInt(100))),
func(suite *AnteTestSuite) {},
feemarkettypes.ErrTooManyFeeCoins,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
suite := SetupTestSuite(t, true)

tc.malleate(suite)
suite.txBuilder.SetGasLimit(gasLimit)
suite.txBuilder.SetFeeAmount(tc.feeAmount)
suite.ctx = suite.ctx.WithMinGasPrices(minGasPrice)

// Construct tx and run through mempool decorator
tx := suite.txBuilder.GetTx()
feemarketDecorator := feemarketante.NewFeeMarketCheckDecorator(suite.feemarketKeeper, nil)
antehandler := sdk.ChainAnteDecorators(feemarketDecorator)

// Run the ante handler
_, err := antehandler(suite.ctx, tx, false)

if tc.expErr != nil {
require.Error(t, err)
require.ErrorContains(t, err, tc.expErr.Error())
} else {
require.NoError(t, err)
}
})
}
}
Loading

0 comments on commit 709efaa

Please sign in to comment.