-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Test: add unit tests for decorators #182
Changes from 3 commits
a356751
c5a31ab
c864502
83af5f6
9a81086
619b3fa
639167f
4345377
7f233a1
080b9e2
a2861fe
b3c60b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package ante_test | ||
|
||
import ( | ||
"testing" | ||
|
||
Check failure on line 5 in x/feeabs/ante/ante_test.go GitHub Actions / lint
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
"github.com/golang/mock/gomock" | ||
Check failure on line 8 in x/feeabs/ante/ante_test.go GitHub Actions / lint
|
||
"github.com/osmosis-labs/fee-abstraction/v7/x/feeabs/ante" | ||
"github.com/osmosis-labs/fee-abstraction/v7/x/feeabs/types" | ||
"github.com/stretchr/testify/require" | ||
Check failure on line 11 in x/feeabs/ante/ante_test.go GitHub Actions / lint
|
||
) | ||
|
||
func TestMempoolDecorator(t *testing.T) { | ||
gasLimit := uint64(200000) | ||
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, 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) | ||
}, | ||
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, 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, false) | ||
// Setup test context | ||
tc.malleate(suite) | ||
suite.txBuilder.SetGasLimit(gasLimit) | ||
suite.txBuilder.SetFeeAmount(tc.feeAmount) | ||
suite.ctx = suite.ctx.WithIsCheckTx(true) | ||
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) | ||
} | ||
}) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
package ante_test | ||
|
||
import ( | ||
"testing" | ||
|
||
Check failure on line 5 in x/feeabs/ante/testutil_test.go GitHub Actions / lint
|
||
"github.com/cosmos/cosmos-sdk/client" | ||
"github.com/cosmos/cosmos-sdk/client/tx" | ||
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/types/tx/signing" | ||
"github.com/cosmos/cosmos-sdk/x/auth" | ||
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" | ||
xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" | ||
"github.com/cosmos/cosmos-sdk/x/auth/types" | ||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" | ||
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" | ||
transferkeeper "github.com/cosmos/ibc-go/v7/modules/apps/transfer/keeper" | ||
Check failure on line 21 in x/feeabs/ante/testutil_test.go GitHub Actions / lint
|
||
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" | ||
"github.com/stretchr/testify/require" | ||
Check failure on line 25 in x/feeabs/ante/testutil_test.go GitHub Actions / lint
|
||
ubermock "go.uber.org/mock/gomock" | ||
) | ||
|
||
// TestAccount represents an account used in the tests in x/auth/ante. | ||
type TestAccount struct { | ||
acc types.AccountI | ||
priv cryptotypes.PrivKey | ||
} | ||
|
||
// AnteTestSuite is a test suite to be used with ante handler tests. | ||
type AnteTestSuite struct { | ||
anteHandler sdk.AnteHandler | ||
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{} | ||
uberCtrl := ubermock.NewController(t) | ||
suite.bankKeeper = feeabstestutil.NewMockBankKeeper(uberCtrl) | ||
suite.stakingKeeper = feeabstestutil.NewMockStakingKeeper(uberCtrl) | ||
suite.feeGrantKeeper = feeabstestutil.NewMockFeegrantKeeper(uberCtrl) | ||
suite.channelKeeper = feeabstestutil.NewMockChannelKeeper(uberCtrl) | ||
suite.portKeeper = feeabstestutil.NewMockPortKeeper(uberCtrl) | ||
suite.scopedKeeper = feeabstestutil.NewMockScopedKeeper(uberCtrl) | ||
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, | ||
} | ||
|
||
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{}) | ||
|
||
// We're using TestMsg encoding in some tests, so register it here. | ||
suite.encCfg.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) | ||
testdata.RegisterInterfaces(suite.encCfg.InterfaceRegistry) | ||
suite.accountKeeper = authkeeper.NewAccountKeeper( | ||
suite.encCfg.Codec, authKey, types.ProtoBaseAccount, maccPerms, sdk.Bech32MainPrefix, types.NewModuleAddress("gov").String(), | ||
) | ||
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) | ||
|
||
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() | ||
|
||
return suite | ||
} | ||
|
||
// TestCase represents a test case used in test tables. | ||
type TestCase struct { | ||
desc string | ||
malleate func(*AnteTestSuite) TestCaseArgs | ||
simulate bool | ||
expPass bool | ||
expErr error | ||
} | ||
|
||
type TestCaseArgs struct { | ||
chainID string | ||
accNums []uint64 | ||
accSeqs []uint64 | ||
feeAmount sdk.Coins | ||
gasLimit uint64 | ||
msgs []sdk.Msg | ||
privs []cryptotypes.PrivKey | ||
} | ||
|
||
// DeliverMsgs constructs a tx and runs it through the ante handler. This is used to set the context for a test case, for | ||
// example to test for replay protection. | ||
func (suite *AnteTestSuite) DeliverMsgs(t *testing.T, privs []cryptotypes.PrivKey, msgs []sdk.Msg, feeAmount sdk.Coins, gasLimit uint64, accNums, accSeqs []uint64, chainID string, simulate bool) (sdk.Context, error) { | ||
t.Helper() | ||
require.NoError(t, suite.txBuilder.SetMsgs(msgs...)) | ||
suite.txBuilder.SetFeeAmount(feeAmount) | ||
suite.txBuilder.SetGasLimit(gasLimit) | ||
|
||
tx, txErr := suite.CreateTestTx(privs, accNums, accSeqs, chainID) | ||
require.NoError(t, txErr) | ||
return suite.anteHandler(suite.ctx, tx, simulate) | ||
} | ||
|
||
func (suite *AnteTestSuite) RunTestCase(t *testing.T, tc TestCase, args TestCaseArgs) { | ||
t.Helper() | ||
require.NoError(t, suite.txBuilder.SetMsgs(args.msgs...)) | ||
suite.txBuilder.SetFeeAmount(args.feeAmount) | ||
suite.txBuilder.SetGasLimit(args.gasLimit) | ||
// Theoretically speaking, ante handler unit tests should only test | ||
// ante handlers, but here we sometimes also test the tx creation | ||
// process. | ||
tx, txErr := suite.CreateTestTx(args.privs, args.accNums, args.accSeqs, args.chainID) | ||
newCtx, anteErr := suite.anteHandler(suite.ctx, tx, tc.simulate) | ||
|
||
if tc.expPass { | ||
require.NoError(t, txErr) | ||
require.NoError(t, anteErr) | ||
require.NotNil(t, newCtx) | ||
|
||
suite.ctx = newCtx | ||
} else { | ||
switch { | ||
case txErr != nil: | ||
require.Error(t, txErr) | ||
require.ErrorIs(t, txErr, tc.expErr) | ||
|
||
case anteErr != nil: | ||
require.Error(t, anteErr) | ||
require.ErrorIs(t, anteErr, tc.expErr) | ||
|
||
default: | ||
t.Fatal("expected one of txErr, anteErr to be an error") | ||
} | ||
} | ||
} | ||
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) | ||
acc.SetAccountNumber(uint64(i)) | ||
suite.accountKeeper.SetAccount(suite.ctx, acc) | ||
accounts = append(accounts, TestAccount{acc, priv}) | ||
} | ||
|
||
return accounts | ||
} | ||
|
||
// CreateTestTx is a helper function to create a tx given multiple inputs. | ||
func (suite *AnteTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) { | ||
// First round: we gather all the signer infos. We use the "set empty | ||
// signature" hack to do that. | ||
var sigsV2 []signing.SignatureV2 | ||
for i, priv := range privs { | ||
sigV2 := signing.SignatureV2{ | ||
PubKey: priv.PubKey(), | ||
Data: &signing.SingleSignatureData{ | ||
SignMode: suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), | ||
Signature: nil, | ||
}, | ||
Sequence: accSeqs[i], | ||
} | ||
|
||
sigsV2 = append(sigsV2, sigV2) | ||
} | ||
err := suite.txBuilder.SetSignatures(sigsV2...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Second round: all signer infos are set, so each signer can sign. | ||
sigsV2 = []signing.SignatureV2{} | ||
for i, priv := range privs { | ||
signerData := xauthsigning.SignerData{ | ||
ChainID: chainID, | ||
AccountNumber: accNums[i], | ||
Sequence: accSeqs[i], | ||
} | ||
sigV2, err := tx.SignWithPrivKey( | ||
suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData, | ||
suite.txBuilder, priv, suite.clientCtx.TxConfig, accSeqs[i]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
sigsV2 = append(sigsV2, sigV2) | ||
} | ||
err = suite.txBuilder.SetSignatures(sigsV2...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return suite.txBuilder.GetTx(), nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feeCoins
is the initial fees(which might be an ibc denom), so when compare tononZeroCoinFeeReq
, which are the amount of fee required in native token, would fail if provided fee is IBC denom.feeCoinsNonZeroDenom
is calculated from feeCoins and TwapPrices here, and also alway in native denom, so have to use it instead.