diff --git a/CHANGELOG.md b/CHANGELOG.md index 67c335cabd..aa1a0a2e45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (rpc, evm) [tharsis#673](https://github.com/tharsis/ethermint/pull/673) Use tendermint events to store fee market basefee. * (rpc) [tharsis#624](https://github.com/tharsis/ethermint/pull/624) Implement new JSON-RPC endpoints from latest geth version * (evm) [tharsis#662](https://github.com/tharsis/ethermint/pull/662) Disable basefee for non london blocks +* (cmd) [tharsis#712](https://github.com/tharsis/ethermint/pull/712) add tx cli to build evm transaction ### Bug Fixes diff --git a/rpc/ethereum/namespaces/eth/api.go b/rpc/ethereum/namespaces/eth/api.go index 071dcadda7..c19f9dc004 100644 --- a/rpc/ethereum/namespaces/eth/api.go +++ b/rpc/ethereum/namespaces/eth/api.go @@ -20,10 +20,8 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" - authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -452,22 +450,6 @@ func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) return common.Hash{}, err } - builder, ok := e.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) - if !ok { - e.logger.Error("clientCtx.TxConfig.NewTxBuilder returns unsupported builder") - } - - option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) - if err != nil { - e.logger.Error("codectypes.NewAnyWithValue failed to pack an obvious value", "error", err.Error()) - } - - builder.SetExtensionOptions(option) - err = builder.SetMsgs(tx.GetMsgs()...) - if err != nil { - e.logger.Error("builder.SetMsgs failed", "error", err.Error()) - } - // Query params to use the EVM denomination res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{}) if err != nil { @@ -475,23 +457,14 @@ func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) return common.Hash{}, err } - txData, err := evmtypes.UnpackTxData(ethereumTx.Data) + cosmosTx, err := ethereumTx.BuildTx(e.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom) if err != nil { - e.logger.Error("failed to unpack tx data", "error", err.Error()) + e.logger.Error("failed to build cosmos tx", "error", err.Error()) return common.Hash{}, err } - fees := sdk.Coins{ - { - Denom: res.Params.EvmDenom, - Amount: sdk.NewIntFromBigInt(txData.Fee()), - }, - } - builder.SetFeeAmount(fees) - builder.SetGasLimit(ethereumTx.GetGas()) - // Encode transaction by default Tx encoder - txBytes, err := e.clientCtx.TxConfig.TxEncoder()(builder.GetTx()) + txBytes, err := e.clientCtx.TxConfig.TxEncoder()(cosmosTx) if err != nil { e.logger.Error("failed to encode eth tx using default encoder", "error", err.Error()) return common.Hash{}, err diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go new file mode 100644 index 0000000000..200dc400c9 --- /dev/null +++ b/x/evm/client/cli/tx.go @@ -0,0 +1,111 @@ +package cli + +import ( + "bufio" + "fmt" + "os" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/input" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + rpctypes "github.com/tharsis/ethermint/rpc/ethereum/types" + "github.com/tharsis/ethermint/x/evm/types" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + cmd.AddCommand(NewRawTxCmd()) + return cmd +} + +// NewRawTxCmd command build cosmos transaction from raw ethereum transaction +func NewRawTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "raw [tx-hex]", + Short: "Build cosmos transaction from raw ethereum transaction", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + data, err := hexutil.Decode(args[0]) + if err != nil { + return errors.Wrap(err, "failed to decode ethereum tx hex bytes") + } + + msg := &types.MsgEthereumTx{} + if err := msg.UnmarshalBinary(data); err != nil { + return err + } + + if err := msg.ValidateBasic(); err != nil { + return err + } + + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + rsp, err := rpctypes.NewQueryClient(clientCtx).Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + tx, err := msg.BuildTx(clientCtx.TxConfig.NewTxBuilder(), rsp.Params.EvmDenom) + if err != nil { + return err + } + + if clientCtx.GenerateOnly { + json, err := clientCtx.TxConfig.TxJSONEncoder()(tx) + if err != nil { + return err + } + + return clientCtx.PrintString(fmt.Sprintf("%s\n", json)) + } + + if !clientCtx.SkipConfirm { + out, err := clientCtx.TxConfig.TxJSONEncoder()(tx) + if err != nil { + return err + } + + _, _ = fmt.Fprintf(os.Stderr, "%s\n\n", out) + + buf := bufio.NewReader(os.Stdin) + ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, os.Stderr) + + if err != nil || !ok { + _, _ = fmt.Fprintf(os.Stderr, "%s\n", "canceled transaction") + return err + } + } + + txBytes, err := clientCtx.TxConfig.TxEncoder()(tx) + if err != nil { + return err + } + + // broadcast to a Tendermint node + res, err := clientCtx.BroadcastTx(txBytes) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} diff --git a/x/evm/module.go b/x/evm/module.go index 8c1553322c..8c38bf55d7 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -76,7 +76,7 @@ func (b AppModuleBasic) RegisterGRPCGatewayRoutes(c client.Context, serveMux *ru // GetTxCmd returns the root tx command for the evm module. func (AppModuleBasic) GetTxCmd() *cobra.Command { - return nil + return cli.GetTxCmd() } // GetQueryCmd returns no root query command for the evm module. diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index 12ecb87ff0..ae03e251df 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -1,14 +1,18 @@ package types import ( + "errors" "fmt" "math/big" + "github.com/cosmos/cosmos-sdk/client" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" "github.com/tharsis/ethermint/types" @@ -282,3 +286,46 @@ func (msg *MsgEthereumTx) GetSender(chainID *big.Int) (common.Address, error) { func (msg MsgEthereumTx) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { return unpacker.UnpackAny(msg.Data, new(TxData)) } + +// UnmarshalBinary decodes the canonical encoding of transactions. +func (msg *MsgEthereumTx) UnmarshalBinary(b []byte) error { + tx := ðtypes.Transaction{} + if err := tx.UnmarshalBinary(b); err != nil { + return err + } + return msg.FromEthereumTx(tx) +} + +// BuildTx builds the canonical cosmos tx from ethereum msg +func (msg *MsgEthereumTx) BuildTx(b client.TxBuilder, evmDenom string) (signing.Tx, error) { + builder, ok := b.(authtx.ExtensionOptionsTxBuilder) + if !ok { + return nil, errors.New("unsupported builder") + } + + option, err := codectypes.NewAnyWithValue(&ExtensionOptionsEthereumTx{}) + if err != nil { + return nil, err + } + + txData, err := UnpackTxData(msg.Data) + if err != nil { + return nil, err + } + fees := sdk.Coins{ + { + Denom: evmDenom, + Amount: sdk.NewIntFromBigInt(txData.Fee()), + }, + } + + builder.SetExtensionOptions(option) + err = builder.SetMsgs(msg) + if err != nil { + return nil, err + } + builder.SetFeeAmount(fees) + builder.SetGasLimit(msg.GetGas()) + tx := builder.GetTx() + return tx, nil +} diff --git a/x/evm/types/msg_test.go b/x/evm/types/msg_test.go index ca1dfbedb9..6477d3f8db 100644 --- a/x/evm/types/msg_test.go +++ b/x/evm/types/msg_test.go @@ -1,19 +1,26 @@ -package types +package types_test import ( + "fmt" "math/big" + "reflect" "testing" "github.com/stretchr/testify/suite" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tharsis/ethermint/crypto/ethsecp256k1" "github.com/tharsis/ethermint/tests" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/tharsis/ethermint/app" + "github.com/tharsis/ethermint/encoding" + "github.com/tharsis/ethermint/x/evm/types" ) const invalidFromAddress = "0x0000" @@ -25,6 +32,8 @@ type MsgsTestSuite struct { from common.Address to common.Address chainID *big.Int + + clientCtx client.Context } func TestMsgsTestSuite(t *testing.T) { @@ -38,25 +47,43 @@ func (suite *MsgsTestSuite) SetupTest() { suite.from = from suite.to = tests.GenerateAddress() suite.chainID = big.NewInt(1) + + encodingConfig := encoding.MakeConfig(app.ModuleBasics) + suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig) } func (suite *MsgsTestSuite) TestMsgEthereumTx_Constructor() { - msg := NewTx(nil, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil) + msg := types.NewTx(nil, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil) // suite.Require().Equal(msg.Data.To, suite.to.Hex()) - suite.Require().Equal(msg.Route(), RouterKey) - suite.Require().Equal(msg.Type(), TypeMsgEthereumTx) + suite.Require().Equal(msg.Route(), types.RouterKey) + suite.Require().Equal(msg.Type(), types.TypeMsgEthereumTx) // suite.Require().NotNil(msg.To()) suite.Require().Equal(msg.GetMsgs(), []sdk.Msg{msg}) suite.Require().Panics(func() { msg.GetSigners() }) suite.Require().Panics(func() { msg.GetSignBytes() }) - msg = NewTxContract(nil, 0, nil, 100000, nil, nil, nil, []byte("test"), nil) + msg = types.NewTxContract(nil, 0, nil, 100000, nil, nil, nil, []byte("test"), nil) suite.Require().NotNil(msg) // suite.Require().Empty(msg.Data.To) // suite.Require().Nil(msg.To()) } +func (suite *MsgsTestSuite) TestMsgEthereumTx_BuildTx() { + msg := types.NewTx(nil, 0, &suite.to, nil, 100000, big.NewInt(1), big.NewInt(1), big.NewInt(0), []byte("test"), nil) + + err := msg.ValidateBasic() + suite.Require().NoError(err) + + tx, err := msg.BuildTx(suite.clientCtx.TxConfig.NewTxBuilder(), "aphoton") + suite.Require().NoError(err) + + suite.Require().Empty(tx.GetMemo()) + suite.Require().Empty(tx.GetTimeoutHeight()) + suite.Require().Equal(uint64(100000), tx.GetGas()) + suite.Require().Equal(sdk.NewCoins(sdk.NewCoin("aphoton", sdk.NewInt(100000))), tx.GetFee()) +} + func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() { hundredInt := sdk.NewInt(100) zeroInt := sdk.ZeroInt() @@ -69,12 +96,12 @@ func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() { amount *sdk.Int gasPrice *sdk.Int from string - accessList *types.AccessList + accessList *ethtypes.AccessList chainID *sdk.Int expectPass bool }{ {msg: "pass with recipient - Legacy Tx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &hundredInt, expectPass: true}, - {msg: "pass with recipient - AccessList Tx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &zeroInt, accessList: &types.AccessList{}, chainID: &hundredInt, expectPass: true}, + {msg: "pass with recipient - AccessList Tx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &zeroInt, accessList: ðtypes.AccessList{}, chainID: &hundredInt, expectPass: true}, {msg: "pass contract - Legacy Tx", to: "", amount: &hundredInt, gasPrice: &hundredInt, expectPass: true}, // {msg: "invalid recipient", to: invalidFromAddress, amount: &minusOneInt, gasPrice: &hundredInt, expectPass: false}, {msg: "nil amount - Legacy Tx", to: suite.to.Hex(), amount: nil, gasPrice: &hundredInt, expectPass: true}, @@ -84,13 +111,13 @@ func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() { {msg: "zero gas price - Legacy Tx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &zeroInt, expectPass: true}, {msg: "invalid from address - Legacy Tx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &zeroInt, from: invalidFromAddress, expectPass: false}, {msg: "out of bound gas fee - Legacy Tx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &exp_2_255, expectPass: false}, - {msg: "nil amount - AccessListTx", to: suite.to.Hex(), amount: nil, gasPrice: &hundredInt, accessList: &types.AccessList{}, chainID: &hundredInt, expectPass: true}, - {msg: "negative amount - AccessListTx", to: suite.to.Hex(), amount: &minusOneInt, gasPrice: &hundredInt, accessList: &types.AccessList{}, chainID: nil, expectPass: false}, - {msg: "nil gas price - AccessListTx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: nil, accessList: &types.AccessList{}, chainID: &hundredInt, expectPass: false}, - {msg: "negative gas price - AccessListTx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &minusOneInt, accessList: &types.AccessList{}, chainID: nil, expectPass: false}, - {msg: "zero gas price - AccessListTx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &zeroInt, accessList: &types.AccessList{}, chainID: &hundredInt, expectPass: true}, - {msg: "invalid from address - AccessListTx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &zeroInt, from: invalidFromAddress, accessList: &types.AccessList{}, chainID: &hundredInt, expectPass: false}, - {msg: "chain ID not set on AccessListTx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &zeroInt, accessList: &types.AccessList{}, chainID: nil, expectPass: false}, + {msg: "nil amount - AccessListTx", to: suite.to.Hex(), amount: nil, gasPrice: &hundredInt, accessList: ðtypes.AccessList{}, chainID: &hundredInt, expectPass: true}, + {msg: "negative amount - AccessListTx", to: suite.to.Hex(), amount: &minusOneInt, gasPrice: &hundredInt, accessList: ðtypes.AccessList{}, chainID: nil, expectPass: false}, + {msg: "nil gas price - AccessListTx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: nil, accessList: ðtypes.AccessList{}, chainID: &hundredInt, expectPass: false}, + {msg: "negative gas price - AccessListTx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &minusOneInt, accessList: ðtypes.AccessList{}, chainID: nil, expectPass: false}, + {msg: "zero gas price - AccessListTx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &zeroInt, accessList: ðtypes.AccessList{}, chainID: &hundredInt, expectPass: true}, + {msg: "invalid from address - AccessListTx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &zeroInt, from: invalidFromAddress, accessList: ðtypes.AccessList{}, chainID: &hundredInt, expectPass: false}, + {msg: "chain ID not set on AccessListTx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &zeroInt, accessList: ðtypes.AccessList{}, chainID: nil, expectPass: false}, } for i, tc := range testCases { @@ -107,7 +134,7 @@ func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() { gasPrice = tc.gasPrice.BigInt() } - tx := NewTx(chainID, 1, &to, amount, 1000, gasPrice, nil, nil, nil, tc.accessList) + tx := types.NewTx(chainID, 1, &to, amount, 1000, gasPrice, nil, nil, nil, tc.accessList) tx.From = tc.from err := tx.ValidateBasic() @@ -123,51 +150,51 @@ func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() { func (suite *MsgsTestSuite) TestMsgEthereumTx_Sign() { testCases := []struct { msg string - tx *MsgEthereumTx - ethSigner types.Signer - malleate func(tx *MsgEthereumTx) + tx *types.MsgEthereumTx + ethSigner ethtypes.Signer + malleate func(tx *types.MsgEthereumTx) expectPass bool }{ { "pass - EIP2930 signer", - NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), &types.AccessList{}), - types.NewEIP2930Signer(suite.chainID), - func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() }, + types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), ðtypes.AccessList{}), + ethtypes.NewEIP2930Signer(suite.chainID), + func(tx *types.MsgEthereumTx) { tx.From = suite.from.Hex() }, true, }, { "pass - EIP155 signer", - NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil), - types.NewEIP155Signer(suite.chainID), - func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() }, + types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil), + ethtypes.NewEIP155Signer(suite.chainID), + func(tx *types.MsgEthereumTx) { tx.From = suite.from.Hex() }, true, }, { "pass - Homestead signer", - NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil), - types.HomesteadSigner{}, - func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() }, + types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil), + ethtypes.HomesteadSigner{}, + func(tx *types.MsgEthereumTx) { tx.From = suite.from.Hex() }, true, }, { "pass - Frontier signer", - NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil), - types.FrontierSigner{}, - func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() }, + types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil), + ethtypes.FrontierSigner{}, + func(tx *types.MsgEthereumTx) { tx.From = suite.from.Hex() }, true, }, { "no from address ", - NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), &types.AccessList{}), - types.NewEIP2930Signer(suite.chainID), - func(tx *MsgEthereumTx) { tx.From = "" }, + types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), ðtypes.AccessList{}), + ethtypes.NewEIP2930Signer(suite.chainID), + func(tx *types.MsgEthereumTx) { tx.From = "" }, false, }, { "from address ≠ signer address", - NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), &types.AccessList{}), - types.NewEIP2930Signer(suite.chainID), - func(tx *MsgEthereumTx) { tx.From = suite.to.Hex() }, + types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), ðtypes.AccessList{}), + ethtypes.NewEIP2930Signer(suite.chainID), + func(tx *types.MsgEthereumTx) { tx.From = suite.to.Hex() }, false, }, } @@ -209,7 +236,7 @@ func (suite *MsgsTestSuite) TestFromEthereumTx() { 21000, big.NewInt(0), nil, ) - tx, err := ethtypes.SignTx(tx, types.NewEIP2930Signer(suite.chainID), ethPriv) + tx, err := ethtypes.SignTx(tx, ethtypes.NewEIP2930Signer(suite.chainID), ethPriv) suite.Require().NoError(err) return tx }}, @@ -221,7 +248,7 @@ func (suite *MsgsTestSuite) TestFromEthereumTx() { 21000, big.NewInt(0), nil, ) - tx, err := ethtypes.SignTx(tx, types.NewEIP2930Signer(suite.chainID), ethPriv) + tx, err := ethtypes.SignTx(tx, ethtypes.NewEIP2930Signer(suite.chainID), ethPriv) suite.Require().NoError(err) return tx }}, @@ -233,7 +260,7 @@ func (suite *MsgsTestSuite) TestFromEthereumTx() { 21000, exp_10_80, nil, ) - tx, err := ethtypes.SignTx(tx, types.NewEIP2930Signer(suite.chainID), ethPriv) + tx, err := ethtypes.SignTx(tx, ethtypes.NewEIP2930Signer(suite.chainID), ethPriv) suite.Require().NoError(err) return tx }}, @@ -241,26 +268,120 @@ func (suite *MsgsTestSuite) TestFromEthereumTx() { for _, tc := range testCases { ethTx := tc.buildTx() - tx := &MsgEthereumTx{} + tx := &types.MsgEthereumTx{} err := tx.FromEthereumTx(ethTx) if tc.expectPass { suite.Require().NoError(err) // round-trip test - suite.assertEthTxEqual(tx.AsTransaction(), ethTx) + suite.Require().NoError(assertEqual(tx.AsTransaction(), ethTx)) } else { suite.Require().Error(err) } } } -func (suite *MsgsTestSuite) assertEthTxEqual(tx1 *ethtypes.Transaction, tx2 *ethtypes.Transaction) { - suite.Require().Equal(tx1.Hash(), tx2.Hash()) - suite.Require().Equal(tx1.Size(), tx2.Size()) +// TestTransactionCoding tests serializing/de-serializing to/from rlp and JSON. +// adapted from go-ethereum +func (suite *MsgsTestSuite) TestTransactionCoding() { + key, err := crypto.GenerateKey() + if err != nil { + suite.T().Fatalf("could not generate key: %v", err) + } + var ( + signer = ethtypes.NewEIP2930Signer(common.Big1) + addr = common.HexToAddress("0x0000000000000000000000000000000000000001") + recipient = common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") + accesses = ethtypes.AccessList{{Address: addr, StorageKeys: []common.Hash{{0}}}} + ) + for i := uint64(0); i < 500; i++ { + var txdata ethtypes.TxData + switch i % 5 { + case 0: + // Legacy tx. + txdata = ðtypes.LegacyTx{ + Nonce: i, + To: &recipient, + Gas: 1, + GasPrice: big.NewInt(2), + Data: []byte("abcdef"), + } + case 1: + // Legacy tx contract creation. + txdata = ðtypes.LegacyTx{ + Nonce: i, + Gas: 1, + GasPrice: big.NewInt(2), + Data: []byte("abcdef"), + } + case 2: + // Tx with non-zero access list. + txdata = ðtypes.AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + To: &recipient, + Gas: 123457, + GasPrice: big.NewInt(10), + AccessList: accesses, + Data: []byte("abcdef"), + } + case 3: + // Tx with empty access list. + txdata = ðtypes.AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + To: &recipient, + Gas: 123457, + GasPrice: big.NewInt(10), + Data: []byte("abcdef"), + } + case 4: + // Contract creation with access list. + txdata = ðtypes.AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + Gas: 123457, + GasPrice: big.NewInt(10), + AccessList: accesses, + } + } + tx, err := ethtypes.SignNewTx(key, signer, txdata) + if err != nil { + suite.T().Fatalf("could not sign transaction: %v", err) + } + // RLP + parsedTx, err := encodeDecodeBinary(tx) + if err != nil { + suite.T().Fatal(err) + } + assertEqual(parsedTx.AsTransaction(), tx) + } +} + +func encodeDecodeBinary(tx *ethtypes.Transaction) (*types.MsgEthereumTx, error) { + data, err := tx.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("rlp encoding failed: %v", err) + } + var parsedTx = &types.MsgEthereumTx{} + if err := parsedTx.UnmarshalBinary(data); err != nil { + return nil, fmt.Errorf("rlp decoding failed: %v", err) + } + return parsedTx, nil +} - bin1, err := tx1.MarshalBinary() - suite.Require().NoError(err) - bin2, err := tx2.MarshalBinary() - suite.Require().NoError(err) - suite.Require().Equal(bin1, bin2) +func assertEqual(orig *ethtypes.Transaction, cpy *ethtypes.Transaction) error { + // compare nonce, price, gaslimit, recipient, amount, payload, V, R, S + if want, got := orig.Hash(), cpy.Hash(); want != got { + return fmt.Errorf("parsed tx differs from original tx, want %v, got %v", want, got) + } + if want, got := orig.ChainId(), cpy.ChainId(); want.Cmp(got) != 0 { + return fmt.Errorf("invalid chain id, want %d, got %d", want, got) + } + if orig.AccessList() != nil { + if !reflect.DeepEqual(orig.AccessList(), cpy.AccessList()) { + return fmt.Errorf("access list wrong") + } + } + return nil }