Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem: state overrides are not supported in eth_call #369

Merged
merged 11 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (ante) [#310](https://github.com/crypto-org-chain/ethermint/pull/310) Support blocking list of addresses in mempool.
* (evm) [#328](https://github.com/crypto-org-chain/ethermint/pull/328) Support precompile interface.
* (statedb) [#333](https://github.com/crypto-org-chain/ethermint/pull/333) Support native action in statedb, prepare for precompiles.
* (rpc) [#369](https://github.com/crypto-org-chain/ethermint/pull/369) Support state overrides in eth_call.

### State Machine Breaking

Expand Down
2 changes: 2 additions & 0 deletions proto/ethermint/evm/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ message EthCallRequest {
bytes proposer_address = 3 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.ConsAddress"];
// chain_id is the eip155 chain id parsed from the requested block header
int64 chain_id = 4;
// state overrides encoded as json
bytes overrides = 5;
}

// EstimateGasResponse defines EstimateGas response
Expand Down
2 changes: 1 addition & 1 deletion rpc/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/evmos/ethermint/rpc/types"

Check failure on line 35 in rpc/backend/backend.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

ST1019: package "github.com/evmos/ethermint/rpc/types" is being imported more than once (stylecheck)
rpctypes "github.com/evmos/ethermint/rpc/types"

Check failure on line 36 in rpc/backend/backend.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

ST1019(related information): other import of "github.com/evmos/ethermint/rpc/types" (stylecheck)
"github.com/evmos/ethermint/server/config"
ethermint "github.com/evmos/ethermint/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
Expand Down Expand Up @@ -128,7 +128,7 @@
SendRawTransaction(data hexutil.Bytes) (common.Hash, error)
SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error)
EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error)
DoCall(args evmtypes.TransactionArgs, blockNr rpctypes.BlockNumber) (*evmtypes.MsgEthereumTxResponse, error)
DoCall(args evmtypes.TransactionArgs, blockNr rpctypes.BlockNumber, overrides *rpctypes.StateOverride) (*evmtypes.MsgEthereumTxResponse, error)
GasPrice() (*hexutil.Big, error)

// Filter API
Expand Down
10 changes: 10 additions & 0 deletions rpc/backend/call_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@
// estimated gas used on the operation or an error if fails.
func (b *Backend) DoCall(
args evmtypes.TransactionArgs, blockNr rpctypes.BlockNumber,
overrides *rpctypes.StateOverride,
) (*evmtypes.MsgEthereumTxResponse, error) {
bz, err := json.Marshal(&args)
if err != nil {
Expand All @@ -371,11 +372,20 @@
return nil, errors.New("header not found")
}

var overridesJson []byte

Check warning on line 375 in rpc/backend/call_tx.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

var-naming: var overridesJson should be overridesJSON (revive)
if overrides != nil {
overridesJson, err = json.Marshal(overrides)
if err != nil {
return nil, err
}
}

req := evmtypes.EthCallRequest{
Args: bz,
GasCap: b.RPCGasCap(),
ProposerAddress: sdk.ConsAddress(header.Block.ProposerAddress),
ChainId: b.chainID.Int64(),
Overrides: overridesJson,
}

// From ContextWithHeight: if the provided height is 0,
Expand Down
6 changes: 3 additions & 3 deletions rpc/namespaces/ethereum/eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ type EthereumAPI interface {
//
// Allows developers to read data from the blockchain which includes executing
// smart contracts. However, no data is published to the Ethereum network.
Call(args evmtypes.TransactionArgs, blockNrOrHash rpctypes.BlockNumberOrHash, _ *rpctypes.StateOverride) (hexutil.Bytes, error)
Call(args evmtypes.TransactionArgs, blockNrOrHash rpctypes.BlockNumberOrHash, overrides *rpctypes.StateOverride) (hexutil.Bytes, error)

// Chain Information
//
Expand Down Expand Up @@ -280,15 +280,15 @@ func (e *PublicAPI) GetProof(address common.Address,
// Call performs a raw contract call.
func (e *PublicAPI) Call(args evmtypes.TransactionArgs,
blockNrOrHash rpctypes.BlockNumberOrHash,
_ *rpctypes.StateOverride,
overrides *rpctypes.StateOverride,
) (hexutil.Bytes, error) {
e.logger.Debug("eth_call", "args", args.String(), "block number or hash", blockNrOrHash)

blockNum, err := e.backend.BlockNumberFromTendermint(blockNrOrHash)
if err != nil {
return nil, err
}
data, err := e.backend.DoCall(args, blockNum)
data, err := e.backend.DoCall(args, blockNum, overrides)
if err != nil {
return []byte{}, err
}
Expand Down
37 changes: 37 additions & 0 deletions rpc/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
package types

import (
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/evmos/ethermint/x/evm/statedb"
)

// Copied the Account and StorageResult types since they are registered under an
Expand Down Expand Up @@ -70,6 +72,41 @@
// StateOverride is the collection of overridden accounts.
type StateOverride map[common.Address]OverrideAccount

// Apply overrides the fields of specified accounts into the given state.
func (diff *StateOverride) Apply(db *statedb.StateDB) error {
if diff == nil {
return nil
}
for addr, account := range *diff {
// Override account nonce.
if account.Nonce != nil {
db.SetNonce(addr, uint64(*account.Nonce))
}
// Override account(contract) code.
if account.Code != nil {
db.SetCode(addr, *account.Code)
}
// Override account balance.
if account.Balance != nil {
db.SetBalance(addr, (*big.Int)(*account.Balance))
}
if account.State != nil && account.StateDiff != nil {
return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
}
// Replace entire state if caller requires.
if account.State != nil {
db.SetStorage(addr, *account.State)
}
// Apply state diff into specified accounts.
if account.StateDiff != nil {
for key, value := range *account.StateDiff {
db.SetState(addr, key, value)
}
Comment on lines +102 to +104

Check failure

Code scanning / gosec

the value in the range statement should be _ unless copying a map: want: for key := range m Error

the value in the range statement should be _ unless copying a map: want: for key := range m
}
}
Comment on lines +80 to +106

Check failure

Code scanning / gosec

the value in the range statement should be _ unless copying a map: want: for key := range m Error

expected exactly 1 statement (either append, delete, or copying to another map) in a range with a map, got 6
return nil
}

// OverrideAccount indicates the overriding fields of account during the execution of
// a message call.
// Note, state and stateDiff can't be specified at the same time. If state is
Expand Down
5 changes: 5 additions & 0 deletions tests/integration_tests/hardhat/contracts/Greeter.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pragma solidity >0.5.0;

contract Greeter {
uint public n;
string public greeting;

event ChangeGreeting(address from, string value);
Expand All @@ -17,4 +18,8 @@ contract Greeter {
function greet() public view returns (string memory) {
return greeting;
}

function intValue() public view returns (uint) {
return n;
}
}
34 changes: 34 additions & 0 deletions tests/integration_tests/test_call.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import json
from web3 import Web3

Check failure on line 2 in tests/integration_tests/test_call.py

View workflow job for this annotation

GitHub Actions / Run flake8 on python integration tests

./tests/integration_tests/test_call.py:2:1: I003 isort expected 1 blank line in imports, found 0
from web3._utils.contracts import encode_transaction_data
from hexbytes import HexBytes

Check failure on line 4 in tests/integration_tests/test_call.py

View workflow job for this annotation

GitHub Actions / Run flake8 on python integration tests

./tests/integration_tests/test_call.py:4:1: I001 isort found an import in the wrong position

from .utils import CONTRACTS


def test_state_override(cronos):
state = 100
w3: Web3 = cronos.w3
mmsqe marked this conversation as resolved.
Show resolved Hide resolved
info = json.loads(CONTRACTS["Greeter"].read_text())
data = encode_transaction_data(w3, "intValue", info["abi"])
# call an arbitrary address
address = w3.toChecksumAddress("0x0000000000000000000000000000ffffffffffff")
overrides = {
address: {
"code": info["deployedBytecode"],
"state": {
("0x" + "0" * 64): HexBytes(
w3.codec.encode(("uint256",), (state,))
).hex(),
},
},
}
result = w3.eth.call(
{
"to": address,
"data": data,
},
"latest",
overrides,
)
assert (state,) == w3.codec.decode(("uint256",), result)
16 changes: 12 additions & 4 deletions x/evm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
ethparams "github.com/ethereum/go-ethereum/params"

rpctypes "github.com/evmos/ethermint/rpc/types"
ethermint "github.com/evmos/ethermint/types"
"github.com/evmos/ethermint/x/evm/statedb"
"github.com/evmos/ethermint/x/evm/types"
Expand Down Expand Up @@ -231,6 +232,13 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.Ms
return nil, status.Error(codes.InvalidArgument, "empty request")
}

var overrides rpctypes.StateOverride
if len(req.Overrides) > 0 {
if err := json.Unmarshal(req.Overrides, &overrides); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
}

ctx := sdk.UnwrapSDKContext(c)

var args types.TransactionArgs
Expand Down Expand Up @@ -259,7 +267,7 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.Ms
txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))

// pass false to not commit StateDB
res, err := k.ApplyMessageWithConfig(ctx, msg, nil, false, cfg, txConfig)
res, err := k.ApplyMessageWithConfig(ctx, msg, nil, false, cfg, txConfig, &overrides)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
Expand Down Expand Up @@ -354,7 +362,7 @@ func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*type
)

// pass false to not commit StateDB
rsp, err = k.ApplyMessageWithConfig(ctx, msg, nil, false, cfg, txConfig)
rsp, err = k.ApplyMessageWithConfig(ctx, msg, nil, false, cfg, txConfig, nil)
if err != nil {
if errors.Is(err, core.ErrIntrinsicGas) {
return true, nil, nil // Special case, raise gas limit
Expand Down Expand Up @@ -436,7 +444,7 @@ func (k Keeper) TraceTx(c context.Context, req *types.QueryTraceTxRequest) (*typ
}
txConfig.TxHash = ethTx.Hash()
txConfig.TxIndex = uint(i)
rsp, err := k.ApplyMessageWithConfig(ctx, msg, types.NewNoOpTracer(), true, cfg, txConfig)
rsp, err := k.ApplyMessageWithConfig(ctx, msg, types.NewNoOpTracer(), true, cfg, txConfig, nil)
if err != nil {
continue
}
Expand Down Expand Up @@ -606,7 +614,7 @@ func (k *Keeper) traceTx(
}
}()

res, err := k.ApplyMessageWithConfig(ctx, msg, tracer, commitMessage, cfg, txConfig)
res, err := k.ApplyMessageWithConfig(ctx, msg, tracer, commitMessage, cfg, txConfig, nil)
if err != nil {
return nil, 0, status.Error(codes.Internal, err.Error())
}
Expand Down
12 changes: 10 additions & 2 deletions x/evm/keeper/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

rpctypes "github.com/evmos/ethermint/rpc/types"
ethermint "github.com/evmos/ethermint/types"
"github.com/evmos/ethermint/x/evm/statedb"
"github.com/evmos/ethermint/x/evm/types"
Expand Down Expand Up @@ -194,7 +195,7 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, msgEth *types.MsgEthereumTx)
}

// pass true to commit the StateDB
res, err := k.ApplyMessageWithConfig(tmpCtx, msg, nil, true, cfg, txConfig)
res, err := k.ApplyMessageWithConfig(tmpCtx, msg, nil, true, cfg, txConfig, nil)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to apply ethereum core message")
}
Expand Down Expand Up @@ -286,7 +287,7 @@ func (k *Keeper) ApplyMessage(ctx sdk.Context, msg core.Message, tracer vm.EVMLo
}

txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))
return k.ApplyMessageWithConfig(ctx, msg, tracer, commit, cfg, txConfig)
return k.ApplyMessageWithConfig(ctx, msg, tracer, commit, cfg, txConfig, nil)
}

