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

[EVM] Add EVM type ID #5091

Merged
merged 14 commits into from
Jan 22, 2024
143 changes: 105 additions & 38 deletions fvm/evm/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ package types

import (
"encoding/hex"
"encoding/json"
"fmt"
"strings"

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

"github.com/onflow/flow-go/model/flow"
)

const (
EventTypeBlockExecuted flow.EventType = "evm.BlockExecuted"
EventTypeTransactionExecuted flow.EventType = "evm.TransactionExecuted"
EventTypeBlockExecuted flow.EventType = "BlockExecuted"
EventTypeTransactionExecuted flow.EventType = "TransactionExecuted"
evmLocationPrefix = "evm"
locationDivider = "."
)

type EventPayload interface {
Expand All @@ -26,6 +31,72 @@ type Event struct {
Payload EventPayload
}

var _ common.Location = EVMLocation{}

type EVMLocation struct{}

func (l EVMLocation) TypeID(memoryGauge common.MemoryGauge, qualifiedIdentifier string) common.TypeID {
id := fmt.Sprintf("%s%s%s", evmLocationPrefix, locationDivider, qualifiedIdentifier)
common.UseMemory(memoryGauge, common.NewRawStringMemoryUsage(len(id)))

return common.TypeID(id)
}

func (l EVMLocation) QualifiedIdentifier(typeID common.TypeID) string {
pieces := strings.SplitN(string(typeID), locationDivider, 2)

if len(pieces) < 2 {
return ""
}

return pieces[1]
}

func (l EVMLocation) String() string {
return evmLocationPrefix
}

func (l EVMLocation) Description() string {
return evmLocationPrefix
}

func (l EVMLocation) ID() string {
return evmLocationPrefix
}

func (l EVMLocation) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Type string
}{
Type: "EVMLocation",
})
}

func init() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't like using init but not sure if there's a better way of doing this without exporting types.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like these are usually in init... Not sure if there is another way.

common.RegisterTypeIDDecoder(
evmLocationPrefix,
func(_ common.MemoryGauge, typeID string) (common.Location, string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need a test for this part specifically?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is covered in the test I wrote, however edge cases with errors are not.

if typeID == "" {
return nil, "", fmt.Errorf("invalid EVM type location ID: missing type prefix")
}

parts := strings.SplitN(typeID, ".", 2)
prefix := parts[0]
if prefix != evmLocationPrefix {
return EVMLocation{}, "", fmt.Errorf("invalid EVM type location ID: invalid prefix")
}

var qualifiedIdentifier string
pieceCount := len(parts)
if pieceCount > 1 {
qualifiedIdentifier = parts[1]
}

return EVMLocation{}, qualifiedIdentifier, nil
},
)
}

// we might break this event into two (tx included /tx executed) if size becomes an issue
type TransactionExecutedPayload struct {
BlockHeight uint64
Expand All @@ -34,24 +105,6 @@ type TransactionExecutedPayload struct {
Result *Result
}

var transactionExecutedEventCadenceType = &cadence.EventType{
Location: stdlib.FlowLocation{},
QualifiedIdentifier: string(EventTypeTransactionExecuted),
Fields: []cadence.Field{
cadence.NewField("blockHeight", cadence.UInt64Type{}),
cadence.NewField("transactionHash", cadence.StringType{}),
cadence.NewField("transaction", cadence.StringType{}),
cadence.NewField("failed", cadence.BoolType{}),
cadence.NewField("transactionType", cadence.UInt8Type{}),
cadence.NewField("gasConsumed", cadence.UInt64Type{}),
cadence.NewField("deployedContractAddress", cadence.StringType{}),
cadence.NewField("returnedValue", cadence.StringType{}),
cadence.NewField("logs", cadence.StringType{}),
},
}

// todo add decoder for events from cadence to evm payload

func (p *TransactionExecutedPayload) CadenceEvent() (cadence.Event, error) {
var encodedLogs []byte
var err error
Expand All @@ -62,21 +115,35 @@ func (p *TransactionExecutedPayload) CadenceEvent() (cadence.Event, error) {
}
}

fields := []cadence.Value{
cadence.NewUInt64(p.BlockHeight),
cadence.String(p.TxHash.String()),
cadence.String(hex.EncodeToString(p.TxEncoded)),
cadence.NewBool(p.Result.Failed),
cadence.NewUInt8(p.Result.TxType),
cadence.NewUInt64(p.Result.GasConsumed),
cadence.String(hex.EncodeToString(p.Result.DeployedContractAddress.Bytes())),
cadence.String(hex.EncodeToString(p.Result.ReturnedValue)),
cadence.String(hex.EncodeToString(encodedLogs)),
}

return cadence.
NewEvent(fields).
WithType(transactionExecutedEventCadenceType), nil
return cadence.Event{
EventType: cadence.NewEventType(
EVMLocation{},
string(EventTypeTransactionExecuted),
[]cadence.Field{
cadence.NewField("blockHeight", cadence.UInt64Type{}),
cadence.NewField("transactionHash", cadence.StringType{}),
cadence.NewField("transaction", cadence.StringType{}),
cadence.NewField("failed", cadence.BoolType{}),
cadence.NewField("transactionType", cadence.UInt8Type{}),
cadence.NewField("gasConsumed", cadence.UInt64Type{}),
cadence.NewField("deployedContractAddress", cadence.StringType{}),
cadence.NewField("returnedValue", cadence.StringType{}),
cadence.NewField("logs", cadence.StringType{}),
},
nil,
),
Fields: []cadence.Value{
cadence.NewUInt64(p.BlockHeight),
cadence.String(p.TxHash.String()),
cadence.String(hex.EncodeToString(p.TxEncoded)),
cadence.NewBool(p.Result.Failed),
cadence.NewUInt8(p.Result.TxType),
cadence.NewUInt64(p.Result.GasConsumed),
cadence.String(hex.EncodeToString(p.Result.DeployedContractAddress.Bytes())),
cadence.String(hex.EncodeToString(p.Result.ReturnedValue)),
cadence.String(hex.EncodeToString(encodedLogs)),
},
}, nil
}

