feat: add raw ethereum tx CLI (#712)
Closes #709 fix index Apply suggestions from code review Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> fix lint transaction decoding unit test test BuildTx fix lint changelog
This commit is contained in:
parent
d1446fc1f4
commit
10c49f7748
@ -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, 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
|
* (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
|
* (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
|
### Bug Fixes
|
||||||
|
|
||||||
|
@ -20,10 +20,8 @@ import (
|
|||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
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"
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"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
|
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
|
// Query params to use the EVM denomination
|
||||||
res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{})
|
res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -475,23 +457,14 @@ func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error)
|
|||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
txData, err := evmtypes.UnpackTxData(ethereumTx.Data)
|
cosmosTx, err := ethereumTx.BuildTx(e.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom)
|
||||||
if err != nil {
|
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
|
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
|
// Encode transaction by default Tx encoder
|
||||||
txBytes, err := e.clientCtx.TxConfig.TxEncoder()(builder.GetTx())
|
txBytes, err := e.clientCtx.TxConfig.TxEncoder()(cosmosTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.logger.Error("failed to encode eth tx using default encoder", "error", err.Error())
|
e.logger.Error("failed to encode eth tx using default encoder", "error", err.Error())
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
|
111
x/evm/client/cli/tx.go
Normal file
111
x/evm/client/cli/tx.go
Normal file
@ -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
|
||||||
|
}
|
@ -76,7 +76,7 @@ func (b AppModuleBasic) RegisterGRPCGatewayRoutes(c client.Context, serveMux *ru
|
|||||||
|
|
||||||
// GetTxCmd returns the root tx command for the evm module.
|
// GetTxCmd returns the root tx command for the evm module.
|
||||||
func (AppModuleBasic) GetTxCmd() *cobra.Command {
|
func (AppModuleBasic) GetTxCmd() *cobra.Command {
|
||||||
return nil
|
return cli.GetTxCmd()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetQueryCmd returns no root query command for the evm module.
|
// GetQueryCmd returns no root query command for the evm module.
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
"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"
|
"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 {
|
func (msg MsgEthereumTx) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
|
||||||
return unpacker.UnpackAny(msg.Data, new(TxData))
|
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
|
||||||
|
}
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
package types
|
package types_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/tharsis/ethermint/crypto/ethsecp256k1"
|
"github.com/tharsis/ethermint/crypto/ethsecp256k1"
|
||||||
"github.com/tharsis/ethermint/tests"
|
"github.com/tharsis/ethermint/tests"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
ethtypes "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"
|
const invalidFromAddress = "0x0000"
|
||||||
@ -25,6 +32,8 @@ type MsgsTestSuite struct {
|
|||||||
from common.Address
|
from common.Address
|
||||||
to common.Address
|
to common.Address
|
||||||
chainID *big.Int
|
chainID *big.Int
|
||||||
|
|
||||||
|
clientCtx client.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMsgsTestSuite(t *testing.T) {
|
func TestMsgsTestSuite(t *testing.T) {
|
||||||
@ -38,25 +47,43 @@ func (suite *MsgsTestSuite) SetupTest() {
|
|||||||
suite.from = from
|
suite.from = from
|
||||||
suite.to = tests.GenerateAddress()
|
suite.to = tests.GenerateAddress()
|
||||||
suite.chainID = big.NewInt(1)
|
suite.chainID = big.NewInt(1)
|
||||||
|
|
||||||
|
encodingConfig := encoding.MakeConfig(app.ModuleBasics)
|
||||||
|
suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *MsgsTestSuite) TestMsgEthereumTx_Constructor() {
|
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.Data.To, suite.to.Hex())
|
||||||
suite.Require().Equal(msg.Route(), RouterKey)
|
suite.Require().Equal(msg.Route(), types.RouterKey)
|
||||||
suite.Require().Equal(msg.Type(), TypeMsgEthereumTx)
|
suite.Require().Equal(msg.Type(), types.TypeMsgEthereumTx)
|
||||||
// suite.Require().NotNil(msg.To())
|
// suite.Require().NotNil(msg.To())
|
||||||
suite.Require().Equal(msg.GetMsgs(), []sdk.Msg{msg})
|
suite.Require().Equal(msg.GetMsgs(), []sdk.Msg{msg})
|
||||||
suite.Require().Panics(func() { msg.GetSigners() })
|
suite.Require().Panics(func() { msg.GetSigners() })
|
||||||
suite.Require().Panics(func() { msg.GetSignBytes() })
|
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().NotNil(msg)
|
||||||
// suite.Require().Empty(msg.Data.To)
|
// suite.Require().Empty(msg.Data.To)
|
||||||
// suite.Require().Nil(msg.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() {
|
func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() {
|
||||||
hundredInt := sdk.NewInt(100)
|
hundredInt := sdk.NewInt(100)
|
||||||
zeroInt := sdk.ZeroInt()
|
zeroInt := sdk.ZeroInt()
|
||||||
@ -69,12 +96,12 @@ func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() {
|
|||||||
amount *sdk.Int
|
amount *sdk.Int
|
||||||
gasPrice *sdk.Int
|
gasPrice *sdk.Int
|
||||||
from string
|
from string
|
||||||
accessList *types.AccessList
|
accessList *ethtypes.AccessList
|
||||||
chainID *sdk.Int
|
chainID *sdk.Int
|
||||||
expectPass bool
|
expectPass bool
|
||||||
}{
|
}{
|
||||||
{msg: "pass with recipient - Legacy Tx", to: suite.to.Hex(), amount: &hundredInt, gasPrice: &hundredInt, expectPass: true},
|
{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: "pass contract - Legacy Tx", to: "", amount: &hundredInt, gasPrice: &hundredInt, expectPass: true},
|
||||||
// {msg: "invalid recipient", to: invalidFromAddress, amount: &minusOneInt, gasPrice: &hundredInt, expectPass: false},
|
// {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},
|
{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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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 {
|
for i, tc := range testCases {
|
||||||
@ -107,7 +134,7 @@ func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() {
|
|||||||
gasPrice = tc.gasPrice.BigInt()
|
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
|
tx.From = tc.from
|
||||||
|
|
||||||
err := tx.ValidateBasic()
|
err := tx.ValidateBasic()
|
||||||
@ -123,51 +150,51 @@ func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() {
|
|||||||
func (suite *MsgsTestSuite) TestMsgEthereumTx_Sign() {
|
func (suite *MsgsTestSuite) TestMsgEthereumTx_Sign() {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
msg string
|
msg string
|
||||||
tx *MsgEthereumTx
|
tx *types.MsgEthereumTx
|
||||||
ethSigner types.Signer
|
ethSigner ethtypes.Signer
|
||||||
malleate func(tx *MsgEthereumTx)
|
malleate func(tx *types.MsgEthereumTx)
|
||||||
expectPass bool
|
expectPass bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"pass - EIP2930 signer",
|
"pass - EIP2930 signer",
|
||||||
NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), &types.AccessList{}),
|
types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), ðtypes.AccessList{}),
|
||||||
types.NewEIP2930Signer(suite.chainID),
|
ethtypes.NewEIP2930Signer(suite.chainID),
|
||||||
func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() },
|
func(tx *types.MsgEthereumTx) { tx.From = suite.from.Hex() },
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pass - EIP155 signer",
|
"pass - EIP155 signer",
|
||||||
NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil),
|
types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil),
|
||||||
types.NewEIP155Signer(suite.chainID),
|
ethtypes.NewEIP155Signer(suite.chainID),
|
||||||
func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() },
|
func(tx *types.MsgEthereumTx) { tx.From = suite.from.Hex() },
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pass - Homestead signer",
|
"pass - Homestead signer",
|
||||||
NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil),
|
types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil),
|
||||||
types.HomesteadSigner{},
|
ethtypes.HomesteadSigner{},
|
||||||
func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() },
|
func(tx *types.MsgEthereumTx) { tx.From = suite.from.Hex() },
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pass - Frontier signer",
|
"pass - Frontier signer",
|
||||||
NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil),
|
types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), nil),
|
||||||
types.FrontierSigner{},
|
ethtypes.FrontierSigner{},
|
||||||
func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() },
|
func(tx *types.MsgEthereumTx) { tx.From = suite.from.Hex() },
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"no from address ",
|
"no from address ",
|
||||||
NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), &types.AccessList{}),
|
types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), ðtypes.AccessList{}),
|
||||||
types.NewEIP2930Signer(suite.chainID),
|
ethtypes.NewEIP2930Signer(suite.chainID),
|
||||||
func(tx *MsgEthereumTx) { tx.From = "" },
|
func(tx *types.MsgEthereumTx) { tx.From = "" },
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from address ≠ signer address",
|
"from address ≠ signer address",
|
||||||
NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), &types.AccessList{}),
|
types.NewTx(suite.chainID, 0, &suite.to, nil, 100000, nil, nil, nil, []byte("test"), ðtypes.AccessList{}),
|
||||||
types.NewEIP2930Signer(suite.chainID),
|
ethtypes.NewEIP2930Signer(suite.chainID),
|
||||||
func(tx *MsgEthereumTx) { tx.From = suite.to.Hex() },
|
func(tx *types.MsgEthereumTx) { tx.From = suite.to.Hex() },
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -209,7 +236,7 @@ func (suite *MsgsTestSuite) TestFromEthereumTx() {
|
|||||||
21000, big.NewInt(0),
|
21000, big.NewInt(0),
|
||||||
nil,
|
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)
|
suite.Require().NoError(err)
|
||||||
return tx
|
return tx
|
||||||
}},
|
}},
|
||||||
@ -221,7 +248,7 @@ func (suite *MsgsTestSuite) TestFromEthereumTx() {
|
|||||||
21000, big.NewInt(0),
|
21000, big.NewInt(0),
|
||||||
nil,
|
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)
|
suite.Require().NoError(err)
|
||||||
return tx
|
return tx
|
||||||
}},
|
}},
|
||||||
@ -233,7 +260,7 @@ func (suite *MsgsTestSuite) TestFromEthereumTx() {
|
|||||||
21000, exp_10_80,
|
21000, exp_10_80,
|
||||||
nil,
|
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)
|
suite.Require().NoError(err)
|
||||||
return tx
|
return tx
|
||||||
}},
|
}},
|
||||||
@ -241,26 +268,120 @@ func (suite *MsgsTestSuite) TestFromEthereumTx() {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
ethTx := tc.buildTx()
|
ethTx := tc.buildTx()
|
||||||
tx := &MsgEthereumTx{}
|
tx := &types.MsgEthereumTx{}
|
||||||
err := tx.FromEthereumTx(ethTx)
|
err := tx.FromEthereumTx(ethTx)
|
||||||
if tc.expectPass {
|
if tc.expectPass {
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
// round-trip test
|
// round-trip test
|
||||||
suite.assertEthTxEqual(tx.AsTransaction(), ethTx)
|
suite.Require().NoError(assertEqual(tx.AsTransaction(), ethTx))
|
||||||
} else {
|
} else {
|
||||||
suite.Require().Error(err)
|
suite.Require().Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *MsgsTestSuite) assertEthTxEqual(tx1 *ethtypes.Transaction, tx2 *ethtypes.Transaction) {
|
// TestTransactionCoding tests serializing/de-serializing to/from rlp and JSON.
|
||||||
suite.Require().Equal(tx1.Hash(), tx2.Hash())
|
// adapted from go-ethereum
|
||||||
suite.Require().Equal(tx1.Size(), tx2.Size())
|
func (suite *MsgsTestSuite) TestTransactionCoding() {
|
||||||
|
key, err := crypto.GenerateKey()
|
||||||
bin1, err := tx1.MarshalBinary()
|
if err != nil {
|
||||||
suite.Require().NoError(err)
|
suite.T().Fatalf("could not generate key: %v", err)
|
||||||
bin2, err := tx2.MarshalBinary()
|
}
|
||||||
suite.Require().NoError(err)
|
var (
|
||||||
suite.Require().Equal(bin1, bin2)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user