// ApplyMessageWithConfig computes the new state by applying the given message against the existing state.
Expand Down Expand Up @@ -333,6 +334,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context,
commit bool,
cfg *statedb.EVMConfig,
txConfig statedb.TxConfig,
overrides *rpctypes.StateOverride,
) (*types.MsgEthereumTxResponse, error) {
var (
ret []byte // return bytes from evm execution
Expand All @@ -347,6 +349,12 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context,
}

stateDB := statedb.NewWithParams(ctx, k, txConfig, cfg.Params)
if overrides != nil {
if err := overrides.Apply(stateDB); err != nil {
return nil, errorsmod.Wrap(err, "failed to apply state override")
}
}

evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB, k.customContracts)
leftoverGas := msg.Gas()
// Allow the tracer captures the tx level events, mainly the gas consumption.
Expand Down
2 changes: 1 addition & 1 deletion x/evm/keeper/state_transition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
txConfig = suite.app.EvmKeeper.TxConfig(suite.ctx, common.Hash{})

tc.malleate()
res, err := suite.app.EvmKeeper.ApplyMessageWithConfig(suite.ctx, msg, nil, true, config, txConfig)
res, err := suite.app.EvmKeeper.ApplyMessageWithConfig(suite.ctx, msg, nil, true, config, txConfig, nil)