func NewTransactionExecutedEvent(
Expand All @@ -97,7 +164,7 @@ func NewTransactionExecutedEvent(
}

var blockExecutedEventCadenceType = &cadence.EventType{
Location: stdlib.FlowLocation{}, // todo create evm custom location
Location: EVMLocation{},
QualifiedIdentifier: string(EventTypeBlockExecuted),
Fields: []cadence.Field{
cadence.NewField("height", cadence.UInt64Type{}),
Expand All @@ -124,8 +191,8 @@ func (p *BlockExecutedEventPayload) CadenceEvent() (cadence.Event, error) {
fields := []cadence.Value{
cadence.NewUInt64(p.Block.Height),
cadence.NewUInt64(p.Block.TotalSupply),
cadence.String(p.Block.ReceiptRoot.String()),
cadence.String(p.Block.ParentBlockHash.String()),
cadence.String(p.Block.ReceiptRoot.String()),
cadence.NewArray(hashes).WithType(cadence.NewVariableSizedArrayType(cadence.StringType{})),
}

Expand Down
56 changes: 56 additions & 0 deletions fvm/fvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3156,4 +3156,60 @@ func TestEVM(t *testing.T) {
}
}),
)

t.Run("deploy contract code", newVMTest().
withBootstrapProcedureOptions(fvm.WithSetupEVMEnabled(true)).
run(func(
t *testing.T,
vm fvm.VM,
chain flow.Chain,
ctx fvm.Context,
snapshotTree snapshot.SnapshotTree,
) {
sc := systemcontracts.SystemContractsForChain(chain.ChainID())

txBody := flow.NewTransactionBody().
SetScript([]byte(fmt.Sprintf(`
import FungibleToken from %s
import FlowToken from %s
import EVM from %s

transaction() {
prepare(acc: AuthAccount) {
let vaultRef = acc.borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)
?? panic("Could not borrow reference to the owner's Vault!")

let acc <- EVM.createBridgedAccount()
let amount <- vaultRef.withdraw(amount: 0.0000001) as! @FlowToken.Vault
acc.deposit(from: <- amount)
destroy acc
}
}`,
sc.FungibleToken.Address.HexWithPrefix(),
sc.FlowToken.Address.HexWithPrefix(),
sc.FlowServiceAccount.Address.HexWithPrefix(), // TODO this should be sc.EVM.Address not found there???
))).
SetProposalKey(chain.ServiceAddress(), 0, 0).
AddAuthorizer(chain.ServiceAddress()).
SetPayer(chain.ServiceAddress())

err := testutil.SignTransactionAsServiceAccount(txBody, 0, chain)
require.NoError(t, err)

ctx = fvm.NewContextFromParent(ctx, fvm.WithEVMEnabled(true))
_, output, err := vm.Run(
ctx,
fvm.Transaction(txBody, 0),
snapshotTree)

require.NoError(t, err)
require.NoError(t, output.Err)
require.Len(t, output.Events, 3)

evmLocation := types.EVMLocation{}
txExe, blockExe := output.Events[1], output.Events[2]
assert.Equal(t, evmLocation.TypeID(nil, string(types.EventTypeTransactionExecuted)), common.TypeID(txExe.Type))
assert.Equal(t, evmLocation.TypeID(nil, string(types.EventTypeBlockExecuted)), common.TypeID(blockExe.Type))
}),
)
}
Loading