diff --git a/x/evm/keeper/config.go b/x/evm/keeper/config.go new file mode 100644 index 0000000000..c7cb774fe2 --- /dev/null +++ b/x/evm/keeper/config.go @@ -0,0 +1,165 @@ +package keeper + +import ( + "math/big" + + tmtypes "github.com/tendermint/tendermint/types" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/vm" + + ethermint "github.com/evmos/ethermint/types" + "github.com/evmos/ethermint/x/evm/statedb" + "github.com/evmos/ethermint/x/evm/types" + evm "github.com/evmos/ethermint/x/evm/vm" +) + +// EVMConfig creates the EVMConfig based on current state +func (k *Keeper) EVMConfig(ctx sdk.Context, proposerAddress sdk.ConsAddress, chainID *big.Int) (*types.EVMConfig, error) { + chainID, err := GetChainID(ctx, chainID) + if err != nil { + return nil, err + } + + params := k.GetParams(ctx) + ethCfg := params.ChainConfig.EthereumConfig(chainID) + + // get the coinbase address from the block proposer + coinbase, err := k.GetCoinbaseAddress(ctx, proposerAddress) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to obtain coinbase address") + } + + baseFee := k.GetBaseFee(ctx, ethCfg) + return &types.EVMConfig{ + Params: params, + ChainConfig: ethCfg, + CoinBase: coinbase, + BaseFee: baseFee, + }, nil +} + +// TxConfig loads `TxConfig` from current transient storage +func (k *Keeper) TxConfig(ctx sdk.Context, txHash common.Hash) statedb.TxConfig { + return statedb.NewTxConfig( + common.BytesToHash(ctx.HeaderHash()), // BlockHash + txHash, // TxHash + uint(k.GetTxIndexTransient(ctx)), // TxIndex + uint(k.GetLogSizeTransient(ctx)), // LogIndex + ) +} + +// NewEVM generates a go-ethereum VM from the provided Message fields and the chain parameters +// (ChainConfig and module Params). It additionally sets the validator operator address as the +// coinbase address to make it available for the COINBASE opcode, even though there is no +// beneficiary of the coinbase transaction (since we're not mining). +func (k *Keeper) NewEVM( + ctx sdk.Context, + msg core.Message, + cfg *types.EVMConfig, + tracer vm.EVMLogger, + stateDB vm.StateDB, +) evm.EVM { + blockCtx := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + GetHash: k.GetHashFn(ctx), + Coinbase: cfg.CoinBase, + GasLimit: ethermint.BlockGasLimit(ctx), + BlockNumber: big.NewInt(ctx.BlockHeight()), + Time: big.NewInt(ctx.BlockHeader().Time.Unix()), + Difficulty: big.NewInt(0), // unused. Only required in PoW context + BaseFee: cfg.BaseFee, + Random: nil, // TODO: set randomness + } + + txCtx := core.NewEVMTxContext(msg) + if tracer == nil { + tracer = k.Tracer(ctx, msg, cfg.ChainConfig) + } + vmConfig := k.VMConfig(ctx, msg, cfg, tracer) + return k.evmConstructor(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig, k.customPrecompiles) +} + +// VMConfig creates an EVM configuration from the debug setting and the extra EIPs enabled on the +// module parameters. The config generated uses the default JumpTable from the EVM. +func (k Keeper) VMConfig(ctx sdk.Context, msg core.Message, cfg *types.EVMConfig, tracer vm.EVMLogger) vm.Config { + noBaseFee := true + if types.IsLondon(cfg.ChainConfig, ctx.BlockHeight()) { + noBaseFee = k.feeMarketKeeper.GetParams(ctx).NoBaseFee + } + + var debug bool + if _, ok := tracer.(types.NoOpTracer); !ok { + debug = true + } + + return vm.Config{ + Debug: debug, + Tracer: tracer, + NoBaseFee: noBaseFee, + EnablePreimageRecording: false, + JumpTable: nil, // Set to the default by the EVM interpreter + ExtraEips: cfg.Params.EIPs(), + } +} + +// GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases: +// 1. The requested height matches the current height from context (and thus same epoch number) +// 2. The requested height is from an previous height from the same chain epoch +// 3. The requested height is from a height greater than the latest one +func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc { + return func(height uint64) common.Hash { + h, err := ethermint.SafeInt64(height) + if err != nil { + k.Logger(ctx).Error("failed to cast height to int64", "error", err) + return common.Hash{} + } + + switch { + case ctx.BlockHeight() == h: + // Case 1: The requested height matches the one from the context so we can retrieve the header + // hash directly from the context. + // Note: The headerHash is only set at begin block, it will be nil in case of a query context + headerHash := ctx.HeaderHash() + if len(headerHash) != 0 { + return common.BytesToHash(headerHash) + } + + // only recompute the hash if not set (eg: checkTxState) + contextBlockHeader := ctx.BlockHeader() + header, err := tmtypes.HeaderFromProto(&contextBlockHeader) + if err != nil { + k.Logger(ctx).Error("failed to cast tendermint header from proto", "error", err) + return common.Hash{} + } + + headerHash = header.Hash() + return common.BytesToHash(headerHash) + + case ctx.BlockHeight() > h: + // Case 2: if the chain is not the current height we need to retrieve the hash from the store for the + // current chain epoch. This only applies if the current height is greater than the requested height. + histInfo, found := k.stakingKeeper.GetHistoricalInfo(ctx, h) + if !found { + k.Logger(ctx).Debug("historical info not found", "height", h) + return common.Hash{} + } + + header, err := tmtypes.HeaderFromProto(&histInfo.Header) + if err != nil { + k.Logger(ctx).Error("failed to cast tendermint header from proto", "error", err) + return common.Hash{} + } + + return common.BytesToHash(header.Hash()) + default: + // Case 3: heights greater than the current one returns an empty hash. + return common.Hash{} + } + } +} diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index dec3d06031..ac6fc38789 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -222,10 +222,12 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.Ms if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } - chainID, err := getChainID(ctx, req.ChainId) + + chainID, err := GetChainID(ctx, big.NewInt(req.ChainId)) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } + cfg, err := k.EVMConfig(ctx, GetProposerAddress(ctx, req.ProposerAddress), chainID) if err != nil { return nil, status.Error(codes.Internal, err.Error()) @@ -258,7 +260,7 @@ func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*type } ctx := sdk.UnwrapSDKContext(c) - chainID, err := getChainID(ctx, req.ChainId) + chainID, err := GetChainID(ctx, big.NewInt(req.ChainId)) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } @@ -268,6 +270,7 @@ func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*type } var args types.TransactionArgs + err = json.Unmarshal(req.Args, &args) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) @@ -394,18 +397,21 @@ func (k Keeper) TraceTx(c context.Context, req *types.QueryTraceTxRequest) (*typ contextHeight = 1 } - ctx := sdk.UnwrapSDKContext(c) - ctx = ctx.WithBlockHeight(contextHeight) - ctx = ctx.WithBlockTime(req.BlockTime) - ctx = ctx.WithHeaderHash(common.Hex2Bytes(req.BlockHash)) - chainID, err := getChainID(ctx, req.ChainId) + ctx := sdk.UnwrapSDKContext(c). + WithBlockHeight(contextHeight). + WithBlockTime(req.BlockTime). + WithHeaderHash(common.Hex2Bytes(req.BlockHash)) + + chainID, err := GetChainID(ctx, big.NewInt(req.ChainId)) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } + cfg, err := k.EVMConfig(ctx, GetProposerAddress(ctx, req.ProposerAddress), chainID) if err != nil { return nil, status.Errorf(codes.Internal, "failed to load evm config: %s", err.Error()) } + signer := ethtypes.MakeSigner(cfg.ChainConfig, big.NewInt(ctx.BlockHeight())) txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes())) @@ -417,10 +423,12 @@ 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) if err != nil { continue } + txConfig.LogIndex += uint(len(rsp.Logs)) } @@ -471,11 +479,12 @@ func (k Keeper) TraceBlock(c context.Context, req *types.QueryTraceBlockRequest) contextHeight = 1 } - ctx := sdk.UnwrapSDKContext(c) - ctx = ctx.WithBlockHeight(contextHeight) - ctx = ctx.WithBlockTime(req.BlockTime) - ctx = ctx.WithHeaderHash(common.Hex2Bytes(req.BlockHash)) - chainID, err := getChainID(ctx, req.ChainId) + ctx := sdk.UnwrapSDKContext(c). + WithBlockHeight(contextHeight). + WithBlockTime(req.BlockTime). + WithHeaderHash(common.Hex2Bytes(req.BlockHash)) + + chainID, err := GetChainID(ctx, big.NewInt(req.ChainId)) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } @@ -489,6 +498,7 @@ func (k Keeper) TraceBlock(c context.Context, req *types.QueryTraceBlockRequest) results := make([]*types.TxTraceResult, 0, txsLength) txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes())) + for i, tx := range req.Txs { result := types.TxTraceResult{} ethTx := tx.AsTransaction() @@ -532,6 +542,7 @@ func (k *Keeper) traceTx( err error timeout = defaultTraceTimeout ) + msg, err := tx.AsMessage(signer, cfg.BaseFee) if err != nil { return nil, 0, status.Error(codes.Internal, err.Error()) @@ -617,11 +628,3 @@ func (k Keeper) BaseFee(c context.Context, _ *types.QueryBaseFeeRequest) (*types return res, nil } - -// getChainID parse chainID from current context if not provided -func getChainID(ctx sdk.Context, chainID int64) (*big.Int, error) { - if chainID == 0 { - return ethermint.ParseChainID(ctx.ChainID()) - } - return big.NewInt(chainID), nil -} diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 3103a45cad..1a15680731 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -3,20 +3,15 @@ package keeper import ( "math/big" - sdkmath "cosmossdk.io/math" - - tmtypes "github.com/tendermint/tendermint/types" - errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - ethermint "github.com/evmos/ethermint/types" "github.com/evmos/ethermint/x/evm/statedb" "github.com/evmos/ethermint/x/evm/types" - evm "github.com/evmos/ethermint/x/evm/vm" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" @@ -38,144 +33,6 @@ func GasToRefund(availableRefund, gasConsumed, refundQuotient uint64) uint64 { return refund } -// EVMConfig creates the EVMConfig based on current state -func (k *Keeper) EVMConfig(ctx sdk.Context, proposerAddress sdk.ConsAddress, chainID *big.Int) (*types.EVMConfig, error) { - params := k.GetParams(ctx) - ethCfg := params.ChainConfig.EthereumConfig(chainID) - - // get the coinbase address from the block proposer - coinbase, err := k.GetCoinbaseAddress(ctx, proposerAddress) - if err != nil { - return nil, errorsmod.Wrap(err, "failed to obtain coinbase address") - } - - baseFee := k.GetBaseFee(ctx, ethCfg) - return &types.EVMConfig{ - Params: params, - ChainConfig: ethCfg, - CoinBase: coinbase, - BaseFee: baseFee, - }, nil -} - -// TxConfig loads `TxConfig` from current transient storage -func (k *Keeper) TxConfig(ctx sdk.Context, txHash common.Hash) statedb.TxConfig { - return statedb.NewTxConfig( - common.BytesToHash(ctx.HeaderHash()), // BlockHash - txHash, // TxHash - uint(k.GetTxIndexTransient(ctx)), // TxIndex - uint(k.GetLogSizeTransient(ctx)), // LogIndex - ) -} - -// NewEVM generates a go-ethereum VM from the provided Message fields and the chain parameters -// (ChainConfig and module Params). It additionally sets the validator operator address as the -// coinbase address to make it available for the COINBASE opcode, even though there is no -// beneficiary of the coinbase transaction (since we're not mining). -func (k *Keeper) NewEVM( - ctx sdk.Context, - msg core.Message, - cfg *types.EVMConfig, - tracer vm.EVMLogger, - stateDB vm.StateDB, -) evm.EVM { - blockCtx := vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - GetHash: k.GetHashFn(ctx), - Coinbase: cfg.CoinBase, - GasLimit: ethermint.BlockGasLimit(ctx), - BlockNumber: big.NewInt(ctx.BlockHeight()), - Time: big.NewInt(ctx.BlockHeader().Time.Unix()), - Difficulty: big.NewInt(0), // unused. Only required in PoW context - BaseFee: cfg.BaseFee, - } - - txCtx := core.NewEVMTxContext(msg) - if tracer == nil { - tracer = k.Tracer(ctx, msg, cfg.ChainConfig) - } - vmConfig := k.VMConfig(ctx, msg, cfg, tracer) - return k.evmConstructor(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig, k.customPrecompiles) -} - -// VMConfig creates an EVM configuration from the debug setting and the extra EIPs enabled on the -// module parameters. The config generated uses the default JumpTable from the EVM. -func (k Keeper) VMConfig(ctx sdk.Context, msg core.Message, cfg *types.EVMConfig, tracer vm.EVMLogger) vm.Config { - noBaseFee := true - if types.IsLondon(cfg.ChainConfig, ctx.BlockHeight()) { - noBaseFee = k.feeMarketKeeper.GetParams(ctx).NoBaseFee - } - - var debug bool - if _, ok := tracer.(types.NoOpTracer); !ok { - debug = true - } - - return vm.Config{ - Debug: debug, - Tracer: tracer, - NoBaseFee: noBaseFee, - ExtraEips: cfg.Params.EIPs(), - } -} - -// GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases: -// 1. The requested height matches the current height from context (and thus same epoch number) -// 2. The requested height is from an previous height from the same chain epoch -// 3. The requested height is from a height greater than the latest one -func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc { - return func(height uint64) common.Hash { - h, err := ethermint.SafeInt64(height) - if err != nil { - k.Logger(ctx).Error("failed to cast height to int64", "error", err) - return common.Hash{} - } - - switch { - case ctx.BlockHeight() == h: - // Case 1: The requested height matches the one from the context so we can retrieve the header - // hash directly from the context. - // Note: The headerHash is only set at begin block, it will be nil in case of a query context - headerHash := ctx.HeaderHash() - if len(headerHash) != 0 { - return common.BytesToHash(headerHash) - } - - // only recompute the hash if not set (eg: checkTxState) - contextBlockHeader := ctx.BlockHeader() - header, err := tmtypes.HeaderFromProto(&contextBlockHeader) - if err != nil { - k.Logger(ctx).Error("failed to cast tendermint header from proto", "error", err) - return common.Hash{} - } - - headerHash = header.Hash() - return common.BytesToHash(headerHash) - - case ctx.BlockHeight() > h: - // Case 2: if the chain is not the current height we need to retrieve the hash from the store for the - // current chain epoch. This only applies if the current height is greater than the requested height. - histInfo, found := k.stakingKeeper.GetHistoricalInfo(ctx, h) - if !found { - k.Logger(ctx).Debug("historical info not found", "height", h) - return common.Hash{} - } - - header, err := tmtypes.HeaderFromProto(&histInfo.Header) - if err != nil { - k.Logger(ctx).Error("failed to cast tendermint header from proto", "error", err) - return common.Hash{} - } - - return common.BytesToHash(header.Hash()) - default: - // Case 3: heights greater than the current one returns an empty hash. - return common.Hash{} - } - } -} - // ApplyTransaction runs and attempts to perform a state transition with the given transaction (i.e Message), that will // only be persisted (committed) to the underlying KVStore if the transaction does not fail. // diff --git a/x/evm/keeper/utils.go b/x/evm/keeper/utils.go index beffb5e883..5f04b4fe92 100644 --- a/x/evm/keeper/utils.go +++ b/x/evm/keeper/utils.go @@ -9,8 +9,10 @@ import ( errortypes "github.com/cosmos/cosmos-sdk/types/errors" authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + ethermint "github.com/evmos/ethermint/types" evmtypes "github.com/evmos/ethermint/x/evm/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" ) @@ -106,3 +108,11 @@ func CheckSenderBalance( } return nil } + +// GetChainID parse chainID from current context if not provided +func GetChainID(ctx sdk.Context, chainID *big.Int) (*big.Int, error) { + if chainID == nil || chainID.Cmp(common.Big0) == 0 { + return ethermint.ParseChainID(ctx.ChainID()) + } + return chainID, nil +}