Skip to content

Commit

Permalink
Merge pull request #6224 from onflow/bastian/improve-evm-events
Browse files Browse the repository at this point in the history
[Flow EVM] Improve event emission
  • Loading branch information
turbolent authored Jul 19, 2024
2 parents 827ef0c + dc063c9 commit d2dfdb8
Show file tree
Hide file tree
Showing 15 changed files with 669 additions and 222 deletions.
79 changes: 22 additions & 57 deletions fvm/evm/types/events.go → fvm/evm/events/events.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package types
package events

import (
"fmt"

"github.com/onflow/cadence"
"github.com/onflow/cadence/encoding/ccf"
"github.com/onflow/cadence/runtime/common"
gethCommon "github.com/onflow/go-ethereum/common"
"github.com/onflow/go-ethereum/rlp"

"github.com/onflow/flow-go/fvm/evm/stdlib"
"github.com/onflow/flow-go/fvm/evm/types"
"github.com/onflow/flow-go/model/flow"
)

Expand All @@ -19,7 +20,7 @@ const (

type EventPayload interface {
// ToCadence converts the event to Cadence event
ToCadence(location common.Location) (cadence.Event, error)
ToCadence(chainID flow.ChainID) (cadence.Event, error)
}

type Event struct {
Expand All @@ -30,8 +31,8 @@ type Event struct {
// todo we might have to break this event into two (tx included /tx executed) if size becomes an issue

type transactionEvent struct {
Payload []byte // transaction RLP-encoded payload
Result *Result // transaction execution result
Payload []byte // transaction RLP-encoded payload
Result *types.Result // transaction execution result
BlockHeight uint64
}

Expand All @@ -40,7 +41,7 @@ type transactionEvent struct {
// - payload: the RLP-encoded payload of the transaction
// - blockHeight: the height of the block where the transaction is included
func NewTransactionEvent(
result *Result,
result *types.Result,
payload []byte,
blockHeight uint64,
) *Event {
Expand All @@ -54,22 +55,7 @@ func NewTransactionEvent(
}
}

var transactionEventFields = []cadence.Field{
cadence.NewField("hash", cadenceHashType),
cadence.NewField("index", cadence.UInt16Type),
cadence.NewField("type", cadence.UInt8Type),
cadence.NewField("payload", cadenceArrayTypeOfUInt8),
cadence.NewField("errorCode", cadence.UInt16Type),
cadence.NewField("errorMessage", cadence.StringType),
cadence.NewField("gasConsumed", cadence.UInt64Type),
cadence.NewField("contractAddress", cadence.StringType),
cadence.NewField("logs", cadenceArrayTypeOfUInt8),
cadence.NewField("blockHeight", cadence.UInt64Type),
cadence.NewField("returnedData", cadenceArrayTypeOfUInt8),
cadence.NewField("precompiledCalls", cadenceArrayTypeOfUInt8),
}

func (p *transactionEvent) ToCadence(location common.Location) (cadence.Event, error) {
func (p *transactionEvent) ToCadence(chainID flow.ChainID) (cadence.Event, error) {
var encodedLogs []byte
var err error
if len(p.Result.Logs) > 0 {
Expand All @@ -94,74 +80,53 @@ func (p *transactionEvent) ToCadence(location common.Location) (cadence.Event, e
errorMsg = p.Result.ValidationError.Error()
}

eventType := cadence.NewEventType(
location,
string(EventTypeTransactionExecuted),
transactionEventFields,
nil,
)
eventType := stdlib.CadenceTypesForChain(chainID).TransactionExecuted

return cadence.NewEvent([]cadence.Value{
HashToCadenceArrayValue(p.Result.TxHash),
hashToCadenceArrayValue(p.Result.TxHash),
cadence.NewUInt16(p.Result.Index),
cadence.NewUInt8(p.Result.TxType),
BytesToCadenceUInt8ArrayValue(p.Payload),
bytesToCadenceUInt8ArrayValue(p.Payload),
cadence.NewUInt16(uint16(p.Result.ResultSummary().ErrorCode)),
cadence.String(errorMsg),
cadence.NewUInt64(p.Result.GasConsumed),
deployedAddress,
BytesToCadenceUInt8ArrayValue(encodedLogs),
bytesToCadenceUInt8ArrayValue(encodedLogs),
cadence.NewUInt64(p.BlockHeight),
BytesToCadenceUInt8ArrayValue(p.Result.ReturnedData),
BytesToCadenceUInt8ArrayValue(p.Result.PrecompiledCalls),
bytesToCadenceUInt8ArrayValue(p.Result.ReturnedData),
bytesToCadenceUInt8ArrayValue(p.Result.PrecompiledCalls),
}).WithType(eventType), nil
}

type blockEvent struct {
*Block
*types.Block
}

// NewBlockEvent creates a new block event with the given block as payload.
func NewBlockEvent(block *Block) *Event {
func NewBlockEvent(block *types.Block) *Event {
return &Event{
Etype: EventTypeBlockExecuted,
Payload: &blockEvent{block},
}
}

var blockEventFields = []cadence.Field{
cadence.NewField("height", cadence.UInt64Type),
cadence.NewField("hash", cadenceHashType),
cadence.NewField("timestamp", cadence.UInt64Type),
cadence.NewField("totalSupply", cadence.IntType),
cadence.NewField("totalGasUsed", cadence.UInt64Type),
cadence.NewField("parentHash", cadenceHashType),
cadence.NewField("receiptRoot", cadenceHashType),
cadence.NewField("transactionHashRoot", cadenceHashType),
}

func (p *blockEvent) ToCadence(location common.Location) (cadence.Event, error) {
func (p *blockEvent) ToCadence(chainID flow.ChainID) (cadence.Event, error) {
blockHash, err := p.Hash()
if err != nil {
return cadence.Event{}, err
}

eventType := cadence.NewEventType(
location,
string(EventTypeBlockExecuted),
blockEventFields,
nil,
)
eventType := stdlib.CadenceTypesForChain(chainID).BlockExecuted

return cadence.NewEvent([]cadence.Value{
cadence.NewUInt64(p.Height),
HashToCadenceArrayValue(blockHash),
hashToCadenceArrayValue(blockHash),
cadence.NewUInt64(p.Timestamp),
cadence.NewIntFromBig(p.TotalSupply),
cadence.NewUInt64(p.TotalGasUsed),
HashToCadenceArrayValue(p.ParentBlockHash),
HashToCadenceArrayValue(p.ReceiptRoot),
HashToCadenceArrayValue(p.TransactionHashRoot),
hashToCadenceArrayValue(p.ParentBlockHash),
hashToCadenceArrayValue(p.ReceiptRoot),
hashToCadenceArrayValue(p.TransactionHashRoot),
}).WithType(eventType), nil
}

Expand Down
48 changes: 22 additions & 26 deletions fvm/evm/types/events_test.go → fvm/evm/events/events_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package types_test
package events_test

import (
"encoding/hex"
"fmt"
"math/big"
"testing"

"github.com/onflow/flow-go/fvm/evm/events"
"github.com/onflow/flow-go/fvm/systemcontracts"
"github.com/onflow/flow-go/model/flow"

Expand All @@ -23,12 +23,6 @@ import (
"github.com/onflow/flow-go/fvm/evm/types"
)

var evmLocation = cdcCommon.NewAddressLocation(
nil,
cdcCommon.Address(systemcontracts.SystemContractsForChain(flow.Emulator).EVMContract.Address),
"EVM",
)

func TestEVMBlockExecutedEventCCFEncodingDecoding(t *testing.T) {
t.Parallel()

Expand All @@ -42,11 +36,11 @@ func TestEVMBlockExecutedEventCCFEncodingDecoding(t *testing.T) {
TransactionHashRoot: gethCommon.HexToHash("0x70b67ce6710355acf8d69b2ea013d34e212bc4824926c5d26f189c1ca9667246"),
}

event := types.NewBlockEvent(block)
ev, err := event.Payload.ToCadence(evmLocation)
event := events.NewBlockEvent(block)
ev, err := event.Payload.ToCadence(flow.Emulator)
require.NoError(t, err)

bep, err := types.DecodeBlockEventPayload(ev)
bep, err := events.DecodeBlockEventPayload(ev)
require.NoError(t, err)

assert.Equal(t, bep.Height, block.Height)
Expand Down Expand Up @@ -75,7 +69,7 @@ func TestEVMBlockExecutedEventCCFEncodingDecoding(t *testing.T) {
cdcCommon.NewAddressLocation(
nil,
cdcCommon.Address(sc.EVMContract.Address),
string(types.EventTypeBlockExecuted),
string(events.EventTypeBlockExecuted),
).ID(),
evt.Type().ID(),
)
Expand Down Expand Up @@ -118,11 +112,11 @@ func TestEVMTransactionExecutedEventCCFEncodingDecoding(t *testing.T) {
}

t.Run("evm.TransactionExecuted with failed status", func(t *testing.T) {
event := types.NewTransactionEvent(txResult, txBytes, blockHeight)
ev, err := event.Payload.ToCadence(evmLocation)
event := events.NewTransactionEvent(txResult, txBytes, blockHeight)
ev, err := event.Payload.ToCadence(flow.Emulator)
require.NoError(t, err)

tep, err := types.DecodeTransactionEventPayload(ev)
tep, err := events.DecodeTransactionEventPayload(ev)
require.NoError(t, err)

assert.Equal(t, tep.BlockHeight, blockHeight)
Expand Down Expand Up @@ -150,20 +144,21 @@ func TestEVMTransactionExecutedEventCCFEncodingDecoding(t *testing.T) {
evt, err := ccf.Decode(nil, v)
require.NoError(t, err)

assert.Equal(t, evt.Type().ID(), fmt.Sprintf(
"A.%s.EVM.TransactionExecuted",
systemcontracts.SystemContractsForChain(flow.Emulator).EVMContract.Address,
))
location := systemcontracts.SystemContractsForChain(flow.Emulator).EVMContract.Location()
assert.Equal(t,
string(location.TypeID(nil, "EVM.TransactionExecuted")),
evt.Type().ID(),
)
})

t.Run("evm.TransactionExecuted with non-failed status", func(t *testing.T) {
txResult.VMError = nil

event := types.NewTransactionEvent(txResult, txBytes, blockHeight)
ev, err := event.Payload.ToCadence(evmLocation)
event := events.NewTransactionEvent(txResult, txBytes, blockHeight)
ev, err := event.Payload.ToCadence(flow.Emulator)
require.NoError(t, err)

tep, err := types.DecodeTransactionEventPayload(ev)
tep, err := events.DecodeTransactionEventPayload(ev)
require.NoError(t, err)

assert.Equal(t, tep.BlockHeight, blockHeight)
Expand Down Expand Up @@ -192,9 +187,10 @@ func TestEVMTransactionExecutedEventCCFEncodingDecoding(t *testing.T) {
evt, err := ccf.Decode(nil, v)
require.NoError(t, err)

assert.Equal(t, evt.Type().ID(), fmt.Sprintf(
"A.%s.EVM.TransactionExecuted",
systemcontracts.SystemContractsForChain(flow.Emulator).EVMContract.Address,
))
location := systemcontracts.SystemContractsForChain(flow.Emulator).EVMContract.Location()
assert.Equal(t,
string(location.TypeID(nil, "EVM.TransactionExecuted")),
evt.Type().ID(),
)
})
}
32 changes: 32 additions & 0 deletions fvm/evm/events/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package events

import (
"github.com/onflow/cadence"
gethCommon "github.com/onflow/go-ethereum/common"
)

// cadenceArrayTypeOfUInt8 is the Cadence type [UInt8]
var cadenceArrayTypeOfUInt8 = cadence.NewVariableSizedArrayType(cadence.UInt8Type)

// bytesToCadenceUInt8ArrayValue converts bytes into a Cadence array of type UInt8
func bytesToCadenceUInt8ArrayValue(b []byte) cadence.Array {
values := make([]cadence.Value, len(b))
for i, v := range b {
values[i] = cadence.NewUInt8(v)
}
return cadence.NewArray(values).
WithType(cadenceArrayTypeOfUInt8)
}

// cadenceHashType is the Cadence type [UInt8;32]
var cadenceHashType = cadence.NewConstantSizedArrayType(gethCommon.HashLength, cadence.UInt8Type)

// hashToCadenceArrayValue EVM hash ([32]byte) into a Cadence array of type [UInt8;32]
func hashToCadenceArrayValue(hash gethCommon.Hash) cadence.Array {
values := make([]cadence.Value, len(hash))
for i, v := range hash {
values[i] = cadence.NewUInt8(v)
}
return cadence.NewArray(values).
WithType(cadenceHashType)
}
74 changes: 74 additions & 0 deletions fvm/evm/events/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package events

import (
"testing"

"github.com/onflow/cadence"
gethCommon "github.com/onflow/go-ethereum/common"
"github.com/stretchr/testify/require"
)

func TestBytesToCadenceUInt8ArrayValue(t *testing.T) {
t.Parallel()

input := []byte{1, 2, 3, 4, 5}

inCadence := bytesToCadenceUInt8ArrayValue(input)

require.Equal(t,
cadence.NewArray([]cadence.Value{
cadence.UInt8(1),
cadence.UInt8(2),
cadence.UInt8(3),
cadence.UInt8(4),
cadence.UInt8(5),
}).WithType(cadenceArrayTypeOfUInt8),
inCadence,
)
}

func TestHashToCadenceArrayValue(t *testing.T) {
t.Parallel()

input := gethCommon.Hash{1, 2, 3, 4, 5}

inCadence := hashToCadenceArrayValue(input)

require.Equal(t,
cadence.NewArray([]cadence.Value{
cadence.UInt8(1),
cadence.UInt8(2),
cadence.UInt8(3),
cadence.UInt8(4),
cadence.UInt8(5),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
cadence.UInt8(0),
}).WithType(cadenceHashType),
inCadence,
)
}
Loading

0 comments on commit d2dfdb8

Please sign in to comment.