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

[Flow EVM] update error handling #5357

Merged
merged 36 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
194c9b9
refactor emulator errors
ramtinms Feb 6, 2024
26f938a
update validation errors
ramtinms Feb 6, 2024
36e64e2
prepare handler for try methods
ramtinms Feb 6, 2024
f38c939
error handling
ramtinms Feb 7, 2024
9c032f5
add error codes
ramtinms Feb 8, 2024
7da9249
renaming
ramtinms Feb 8, 2024
e320e7f
update error codes
ramtinms Feb 8, 2024
43dd68a
update tests
ramtinms Feb 8, 2024
60a3431
clean up
ramtinms Feb 8, 2024
b0e7691
.
ramtinms Feb 8, 2024
babd5d8
move to wrapped backend
ramtinms Feb 8, 2024
8a215ec
Merge branch 'master' into ramtin/5225-returning-error
ramtinms Feb 8, 2024
6ed9a45
refactor wrapped backend
ramtinms Feb 8, 2024
45193eb
.
ramtinms Feb 8, 2024
70c2211
fix tests
ramtinms Feb 9, 2024
42418ed
error improvements
ramtinms Feb 9, 2024
685dc44
add tests for try run
ramtinms Feb 9, 2024
37d820f
clean up
ramtinms Feb 9, 2024
47e978a
fix tests
ramtinms Feb 9, 2024
9c5845b
remove print line
ramtinms Feb 9, 2024
6a6d16b
lint fix
ramtinms Feb 10, 2024
d646067
Merge branch 'master' into ramtin/5225-returning-error
ramtinms Feb 10, 2024
288b261
lint fix
ramtinms Feb 10, 2024
53fa23c
Merge branch 'master' into ramtin/5225-returning-error
ramtinms Feb 12, 2024
1bfaf08
Merge branch 'master' into ramtin/5225-returning-error
ramtinms Feb 13, 2024
d3f2568
fix test
ramtinms Feb 13, 2024
6c00c5b
use validation error for balance checks
ramtinms Feb 13, 2024
5b89a51
.
ramtinms Feb 13, 2024
b4f04af
.
ramtinms Feb 13, 2024
32a6d24
fix test
ramtinms Feb 13, 2024
af7dcb5
reduce block store capacity to 16 for now
ramtinms Feb 13, 2024
288a24d
Merge branch 'master' into ramtin/5225-returning-error
ramtinms Feb 13, 2024
cb54731
Merge branch 'master' into ramtin/5225-returning-error
ramtinms Feb 14, 2024
b3eef9e
Merge branch 'master' into ramtin/5225-returning-error
ramtinms Feb 15, 2024
2159604
apply PR feedback
ramtinms Feb 15, 2024
ad735a5
Merge branch 'master' into ramtin/5225-returning-error
franklywatson Feb 16, 2024
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 fvm/errors/codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
FailureCodePayerBalanceCheckFailure FailureCode = 2007
FailureCodeDerivedDataCacheImplementationFailure FailureCode = 2008
FailureCodeRandomSourceFailure FailureCode = 2009
FailureCodeEVMFailure FailureCode = 2010
// Deprecated: No longer used.
FailureCodeMetaTransactionFailure FailureCode = 2100
)
Expand Down
1 change: 1 addition & 0 deletions fvm/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ func NewEventEncodingError(err error) CodedError {
// in order for Cadence to correctly handle the error
var _ errors.UserError = &(EVMError{})

// EVMError captures any non-fatal EVM error
type EVMError struct {
CodedError
}
Expand Down
9 changes: 9 additions & 0 deletions fvm/errors/failures.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,12 @@ func NewParseRestrictedModeInvalidAccessFailure(
"cannot access %s while cadence is in parse restricted mode",
spanName)
}

// NewEVMFailure constructs a new CodedFailure which captures a fatal
// caused by the EVM.
func NewEVMFailure(err error) CodedFailure {
return WrapCodedFailure(
FailureCodeEVMFailure,
err,
"evm failure")
}
153 changes: 153 additions & 0 deletions fvm/evm/backends/wrappedEnv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package backends

import (
"github.com/onflow/atree"
"github.com/onflow/cadence"
"github.com/onflow/cadence/runtime"
"github.com/onflow/cadence/runtime/common"

"github.com/onflow/flow-go/fvm/environment"
"github.com/onflow/flow-go/fvm/errors"
"github.com/onflow/flow-go/fvm/evm/types"
"github.com/onflow/flow-go/fvm/meter"
"github.com/onflow/flow-go/model/flow"
)

// WrappedEnvironment wraps an FVM environment
type WrappedEnvironment struct {
env environment.Environment
}

// NewWrappedEnvironment constructs a new wrapped environment
func NewWrappedEnvironment(env environment.Environment) types.Backend {
return &WrappedEnvironment{env}
}

var _ types.Backend = &WrappedEnvironment{}

func (we *WrappedEnvironment) GetValue(owner, key []byte) ([]byte, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these don't need to be pointer receivers.

val, err := we.env.GetValue(owner, key)
return val, handleEnvironmentError(err)
}

