diff --git a/test/core_events/core_events.cdc b/test/core_events/core_events.cdc new file mode 100644 index 00000000..dca0f4f5 --- /dev/null +++ b/test/core_events/core_events.cdc @@ -0,0 +1,31 @@ +access(all) +contract CoreEvents { + + access(all) + event AccountCreated(address: Address) + + access(all) + event BlockExecuted( + height: UInt64, + hash: String, + totalSupply: Int, + parentHash: String, + receiptRoot: String, + transactionHashes: [String] + ) + + access(all) + event TransactionExecuted( + blockHeight: UInt64, + blockHash: String, + transactionHash: String, + encodedTransaction: String, + failed: Bool, + vmError: String, + transactionType: UInt8, + gasConsumed: UInt64, + deployedContractAddress: String, + returnedValue: String, + logs: String + ) +} diff --git a/test/core_events/core_events.go b/test/core_events/core_events.go new file mode 100644 index 00000000..02271e49 --- /dev/null +++ b/test/core_events/core_events.go @@ -0,0 +1,92 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package core_events + +import ( + _ "embed" + "fmt" + + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/parser" + "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" +) + +//go:embed core_events.cdc +var CoreEvents []byte + +const CoreEventsLocation = common.IdentifierLocation("CoreEvents") + +func CoreEventsChecker() *sema.Checker { + program, err := parser.ParseProgram( + nil, + CoreEvents, + parser.Config{}, + ) + if err != nil { + panic(err) + } + + importHandler := func( + checker *sema.Checker, + importedLocation common.Location, + importRange ast.Range, + ) (sema.Import, error) { + var elaboration *sema.Elaboration + switch importedLocation { + case stdlib.TestContractLocation: + testChecker := stdlib.GetTestContractType().Checker + elaboration = testChecker.Elaboration + default: + return nil, fmt.Errorf("import not supported") + } + + return sema.ElaborationImport{ + Elaboration: elaboration, + }, nil + } + + activation := sema.NewVariableActivation(sema.BaseValueActivation) + activation.DeclareValue(stdlib.AssertFunction) + activation.DeclareValue(stdlib.PanicFunction) + + checker, err := sema.NewChecker( + program, + CoreEventsLocation, + nil, + &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return activation + }, + AccessCheckMode: sema.AccessCheckModeStrict, + ImportHandler: importHandler, + }, + ) + if err != nil { + panic(err) + } + + err = checker.Check() + if err != nil { + panic(err) + } + + return checker +} diff --git a/test/core_events/core_events_test.go b/test/core_events/core_events_test.go new file mode 100644 index 00000000..b44151ab --- /dev/null +++ b/test/core_events/core_events_test.go @@ -0,0 +1,33 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package core_events + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCoreEventsChecker(t *testing.T) { + t.Parallel() + + checker := CoreEventsChecker() + err := checker.Check() + assert.NoError(t, err) +} diff --git a/test/emulator_backend.go b/test/emulator_backend.go index 1f2718d9..380468da 100644 --- a/test/emulator_backend.go +++ b/test/emulator_backend.go @@ -537,6 +537,13 @@ func (e *EmulatorBackend) Events( eventTypeString = "" case *interpreter.CompositeStaticType: eventTypeString = eventType.String() + if eventTypeString == "I.CoreEvents.CoreEvents.BlockExecuted" { + eventTypeString = "evm.BlockExecuted" + } else if eventTypeString == "I.CoreEvents.CoreEvents.TransactionExecuted" { + eventTypeString = "evm.TransactionExecuted" + } else if eventTypeString == "I.CoreEvents.CoreEvents.AccountCreated" { + eventTypeString = "flow.AccountCreated" + } default: panic(errors.NewUnreachableError()) } diff --git a/test/go.mod b/test/go.mod index 3a7da424..39e89bb7 100644 --- a/test/go.mod +++ b/test/go.mod @@ -192,3 +192,5 @@ require ( modernc.org/sqlite v1.28.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) + +replace github.com/onflow/flow-go => ../../flow-go diff --git a/test/go.sum b/test/go.sum index f71ab4b2..5ee2ed8d 100644 --- a/test/go.sum +++ b/test/go.sum @@ -1827,8 +1827,6 @@ github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20240327162334-bd133114f87a github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20240327162334-bd133114f87a/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= github.com/onflow/flow-ft/lib/go/templates v0.7.1-0.20240327162334-bd133114f87a h1:oe3ErYY8qds7IER/zmNGKT3zz6cFcjC0doX2Q0WOUv0= github.com/onflow/flow-ft/lib/go/templates v0.7.1-0.20240327162334-bd133114f87a/go.mod h1:uQ8XFqmMK2jxyBSVrmyuwdWjTEb+6zGjRYotfDJ5pAE= -github.com/onflow/flow-go v0.34.0-crescendo-preview.8.0.20240328003708-11040f76d0cd h1:QWLgb4okWnmZHN2ebiGUNR1CNdHICE1CxVx7wQY9Q40= -github.com/onflow/flow-go v0.34.0-crescendo-preview.8.0.20240328003708-11040f76d0cd/go.mod h1:vaUovXWZxbcxCg+wFAvb5SFouo4Q2OI3fj9XwCpbU6M= github.com/onflow/flow-go-sdk v1.0.0-M1/go.mod h1:TDW0MNuCs4SvqYRUzkbRnRmHQL1h4X8wURsCw9P9beo= github.com/onflow/flow-go-sdk v1.0.0-preview.16 h1:m5Dj5XLUTHIFgjWPDsapaIFKIheBlhFLwZ9aXxwX6hQ= github.com/onflow/flow-go-sdk v1.0.0-preview.16/go.mod h1:zQwbb+mHfV7R+xa03xr6RoU13bZOF2uKgdf7dOGELvc= diff --git a/test/test_framework_test.go b/test/test_framework_test.go index c2e34ffc..b0dfdda1 100644 --- a/test/test_framework_test.go +++ b/test/test_framework_test.go @@ -22,7 +22,9 @@ import ( "bytes" "errors" "fmt" + "os" "testing" + "time" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" @@ -1352,14 +1354,21 @@ func TestCreateAccount(t *testing.T) { const code = ` import Test + import CoreEvents access(all) fun test() { let account = Test.createAccount() - let typ = CompositeType("flow.AccountCreated")! + let typ = Type() let events = Test.eventsOfType(typ) Test.expect(events.length, Test.beGreaterThan(1)) + + let accountCreatedEvent = events[0] as! CoreEvents.AccountCreated + Test.assertEqual( + Address(0x0000000000000006), + accountCreatedEvent.address + ) } ` @@ -4235,6 +4244,7 @@ func TestCoverageReportForUnitTests(t *testing.T) { "I.Test", "I.Crypto", "I.BlockchainHelpers", + "I.CoreEvents", "s.7465737400000000000000000000000000000000000000000000000000000000", "A.0000000000000002.FungibleTokenSwitchboard", "A.0000000000000001.Burner", @@ -4455,6 +4465,7 @@ func TestCoverageReportForIntegrationTests(t *testing.T) { "I.Crypto", "I.Test", "I.BlockchainHelpers", + "I.CoreEvents", "A.0000000000000001.ExampleNFT", "A.0000000000000001.MetadataViews", "A.0000000000000001.NonFungibleToken", @@ -5932,3 +5943,115 @@ func TestGetTests(t *testing.T) { assert.ElementsMatch(t, []string{"test1", "test2", "test3"}, tests) } + +func TestEVMContractEvents(t *testing.T) { + t.Parallel() + + const testCode = ` + import Test + import "EVM" + import CoreEvents + + access(all) + let account = Test.createAccount() + + access(all) + fun testEVMContractEvents() { + let code = Test.readFile("../transactions/test_transaction.cdc") + let tx = Test.Transaction( + code: code, + authorizers: [account.address], + signers: [account], + arguments: [] + ) + + let result = Test.executeTransaction(tx) + Test.expect(result, Test.beSucceeded()) + + let blockEventType = Type() + let blockEvents = Test.eventsOfType(blockEventType) + Test.assertEqual(1, blockEvents.length) + + let blockEvent = blockEvents[0] as! CoreEvents.BlockExecuted + Test.assertEqual(UInt64(1), blockEvent.height) + Test.assertEqual( + 66, + blockEvent.hash.length + ) + Test.assertEqual(0, blockEvent.totalSupply) + Test.assertEqual( + "0x716d10fd2eb72b01e876580d348d8a9069e4a3395ab7cc27196971f04e9280f4", + blockEvent.parentHash + ) + Test.assertEqual( + "0x0000000000000000000000000000000000000000000000000000000000000000", + blockEvent.receiptRoot + ) + Test.assertEqual( + 1, + blockEvent.transactionHashes.length + ) + + let txEventType = Type() + let txEvents = Test.eventsOfType(txEventType) + Test.assertEqual(1, txEvents.length) + + let txEvent = txEvents[0] as! CoreEvents.TransactionExecuted + + Test.assertEqual(UInt64(1), txEvent.blockHeight) + Test.assertEqual(66, txEvent.blockHash.length) + Test.assertEqual(66, txEvent.transactionHash.length) + Test.assertEqual(7200, txEvent.encodedTransaction.length) + Test.assertEqual(false, txEvent.failed) + Test.assertEqual("", txEvent.vmError) + Test.assertEqual(UInt8(255), txEvent.transactionType) + Test.assertEqual(UInt64(702600), txEvent.gasConsumed) + Test.assertEqual(42, txEvent.deployedContractAddress.length) + Test.assertEqual("", txEvent.returnedValue) + Test.assertEqual("", txEvent.logs) + } + ` + + const testTransactionCode = ` + import "EVM" + + transaction { + prepare(signer: auth(SaveValue) &Account) { + let coa <- EVM.createCadenceOwnedAccount() + let address = coa.address() + + assert(address.bytes != [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + signer.storage.save<@EVM.CadenceOwnedAccount>( + <-coa, + to: StoragePath(identifier: "evm")! + ) + } + } + ` + + fileResolver := func(path string) (string, error) { + switch path { + case "../transactions/test_transaction.cdc": + return testTransactionCode, nil + default: + return "", fmt.Errorf("cannot find file path: %s", path) + } + } + + importResolver := func(location common.Location) (string, error) { + return "", fmt.Errorf("cannot find import location: %s", location) + } + + output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339} + log := zerolog.New(output).With().Timestamp().Logger() + runner := NewTestRunner(). + WithFileResolver(fileResolver). + WithImportResolver(importResolver). + WithLogger(log) + + results, err := runner.RunTests(testCode) + require.NoError(t, err) + for _, result := range results { + require.NoError(t, result.Error) + } +} diff --git a/test/test_runner.go b/test/test_runner.go index 77255437..f4fa0499 100644 --- a/test/test_runner.go +++ b/test/test_runner.go @@ -27,6 +27,8 @@ import ( "github.com/rs/zerolog" "github.com/onflow/atree" + "github.com/onflow/flow-go/fvm/evm" + "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/model/flow" "github.com/onflow/cadence/runtime" @@ -38,6 +40,7 @@ import ( "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" + "github.com/onflow/cadence-tools/test/core_events" "github.com/onflow/cadence-tools/test/helpers" ) @@ -474,8 +477,9 @@ func (r *TestRunner) initializeEnvironment() ( backend.contracts = r.contracts r.backend = backend + fvmEnv := r.backend.blockchain.NewScriptEnvironment() ctx := runtime.Context{ - Interface: r.backend.blockchain.NewScriptEnvironment(), + Interface: fvmEnv, Location: testScriptLocation, Environment: env, } @@ -483,6 +487,7 @@ func (r *TestRunner) initializeEnvironment() ( r.coverageReport.ExcludeLocation(stdlib.CryptoCheckerLocation) r.coverageReport.ExcludeLocation(stdlib.TestContractLocation) r.coverageReport.ExcludeLocation(helpers.BlockchainHelpersLocation) + r.coverageReport.ExcludeLocation(core_events.CoreEventsLocation) r.coverageReport.ExcludeLocation(testScriptLocation) ctx.CoverageReport = r.coverageReport } @@ -499,6 +504,25 @@ func (r *TestRunner) initializeEnvironment() ( // returned from blockchain to the test script) env.InterpreterConfig.ContractValueHandler = r.interpreterContractValueHandler(env) + stdlibHandler := env.InterpreterConfig.CompositeTypeHandler + testHandler := func(location common.Location, typeID common.TypeID) *sema.CompositeType { + if typeID == "evm.BlockExecuted" { + fmt.Println("Event Types: ", EVMEventTypes) + typeID := common.TypeID("I.CoreEvents.CoreEvents.BlockExecuted") + fmt.Println("TypeID: ", typeID) + fmt.Println("Returning: ", EVMEventTypes[typeID]) + return EVMEventTypes[typeID] + } else if typeID == "evm.TransactionExecuted" { + typeID := common.TypeID("I.CoreEvents.CoreEvents.TransactionExecuted") + return EVMEventTypes[typeID] + } else if typeID == "flow.AccountCreated" { + typeID := common.TypeID("I.CoreEvents.CoreEvents.AccountCreated") + return EVMEventTypes[typeID] + } + return stdlibHandler(location, typeID) + } + env.InterpreterConfig.CompositeTypeHandler = testHandler + env.Configure( ctx.Interface, runtime.NewCodesAndPrograms(), @@ -506,9 +530,43 @@ func (r *TestRunner) initializeEnvironment() ( r.coverageReport, ) + chain := flow.MonotonicEmulator.Chain() + sc := systemcontracts.SystemContractsForChain(chain.ChainID()) + err := evm.SetupEnvironment( + chain.ChainID(), + fvmEnv, + env, + chain.ServiceAddress(), + sc.FlowToken.Address, + ) + if err != nil { + panic(err) + } + return env, ctx } +var EVMEventTypes = map[common.TypeID]*sema.CompositeType{} + +func newEVMEventType(identifier string) *sema.CompositeType { + eventType := &sema.CompositeType{ + Kind: common.CompositeKindEvent, + Location: core_events.CoreEventsLocation, + Identifier: identifier, + Fields: []string{}, + Members: &sema.StringMemberOrderedMap{}, + } + EVMEventTypes[eventType.ID()] = eventType + + return eventType +} + +var BlockExecutedEventType = newEVMEventType("CoreEvents.BlockExecuted") + +var TransactionExecutedEventType = newEVMEventType("CoreEvents.TransactionExecuted") + +var AccountCreatedEventType = newEVMEventType("CoreEvents.AccountCreated") + func (r *TestRunner) checkerImportHandler(ctx runtime.Context) sema.ImportHandlerFunc { return func( checker *sema.Checker, @@ -529,6 +587,10 @@ func (r *TestRunner) checkerImportHandler(ctx runtime.Context) sema.ImportHandle helpersChecker := helpers.BlockchainHelpersChecker() elaboration = helpersChecker.Elaboration + case core_events.CoreEventsLocation: + coreEventsChecker := core_events.CoreEventsChecker() + elaboration = coreEventsChecker.Elaboration + default: _, importedElaboration, err := r.parseAndCheckImport(importedLocation, ctx) if err != nil { @@ -685,6 +747,10 @@ func (r *TestRunner) interpreterImportHandler(ctx runtime.Context) interpreter.I helpersChecker := helpers.BlockchainHelpersChecker() program = interpreter.ProgramFromChecker(helpersChecker) + case core_events.CoreEventsLocation: + coreEventsChecker := core_events.CoreEventsChecker() + program = interpreter.ProgramFromChecker(coreEventsChecker) + default: importedProgram, importedElaboration, err := r.parseAndCheckImport(location, ctx) if err != nil { @@ -786,6 +852,19 @@ func (r *TestRunner) parseAndCheckImport( env.CheckerConfig.ContractValueHandler = contractValueHandler + chain := flow.MonotonicEmulator.Chain() + sc := systemcontracts.SystemContractsForChain(chain.ChainID()) + err = evm.SetupEnvironment( + chain.ChainID(), + r.backend.blockchain.NewScriptEnvironment(), + env, + chain.ServiceAddress(), + sc.FlowToken.Address, + ) + if err != nil { + panic(err) + } + code = r.replaceImports(code) program, err := r.testRuntime.ParseAndCheckProgram([]byte(code), ctx)