From 69e0873dd9dcd3c2556c32a8f272c6748036cdcc Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Wed, 30 Oct 2019 13:30:24 -0500 Subject: [PATCH] eth_estimateGas (#128) * Draft eth_estimateGas * implemented eth_estimateGas * refactored doCall to be used for both eth_call and eth_estiamteGas * updated to reflect requested changes * moved GenerateFromArgs func * removed todo * revert comment * fixes dereference issue * gofmted --- .gitignore | 3 +- app/ante_test.go | 14 +++--- rpc/eth_api.go | 106 +++++++++++++++++++++++++++++++++------- x/evm/client/cli/tx.go | 7 +-- x/evm/types/msg.go | 68 ++------------------------ x/evm/types/msg_test.go | 20 ++++---- 6 files changed, 115 insertions(+), 103 deletions(-) diff --git a/.gitignore b/.gitignore index bb3a3e0e..732a759b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ vendor/ build/ # Goland -.idea/ \ No newline at end of file +.idea/ +start.sh \ No newline at end of file diff --git a/app/ante_test.go b/app/ante_test.go index 7cd30c57..41e2d8ff 100644 --- a/app/ante_test.go +++ b/app/ante_test.go @@ -68,7 +68,7 @@ func TestValidEthTx(t *testing.T) { to := ethcmn.BytesToAddress(addr2.Bytes()) amt := big.NewInt(32) gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test")) + ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) tx := newTestEthTx(input.ctx, ethMsg, priv1) requireValidTx(t, input.anteHandler, input.ctx, tx, false) @@ -204,7 +204,7 @@ func TestEthInvalidSig(t *testing.T) { to := ethcmn.BytesToAddress(addr2.Bytes()) amt := big.NewInt(32) gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test")) + ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) tx := newTestEthTx(input.ctx, ethMsg, priv1) ctx := input.ctx.WithChainID("4") @@ -229,7 +229,7 @@ func TestEthInvalidNonce(t *testing.T) { to := ethcmn.BytesToAddress(addr2.Bytes()) amt := big.NewInt(32) gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test")) + ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) tx := newTestEthTx(input.ctx, ethMsg, priv1) requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInvalidSequence) @@ -249,7 +249,7 @@ func TestEthInsufficientBalance(t *testing.T) { to := ethcmn.BytesToAddress(addr2.Bytes()) amt := big.NewInt(32) gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test")) + ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) tx := newTestEthTx(input.ctx, ethMsg, priv1) requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInsufficientFunds) @@ -272,7 +272,7 @@ func TestEthInvalidIntrinsicGas(t *testing.T) { amt := big.NewInt(32) gas := big.NewInt(20) gasLimit := uint64(1000) - ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, gasLimit, gas, []byte("test")) + ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, gasLimit, gas, []byte("test")) tx := newTestEthTx(input.ctx, ethMsg, priv1) requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInternal) @@ -295,7 +295,7 @@ func TestEthInvalidMempoolFees(t *testing.T) { to := ethcmn.BytesToAddress(addr2.Bytes()) amt := big.NewInt(32) gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test")) + ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) tx := newTestEthTx(input.ctx, ethMsg, priv1) requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInsufficientFee) @@ -317,7 +317,7 @@ func TestEthInvalidChainID(t *testing.T) { to := ethcmn.BytesToAddress(addr2.Bytes()) amt := big.NewInt(32) gas := big.NewInt(20) - ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test")) + ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test")) tx := newTestEthTx(input.ctx, ethMsg, priv1) ctx := input.ctx.WithChainID("bad-chain-id") diff --git a/rpc/eth_api.go b/rpc/eth_api.go index 82cce33a..7b2d6684 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -9,8 +9,9 @@ import ( emintcrypto "github.com/cosmos/ethermint/crypto" emintkeys "github.com/cosmos/ethermint/keys" - "github.com/cosmos/ethermint/rpc/args" + params "github.com/cosmos/ethermint/rpc/args" emint "github.com/cosmos/ethermint/types" + etypes "github.com/cosmos/ethermint/types" "github.com/cosmos/ethermint/utils" "github.com/cosmos/ethermint/version" "github.com/cosmos/ethermint/x/evm" @@ -23,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" @@ -32,7 +32,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authutils "github.com/cosmos/cosmos-sdk/x/auth/client/utils" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/spf13/viper" ) @@ -249,7 +248,7 @@ func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil } // SendTransaction sends an Ethereum transaction. -func (e *PublicEthAPI) SendTransaction(args args.SendTxArgs) (common.Hash, error) { +func (e *PublicEthAPI) SendTransaction(args params.SendTxArgs) (common.Hash, error) { // TODO: Change this functionality to find an unlocked account by address if e.key == nil || !bytes.Equal(e.key.PubKey().Address().Bytes(), args.From.Bytes()) { return common.Hash{}, keystore.ErrLocked @@ -262,7 +261,7 @@ func (e *PublicEthAPI) SendTransaction(args args.SendTxArgs) (common.Hash, error } // Assemble transaction from fields - tx, err := types.GenerateFromArgs(args, e.cliCtx) + tx, err := e.GenerateFromArgs(args) if err != nil { return common.Hash{}, err } @@ -341,8 +340,14 @@ type CallArgs struct { // Call performs a raw contract call. func (e *PublicEthAPI) Call(args CallArgs, blockNr rpc.BlockNumber, overrides *map[common.Address]account) (hexutil.Bytes, error) { - result, err := e.doCall(args, blockNr, vm.Config{}, big.NewInt(emint.DefaultRPCGasLimit)) - return (hexutil.Bytes)(result), err + result, err := e.doCall(args, blockNr, big.NewInt(emint.DefaultRPCGasLimit)) + if err != nil { + return []byte{}, err + } + + _, _, ret, err := types.DecodeReturnData(result.Data) + + return (hexutil.Bytes)(ret), err } // account indicates the overriding fields of account during the execution of @@ -360,7 +365,7 @@ type account struct { } // DoCall performs a simulated call operation through the evm -func (e *PublicEthAPI) doCall(args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, globalGasCap *big.Int) ([]byte, error) { +func (e *PublicEthAPI) doCall(args CallArgs, blockNr rpc.BlockNumber, globalGasCap *big.Int) (sdk.Result, error) { // Set height for historical queries ctx := e.cliCtx.WithHeight(blockNr.Int64()) @@ -421,28 +426,31 @@ func (e *PublicEthAPI) doCall(args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.C txEncoder := authutils.GetTxEncoder(ctx.Codec) txBytes, err := txEncoder(tx) if err != nil { - return []byte{}, err + return sdk.Result{}, err } // Transaction simulation through query res, _, err := ctx.QueryWithData("app/simulate", txBytes) if err != nil { - return []byte{}, err + return sdk.Result{}, err } var simResult sdk.Result - if err = ctx.Codec.UnmarshalBinaryLengthPrefixed(res, &simResult); err != nil { - return nil, err + if err = e.cliCtx.Codec.UnmarshalBinaryLengthPrefixed(res, &simResult); err != nil { + return sdk.Result{}, err } - _, _, ret, err := types.DecodeReturnData(simResult.Data) - - return ret, err + return simResult, nil } // EstimateGas estimates gas usage for the given smart contract call. -func (e *PublicEthAPI) EstimateGas(args CallArgs, blockNum BlockNumber) hexutil.Uint64 { - return 0 +func (e *PublicEthAPI) EstimateGas(args CallArgs, blockNr rpc.BlockNumber) (hexutil.Uint64, error) { + result, err := e.doCall(args, blockNr, big.NewInt(emint.DefaultRPCGasLimit)) + if err != nil { + return 0, err + } + + return hexutil.Uint64(result.GasUsed), nil } // GetBlockByHash returns the block identified by hash. @@ -844,3 +852,67 @@ func (e *PublicEthAPI) getGasLimit() (int64, error) { e.gasLimit = &gasLimit return gasLimit, nil } + +// GenerateFromArgs populates tx message with args (used in RPC API) +func (e *PublicEthAPI) GenerateFromArgs(args params.SendTxArgs) (msg *types.EthereumTxMsg, err error) { + var nonce uint64 + + var gasLimit uint64 + + amount := (*big.Int)(args.Value) + + gasPrice := (*big.Int)(args.GasPrice) + + if args.GasPrice == nil { + + // Set default gas price + // TODO: Change to min gas price from context once available through server/daemon + gasPrice = big.NewInt(etypes.DefaultGasPrice) + } + + if args.Nonce == nil { + // Get nonce (sequence) from account + from := sdk.AccAddress(args.From.Bytes()) + _, nonce, err = authtypes.NewAccountRetriever(e.cliCtx).GetAccountNumberSequence(from) + if err != nil { + return nil, err + } + } else { + nonce = (uint64)(*args.Nonce) + } + + if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { + return nil, fmt.Errorf(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) + } + + // Sets input to either Input or Data, if both are set and not equal error above returns + var input []byte + if args.Input != nil { + input = *args.Input + } else if args.Data != nil { + input = *args.Data + } + + if args.To == nil { + // Contract creation + if len(input) == 0 { + return nil, fmt.Errorf("contract creation without any data provided") + } + } + if args.Gas == nil { + callArgs := CallArgs{ + From: &args.From, + To: args.To, + Gas: args.Gas, + GasPrice: args.GasPrice, + Value: args.Value, + Data: args.Data, + } + g, _ := e.EstimateGas(callArgs, rpc.BlockNumber(e.cliCtx.Height)) + gasLimit = uint64(g) + } else { + gasLimit = (uint64)(*args.Gas) + } + + return types.NewEthereumTxMsg(nonce, args.To, amount, gasLimit, gasPrice, input), nil +} diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go index 24b842fc..96426c17 100644 --- a/x/evm/client/cli/tx.go +++ b/x/evm/client/cli/tx.go @@ -71,9 +71,9 @@ func GetCmdGenTx(cdc *codec.Codec) *cobra.Command { } payload := args[4] - + addr := ethcmn.HexToAddress(args[0]) // TODO: Remove explicit photon check and check variables - msg := types.NewEthereumTxMsg(0, ethcmn.HexToAddress(args[0]), big.NewInt(coins.AmountOf(emintTypes.DenomDefault).Int64()), gasLimit, new(big.Int).SetUint64(gasPrice), []byte(payload)) + msg := types.NewEthereumTxMsg(0, &addr, big.NewInt(coins.AmountOf(emintTypes.DenomDefault).Int64()), gasLimit, new(big.Int).SetUint64(gasPrice), []byte(payload)) err = msg.ValidateBasic() if err != nil { return err @@ -125,7 +125,8 @@ func GetCmdGenETHTx(cdc *codec.Codec) *cobra.Command { var tx *types.EthereumTxMsg if len(args) == 5 { - tx = types.NewEthereumTxMsg(txBldr.Sequence(), ethcmn.HexToAddress(args[4]), big.NewInt(coins.AmountOf(emintTypes.DenomDefault).Int64()), gasLimit, new(big.Int).SetUint64(gasPrice), []byte(payload)) + addr := ethcmn.HexToAddress(args[4]) + tx = types.NewEthereumTxMsg(txBldr.Sequence(), &addr, big.NewInt(coins.AmountOf(emintTypes.DenomDefault).Int64()), gasLimit, new(big.Int).SetUint64(gasPrice), []byte(payload)) } else { tx = types.NewEthereumTxMsgContract(txBldr.Sequence(), big.NewInt(coins.AmountOf(emintTypes.DenomDefault).Int64()), gasLimit, new(big.Int).SetUint64(gasPrice), []byte(payload)) } diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index f5c59137..393dd090 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -1,21 +1,16 @@ package types import ( - "bytes" "crypto/ecdsa" "errors" "fmt" "io" "math/big" "sync/atomic" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/ethermint/rpc/args" "github.com/cosmos/ethermint/types" - "github.com/cosmos/cosmos-sdk/client/context" ethcmn "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" ethcrypto "github.com/ethereum/go-ethereum/crypto" @@ -75,11 +70,11 @@ type ( // NewEthereumTxMsg returns a reference to a new Ethereum transaction message. func NewEthereumTxMsg( - nonce uint64, to ethcmn.Address, amount *big.Int, + nonce uint64, to *ethcmn.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte, ) *EthereumTxMsg { - return newEthereumTxMsg(nonce, &to, amount, gasLimit, gasPrice, payload) + return newEthereumTxMsg(nonce, to, amount, gasLimit, gasPrice, payload) } // NewEthereumTxMsgContract returns a reference to a new Ethereum transaction @@ -376,61 +371,4 @@ func recoverEthSig(R, S, Vb *big.Int, sigHash ethcmn.Hash) (ethcmn.Address, erro copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:]) return addr, nil -} - -// GenerateFromArgs populates tx message with args (used in RPC API) -func GenerateFromArgs(args args.SendTxArgs, ctx context.CLIContext) (msg *EthereumTxMsg, err error) { - var nonce uint64 - - var gasLimit uint64 - - amount := (*big.Int)(args.Value) - - gasPrice := (*big.Int)(args.GasPrice) - - if args.GasPrice == nil { - // Set default gas price - // TODO: Change to min gas price from context once available through server/daemon - gasPrice = big.NewInt(types.DefaultGasPrice) - } - - if args.Nonce == nil { - // Get nonce (sequence) from account - from := sdk.AccAddress(args.From.Bytes()) - _, nonce, err = authtypes.NewAccountRetriever(ctx).GetAccountNumberSequence(from) - if err != nil { - return nil, err - } - } else { - nonce = (uint64)(*args.Nonce) - } - - if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { - return nil, fmt.Errorf(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) - } - - // Sets input to either Input or Data, if both are set and not equal error above returns - var input []byte - if args.Input != nil { - input = *args.Input - } else if args.Data != nil { - input = *args.Data - } - - if args.To == nil { - // Contract creation - if len(input) == 0 { - return nil, fmt.Errorf("contract creation without any data provided") - } - } - - if args.Gas == nil { - // Estimate the gas usage if necessary. - // TODO: Set gas based on estimate when simulating txs are setup - gasLimit = 60000 - } else { - gasLimit = (uint64)(*args.Gas) - } - - return newEthereumTxMsg(nonce, args.To, amount, gasLimit, gasPrice, input), nil -} +} \ No newline at end of file diff --git a/x/evm/types/msg_test.go b/x/evm/types/msg_test.go index 0bbb53f2..3ec12649 100644 --- a/x/evm/types/msg_test.go +++ b/x/evm/types/msg_test.go @@ -19,7 +19,7 @@ import ( func TestMsgEthereumTx(t *testing.T) { addr := GenerateEthAddress() - msg1 := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test")) + msg1 := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) require.NotNil(t, msg1) require.Equal(t, *msg1.Data.Recipient, addr) @@ -27,7 +27,7 @@ func TestMsgEthereumTx(t *testing.T) { require.NotNil(t, msg2) require.Nil(t, msg2.Data.Recipient) - msg3 := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test")) + msg3 := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) require.Equal(t, msg3.Route(), RouteEthereumTxMsg) require.Equal(t, msg3.Type(), TypeEthereumTxMsg) require.Panics(t, func() { msg3.GetSigners() }) @@ -50,7 +50,7 @@ func TestMsgEthereumTxValidation(t *testing.T) { } for i, tc := range testCases { - msg := NewEthereumTxMsg(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload) + msg := NewEthereumTxMsg(tc.nonce, &tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", i) @@ -64,14 +64,14 @@ func TestMsgEthereumTxRLPSignBytes(t *testing.T) { addr := ethcmn.BytesToAddress([]byte("test_address")) chainID := big.NewInt(3) - msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test")) + msg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) hash := msg.RLPSignBytes(chainID) require.Equal(t, "5BD30E35AD27449390B14C91E6BCFDCAADF8FE44EF33680E3BC200FC0DC083C7", fmt.Sprintf("%X", hash)) } func TestMsgEthereumTxRLPEncode(t *testing.T) { addr := ethcmn.BytesToAddress([]byte("test_address")) - msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test")) + msg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) raw, err := rlp.EncodeToBytes(msg) require.NoError(t, err) @@ -83,7 +83,7 @@ func TestMsgEthereumTxRLPDecode(t *testing.T) { raw := ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080") addr := ethcmn.BytesToAddress([]byte("test_address")) - expectedMsg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test")) + expectedMsg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) err := rlp.Decode(bytes.NewReader(raw), &msg) require.NoError(t, err) @@ -92,7 +92,7 @@ func TestMsgEthereumTxRLPDecode(t *testing.T) { func TestMsgEthereumTxHash(t *testing.T) { addr := ethcmn.BytesToAddress([]byte("test_address")) - msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test")) + msg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) hash := msg.Hash() require.Equal(t, "E2AA2E68E7586AE9700F1D3D643330866B6AC2B6CA4C804F7C85ECB11D0B0B29", fmt.Sprintf("%X", hash)) @@ -107,7 +107,7 @@ func TestMsgEthereumTxSig(t *testing.T) { addr2 := ethcmn.BytesToAddress(priv2.PubKey().Address().Bytes()) // require valid signature passes validation - msg := NewEthereumTxMsg(0, addr1, nil, 100000, nil, []byte("test")) + msg := NewEthereumTxMsg(0, &addr1, nil, 100000, nil, []byte("test")) msg.Sign(chainID, priv1.ToECDSA()) signer, err := msg.VerifySig(chainID) @@ -116,7 +116,7 @@ func TestMsgEthereumTxSig(t *testing.T) { require.NotEqual(t, addr2, signer) // require invalid chain ID fail validation - msg = NewEthereumTxMsg(0, addr1, nil, 100000, nil, []byte("test")) + msg = NewEthereumTxMsg(0, &addr1, nil, 100000, nil, []byte("test")) msg.Sign(chainID, priv1.ToECDSA()) signer, err = msg.VerifySig(big.NewInt(4)) @@ -126,7 +126,7 @@ func TestMsgEthereumTxSig(t *testing.T) { func TestMsgEthereumTxAmino(t *testing.T) { addr := GenerateEthAddress() - msg := NewEthereumTxMsg(5, addr, big.NewInt(1), 100000, big.NewInt(3), []byte("test")) + msg := NewEthereumTxMsg(5, &addr, big.NewInt(1), 100000, big.NewInt(3), []byte("test")) msg.Data.V = big.NewInt(1) msg.Data.R = big.NewInt(2)