package backend import ( "fmt" "github.com/cosmos/cosmos-sdk/crypto" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" goethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/evmos/ethermint/crypto/ethsecp256k1" "github.com/evmos/ethermint/rpc/backend/mocks" "github.com/evmos/ethermint/tests" evmtypes "github.com/evmos/ethermint/x/evm/types" "google.golang.org/grpc/metadata" ) func (suite *BackendTestSuite) TestSendTransaction() { gasPrice := new(hexutil.Big) gas := hexutil.Uint64(1) zeroGas := hexutil.Uint64(0) toAddr := tests.GenerateAddress() priv, _ := ethsecp256k1.GenerateKey() from := common.BytesToAddress(priv.PubKey().Address().Bytes()) nonce := hexutil.Uint64(1) baseFee := sdk.NewInt(1) callArgsDefault := evmtypes.TransactionArgs{ From: &from, To: &toAddr, GasPrice: gasPrice, Gas: &gas, Nonce: &nonce, } hash := common.Hash{} testCases := []struct { name string registerMock func() args evmtypes.TransactionArgs expHash common.Hash expPass bool }{ { "fail - Can't find account in Keyring", func() {}, evmtypes.TransactionArgs{}, hash, false, }, { "fail - Block error can't set Tx defaults", func() { var header metadata.MD queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) client := suite.backend.clientCtx.Client.(*mocks.Client) armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") RegisterParams(queryClient, &header, 1) RegisterBlockError(client, 1) }, callArgsDefault, hash, false, }, { "fail - Cannot validate transaction gas set to 0", func() { var header metadata.MD queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) client := suite.backend.clientCtx.Client.(*mocks.Client) armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") RegisterParams(queryClient, &header, 1) RegisterBlock(client, 1, nil) RegisterBlockResults(client, 1) RegisterBaseFee(queryClient, baseFee) }, evmtypes.TransactionArgs{ From: &from, To: &toAddr, GasPrice: gasPrice, Gas: &zeroGas, Nonce: &nonce, }, hash, false, }, { "fail - Cannot broadcast transaction", func() { var header metadata.MD queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) client := suite.backend.clientCtx.Client.(*mocks.Client) armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") RegisterParams(queryClient, &header, 1) RegisterBlock(client, 1, nil) RegisterBlockResults(client, 1) RegisterBaseFee(queryClient, baseFee) RegisterParamsWithoutHeader(queryClient, 1) ethSigner := ethtypes.LatestSigner(suite.backend.ChainConfig()) msg := callArgsDefault.ToTransaction() msg.Sign(ethSigner, suite.backend.clientCtx.Keyring) tx, _ := msg.BuildTx(suite.backend.clientCtx.TxConfig.NewTxBuilder(), "aphoton") txEncoder := suite.backend.clientCtx.TxConfig.TxEncoder() txBytes, _ := txEncoder(tx) RegisterBroadcastTxError(client, txBytes) }, callArgsDefault, common.Hash{}, false, }, { "pass - Return the transaction hash", func() { var header metadata.MD queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) client := suite.backend.clientCtx.Client.(*mocks.Client) armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") RegisterParams(queryClient, &header, 1) RegisterBlock(client, 1, nil) RegisterBlockResults(client, 1) RegisterBaseFee(queryClient, baseFee) RegisterParamsWithoutHeader(queryClient, 1) ethSigner := ethtypes.LatestSigner(suite.backend.ChainConfig()) msg := callArgsDefault.ToTransaction() msg.Sign(ethSigner, suite.backend.clientCtx.Keyring) tx, _ := msg.BuildTx(suite.backend.clientCtx.TxConfig.NewTxBuilder(), "aphoton") txEncoder := suite.backend.clientCtx.TxConfig.TxEncoder() txBytes, _ := txEncoder(tx) RegisterBroadcastTx(client, txBytes) }, callArgsDefault, hash, true, }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("case %s", tc.name), func() { suite.SetupTest() // reset test and queries tc.registerMock() if tc.expPass { // Sign the transaction and get the hash queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterParamsWithoutHeader(queryClient, 1) ethSigner := ethtypes.LatestSigner(suite.backend.ChainConfig()) msg := callArgsDefault.ToTransaction() msg.Sign(ethSigner, suite.backend.clientCtx.Keyring) tc.expHash = msg.AsTransaction().Hash() } responseHash, err := suite.backend.SendTransaction(tc.args) if tc.expPass { suite.Require().NoError(err) suite.Require().Equal(tc.expHash, responseHash) } else { suite.Require().Error(err) } }) } } func (suite *BackendTestSuite) TestSign() { from, priv := tests.NewAddrKey() testCases := []struct { name string registerMock func() fromAddr common.Address inputBz hexutil.Bytes expPass bool }{ { "fail - can't find key in Keyring", func() {}, from, nil, false, }, { "pass - sign nil data", func() { armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") }, from, nil, true, }, } for _, tc := range testCases { suite.Run(fmt.Sprintf("case %s", tc.name), func() { suite.SetupTest() // reset test and queries tc.registerMock() responseBz, err := suite.backend.Sign(tc.fromAddr, tc.inputBz) if tc.expPass { signature, _, err := suite.backend.clientCtx.Keyring.SignByAddress((sdk.AccAddress)(from.Bytes()), tc.inputBz) signature[goethcrypto.RecoveryIDOffset] += 27 suite.Require().NoError(err) suite.Require().Equal((hexutil.Bytes)(signature), responseBz) } else { suite.Require().Error(err) } }) } } func (suite *BackendTestSuite) TestSignTypedData() { from, priv := tests.NewAddrKey() testCases := []struct { name string registerMock func() fromAddr common.Address inputTypedData apitypes.TypedData expPass bool }{ { "fail - can't find key in Keyring", func() {}, from, apitypes.TypedData{}, false, }, { "fail - empty TypeData", func() { armor := crypto.EncryptArmorPrivKey(priv, "", "eth_secp256k1") suite.backend.clientCtx.Keyring.ImportPrivKey("test_key", armor, "") }, from, apitypes.TypedData{}, false, }, // TODO: Generate a TypedData msg } for _, tc := range testCases { suite.Run(fmt.Sprintf("case %s", tc.name), func() { suite.SetupTest() // reset test and queries tc.registerMock() responseBz, err := suite.backend.SignTypedData(tc.fromAddr, tc.inputTypedData) if tc.expPass { sigHash, _, err := apitypes.TypedDataAndHash(tc.inputTypedData) signature, _, err := suite.backend.clientCtx.Keyring.SignByAddress((sdk.AccAddress)(from.Bytes()), sigHash) signature[goethcrypto.RecoveryIDOffset] += 27 suite.Require().NoError(err) suite.Require().Equal((hexutil.Bytes)(signature), responseBz) } else { suite.Require().Error(err) } }) } }