diff --git a/app/ante.go b/app/ante.go index d54a37b9..07fc2c63 100644 --- a/app/ante.go +++ b/app/ante.go @@ -155,6 +155,16 @@ func ethAnteHandler( gas, _ := ethcore.IntrinsicGas(ethTxMsg.Data.Payload, ethTxMsg.To() == nil, true) newCtx.GasMeter().ConsumeGas(gas, "eth intrinsic gas") + // no need to increment sequence on CheckTx or RecheckTx + if !(ctx.IsCheckTx() && !sim) { + // increment sequence of sender + acc := ak.GetAccount(ctx, senderAddr) + if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { + panic(err) + } + ak.SetAccount(ctx, acc) + } + return newCtx, nil } diff --git a/cmd/emintd/genaccounts.go b/client/genaccounts/main.go similarity index 99% rename from cmd/emintd/genaccounts.go rename to client/genaccounts/main.go index 9b1a23bf..7de5765e 100644 --- a/cmd/emintd/genaccounts.go +++ b/client/genaccounts/main.go @@ -1,4 +1,4 @@ -package main +package genaccounts import ( "errors" diff --git a/cmd/emintcli/main.go b/cmd/emintcli/main.go index 078d2c96..1795cbf4 100644 --- a/cmd/emintcli/main.go +++ b/cmd/emintcli/main.go @@ -5,6 +5,7 @@ import ( "path" emintapp "github.com/cosmos/ethermint/app" + emintcrypto "github.com/cosmos/ethermint/crypto" "github.com/cosmos/ethermint/rpc" "github.com/tendermint/go-amino" @@ -20,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + tmamino "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/libs/cli" ) @@ -28,6 +30,9 @@ func main() { cdc := emintapp.MakeCodec() + tmamino.RegisterKeyType(emintcrypto.PubKeySecp256k1{}, emintcrypto.PubKeyAminoName) + tmamino.RegisterKeyType(emintcrypto.PrivKeySecp256k1{}, emintcrypto.PrivKeyAminoName) + cryptokeys.CryptoCdc = cdc clientkeys.KeysCdc = cdc diff --git a/cmd/emintd/main.go b/cmd/emintd/main.go index f5f497d5..08d87312 100644 --- a/cmd/emintd/main.go +++ b/cmd/emintd/main.go @@ -24,8 +24,11 @@ import ( "github.com/cosmos/ethermint/app" emintapp "github.com/cosmos/ethermint/app" + "github.com/cosmos/ethermint/client/genaccounts" + emintcrypto "github.com/cosmos/ethermint/crypto" abci "github.com/tendermint/tendermint/abci/types" + tmamino "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/libs/cli" tmlog "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" @@ -37,6 +40,9 @@ func main() { cdc := emintapp.MakeCodec() + tmamino.RegisterKeyType(emintcrypto.PubKeySecp256k1{}, emintcrypto.PubKeyAminoName) + tmamino.RegisterKeyType(emintcrypto.PrivKeySecp256k1{}, emintcrypto.PrivKeyAminoName) + cryptokeys.CryptoCdc = cdc genutil.ModuleCdc = cdc genutiltypes.ModuleCdc = cdc @@ -65,7 +71,7 @@ func main() { genutilcli.ValidateGenesisCmd(ctx, cdc, emintapp.ModuleBasics), // AddGenesisAccountCmd allows users to add accounts to the genesis file - AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome), + genaccounts.AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome), ) // Tendermint node base commands diff --git a/crypto/secp256k1.go b/crypto/secp256k1.go index 7b6c4cdb..9bdcf867 100644 --- a/crypto/secp256k1.go +++ b/crypto/secp256k1.go @@ -10,12 +10,9 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" tmcrypto "github.com/tendermint/tendermint/crypto" - tmamino "github.com/tendermint/tendermint/crypto/encoding/amino" ) func init() { - tmamino.RegisterKeyType(PubKeySecp256k1{}, PubKeyAminoName) - tmamino.RegisterKeyType(PrivKeySecp256k1{}, PrivKeyAminoName) authtypes.RegisterAccountTypeCodec(PubKeySecp256k1{}, PubKeyAminoName) authtypes.RegisterAccountTypeCodec(PrivKeySecp256k1{}, PrivKeyAminoName) } diff --git a/rpc/eth_api.go b/rpc/eth_api.go index af04151e..cda253bb 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -268,7 +268,7 @@ func (e *PublicEthAPI) SendTransaction(args params.SendTxArgs) (common.Hash, err } // Assemble transaction from fields - tx, err := e.GenerateFromArgs(args) + tx, err := e.generateFromArgs(args) if err != nil { return common.Hash{}, err } @@ -871,8 +871,8 @@ func (e *PublicEthAPI) getGasLimit() (int64, error) { 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) { +// 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 diff --git a/x/evm/client/cli/query.go b/x/evm/client/cli/query.go index 21c19722..61f15769 100644 --- a/x/evm/client/cli/query.go +++ b/x/evm/client/cli/query.go @@ -3,11 +3,14 @@ package cli import ( "fmt" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/ethermint/x/evm/types" - "github.com/spf13/cobra" ) // GetQueryCmd defines evm module queries through the cli @@ -20,35 +23,12 @@ func GetQueryCmd(moduleName string, cdc *codec.Codec) *cobra.Command { RunE: client.ValidateCmd, } evmQueryCmd.AddCommand(client.GetCommands( - GetCmdGetBlockNumber(moduleName, cdc), GetCmdGetStorageAt(moduleName, cdc), GetCmdGetCode(moduleName, cdc), - GetCmdGetNonce(moduleName, cdc), )...) return evmQueryCmd } -// GetCmdGetBlockNumber queries information about the current block number -func GetCmdGetBlockNumber(queryRoute string, cdc *codec.Codec) *cobra.Command { - return &cobra.Command{ - Use: "block-number", - Short: "Gets block number (block height)", - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", queryRoute), nil) - if err != nil { - fmt.Printf("could not resolve: %s\n", err) - return nil - } - - var out types.QueryResBlockNumber - cdc.MustUnmarshalJSON(res, &out) - return cliCtx.PrintOutput(out) - }, - } -} - // GetCmdGetStorageAt queries a key in an accounts storage func GetCmdGetStorageAt(queryRoute string, cdc *codec.Codec) *cobra.Command { return &cobra.Command{ @@ -58,14 +38,18 @@ func GetCmdGetStorageAt(queryRoute string, cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - // TODO: Validate args - account := args[0] - key := args[1] - - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/storage/%s/%s", queryRoute, account, key), nil) + account, err := accountToHex(args[0]) if err != nil { - fmt.Printf("could not resolve: %s\n", err) - return nil + return errors.Wrap(err, "could not parse account address") + } + + key := formatKeyToHash(args[1]) + + res, _, err := cliCtx.Query( + fmt.Sprintf("custom/%s/storage/%s/%s", queryRoute, account, key)) + + if err != nil { + return fmt.Errorf("could not resolve: %s", err) } var out types.QueryResStorage cdc.MustUnmarshalJSON(res, &out) @@ -83,41 +67,21 @@ func GetCmdGetCode(queryRoute string, cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - // TODO: Validate args - account := args[0] - - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/code/%s", queryRoute, account), nil) + account, err := accountToHex(args[0]) if err != nil { - fmt.Printf("could not resolve: %s\n", err) - return nil + return errors.Wrap(err, "could not parse account address") } + + res, _, err := cliCtx.Query( + fmt.Sprintf("custom/%s/code/%s", queryRoute, account)) + + if err != nil { + return fmt.Errorf("could not resolve: %s", err) + } + var out types.QueryResCode cdc.MustUnmarshalJSON(res, &out) return cliCtx.PrintOutput(out) }, } } - -// GetCmdGetCode queries the nonce field of a given address -func GetCmdGetNonce(queryRoute string, cdc *codec.Codec) *cobra.Command { - return &cobra.Command{ - Use: "nonce [account]", - Short: "Gets nonce from an account", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - // TODO: Validate args - account := args[0] - - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/nonce/%s", queryRoute, account), nil) - if err != nil { - fmt.Printf("could not resolve: %s\n", err) - return nil - } - var out types.QueryResNonce - cdc.MustUnmarshalJSON(res, &out) - return cliCtx.PrintOutput(out) - }, - } -} diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go index e8a3ad5f..416eb580 100644 --- a/x/evm/client/cli/tx.go +++ b/x/evm/client/cli/tx.go @@ -1,22 +1,25 @@ package cli import ( - "math/big" + "fmt" "strconv" + "strings" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth/client/utils" - emintTypes "github.com/cosmos/ethermint/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + emint "github.com/cosmos/ethermint/types" "github.com/cosmos/ethermint/x/evm/types" - - ethcmn "github.com/ethereum/go-ethereum/common" ) // GetTxCmd defines the CLI commands regarding evm module transactions @@ -30,56 +33,126 @@ func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { } evmTxCmd.AddCommand(client.PostCommands( - // TODO: Add back generating cosmos tx for Ethereum tx message - // GetCmdGenTx(cdc), + GetCmdGenTx(cdc), + GetCmdGenCreateTx(cdc), )...) return evmTxCmd } -// GetCmdGenTx generates an ethereum transaction wrapped in a Cosmos standard transaction +// GetCmdGenTx generates an Emint transaction (excludes create operations) func GetCmdGenTx(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ - Use: "generate-tx [ethaddress] [amount] [gaslimit] [gasprice] [payload]", - Short: "generate eth tx wrapped in a Cosmos Standard tx", - Args: cobra.ExactArgs(5), + Use: "send [to_address] [amount (in photons)] []", + Short: "send transaction to address (call operations included)", + Args: cobra.RangeArgs(2, 3), RunE: func(cmd *cobra.Command, args []string) error { - // TODO: remove inputs and infer based on StdTx cliCtx := context.NewCLIContext().WithCodec(cdc) txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) - kb, err := keys.NewKeyBaseFromHomeFlag() + toAddr, err := cosmosAddressFromArg(args[0]) if err != nil { - panic(err) + return errors.Wrap(err, "must provide a valid Bech32 address for to_address") } - coins, err := sdk.ParseCoins(args[1]) + // Ambiguously decode amount from any base + amount, err := strconv.ParseInt(args[1], 0, 64) if err != nil { return err } - gasLimit, err := strconv.ParseUint(args[2], 0, 64) - if err != nil { - return err + var data []byte + if len(args) > 2 { + payload := args[2] + if !strings.HasPrefix(payload, "0x") { + payload = "0x" + payload + } + + data, err = hexutil.Decode(payload) + if err != nil { + fmt.Println(err) + } } - gasPrice, err := strconv.ParseUint(args[3], 0, 64) + from := cliCtx.GetFromAddress() + + _, seq, err := authtypes.NewAccountRetriever(cliCtx).GetAccountNumberSequence(from) if err != nil { - return err + return errors.Wrap(err, "Could not retrieve account sequence") } - payload := args[4] - addr := ethcmn.HexToAddress(args[0]) - // TODO: Remove explicit photon check and check variables - msg := types.NewEthereumTxMsg(0, &addr, big.NewInt(coins.AmountOf(emintTypes.DenomDefault).Int64()), gasLimit, new(big.Int).SetUint64(gasPrice), []byte(payload)) + // TODO: Potentially allow overriding of gas price and gas limit + msg := types.NewEmintMsg(seq, &toAddr, sdk.NewInt(amount), txBldr.Gas(), + sdk.NewInt(emint.DefaultGasPrice), data, from) + err = msg.ValidateBasic() if err != nil { return err } - // TODO: possibly overwrite gas values in txBldr - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr.WithKeybase(kb), []sdk.Msg{msg}) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } +} + +// GetCmdGenTx generates an Emint transaction (excludes create operations) +func GetCmdGenCreateTx(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "create [contract bytecode] []", + Short: "create contract through the evm using compiled bytecode", + Args: cobra.RangeArgs(1, 2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + payload := args[0] + if !strings.HasPrefix(payload, "0x") { + payload = "0x" + payload + } + + data, err := hexutil.Decode(payload) + if err != nil { + fmt.Println(err) + } + + var amount int64 + if len(args) > 1 { + // Ambiguously decode amount from any base + amount, err = strconv.ParseInt(args[1], 0, 64) + if err != nil { + return errors.Wrap(err, "invalid amount") + } + } + + from := cliCtx.GetFromAddress() + + _, seq, err := authtypes.NewAccountRetriever(cliCtx).GetAccountNumberSequence(from) + if err != nil { + return errors.Wrap(err, "Could not retrieve account sequence") + } + + // TODO: Potentially allow overriding of gas price and gas limit + msg := types.NewEmintMsg(seq, nil, sdk.NewInt(amount), txBldr.Gas(), + sdk.NewInt(emint.DefaultGasPrice), data, from) + + err = msg.ValidateBasic() + if err != nil { + return err + } + + if err = utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}); err != nil { + return err + } + + contractAddr := ethcrypto.CreateAddress(common.BytesToAddress(from.Bytes()), seq) + fmt.Printf( + "Contract will be deployed to: \nHex: %s\nCosmos Address: %s\n", + contractAddr.Hex(), + sdk.AccAddress(contractAddr.Bytes()), + ) + return nil }, } } diff --git a/x/evm/client/cli/utils.go b/x/evm/client/cli/utils.go new file mode 100644 index 00000000..ef3fd5cd --- /dev/null +++ b/x/evm/client/cli/utils.go @@ -0,0 +1,63 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/common" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func accountToHex(addr string) (string, error) { + if strings.HasPrefix(addr, sdk.Bech32PrefixAccAddr) { + // Check to see if address is Cosmos bech32 formatted + toAddr, err := sdk.AccAddressFromBech32(addr) + if err != nil { + return "", errors.Wrap(err, "must provide a valid Bech32 address") + } + ethAddr := common.BytesToAddress(toAddr.Bytes()) + return ethAddr.Hex(), nil + } + + if !strings.HasPrefix(addr, "0x") { + addr = "0x" + addr + } + + valid := common.IsHexAddress(addr) + if !valid { + return "", fmt.Errorf("%s is not a valid Ethereum or Cosmos address", addr) + } + + ethAddr := common.HexToAddress(addr) + + return ethAddr.Hex(), nil +} + +func formatKeyToHash(key string) string { + if !strings.HasPrefix(key, "0x") { + key = "0x" + key + } + + ethkey := common.HexToHash(key) + + return ethkey.Hex() +} + +func cosmosAddressFromArg(addr string) (sdk.AccAddress, error) { + if strings.HasPrefix(addr, sdk.Bech32PrefixAccAddr) { + // Check to see if address is Cosmos bech32 formatted + toAddr, err := sdk.AccAddressFromBech32(addr) + if err != nil { + return nil, errors.Wrap(err, "invalid bech32 formatted address") + } + return toAddr, nil + } + + // Strip 0x prefix if exists + addr = strings.TrimPrefix(addr, "0x") + + return sdk.AccAddressFromHex(addr) +} diff --git a/x/evm/client/cli/utils_test.go b/x/evm/client/cli/utils_test.go new file mode 100644 index 00000000..568bd44a --- /dev/null +++ b/x/evm/client/cli/utils_test.go @@ -0,0 +1,82 @@ +package cli + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestAddressFormats(t *testing.T) { + testCases := []struct { + name string + addrString string + expectedHex string + expectErr bool + }{ + {"Cosmos Address", "cosmos18wvvwfmq77a6d8tza4h5sfuy2yj3jj88yqg82a", "0x3B98c72760f7BBa69D62ED6f48278451251948e7", false}, + {"hex without 0x", "3B98C72760F7BBA69D62ED6F48278451251948E7", "0x3B98c72760f7BBa69D62ED6f48278451251948e7", false}, + {"hex with mixed casing", "3b98C72760f7BBA69D62ED6F48278451251948e7", "0x3B98c72760f7BBa69D62ED6f48278451251948e7", false}, + {"hex with 0x", "0x3B98C72760F7BBA69D62ED6F48278451251948E7", "0x3B98c72760f7BBa69D62ED6f48278451251948e7", false}, + {"invalid hex ethereum address", "0x3B98C72760F7BBA69D62ED6F48278451251948E", "", true}, + {"invalid Cosmos address", "cosmos18wvvwfmq77a6d8tza4h5sfuy2yj3jj88", "", true}, + {"empty string", "", "", true}, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + hex, err := accountToHex(tc.addrString) + require.Equal(t, tc.expectErr, err != nil, err) + + if !tc.expectErr { + require.Equal(t, hex, tc.expectedHex) + } + }) + } +} + +func TestCosmosToEthereumTypes(t *testing.T) { + hexString := "0x3B98D72760f7bbA69d62Ed6F48278451251948E7" + cosmosAddr, err := sdk.AccAddressFromHex(hexString[2:]) + require.NoError(t, err) + + cosmosFormatted := cosmosAddr.String() + + // Test decoding a cosmos formatted address + decodedHex, err := accountToHex(cosmosFormatted) + require.NoError(t, err) + require.Equal(t, hexString, decodedHex) + + // Test converting cosmos address with eth address from hex + hexEth := common.HexToAddress(hexString) + convertedEth := common.BytesToAddress(cosmosAddr.Bytes()) + require.Equal(t, hexEth, convertedEth) + + // Test decoding eth hex output against hex string + ethDecoded, err := accountToHex(hexEth.Hex()) + require.NoError(t, err) + require.Equal(t, hexString, ethDecoded) +} + +func TestAddressToCosmosAddress(t *testing.T) { + baseAddr, err := sdk.AccAddressFromHex("6A98D72760f7bbA69d62Ed6F48278451251948E7") + require.NoError(t, err) + + // Test cosmos string back to address + cosmosFormatted, err := cosmosAddressFromArg(baseAddr.String()) + require.NoError(t, err) + require.Equal(t, baseAddr, cosmosFormatted) + + // Test account address from Ethereum address + ethAddr := common.BytesToAddress(baseAddr.Bytes()) + ethFormatted, err := cosmosAddressFromArg(ethAddr.Hex()) + require.NoError(t, err) + require.Equal(t, baseAddr, ethFormatted) + + // Test encoding without the 0x prefix + ethFormatted, err = cosmosAddressFromArg(ethAddr.Hex()[2:]) + require.NoError(t, err) + require.Equal(t, baseAddr, ethFormatted) +} diff --git a/x/evm/module.go b/x/evm/module.go index ce8d138a..c6dcbe1d 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -120,12 +120,18 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val // Gas costs are handled within msg handler so costs should be ignored ebCtx := ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) + // Update account balances before committing other parts of state + am.keeper.csdb.UpdateAccounts() + // Commit state objects to KV store _, err := am.keeper.csdb.WithContext(ebCtx).Commit(true) if err != nil { panic(err) } + // Clear accounts cache after account data has been committed + am.keeper.csdb.ClearStateObjects() + return []abci.ValidatorUpdate{} } diff --git a/x/evm/types/codec.go b/x/evm/types/codec.go index 4ea5dd38..a657b845 100644 --- a/x/evm/types/codec.go +++ b/x/evm/types/codec.go @@ -10,7 +10,6 @@ var ModuleCdc = codec.New() func init() { cdc := codec.New() - RegisterCodec(cdc) codec.RegisterCrypto(cdc) ModuleCdc = cdc.Seal() diff --git a/x/evm/types/key.go b/x/evm/types/key.go index d257084f..839a110b 100644 --- a/x/evm/types/key.go +++ b/x/evm/types/key.go @@ -2,7 +2,7 @@ package types const ( // ModuleName string name of module - ModuleName = "ethermint" + ModuleName = "evm" // EvmStoreKey key for ethereum storage data EvmStoreKey = "evmstore" diff --git a/x/evm/types/state_transition.go b/x/evm/types/state_transition.go index 4944e74a..1ebc9125 100644 --- a/x/evm/types/state_transition.go +++ b/x/evm/types/state_transition.go @@ -30,11 +30,8 @@ type StateTransition struct { // TransitionCSDB performs an evm state transition from a transaction func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*big.Int, sdk.Result) { - contractCreation := st.Recipient == nil - if res := st.checkNonce(); !res.IsOK() { - return nil, res - } + contractCreation := st.Recipient == nil cost, err := core.IntrinsicGas(st.Payload, contractCreation, true) if err != nil { @@ -44,7 +41,7 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*big.Int, sdk.Result) // This gas limit the the transaction gas limit with intrinsic gas subtracted gasLimit := st.GasLimit - ctx.GasMeter().GasConsumed() - csdb := st.Csdb + csdb := st.Csdb.WithContext(ctx) if st.Simulate { // gasLimit is set here because stdTxs incur gaskv charges in the ante handler, but for eth_call // the cost needs to be the same as an Ethereum transaction sent through the web3 API @@ -59,6 +56,9 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*big.Int, sdk.Result) csdb = st.Csdb.Copy() } + // Clear cache of accounts to handle changes outside of the EVM + csdb.UpdateAccounts() + // Create context for evm context := vm.Context{ CanTransfer: core.CanTransfer, @@ -88,14 +88,23 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*big.Int, sdk.Result) senderRef = vm.AccountRef(st.Sender) ) + // Get nonce of account outside of the EVM + currentNonce := st.Csdb.GetNonce(st.Sender) + // Set nonce of sender account before evm state transition for usage in generating Create address + st.Csdb.SetNonce(st.Sender, st.AccountNonce) + if contractCreation { ret, addr, leftOverGas, vmerr = vmenv.Create(senderRef, st.Payload, gasLimit, st.Amount) } else { - // Increment the nonce for the next transaction + // Increment the nonce for the next transaction (just for evm state transition) csdb.SetNonce(st.Sender, csdb.GetNonce(st.Sender)+1) + ret, leftOverGas, vmerr = vmenv.Call(senderRef, *st.Recipient, st.Payload, gasLimit, st.Amount) } + // Resets nonce to value pre state transition + st.Csdb.SetNonce(st.Sender, currentNonce) + // Generate bloom filter to be saved in tx receipt data bloomInt := big.NewInt(0) var bloomFilter ethtypes.Bloom @@ -132,18 +141,3 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*big.Int, sdk.Result) return bloomInt, sdk.Result{Data: returnData, GasUsed: st.GasLimit - leftOverGas} } - -func (st *StateTransition) checkNonce() sdk.Result { - // If simulated transaction, don't verify nonce - if !st.Simulate { - // Make sure this transaction's nonce is correct. - nonce := st.Csdb.GetNonce(st.Sender) - if nonce < st.AccountNonce { - return emint.ErrInvalidNonce("nonce too high").Result() - } else if nonce > st.AccountNonce { - return emint.ErrInvalidNonce("nonce too low").Result() - } - } - - return sdk.Result{} -} diff --git a/x/evm/types/statedb.go b/x/evm/types/statedb.go index 32a3a961..d58e89c8 100644 --- a/x/evm/types/statedb.go +++ b/x/evm/types/statedb.go @@ -9,6 +9,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + emint "github.com/cosmos/ethermint/types" + ethcmn "github.com/ethereum/go-ethereum/common" ethstate "github.com/ethereum/go-ethereum/core/state" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -523,7 +525,7 @@ func (csdb *CommitStateDB) Suicide(addr ethcmn.Address) bool { // Reset clears out all ephemeral state objects from the state db, but keeps // the underlying account mapper and store keys to avoid reloading data for the // next operations. -func (csdb *CommitStateDB) Reset(root ethcmn.Hash) error { +func (csdb *CommitStateDB) Reset(_ ethcmn.Hash) error { csdb.stateObjects = make(map[ethcmn.Address]*stateObject) csdb.stateObjectsDirty = make(map[ethcmn.Address]struct{}) csdb.thash = ethcmn.Hash{} @@ -537,6 +539,27 @@ func (csdb *CommitStateDB) Reset(root ethcmn.Hash) error { return nil } +// UpdateAccounts updates the nonce and coin balances of accounts +func (csdb *CommitStateDB) UpdateAccounts() { + for addr, so := range csdb.stateObjects { + currAcc := csdb.ak.GetAccount(csdb.ctx, sdk.AccAddress(addr.Bytes())) + emintAcc, ok := currAcc.(*emint.Account) + if ok { + if (so.Balance() != emintAcc.Balance().BigInt()) || (so.Nonce() != emintAcc.GetSequence()) { + // If queried account's balance or nonce are invalid, update the account pointer + so.account = emintAcc + } + } + + } +} + +// ClearStateObjects clears cache of state objects to handle account changes outside of the EVM +func (csdb *CommitStateDB) ClearStateObjects() { + csdb.stateObjects = make(map[ethcmn.Address]*stateObject) + csdb.stateObjectsDirty = make(map[ethcmn.Address]struct{}) +} + func (csdb *CommitStateDB) clearJournalAndRefund() { csdb.journal = newJournal() csdb.validRevisions = csdb.validRevisions[:0]