-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Test: add unit tests for decorators (#182)
* wip: setup mocks for ante test * remove mock ante handler * add test for ibc fee * linting * add comment on unimplemented feature * extract duplicated code * chore: refactor code and add comments * test: add test for FeeabsDeductFee decorator * linting * remove unused code
- Loading branch information
1 parent
b879162
commit f2241e6
Showing
6 changed files
with
984 additions
and
7 deletions.
There are no files selected for viewing
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
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,236 @@ | ||
package ante_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/golang/mock/gomock" | ||
"github.com/stretchr/testify/require" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" | ||
|
||
"github.com/osmosis-labs/fee-abstraction/v7/x/feeabs/ante" | ||
"github.com/osmosis-labs/fee-abstraction/v7/x/feeabs/types" | ||
) | ||
|
||
func TestMempoolDecorator(t *testing.T) { | ||
gasLimit := uint64(200000) | ||
// 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, | ||
MinSwapAmount: 0, | ||
} | ||
testCases := []struct { | ||
name string | ||
feeAmount sdk.Coins | ||
minGasPrice sdk.DecCoins | ||
malleate func(*AnteTestSuite) | ||
isErr bool | ||
expErr error | ||
}{ | ||
{ | ||
"empty fee, should fail", | ||
sdk.Coins{}, | ||
sdk.NewDecCoinsFromCoins(sdk.NewCoins(sdk.NewInt64Coin("native", 100))...), | ||
func(suite *AnteTestSuite) { | ||
}, | ||
true, | ||
sdkerrors.ErrInsufficientFee, | ||
}, | ||
{ | ||
"not enough native fee, should fail", | ||
sdk.NewCoins(sdk.NewInt64Coin("native", 100)), | ||
sdk.NewDecCoinsFromCoins(sdk.NewCoins(sdk.NewInt64Coin("native", 1000))...), | ||
func(suite *AnteTestSuite) {}, | ||
true, | ||
sdkerrors.ErrInsufficientFee, | ||
}, | ||
{ | ||
"enough native fee, should pass", | ||
sdk.NewCoins(sdk.NewInt64Coin("native", 1000*int64(gasLimit))), | ||
sdk.NewDecCoinsFromCoins(sdk.NewCoins(sdk.NewInt64Coin("native", 1000))...), | ||
func(suite *AnteTestSuite) {}, | ||
false, | ||
nil, | ||
}, | ||
{ | ||
"unknown ibc fee denom, should fail", | ||
sdk.NewCoins(sdk.NewInt64Coin("ibcfee", 1000*int64(gasLimit))), | ||
sdk.NewDecCoinsFromCoins(sdk.NewCoins(sdk.NewInt64Coin("native", 1000))...), | ||
func(suite *AnteTestSuite) {}, | ||
true, | ||
sdkerrors.ErrInvalidCoins, | ||
}, | ||
{ | ||
"not enough ibc fee, should fail", | ||
sdk.NewCoins(sdk.NewInt64Coin("ibcfee", 999*int64(gasLimit))), | ||
sdk.NewDecCoinsFromCoins(sdk.NewCoins(sdk.NewInt64Coin("native", 1000))...), | ||
func(suite *AnteTestSuite) { | ||
err := suite.feeabsKeeper.SetHostZoneConfig(suite.ctx, mockHostZoneConfig) | ||
require.NoError(t, err) | ||
suite.feeabsKeeper.SetTwapRate(suite.ctx, "ibcfee", sdk.NewDec(1)) | ||
suite.stakingKeeper.EXPECT().BondDenom(gomock.Any()).Return("native").MinTimes(1) | ||
}, | ||
true, | ||
sdkerrors.ErrInsufficientFee, | ||
}, | ||
|
||
{ | ||
"enough ibc fee, should pass", | ||
sdk.NewCoins(sdk.NewInt64Coin("ibcfee", 1000*int64(gasLimit))), | ||
sdk.NewDecCoinsFromCoins(sdk.NewCoins(sdk.NewInt64Coin("native", 1000))...), | ||
func(suite *AnteTestSuite) { | ||
err := suite.feeabsKeeper.SetHostZoneConfig(suite.ctx, mockHostZoneConfig) | ||
require.NoError(t, err) | ||
suite.feeabsKeeper.SetTwapRate(suite.ctx, "ibcfee", sdk.NewDec(1)) | ||
suite.stakingKeeper.EXPECT().BondDenom(gomock.Any()).Return("native").MinTimes(1) | ||
}, | ||
false, | ||
nil, | ||
}, | ||
// TODO: Add support for multiple denom fees(--fees 50ibc,50native) | ||
// { | ||
// "half native fee, half ibc fee, should pass", | ||
// sdk.NewCoins(sdk.NewInt64Coin("native", 500*int64(gasLimit)), sdk.NewInt64Coin("ibcfee", 500*int64(gasLimit))), | ||
// sdk.NewDecCoinsFromCoins(sdk.NewCoins(sdk.NewInt64Coin("native", 1000))...), | ||
// func(suite *AnteTestSuite) { | ||
// err := suite.feeabsKeeper.SetHostZoneConfig(suite.ctx, types.HostChainFeeAbsConfig{ | ||
// IbcDenom: "ibcfee", | ||
// OsmosisPoolTokenDenomIn: "osmosis", | ||
// PoolId: 1, | ||
// Status: types.HostChainFeeAbsStatus_UPDATED, | ||
// MinSwapAmount: 0, | ||
// }) | ||
// require.NoError(t, err) | ||
// suite.feeabsKeeper.SetTwapRate(suite.ctx, "ibcfee", sdk.NewDec(1)) | ||
// suite.stakingKeeper.EXPECT().BondDenom(gomock.Any()).Return("native").MinTimes(1) | ||
// }, | ||
// false, | ||
// nil, | ||
// }, | ||
} | ||
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(tc.minGasPrice) | ||
|
||
// Construct tx and run through mempool decorator | ||
tx := suite.txBuilder.GetTx() | ||
mempoolDecorator := ante.NewFeeAbstrationMempoolFeeDecorator(suite.feeabsKeeper) | ||
antehandler := sdk.ChainAnteDecorators(mempoolDecorator) | ||
|
||
// Run the ante handler | ||
_, err := antehandler(suite.ctx, tx, false) | ||
|
||
if tc.isErr { | ||
require.Error(t, err) | ||
require.ErrorIs(t, err, tc.expErr) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestDeductFeeDecorator(t *testing.T) { | ||
gasLimit := uint64(200000) | ||
minGasPrice := sdk.NewDecCoinsFromCoins(sdk.NewCoins(sdk.NewInt64Coin("native", 1000))...) | ||
feeAmount := sdk.NewCoins(sdk.NewInt64Coin("native", 1000*int64(gasLimit))) | ||
ibcFeeAmount := sdk.NewCoins(sdk.NewInt64Coin("ibcfee", 1000*int64(gasLimit))) | ||
// 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, | ||
MinSwapAmount: 0, | ||
} | ||
testCases := []struct { | ||
name string | ||
malleate func(*AnteTestSuite) | ||
isErr bool | ||
expErr error | ||
}{ | ||
{ | ||
"not enough native fee in balance, should fail", | ||
func(suite *AnteTestSuite) { | ||
suite.feeabsKeeper.SetTwapRate(suite.ctx, "ibcfee", sdk.NewDec(1)) | ||
// suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), types.ModuleName, feeAmount).Return(sdkerrors.ErrInsufficientFee).MinTimes(1) | ||
suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), authtypes.FeeCollectorName, feeAmount).Return(sdkerrors.ErrInsufficientFee).MinTimes(1) | ||
}, | ||
true, | ||
sdkerrors.ErrInsufficientFunds, | ||
}, | ||
{ | ||
"enough native fee in balance, should pass", | ||
func(suite *AnteTestSuite) { | ||
suite.feeabsKeeper.SetTwapRate(suite.ctx, "ibcfee", sdk.NewDec(1)) | ||
suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), authtypes.FeeCollectorName, feeAmount).Return(nil).MinTimes(1) | ||
}, | ||
false, | ||
nil, | ||
}, | ||
{ | ||
"not enough ibc fee in balance, should fail", | ||
func(suite *AnteTestSuite) { | ||
err := suite.feeabsKeeper.SetHostZoneConfig(suite.ctx, mockHostZoneConfig) | ||
require.NoError(t, err) | ||
suite.feeabsKeeper.SetTwapRate(suite.ctx, "ibcfee", sdk.NewDec(1)) | ||
suite.txBuilder.SetFeeAmount(ibcFeeAmount) | ||
suite.stakingKeeper.EXPECT().BondDenom(gomock.Any()).Return("native").MinTimes(1) | ||
suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), types.ModuleName, ibcFeeAmount).Return(sdkerrors.ErrInsufficientFunds).MinTimes(1) | ||
}, | ||
true, | ||
sdkerrors.ErrInsufficientFunds, | ||
}, | ||
{ | ||
"enough ibc fee in balance, should pass", | ||
func(suite *AnteTestSuite) { | ||
err := suite.feeabsKeeper.SetHostZoneConfig(suite.ctx, mockHostZoneConfig) | ||
require.NoError(t, err) | ||
suite.feeabsKeeper.SetTwapRate(suite.ctx, "ibcfee", sdk.NewDec(1)) | ||
suite.txBuilder.SetFeeAmount(ibcFeeAmount) | ||
suite.stakingKeeper.EXPECT().BondDenom(gomock.Any()).Return("native").MinTimes(1) | ||
suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), types.ModuleName, ibcFeeAmount).Return(nil).MinTimes(1) | ||
suite.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), authtypes.FeeCollectorName, feeAmount).Return(nil).MinTimes(1) | ||
}, | ||
false, | ||
nil, | ||
}, | ||
} | ||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
suite := SetupTestSuite(t, false) | ||
acc := suite.CreateTestAccounts(1)[0] | ||
// default value for gasLimit, feeAmount, feePayer. Use native token fee as default | ||
suite.txBuilder.SetGasLimit(gasLimit) | ||
suite.txBuilder.SetFeeAmount(feeAmount) | ||
suite.txBuilder.SetFeePayer(acc.acc.GetAddress()) | ||
suite.ctx = suite.ctx.WithMinGasPrices(minGasPrice) | ||
|
||
// mallate the test case, e.g. setup to pay fee in IBC token | ||
tc.malleate(suite) | ||
|
||
// Construct tx and run through mempool decorator | ||
tx := suite.txBuilder.GetTx() | ||
deductFeeDecorator := ante.NewFeeAbstractionDeductFeeDecorate(suite.accountKeeper, suite.bankKeeper, suite.feeabsKeeper, suite.feeGrantKeeper) | ||
antehandler := sdk.ChainAnteDecorators(deductFeeDecorator) | ||
_, err := antehandler(suite.ctx, tx, false) | ||
|
||
if tc.isErr { | ||
require.Error(t, err) | ||
require.ErrorIs(t, err, tc.expErr) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} |
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
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,120 @@ | ||
package ante_test | ||
|
||
import ( | ||
"testing" | ||
|
||
transferkeeper "github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper" | ||
"github.com/stretchr/testify/require" | ||
ubermock "go.uber.org/mock/gomock" | ||
|
||
"github.com/cosmos/cosmos-sdk/client" | ||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" | ||
storetypes "github.com/cosmos/cosmos-sdk/store/types" | ||
"github.com/cosmos/cosmos-sdk/testutil" | ||
"github.com/cosmos/cosmos-sdk/testutil/testdata" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" | ||
"github.com/cosmos/cosmos-sdk/x/auth" | ||
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" | ||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" | ||
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" | ||
|
||
feeabskeeper "github.com/osmosis-labs/fee-abstraction/v7/x/feeabs/keeper" | ||
feeabstestutil "github.com/osmosis-labs/fee-abstraction/v7/x/feeabs/testutil" | ||
feeabstypes "github.com/osmosis-labs/fee-abstraction/v7/x/feeabs/types" | ||
) | ||
|
||
// TestAccount represents an account used in the tests in x/auth/ante. | ||
type TestAccount struct { | ||
acc authtypes.AccountI | ||
priv cryptotypes.PrivKey | ||
} | ||
|
||
// AnteTestSuite is a test suite to be used with ante handler tests. | ||
type AnteTestSuite struct { | ||
ctx sdk.Context | ||
clientCtx client.Context | ||
txBuilder client.TxBuilder | ||
accountKeeper authkeeper.AccountKeeper | ||
bankKeeper *feeabstestutil.MockBankKeeper | ||
feeGrantKeeper *feeabstestutil.MockFeegrantKeeper | ||
stakingKeeper *feeabstestutil.MockStakingKeeper | ||
feeabsKeeper feeabskeeper.Keeper | ||
channelKeeper *feeabstestutil.MockChannelKeeper | ||
portKeeper *feeabstestutil.MockPortKeeper | ||
scopedKeeper *feeabstestutil.MockScopedKeeper | ||
encCfg moduletestutil.TestEncodingConfig | ||
} | ||
|
||
// SetupTest setups a new test, with new app, context, and anteHandler. | ||
func SetupTestSuite(t *testing.T, isCheckTx bool) *AnteTestSuite { | ||
t.Helper() | ||
suite := &AnteTestSuite{} | ||
ctrl := ubermock.NewController(t) | ||
|
||
// Setup mock keepers | ||
suite.bankKeeper = feeabstestutil.NewMockBankKeeper(ctrl) | ||
suite.stakingKeeper = feeabstestutil.NewMockStakingKeeper(ctrl) | ||
suite.feeGrantKeeper = feeabstestutil.NewMockFeegrantKeeper(ctrl) | ||
suite.channelKeeper = feeabstestutil.NewMockChannelKeeper(ctrl) | ||
suite.portKeeper = feeabstestutil.NewMockPortKeeper(ctrl) | ||
suite.scopedKeeper = feeabstestutil.NewMockScopedKeeper(ctrl) | ||
|
||
// setup necessary params for Account Keeper | ||
key := sdk.NewKVStoreKey(feeabstypes.StoreKey) | ||
authKey := sdk.NewKVStoreKey(authtypes.StoreKey) | ||
subspace := paramtypes.NewSubspace(nil, nil, nil, nil, "feeabs") | ||
subspace = subspace.WithKeyTable(feeabstypes.ParamKeyTable()) | ||
maccPerms := map[string][]string{ | ||
"fee_collector": nil, | ||
"mint": {"minter"}, | ||
"bonded_tokens_pool": {"burner", "staking"}, | ||
"not_bonded_tokens_pool": {"burner", "staking"}, | ||
"multiPerm": {"burner", "minter", "staking"}, | ||
"random": {"random"}, | ||
"feeabs": nil, | ||
} | ||
|
||
// setup context for Account Keeper | ||
testCtx := testutil.DefaultContextWithDB(t, key, sdk.NewTransientStoreKey("transient_test")) | ||
testCtx.CMS.MountStoreWithDB(authKey, storetypes.StoreTypeIAVL, testCtx.DB) | ||
testCtx.CMS.MountStoreWithDB(sdk.NewTransientStoreKey("transient_test2"), storetypes.StoreTypeTransient, testCtx.DB) | ||
err := testCtx.CMS.LoadLatestVersion() | ||
require.NoError(t, err) | ||
suite.ctx = testCtx.Ctx.WithIsCheckTx(isCheckTx).WithBlockHeight(1) // app.BaseApp.NewContext(isCheckTx, tmproto.Header{}).WithBlockHeight(1) | ||
|
||
suite.encCfg = moduletestutil.MakeTestEncodingConfig(auth.AppModuleBasic{}) | ||
suite.encCfg.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) | ||
testdata.RegisterInterfaces(suite.encCfg.InterfaceRegistry) | ||
suite.accountKeeper = authkeeper.NewAccountKeeper( | ||
suite.encCfg.Codec, authKey, authtypes.ProtoBaseAccount, maccPerms, sdk.Bech32MainPrefix, authtypes.NewModuleAddress("gov").String(), | ||
) | ||
suite.accountKeeper.SetModuleAccount(suite.ctx, authtypes.NewEmptyModuleAccount(feeabstypes.ModuleName)) | ||
// Setup feeabs keeper | ||
suite.feeabsKeeper = feeabskeeper.NewKeeper(suite.encCfg.Codec, key, subspace, suite.stakingKeeper, suite.accountKeeper, nil, transferkeeper.Keeper{}, suite.channelKeeper, suite.portKeeper, suite.scopedKeeper) | ||
suite.clientCtx = client.Context{}. | ||
WithTxConfig(suite.encCfg.TxConfig) | ||
require.NoError(t, err) | ||
|
||
// setup txBuilder | ||
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() | ||
|
||
return suite | ||
} | ||
|
||
func (suite *AnteTestSuite) CreateTestAccounts(numAccs int) []TestAccount { | ||
var accounts []TestAccount | ||
|
||
for i := 0; i < numAccs; i++ { | ||
priv, _, addr := testdata.KeyTestPubAddr() | ||
acc := suite.accountKeeper.NewAccountWithAddress(suite.ctx, addr) | ||
err := acc.SetAccountNumber(uint64(i)) | ||
if err != nil { | ||
panic(err) | ||
} | ||
suite.accountKeeper.SetAccount(suite.ctx, acc) | ||
accounts = append(accounts, TestAccount{acc, priv}) | ||
} | ||
|
||
return accounts | ||
} |
Oops, something went wrong.