if tc.expErr {
suite.Require().Error(err)
Expand Down
1 change: 1 addition & 0 deletions x/evm/statedb/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Keeper interface {

AddBalance(ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) error
SubBalance(ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) error
SetBalance(ctx sdk.Context, addr common.Address, amount *big.Int) error
GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) *big.Int

// Read methods
Expand Down
20 changes: 18 additions & 2 deletions x/evm/statedb/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,14 @@
}
}

func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) {
if err := s.ExecuteNativeAction(common.Address{}, nil, func(ctx sdk.Context) error {
mmsqe marked this conversation as resolved.
Show resolved Hide resolved
return s.keeper.SetBalance(ctx, addr, amount)
}); err != nil {
s.err = err
}
}

// SetNonce sets the nonce of account.
func (s *StateDB) SetNonce(addr common.Address, nonce uint64) {
stateObject := s.getOrNewStateObject(addr)
Expand All @@ -404,9 +412,17 @@
// SetState sets the contract state.
func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
stateObject := s.getOrNewStateObject(addr)
if stateObject != nil {
stateObject.SetState(key, value)
stateObject.SetState(key, value)
}

// SetStorage replaces the entire storage for the specified account with given
// storage. This function should only be used for debugging and the mutations
// must be discarded afterwards.
func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) {
stateObject := s.getOrNewStateObject(addr)
for k, v := range storage {
stateObject.SetState(k, v)
}

Check failure

Code scanning / gosec

the value in the range statement should be _ unless copying a map: want: for key := range m Error

the value in the range statement should be _ unless copying a map: want: for key := range m
}

// Suicide marks the given account as suicided.
Expand Down
Loading
Loading