github.com/onflow/flow-go@v0.33.17/fvm/evm/emulator/emulator.go (about)

     1  package emulator
     2  
     3  import (
     4  	"math/big"
     5  
     6  	gethCommon "github.com/ethereum/go-ethereum/common"
     7  	gethCore "github.com/ethereum/go-ethereum/core"
     8  	gethTypes "github.com/ethereum/go-ethereum/core/types"
     9  	gethVM "github.com/ethereum/go-ethereum/core/vm"
    10  	gethCrypto "github.com/ethereum/go-ethereum/crypto"
    11  	"github.com/onflow/atree"
    12  
    13  	"github.com/onflow/flow-go/fvm/evm/emulator/state"
    14  	"github.com/onflow/flow-go/fvm/evm/types"
    15  	"github.com/onflow/flow-go/model/flow"
    16  )
    17  
    18  // Emulator handles operations against evm runtime
    19  type Emulator struct {
    20  	rootAddr flow.Address
    21  	ledger   atree.Ledger
    22  }
    23  
    24  var _ types.Emulator = &Emulator{}
    25  
    26  // NewEmulator constructs a new EVM Emulator
    27  func NewEmulator(
    28  	ledger atree.Ledger,
    29  	rootAddr flow.Address,
    30  ) *Emulator {
    31  	return &Emulator{
    32  		rootAddr: rootAddr,
    33  		ledger:   ledger,
    34  	}
    35  }
    36  
    37  func newConfig(ctx types.BlockContext) *Config {
    38  	return NewConfig(
    39  		WithBlockNumber(new(big.Int).SetUint64(ctx.BlockNumber)),
    40  		WithCoinbase(ctx.GasFeeCollector.ToCommon()),
    41  		WithDirectCallBaseGasUsage(ctx.DirectCallBaseGasUsage),
    42  	)
    43  }
    44  
    45  // NewReadOnlyBlockView constructs a new readonly block view
    46  func (em *Emulator) NewReadOnlyBlockView(ctx types.BlockContext) (types.ReadOnlyBlockView, error) {
    47  	execState, err := state.NewStateDB(em.ledger, em.rootAddr)
    48  	return &ReadOnlyBlockView{
    49  		state: execState,
    50  	}, err
    51  }
    52  
    53  // NewBlockView constructs a new block view (mutable)
    54  func (em *Emulator) NewBlockView(ctx types.BlockContext) (types.BlockView, error) {
    55  	cfg := newConfig(ctx)
    56  	return &BlockView{
    57  		config:   cfg,
    58  		rootAddr: em.rootAddr,
    59  		ledger:   em.ledger,
    60  	}, nil
    61  }
    62  
    63  // ReadOnlyBlockView provides a read only view of a block
    64  // could be used multiple times for queries
    65  type ReadOnlyBlockView struct {
    66  	state types.StateDB
    67  }
    68  
    69  // BalanceOf returns the balance of the given address
    70  func (bv *ReadOnlyBlockView) BalanceOf(address types.Address) (*big.Int, error) {
    71  	return bv.state.GetBalance(address.ToCommon()), nil
    72  }
    73  
    74  // CodeOf returns the code of the given address
    75  func (bv *ReadOnlyBlockView) CodeOf(address types.Address) (types.Code, error) {
    76  	return bv.state.GetCode(address.ToCommon()), nil
    77  }
    78  
    79  // NonceOf returns the nonce of the given address
    80  func (bv *ReadOnlyBlockView) NonceOf(address types.Address) (uint64, error) {
    81  	return bv.state.GetNonce(address.ToCommon()), nil
    82  }
    83  
    84  // BlockView allows mutation of the evm state as part of a block
    85  //
    86  // TODO: allow  multiple calls per block view
    87  // TODO: add block level commit (separation of trie commit to storage)
    88  type BlockView struct {
    89  	config   *Config
    90  	rootAddr flow.Address
    91  	ledger   atree.Ledger
    92  }
    93  
    94  // DirectCall executes a direct call
    95  func (bl *BlockView) DirectCall(call *types.DirectCall) (*types.Result, error) {
    96  	proc, err := bl.newProcedure()
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	var res *types.Result
   101  	switch call.SubType {
   102  	case types.DepositCallSubType:
   103  		res, err = proc.mintTo(call.To, call.Value)
   104  	case types.WithdrawCallSubType:
   105  		res, err = proc.withdrawFrom(call.From, call.Value)
   106  	default:
   107  		res, err = proc.run(call.Message(), types.DirectCallTxType)
   108  	}
   109  	return res, err
   110  }
   111  
   112  // RunTransaction runs an evm transaction
   113  func (bl *BlockView) RunTransaction(
   114  	tx *gethTypes.Transaction,
   115  ) (*types.Result, error) {
   116  	var err error
   117  	proc, err := bl.newProcedure()
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	msg, err := gethCore.TransactionToMessage(tx, GetSigner(bl.config), proc.config.BlockContext.BaseFee)
   123  	if err != nil {
   124  		// note that this is not a fatal error (e.g. due to bad signature)
   125  		// not a valid transaction
   126  		return nil, types.NewEVMValidationError(err)
   127  	}
   128  
   129  	// update tx context origin
   130  	proc.evm.TxContext.Origin = msg.From
   131  	res, err := proc.run(msg, tx.Type())
   132  	return res, err
   133  }
   134  
   135  func (bl *BlockView) newProcedure() (*procedure, error) {
   136  	execState, err := state.NewStateDB(bl.ledger, bl.rootAddr)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	cfg := bl.config
   141  	return &procedure{
   142  		config: cfg,
   143  		evm: gethVM.NewEVM(
   144  			*cfg.BlockContext,
   145  			*cfg.TxContext,
   146  			execState,
   147  			cfg.ChainConfig,
   148  			cfg.EVMConfig,
   149  		),
   150  		state: execState,
   151  	}, nil
   152  }
   153  
   154  type procedure struct {
   155  	config *Config
   156  	evm    *gethVM.EVM
   157  	state  types.StateDB
   158  }
   159  
   160  // commit commits the changes to the state.
   161  func (proc *procedure) commit() error {
   162  	return handleCommitError(proc.state.Commit())
   163  }
   164  
   165  func handleCommitError(err error) error {
   166  	if err == nil {
   167  		return nil
   168  	}
   169  	// if known types (state errors) don't do anything and return
   170  	if types.IsAFatalError(err) || types.IsAStateError(err) {
   171  		return err
   172  	}
   173  
   174  	// else is a new fatal error
   175  	return types.NewFatalError(err)
   176  }
   177  
   178  func (proc *procedure) mintTo(address types.Address, amount *big.Int) (*types.Result, error) {
   179  	addr := address.ToCommon()
   180  	res := &types.Result{
   181  		GasConsumed: proc.config.DirectCallBaseGasUsage,
   182  		TxType:      types.DirectCallTxType,
   183  	}
   184  
   185  	// create account if not exist
   186  	if !proc.state.Exist(addr) {
   187  		proc.state.CreateAccount(addr)
   188  	}
   189  
   190  	// add balance
   191  	proc.state.AddBalance(addr, amount)
   192  
   193  	// we don't need to increment any nonce, given the origin doesn't exist
   194  	return res, proc.commit()
   195  }
   196  
   197  func (proc *procedure) withdrawFrom(address types.Address, amount *big.Int) (*types.Result, error) {
   198  
   199  	addr := address.ToCommon()
   200  	res := &types.Result{
   201  		GasConsumed: proc.config.DirectCallBaseGasUsage,
   202  		TxType:      types.DirectCallTxType,
   203  	}
   204  
   205  	// check if account exists
   206  	// while this method is only called from bridged accounts
   207  	// it might be the case that someone creates a bridged account
   208  	// and never transfer tokens to and call for withdraw
   209  	// TODO: we might revisit this apporach and
   210  	// 		return res, types.ErrAccountDoesNotExist
   211  	// instead
   212  	if !proc.state.Exist(addr) {
   213  		proc.state.CreateAccount(addr)
   214  	}
   215  
   216  	// check the source account balance
   217  	// if balance is lower than amount needed for withdrawal, error out
   218  	if proc.state.GetBalance(addr).Cmp(amount) < 0 {
   219  		return res, types.ErrInsufficientBalance
   220  	}
   221  
   222  	// sub balance
   223  	proc.state.SubBalance(addr, amount)
   224  
   225  	// we increment the nonce for source account cause
   226  	// withdraw counts as a transaction
   227  	nonce := proc.state.GetNonce(addr)
   228  	proc.state.SetNonce(addr, nonce+1)
   229  
   230  	return res, proc.commit()
   231  }
   232  
   233  func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result, error) {
   234  	res := types.Result{
   235  		TxType: txType,
   236  	}
   237  
   238  	gasPool := (*gethCore.GasPool)(&proc.config.BlockContext.GasLimit)
   239  	execResult, err := gethCore.NewStateTransition(
   240  		proc.evm,
   241  		msg,
   242  		gasPool,
   243  	).TransitionDb()
   244  	if err != nil {
   245  		res.Failed = true
   246  		// if the error is a fatal error or a non-fatal state error return it
   247  		if types.IsAFatalError(err) || types.IsAStateError(err) {
   248  			return &res, err
   249  		}
   250  		// otherwise is a validation error (pre-check failure)
   251  		// no state change, wrap the error and return
   252  		return &res, types.NewEVMValidationError(err)
   253  	}
   254  
   255  	// if prechecks are passed, the exec result won't be nil
   256  	if execResult != nil {
   257  		res.GasConsumed = execResult.UsedGas
   258  		if !execResult.Failed() { // collect vm errors
   259  			res.ReturnedValue = execResult.ReturnData
   260  			// If the transaction created a contract, store the creation address in the receipt.
   261  			if msg.To == nil {
   262  				res.DeployedContractAddress = types.NewAddress(gethCrypto.CreateAddress(msg.From, msg.Nonce))
   263  			}
   264  			res.Logs = proc.state.Logs(
   265  				// TODO pass proper hash values
   266  				gethCommon.Hash{},
   267  				proc.config.BlockContext.BlockNumber.Uint64(),
   268  				gethCommon.Hash{},
   269  				0,
   270  			)
   271  		} else {
   272  			res.Failed = true
   273  			err = types.NewEVMExecutionError(execResult.Err)
   274  		}
   275  	}
   276  	commitErr := proc.commit()
   277  	if commitErr != nil {
   278  		return &res, commitErr
   279  	}
   280  	return &res, err
   281  }