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

     1  package handler
     2  
     3  import (
     4  	"bytes"
     5  
     6  	gethTypes "github.com/ethereum/go-ethereum/core/types"
     7  	"github.com/ethereum/go-ethereum/rlp"
     8  	"github.com/onflow/cadence/runtime/common"
     9  
    10  	"github.com/onflow/flow-go/fvm/environment"
    11  	"github.com/onflow/flow-go/fvm/errors"
    12  	"github.com/onflow/flow-go/fvm/evm/types"
    13  )
    14  
    15  // ContractHandler is responsible for triggering calls to emulator, metering,
    16  // event emission and updating the block
    17  //
    18  // TODO and Warning: currently database keeps a copy of roothash, and if after
    19  // commiting the changes by the evm we want to revert in this code we need to reset that
    20  // or we should always do all the checks and return before calling the emulator,
    21  // after that should be only event emissions and computation usage updates.
    22  // thats another reason we first check the computation limit before using.
    23  // in the future we might benefit from a view style of access to db passed as
    24  // a param to the emulator.
    25  type ContractHandler struct {
    26  	flowTokenAddress common.Address
    27  	blockstore       types.BlockStore
    28  	addressAllocator types.AddressAllocator
    29  	backend          types.Backend
    30  	emulator         types.Emulator
    31  }
    32  
    33  func (h *ContractHandler) FlowTokenAddress() common.Address {
    34  	return h.flowTokenAddress
    35  }
    36  
    37  var _ types.ContractHandler = &ContractHandler{}
    38  
    39  func NewContractHandler(
    40  	flowTokenAddress common.Address,
    41  	blockstore types.BlockStore,
    42  	addressAllocator types.AddressAllocator,
    43  	backend types.Backend,
    44  	emulator types.Emulator,
    45  ) *ContractHandler {
    46  	return &ContractHandler{
    47  		flowTokenAddress: flowTokenAddress,
    48  		blockstore:       blockstore,
    49  		addressAllocator: addressAllocator,
    50  		backend:          backend,
    51  		emulator:         emulator,
    52  	}
    53  }
    54  
    55  // AllocateAddress allocates an address to be used by the bridged accounts
    56  func (h *ContractHandler) AllocateAddress() types.Address {
    57  	target, err := h.addressAllocator.AllocateAddress()
    58  	handleError(err)
    59  	return target
    60  }
    61  
    62  // AccountByAddress returns the account for the given address,
    63  // if isAuthorized is set, account is controlled by the FVM (bridged accounts)
    64  func (h *ContractHandler) AccountByAddress(addr types.Address, isAuthorized bool) types.Account {
    65  	return newAccount(h, addr, isAuthorized)
    66  }
    67  
    68  // LastExecutedBlock returns the last executed block
    69  func (h *ContractHandler) LastExecutedBlock() *types.Block {
    70  	block, err := h.blockstore.LatestBlock()
    71  	handleError(err)
    72  	return block
    73  }
    74  
    75  // Run runs an rlpencoded evm transaction and
    76  // collects the gas fees and pay it to the coinbase address provided.
    77  func (h *ContractHandler) Run(rlpEncodedTx []byte, coinbase types.Address) {
    78  	// step 1 - transaction decoding
    79  	encodedLen := uint(len(rlpEncodedTx))
    80  	err := h.backend.MeterComputation(environment.ComputationKindRLPDecoding, encodedLen)
    81  	handleError(err)
    82  
    83  	tx := gethTypes.Transaction{}
    84  	err = tx.DecodeRLP(
    85  		rlp.NewStream(
    86  			bytes.NewReader(rlpEncodedTx),
    87  			uint64(encodedLen)))
    88  	handleError(err)
    89  
    90  	// step 2 - run transaction
    91  	h.checkGasLimit(types.GasLimit(tx.Gas()))
    92  
    93  	ctx := h.getBlockContext()
    94  	ctx.GasFeeCollector = coinbase
    95  	blk, err := h.emulator.NewBlockView(ctx)
    96  	handleError(err)
    97  
    98  	res, err := blk.RunTransaction(&tx)
    99  	h.meterGasUsage(res)
   100  	handleError(err)
   101  
   102  	// step 3 - update block proposal
   103  	bp, err := h.blockstore.BlockProposal()
   104  	handleError(err)
   105  
   106  	txHash := tx.Hash()
   107  	bp.AppendTxHash(txHash)
   108  
   109  	// step 4 - emit events
   110  	h.emitEvent(types.NewTransactionExecutedEvent(
   111  		bp.Height,
   112  		rlpEncodedTx,
   113  		txHash,
   114  		res,
   115  	))
   116  	h.emitEvent(types.NewBlockExecutedEvent(bp))
   117  
   118  	// step 5 - commit block proposal
   119  	err = h.blockstore.CommitBlockProposal()
   120  	handleError(err)
   121  }
   122  
   123  func (h *ContractHandler) checkGasLimit(limit types.GasLimit) {
   124  	// check gas limit against what has been left on the transaction side
   125  	if !h.backend.ComputationAvailable(environment.ComputationKindEVMGasUsage, uint(limit)) {
   126  		handleError(types.ErrInsufficientComputation)
   127  	}
   128  }
   129  
   130  func (h *ContractHandler) meterGasUsage(res *types.Result) {
   131  	if res != nil {
   132  		err := h.backend.MeterComputation(environment.ComputationKindEVMGasUsage, uint(res.GasConsumed))
   133  		handleError(err)
   134  	}
   135  }
   136  
   137  func (h *ContractHandler) emitEvent(event *types.Event) {
   138  	ev, err := event.Payload.CadenceEvent()
   139  	handleError(err)
   140  
   141  	err = h.backend.EmitEvent(ev)
   142  	handleError(err)
   143  }
   144  
   145  func (h *ContractHandler) getBlockContext() types.BlockContext {
   146  	bp, err := h.blockstore.BlockProposal()
   147  	handleError(err)
   148  	return types.BlockContext{
   149  		BlockNumber:            bp.Height,
   150  		DirectCallBaseGasUsage: types.DefaultDirectCallBaseGasUsage,
   151  	}
   152  }
   153  
   154  type Account struct {
   155  	isAuthorized bool
   156  	address      types.Address
   157  	fch          *ContractHandler
   158  }
   159  
   160  // newAccount creates a new evm account
   161  func newAccount(fch *ContractHandler, addr types.Address, isAuthorized bool) *Account {
   162  	return &Account{
   163  		isAuthorized: isAuthorized,
   164  		fch:          fch,
   165  		address:      addr,
   166  	}
   167  }
   168  
   169  // Address returns the address associated with the bridged account
   170  func (a *Account) Address() types.Address {
   171  	return a.address
   172  }
   173  
   174  // Balance returns the balance of this bridged account
   175  //
   176  // TODO: we might need to meter computation for read only operations as well
   177  // currently the storage limits is enforced
   178  func (a *Account) Balance() types.Balance {
   179  	ctx := a.fch.getBlockContext()
   180  
   181  	blk, err := a.fch.emulator.NewReadOnlyBlockView(ctx)
   182  	handleError(err)
   183  
   184  	bl, err := blk.BalanceOf(a.address)
   185  	handleError(err)
   186  
   187  	balance, err := types.NewBalanceFromAttoFlow(bl)
   188  	handleError(err)
   189  	return balance
   190  }
   191  
   192  // Deposit deposits the token from the given vault into the flow evm main vault
   193  // and update the account balance with the new amount
   194  func (a *Account) Deposit(v *types.FLOWTokenVault) {
   195  	cfg := a.fch.getBlockContext()
   196  	a.fch.checkGasLimit(types.GasLimit(cfg.DirectCallBaseGasUsage))
   197  
   198  	call := types.NewDepositCall(
   199  		a.address,
   200  		v.Balance().ToAttoFlow(),
   201  	)
   202  	a.executeAndHandleCall(a.fch.getBlockContext(), call, v.Balance().ToAttoFlow().Uint64(), false)
   203  }
   204  
   205  // Withdraw deducts the balance from the account and
   206  // withdraw and return flow token from the Flex main vault.
   207  func (a *Account) Withdraw(b types.Balance) *types.FLOWTokenVault {
   208  	a.checkAuthorized()
   209  
   210  	cfg := a.fch.getBlockContext()
   211  	a.fch.checkGasLimit(types.GasLimit(cfg.DirectCallBaseGasUsage))
   212  
   213  	// check balance of flex vault
   214  	bp, err := a.fch.blockstore.BlockProposal()
   215  	handleError(err)
   216  	if b.ToAttoFlow().Uint64() > bp.TotalSupply {
   217  		handleError(types.ErrInsufficientTotalSupply)
   218  	}
   219  
   220  	call := types.NewWithdrawCall(
   221  		a.address,
   222  		b.ToAttoFlow(),
   223  	)
   224  	a.executeAndHandleCall(a.fch.getBlockContext(), call, b.ToAttoFlow().Uint64(), true)
   225  
   226  	return types.NewFlowTokenVault(b)
   227  }
   228  
   229  // Transfer transfers tokens between accounts
   230  func (a *Account) Transfer(to types.Address, balance types.Balance) {
   231  	a.checkAuthorized()
   232  
   233  	ctx := a.fch.getBlockContext()
   234  	a.fch.checkGasLimit(types.GasLimit(ctx.DirectCallBaseGasUsage))
   235  
   236  	call := types.NewTransferCall(
   237  		a.address,
   238  		to,
   239  		balance.ToAttoFlow(),
   240  	)
   241  	a.executeAndHandleCall(ctx, call, 0, false)
   242  }
   243  
   244  // Deploy deploys a contract to the EVM environment
   245  // the new deployed contract would be at the returned address and
   246  // the contract data is not controlled by the caller accounts
   247  func (a *Account) Deploy(code types.Code, gaslimit types.GasLimit, balance types.Balance) types.Address {
   248  	a.checkAuthorized()
   249  	a.fch.checkGasLimit(gaslimit)
   250  
   251  	call := types.NewDeployCall(
   252  		a.address,
   253  		code,
   254  		uint64(gaslimit),
   255  		balance.ToAttoFlow(),
   256  	)
   257  	res := a.executeAndHandleCall(a.fch.getBlockContext(), call, 0, false)
   258  	return types.Address(res.DeployedContractAddress)
   259  }
   260  
   261  // Call calls a smart contract function with the given data
   262  // it would limit the gas used according to the limit provided
   263  // given it doesn't goes beyond what Flow transaction allows.
   264  // the balance would be deducted from the OFA account and would be transferred to the target address
   265  func (a *Account) Call(to types.Address, data types.Data, gaslimit types.GasLimit, balance types.Balance) types.Data {
   266  	a.checkAuthorized()
   267  	a.fch.checkGasLimit(gaslimit)
   268  	call := types.NewContractCall(
   269  		a.address,
   270  		to,
   271  		data,
   272  		uint64(gaslimit),
   273  		balance.ToAttoFlow(),
   274  	)
   275  	res := a.executeAndHandleCall(a.fch.getBlockContext(), call, 0, false)
   276  	return res.ReturnedValue
   277  }
   278  
   279  func (a *Account) executeAndHandleCall(
   280  	ctx types.BlockContext,
   281  	call *types.DirectCall,
   282  	totalSupplyDiff uint64,
   283  	deductSupplyDiff bool,
   284  ) *types.Result {
   285  	// execute the call
   286  	blk, err := a.fch.emulator.NewBlockView(ctx)
   287  	handleError(err)
   288  
   289  	res, err := blk.DirectCall(call)
   290  	a.fch.meterGasUsage(res)
   291  	handleError(err)
   292  
   293  	// update block proposal
   294  	callHash, err := call.Hash()
   295  	if err != nil {
   296  		err = types.NewFatalError(err)
   297  		handleError(err)
   298  	}
   299  
   300  	bp, err := a.fch.blockstore.BlockProposal()
   301  	handleError(err)
   302  	bp.AppendTxHash(callHash)
   303  	if deductSupplyDiff {
   304  		bp.TotalSupply -= totalSupplyDiff
   305  	} else {
   306  		// TODO: add overflow errors (even though we might never get there)
   307  		bp.TotalSupply += totalSupplyDiff
   308  	}
   309  
   310  	// emit events
   311  	encoded, err := call.Encode()
   312  	handleError(err)
   313  
   314  	a.fch.emitEvent(
   315  		types.NewTransactionExecutedEvent(
   316  			bp.Height,
   317  			encoded,
   318  			callHash,
   319  			res,
   320  		),
   321  	)
   322  	a.fch.emitEvent(types.NewBlockExecutedEvent(bp))
   323  
   324  	// commit block proposal
   325  	err = a.fch.blockstore.CommitBlockProposal()
   326  	handleError(err)
   327  
   328  	return res
   329  }
   330  
   331  func (a *Account) checkAuthorized() {
   332  	// check if account is authorized (i.e. is a bridged account)
   333  	if !a.isAuthorized {
   334  		handleError(types.ErrUnAuthroizedMethodCall)
   335  	}
   336  }
   337  
   338  func handleError(err error) {
   339  	if err == nil {
   340  		return
   341  	}
   342  
   343  	if types.IsAFatalError(err) {
   344  		// don't wrap it
   345  		panic(err)
   346  	}
   347  	panic(errors.NewEVMError(err))
   348  }