From 3ed296dfbe83c41fc58378dea713065a8d4748ce Mon Sep 17 00:00:00 2001 From: emidev98 Date: Tue, 21 Nov 2023 14:25:42 +0200 Subject: [PATCH] feat: feeshare post hanlder tests --- app/app_test/test_helpers.go | 13 +- app/post/mocks/post_mock.go | 92 ++++++++++ go.mod | 1 + go.sum | 3 +- x/feeshare/post/post_test.go | 345 +++++++++++++++++++++++++++++++++++ 5 files changed, 449 insertions(+), 5 deletions(-) create mode 100644 app/post/mocks/post_mock.go diff --git a/app/app_test/test_helpers.go b/app/app_test/test_helpers.go index 7d77b1cd..727c6f88 100644 --- a/app/app_test/test_helpers.go +++ b/app/app_test/test_helpers.go @@ -36,10 +36,11 @@ import ( type AppTestSuite struct { suite.Suite - App *app.TerraApp - Ctx sdk.Context - QueryHelper *baseapp.QueryServiceTestHelper - TestAccs []sdk.AccAddress + App *app.TerraApp + Ctx sdk.Context + QueryHelper *baseapp.QueryServiceTestHelper + TestAccs []sdk.AccAddress + EncodingConfig appparams.EncodingConfig } // Setup sets up basic environment for suite (App, Ctx, and test accounts) @@ -62,6 +63,7 @@ func (s *AppTestSuite) Setup() { simtestutil.EmptyAppOptions{}, wasmconfig.DefaultConfig(), ) + s.EncodingConfig = encCfg s.Ctx = s.App.NewContext(true, tmproto.Header{Height: 1, Time: time.Now()}) s.QueryHelper = &baseapp.QueryServiceTestHelper{ @@ -80,6 +82,9 @@ func (s *AppTestSuite) Setup() { err = s.App.Keepers.TokenFactoryKeeper.SetParams(s.Ctx, tokenfactorytypes.DefaultParams()) s.Require().NoError(err) + err = s.FundModule(authtypes.FeeCollectorName, sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(1000)), sdk.NewCoin("utoken", sdk.NewInt(500)))) + s.Require().NoError(err) + s.App.Keepers.DistrKeeper.SetFeePool(s.Ctx, distrtypes.InitialFeePool()) s.TestAccs = s.CreateRandomAccounts(3) diff --git a/app/post/mocks/post_mock.go b/app/post/mocks/post_mock.go new file mode 100644 index 00000000..c2de3e16 --- /dev/null +++ b/app/post/mocks/post_mock.go @@ -0,0 +1,92 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: types/handler.go +// +// Generated by this command: +// +// mockgen -source=types/handler.go -package=mocks -destination=types/mocks/interfaces.go PostDecorator +// +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + types "github.com/cosmos/cosmos-sdk/types" + gomock "go.uber.org/mock/gomock" +) + +// MockAnteDecorator is a mock of AnteDecorator interface. +type MockAnteDecorator struct { + ctrl *gomock.Controller + recorder *MockAnteDecoratorMockRecorder +} + +// MockAnteDecoratorMockRecorder is the mock recorder for MockAnteDecorator. +type MockAnteDecoratorMockRecorder struct { + mock *MockAnteDecorator +} + +// NewMockAnteDecorator creates a new mock instance. +func NewMockAnteDecorator(ctrl *gomock.Controller) *MockAnteDecorator { + mock := &MockAnteDecorator{ctrl: ctrl} + mock.recorder = &MockAnteDecoratorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAnteDecorator) EXPECT() *MockAnteDecoratorMockRecorder { + return m.recorder +} + +// AnteHandle mocks base method. +func (m *MockAnteDecorator) AnteHandle(ctx types.Context, tx types.Tx, simulate bool, next types.AnteHandler) (types.Context, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AnteHandle", ctx, tx, simulate, next) + ret0, _ := ret[0].(types.Context) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AnteHandle indicates an expected call of AnteHandle. +func (mr *MockAnteDecoratorMockRecorder) AnteHandle(ctx, tx, simulate, next any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnteHandle", reflect.TypeOf((*MockAnteDecorator)(nil).AnteHandle), ctx, tx, simulate, next) +} + +// MockPostDecorator is a mock of PostDecorator interface. +type MockPostDecorator struct { + ctrl *gomock.Controller + recorder *MockPostDecoratorMockRecorder +} + +// MockPostDecoratorMockRecorder is the mock recorder for MockPostDecorator. +type MockPostDecoratorMockRecorder struct { + mock *MockPostDecorator +} + +// NewMockPostDecorator creates a new mock instance. +func NewMockPostDecorator(ctrl *gomock.Controller) *MockPostDecorator { + mock := &MockPostDecorator{ctrl: ctrl} + mock.recorder = &MockPostDecoratorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPostDecorator) EXPECT() *MockPostDecoratorMockRecorder { + return m.recorder +} + +// PostHandle mocks base method. +func (m *MockPostDecorator) PostHandle(ctx types.Context, tx types.Tx, simulate, success bool, next types.PostHandler) (types.Context, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PostHandle", ctx, tx, simulate, success, next) + ret0, _ := ret[0].(types.Context) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PostHandle indicates an expected call of PostHandle. +func (mr *MockPostDecoratorMockRecorder) PostHandle(ctx, tx, simulate, success, next any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostHandle", reflect.TypeOf((*MockPostDecorator)(nil).PostHandle), ctx, tx, simulate, success, next) +} diff --git a/go.mod b/go.mod index 86575911..c55cdccc 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 github.com/terra-money/alliance v0.3.2 + go.uber.org/mock v0.3.0 google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 google.golang.org/grpc v1.58.3 ) diff --git a/go.sum b/go.sum index ad3fe576..9a4932ed 100644 --- a/go.sum +++ b/go.sum @@ -1198,7 +1198,8 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU= +go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= diff --git a/x/feeshare/post/post_test.go b/x/feeshare/post/post_test.go index 6798d6ab..40857834 100644 --- a/x/feeshare/post/post_test.go +++ b/x/feeshare/post/post_test.go @@ -3,13 +3,20 @@ package ante_test import ( "testing" + errorsmod "cosmossdk.io/errors" "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + abci "github.com/cometbft/cometbft/abci/types" app "github.com/terra-money/core/v2/app/app_test" + "github.com/terra-money/core/v2/app/post/mocks" post "github.com/terra-money/core/v2/x/feeshare/post" "github.com/terra-money/core/v2/x/feeshare/types" + customwasmtypes "github.com/terra-money/core/v2/x/wasm/types" ) type AnteTestSuite struct { @@ -49,6 +56,17 @@ func (suite *AnteTestSuite) TestGetWithdrawalAddressFromContract() { }, false, }, + { + "two valid contract addresses with one not registered", + []string{ + "terra1u3z42fpctuhh8mranz4tatacqhty6a8yk7l5wvj7dshsuytcms2qda4f5x", // not registered address + "terra1jwyzzsaag4t0evnuukc35ysyrx9arzdde2kg9cld28alhjurtthq0prs2s", + }, + []sdk.AccAddress{ + sdk.MustAccAddressFromBech32("terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je"), + }, + false, + }, { "without withdrawer contract addresses", []string{"terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa"}, @@ -178,3 +196,330 @@ func (suite *AnteTestSuite) TestCalculateFee() { suite.Require().Equal(tc.expectedFeePayment, feeToBePaid, tc.name) } } + +func (suite *AnteTestSuite) TestPostHandler() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Register the feeshare contract... + suite.App.Keepers.FeeShareKeeper.SetFeeShare(suite.Ctx, types.FeeShare{ + ContractAddress: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + DeployerAddress: "", + WithdrawerAddress: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + }) + // ... append the executed contract addresses in the wasm keeper ... + suite.App.Keepers.WasmKeeper.SetExecutedContractAddresses(suite.Ctx, customwasmtypes.ExecutedContracts{ + ContractAddresses: []string{"terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa"}, + }) + + // build a tx with a fee amount ... + txFee := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetFeeAmount(txFee) + txBuilder.SetMsgs(&wasmtypes.MsgExecuteContract{ + Sender: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + Contract: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + Msg: nil, + Funds: nil, + }) + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + // Remove all events from the context to assert the events being added correctly. + suite.Ctx = suite.Ctx.WithEventManager(sdk.NewEventManager()) + + // Assert the next hanlder is called once + mockedPostDecorator. + EXPECT(). + PostHandle(gomock.Any(), gomock.Any(), false, true, gomock.Any()). + Times(1) + + // Execute the PostHandle function + _, err := handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite.Require().NoError(err) + suite.Require().Equal(suite.Ctx.EventManager().ABCIEvents(), + []abci.Event{ + { + Type: "coin_spent", + Attributes: []abci.EventAttribute{ + {Key: "spender", Value: "terra17xpfvakm2amg962yls6f84z3kell8c5lkaeqfa", Index: false}, + {Key: "amount", Value: "250uluna,125utoken", Index: false}, + }, + }, + { + Type: "coin_received", + Attributes: []abci.EventAttribute{ + {Key: "receiver", Value: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", Index: false}, + {Key: "amount", Value: "250uluna,125utoken", Index: false}, + }, + }, + { + Type: "transfer", + Attributes: []abci.EventAttribute{ + {Key: "recipient", Value: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", Index: false}, + {Key: "sender", Value: "terra17xpfvakm2amg962yls6f84z3kell8c5lkaeqfa", Index: false}, + {Key: "amount", Value: "250uluna,125utoken", Index: false}, + }, + }, + { + Type: "message", + Attributes: []abci.EventAttribute{ + {Key: "sender", Value: "terra17xpfvakm2amg962yls6f84z3kell8c5lkaeqfa", Index: false}, + }, + }, + { + Type: "juno.feeshare.v1.FeePayoutEvent", + Attributes: []abci.EventAttribute{ + {Key: "fees_paid", Value: "[{\"denom\":\"uluna\",\"amount\":\"250\"},{\"denom\":\"utoken\",\"amount\":\"125\"}]", Index: false}, + {Key: "withdraw_address", Value: "\"terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je\"", Index: false}, + }, + }, + }) +} + +func (suite *AnteTestSuite) TestDisabledPostHandle() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Disable the feeshare module... + err := suite.App.Keepers.FeeShareKeeper.SetParams(suite.Ctx, types.Params{ + EnableFeeShare: false, + DeveloperShares: sdk.MustNewDecFromStr("0.5"), + AllowedDenoms: []string{}, + }) + suite.Require().NoError(err) + + // build a tx with a fee amount ... + txFee := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetFeeAmount(txFee) + txBuilder.SetMsgs(&wasmtypes.MsgExecuteContract{}) + + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + + // Assert the next hanlder is called once + mockedPostDecorator. + EXPECT(). + PostHandle(gomock.Any(), gomock.Any(), false, true, gomock.Any()). + Times(1) + + // Execute the PostHandle function + _, err = handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite.Require().NoError(err) +} + +func (suite *AnteTestSuite) TestWithZeroFeesPostHandle() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Build a tx with a fee amount ... + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + + // Assert the next hanlder is called once + mockedPostDecorator. + EXPECT(). + PostHandle(gomock.Any(), gomock.Any(), false, true, gomock.Any()). + Times(1) + + // Execute the PostHandle function + _, err := handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite.Require().NoError(err) +} + +func (suite *AnteTestSuite) TestPostHandlerWithEmptySmartContractStore() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Register the feeshare contract... + suite.App.Keepers.FeeShareKeeper.SetFeeShare(suite.Ctx, types.FeeShare{ + ContractAddress: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + DeployerAddress: "", + WithdrawerAddress: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + }) + + // build a tx with a fee amount ... + txFee := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetFeeAmount(txFee) + txBuilder.SetMsgs(&wasmtypes.MsgExecuteContract{ + Sender: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + Contract: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + Msg: nil, + Funds: nil, + }) + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + + // Assert the next hanlder is called once + mockedPostDecorator. + EXPECT(). + PostHandle(gomock.Any(), gomock.Any(), false, true, gomock.Any()). + Times(1) + + // Execute the PostHandle function + _, err := handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite.Require().NoError(err) +} + +func (suite *AnteTestSuite) TestPostHandlerNoSmartContractExecuted() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Register the feeshare contract... + suite.App.Keepers.FeeShareKeeper.SetFeeShare(suite.Ctx, types.FeeShare{ + ContractAddress: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + DeployerAddress: "", + WithdrawerAddress: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + }) + // ... create the store key ... + suite.App.Keepers.WasmKeeper.SetExecutedContractAddresses(suite.Ctx, customwasmtypes.ExecutedContracts{ + ContractAddresses: []string{}, + }) + + // build a tx with a fee amount ... + txFee := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetFeeAmount(txFee) + txBuilder.SetMsgs(&wasmtypes.MsgExecuteContract{ + Sender: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + Contract: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + Msg: nil, + Funds: nil, + }) + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + + // Assert the next hanlder is called once + mockedPostDecorator. + EXPECT(). + PostHandle(gomock.Any(), gomock.Any(), false, true, gomock.Any()). + Times(1) + + // Execute the PostHandle function + _, err := handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite.Require().NoError(err) +} + +func (suite *AnteTestSuite) TestPostHandlerWithInvalidContractAddrOnExecution() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Register the feeshare contract... + suite.App.Keepers.FeeShareKeeper.SetFeeShare(suite.Ctx, types.FeeShare{ + ContractAddress: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + DeployerAddress: "", + WithdrawerAddress: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + }) + // ... create the store key ... + suite.App.Keepers.WasmKeeper.SetExecutedContractAddresses(suite.Ctx, customwasmtypes.ExecutedContracts{ + ContractAddresses: []string{"invalid_contract_addr"}, + }) + + // build a tx with a fee amount ... + txFee := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetFeeAmount(txFee) + txBuilder.SetMsgs(&wasmtypes.MsgExecuteContract{}) + + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + + // Execute the PostHandle function + _, err := handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite. + Require(). + ErrorIs(err, errorsmod.Wrapf(sdkerrors.ErrLogic, err.Error())) +}