func (we *WrappedEnvironment) SetValue(owner, key, value []byte) error {
err := we.env.SetValue(owner, key, value)
return handleEnvironmentError(err)
}

func (we *WrappedEnvironment) ValueExists(owner, key []byte) (bool, error) {
b, err := we.env.ValueExists(owner, key)
return b, handleEnvironmentError(err)
}

func (we *WrappedEnvironment) AllocateStorageIndex(owner []byte) (atree.StorageIndex, error) {
index, err := we.env.AllocateStorageIndex(owner)
return index, handleEnvironmentError(err)
}

func (we *WrappedEnvironment) MeterComputation(kind common.ComputationKind, intensity uint) error {
err := we.env.MeterComputation(kind, intensity)
return handleEnvironmentError(err)
}

func (we *WrappedEnvironment) ComputationUsed() (uint64, error) {
val, err := we.env.ComputationUsed()
return val, handleEnvironmentError(err)
}

func (we *WrappedEnvironment) ComputationIntensities() meter.MeteredComputationIntensities {
return we.env.ComputationIntensities()
}

func (we *WrappedEnvironment) ComputationAvailable(kind common.ComputationKind, intensity uint) bool {
return we.env.ComputationAvailable(kind, intensity)
}

func (we *WrappedEnvironment) MeterMemory(usage common.MemoryUsage) error {
err := we.env.MeterMemory(usage)
return handleEnvironmentError(err)
}

func (we *WrappedEnvironment) MemoryUsed() (uint64, error) {
val, err := we.env.MemoryUsed()
return val, handleEnvironmentError(err)
}

func (we *WrappedEnvironment) MeterEmittedEvent(byteSize uint64) error {
err := we.env.MeterEmittedEvent(byteSize)
return handleEnvironmentError(err)
}

func (we *WrappedEnvironment) TotalEmittedEventBytes() uint64 {
return we.env.TotalEmittedEventBytes()
}

func (we *WrappedEnvironment) InteractionUsed() (uint64, error) {
val, err := we.env.InteractionUsed()
return val, handleEnvironmentError(err)
}

func (we *WrappedEnvironment) EmitEvent(event cadence.Event) error {
err := we.env.EmitEvent(event)
return handleEnvironmentError(err)
}

func (we *WrappedEnvironment) Events() flow.EventsList {
return we.env.Events()

}

func (we *WrappedEnvironment) ServiceEvents() flow.EventsList {
return we.env.ServiceEvents()
}

func (we *WrappedEnvironment) ConvertedServiceEvents() flow.ServiceEventList {
return we.env.ConvertedServiceEvents()
}

func (we *WrappedEnvironment) Reset() {
we.env.Reset()
}

func (we *WrappedEnvironment) GetCurrentBlockHeight() (uint64, error) {
val, err := we.env.GetCurrentBlockHeight()
return val, handleEnvironmentError(err)
}

func (we *WrappedEnvironment) GetBlockAtHeight(height uint64) (
runtime.Block,
bool,
error,
) {
val, found, err := we.env.GetBlockAtHeight(height)
return val, found, handleEnvironmentError(err)
}

func (we *WrappedEnvironment) ReadRandom(buffer []byte) error {
err := we.env.ReadRandom(buffer)
return handleEnvironmentError(err)
}

func (we *WrappedEnvironment) Invoke(
spec environment.ContractFunctionSpec,
arguments []cadence.Value,
) (
cadence.Value,
error,
) {
val, err := we.env.Invoke(spec, arguments)
return val, handleEnvironmentError(err)
}

func handleEnvironmentError(err error) error {
if err == nil {
return nil
}

// fvm fatal errors
if errors.IsFailure(err) {
return types.NewFatalError(err)
}

return types.NewBackendError(err)
}
79 changes: 37 additions & 42 deletions fvm/evm/emulator/emulator.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package emulator

