Skip to content

Commit d50ad45

Browse files
Merge pull request #6836 from onflow/mpeter/evm-events-abi-decode-assert-errors
Include human-friendly error messages on EVM transaction events
2 parents 5ba9d90 + e682cd2 commit d50ad45

File tree

3 files changed

+189
-1
lines changed

3 files changed

+189
-1
lines changed

fvm/evm/events/events.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func (p *transactionEvent) ToCadence(chainID flow.ChainID) (cadence.Event, error
6868
cadence.NewUInt8(p.Result.TxType),
6969
bytesToCadenceUInt8ArrayValue(p.Payload),
7070
cadence.NewUInt16(uint16(p.Result.ResultSummary().ErrorCode)),
71-
cadence.String(p.Result.ErrorMsg()),
71+
cadence.String(p.Result.ErrorMessageWithRevertReason()),
7272
cadence.NewUInt64(p.Result.GasConsumed),
7373
cadence.String(p.Result.DeployedContractAddressString()),
7474
bytesToCadenceUInt8ArrayValue(encodedLogs),

fvm/evm/evm_test.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,174 @@ func TestEVMRun(t *testing.T) {
391391
assert.Equal(t, num, last.Big().Int64())
392392
})
393393
})
394+
395+
t.Run("testing EVM.run execution reverted with assert error", func(t *testing.T) {
396+
397+
t.Parallel()
398+
399+
RunWithNewEnvironment(t,
400+
chain, func(
401+
ctx fvm.Context,
402+
vm fvm.VM,
403+
snapshot snapshot.SnapshotTree,
404+
testContract *TestContract,
405+
testAccount *EOATestAccount,
406+
) {
407+
sc := systemcontracts.SystemContractsForChain(chain.ChainID())
408+
code := []byte(fmt.Sprintf(
409+
`
410+
import EVM from %s
411+
412+
transaction(tx: [UInt8], coinbaseBytes: [UInt8; 20]){
413+
prepare(account: &Account) {
414+
let coinbase = EVM.EVMAddress(bytes: coinbaseBytes)
415+
let res = EVM.run(tx: tx, coinbase: coinbase)
416+
417+
assert(res.status == EVM.Status.failed, message: "unexpected status")
418+
assert(res.errorCode == 306, message: "unexpected error code")
419+
assert(res.deployedContract == nil, message: "unexpected deployed contract")
420+
}
421+
}
422+
`,
423+
sc.EVMContract.Address.HexWithPrefix(),
424+
))
425+
426+
coinbaseAddr := types.Address{1, 2, 3}
427+
coinbaseBalance := getEVMAccountBalance(t, ctx, vm, snapshot, coinbaseAddr)
428+
require.Zero(t, types.BalanceToBigInt(coinbaseBalance).Uint64())
429+
430+
innerTxBytes := testAccount.PrepareSignAndEncodeTx(t,
431+
testContract.DeployedAt.ToCommon(),
432+
testContract.MakeCallData(t, "assertError"),
433+
big.NewInt(0),
434+
uint64(100_000),
435+
big.NewInt(1),
436+
)
437+
438+
innerTx := cadence.NewArray(
439+
ConvertToCadence(innerTxBytes),
440+
).WithType(stdlib.EVMTransactionBytesCadenceType)
441+
442+
coinbase := cadence.NewArray(
443+
ConvertToCadence(coinbaseAddr.Bytes()),
444+
).WithType(stdlib.EVMAddressBytesCadenceType)
445+
446+
tx := fvm.Transaction(
447+
flow.NewTransactionBody().
448+
SetScript(code).
449+
AddAuthorizer(sc.FlowServiceAccount.Address).
450+
AddArgument(json.MustEncode(innerTx)).
451+
AddArgument(json.MustEncode(coinbase)),
452+
0)
453+
454+
state, output, err := vm.Run(
455+
ctx,
456+
tx,
457+
snapshot,
458+
)
459+
require.NoError(t, err)
460+
require.NoError(t, output.Err)
461+
require.NotEmpty(t, state.WriteSet)
462+
463+
// assert event fields are correct
464+
require.Len(t, output.Events, 2)
465+
txEvent := output.Events[0]
466+
txEventPayload := TxEventToPayload(t, txEvent, sc.EVMContract.Address)
467+
require.NoError(t, err)
468+
469+
assert.Equal(
470+
t,
471+
"execution reverted: Assert Error Message",
472+
txEventPayload.ErrorMessage,
473+
)
474+
},
475+
)
476+
})
477+
478+
t.Run("testing EVM.run execution reverted with custom error", func(t *testing.T) {
479+
480+
t.Parallel()
481+
482+
RunWithNewEnvironment(t,
483+
chain, func(
484+
ctx fvm.Context,
485+
vm fvm.VM,
486+
snapshot snapshot.SnapshotTree,
487+
testContract *TestContract,
488+
testAccount *EOATestAccount,
489+
) {
490+
sc := systemcontracts.SystemContractsForChain(chain.ChainID())
491+
code := []byte(fmt.Sprintf(
492+
`
493+
import EVM from %s
494+
495+
transaction(tx: [UInt8], coinbaseBytes: [UInt8; 20]){
496+
prepare(account: &Account) {
497+
let coinbase = EVM.EVMAddress(bytes: coinbaseBytes)
498+
let res = EVM.run(tx: tx, coinbase: coinbase)
499+
500+
assert(res.status == EVM.Status.failed, message: "unexpected status")
501+
assert(res.errorCode == 306, message: "unexpected error code")
502+
assert(res.deployedContract == nil, message: "unexpected deployed contract")
503+
}
504+
}
505+
`,
506+
sc.EVMContract.Address.HexWithPrefix(),
507+
))
508+
509+
coinbaseAddr := types.Address{1, 2, 3}
510+
coinbaseBalance := getEVMAccountBalance(t, ctx, vm, snapshot, coinbaseAddr)
511+
require.Zero(t, types.BalanceToBigInt(coinbaseBalance).Uint64())
512+
513+
innerTxBytes := testAccount.PrepareSignAndEncodeTx(t,
514+
testContract.DeployedAt.ToCommon(),
515+
testContract.MakeCallData(t, "customError"),
516+
big.NewInt(0),
517+
uint64(100_000),
518+
big.NewInt(1),
519+
)
520+
521+
innerTx := cadence.NewArray(
522+
ConvertToCadence(innerTxBytes),
523+
).WithType(stdlib.EVMTransactionBytesCadenceType)
524+
525+
coinbase := cadence.NewArray(
526+
ConvertToCadence(coinbaseAddr.Bytes()),
527+
).WithType(stdlib.EVMAddressBytesCadenceType)
528+
529+
tx := fvm.Transaction(
530+
flow.NewTransactionBody().
531+
SetScript(code).
532+
AddAuthorizer(sc.FlowServiceAccount.Address).
533+
AddArgument(json.MustEncode(innerTx)).
534+
AddArgument(json.MustEncode(coinbase)),
535+
0)
536+
537+
state, output, err := vm.Run(
538+
ctx,
539+
tx,
540+
snapshot,
541+
)
542+
require.NoError(t, err)
543+
require.NoError(t, output.Err)
544+
require.NotEmpty(t, state.WriteSet)
545+
546+
// assert event fields are correct
547+
require.Len(t, output.Events, 2)
548+
txEvent := output.Events[0]
549+
txEventPayload := TxEventToPayload(t, txEvent, sc.EVMContract.Address)
550+
require.NoError(t, err)
551+
552+
// Unlike assert errors, custom errors cannot be further examined
553+
// or ABI decoded, as we do not have access to the contract's ABI.
554+
assert.Equal(
555+
t,
556+
"execution reverted",
557+
txEventPayload.ErrorMessage,
558+
)
559+
},
560+
)
561+
})
394562
}
395563

