diff --git a/x/erc20/keeper/evm.go b/x/erc20/keeper/evm.go index 5cbbecfcce..7114dba5ba 100644 --- a/x/erc20/keeper/evm.go +++ b/x/erc20/keeper/evm.go @@ -101,7 +101,7 @@ func (k Keeper) CallEVMWithData( Data: (*hexutil.Bytes)(&data), }) if err != nil { - return nil, sdkerrors.Wrapf(sdkerrors.ErrJSONMarshal, "failled to marshal tx args: %s", err.Error()) + return nil, sdkerrors.Wrapf(sdkerrors.ErrJSONMarshal, "failed to marshal tx args: %s", err.Error()) } gasRes, err := k.evmKeeper.EstimateGas(sdk.WrapSDKContext(ctx), &evmtypes.EthCallRequest{ diff --git a/x/erc20/keeper/evm_hooks.go b/x/erc20/keeper/evm_hooks.go index 8c3d36728f..5e5a5ad9f0 100644 --- a/x/erc20/keeper/evm_hooks.go +++ b/x/erc20/keeper/evm_hooks.go @@ -99,7 +99,7 @@ func (h Hooks) PostTxProcessing( // continue to allow transfers for the ERC20 in case the token pair is disabled h.k.Logger(ctx).Debug( "ERC20 token -> Cosmos coin conversion is disabled for pair", - "coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(), + "coin", pair.Denom, "contract", pair.Erc20Address, ) continue } diff --git a/x/erc20/keeper/evm_hooks_test.go b/x/erc20/keeper/evm_hooks_test.go index 617ebe75f3..2ea248d6b0 100644 --- a/x/erc20/keeper/evm_hooks_test.go +++ b/x/erc20/keeper/evm_hooks_test.go @@ -6,7 +6,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/tharsis/ethermint/tests" + "github.com/tharsis/evmos/v3/contracts" "github.com/tharsis/evmos/v3/x/erc20/types" ) @@ -64,8 +67,47 @@ func (suite *KeeperTestSuite) TestEvmHooksRegisterERC20() { }, false, }, + { + "Pair is disabled", + func(contractAddr common.Address) { + pair, err := suite.app.Erc20Keeper.RegisterERC20(suite.ctx, contractAddr) + suite.Require().NoError(err) + + pair.Enabled = false + suite.app.Erc20Keeper.SetTokenPair(suite.ctx, *pair) + // Mint 10 tokens to suite.address (owner) + _ = suite.MintERC20Token(contractAddr, suite.address, suite.address, big.NewInt(10)) + suite.Commit() + + // Burn the 10 tokens of suite.address (owner) + _ = suite.BurnERC20Token(contractAddr, suite.address, big.NewInt(10)) + + }, + false, + }, + { + "Pair is incorrectly loaded", + func(contractAddr common.Address) { + pair, err := suite.app.Erc20Keeper.RegisterERC20(suite.ctx, contractAddr) + suite.Require().NoError(err) + + suite.app.Erc20Keeper.DeleteTokenPair(suite.ctx, *pair) + + suite.app.Erc20Keeper.SetDenomMap(suite.ctx, pair.Denom, pair.GetID()) + suite.app.Erc20Keeper.SetERC20Map(suite.ctx, pair.GetERC20Contract(), pair.GetID()) + // Mint 10 tokens to suite.address (owner) + _ = suite.MintERC20Token(contractAddr, suite.address, suite.address, big.NewInt(10)) + suite.Commit() + + // Burn the 10 tokens of suite.address (owner) + _ = suite.BurnERC20Token(contractAddr, suite.address, big.NewInt(10)) + + }, + false, + }, } for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { suite.mintFeeCollector = true suite.SetupTest() @@ -156,3 +198,248 @@ func (suite *KeeperTestSuite) TestEvmHooksRegisterCoin() { } suite.mintFeeCollector = false } + +func (suite *KeeperTestSuite) TestEvmHooksForceError() { + + msg := ethtypes.NewMessage( + types.ModuleAddress, + &common.Address{}, + 0, + big.NewInt(0), // amount + uint64(0), // gasLimit + big.NewInt(0), // gasFeeCap + big.NewInt(0), // gasTipCap + big.NewInt(0), // gasPrice + []byte{}, + ethtypes.AccessList{}, // AccessList + true, // checkNonce + ) + + account := tests.GenerateAddress() + + transferData := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + transferData[31] = uint8(10) + erc20 := contracts.ERC20BurnableContract.ABI + + transferEvent := erc20.Events["Transfer"] + + testCases := []struct { + name string + test func() + }{ + { + "correct transfer (non burn)", + func() { + contractAddr, err := suite.DeployContract("coin", "token", erc20Decimals) + suite.Require().NoError(err) + suite.Commit() + + _, err = suite.app.Erc20Keeper.RegisterERC20(suite.ctx, contractAddr) + suite.Require().NoError(err) + + topics := []common.Hash{transferEvent.ID, account.Hash(), account.Hash()} + log := ethtypes.Log{ + Topics: topics, + Data: transferData, + Address: contractAddr, + } + receipt := ðtypes.Receipt{ + Logs: []*ethtypes.Log{&log}, + } + + err = suite.app.Erc20Keeper.Hooks().PostTxProcessing(suite.ctx, msg, receipt) + suite.Require().NoError(err) + }, + }, + { + "correct burn", + func() { + contractAddr, err := suite.DeployContract("coin", "token", erc20Decimals) + suite.Require().NoError(err) + suite.Commit() + + pair, err := suite.app.Erc20Keeper.RegisterERC20(suite.ctx, contractAddr) + suite.Require().NoError(err) + + topics := []common.Hash{transferEvent.ID, account.Hash(), types.ModuleAddress.Hash()} + log := ethtypes.Log{ + Topics: topics, + Data: transferData, + Address: contractAddr, + } + receipt := ðtypes.Receipt{ + Logs: []*ethtypes.Log{&log}, + } + + err = suite.app.Erc20Keeper.Hooks().PostTxProcessing(suite.ctx, msg, receipt) + suite.Require().NoError(err) + sender := sdk.AccAddress(account.Bytes()) + cosmosBalance := suite.app.BankKeeper.GetBalance(suite.ctx, sender, pair.Denom) + + transferEvent, err := erc20.Unpack("Transfer", transferData) + suite.Require().NoError(err) + + tokens, _ := transferEvent[0].(*big.Int) + suite.Require().Equal(cosmosBalance.Amount.String(), tokens.String()) + + }, + }, + { + "Unspecified Owner", + func() { + contractAddr, err := suite.DeployContract("coin", "token", erc20Decimals) + suite.Require().NoError(err) + suite.Commit() + + pair, err := suite.app.Erc20Keeper.RegisterERC20(suite.ctx, contractAddr) + suite.Require().NoError(err) + + pair.ContractOwner = types.OWNER_UNSPECIFIED + suite.app.Erc20Keeper.SetTokenPair(suite.ctx, *pair) + + topics := []common.Hash{transferEvent.ID, account.Hash(), types.ModuleAddress.Hash()} + log := ethtypes.Log{ + Topics: topics, + Data: transferData, + Address: contractAddr, + } + receipt := ðtypes.Receipt{ + Logs: []*ethtypes.Log{&log}, + } + + err = suite.app.Erc20Keeper.Hooks().PostTxProcessing(suite.ctx, msg, receipt) + suite.Require().NoError(err) + }, + }, + { + "Fail Evm", + func() { + contractAddr, err := suite.DeployContract("coin", "token", erc20Decimals) + suite.Require().NoError(err) + suite.Commit() + + pair, err := suite.app.Erc20Keeper.RegisterERC20(suite.ctx, contractAddr) + suite.Require().NoError(err) + + pair.ContractOwner = types.OWNER_MODULE + suite.app.Erc20Keeper.SetTokenPair(suite.ctx, *pair) + + topics := []common.Hash{transferEvent.ID, account.Hash(), types.ModuleAddress.Hash()} + log := ethtypes.Log{ + Topics: topics, + Data: transferData, + Address: contractAddr, + } + receipt := ðtypes.Receipt{ + Logs: []*ethtypes.Log{&log}, + } + + err = suite.app.Erc20Keeper.Hooks().PostTxProcessing(suite.ctx, msg, receipt) + suite.Require().NoError(err) + }, + }, + { + "No log address", + func() { + topics := []common.Hash{transferEvent.ID, account.Hash(), types.ModuleAddress.Hash()} + log := ethtypes.Log{ + Topics: topics, + Data: transferData, + } + receipt := ðtypes.Receipt{ + Logs: []*ethtypes.Log{&log}, + } + + err := suite.app.Erc20Keeper.Hooks().PostTxProcessing(suite.ctx, msg, receipt) + suite.Require().NoError(err) + }, + }, + { + "No data on topic", + func() { + topics := []common.Hash{transferEvent.ID} + log := ethtypes.Log{ + Topics: topics, + Data: transferData, + } + receipt := ðtypes.Receipt{ + Logs: []*ethtypes.Log{&log}, + } + + err := suite.app.Erc20Keeper.Hooks().PostTxProcessing(suite.ctx, msg, receipt) + suite.Require().NoError(err) + }, + }, + { + "Empty logs", + func() { + log := ethtypes.Log{} + receipt := ðtypes.Receipt{ + Logs: []*ethtypes.Log{&log}, + } + + err := suite.app.Erc20Keeper.Hooks().PostTxProcessing(suite.ctx, msg, receipt) + suite.Require().NoError(err) + }, + }, + { + "No log data", + func() { + topics := []common.Hash{transferEvent.ID, account.Hash(), types.ModuleAddress.Hash()} + log := ethtypes.Log{ + Topics: topics, + } + receipt := ðtypes.Receipt{ + Logs: []*ethtypes.Log{&log}, + } + + err := suite.app.Erc20Keeper.Hooks().PostTxProcessing(suite.ctx, msg, receipt) + suite.Require().NoError(err) + }, + }, + { + "Non transfer event", + func() { + aprovalEvent := erc20.Events["Approval"] + topics := []common.Hash{aprovalEvent.ID, account.Hash(), account.Hash()} + log := ethtypes.Log{ + Topics: topics, + Data: transferData, + } + receipt := ðtypes.Receipt{ + Logs: []*ethtypes.Log{&log}, + } + + err := suite.app.Erc20Keeper.Hooks().PostTxProcessing(suite.ctx, msg, receipt) + suite.Require().NoError(err) + }, + }, + { + "Non recognized event", + func() { + topics := []common.Hash{{}, account.Hash(), account.Hash()} + log := ethtypes.Log{ + Topics: topics, + Data: transferData, + } + receipt := ðtypes.Receipt{ + Logs: []*ethtypes.Log{&log}, + } + + err := suite.app.Erc20Keeper.Hooks().PostTxProcessing(suite.ctx, msg, receipt) + suite.Require().NoError(err) + }, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.mintFeeCollector = true + suite.SetupTest() + suite.ensureHooksSet() + + tc.test() + + }) + } + suite.mintFeeCollector = false +} diff --git a/x/erc20/keeper/evm_test.go b/x/erc20/keeper/evm_test.go index de7f4222b9..ff7813f90e 100644 --- a/x/erc20/keeper/evm_test.go +++ b/x/erc20/keeper/evm_test.go @@ -1,6 +1,8 @@ package keeper_test import ( + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/tharsis/ethermint/tests" evmtypes "github.com/tharsis/ethermint/x/evm/types" @@ -79,3 +81,98 @@ func (suite *KeeperTestSuite) TestCallEVM() { } } } + +func (suite *KeeperTestSuite) TestCallEVMWithData() { + erc20 := contracts.ERC20MinterBurnerDecimalsContract.ABI + testCases := []struct { + name string + from common.Address + malleate func() ([]byte, *common.Address) + expPass bool + }{ + { + "unknown method", + types.ModuleAddress, + func() ([]byte, *common.Address) { + contract, err := suite.DeployContract("coin", "token", erc20Decimals) + suite.Require().NoError(err) + account := tests.GenerateAddress() + data, _ := erc20.Pack("", account) + return data, &contract + }, + false, + }, + { + "pass", + types.ModuleAddress, + func() ([]byte, *common.Address) { + contract, err := suite.DeployContract("coin", "token", erc20Decimals) + suite.Require().NoError(err) + account := tests.GenerateAddress() + data, _ := erc20.Pack("balanceOf", account) + return data, &contract + }, + true, + }, + { + "fail empty data", + types.ModuleAddress, + func() ([]byte, *common.Address) { + contract, err := suite.DeployContract("coin", "token", erc20Decimals) + suite.Require().NoError(err) + return []byte{}, &contract + }, + false, + }, + + { + "fail empty sender", + common.Address{}, + func() ([]byte, *common.Address) { + contract, err := suite.DeployContract("coin", "token", erc20Decimals) + suite.Require().NoError(err) + return []byte{}, &contract + }, + false, + }, + { + "deploy", + types.ModuleAddress, + func() ([]byte, *common.Address) { + ctorArgs, _ := contracts.ERC20MinterBurnerDecimalsContract.ABI.Pack("", "test", "test", uint8(18)) + data := append(contracts.ERC20MinterBurnerDecimalsContract.Bin, ctorArgs...) + return data, nil + }, + true, + }, + { + "fail deploy", + types.ModuleAddress, + func() ([]byte, *common.Address) { + params := suite.app.EvmKeeper.GetParams(suite.ctx) + params.EnableCreate = false + suite.app.EvmKeeper.SetParams(suite.ctx, params) + ctorArgs, _ := contracts.ERC20MinterBurnerDecimalsContract.ABI.Pack("", "test", "test", uint8(18)) + data := append(contracts.ERC20MinterBurnerDecimalsContract.Bin, ctorArgs...) + return data, nil + }, + false, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset + + data, contract := tc.malleate() + + res, err := suite.app.Erc20Keeper.CallEVMWithData(suite.ctx, tc.from, contract, data) + if tc.expPass { + suite.Require().IsTypef(&evmtypes.MsgEthereumTxResponse{}, res, tc.name) + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/x/erc20/keeper/grpc_query_test.go b/x/erc20/keeper/grpc_query_test.go index 04de87a271..881de737ec 100644 --- a/x/erc20/keeper/grpc_query_test.go +++ b/x/erc20/keeper/grpc_query_test.go @@ -135,6 +135,21 @@ func (suite *KeeperTestSuite) TestTokenPair() { }, true, }, + { + "token pair not found - with erc20 existant", + func() { + addr := tests.GenerateAddress() + pair := types.NewTokenPair(addr, "coin", true, types.OWNER_MODULE) + suite.app.Erc20Keeper.SetERC20Map(suite.ctx, addr, pair.GetID()) + suite.app.Erc20Keeper.SetDenomMap(suite.ctx, pair.Denom, pair.GetID()) + + req = &types.QueryTokenPairRequest{ + Token: pair.Erc20Address, + } + expRes = &types.QueryTokenPairResponse{TokenPair: pair} + }, + false, + }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { diff --git a/x/erc20/keeper/mint_test.go b/x/erc20/keeper/mint_test.go index 99e763b6f2..b6724de5c2 100644 --- a/x/erc20/keeper/mint_test.go +++ b/x/erc20/keeper/mint_test.go @@ -61,6 +61,14 @@ func (suite *KeeperTestSuite) TestMintingEnabled() { }, false, }, + { + "token not registered", + func() { + suite.app.Erc20Keeper.SetDenomMap(suite.ctx, expPair.Denom, id) + suite.app.Erc20Keeper.SetERC20Map(suite.ctx, expPair.GetERC20Contract(), id) + }, + false, + }, { "ok", func() { diff --git a/x/erc20/keeper/msg_server_test.go b/x/erc20/keeper/msg_server_test.go index da7e9251c1..205d8f128b 100644 --- a/x/erc20/keeper/msg_server_test.go +++ b/x/erc20/keeper/msg_server_test.go @@ -458,3 +458,55 @@ func (suite *KeeperTestSuite) TestConvertNativeIBC() { suite.Require().NoError(err) suite.Commit() } + +func (suite *KeeperTestSuite) TestWrongPairOwnerERC20NativeCoin() { + testCases := []struct { + name string + mint int64 + burn int64 + reconvert int64 + expPass bool + }{ + {"ok - sufficient funds", 100, 10, 5, true}, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.mintFeeCollector = true + suite.SetupTest() + metadata, pair := suite.setupRegisterCoin() + suite.Require().NotNil(metadata) + suite.Require().NotNil(pair) + + // Precondition: Convert Coin to ERC20 + coins := sdk.NewCoins(sdk.NewCoin(cosmosTokenBase, sdk.NewInt(tc.mint))) + sender := sdk.AccAddress(suite.address.Bytes()) + suite.app.BankKeeper.MintCoins(suite.ctx, types.ModuleName, coins) + suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, types.ModuleName, sender, coins) + msg := types.NewMsgConvertCoin( + sdk.NewCoin(cosmosTokenBase, sdk.NewInt(tc.burn)), + suite.address, + sender, + ) + + pair.ContractOwner = types.OWNER_UNSPECIFIED + suite.app.Erc20Keeper.SetTokenPair(suite.ctx, *pair) + + ctx := sdk.WrapSDKContext(suite.ctx) + _, err := suite.app.Erc20Keeper.ConvertCoin(ctx, msg) + suite.Require().Error(err, tc.name) + + // Convert ERC20s back to Coins + ctx = sdk.WrapSDKContext(suite.ctx) + contractAddr := common.HexToAddress(pair.Erc20Address) + msgConvertERC20 := types.NewMsgConvertERC20( + sdk.NewInt(tc.reconvert), + sender, + contractAddr, + suite.address, + ) + + _, err = suite.app.Erc20Keeper.ConvertERC20(ctx, msgConvertERC20) + suite.Require().Error(err, tc.name) + }) + } +}