diff --git a/engine/execution/computation/computer/computer_test.go b/engine/execution/computation/computer/computer_test.go index c0ba76bfac0..72a4c54d4c4 100644 --- a/engine/execution/computation/computer/computer_test.go +++ b/engine/execution/computation/computer/computer_test.go @@ -378,6 +378,7 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { // include all fees. System chunk should ignore them contextOptions := []fvm.Option{ + fvm.WithEVMEnabled(true), fvm.WithTransactionFeesEnabled(true), fvm.WithAccountStorageLimit(true), fvm.WithBlocks(&environment.NoopBlockFinder{}), @@ -1223,6 +1224,7 @@ func (f *FixedAddressGenerator) AddressCount() uint64 { func Test_ExecutingSystemCollection(t *testing.T) { execCtx := fvm.NewContext( + fvm.WithEVMEnabled(true), fvm.WithChain(flow.Localnet.Chain()), fvm.WithBlocks(&environment.NoopBlockFinder{}), ) @@ -1245,8 +1247,8 @@ func Test_ExecutingSystemCollection(t *testing.T) { noopCollector := metrics.NewNoopCollector() - expectedNumberOfEvents := 3 - expectedEventSize := 1497 + expectedNumberOfEvents := 4 + expectedEventSize := 1961 // bootstrapping does not cache programs expectedCachedPrograms := 0 diff --git a/engine/execution/computation/programs_test.go b/engine/execution/computation/programs_test.go index f918a378ae1..75467f4054d 100644 --- a/engine/execution/computation/programs_test.go +++ b/engine/execution/computation/programs_test.go @@ -207,6 +207,7 @@ func TestPrograms_TestBlockForks(t *testing.T) { chain := flow.Emulator.Chain() vm := fvm.NewVirtualMachine() execCtx := fvm.NewContext( + fvm.WithEVMEnabled(true), fvm.WithBlockHeader(block.Header), fvm.WithBlocks(blockProvider{map[uint64]*flow.Block{0: &block}}), fvm.WithChain(chain)) diff --git a/engine/execution/state/bootstrap/bootstrap_test.go b/engine/execution/state/bootstrap/bootstrap_test.go index 9cd60681595..c90afc7673f 100644 --- a/engine/execution/state/bootstrap/bootstrap_test.go +++ b/engine/execution/state/bootstrap/bootstrap_test.go @@ -53,7 +53,7 @@ func TestBootstrapLedger(t *testing.T) { } func TestBootstrapLedger_ZeroTokenSupply(t *testing.T) { - expectedStateCommitmentBytes, _ := hex.DecodeString("48460c42a43f700562d6c17408af8b52823774cc24fe795148c8e37b62f77615") + expectedStateCommitmentBytes, _ := hex.DecodeString("a738c835be7196f62900595d186557ba56d5ee4221b0a108782967c5d1d110d7") expectedStateCommitment, err := flow.ToStateCommitment(expectedStateCommitmentBytes) require.NoError(t, err) diff --git a/fvm/blueprints/scripts/systemChunkTransactionTemplate.cdc b/fvm/blueprints/scripts/systemChunkTransactionTemplate.cdc index 222f6926ab2..7c9564e1486 100644 --- a/fvm/blueprints/scripts/systemChunkTransactionTemplate.cdc +++ b/fvm/blueprints/scripts/systemChunkTransactionTemplate.cdc @@ -1,6 +1,7 @@ import FlowEpoch from "FlowEpoch" import NodeVersionBeacon from "NodeVersionBeacon" import RandomBeaconHistory from "RandomBeaconHistory" +import EVM from "EVM" transaction { prepare(serviceAccount: auth(BorrowValue) &Account) { @@ -17,5 +18,10 @@ transaction { .borrow<&RandomBeaconHistory.Heartbeat>(from: RandomBeaconHistory.HeartbeatStoragePath) ?? panic("Couldn't borrow RandomBeaconHistory.Heartbeat Resource") randomBeaconHistoryHeartbeat.heartbeat(randomSourceHistory: randomSourceHistory()) + + let evmHeartbeat = serviceAccount.storage.borrow<&EVM.Heartbeat>(from: /storage/EVMHeartbeat) + if evmHeartbeat != nil { // skip if not available + evmHeartbeat!.heartbeat() + } } } diff --git a/fvm/blueprints/system.go b/fvm/blueprints/system.go index 1dff14a0824..466f8ee47f9 100644 --- a/fvm/blueprints/system.go +++ b/fvm/blueprints/system.go @@ -2,6 +2,7 @@ package blueprints import ( _ "embed" + "strings" "github.com/onflow/flow-core-contracts/lib/go/templates" @@ -17,17 +18,31 @@ const SystemChunkTransactionGasLimit = 100_000_000 //go:embed scripts/systemChunkTransactionTemplate.cdc var systemChunkTransactionTemplate string +// TODO: when the EVM contract is moved to the flow-core-contracts, we can +// just directly use the replace address functionality of the templates package. + +var placeholderEVMAddress = "\"EVM\"" + +func prepareSystemContractCode(chainID flow.ChainID) string { + sc := systemcontracts.SystemContractsForChain(chainID) + code := templates.ReplaceAddresses( + systemChunkTransactionTemplate, + sc.AsTemplateEnv(), + ) + code = strings.ReplaceAll( + code, + placeholderEVMAddress, + sc.EVMContract.Address.HexWithPrefix(), + ) + return code +} + // SystemChunkTransaction creates and returns the transaction corresponding to the // system chunk for the given chain. func SystemChunkTransaction(chain flow.Chain) (*flow.TransactionBody, error) { - contracts := systemcontracts.SystemContractsForChain(chain.ChainID()) - tx := flow.NewTransactionBody(). SetScript( - []byte(templates.ReplaceAddresses( - systemChunkTransactionTemplate, - contracts.AsTemplateEnv(), - )), + []byte(prepareSystemContractCode(chain.ChainID())), ). // The heartbeat resources needed by the system tx have are on the service account, // therefore, the service account is the only authorizer needed. diff --git a/fvm/blueprints/system_test.go b/fvm/blueprints/system_test.go index 6dab3d9f00e..a63ce1c28c9 100644 --- a/fvm/blueprints/system_test.go +++ b/fvm/blueprints/system_test.go @@ -18,10 +18,10 @@ func TestSystemChunkTransactionHash(t *testing.T) { // this is formatted in a way that the resulting error message is easy to copy-paste into the test. expectedHashes := []chainHash{ - {chainId: "flow-mainnet", expectedHash: "0a7ea89ad32d79a30b91b4c1202230a1e29310e1b92e01c76d036d2e3839159b"}, - {chainId: "flow-testnet", expectedHash: "368434cb7c792c3c35647f30aa90aae5798a45efcf2ff6abb7123b70c1e7850c"}, - {chainId: "flow-previewnet", expectedHash: "e90268cb6e8385d9eb50f2956f47c1c5f77a7b3111de2f66756b2a48855e05ce"}, - {chainId: "flow-emulator", expectedHash: "c6ccd6b805adcfaa6f9719f1dc71c831c40712977f12d82332ba23e2cb499475"}, + {chainId: "flow-mainnet", expectedHash: "0e56f890392ad3f2a0dcc7a0e859b6d41f3c17556aa85a0f8831ae25a6537e39"}, + {chainId: "flow-testnet", expectedHash: "69c922177af520e8ecaf0ba97cb174a3ebbd03de6d5b5d1f6b6c043a9638dba3"}, + {chainId: "flow-previewnet", expectedHash: "2ec3b6b7e7d70a651600eb80f775cb57613a0a168c847b70040c469f09066a55"}, + {chainId: "flow-emulator", expectedHash: "7dcc0daaecebc7be33b068ed5c8da0620c89fd896abfc498fc0cc32a261aab1f"}, } var actualHashes []chainHash diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index be8c3a200da..e0866807afc 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -7,8 +7,6 @@ import ( "math/big" "testing" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/encoding/ccf" gethTypes "github.com/onflow/go-ethereum/core/types" gethParams "github.com/onflow/go-ethereum/params" @@ -103,49 +101,25 @@ func TestEVMRun(t *testing.T) { require.NoError(t, err) require.NoError(t, output.Err) require.NotEmpty(t, state.WriteSet) + snapshot = snapshot.Append(state) - // assert event fiedls are correct - require.Len(t, output.Events, 2) - - blockEvent := output.Events[1] - - assert.Equal( - t, - common.NewAddressLocation( - nil, - common.Address(sc.EVMContract.Address), - string(types.EventTypeBlockExecuted), - ).ID(), - string(blockEvent.Type), - ) - - ev, err := ccf.Decode(nil, blockEvent.Payload) - require.NoError(t, err) - cadenceEvent, ok := ev.(cadence.Event) - require.True(t, ok) - - blockEventPayload, err := types.DecodeBlockEventPayload(cadenceEvent) - require.NoError(t, err) - require.NotEmpty(t, blockEventPayload.Hash) - + // assert event fields are correct + require.Len(t, output.Events, 1) txEvent := output.Events[0] - assert.Equal( - t, - common.NewAddressLocation( - nil, - common.Address(sc.EVMContract.Address), - string(types.EventTypeTransactionExecuted), - ).ID(), - string(txEvent.Type), - ) + // commit block + blockEventPayload, snapshot := callEVMHeartBeat(t, + ctx, + vm, + snapshot) - ev, err = ccf.Decode(nil, txEvent.Payload) - require.NoError(t, err) - cadenceEvent, ok = ev.(cadence.Event) - require.True(t, ok) + require.NotEmpty(t, blockEventPayload.Hash) + require.Equal(t, uint64(43785), blockEventPayload.TotalGasUsed) + require.NotEmpty(t, blockEventPayload.Hash) + require.Len(t, blockEventPayload.TransactionHashes, 1) + require.NotEmpty(t, blockEventPayload.ReceiptRoot) - txEventPayload, err := types.DecodeTransactionEventPayload(cadenceEvent) + txEventPayload := testutils.TxEventToPayload(t, txEvent, sc.EVMContract.Address) require.NoError(t, err) txPayload, err := types.CadenceUInt8ArrayValueToBytes(txEventPayload.Payload) @@ -388,16 +362,11 @@ func TestEVMRun(t *testing.T) { require.NotEmpty(t, state.WriteSet) txEvent := output.Events[0] - ev, err := ccf.Decode(nil, txEvent.Payload) - require.NoError(t, err) - cadenceEvent, ok := ev.(cadence.Event) - require.True(t, ok) + txEventPayload := testutils.TxEventToPayload(t, txEvent, sc.EVMContract.Address) - event, err := types.DecodeTransactionEventPayload(cadenceEvent) - require.NoError(t, err) - require.NotEmpty(t, event.Hash) + require.NotEmpty(t, txEventPayload.Hash) - encodedLogs, err := types.CadenceUInt8ArrayValueToBytes(event.Logs) + encodedLogs, err := types.CadenceUInt8ArrayValueToBytes(txEventPayload.Logs) require.NoError(t, err) var logs []*gethTypes.Log @@ -491,7 +460,10 @@ func TestEVMBatchRun(t *testing.T) { require.NoError(t, output.Err) require.NotEmpty(t, state.WriteSet) - require.Len(t, output.Events, batchCount+1) // +1 block executed + // append the state + snapshot = snapshot.Append(state) + + require.Len(t, output.Events, batchCount) for i, event := range output.Events { if i == batchCount { // last one is block executed continue @@ -519,31 +491,15 @@ func TestEVMBatchRun(t *testing.T) { assert.Equal(t, storedValues[i], last.Big().Int64()) } - // last one is block executed, make sure TotalGasUsed is non-zero - blockEvent := output.Events[batchCount] - - assert.Equal( - t, - common.NewAddressLocation( - nil, - common.Address(sc.EVMContract.Address), - string(types.EventTypeBlockExecuted), - ).ID(), - string(blockEvent.Type), - ) - - ev, err := ccf.Decode(nil, blockEvent.Payload) - require.NoError(t, err) - cadenceEvent, ok := ev.(cadence.Event) - require.True(t, ok) + // commit block + blockEventPayload, snapshot := callEVMHeartBeat(t, + ctx, + vm, + snapshot) - blockEventPayload, err := types.DecodeBlockEventPayload(cadenceEvent) - require.NoError(t, err) require.NotEmpty(t, blockEventPayload.Hash) require.Equal(t, uint64(155513), blockEventPayload.TotalGasUsed) - - // append the state - snapshot = snapshot.Append(state) + require.Len(t, blockEventPayload.TransactionHashes, 5) // retrieve the values retrieveCode := []byte(fmt.Sprintf( @@ -1003,31 +959,13 @@ func TestEVMAddressDeposit(t *testing.T) { bal := getEVMAccountBalance(t, ctx, vm, snapshot, addr) require.Equal(t, expectedBalance, bal) - // block executed event, make sure TotalGasUsed is non-zero - blockEvent := output.Events[3] - - assert.Equal( - t, - common.NewAddressLocation( - nil, - common.Address(sc.EVMContract.Address), - string(types.EventTypeBlockExecuted), - ).ID(), - string(blockEvent.Type), - ) - - ev, err := ccf.Decode(nil, blockEvent.Payload) + // tx executed event + txEvent := output.Events[2] + txEventPayload := testutils.TxEventToPayload(t, txEvent, sc.EVMContract.Address) require.NoError(t, err) - cadenceEvent, ok := ev.(cadence.Event) - require.True(t, ok) - - blockEventPayload, err := types.DecodeBlockEventPayload(cadenceEvent) - require.NoError(t, err) - require.NotEmpty(t, blockEventPayload.Hash) - require.Equal(t, uint64(21000), blockEventPayload.TotalGasUsed) // deposit event - depositEvent := output.Events[4] + depositEvent := output.Events[3] depEv, err := types.FlowEventToCadenceEvent(depositEvent) require.NoError(t, err) @@ -1035,6 +973,17 @@ func TestEVMAddressDeposit(t *testing.T) { require.NoError(t, err) require.Equal(t, types.OneFlow, depEvPayload.BalanceAfterInAttoFlow.Value) + + // commit block + blockEventPayload, _ := callEVMHeartBeat(t, + ctx, + vm, + snapshot) + + require.NotEmpty(t, blockEventPayload.Hash) + require.Equal(t, uint64(21000), blockEventPayload.TotalGasUsed) + require.Len(t, blockEventPayload.TransactionHashes, 1) + require.Equal(t, txEventPayload.Hash, string(blockEventPayload.TransactionHashes[0])) }) } @@ -1197,7 +1146,7 @@ func TestCadenceOwnedAccountFunctionalities(t *testing.T) { require.NoError(t, err) require.NoError(t, output.Err) - withdrawEvent := output.Events[10] + withdrawEvent := output.Events[7] ev, err := types.FlowEventToCadenceEvent(withdrawEvent) require.NoError(t, err) @@ -2557,12 +2506,52 @@ func setupCOA( snap = snap.Append(es) // 3rd event is the cadence owned account created event - coaAddress, err := types.COAAddressFromFlowCOACreatedEvent(sc.EVMContract.Address, output.Events[2]) + coaAddress, err := types.COAAddressFromFlowCOACreatedEvent(sc.EVMContract.Address, output.Events[1]) require.NoError(t, err) return coaAddress, snap } +func callEVMHeartBeat( + t *testing.T, + ctx fvm.Context, + vm fvm.VM, + snap snapshot.SnapshotTree, +) (*types.BlockEventPayload, snapshot.SnapshotTree) { + sc := systemcontracts.SystemContractsForChain(ctx.Chain.ChainID()) + + heartBeatCode := []byte(fmt.Sprintf( + ` + import EVM from %s + transaction { + prepare(serviceAccount: auth(BorrowValue) &Account) { + let evmHeartbeat = serviceAccount.storage + .borrow<&EVM.Heartbeat>(from: /storage/EVMHeartbeat) + ?? panic("Couldn't borrow EVM.Heartbeat Resource") + evmHeartbeat.heartbeat() + } + } + `, + sc.EVMContract.Address.HexWithPrefix(), + )) + tx := fvm.Transaction( + flow.NewTransactionBody(). + SetScript(heartBeatCode). + AddAuthorizer(sc.FlowServiceAccount.Address), + 0) + + state, output, err := vm.Run(ctx, tx, snap) + require.NoError(t, err) + require.NoError(t, output.Err) + require.NotEmpty(t, state.WriteSet) + snap = snap.Append(state) + + // validate block event + require.Len(t, output.Events, 1) + blockEvent := output.Events[0] + return BlockEventToPayload(t, blockEvent, sc.EVMContract.Address), snap +} + func getFlowAccountBalance( t *testing.T, ctx fvm.Context, diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index d41e5f591f7..fa7e53d8ceb 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -213,7 +213,7 @@ func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte, coinbase types.Addres } // if there were no valid transactions skip emitting events - // and commiting a new block + // and committing a new block if len(bp.TransactionHashes) == 0 { return res, nil } @@ -240,12 +240,6 @@ func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte, coinbase types.Addres return nil, err } - // TODO: don't commit right away. - err = h.commitBlockProposal() - if err != nil { - return nil, err - } - return res, nil } @@ -307,7 +301,7 @@ func (h *ContractHandler) run( return nil, err } - // saftey check for result + // safety check for result if res == nil { return nil, types.ErrUnexpectedEmptyResult } @@ -323,35 +317,25 @@ func (h *ContractHandler) run( return res, nil } - // step 3 - update block proposal + // step 3 - update the block proposal bp, err := h.blockStore.BlockProposal() if err != nil { return nil, err } - - // append tx to the block proposal bp.AppendTransaction(res) - - // step 4 - emit events - err = h.emitEvent(types.NewTransactionEvent(res, rlpEncodedTx, bp.Height)) - if err != nil { - return nil, err - } - - // update the block proposal err = h.blockStore.UpdateBlockProposal(bp) if err != nil { return nil, err } - // TODO: don't commit right away. - err = h.commitBlockProposal() + // step 4 - emit events + err = h.emitEvent(types.NewTransactionEvent(res, rlpEncodedTx, bp.Height)) if err != nil { return nil, err } + // step 5 - collect traces h.tracer.Collect(res.TxHash) - return res, nil } @@ -488,7 +472,7 @@ func (h *ContractHandler) executeAndHandleCall( return nil, err } - // saftey check for result + // safety check for result if res == nil { return nil, types.ErrUnexpectedEmptyResult } @@ -524,6 +508,12 @@ func (h *ContractHandler) executeAndHandleCall( } } + // update the block proposal + err = h.blockStore.UpdateBlockProposal(bp) + if err != nil { + return nil, err + } + // emit events encoded, err := call.Encode() if err != nil { @@ -537,20 +527,8 @@ func (h *ContractHandler) executeAndHandleCall( return nil, err } - // update the block proposal - err = h.blockStore.UpdateBlockProposal(bp) - if err != nil { - return nil, err - } - - // TODO: don't commit right away. - err = h.commitBlockProposal() - if err != nil { - return nil, err - } - + // collect traces h.tracer.Collect(res.TxHash) - return res, nil } diff --git a/fvm/evm/handler/handler_benchmark_test.go b/fvm/evm/handler/handler_benchmark_test.go index 8c7ff388706..136a431aec7 100644 --- a/fvm/evm/handler/handler_benchmark_test.go +++ b/fvm/evm/handler/handler_benchmark_test.go @@ -10,10 +10,10 @@ import ( "github.com/onflow/flow-go/model/flow" ) -func BenchmarkStorage(b *testing.B) { benchmarkStorageGrowth(b, 100, 100) } +func BenchmarkStorage(b *testing.B) { benchmarkStorageGrowth(b, 100, 100, 100) } // benchmark -func benchmarkStorageGrowth(b *testing.B, accountCount, setupKittyCount int) { +func benchmarkStorageGrowth(b *testing.B, accountCount, setupKittyCount, txPerBlock int) { testutils.RunWithTestBackend(b, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(b, backend, func(rootAddr flow.Address) { testutils.RunWithDeployedContract(b, @@ -48,12 +48,17 @@ func benchmarkStorageGrowth(b *testing.B, accountCount, setupKittyCount int) { generation, genes, ), - 300_000_000, + 50_000, types.NewBalanceFromUFix64(0), ) - require.Equal(b, 2, len(backend.Events())) + require.Equal(b, 1, len(backend.Events())) backend.DropEvents() // this would make things lighter backend.ResetStats() // reset stats + + if i%txPerBlock == 0 { + handler.CommitBlockProposal() + backend.DropEvents() + } } accounts[0].Call( @@ -65,7 +70,7 @@ func benchmarkStorageGrowth(b *testing.B, accountCount, setupKittyCount int) { testutils.RandomBigInt(1000), testutils.RandomBigInt(1000), ), - 300_000_000, + 50_000, types.NewBalanceFromUFix64(0), ) diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index 6ba6ee1dad1..79ddf82d23e 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/onflow/cadence" - jsoncdc "github.com/onflow/cadence/encoding/json" "github.com/onflow/cadence/runtime/common" gethCommon "github.com/onflow/go-ethereum/common" gethCore "github.com/onflow/go-ethereum/core" @@ -35,8 +34,6 @@ import ( "github.com/onflow/flow-go/module/trace" ) -// TODO add test for fatal errors - var flowTokenAddress = common.MustBytesToAddress(systemcontracts.SystemContractsForChain(flow.Emulator).FlowToken.Address.Bytes()) var randomBeaconAddress = systemcontracts.SystemContractsForChain(flow.Emulator).RandomBeaconHistory.Address @@ -90,52 +87,32 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) { // successfully run (no-panic) handler.RunOrPanic(tx, coinbase) - // check gas usage - // TODO: uncomment and investigate me - // computationUsed, err := backend.ComputationUsed() - // require.NoError(t, err) - // require.Equal(t, result.GasConsumed, computationUsed) - - // check events (1 extra for block event) + // check event events := backend.Events() + require.Len(t, events, 1) + txEventPayload := testutils.TxEventToPayload(t, events[0], rootAddr) - require.Len(t, events, 2) - - event := events[0] - assert.Equal(t, event.Type, types.EventTypeTransactionExecuted) - ev, err := jsoncdc.Decode(nil, event.Payload) - require.NoError(t, err) - cadenceEvent, ok := ev.(cadence.Event) - require.True(t, ok) - - // TODO: add an event decoder in types.event - cadenceLogs := cadence.SearchFieldByName(cadenceEvent, "logs") - - encodedLogs, err := types.CadenceUInt8ArrayValueToBytes(cadenceLogs) + // check logs + encodedLogs, err := types.CadenceUInt8ArrayValueToBytes(txEventPayload.Logs) require.NoError(t, err) var logs []*gethTypes.Log err = rlp.DecodeBytes(encodedLogs, &logs) require.NoError(t, err) - for i, l := range result.Logs { assert.Equal(t, l, logs[i]) } - // check block event - event = events[1] - - assert.Equal(t, event.Type, types.EventTypeBlockExecuted) - ev, err = jsoncdc.Decode(nil, event.Payload) - require.NoError(t, err) + // form block + handler.CommitBlockProposal() + // check block event + events = backend.Events() + require.Len(t, events, 2) + blockEventPayload := testutils.BlockEventToPayload(t, events[1], rootAddr) // make sure block transaction list references the above transaction id - cadenceEvent, ok = ev.(cadence.Event) - require.True(t, ok) - blockEvent, err := types.DecodeBlockEventPayload(cadenceEvent) - require.NoError(t, err) - - eventTxID := blockEvent.TransactionHashes[0] // only one hash in block + require.Len(t, blockEventPayload.TransactionHashes, 1) + eventTxID := blockEventPayload.TransactionHashes[0] // only one hash in block // make sure the transaction id included in the block transaction list is the same as tx sumbmitted assert.Equal( t, @@ -314,6 +291,7 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { bal := types.OneFlowBalance account.Deposit(types.NewFlowTokenVault(bal)) + handler.CommitBlockProposal() // check if block height has been incremented b = handler.LastExecutedBlock() require.Equal(t, uint64(1), b.Height) @@ -364,30 +342,52 @@ func TestHandler_COA(t *testing.T) { zeroBalance, foa.Balance())) events := backend.Events() - require.Len(t, events, 6) + require.Len(t, events, 3) - // first two transactions are for COA setup + // Block level expected values + txHashes := make([]gethCommon.Hash, 0) + totalGasUsed := uint64(0) - // transaction event - event := events[2] - assert.Equal(t, event.Type, types.EventTypeTransactionExecuted) - - // block event - event = events[3] - assert.Equal(t, event.Type, types.EventTypeBlockExecuted) + // deploy COA transaction event + txEventPayload := testutils.TxEventToPayload(t, events[0], rootAddr) + txContent, err := types.CadenceUInt8ArrayValueToBytes(txEventPayload.Payload) + require.NoError(t, err) + tx, err := types.DirectCallFromEncoded(txContent) + require.NoError(t, err) + txHashes = append(txHashes, tx.Hash()) + totalGasUsed += txEventPayload.GasConsumed - // transaction event - event = events[4] - assert.Equal(t, event.Type, types.EventTypeTransactionExecuted) - _, err := jsoncdc.Decode(nil, event.Payload) + // deposit transaction event + txEventPayload = testutils.TxEventToPayload(t, events[1], rootAddr) + txContent, err = types.CadenceUInt8ArrayValueToBytes(txEventPayload.Payload) require.NoError(t, err) - // TODO: decode encoded tx and check for the amount and value - // assert.Equal(t, foa.Address(), ret.Address) - // assert.Equal(t, balance, ret.Amount) + tx, err = types.DirectCallFromEncoded(txContent) + require.NoError(t, err) + require.Equal(t, foa.Address(), tx.To) + require.Equal(t, types.BalanceToBigInt(balance), tx.Value) + txHashes = append(txHashes, tx.Hash()) + totalGasUsed += txEventPayload.GasConsumed + + // withdraw transaction event + txEventPayload = testutils.TxEventToPayload(t, events[2], rootAddr) + txContent, err = types.CadenceUInt8ArrayValueToBytes(txEventPayload.Payload) + require.NoError(t, err) + tx, err = types.DirectCallFromEncoded(txContent) + require.NoError(t, err) + require.Equal(t, foa.Address(), tx.From) + require.Equal(t, types.BalanceToBigInt(balance), tx.Value) + txHashes = append(txHashes, tx.Hash()) + totalGasUsed += txEventPayload.GasConsumed // block event - event = events[5] - assert.Equal(t, event.Type, types.EventTypeBlockExecuted) + handler.CommitBlockProposal() + events = backend.Events() + require.Len(t, events, 4) + blockEventPayload := testutils.BlockEventToPayload(t, events[3], rootAddr) + for i, txHash := range txHashes { + require.Equal(t, txHash.Hex(), string(blockEventPayload.TransactionHashes[i])) + } + require.Equal(t, totalGasUsed, blockEventPayload.TotalGasUsed) // check gas usage computationUsed, err := backend.ComputationUsed() @@ -641,17 +641,10 @@ func TestHandler_COA(t *testing.T) { require.Equal(t, big.NewInt(int64(blockHeight)), new(big.Int).SetBytes(ret.ReturnedData)) events := backend.Events() - require.Len(t, events, 6) + require.Len(t, events, 3) // last transaction executed event - event := events[4] - assert.Equal(t, event.Type, types.EventTypeTransactionExecuted) - ev, err := jsoncdc.Decode(nil, event.Payload) - require.NoError(t, err) - cadenceEvent, ok := ev.(cadence.Event) - require.True(t, ok) - txEventPayload, err := types.DecodeTransactionEventPayload(cadenceEvent) - require.NoError(t, err) - + event := events[2] + txEventPayload := testutils.TxEventToPayload(t, event, rootAddr) values := txEventPayload.PrecompiledCalls.Values aggregated := make([]byte, len(values)) for i, v := range values { @@ -926,6 +919,7 @@ func TestHandler_TransactionRun(t *testing.T) { require.Equal(t, types.ErrCodeNoError, rs.ErrorCode) } + handler.CommitBlockProposal() events := backend.Events() require.Len(t, events, batchSize+1) // +1 block event @@ -933,15 +927,8 @@ func TestHandler_TransactionRun(t *testing.T) { if i == batchSize { continue // don't check last block event } - assert.Equal(t, event.Type, types.EventTypeTransactionExecuted) - ev, err := jsoncdc.Decode(nil, event.Payload) - require.NoError(t, err) - cadenceEvent, ok := ev.(cadence.Event) - require.True(t, ok) - - // TODO: add an event decoder in types.event - cadenceLogs := cadence.SearchFieldByName(cadenceEvent, "logs") - encodedLogs, err := types.CadenceUInt8ArrayValueToBytes(cadenceLogs.(cadence.Array)) + txEventPayload := testutils.TxEventToPayload(t, event, rootAddr) + encodedLogs, err := types.CadenceUInt8ArrayValueToBytes(txEventPayload.Logs) require.NoError(t, err) var logs []*gethTypes.Log diff --git a/fvm/evm/precompiles/arch.go b/fvm/evm/precompiles/arch.go index ae6fe57ce50..5f9bc418347 100644 --- a/fvm/evm/precompiles/arch.go +++ b/fvm/evm/precompiles/arch.go @@ -8,7 +8,7 @@ import ( var ( FlowBlockHeightFuncSig = ComputeFunctionSelector("flowBlockHeight", nil) - // TODO: fix me + ProofVerifierFuncSig = ComputeFunctionSelector( "verifyCOAOwnershipProof", []string{"address", "bytes32", "bytes"}, diff --git a/fvm/evm/stdlib/contract.cdc b/fvm/evm/stdlib/contract.cdc index 4856ada9537..ecb006a0d55 100644 --- a/fvm/evm/stdlib/contract.cdc +++ b/fvm/evm/stdlib/contract.cdc @@ -815,16 +815,23 @@ contract EVM { } /// The Heartbeat resource controls the block production - access(all) resource Heartbeat { - + access(all) + resource Heartbeat { /// heartbeat calls commit block proposals and forms new blocks including all the /// recently executed transactions. /// The Flow protocol makes sure to call this function once per block as a system call. - access(all) fun heartbeat() { + access(all) + fun heartbeat() { InternalEVM.commitBlockProposal() } } + /// createHeartBeat creates a heartbeat resource + access(account) + fun createHeartBeat(): @Heartbeat{ + return <-create Heartbeat() + } + init() { self.account.storage.save(<-create Heartbeat(), to: /storage/EVMHeartbeat) } diff --git a/fvm/evm/testutils/backend.go b/fvm/evm/testutils/backend.go index 6d5616a488e..498fd2948b0 100644 --- a/fvm/evm/testutils/backend.go +++ b/fvm/evm/testutils/backend.go @@ -11,7 +11,7 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence" - jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/onflow/cadence/encoding/ccf" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/stretchr/testify/require" @@ -25,7 +25,7 @@ import ( "github.com/onflow/flow-go/module/trace" ) -var TestFlowEVMRootAddress = flow.BytesToAddress([]byte("FlowEVM")) +var TestFlowEVMRootAddress = flow.Address{1, 2, 3, 4} var TestComputationLimit = uint(100_000_000) func RunWithTestFlowEVMRootAddress(t testing.TB, backend atree.Ledger, f func(flow.Address)) { @@ -127,12 +127,15 @@ func getSimpleEventEmitter() *testEventEmitter { events := make(flow.EventsList, 0) return &testEventEmitter{ emitEvent: func(event cadence.Event) error { - payload, err := jsoncdc.Encode(event) + payload, err := ccf.Encode(event) if err != nil { return err } - - events = append(events, flow.Event{Type: flow.EventType(event.EventType.QualifiedIdentifier), Payload: payload}) + eventType := flow.EventType(event.EventType.ID()) + events = append(events, flow.Event{ + Type: eventType, + Payload: payload, + }) return nil }, events: func() flow.EventsList { diff --git a/fvm/evm/testutils/event.go b/fvm/evm/testutils/event.go new file mode 100644 index 00000000000..004f42ca166 --- /dev/null +++ b/fvm/evm/testutils/event.go @@ -0,0 +1,55 @@ +package testutils + +import ( + "testing" + + "github.com/onflow/cadence" + "github.com/onflow/cadence/encoding/ccf" + "github.com/onflow/cadence/runtime/common" + "github.com/stretchr/testify/require" + "gotest.tools/assert" + + "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/model/flow" +) + +func flowToCadenceEvent(t testing.TB, event flow.Event) cadence.Event { + ev, err := ccf.Decode(nil, event.Payload) + require.NoError(t, err) + cadenceEvent, ok := ev.(cadence.Event) + require.True(t, ok) + return cadenceEvent +} + +func TxEventToPayload(t testing.TB, event flow.Event, evmContract flow.Address) *types.TransactionEventPayload { + assert.Equal( + t, + common.NewAddressLocation( + nil, + common.Address(evmContract), + string(types.EventTypeTransactionExecuted), + ).ID(), + string(event.Type), + ) + cadenceEvent := flowToCadenceEvent(t, event) + txEventPayload, err := types.DecodeTransactionEventPayload(cadenceEvent) + require.NoError(t, err) + return txEventPayload +} + +func BlockEventToPayload(t testing.TB, event flow.Event, evmContract flow.Address) *types.BlockEventPayload { + assert.Equal( + t, + common.NewAddressLocation( + nil, + common.Address(evmContract), + string(types.EventTypeBlockExecuted), + ).ID(), + string(event.Type), + ) + + cadenceEvent := flowToCadenceEvent(t, event) + blockEventPayload, err := types.DecodeBlockEventPayload(cadenceEvent) + require.NoError(t, err) + return blockEventPayload +} diff --git a/fvm/fvm_test.go b/fvm/fvm_test.go index 89b04ac5697..da35a7260c3 100644 --- a/fvm/fvm_test.go +++ b/fvm/fvm_test.go @@ -3015,10 +3015,17 @@ func TestEVM(t *testing.T) { .borrow(from: /storage/flowTokenVault) ?? panic("Could not borrow reference to the owner's Vault!") + let evmHeartbeat = acc.storage + .borrow<&EVM.Heartbeat>(from: /storage/EVMHeartbeat) + ?? panic("Couldn't borrow EVM.Heartbeat Resource") + let acc <- EVM.createCadenceOwnedAccount() let amount <- vaultRef.withdraw(amount: 0.0000001) as! @FlowToken.Vault acc.deposit(from: <- amount) destroy acc + + // commit blocks + evmHeartbeat.heartbeat() } }`, sc.FungibleToken.Address.HexWithPrefix(), @@ -3040,9 +3047,9 @@ func TestEVM(t *testing.T) { require.NoError(t, err) require.NoError(t, output.Err) - require.Len(t, output.Events, 7) + require.Len(t, output.Events, 6) - txExe, blockExe := output.Events[4], output.Events[5] + txExe, blockExe := output.Events[3], output.Events[5] txExecutedID := common.NewAddressLocation( nil, common.Address(sc.EVMContract.Address), @@ -3067,12 +3074,11 @@ func TestEVM(t *testing.T) { t, []common.TypeID{ common.TypeID(txExecutedID), - common.TypeID(blockExecutedID), "A.f8d6e0586b0a20c7.EVM.CadenceOwnedAccountCreated", "A.ee82856bf20e2aa6.FungibleToken.Withdrawn", common.TypeID(txExecutedID), - common.TypeID(blockExecutedID), "A.f8d6e0586b0a20c7.EVM.FLOWTokensDeposited", + common.TypeID(blockExecutedID), }, eventTypeIDs, ) diff --git a/utils/unittest/execution_state.go b/utils/unittest/execution_state.go index cf331cdfd7d..4be593292ef 100644 --- a/utils/unittest/execution_state.go +++ b/utils/unittest/execution_state.go @@ -23,7 +23,7 @@ const ServiceAccountPrivateKeySignAlgo = crypto.ECDSAP256 const ServiceAccountPrivateKeyHashAlgo = hash.SHA2_256 // Pre-calculated state commitment with root account with the above private key -const GenesisStateCommitmentHex = "d31a2d57a74c97aca5df275819b4b42141672808e0bece2fda417e8dc563c818" +const GenesisStateCommitmentHex = "84ff0ef1d33db21e3520f1db78e4a676146c92f8300298320f9a186735074035" var GenesisStateCommitment flow.StateCommitment @@ -87,10 +87,10 @@ func genesisCommitHexByChainID(chainID flow.ChainID) string { return GenesisStateCommitmentHex } if chainID == flow.Testnet { - return "e222dc960f317d13cb2b10b92760a8f3ee4c89052745d560f3a7cebb4517dc08" + return "48a9f343a11898d75abe8875f31136f1f892f193abf3f4ac6c92a39211844f2c" } if chainID == flow.Sandboxnet { return "e1c08b17f9e5896f03fe28dd37ca396c19b26628161506924fbf785834646ea1" } - return "5e178f8a23aa21c27b127104a3964b3e3ea273c4c531d48b572086fa68e07eba" + return "4322de872268ac2188e55d2649bb65736f6bbb143bd010348f87df72afa50380" }