396564
func TestEVMBatchRun(t *testing.T) {

fvm/evm/types/result.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package types
22

33
import (
4+
"fmt"
5+
6+
"github.com/onflow/go-ethereum/accounts/abi"
47
gethCommon "github.com/onflow/go-ethereum/common"
58
gethTypes "github.com/onflow/go-ethereum/core/types"
9+
gethVM "github.com/onflow/go-ethereum/core/vm"
610
"github.com/onflow/go-ethereum/rlp"
711
)
812

@@ -144,6 +148,22 @@ func (res *Result) ErrorMsg() string {
144148
return errorMsg
145149
}
146150

151+
// ErrorMessageWithRevertReason returns the error message, if any VM or Validation
152+
// error occurred. Execution reverts coming from `assert` or `require` Solidity
153+
// statements, are parsed into their human-friendly representation.
154+
func (res *Result) ErrorMessageWithRevertReason() string {
155+
errorMessage := res.ErrorMsg()
156+
157+
if res.ResultSummary().ErrorCode == ExecutionErrCodeExecutionReverted {
158+
reason, errUnpack := abi.UnpackRevert(res.ReturnedData)
159+
if errUnpack == nil {
160+
errorMessage = fmt.Sprintf("%v: %v", gethVM.ErrExecutionReverted.Error(), reason)
161+
}
162+
}
163+
164+
return errorMessage
165+
}
166+
147167
// RLPEncodedLogs returns the rlp encoding of the logs
148168
func (res *Result) RLPEncodedLogs() ([]byte, error) {
149169
var encodedLogs []byte

0 commit comments

Comments
 (0)