-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
d3f8d2e
commit 709efaa
Showing
12 changed files
with
730 additions
and
185 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.