Skip to content
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

Merged
merged 12 commits into from
Apr 22, 2024
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/spf13/cast v1.5.1
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4
go.uber.org/mock v0.2.0
golang.org/x/tools v0.6.0
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0
google.golang.org/grpc v1.60.1
Expand Down Expand Up @@ -233,7 +234,7 @@ require (
github.com/gogo/googleapis v1.4.1 // indirect
github.com/golang/glog v1.1.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/mock v1.6.0
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
Expand Down
124 changes: 124 additions & 0 deletions x/feeabs/ante/ante_test.go
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

View workflow job for this annotation

GitHub Actions / lint

File is not `gci`-ed with --skip-generated -s standard -s default -s blank -s dot -s prefix(cosmossdk.io) -s prefix(github.com/cosmos/cosmos-sdk) -s prefix(github.com/cometbft/cometbft) -s prefix(github.com/osmosis-labs/fee-abstraction) --custom-order (gci)
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

View workflow job for this annotation

GitHub Actions / lint

File is not `gci`-ed with --skip-generated -s standard -s default -s blank -s dot -s prefix(cosmossdk.io) -s prefix(github.com/cosmos/cosmos-sdk) -s prefix(github.com/cometbft/cometbft) -s prefix(github.com/osmosis-labs/fee-abstraction) --custom-order (gci)
"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

View workflow job for this annotation

GitHub Actions / lint

File is not `gci`-ed with --skip-generated -s standard -s default -s blank -s dot -s prefix(cosmossdk.io) -s prefix(github.com/cosmos/cosmos-sdk) -s prefix(github.com/cometbft/cometbft) -s prefix(github.com/osmosis-labs/fee-abstraction) --custom-order (gci)
)

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)
}
})
}

Check failure on line 123 in x/feeabs/ante/ante_test.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gofumpt`-ed (gofumpt)
}
6 changes: 3 additions & 3 deletions x/feeabs/ante/decorate.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func (famfd FeeAbstrationMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk

// After replace the feeCoinsNonZeroDenom, feeCoinsNonZeroDenom must be in denom subset of nonZeroCoinFeesReq
if !feeCoinsNonZeroDenom.DenomsSubsetOf(nonZeroCoinFeesReq) {
return ctx, sdkerrors.Wrapf(errorstypes.ErrInsufficientFee, "fee is not a subset of required fees; got %s, required: %s", feeCoins.String(), feeRequired.String())
return ctx, sdkerrors.Wrapf(errorstypes.ErrInvalidCoins, "fee is not a subset of required fees; got %s, required: %s", feeCoins.String(), feeRequired.String())
}

// if the msg does not satisfy bypass condition and the feeCoins denoms are subset of fezeRequired,
Expand All @@ -315,8 +315,8 @@ func (famfd FeeAbstrationMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk
// Not contain zeroCoinFeesDenomReq's denoms
//
// check if the feeCoins has coins' amount higher/equal to nonZeroCoinFeesReq
if !feeCoins.IsAnyGTE(nonZeroCoinFeesReq) {
err := sdkerrors.Wrapf(errorstypes.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, feeRequired)
if !feeCoinsNonZeroDenom.IsAnyGTE(nonZeroCoinFeesReq) {
err := sdkerrors.Wrapf(errorstypes.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoinsNonZeroDenom, nonZeroCoinFeesReq)
Copy link
Collaborator Author

@tuantran1702 tuantran1702 Apr 8, 2024

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 to nonZeroCoinFeeReq, 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.

if byPassExceedMaxGasUsage {
err = sdkerrors.Wrapf(errorstypes.ErrInsufficientFee, "Insufficient fees; bypass-min-fee-msg-types with gas consumption exceeds the maximum allowed gas value.")
}
Expand Down
226 changes: 226 additions & 0 deletions x/feeabs/ante/testutil_test.go
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

View workflow job for this annotation

GitHub Actions / lint

File is not `gci`-ed with --skip-generated -s standard -s default -s blank -s dot -s prefix(cosmossdk.io) -s prefix(github.com/cosmos/cosmos-sdk) -s prefix(github.com/cometbft/cometbft) -s prefix(github.com/osmosis-labs/fee-abstraction) --custom-order (gci)
"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

View workflow job for this annotation

GitHub Actions / lint

File is not `gci`-ed with --skip-generated -s standard -s default -s blank -s dot -s prefix(cosmossdk.io) -s prefix(github.com/cosmos/cosmos-sdk) -s prefix(github.com/cometbft/cometbft) -s prefix(github.com/osmosis-labs/fee-abstraction) --custom-order (gci)
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

View workflow job for this annotation

GitHub Actions / lint

File is not `gci`-ed with --skip-generated -s standard -s default -s blank -s dot -s prefix(cosmossdk.io) -s prefix(github.com/cosmos/cosmos-sdk) -s prefix(github.com/cometbft/cometbft) -s prefix(github.com/osmosis-labs/fee-abstraction) --custom-order (gci)
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))

Check failure on line 173 in x/feeabs/ante/testutil_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `acc.SetAccountNumber` is not checked (errcheck)
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
}
Loading
Loading