import (
"errors"
"math/big"

gethCommon "github.com/ethereum/go-ethereum/common"
Expand All @@ -17,8 +16,6 @@ import (
"github.com/onflow/flow-go/model/flow"
)

var ErrInvalidBalance = errors.New("invalid balance for transfer")

// Emulator handles operations against evm runtime
type Emulator struct {
rootAddr flow.Address
Expand Down Expand Up @@ -129,22 +126,22 @@ func (bl *BlockView) DirectCall(call *types.DirectCall) (*types.Result, error) {
func (bl *BlockView) RunTransaction(
tx *gethTypes.Transaction,
) (*types.Result, error) {
var res *types.Result
var err error
proc, err := bl.newProcedure()
if err != nil {
return nil, err
}

msg, err := gethCore.TransactionToMessage(tx, GetSigner(bl.config), proc.config.BlockContext.BaseFee)
if err != nil {
// note that this is not a fatal error (e.g. due to bad signature)
// this is not a fatal error (e.g. due to bad signature)
// not a valid transaction
return nil, types.NewEVMValidationError(err)
return res, types.NewEVMValidationError(err)
}

// update tx context origin
proc.evm.TxContext.Origin = msg.From
res, err := proc.run(msg, tx.Type())
res, err = proc.run(msg, tx.Type())
return res, err
}

Expand Down Expand Up @@ -175,20 +172,17 @@ type procedure struct {

// commit commits the changes to the state.
func (proc *procedure) commit() error {
return handleCommitError(proc.state.Commit())
}
err := proc.state.Commit()
if err != nil {
// if known types (state errors) don't do anything and return
if types.IsAFatalError(err) || types.IsAStateError(err) {
return err
}

func handleCommitError(err error) error {
if err == nil {
return nil
}
// if known types (state errors) don't do anything and return
if types.IsAFatalError(err) || types.IsAStateError(err) {
return err
// else is a new fatal error
return types.NewFatalError(err)
}

// else is a new fatal error
return types.NewFatalError(err)
return nil
}

func (proc *procedure) mintTo(address types.Address, amount *big.Int) (*types.Result, error) {
Expand Down Expand Up @@ -229,7 +223,7 @@ func (proc *procedure) withdrawFrom(address types.Address, amount *big.Int) (*ty
// check the source account balance
// if balance is lower than amount needed for withdrawal, error out
if proc.state.GetBalance(addr).Cmp(amount) < 0 {
return res, types.ErrInsufficientBalance
return res, gethCore.ErrInsufficientFundsForTransfer
}

// sub balance
Expand Down Expand Up @@ -257,6 +251,10 @@ func (proc *procedure) deployAt(
gasLimit uint64,
value *big.Int,
) (*types.Result, error) {
if value.Sign() < 0 {
return nil, types.ErrInvalidBalance
}

res := &types.Result{
TxType: types.DirectCallTxType,
}
Expand All @@ -265,14 +263,15 @@ func (proc *procedure) deployAt(
// precheck 1 - check balance of the source
if value.Sign() != 0 &&
!proc.evm.Context.CanTransfer(proc.state, caller.ToCommon(), value) {
return res, gethVM.ErrInsufficientBalance
return res, gethCore.ErrInsufficientFundsForTransfer
}

// precheck 2 - ensure there's no existing contract is deployed at the address
// precheck 2 - ensure there's no existing eoa or contract is deployed at the address
contractHash := proc.state.GetCodeHash(addr)
if proc.state.GetNonce(addr) != 0 ||
(contractHash != (gethCommon.Hash{}) && contractHash != gethTypes.EmptyCodeHash) {
return res, gethVM.ErrContractAddressCollision
res.VMError = gethVM.ErrContractAddressCollision
return res, nil
}

callerCommon := caller.ToCommon()
Expand All @@ -283,9 +282,6 @@ func (proc *procedure) deployAt(
// increment the nonce for the caller
proc.state.SetNonce(callerCommon, proc.state.GetNonce(callerCommon)+1)

if value.Sign() < 0 {
return res, ErrInvalidBalance
}
// setup account
proc.state.CreateAccount(addr)
proc.state.SetNonce(addr, 1) // (EIP-158)
Expand Down Expand Up @@ -322,32 +318,32 @@ func (proc *procedure) deployAt(
if err != gethVM.ErrExecutionReverted {
res.GasConsumed = gasLimit
}
res.Failed = true
return res, err
res.VMError = err
return res, nil
}

// update gas usage
if gasCost > gasLimit {
// consume all the remaining gas (Homestead)
res.GasConsumed = gasLimit
res.Failed = true
return res, gethVM.ErrCodeStoreOutOfGas
res.VMError = gethVM.ErrCodeStoreOutOfGas
return res, nil
}

// check max code size (EIP-158)
if len(ret) > gethParams.MaxCodeSize {
// consume all the remaining gas (Homestead)
res.GasConsumed = gasLimit
res.Failed = true
return res, gethVM.ErrMaxCodeSizeExceeded
res.VMError = gethVM.ErrMaxCodeSizeExceeded
return res, nil
}

// reject code starting with 0xEF (EIP-3541)
if len(ret) >= 1 && ret[0] == 0xEF {
// consume all the remaining gas (Homestead)
res.GasConsumed = gasLimit
res.Failed = true
return res, gethVM.ErrInvalidCode
res.VMError = gethVM.ErrInvalidCode
return res, nil
}

proc.state.SetCode(addr, ret)
Expand All @@ -374,8 +370,9 @@ func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result,
gasPool,
).TransitionDb()
if err != nil {
res.Failed = true
// if the error is a fatal error or a non-fatal state error return it
// this condition should never happen
// given all StateDB errors are withheld for the commit time.
if types.IsAFatalError(err) || types.IsAStateError(err) {
return &res, err
}
Expand All @@ -401,15 +398,13 @@ func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result,
0,
)
} else {
res.Failed = true
err = types.NewEVMExecutionError(execResult.Err)
// execResult.Err is VM errors (we don't return it as error)
res.VMError = execResult.Err
}
}
commitErr := proc.commit()
if commitErr != nil {
return &res, commitErr
}
return &res, err
// all commmit errors (StateDB errors) has to be returned
// TODO: maybe handle them (if there are happy errors)
return &res, proc.commit()
}

func SetupPrecompile(cfg *Config) {
Expand Down
Loading
Loading