github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/execution/evm/evm.go (about)

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package evm
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"math"
    12  	"math/big"
    13  	"time"
    14  
    15  	"github.com/ethereum/go-ethereum/common"
    16  	"github.com/ethereum/go-ethereum/core/types"
    17  	"github.com/ethereum/go-ethereum/core/vm"
    18  	"github.com/ethereum/go-ethereum/crypto"
    19  	"github.com/ethereum/go-ethereum/params"
    20  	"github.com/pkg/errors"
    21  	"go.uber.org/zap"
    22  	"google.golang.org/protobuf/proto"
    23  
    24  	"github.com/iotexproject/go-pkgs/hash"
    25  	"github.com/iotexproject/iotex-address/address"
    26  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    27  
    28  	"github.com/iotexproject/iotex-core/action"
    29  	"github.com/iotexproject/iotex-core/action/protocol"
    30  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    31  	"github.com/iotexproject/iotex-core/pkg/log"
    32  	"github.com/iotexproject/iotex-core/pkg/tracer"
    33  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    34  )
    35  
    36  var (
    37  	// TODO: whenever ActionGasLimit is removed from genesis, we need to hard code it to 5M to make it compatible with
    38  	// the mainnet.
    39  	_preAleutianActionGasLimit = genesis.Default.ActionGasLimit
    40  
    41  	_inContractTransfer = hash.BytesToHash256([]byte{byte(iotextypes.TransactionLogType_IN_CONTRACT_TRANSFER)})
    42  
    43  	// _revertSelector is a special function selector for revert reason unpacking.
    44  	_revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
    45  
    46  	// ErrInconsistentNonce is the error that the nonce is different from executor's nonce
    47  	ErrInconsistentNonce = errors.New("Nonce is not identical to executor nonce")
    48  )
    49  
    50  type (
    51  	// GetBlockHash gets block hash by height
    52  	GetBlockHash func(uint64) (hash.Hash256, error)
    53  
    54  	// GetBlockTime gets block time by height
    55  	GetBlockTime func(uint64) (time.Time, error)
    56  
    57  	// DepositGasWithSGD deposits gas with Sharing of Gas-fee with DApps
    58  	DepositGasWithSGD func(context.Context, protocol.StateManager, address.Address, *big.Int, *big.Int) (*action.TransactionLog, error)
    59  
    60  	// SGDRegistry is the interface for handling Sharing of Gas-fee with DApps
    61  	SGDRegistry interface {
    62  		CheckContract(context.Context, string, uint64) (address.Address, uint64, bool, error)
    63  	}
    64  )
    65  
    66  // CanTransfer checks whether the from account has enough balance
    67  func CanTransfer(db vm.StateDB, fromHash common.Address, balance *big.Int) bool {
    68  	return db.GetBalance(fromHash).Cmp(balance) >= 0
    69  }
    70  
    71  // MakeTransfer transfers account
    72  func MakeTransfer(db vm.StateDB, fromHash, toHash common.Address, amount *big.Int) {
    73  	db.SubBalance(fromHash, amount)
    74  	db.AddBalance(toHash, amount)
    75  
    76  	db.AddLog(&types.Log{
    77  		Topics: []common.Hash{
    78  			common.BytesToHash(_inContractTransfer[:]),
    79  			common.BytesToHash(fromHash[:]),
    80  			common.BytesToHash(toHash[:]),
    81  		},
    82  		Data: amount.Bytes(),
    83  	})
    84  }
    85  
    86  type (
    87  	// Params is the context and parameters
    88  	Params struct {
    89  		context     vm.BlockContext
    90  		txCtx       vm.TxContext
    91  		nonce       uint64
    92  		amount      *big.Int
    93  		contract    *common.Address
    94  		gas         uint64
    95  		data        []byte
    96  		accessList  types.AccessList
    97  		evmConfig   vm.Config
    98  		chainConfig *params.ChainConfig
    99  		genesis     genesis.Blockchain
   100  		blkCtx      protocol.BlockCtx
   101  		featureCtx  protocol.FeatureCtx
   102  		actionCtx   protocol.ActionCtx
   103  		helperCtx   HelperContext
   104  	}
   105  )
   106  
   107  // newParams creates a new context for use in the EVM.
   108  func newParams(
   109  	ctx context.Context,
   110  	execution *action.Execution,
   111  	stateDB *StateDBAdapter,
   112  ) (*Params, error) {
   113  	var (
   114  		actionCtx    = protocol.MustGetActionCtx(ctx)
   115  		blkCtx       = protocol.MustGetBlockCtx(ctx)
   116  		featureCtx   = protocol.MustGetFeatureCtx(ctx)
   117  		g            = genesis.MustExtractGenesisContext(ctx)
   118  		helperCtx    = mustGetHelperCtx(ctx)
   119  		evmNetworkID = protocol.MustGetBlockchainCtx(ctx).EvmNetworkID
   120  		executorAddr = common.BytesToAddress(actionCtx.Caller.Bytes())
   121  		getBlockHash = helperCtx.GetBlockHash
   122  
   123  		vmConfig            vm.Config
   124  		contractAddrPointer *common.Address
   125  		getHashFn           vm.GetHashFunc
   126  	)
   127  
   128  	if dest := execution.Contract(); dest != action.EmptyAddress {
   129  		contract, err := address.FromString(execution.Contract())
   130  		if err != nil {
   131  			return nil, errors.Wrapf(err, "failed to decode contract address %s", dest)
   132  		}
   133  		contractAddr := common.BytesToAddress(contract.Bytes())
   134  		contractAddrPointer = &contractAddr
   135  	}
   136  
   137  	gasLimit := execution.GasLimit()
   138  	// Reset gas limit to the system wide action gas limit cap if it's greater than it
   139  	if blkCtx.BlockHeight > 0 && featureCtx.SystemWideActionGasLimit && gasLimit > _preAleutianActionGasLimit {
   140  		gasLimit = _preAleutianActionGasLimit
   141  	}
   142  
   143  	switch {
   144  	case featureCtx.CorrectGetHashFn:
   145  		getHashFn = func(n uint64) common.Hash {
   146  			hash, err := getBlockHash(n)
   147  			if err == nil {
   148  				return common.BytesToHash(hash[:])
   149  			}
   150  			return common.Hash{}
   151  		}
   152  	case featureCtx.FixGetHashFnHeight:
   153  		getHashFn = func(n uint64) common.Hash {
   154  			hash, err := getBlockHash(stateDB.blockHeight - (n + 1))
   155  			if err == nil {
   156  				return common.BytesToHash(hash[:])
   157  			}
   158  			return common.Hash{}
   159  		}
   160  	default:
   161  		getHashFn = func(n uint64) common.Hash {
   162  			hash, err := getBlockHash(stateDB.blockHeight - n)
   163  			if err != nil {
   164  				// initial implementation did wrong, should return common.Hash{} in case of error
   165  				return common.BytesToHash(hash[:])
   166  			}
   167  			return common.Hash{}
   168  		}
   169  	}
   170  
   171  	context := vm.BlockContext{
   172  		CanTransfer: CanTransfer,
   173  		Transfer:    MakeTransfer,
   174  		GetHash:     getHashFn,
   175  		Coinbase:    common.BytesToAddress(blkCtx.Producer.Bytes()),
   176  		GasLimit:    gasLimit,
   177  		BlockNumber: new(big.Int).SetUint64(blkCtx.BlockHeight),
   178  		Time:        new(big.Int).SetInt64(blkCtx.BlockTimeStamp.Unix()).Uint64(),
   179  		Difficulty:  new(big.Int).SetUint64(uint64(50)),
   180  		BaseFee:     new(big.Int),
   181  	}
   182  	if g.IsSumatra(blkCtx.BlockHeight) {
   183  		// Random opcode (EIP-4399) is not supported
   184  		context.Random = &common.Hash{}
   185  	}
   186  
   187  	if vmCfg, ok := protocol.GetVMConfigCtx(ctx); ok {
   188  		vmConfig = vmCfg
   189  	}
   190  	chainConfig, err := getChainConfig(g.Blockchain, blkCtx.BlockHeight, evmNetworkID, helperCtx.GetBlockTime)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	return &Params{
   196  		context,
   197  		vm.TxContext{
   198  			Origin:   executorAddr,
   199  			GasPrice: execution.GasPrice(),
   200  		},
   201  		execution.Nonce(),
   202  		execution.Amount(),
   203  		contractAddrPointer,
   204  		gasLimit,
   205  		execution.Data(),
   206  		execution.AccessList(),
   207  		vmConfig,
   208  		chainConfig,
   209  		g.Blockchain,
   210  		blkCtx,
   211  		featureCtx,
   212  		actionCtx,
   213  		helperCtx,
   214  	}, nil
   215  }
   216  
   217  func securityDeposit(ps *Params, stateDB vm.StateDB, gasLimit uint64) error {
   218  	executorNonce := stateDB.GetNonce(ps.txCtx.Origin)
   219  	if executorNonce > ps.nonce {
   220  		log.S().Errorf("Nonce on %v: %d vs %d", ps.txCtx.Origin, executorNonce, ps.nonce)
   221  		// TODO ignore inconsistent nonce problem until the actions are executed sequentially
   222  		// return ErrInconsistentNonce
   223  	}
   224  	if gasLimit < ps.gas {
   225  		return action.ErrGasLimit
   226  	}
   227  	gasConsumed := new(big.Int).Mul(new(big.Int).SetUint64(ps.gas), ps.txCtx.GasPrice)
   228  	if stateDB.GetBalance(ps.txCtx.Origin).Cmp(gasConsumed) < 0 {
   229  		return action.ErrInsufficientFunds
   230  	}
   231  	stateDB.SubBalance(ps.txCtx.Origin, gasConsumed)
   232  	return nil
   233  }
   234  
   235  // ExecuteContract processes a transfer which contains a contract
   236  func ExecuteContract(
   237  	ctx context.Context,
   238  	sm protocol.StateManager,
   239  	execution *action.Execution,
   240  ) ([]byte, *action.Receipt, error) {
   241  	ctx, span := tracer.NewSpan(ctx, "evm.ExecuteContract")
   242  	defer span.End()
   243  
   244  	stateDB, err := prepareStateDB(ctx, sm)
   245  	if err != nil {
   246  		return nil, nil, err
   247  	}
   248  	ps, err := newParams(ctx, execution, stateDB)
   249  	if err != nil {
   250  		return nil, nil, err
   251  	}
   252  	sgd := ps.helperCtx.Sgd
   253  	retval, depositGas, remainingGas, contractAddress, statusCode, err := executeInEVM(ps, stateDB)
   254  	if err != nil {
   255  		return nil, nil, err
   256  	}
   257  	receipt := &action.Receipt{
   258  		GasConsumed:     ps.gas - remainingGas,
   259  		BlockHeight:     ps.blkCtx.BlockHeight,
   260  		ActionHash:      ps.actionCtx.ActionHash,
   261  		ContractAddress: contractAddress,
   262  	}
   263  
   264  	receipt.Status = uint64(statusCode)
   265  	var (
   266  		depositLog, burnLog *action.TransactionLog
   267  		consumedGas         = depositGas - remainingGas
   268  	)
   269  	if ps.featureCtx.FixDoubleChargeGas {
   270  		// Refund all deposit and, actual gas fee will be subtracted when depositing gas fee to the rewarding protocol
   271  		stateDB.AddBalance(ps.txCtx.Origin, big.NewInt(0).Mul(big.NewInt(0).SetUint64(depositGas), ps.txCtx.GasPrice))
   272  	} else {
   273  		if remainingGas > 0 {
   274  			remainingValue := new(big.Int).Mul(new(big.Int).SetUint64(remainingGas), ps.txCtx.GasPrice)
   275  			stateDB.AddBalance(ps.txCtx.Origin, remainingValue)
   276  		}
   277  		if consumedGas > 0 {
   278  			burnLog = &action.TransactionLog{
   279  				Type:      iotextypes.TransactionLogType_GAS_FEE,
   280  				Sender:    ps.actionCtx.Caller.String(),
   281  				Recipient: "", // burned
   282  				Amount:    new(big.Int).Mul(new(big.Int).SetUint64(consumedGas), ps.txCtx.GasPrice),
   283  			}
   284  		}
   285  	}
   286  	if consumedGas > 0 {
   287  		var (
   288  			receiver                  address.Address
   289  			sharedGas                 uint64
   290  			sharedGasFee, totalGasFee *big.Int
   291  		)
   292  		if ps.featureCtx.SharedGasWithDapp && sgd != nil {
   293  			// TODO: sgd is whether nil should be checked in processSGD
   294  			receiver, sharedGas, err = processSGD(ctx, sm, execution, consumedGas, sgd)
   295  			if err != nil {
   296  				return nil, nil, errors.Wrap(err, "failed to process Sharing of Gas-fee with DApps")
   297  			}
   298  		}
   299  		if sharedGas > 0 {
   300  			sharedGasFee = big.NewInt(int64(sharedGas))
   301  			sharedGasFee.Mul(sharedGasFee, ps.txCtx.GasPrice)
   302  		}
   303  		totalGasFee = new(big.Int).Mul(new(big.Int).SetUint64(consumedGas), ps.txCtx.GasPrice)
   304  		if ps.helperCtx.DepositGasFunc != nil {
   305  			depositLog, err = ps.helperCtx.DepositGasFunc(ctx, sm, receiver, totalGasFee, sharedGasFee)
   306  			if err != nil {
   307  				return nil, nil, err
   308  			}
   309  		}
   310  
   311  	}
   312  
   313  	if err := stateDB.CommitContracts(); err != nil {
   314  		return nil, nil, errors.Wrap(err, "failed to commit contracts to underlying db")
   315  	}
   316  	receipt.AddLogs(stateDB.Logs()...).AddTransactionLogs(depositLog, burnLog)
   317  	if receipt.Status == uint64(iotextypes.ReceiptStatus_Success) ||
   318  		ps.featureCtx.AddOutOfGasToTransactionLog && receipt.Status == uint64(iotextypes.ReceiptStatus_ErrCodeStoreOutOfGas) {
   319  		receipt.AddTransactionLogs(stateDB.TransactionLogs()...)
   320  	}
   321  	stateDB.clear()
   322  
   323  	if ps.featureCtx.SetRevertMessageToReceipt && receipt.Status == uint64(iotextypes.ReceiptStatus_ErrExecutionReverted) && retval != nil && bytes.Equal(retval[:4], _revertSelector) {
   324  		// in case of the execution revert error, parse the retVal and add to receipt
   325  		data := retval[4:]
   326  		msgLength := byteutil.BytesToUint64BigEndian(data[56:64])
   327  		revertMsg := string(data[64 : 64+msgLength])
   328  		receipt.SetExecutionRevertMsg(revertMsg)
   329  	}
   330  	log.S().Debugf("Receipt: %+v, %v", receipt, err)
   331  	return retval, receipt, nil
   332  }
   333  
   334  func processSGD(ctx context.Context, sm protocol.StateManager, execution *action.Execution, consumedGas uint64, sgd SGDRegistry,
   335  ) (address.Address, uint64, error) {
   336  	if execution.Contract() == action.EmptyAddress {
   337  		return nil, 0, nil
   338  	}
   339  	height, err := sm.Height()
   340  	if err != nil {
   341  		return nil, 0, err
   342  	}
   343  	receiver, percentage, ok, err := sgd.CheckContract(ctx, execution.Contract(), height-1)
   344  	if err != nil || !ok {
   345  		return nil, 0, err
   346  	}
   347  
   348  	sharedGas := consumedGas * percentage / 100
   349  	if sharedGas > consumedGas {
   350  		sharedGas = consumedGas
   351  	}
   352  	return receiver, sharedGas, nil
   353  }
   354  
   355  // ReadContractStorage reads contract's storage
   356  func ReadContractStorage(
   357  	ctx context.Context,
   358  	sm protocol.StateManager,
   359  	contract address.Address,
   360  	key []byte,
   361  ) ([]byte, error) {
   362  	bcCtx := protocol.MustGetBlockchainCtx(ctx)
   363  	ctx = protocol.WithFeatureCtx(protocol.WithBlockCtx(protocol.WithActionCtx(ctx,
   364  		protocol.ActionCtx{
   365  			ActionHash: hash.ZeroHash256,
   366  		}),
   367  		protocol.BlockCtx{
   368  			BlockHeight: bcCtx.Tip.Height + 1,
   369  		},
   370  	))
   371  	stateDB, err := prepareStateDB(ctx, sm)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  	res := stateDB.GetState(common.BytesToAddress(contract.Bytes()), common.BytesToHash(key))
   376  	return res[:], nil
   377  }
   378  
   379  func prepareStateDB(ctx context.Context, sm protocol.StateManager) (*StateDBAdapter, error) {
   380  	actionCtx := protocol.MustGetActionCtx(ctx)
   381  	blkCtx := protocol.MustGetBlockCtx(ctx)
   382  	featureCtx := protocol.MustGetFeatureCtx(ctx)
   383  	opts := []StateDBAdapterOption{}
   384  	if featureCtx.CreateLegacyNonceAccount {
   385  		opts = append(opts, LegacyNonceAccountOption())
   386  	}
   387  	if !featureCtx.FixSortCacheContractsAndUsePendingNonce {
   388  		opts = append(opts, DisableSortCachedContractsOption(), UseConfirmedNonceOption())
   389  	}
   390  	// Before featureCtx.RefactorFreshAccountConversion is activated,
   391  	// the type of a legacy fresh account is always 1
   392  	if featureCtx.RefactorFreshAccountConversion {
   393  		opts = append(opts, ZeroNonceForFreshAccountOption())
   394  	}
   395  	if featureCtx.NotFixTopicCopyBug {
   396  		opts = append(opts, NotFixTopicCopyBugOption())
   397  	}
   398  	if featureCtx.AsyncContractTrie {
   399  		opts = append(opts, AsyncContractTrieOption())
   400  	}
   401  	if featureCtx.FixSnapshotOrder {
   402  		opts = append(opts, FixSnapshotOrderOption())
   403  	}
   404  	if featureCtx.RevertLog {
   405  		opts = append(opts, RevertLogOption())
   406  	}
   407  	if !featureCtx.CorrectGasRefund {
   408  		opts = append(opts, ManualCorrectGasRefundOption())
   409  	}
   410  	if featureCtx.SuicideTxLogMismatchPanic {
   411  		opts = append(opts, SuicideTxLogMismatchPanicOption())
   412  	}
   413  
   414  	return NewStateDBAdapter(
   415  		sm,
   416  		blkCtx.BlockHeight,
   417  		actionCtx.ActionHash,
   418  		opts...,
   419  	)
   420  }
   421  
   422  func getChainConfig(g genesis.Blockchain, height uint64, id uint32, getBlockTime GetBlockTime) (*params.ChainConfig, error) {
   423  	var chainConfig params.ChainConfig
   424  	chainConfig.ConstantinopleBlock = new(big.Int).SetUint64(0) // Constantinople switch block (nil = no fork, 0 = already activated)
   425  	chainConfig.BeringBlock = new(big.Int).SetUint64(g.BeringBlockHeight)
   426  	// enable earlier Ethereum forks at Greenland
   427  	chainConfig.GreenlandBlock = new(big.Int).SetUint64(g.GreenlandBlockHeight)
   428  	// support chainid and enable Istanbul + MuirGlacier at Iceland
   429  	chainConfig.IstanbulBlock = new(big.Int).SetUint64(g.IcelandBlockHeight)
   430  	chainConfig.MuirGlacierBlock = new(big.Int).SetUint64(g.IcelandBlockHeight)
   431  	if g.IsIceland(height) {
   432  		chainConfig.ChainID = new(big.Int).SetUint64(uint64(id))
   433  	}
   434  	// enable Berlin and London at Okhotsk
   435  	chainConfig.BerlinBlock = new(big.Int).SetUint64(g.OkhotskBlockHeight)
   436  	chainConfig.LondonBlock = new(big.Int).SetUint64(g.OkhotskBlockHeight)
   437  	// enable ArrowGlacier, GrayGlacier at Redsea
   438  	chainConfig.ArrowGlacierBlock = new(big.Int).SetUint64(g.RedseaBlockHeight)
   439  	chainConfig.GrayGlacierBlock = new(big.Int).SetUint64(g.RedseaBlockHeight)
   440  	// enable Merge, Shanghai at Sumatra
   441  	chainConfig.MergeNetsplitBlock = new(big.Int).SetUint64(g.SumatraBlockHeight)
   442  	// Starting Shanghai, fork scheduling on Ethereum was switched from blocks to timestamps
   443  	sumatraTime, err := getBlockTime(g.SumatraBlockHeight)
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  	sumatraTimestamp := (uint64)(sumatraTime.Unix())
   448  	chainConfig.ShanghaiTime = &sumatraTimestamp
   449  	return &chainConfig, nil
   450  }
   451  
   452  // Error in executeInEVM is a consensus issue
   453  func executeInEVM(evmParams *Params, stateDB *StateDBAdapter) ([]byte, uint64, uint64, string, iotextypes.ReceiptStatus, error) {
   454  	var (
   455  		gasLimit     = evmParams.blkCtx.GasLimit
   456  		blockHeight  = evmParams.blkCtx.BlockHeight
   457  		g            = evmParams.genesis
   458  		remainingGas = evmParams.gas
   459  		chainConfig  = evmParams.chainConfig
   460  	)
   461  	if err := securityDeposit(evmParams, stateDB, gasLimit); err != nil {
   462  		log.L().Warn("unexpected error: not enough security deposit", zap.Error(err))
   463  		return nil, 0, 0, action.EmptyAddress, iotextypes.ReceiptStatus_Failure, err
   464  	}
   465  	var (
   466  		accessList types.AccessList
   467  	)
   468  	evm := vm.NewEVM(evmParams.context, evmParams.txCtx, stateDB, chainConfig, evmParams.evmConfig)
   469  	if g.IsOkhotsk(blockHeight) {
   470  		accessList = evmParams.accessList
   471  	}
   472  	intriGas, err := intrinsicGas(uint64(len(evmParams.data)), accessList)
   473  	if err != nil {
   474  		return nil, evmParams.gas, remainingGas, action.EmptyAddress, iotextypes.ReceiptStatus_Failure, err
   475  	}
   476  	if remainingGas < intriGas {
   477  		return nil, evmParams.gas, remainingGas, action.EmptyAddress, iotextypes.ReceiptStatus_Failure, action.ErrInsufficientFunds
   478  	}
   479  	remainingGas -= intriGas
   480  
   481  	// Set up the initial access list
   482  	rules := chainConfig.Rules(evm.Context.BlockNumber, g.IsSumatra(evmParams.blkCtx.BlockHeight), evmParams.context.Time)
   483  	if rules.IsBerlin {
   484  		stateDB.Prepare(rules, evmParams.txCtx.Origin, evmParams.context.Coinbase, evmParams.contract, vm.ActivePrecompiles(rules), evmParams.accessList)
   485  	}
   486  	var (
   487  		contractRawAddress = action.EmptyAddress
   488  		executor           = vm.AccountRef(evmParams.txCtx.Origin)
   489  		ret                []byte
   490  		evmErr             error
   491  		refund             uint64
   492  	)
   493  	if evmParams.contract == nil {
   494  		// create contract
   495  		var evmContractAddress common.Address
   496  		_, evmContractAddress, remainingGas, evmErr = evm.Create(executor, evmParams.data, remainingGas, evmParams.amount)
   497  		log.L().Debug("evm Create.", log.Hex("addrHash", evmContractAddress[:]))
   498  		if evmErr == nil {
   499  			if contractAddress, err := address.FromBytes(evmContractAddress.Bytes()); err == nil {
   500  				contractRawAddress = contractAddress.String()
   501  			}
   502  		}
   503  	} else {
   504  		stateDB.SetNonce(evmParams.txCtx.Origin, stateDB.GetNonce(evmParams.txCtx.Origin)+1)
   505  		// process contract
   506  		ret, remainingGas, evmErr = evm.Call(executor, *evmParams.contract, evmParams.data, remainingGas, evmParams.amount)
   507  	}
   508  	if evmErr != nil {
   509  		log.L().Debug("evm error", zap.Error(evmErr))
   510  		// The only possible consensus-error would be if there wasn't
   511  		// sufficient balance to make the transfer happen.
   512  		// Should be a hard fork (Bering)
   513  		if evmErr == vm.ErrInsufficientBalance && g.IsBering(blockHeight) {
   514  			return nil, evmParams.gas, remainingGas, action.EmptyAddress, iotextypes.ReceiptStatus_Failure, evmErr
   515  		}
   516  	}
   517  	if stateDB.Error() != nil {
   518  		log.L().Debug("statedb error", zap.Error(stateDB.Error()))
   519  	}
   520  	if !rules.IsLondon {
   521  		// Before EIP-3529: refunds were capped to gasUsed / 2
   522  		refund = (evmParams.gas - remainingGas) / params.RefundQuotient
   523  	} else {
   524  		// After EIP-3529: refunds are capped to gasUsed / 5
   525  		refund = (evmParams.gas - remainingGas) / params.RefundQuotientEIP3529
   526  	}
   527  	// before London EVM activation (at Okhotsk height), in certain cases dynamicGas
   528  	// has caused gas refund to change, which needs to be manually adjusted after
   529  	// the tx is reverted. After Okhotsk height, it is fixed inside RevertToSnapshot()
   530  	var (
   531  		deltaRefundByDynamicGas = evm.DeltaRefundByDynamicGas
   532  		featureCtx              = evmParams.featureCtx
   533  	)
   534  	if !featureCtx.CorrectGasRefund && deltaRefundByDynamicGas != 0 {
   535  		if deltaRefundByDynamicGas > 0 {
   536  			stateDB.SubRefund(uint64(deltaRefundByDynamicGas))
   537  		} else {
   538  			stateDB.AddRefund(uint64(-deltaRefundByDynamicGas))
   539  		}
   540  	}
   541  	if refund > stateDB.GetRefund() {
   542  		refund = stateDB.GetRefund()
   543  	}
   544  	remainingGas += refund
   545  
   546  	errCode := iotextypes.ReceiptStatus_Success
   547  	if evmErr != nil {
   548  		errCode = evmErrToErrStatusCode(evmErr, g, blockHeight)
   549  		if errCode == iotextypes.ReceiptStatus_ErrUnknown {
   550  			var addr string
   551  			if evmParams.contract != nil {
   552  				ioAddr, _ := address.FromBytes((*evmParams.contract)[:])
   553  				addr = ioAddr.String()
   554  			} else {
   555  				addr = "contract creation"
   556  			}
   557  			log.L().Warn("evm internal error", zap.Error(evmErr),
   558  				zap.String("address", addr),
   559  				log.Hex("calldata", evmParams.data))
   560  		}
   561  	}
   562  	return ret, evmParams.gas, remainingGas, contractRawAddress, errCode, nil
   563  }
   564  
   565  // evmErrToErrStatusCode returns ReceiptStatuscode which describes error type
   566  func evmErrToErrStatusCode(evmErr error, g genesis.Blockchain, height uint64) iotextypes.ReceiptStatus {
   567  	// specific error starting London
   568  	if g.IsOkhotsk(height) {
   569  		if evmErr == vm.ErrInvalidCode {
   570  			return iotextypes.ReceiptStatus_ErrInvalidCode
   571  		}
   572  	}
   573  
   574  	// specific error starting Jutland
   575  	if g.IsJutland(height) {
   576  		switch evmErr {
   577  		case vm.ErrInsufficientBalance:
   578  			return iotextypes.ReceiptStatus_ErrInsufficientBalance
   579  		case vm.ErrInvalidJump:
   580  			return iotextypes.ReceiptStatus_ErrInvalidJump
   581  		case vm.ErrReturnDataOutOfBounds:
   582  			return iotextypes.ReceiptStatus_ErrReturnDataOutOfBounds
   583  		case vm.ErrGasUintOverflow:
   584  			return iotextypes.ReceiptStatus_ErrGasUintOverflow
   585  		}
   586  	}
   587  
   588  	// specific error starting Bering
   589  	if g.IsBering(height) {
   590  		switch evmErr {
   591  		case vm.ErrOutOfGas:
   592  			return iotextypes.ReceiptStatus_ErrOutOfGas
   593  		case vm.ErrCodeStoreOutOfGas:
   594  			return iotextypes.ReceiptStatus_ErrCodeStoreOutOfGas
   595  		case vm.ErrDepth:
   596  			return iotextypes.ReceiptStatus_ErrDepth
   597  		case vm.ErrContractAddressCollision:
   598  			return iotextypes.ReceiptStatus_ErrContractAddressCollision
   599  		case vm.ErrExecutionReverted:
   600  			return iotextypes.ReceiptStatus_ErrExecutionReverted
   601  		case vm.ErrMaxCodeSizeExceeded:
   602  			return iotextypes.ReceiptStatus_ErrMaxCodeSizeExceeded
   603  		case vm.ErrWriteProtection:
   604  			return iotextypes.ReceiptStatus_ErrWriteProtection
   605  		default:
   606  			// internal errors from go-ethereum are not directly accessible
   607  			switch evmErr.Error() {
   608  			case "no compatible interpreter":
   609  				return iotextypes.ReceiptStatus_ErrNoCompatibleInterpreter
   610  			default:
   611  				return iotextypes.ReceiptStatus_ErrUnknown
   612  			}
   613  		}
   614  	}
   615  	// before Bering height, return one common failure
   616  	return iotextypes.ReceiptStatus_Failure
   617  }
   618  
   619  // intrinsicGas returns the intrinsic gas of an execution
   620  func intrinsicGas(size uint64, list types.AccessList) (uint64, error) {
   621  	if action.ExecutionDataGas == 0 {
   622  		panic("payload gas price cannot be zero")
   623  	}
   624  
   625  	var accessListGas uint64
   626  	if len(list) > 0 {
   627  		accessListGas = uint64(len(list)) * action.TxAccessListAddressGas
   628  		accessListGas += uint64(list.StorageKeys()) * action.TxAccessListStorageKeyGas
   629  	}
   630  	if (math.MaxInt64-action.ExecutionBaseIntrinsicGas-accessListGas)/action.ExecutionDataGas < size {
   631  		return 0, action.ErrInsufficientFunds
   632  	}
   633  	return size*action.ExecutionDataGas + action.ExecutionBaseIntrinsicGas + accessListGas, nil
   634  }
   635  
   636  // SimulateExecution simulates the execution in evm
   637  func SimulateExecution(
   638  	ctx context.Context,
   639  	sm protocol.StateManager,
   640  	caller address.Address,
   641  	ex *action.Execution,
   642  ) ([]byte, *action.Receipt, error) {
   643  	ctx, span := tracer.NewSpan(ctx, "evm.SimulateExecution")
   644  	defer span.End()
   645  	bcCtx := protocol.MustGetBlockchainCtx(ctx)
   646  	g := genesis.MustExtractGenesisContext(ctx)
   647  	ctx = protocol.WithActionCtx(
   648  		ctx,
   649  		protocol.ActionCtx{
   650  			Caller:     caller,
   651  			ActionHash: hash.Hash256b(byteutil.Must(proto.Marshal(ex.Proto()))),
   652  		},
   653  	)
   654  	zeroAddr, err := address.FromString(address.ZeroAddress)
   655  	if err != nil {
   656  		return nil, nil, err
   657  	}
   658  	ctx = protocol.WithBlockCtx(
   659  		ctx,
   660  		protocol.BlockCtx{
   661  			BlockHeight:    bcCtx.Tip.Height + 1,
   662  			BlockTimeStamp: bcCtx.Tip.Timestamp.Add(g.BlockInterval),
   663  			GasLimit:       g.BlockGasLimitByHeight(bcCtx.Tip.Height + 1),
   664  			Producer:       zeroAddr,
   665  		},
   666  	)
   667  
   668  	ctx = protocol.WithFeatureCtx(ctx)
   669  	return ExecuteContract(
   670  		ctx,
   671  		sm,
   672  		ex,
   673  	)
   674  }