From a5e96b247c55114e95a765256c76ca519605e8ad Mon Sep 17 00:00:00 2001 From: stepit <48993133+0xstepit@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:31:32 +0100 Subject: [PATCH] test(evm): Add tests for bank wrapper (#65) --- CHANGELOG.md | 2 + go.mod | 3 +- go.sum | 2 + x/erc20/keeper/msg_server_test.go | 2 +- x/erc20/types/mocks/BankKeeper.go | 2 +- x/evm/types/scaling.go | 3 + x/evm/wrappers/bank_test.go | 752 ++++++++++++++++++++++++++++++ x/evm/wrappers/testutil/mock.go | 256 ++++++++++ 8 files changed, 1019 insertions(+), 3 deletions(-) create mode 100644 x/evm/wrappers/bank_test.go create mode 100644 x/evm/wrappers/testutil/mock.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 03eac414..0bb0c41c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ This changelog was created using the `clu` binary (https://github.com/MalteHerrmann/changelog-utils). --> + # Changelog ## Unreleased @@ -30,3 +31,4 @@ This changelog was created using the `clu` binary - (proto) [#14](https://github.com/evmos/os/pull/14) Add Protobufs and adjust scripts. - (eip-712) [#13](https://github.com/evmos/os/pull/13) Add EIP-712 package. - (ci) [#12](https://github.com/evmos/os/pull/12) Add CI workflows, configurations, Makefile, License, etc. +- (tests) [#65](https://github.com/evmos/os/pull/65) Add tests for bank wrapper in EVM module with mock. diff --git a/go.mod b/go.mod index bda0086d..d5621079 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,6 @@ require ( github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf github.com/ethereum/go-ethereum v1.11.5 github.com/evmos/os/example_chain v0.0.0-20240924163020-b2a4187dad50 - github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.4 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 @@ -48,6 +47,7 @@ require ( github.com/tidwall/sjson v1.2.5 github.com/tyler-smith/go-bip39 v1.1.0 github.com/zondax/hid v0.9.2 + go.uber.org/mock v0.5.0 golang.org/x/crypto v0.29.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/net v0.31.0 @@ -135,6 +135,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/mock v1.6.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/flatbuffers v23.5.26+incompatible // indirect diff --git a/go.sum b/go.sum index 69756fe6..525f4a78 100644 --- a/go.sum +++ b/go.sum @@ -1194,6 +1194,8 @@ 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/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 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/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= diff --git a/x/erc20/keeper/msg_server_test.go b/x/erc20/keeper/msg_server_test.go index 563fff6e..238928dc 100644 --- a/x/erc20/keeper/msg_server_test.go +++ b/x/erc20/keeper/msg_server_test.go @@ -15,8 +15,8 @@ import ( erc20mocks "github.com/evmos/os/x/erc20/types/mocks" "github.com/evmos/os/x/evm/statedb" evmtypes "github.com/evmos/os/x/evm/types" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/mock" + gomock "go.uber.org/mock/gomock" ) func (suite *KeeperTestSuite) TestConvertERC20NativeERC20() { diff --git a/x/erc20/types/mocks/BankKeeper.go b/x/erc20/types/mocks/BankKeeper.go index a4582cdd..664ff49f 100644 --- a/x/erc20/types/mocks/BankKeeper.go +++ b/x/erc20/types/mocks/BankKeeper.go @@ -12,7 +12,7 @@ import ( query "github.com/cosmos/cosmos-sdk/types/query" keeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" types0 "github.com/cosmos/cosmos-sdk/x/bank/types" - gomock "github.com/golang/mock/gomock" + gomock "go.uber.org/mock/gomock" ) // BankKeeper is a mock of BankKeeper interface. diff --git a/x/evm/types/scaling.go b/x/evm/types/scaling.go index b9bc3bdd..7750457c 100644 --- a/x/evm/types/scaling.go +++ b/x/evm/types/scaling.go @@ -14,6 +14,9 @@ import ( // MustConvertEvmCoinTo18Decimals converts the coin's Amount from its original // representation into a 18 decimals. The function panics if coin denom is // not the evm denom or in case of overflow. +// +// CONTRACT: The function should only be called when the coin denom is the EVM. This means that +// should be called only when the code forces the denom to be the expected one. func MustConvertEvmCoinTo18Decimals(coin sdk.Coin) sdk.Coin { if coin.Denom != GetEVMCoinDenom() { panic(fmt.Sprintf("expected evm denom %s, received %s", GetEVMCoinDenom(), coin.Denom)) diff --git a/x/evm/wrappers/bank_test.go b/x/evm/wrappers/bank_test.go new file mode 100644 index 00000000..b11bfeac --- /dev/null +++ b/x/evm/wrappers/bank_test.go @@ -0,0 +1,752 @@ +package wrappers_test + +import ( + "context" + "errors" + "fmt" + "math/big" + "testing" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + evmtypes "github.com/evmos/os/x/evm/types" + "github.com/evmos/os/x/evm/wrappers" + "github.com/evmos/os/x/evm/wrappers/testutil" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +// --------------------------------------TRANSACTIONS----------------------------------------------- + +const TokenDenom = "token" + +func TestMintAmountToAccount(t *testing.T) { + testCases := []struct { + name string + evmDenom string + evmDecimals uint8 + amount *big.Int + recipient sdk.AccAddress + expectErr string + mockSetup func(*testutil.MockBankWrapper) + }{ + { + name: "success - convert 18 decimals amount to 6 decimals", + evmDenom: TokenDenom, + evmDecimals: 6, + amount: big.NewInt(1e18), // 1 token in 18 decimals + recipient: sdk.AccAddress([]byte("test_address")), + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoin := sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e6)) // 1 token in 6 decimals + expectedCoins := sdk.NewCoins(expectedCoin) + + mbk.EXPECT(). + MintCoins(gomock.Any(), evmtypes.ModuleName, expectedCoins). + Return(nil) + + mbk.EXPECT(). + SendCoinsFromModuleToAccount( + gomock.Any(), + evmtypes.ModuleName, + sdk.AccAddress([]byte("test_address")), + expectedCoins, + ).Return(nil) + }, + }, + { + name: "success - 18 decimals amount not modified", + evmDenom: TokenDenom, + evmDecimals: 18, + amount: big.NewInt(1e18), // 1 token in 18 decimals + recipient: sdk.AccAddress([]byte("test_address")), + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoin := sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)) + expectedCoins := sdk.NewCoins(expectedCoin) + + mbk.EXPECT(). + MintCoins(gomock.Any(), evmtypes.ModuleName, expectedCoins). + Return(nil) + + mbk.EXPECT(). + SendCoinsFromModuleToAccount( + gomock.Any(), + evmtypes.ModuleName, + sdk.AccAddress([]byte("test_address")), + expectedCoins, + ).Return(nil) + }, + }, + { + name: "fail - mint coins error", + evmDenom: TokenDenom, + evmDecimals: 6, + amount: big.NewInt(1e18), + recipient: sdk.AccAddress([]byte("test_address")), + expectErr: "failed to mint coins to account in bank wrapper", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoin := sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e6)) + expectedCoins := sdk.NewCoins(expectedCoin) + + mbk.EXPECT(). + MintCoins(gomock.Any(), evmtypes.ModuleName, expectedCoins). + Return(errors.New("mint error")) + }, + }, + { + name: "fail - send coins error", + evmDenom: "evm", + evmDecimals: 6, + amount: big.NewInt(1e18), + recipient: sdk.AccAddress([]byte("test_address")), + expectErr: "send error", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoin := sdk.NewCoin("evm", sdkmath.NewInt(1e6)) + expectedCoins := sdk.NewCoins(expectedCoin) + + mbk.EXPECT(). + MintCoins(gomock.Any(), evmtypes.ModuleName, expectedCoins). + Return(nil) + + mbk.EXPECT(). + SendCoinsFromModuleToAccount( + gomock.Any(), + evmtypes.ModuleName, + sdk.AccAddress([]byte("test_address")), + expectedCoins, + ).Return(errors.New("send error")) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup EVM configurator to have access to the EVM coin info. + configurator := evmtypes.NewEVMConfigurator() + configurator.ResetTestConfig() + err := configurator.WithEVMCoinInfo(tc.evmDenom, tc.evmDecimals).Configure() + require.NoError(t, err, "failed to configure EVMConfigurator") + + // Setup mock controller + ctrl := gomock.NewController(t) + + mockBankKeeper := testutil.NewMockBankWrapper(ctrl) + tc.mockSetup(mockBankKeeper) + + bankWrapper := wrappers.NewBankWrapper(mockBankKeeper) + err = bankWrapper.MintAmountToAccount(context.Background(), tc.recipient, tc.amount) + + if tc.expectErr != "" { + require.ErrorContains(t, err, tc.expectErr) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestBurnAmountFromAccount(t *testing.T) { + account := sdk.AccAddress([]byte("test_address")) + + testCases := []struct { + name string + evmDenom string + evmDecimals uint8 + amount *big.Int + expectErr string + mockSetup func(*testutil.MockBankWrapper) + }{ + { + name: "success - convert 18 decimals amount to 6 decimals", + evmDenom: TokenDenom, + evmDecimals: 6, + amount: big.NewInt(1e18), + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoin := sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e6)) + expectedCoins := sdk.NewCoins(expectedCoin) + + mbk.EXPECT(). + SendCoinsFromAccountToModule( + gomock.Any(), + account, + evmtypes.ModuleName, + expectedCoins, + ).Return(nil) + + mbk.EXPECT(). + BurnCoins(gomock.Any(), evmtypes.ModuleName, expectedCoins). + Return(nil) + }, + }, + { + name: "success - 18 decimals amount not modified", + evmDenom: TokenDenom, + evmDecimals: 18, + amount: big.NewInt(1e18), + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoin := sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)) + expectedCoins := sdk.NewCoins(expectedCoin) + + mbk.EXPECT(). + SendCoinsFromAccountToModule( + gomock.Any(), + account, + evmtypes.ModuleName, + expectedCoins, + ).Return(nil) + + mbk.EXPECT(). + BurnCoins(gomock.Any(), evmtypes.ModuleName, expectedCoins). + Return(nil) + }, + }, + { + name: "fail - send coins error", + evmDenom: TokenDenom, + evmDecimals: 6, + amount: big.NewInt(1e18), + expectErr: "failed to burn coins from account in bank wrapper", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoin := sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e6)) + expectedCoins := sdk.NewCoins(expectedCoin) + + mbk.EXPECT(). + SendCoinsFromAccountToModule( + gomock.Any(), + account, + evmtypes.ModuleName, + expectedCoins, + ).Return(errors.New("send error")) + }, + }, + { + name: "fail - send burn error", + evmDenom: TokenDenom, + evmDecimals: 6, + amount: big.NewInt(1e18), + expectErr: "burn error", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoin := sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e6)) + expectedCoins := sdk.NewCoins(expectedCoin) + + mbk.EXPECT(). + SendCoinsFromAccountToModule( + gomock.Any(), + account, + evmtypes.ModuleName, + expectedCoins, + ).Return(errors.New("burn error")) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup EVM configurator to have access to the EVM coin info. + configurator := evmtypes.NewEVMConfigurator() + configurator.ResetTestConfig() + err := configurator.WithEVMCoinInfo(tc.evmDenom, tc.evmDecimals).Configure() + require.NoError(t, err, "failed to configure EVMConfigurator") + + // Setup mock controller + ctrl := gomock.NewController(t) + + mockBankKeeper := testutil.NewMockBankWrapper(ctrl) + tc.mockSetup(mockBankKeeper) + + bankWrapper := wrappers.NewBankWrapper(mockBankKeeper) + err = bankWrapper.BurnAmountFromAccount(context.Background(), account, tc.amount) + + if tc.expectErr != "" { + require.ErrorContains(t, err, tc.expectErr) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestSendCoinsFromModuleToAccount(t *testing.T) { + account := sdk.AccAddress([]byte("test_address")) + + testCases := []struct { + name string + evmDenom string + evmDecimals uint8 + coins func() sdk.Coins + expectErr string + mockSetup func(*testutil.MockBankWrapper) + }{ + { + name: "success - does not convert 18 decimals amount with single token", + evmDenom: TokenDenom, + evmDecimals: 18, + coins: func() sdk.Coins { + coins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + }...) + return coins + }, + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + }...) + + mbk.EXPECT(). + SendCoinsFromModuleToAccount( + gomock.Any(), + evmtypes.ModuleName, + account, + expectedCoins, + ).Return(nil) + }, + }, + { + name: "success - convert 18 decimals amount to 6 decimals with single token", + evmDenom: TokenDenom, + evmDecimals: 6, + coins: func() sdk.Coins { + coins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + }...) + return coins + }, + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e6)), + }...) + + mbk.EXPECT(). + SendCoinsFromModuleToAccount( + gomock.Any(), + evmtypes.ModuleName, + account, + expectedCoins, + ).Return(nil) + }, + }, + { + name: "success - does not convert 18 decimals amount with multiple tokens", + evmDenom: TokenDenom, + evmDecimals: 18, + coins: func() sdk.Coins { + coins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + sdk.NewCoin("something", sdkmath.NewInt(3e18)), + }...) + return coins + }, + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + sdk.NewCoin("something", sdkmath.NewInt(3e18)), + }...) + + mbk.EXPECT(). + SendCoinsFromModuleToAccount( + gomock.Any(), + evmtypes.ModuleName, + account, + expectedCoins, + ).Return(nil) + }, + }, + { + name: "success - convert 18 decimals amount to 6 decimals with multiple tokens", + evmDenom: TokenDenom, + evmDecimals: 6, + coins: func() sdk.Coins { + coins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + sdk.NewCoin("something", sdkmath.NewInt(3e18)), + }...) + return coins + }, + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e6)), + sdk.NewCoin("something", sdkmath.NewInt(3e18)), + }...) + + mbk.EXPECT(). + SendCoinsFromModuleToAccount( + gomock.Any(), + evmtypes.ModuleName, + account, + expectedCoins, + ).Return(nil) + }, + }, + { + name: "success - no op if converted coin is zero", + evmDenom: TokenDenom, + evmDecimals: 6, + coins: func() sdk.Coins { + coins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e11)), + }...) + return coins + }, + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + mbk.EXPECT(). + SendCoinsFromModuleToAccount( + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + ).Times(0) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup EVM configurator to have access to the EVM coin info. + configurator := evmtypes.NewEVMConfigurator() + configurator.ResetTestConfig() + err := configurator.WithEVMCoinInfo(tc.evmDenom, tc.evmDecimals).Configure() + require.NoError(t, err, "failed to configure EVMConfigurator") + + // Setup mock controller + ctrl := gomock.NewController(t) + + mockBankKeeper := testutil.NewMockBankWrapper(ctrl) + tc.mockSetup(mockBankKeeper) + + bankWrapper := wrappers.NewBankWrapper(mockBankKeeper) + err = bankWrapper.SendCoinsFromModuleToAccount(context.Background(), evmtypes.ModuleName, account, tc.coins()) + + if tc.expectErr != "" { + require.ErrorContains(t, err, tc.expectErr) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestSendCoinsFromAccountToModule(t *testing.T) { + account := sdk.AccAddress([]byte("test_address")) + + testCases := []struct { + name string + evmDenom string + evmDecimals uint8 + coins func() sdk.Coins + expectErr string + mockSetup func(*testutil.MockBankWrapper) + }{ + { + name: "success - does not convert 18 decimals amount with single token", + evmDenom: TokenDenom, + evmDecimals: 18, + coins: func() sdk.Coins { + coins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + }...) + return coins + }, + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + }...) + + mbk.EXPECT(). + SendCoinsFromAccountToModule( + gomock.Any(), + account, + evmtypes.ModuleName, + expectedCoins, + ).Return(nil) + }, + }, + { + name: "success - convert 18 decimals amount to 6 decimals with single token", + evmDenom: TokenDenom, + evmDecimals: 6, + coins: func() sdk.Coins { + coins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + }...) + return coins + }, + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e6)), + }...) + + mbk.EXPECT(). + SendCoinsFromAccountToModule( + gomock.Any(), + account, + evmtypes.ModuleName, + expectedCoins, + ).Return(nil) + }, + }, + { + name: "success - does not convert 18 decimals amount with multiple tokens", + evmDenom: TokenDenom, + evmDecimals: 18, + coins: func() sdk.Coins { + coins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + sdk.NewCoin("something", sdkmath.NewInt(3e18)), + }...) + return coins + }, + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + sdk.NewCoin("something", sdkmath.NewInt(3e18)), + }...) + + mbk.EXPECT(). + SendCoinsFromAccountToModule( + gomock.Any(), + account, + evmtypes.ModuleName, + expectedCoins, + ).Return(nil) + }, + }, + { + name: "success - convert 18 decimals amount to 6 decimals with multiple tokens", + evmDenom: TokenDenom, + evmDecimals: 6, + coins: func() sdk.Coins { + coins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e18)), + sdk.NewCoin("something", sdkmath.NewInt(3e18)), + }...) + return coins + }, + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + expectedCoins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e6)), + sdk.NewCoin("something", sdkmath.NewInt(3e18)), + }...) + + mbk.EXPECT(). + SendCoinsFromAccountToModule( + gomock.Any(), + account, + evmtypes.ModuleName, + expectedCoins, + ).Return(nil) + }, + }, + { + name: "success - no op if converted coin is zero", + evmDenom: TokenDenom, + evmDecimals: 6, + coins: func() sdk.Coins { + coins := sdk.NewCoins([]sdk.Coin{ + sdk.NewCoin(TokenDenom, sdkmath.NewInt(1e11)), + }...) + return coins + }, + expectErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + mbk.EXPECT(). + SendCoinsFromAccountToModule( + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + ).Times(0) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup EVM configurator to have access to the EVM coin info. + configurator := evmtypes.NewEVMConfigurator() + configurator.ResetTestConfig() + err := configurator.WithEVMCoinInfo(tc.evmDenom, tc.evmDecimals).Configure() + require.NoError(t, err, "failed to configure EVMConfigurator") + + // Setup mock controller + ctrl := gomock.NewController(t) + + mockBankKeeper := testutil.NewMockBankWrapper(ctrl) + tc.mockSetup(mockBankKeeper) + + bankWrapper := wrappers.NewBankWrapper(mockBankKeeper) + err = bankWrapper.SendCoinsFromAccountToModule(context.Background(), account, evmtypes.ModuleName, tc.coins()) + + if tc.expectErr != "" { + require.ErrorContains(t, err, tc.expectErr) + } else { + require.NoError(t, err) + } + }) + } +} + +// ----------------------------------------QUERIES------------------------------------------------- + +func TestGetBalance(t *testing.T) { + maxInt64 := int64(9223372036854775807) + evmDenom := "token" + account := sdk.AccAddress([]byte("test_address")) + + testCases := []struct { + name string + evmDecimals uint8 + denom string + expCoin sdk.Coin + expErr string + expPanic string + mockSetup func(*testutil.MockBankWrapper) + }{ + { + name: "success - convert 6 decimals amount to 18 decimals", + denom: evmDenom, + evmDecimals: 6, + expCoin: sdk.NewCoin(evmDenom, sdkmath.NewInt(1e18)), + expErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + returnedCoin := sdk.NewCoin(evmDenom, sdkmath.NewInt(1e6)) + + mbk.EXPECT(). + GetBalance( + gomock.Any(), + account, + evmDenom, + ).Return(returnedCoin) + }, + }, + { + name: "success - convert max int 6 decimals amount to 18 decimals", + denom: evmDenom, + evmDecimals: 6, + expCoin: sdk.NewCoin(evmDenom, sdkmath.NewInt(1e12).MulRaw(maxInt64)), + expErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + returnedCoin := sdk.NewCoin(evmDenom, sdkmath.NewInt(maxInt64)) + + mbk.EXPECT(). + GetBalance( + gomock.Any(), + account, + evmDenom, + ).Return(returnedCoin) + }, + }, + { + name: "success - does not convert 18 decimals amount", + denom: evmDenom, + evmDecimals: 18, + expCoin: sdk.NewCoin(evmDenom, sdkmath.NewInt(1e18)), + expErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + returnedCoin := sdk.NewCoin(evmDenom, sdkmath.NewInt(1e18)) + + mbk.EXPECT(). + GetBalance( + gomock.Any(), + account, + evmDenom, + ).Return(returnedCoin) + }, + }, + { + name: "success - zero balance", + denom: evmDenom, + evmDecimals: 6, + expCoin: sdk.NewCoin(evmDenom, sdkmath.NewInt(0)), + expErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + returnedCoin := sdk.NewCoin(evmDenom, sdkmath.NewInt(0)) + + mbk.EXPECT(). + GetBalance( + gomock.Any(), + account, + evmDenom, + ).Return(returnedCoin) + }, + }, + { + name: "success - small amount (less than 1 full token)", + denom: evmDenom, + evmDecimals: 6, + expCoin: sdk.NewCoin(evmDenom, sdkmath.NewInt(1e14)), // 0.0001 token in 18 decimals + expErr: "", + mockSetup: func(mbk *testutil.MockBankWrapper) { + returnedCoin := sdk.NewCoin(evmDenom, sdkmath.NewInt(100)) // 0.0001 token in 6 decimals + + mbk.EXPECT(). + GetBalance( + gomock.Any(), + account, + evmDenom, + ).Return(returnedCoin) + }, + }, + { + name: "panic - wrong evm denom", + denom: "wrong_denom", + evmDecimals: 18, + expPanic: "expected evm denom token", + mockSetup: func(mbk *testutil.MockBankWrapper) { + returnedCoin := sdk.NewCoin("wrong_denom", sdkmath.NewInt(1e18)) + + mbk.EXPECT(). + GetBalance( + gomock.Any(), + account, + "wrong_denom", + ).Return(returnedCoin) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup EVM configurator to have access to the EVM coin info. + configurator := evmtypes.NewEVMConfigurator() + configurator.ResetTestConfig() + err := configurator.WithEVMCoinInfo(evmDenom, tc.evmDecimals).Configure() + require.NoError(t, err, "failed to configure EVMConfigurator") + + // Setup mock controller + ctrl := gomock.NewController(t) + + mockBankKeeper := testutil.NewMockBankWrapper(ctrl) + tc.mockSetup(mockBankKeeper) + + bankWrapper := wrappers.NewBankWrapper(mockBankKeeper) + + // When calling the function with a denom different than the evm one, it should panic + defer func() { + if r := recover(); r != nil { + require.Contains(t, fmt.Sprint(r), tc.expPanic) + } + }() + + balance := bankWrapper.GetBalance(context.Background(), account, tc.denom) + + if tc.expErr != "" { + require.ErrorContains(t, err, tc.expErr) + } else { + require.NoError(t, err) + require.Equal(t, tc.expCoin, balance, "expected a different balance") + } + }) + } +} diff --git a/x/evm/wrappers/testutil/mock.go b/x/evm/wrappers/testutil/mock.go new file mode 100644 index 00000000..fa0d6cd0 --- /dev/null +++ b/x/evm/wrappers/testutil/mock.go @@ -0,0 +1,256 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./x/evm/types/interfaces.go +// +// Generated by this command: +// +// mockgen -source=./x/evm/types/interfaces.go -package testutil -destination=./x/evm/wrappers/testutil/mock.go +// + +// Package testutil is a generated GoMock package. +package testutil + +import ( + context "context" + big "math/big" + reflect "reflect" + + math "cosmossdk.io/math" + types "github.com/cosmos/cosmos-sdk/types" + types3 "github.com/evmos/os/x/feemarket/types" + gomock "go.uber.org/mock/gomock" +) + +// MockFeeMarketKeeper is a mock of FeeMarketKeeper interface. +type MockFeeMarketKeeper struct { + ctrl *gomock.Controller + recorder *MockFeeMarketKeeperMockRecorder + isgomock struct{} +} + +// MockFeeMarketKeeperMockRecorder is the mock recorder for MockFeeMarketKeeper. +type MockFeeMarketKeeperMockRecorder struct { + mock *MockFeeMarketKeeper +} + +// NewMockFeeMarketKeeper creates a new mock instance. +func NewMockFeeMarketKeeper(ctrl *gomock.Controller) *MockFeeMarketKeeper { + mock := &MockFeeMarketKeeper{ctrl: ctrl} + mock.recorder = &MockFeeMarketKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFeeMarketKeeper) EXPECT() *MockFeeMarketKeeperMockRecorder { + return m.recorder +} + +// CalculateBaseFee mocks base method. +func (m *MockFeeMarketKeeper) CalculateBaseFee(ctx types.Context) math.LegacyDec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CalculateBaseFee", ctx) + ret0, _ := ret[0].(math.LegacyDec) + return ret0 +} + +// CalculateBaseFee indicates an expected call of CalculateBaseFee. +func (mr *MockFeeMarketKeeperMockRecorder) CalculateBaseFee(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateBaseFee", reflect.TypeOf((*MockFeeMarketKeeper)(nil).CalculateBaseFee), ctx) +} + +// GetBaseFee mocks base method. +func (m *MockFeeMarketKeeper) GetBaseFee(ctx types.Context) math.LegacyDec { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBaseFee", ctx) + ret0, _ := ret[0].(math.LegacyDec) + return ret0 +} + +// GetBaseFee indicates an expected call of GetBaseFee. +func (mr *MockFeeMarketKeeperMockRecorder) GetBaseFee(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBaseFee", reflect.TypeOf((*MockFeeMarketKeeper)(nil).GetBaseFee), ctx) +} + +// GetParams mocks base method. +func (m *MockFeeMarketKeeper) GetParams(ctx types.Context) types3.Params { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetParams", ctx) + ret0, _ := ret[0].(types3.Params) + return ret0 +} + +// GetParams indicates an expected call of GetParams. +func (mr *MockFeeMarketKeeperMockRecorder) GetParams(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockFeeMarketKeeper)(nil).GetParams), ctx) +} + +// MockBankWrapper is a mock of BankWrapper interface. +type MockBankWrapper struct { + ctrl *gomock.Controller + recorder *MockBankWrapperMockRecorder + isgomock struct{} +} + +// MockBankWrapperMockRecorder is the mock recorder for MockBankWrapper. +type MockBankWrapperMockRecorder struct { + mock *MockBankWrapper +} + +// NewMockBankWrapper creates a new mock instance. +func NewMockBankWrapper(ctrl *gomock.Controller) *MockBankWrapper { + mock := &MockBankWrapper{ctrl: ctrl} + mock.recorder = &MockBankWrapperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBankWrapper) EXPECT() *MockBankWrapperMockRecorder { + return m.recorder +} + +// BurnAmountFromAccount mocks base method. +func (m *MockBankWrapper) BurnAmountFromAccount(ctx context.Context, account types.AccAddress, amt *big.Int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BurnAmountFromAccount", ctx, account, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// BurnAmountFromAccount indicates an expected call of BurnAmountFromAccount. +func (mr *MockBankWrapperMockRecorder) BurnAmountFromAccount(ctx, account, amt any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BurnAmountFromAccount", reflect.TypeOf((*MockBankWrapper)(nil).BurnAmountFromAccount), ctx, account, amt) +} + +// BurnCoins mocks base method. +func (m *MockBankWrapper) BurnCoins(ctx context.Context, moduleName string, amt types.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BurnCoins", ctx, moduleName, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// BurnCoins indicates an expected call of BurnCoins. +func (mr *MockBankWrapperMockRecorder) BurnCoins(ctx, moduleName, amt any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BurnCoins", reflect.TypeOf((*MockBankWrapper)(nil).BurnCoins), ctx, moduleName, amt) +} + +// GetAllBalances mocks base method. +func (m *MockBankWrapper) GetAllBalances(ctx context.Context, addr types.AccAddress) types.Coins { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllBalances", ctx, addr) + ret0, _ := ret[0].(types.Coins) + return ret0 +} + +// GetAllBalances indicates an expected call of GetAllBalances. +func (mr *MockBankWrapperMockRecorder) GetAllBalances(ctx, addr any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllBalances", reflect.TypeOf((*MockBankWrapper)(nil).GetAllBalances), ctx, addr) +} + +// GetBalance mocks base method. +func (m *MockBankWrapper) GetBalance(ctx context.Context, addr types.AccAddress, denom string) types.Coin { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBalance", ctx, addr, denom) + ret0, _ := ret[0].(types.Coin) + return ret0 +} + +// GetBalance indicates an expected call of GetBalance. +func (mr *MockBankWrapperMockRecorder) GetBalance(ctx, addr, denom any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBalance", reflect.TypeOf((*MockBankWrapper)(nil).GetBalance), ctx, addr, denom) +} + +// IsSendEnabledCoins mocks base method. +func (m *MockBankWrapper) IsSendEnabledCoins(ctx context.Context, coins ...types.Coin) error { + m.ctrl.T.Helper() + varargs := []any{ctx} + for _, a := range coins { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "IsSendEnabledCoins", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// IsSendEnabledCoins indicates an expected call of IsSendEnabledCoins. +func (mr *MockBankWrapperMockRecorder) IsSendEnabledCoins(ctx any, coins ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx}, coins...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsSendEnabledCoins", reflect.TypeOf((*MockBankWrapper)(nil).IsSendEnabledCoins), varargs...) +} + +// MintAmountToAccount mocks base method. +func (m *MockBankWrapper) MintAmountToAccount(ctx context.Context, recipientAddr types.AccAddress, amt *big.Int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MintAmountToAccount", ctx, recipientAddr, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// MintAmountToAccount indicates an expected call of MintAmountToAccount. +func (mr *MockBankWrapperMockRecorder) MintAmountToAccount(ctx, recipientAddr, amt any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MintAmountToAccount", reflect.TypeOf((*MockBankWrapper)(nil).MintAmountToAccount), ctx, recipientAddr, amt) +} + +// MintCoins mocks base method. +func (m *MockBankWrapper) MintCoins(ctx context.Context, moduleName string, amt types.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MintCoins", ctx, moduleName, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// MintCoins indicates an expected call of MintCoins. +func (mr *MockBankWrapperMockRecorder) MintCoins(ctx, moduleName, amt any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MintCoins", reflect.TypeOf((*MockBankWrapper)(nil).MintCoins), ctx, moduleName, amt) +} + +// SendCoins mocks base method. +func (m *MockBankWrapper) SendCoins(ctx context.Context, from, to types.AccAddress, amt types.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoins", ctx, from, to, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoins indicates an expected call of SendCoins. +func (mr *MockBankWrapperMockRecorder) SendCoins(ctx, from, to, amt any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoins", reflect.TypeOf((*MockBankWrapper)(nil).SendCoins), ctx, from, to, amt) +} + +// SendCoinsFromAccountToModule mocks base method. +func (m *MockBankWrapper) SendCoinsFromAccountToModule(ctx context.Context, senderAddr types.AccAddress, recipientModule string, amt types.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoinsFromAccountToModule", ctx, senderAddr, recipientModule, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoinsFromAccountToModule indicates an expected call of SendCoinsFromAccountToModule. +func (mr *MockBankWrapperMockRecorder) SendCoinsFromAccountToModule(ctx, senderAddr, recipientModule, amt any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromAccountToModule", reflect.TypeOf((*MockBankWrapper)(nil).SendCoinsFromAccountToModule), ctx, senderAddr, recipientModule, amt) +} + +// SendCoinsFromModuleToAccount mocks base method. +func (m *MockBankWrapper) SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr types.AccAddress, amt types.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoinsFromModuleToAccount", ctx, senderModule, recipientAddr, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoinsFromModuleToAccount indicates an expected call of SendCoinsFromModuleToAccount. +func (mr *MockBankWrapperMockRecorder) SendCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, amt any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromModuleToAccount", reflect.TypeOf((*MockBankWrapper)(nil).SendCoinsFromModuleToAccount), ctx, senderModule, recipientAddr, amt) +}