github.com/onflow/flow-go@v0.33.17/engine/execution/computation/execution_verification_test.go (about)

     1  package computation
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"fmt"
     7  	"testing"
     8  
     9  	"github.com/ipfs/go-datastore"
    10  	dssync "github.com/ipfs/go-datastore/sync"
    11  	blockstore "github.com/ipfs/go-ipfs-blockstore"
    12  	"github.com/onflow/cadence"
    13  	jsoncdc "github.com/onflow/cadence/encoding/json"
    14  	"github.com/rs/zerolog"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"github.com/onflow/flow-go/crypto"
    19  
    20  	"github.com/onflow/flow-go/engine/execution"
    21  	"github.com/onflow/flow-go/engine/execution/computation/committer"
    22  	"github.com/onflow/flow-go/engine/execution/computation/computer"
    23  	"github.com/onflow/flow-go/engine/execution/state"
    24  	bootstrapexec "github.com/onflow/flow-go/engine/execution/state/bootstrap"
    25  	"github.com/onflow/flow-go/engine/execution/testutil"
    26  	"github.com/onflow/flow-go/engine/execution/utils"
    27  	"github.com/onflow/flow-go/engine/testutil/mocklocal"
    28  	"github.com/onflow/flow-go/engine/verification/fetcher"
    29  	"github.com/onflow/flow-go/fvm"
    30  	"github.com/onflow/flow-go/fvm/blueprints"
    31  	"github.com/onflow/flow-go/fvm/environment"
    32  	"github.com/onflow/flow-go/fvm/errors"
    33  	"github.com/onflow/flow-go/fvm/storage/derived"
    34  	"github.com/onflow/flow-go/fvm/systemcontracts"
    35  	completeLedger "github.com/onflow/flow-go/ledger/complete"
    36  	"github.com/onflow/flow-go/ledger/complete/wal/fixtures"
    37  	"github.com/onflow/flow-go/model/flow"
    38  	"github.com/onflow/flow-go/model/verification"
    39  	"github.com/onflow/flow-go/module/chunks"
    40  	"github.com/onflow/flow-go/module/executiondatasync/execution_data"
    41  	exedataprovider "github.com/onflow/flow-go/module/executiondatasync/provider"
    42  	mocktracker "github.com/onflow/flow-go/module/executiondatasync/tracker/mock"
    43  	"github.com/onflow/flow-go/module/metrics"
    44  	requesterunit "github.com/onflow/flow-go/module/state_synchronization/requester/unittest"
    45  	"github.com/onflow/flow-go/module/trace"
    46  	"github.com/onflow/flow-go/utils/unittest"
    47  )
    48  
    49  const (
    50  	testVerifyMaxConcurrency = 2
    51  )
    52  
    53  var chain = flow.Emulator.Chain()
    54  
    55  // In the following tests the system transaction is expected to fail, as the epoch related things are not set up properly.
    56  // This is not relevant to the test, as only the non-system transactions are tested.
    57  
    58  func Test_ExecutionMatchesVerification(t *testing.T) {
    59  	t.Run("empty block", func(t *testing.T) {
    60  		executeBlockAndVerify(t,
    61  			[][]*flow.TransactionBody{},
    62  			fvm.DefaultTransactionFees,
    63  			fvm.DefaultMinimumStorageReservation)
    64  	})
    65  
    66  	t.Run("single transaction event", func(t *testing.T) {
    67  
    68  		deployTx := blueprints.DeployContractTransaction(chain.ServiceAddress(), []byte(""+
    69  			`pub contract Foo {
    70  				pub event FooEvent(x: Int, y: Int)
    71  
    72  				pub fun event() { 
    73  					emit FooEvent(x: 2, y: 1)
    74  				}
    75  			}`), "Foo")
    76  
    77  		emitTx := &flow.TransactionBody{
    78  			Script: []byte(fmt.Sprintf(`
    79  			import Foo from 0x%s
    80  			transaction {
    81  				prepare() {}
    82  				execute {
    83  					Foo.event()
    84  				}
    85  			}`, chain.ServiceAddress())),
    86  		}
    87  
    88  		err := testutil.SignTransactionAsServiceAccount(deployTx, 0, chain)
    89  		require.NoError(t, err)
    90  
    91  		err = testutil.SignTransactionAsServiceAccount(emitTx, 1, chain)
    92  		require.NoError(t, err)
    93  
    94  		cr := executeBlockAndVerify(t, [][]*flow.TransactionBody{
    95  			{
    96  				deployTx, emitTx,
    97  			},
    98  		}, fvm.BootstrapProcedureFeeParameters{}, fvm.DefaultMinimumStorageReservation)
    99  
   100  		colResult := cr.CollectionExecutionResultAt(0)
   101  		txResults := colResult.TransactionResults()
   102  		events := colResult.Events()
   103  		// ensure event is emitted
   104  		require.Empty(t, txResults[0].ErrorMessage)
   105  		require.Empty(t, txResults[1].ErrorMessage)
   106  		require.Len(t, events, 2)
   107  		require.Equal(t, flow.EventType(fmt.Sprintf("A.%s.Foo.FooEvent", chain.ServiceAddress())), events[1].Type)
   108  	})
   109  
   110  	t.Run("multiple collections events", func(t *testing.T) {
   111  
   112  		deployTx := blueprints.DeployContractTransaction(chain.ServiceAddress(), []byte(""+
   113  			`pub contract Foo {
   114  				pub event FooEvent(x: Int, y: Int)
   115  
   116  				pub fun event() { 
   117  					emit FooEvent(x: 2, y: 1)
   118  				}
   119  			}`), "Foo")
   120  
   121  		emitTx1 := flow.TransactionBody{
   122  			Script: []byte(fmt.Sprintf(`
   123  			import Foo from 0x%s
   124  			transaction {
   125  				prepare() {}
   126  				execute {
   127  					Foo.event()
   128  				}
   129  			}`, chain.ServiceAddress())),
   130  		}
   131  
   132  		// copy txs
   133  		emitTx2 := emitTx1
   134  		emitTx3 := emitTx1
   135  
   136  		err := testutil.SignTransactionAsServiceAccount(deployTx, 0, chain)
   137  		require.NoError(t, err)
   138  
   139  		err = testutil.SignTransactionAsServiceAccount(&emitTx1, 1, chain)
   140  		require.NoError(t, err)
   141  		err = testutil.SignTransactionAsServiceAccount(&emitTx2, 2, chain)
   142  		require.NoError(t, err)
   143  		err = testutil.SignTransactionAsServiceAccount(&emitTx3, 3, chain)
   144  		require.NoError(t, err)
   145  
   146  		cr := executeBlockAndVerify(t, [][]*flow.TransactionBody{
   147  			{
   148  				deployTx, &emitTx1,
   149  			},
   150  			{
   151  				&emitTx2,
   152  			},
   153  			{
   154  				&emitTx3,
   155  			},
   156  		}, fvm.BootstrapProcedureFeeParameters{}, fvm.DefaultMinimumStorageReservation)
   157  
   158  		verifyTxResults := func(t *testing.T, colIndex, expResCount int) {
   159  			colResult := cr.CollectionExecutionResultAt(colIndex)
   160  			txResults := colResult.TransactionResults()
   161  			require.Len(t, txResults, expResCount)
   162  			for i := 0; i < expResCount; i++ {
   163  				require.Empty(t, txResults[i].ErrorMessage)
   164  			}
   165  		}
   166  
   167  		verifyEvents := func(t *testing.T, colIndex int, eventTypes []flow.EventType) {
   168  			colResult := cr.CollectionExecutionResultAt(colIndex)
   169  			events := colResult.Events()
   170  			require.Len(t, events, len(eventTypes))
   171  			for i, event := range events {
   172  				require.Equal(t, event.Type, eventTypes[i])
   173  			}
   174  		}
   175  
   176  		expEventType1 := flow.EventType("flow.AccountContractAdded")
   177  		expEventType2 := flow.EventType(fmt.Sprintf("A.%s.Foo.FooEvent", chain.ServiceAddress()))
   178  
   179  		// first collection
   180  		verifyTxResults(t, 0, 2)
   181  		verifyEvents(t, 0, []flow.EventType{expEventType1, expEventType2})
   182  
   183  		// second collection
   184  		verifyTxResults(t, 1, 1)
   185  		verifyEvents(t, 1, []flow.EventType{expEventType2})
   186  
   187  		// 3rd collection
   188  		verifyTxResults(t, 2, 1)
   189  		verifyEvents(t, 2, []flow.EventType{expEventType2})
   190  	})
   191  
   192  	t.Run("with failed storage limit", func(t *testing.T) {
   193  
   194  		accountPrivKey, createAccountTx := testutil.CreateAccountCreationTransaction(t, chain)
   195  
   196  		// this should return the address of newly created account
   197  		accountAddress, err := chain.AddressAtIndex(5)
   198  		require.NoError(t, err)
   199  
   200  		err = testutil.SignTransactionAsServiceAccount(createAccountTx, 0, chain)
   201  		require.NoError(t, err)
   202  
   203  		addKeyTx := testutil.CreateAddAnAccountKeyMultipleTimesTransaction(t, &accountPrivKey, 100).AddAuthorizer(accountAddress)
   204  		err = testutil.SignTransaction(addKeyTx, accountAddress, accountPrivKey, 0)
   205  		require.NoError(t, err)
   206  
   207  		minimumStorage, err := cadence.NewUFix64("0.00010807")
   208  		require.NoError(t, err)
   209  
   210  		cr := executeBlockAndVerify(t, [][]*flow.TransactionBody{
   211  			{
   212  				createAccountTx,
   213  			},
   214  			{
   215  				addKeyTx,
   216  			},
   217  		}, fvm.DefaultTransactionFees, minimumStorage)
   218  
   219  		colResult := cr.CollectionExecutionResultAt(0)
   220  		txResults := colResult.TransactionResults()
   221  		// storage limit error
   222  		assert.Len(t, txResults, 1)
   223  		assert.Equal(t, txResults[0].ErrorMessage, "")
   224  		// ensure events from the first transaction is emitted
   225  		require.Len(t, colResult.Events(), 10)
   226  
   227  		colResult = cr.CollectionExecutionResultAt(1)
   228  		txResults = colResult.TransactionResults()
   229  		assert.Len(t, txResults, 1)
   230  		// storage limit error
   231  		assert.Contains(t, txResults[0].ErrorMessage, errors.ErrCodeStorageCapacityExceeded.String())
   232  		// ensure fee deduction events are emitted even though tx fails
   233  		require.Len(t, colResult.Events(), 3)
   234  	})
   235  
   236  	t.Run("with failed transaction fee deduction", func(t *testing.T) {
   237  		accountPrivKey, createAccountTx := testutil.CreateAccountCreationTransaction(t, chain)
   238  		// this should return the address of newly created account
   239  		accountAddress, err := chain.AddressAtIndex(5)
   240  		require.NoError(t, err)
   241  
   242  		err = testutil.SignTransactionAsServiceAccount(createAccountTx, 0, chain)
   243  		require.NoError(t, err)
   244  
   245  		spamTx := &flow.TransactionBody{
   246  			Script: []byte(`
   247  			transaction {
   248  				prepare() {}
   249  				execute {
   250  					var s: Int256 = 1024102410241024
   251  					var i = 0
   252  					var a = Int256(7)
   253  					var b = Int256(5)
   254  					var c = Int256(2)
   255  					while i < 150000 {
   256  						s = s * a
   257  						s = s / b
   258  						s = s / c
   259  						i = i + 1
   260  					}
   261  					log(i)
   262  				}
   263  			}`),
   264  		}
   265  
   266  		spamTx.SetGasLimit(800000)
   267  		err = testutil.SignTransaction(spamTx, accountAddress, accountPrivKey, 0)
   268  		require.NoError(t, err)
   269  
   270  		require.NoError(t, err)
   271  
   272  		cr := executeBlockAndVerifyWithParameters(t, [][]*flow.TransactionBody{
   273  			{
   274  				createAccountTx,
   275  				spamTx,
   276  			},
   277  		},
   278  			[]fvm.Option{
   279  				fvm.WithTransactionFeesEnabled(true),
   280  				fvm.WithAccountStorageLimit(true),
   281  				// make sure we don't run out of memory first.
   282  				fvm.WithMemoryLimit(20_000_000_000),
   283  			}, []fvm.BootstrapProcedureOption{
   284  				fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply),
   285  				fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee),
   286  				fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation),
   287  				fvm.WithTransactionFee(fvm.DefaultTransactionFees),
   288  				fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW),
   289  			})
   290  
   291  		colResult := cr.CollectionExecutionResultAt(0)
   292  		txResults := colResult.TransactionResults()
   293  		events := colResult.Events()
   294  
   295  		// no error
   296  		assert.Equal(t, txResults[0].ErrorMessage, "")
   297  
   298  		// ensure events from the first transaction is emitted. Since transactions are in the same block, get all events from Events[0]
   299  		transactionEvents := 0
   300  		for _, event := range events {
   301  			if event.TransactionID == txResults[0].TransactionID {
   302  				transactionEvents += 1
   303  			}
   304  		}
   305  		require.Equal(t, 10, transactionEvents)
   306  
   307  		assert.Contains(t, txResults[1].ErrorMessage, errors.ErrCodeStorageCapacityExceeded.String())
   308  
   309  		// ensure tx fee deduction events are emitted even though tx failed
   310  		transactionEvents = 0
   311  		for _, event := range events {
   312  			if event.TransactionID == txResults[1].TransactionID {
   313  				transactionEvents += 1
   314  			}
   315  		}
   316  		require.Equal(t, 3, transactionEvents)
   317  	})
   318  
   319  }
   320  
   321  func TestTransactionFeeDeduction(t *testing.T) {
   322  
   323  	type testCase struct {
   324  		name          string
   325  		fundWith      uint64
   326  		tryToTransfer uint64
   327  		checkResult   func(t *testing.T, cr *execution.ComputationResult)
   328  	}
   329  
   330  	txFees := uint64(1_000)
   331  	fundingAmount := uint64(100_000_000)
   332  	transferAmount := uint64(123_456)
   333  
   334  	sc := systemcontracts.SystemContractsForChain(chain.ChainID())
   335  
   336  	depositedEvent := fmt.Sprintf("A.%s.FlowToken.TokensDeposited", sc.FlowToken.Address)
   337  	withdrawnEvent := fmt.Sprintf("A.%s.FlowToken.TokensWithdrawn", sc.FlowToken.Address)
   338  
   339  	testCases := []testCase{
   340  		{
   341  			name:          "Transaction fee deduction emits events",
   342  			fundWith:      fundingAmount,
   343  			tryToTransfer: 0,
   344  			checkResult: func(t *testing.T, cr *execution.ComputationResult) {
   345  				txResults := cr.AllTransactionResults()
   346  
   347  				require.Empty(t, txResults[0].ErrorMessage)
   348  				require.Empty(t, txResults[1].ErrorMessage)
   349  				require.Empty(t, txResults[2].ErrorMessage)
   350  
   351  				var deposits []flow.Event
   352  				var withdraws []flow.Event
   353  
   354  				// events of the first collection
   355  				events := cr.CollectionExecutionResultAt(2).Events()
   356  				for _, e := range events {
   357  					if string(e.Type) == depositedEvent {
   358  						deposits = append(deposits, e)
   359  					}
   360  					if string(e.Type) == withdrawnEvent {
   361  						withdraws = append(withdraws, e)
   362  					}
   363  				}
   364  
   365  				require.Len(t, deposits, 2)
   366  				require.Len(t, withdraws, 2)
   367  			},
   368  		},
   369  		{
   370  			name:          "If just enough balance, fees are still deducted",
   371  			fundWith:      txFees + transferAmount,
   372  			tryToTransfer: transferAmount,
   373  			checkResult: func(t *testing.T, cr *execution.ComputationResult) {
   374  				txResults := cr.AllTransactionResults()
   375  
   376  				require.Empty(t, txResults[0].ErrorMessage)
   377  				require.Empty(t, txResults[1].ErrorMessage)
   378  				require.Empty(t, txResults[2].ErrorMessage)
   379  
   380  				var deposits []flow.Event
   381  				var withdraws []flow.Event
   382  
   383  				// events of the last collection
   384  				events := cr.CollectionExecutionResultAt(2).Events()
   385  				for _, e := range events {
   386  					if string(e.Type) == depositedEvent {
   387  						deposits = append(deposits, e)
   388  					}
   389  					if string(e.Type) == withdrawnEvent {
   390  						withdraws = append(withdraws, e)
   391  					}
   392  				}
   393  
   394  				require.Len(t, deposits, 2)
   395  				require.Len(t, withdraws, 2)
   396  			},
   397  		},
   398  		{
   399  			// this is an edge case that is not applicable to any network.
   400  			// If storage limits were on this would fail due to storage limits
   401  			name:          "If not enough balance, transaction succeeds and fees are deducted to 0",
   402  			fundWith:      txFees,
   403  			tryToTransfer: 1,
   404  			checkResult: func(t *testing.T, cr *execution.ComputationResult) {
   405  				txResults := cr.AllTransactionResults()
   406  
   407  				require.Empty(t, txResults[0].ErrorMessage)
   408  				require.Empty(t, txResults[1].ErrorMessage)
   409  				require.Empty(t, txResults[2].ErrorMessage)
   410  
   411  				var deposits []flow.Event
   412  				var withdraws []flow.Event
   413  
   414  				// events of the last collection
   415  				events := cr.CollectionExecutionResultAt(2).Events()
   416  				for _, e := range events {
   417  					if string(e.Type) == depositedEvent {
   418  						deposits = append(deposits, e)
   419  					}
   420  					if string(e.Type) == withdrawnEvent {
   421  						withdraws = append(withdraws, e)
   422  					}
   423  				}
   424  
   425  				require.Len(t, deposits, 2)
   426  				require.Len(t, withdraws, 2)
   427  			},
   428  		},
   429  		{
   430  			name:          "If tx fails, fees are deducted",
   431  			fundWith:      fundingAmount,
   432  			tryToTransfer: 2 * fundingAmount,
   433  			checkResult: func(t *testing.T, cr *execution.ComputationResult) {
   434  				txResults := cr.AllTransactionResults()
   435  
   436  				require.Empty(t, txResults[0].ErrorMessage)
   437  				require.Empty(t, txResults[1].ErrorMessage)
   438  				require.Contains(t, txResults[2].ErrorMessage, "Error Code: 1101")
   439  
   440  				var deposits []flow.Event
   441  				var withdraws []flow.Event
   442  
   443  				// events of the last collection
   444  				events := cr.CollectionExecutionResultAt(2).Events()
   445  				for _, e := range events {
   446  					if string(e.Type) == depositedEvent {
   447  						deposits = append(deposits, e)
   448  					}
   449  					if string(e.Type) == withdrawnEvent {
   450  						withdraws = append(withdraws, e)
   451  					}
   452  				}
   453  
   454  				require.Len(t, deposits, 1)
   455  				require.Len(t, withdraws, 1)
   456  			},
   457  		},
   458  	}
   459  
   460  	testCasesWithStorageEnabled := []testCase{
   461  		{
   462  			name:          "Transaction fee deduction emits events",
   463  			fundWith:      fundingAmount,
   464  			tryToTransfer: 0,
   465  			checkResult: func(t *testing.T, cr *execution.ComputationResult) {
   466  				txResults := cr.AllTransactionResults()
   467  
   468  				require.Empty(t, txResults[0].ErrorMessage)
   469  				require.Empty(t, txResults[1].ErrorMessage)
   470  				require.Empty(t, txResults[2].ErrorMessage)
   471  
   472  				var deposits []flow.Event
   473  				var withdraws []flow.Event
   474  
   475  				// events of the last collection
   476  				events := cr.CollectionExecutionResultAt(2).Events()
   477  				for _, e := range events {
   478  					if string(e.Type) == depositedEvent {
   479  						deposits = append(deposits, e)
   480  					}
   481  					if string(e.Type) == withdrawnEvent {
   482  						withdraws = append(withdraws, e)
   483  					}
   484  				}
   485  
   486  				require.Len(t, deposits, 2)
   487  				require.Len(t, withdraws, 2)
   488  			},
   489  		},
   490  		{
   491  			name:          "If just enough balance, fees are deducted",
   492  			fundWith:      txFees + transferAmount,
   493  			tryToTransfer: transferAmount,
   494  			checkResult: func(t *testing.T, cr *execution.ComputationResult) {
   495  				txResults := cr.AllTransactionResults()
   496  
   497  				require.Empty(t, txResults[0].ErrorMessage)
   498  				require.Empty(t, txResults[1].ErrorMessage)
   499  				require.Empty(t, txResults[2].ErrorMessage)
   500  
   501  				var deposits []flow.Event
   502  				var withdraws []flow.Event
   503  
   504  				// events of the last collection
   505  				events := cr.CollectionExecutionResultAt(2).Events()
   506  				for _, e := range events {
   507  					if string(e.Type) == depositedEvent {
   508  						deposits = append(deposits, e)
   509  					}
   510  					if string(e.Type) == withdrawnEvent {
   511  						withdraws = append(withdraws, e)
   512  					}
   513  				}
   514  
   515  				require.Len(t, deposits, 2)
   516  				require.Len(t, withdraws, 2)
   517  			},
   518  		},
   519  		{
   520  			name:          "If tx fails, fees are still deducted and fee deduction events are emitted",
   521  			fundWith:      fundingAmount,
   522  			tryToTransfer: 2 * fundingAmount,
   523  			checkResult: func(t *testing.T, cr *execution.ComputationResult) {
   524  				txResults := cr.AllTransactionResults()
   525  
   526  				require.Empty(t, txResults[0].ErrorMessage)
   527  				require.Empty(t, txResults[1].ErrorMessage)
   528  				require.Contains(t, txResults[2].ErrorMessage, "Error Code: 1101")
   529  
   530  				var deposits []flow.Event
   531  				var withdraws []flow.Event
   532  
   533  				// events of the last collection
   534  				events := cr.CollectionExecutionResultAt(2).Events()
   535  				for _, e := range events {
   536  					if string(e.Type) == depositedEvent {
   537  						deposits = append(deposits, e)
   538  					}
   539  					if string(e.Type) == withdrawnEvent {
   540  						withdraws = append(withdraws, e)
   541  					}
   542  				}
   543  
   544  				require.Len(t, deposits, 1)
   545  				require.Len(t, withdraws, 1)
   546  			},
   547  		},
   548  		{
   549  			name:          "If balance at minimum, transaction fails, fees are deducted and fee deduction events are emitted",
   550  			fundWith:      0,
   551  			tryToTransfer: 0,
   552  			checkResult: func(t *testing.T, cr *execution.ComputationResult) {
   553  				txResults := cr.AllTransactionResults()
   554  
   555  				require.Empty(t, txResults[0].ErrorMessage)
   556  				require.Empty(t, txResults[1].ErrorMessage)
   557  				require.Contains(t, txResults[2].ErrorMessage, errors.ErrCodeStorageCapacityExceeded.String())
   558  
   559  				var deposits []flow.Event
   560  				var withdraws []flow.Event
   561  
   562  				// events of the last collection
   563  				events := cr.CollectionExecutionResultAt(2).Events()
   564  				for _, e := range events {
   565  					if string(e.Type) == depositedEvent {
   566  						deposits = append(deposits, e)
   567  					}
   568  					if string(e.Type) == withdrawnEvent {
   569  						withdraws = append(withdraws, e)
   570  					}
   571  				}
   572  
   573  				require.Len(t, deposits, 1)
   574  				require.Len(t, withdraws, 1)
   575  			},
   576  		},
   577  	}
   578  
   579  	transferTokensTx := func(chain flow.Chain) *flow.TransactionBody {
   580  		return flow.NewTransactionBody().
   581  			SetScript([]byte(fmt.Sprintf(`
   582  							// This transaction is a template for a transaction that
   583  							// could be used by anyone to send tokens to another account
   584  							// that has been set up to receive tokens.
   585  							//
   586  							// The withdraw amount and the account from getAccount
   587  							// would be the parameters to the transaction
   588  							
   589  							import FungibleToken from 0x%s
   590  							import FlowToken from 0x%s
   591  							
   592  							transaction(amount: UFix64, to: Address) {
   593  							
   594  								// The Vault resource that holds the tokens that are being transferred
   595  								let sentVault: @FungibleToken.Vault
   596  							
   597  								prepare(signer: AuthAccount) {
   598  							
   599  									// Get a reference to the signer's stored vault
   600  									let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)
   601  										?? panic("Could not borrow reference to the owner's Vault!")
   602  							
   603  									// Withdraw tokens from the signer's stored vault
   604  									self.sentVault <- vaultRef.withdraw(amount: amount)
   605  								}
   606  							
   607  								execute {
   608  							
   609  									// Get the recipient's public account object
   610  									let recipient = getAccount(to)
   611  							
   612  									// Get a reference to the recipient's Receiver
   613  									let receiverRef = recipient.getCapability(/public/flowTokenReceiver)
   614  										.borrow<&{FungibleToken.Receiver}>()
   615  										?? panic("Could not borrow receiver reference to the recipient's Vault")
   616  							
   617  									// Deposit the withdrawn tokens in the recipient's receiver
   618  									receiverRef.deposit(from: <-self.sentVault)
   619  								}
   620  							}`, sc.FungibleToken.Address, sc.FlowToken.Address)),
   621  			)
   622  	}
   623  
   624  	runTx := func(tc testCase,
   625  		opts []fvm.Option,
   626  		bootstrapOpts []fvm.BootstrapProcedureOption) func(t *testing.T) {
   627  		return func(t *testing.T) {
   628  			// ==== Create an account ====
   629  			privateKey, createAccountTx := testutil.CreateAccountCreationTransaction(t, chain)
   630  
   631  			// this should return the address of newly created account
   632  			address, err := chain.AddressAtIndex(5)
   633  			require.NoError(t, err)
   634  
   635  			err = testutil.SignTransactionAsServiceAccount(createAccountTx, 0, chain)
   636  			require.NoError(t, err)
   637  
   638  			// ==== Transfer tokens to new account ====
   639  			transferTx := transferTokensTx(chain).
   640  				AddAuthorizer(chain.ServiceAddress()).
   641  				AddArgument(jsoncdc.MustEncode(cadence.UFix64(tc.fundWith))).
   642  				AddArgument(jsoncdc.MustEncode(cadence.NewAddress(address)))
   643  
   644  			transferTx.SetProposalKey(chain.ServiceAddress(), 0, 1)
   645  			transferTx.SetPayer(chain.ServiceAddress())
   646  
   647  			err = testutil.SignEnvelope(
   648  				transferTx,
   649  				chain.ServiceAddress(),
   650  				unittest.ServiceAccountPrivateKey,
   651  			)
   652  			require.NoError(t, err)
   653  
   654  			// ==== Transfer tokens from new account ====
   655  
   656  			transferTx2 := transferTokensTx(chain).
   657  				AddAuthorizer(address).
   658  				AddArgument(jsoncdc.MustEncode(cadence.UFix64(tc.tryToTransfer))).
   659  				AddArgument(jsoncdc.MustEncode(cadence.NewAddress(chain.ServiceAddress())))
   660  
   661  			transferTx2.SetProposalKey(address, 0, 0)
   662  			transferTx2.SetPayer(address)
   663  
   664  			err = testutil.SignEnvelope(
   665  				transferTx2,
   666  				address,
   667  				privateKey,
   668  			)
   669  			require.NoError(t, err)
   670  
   671  			cr := executeBlockAndVerifyWithParameters(t, [][]*flow.TransactionBody{
   672  				{
   673  					createAccountTx,
   674  				},
   675  				{
   676  					transferTx,
   677  				},
   678  				{
   679  					transferTx2,
   680  				},
   681  			}, opts, bootstrapOpts)
   682  
   683  			tc.checkResult(t, cr)
   684  		}
   685  	}
   686  
   687  	for i, tc := range testCases {
   688  		t.Run(fmt.Sprintf("Transaction Fees without storage %d: %s", i, tc.name), runTx(tc, []fvm.Option{
   689  			fvm.WithTransactionFeesEnabled(true),
   690  			fvm.WithAccountStorageLimit(false),
   691  		}, []fvm.BootstrapProcedureOption{
   692  			fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply),
   693  			fvm.WithTransactionFee(fvm.DefaultTransactionFees),
   694  		}))
   695  	}
   696  
   697  	for i, tc := range testCasesWithStorageEnabled {
   698  		t.Run(fmt.Sprintf("Transaction Fees with storage %d: %s", i, tc.name), runTx(tc, []fvm.Option{
   699  			fvm.WithTransactionFeesEnabled(true),
   700  			fvm.WithAccountStorageLimit(true),
   701  		}, []fvm.BootstrapProcedureOption{
   702  			fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply),
   703  			fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee),
   704  			fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation),
   705  			fvm.WithTransactionFee(fvm.DefaultTransactionFees),
   706  			fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW),
   707  		}))
   708  	}
   709  }
   710  
   711  func executeBlockAndVerifyWithParameters(t *testing.T,
   712  	txs [][]*flow.TransactionBody,
   713  	opts []fvm.Option,
   714  	bootstrapOpts []fvm.BootstrapProcedureOption) *execution.ComputationResult {
   715  	vm := fvm.NewVirtualMachine()
   716  
   717  	logger := zerolog.Nop()
   718  
   719  	opts = append(opts, fvm.WithChain(chain))
   720  	opts = append(opts, fvm.WithLogger(logger))
   721  	opts = append(opts, fvm.WithBlocks(&environment.NoopBlockFinder{}))
   722  
   723  	fvmContext :=
   724  		fvm.NewContext(
   725  			opts...,
   726  		)
   727  
   728  	collector := metrics.NewNoopCollector()
   729  	tracer := trace.NewNoopTracer()
   730  
   731  	wal := &fixtures.NoopWAL{}
   732  
   733  	ledger, err := completeLedger.NewLedger(wal, 100, collector, logger, completeLedger.DefaultPathFinderVersion)
   734  	require.NoError(t, err)
   735  
   736  	compactor := fixtures.NewNoopCompactor(ledger)
   737  	<-compactor.Ready()
   738  	defer func() {
   739  		<-ledger.Done()
   740  		<-compactor.Done()
   741  	}()
   742  
   743  	bootstrapper := bootstrapexec.NewBootstrapper(logger)
   744  
   745  	initialCommit, err := bootstrapper.BootstrapLedger(
   746  		ledger,
   747  		unittest.ServiceAccountPublicKey,
   748  		chain,
   749  		bootstrapOpts...,
   750  	)
   751  
   752  	require.NoError(t, err)
   753  
   754  	ledgerCommiter := committer.NewLedgerViewCommitter(ledger, tracer)
   755  
   756  	bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore())))
   757  	trackerStorage := mocktracker.NewMockStorage()
   758  
   759  	prov := exedataprovider.NewProvider(
   760  		zerolog.Nop(),
   761  		metrics.NewNoopCollector(),
   762  		execution_data.DefaultSerializer,
   763  		bservice,
   764  		trackerStorage,
   765  	)
   766  
   767  	// generates signing identity including staking key for signing
   768  	myIdentity := unittest.IdentityFixture()
   769  	seed := make([]byte, crypto.KeyGenSeedMinLen)
   770  	n, err := rand.Read(seed)
   771  	require.Equal(t, n, crypto.KeyGenSeedMinLen)
   772  	require.NoError(t, err)
   773  	sk, err := crypto.GeneratePrivateKey(crypto.BLSBLS12381, seed)
   774  	require.NoError(t, err)
   775  	myIdentity.StakingPubKey = sk.PublicKey()
   776  	me := mocklocal.NewMockLocal(sk, myIdentity.ID(), t)
   777  
   778  	// used by computer to generate the prng used in the service tx
   779  	stateForRandomSource := testutil.ProtocolStateWithSourceFixture(nil)
   780  
   781  	blockComputer, err := computer.NewBlockComputer(
   782  		vm,
   783  		fvmContext,
   784  		collector,
   785  		tracer,
   786  		logger,
   787  		ledgerCommiter,
   788  		me,
   789  		prov,
   790  		nil,
   791  		stateForRandomSource,
   792  		testVerifyMaxConcurrency)
   793  	require.NoError(t, err)
   794  
   795  	executableBlock := unittest.ExecutableBlockFromTransactions(chain.ChainID(), txs)
   796  	executableBlock.StartState = &initialCommit
   797  
   798  	prevResultId := unittest.IdentifierFixture()
   799  	computationResult, err := blockComputer.ExecuteBlock(
   800  		context.Background(),
   801  		prevResultId,
   802  		executableBlock,
   803  		state.NewLedgerStorageSnapshot(
   804  			ledger,
   805  			initialCommit),
   806  		derived.NewEmptyDerivedBlockData(0))
   807  	require.NoError(t, err)
   808  
   809  	spockHasher := utils.NewSPOCKHasher()
   810  
   811  	for i := 0; i < computationResult.BlockExecutionResult.Size(); i++ {
   812  		res := computationResult.CollectionExecutionResultAt(i)
   813  		snapshot := res.ExecutionSnapshot()
   814  		valid, err := crypto.SPOCKVerifyAgainstData(
   815  			myIdentity.StakingPubKey,
   816  			computationResult.Spocks[i],
   817  			snapshot.SpockSecret,
   818  			spockHasher)
   819  		require.NoError(t, err)
   820  		require.True(t, valid)
   821  	}
   822  
   823  	receipt := computationResult.ExecutionReceipt
   824  	receiptID := receipt.ID()
   825  	valid, err := myIdentity.StakingPubKey.Verify(
   826  		receipt.ExecutorSignature,
   827  		receiptID[:],
   828  		utils.NewExecutionReceiptHasher())
   829  
   830  	require.NoError(t, err)
   831  	require.True(t, valid)
   832  
   833  	chdps := computationResult.AllChunkDataPacks()
   834  	require.Equal(t, len(chdps), len(receipt.Spocks))
   835  
   836  	er := &computationResult.ExecutionResult
   837  
   838  	verifier := chunks.NewChunkVerifier(vm, fvmContext, logger)
   839  
   840  	vcds := make([]*verification.VerifiableChunkData, er.Chunks.Len())
   841  
   842  	for i, chunk := range er.Chunks {
   843  		isSystemChunk := i == er.Chunks.Len()-1
   844  		offsetForChunk, err := fetcher.TransactionOffsetForChunk(er.Chunks, chunk.Index)
   845  		require.NoError(t, err)
   846  
   847  		vcds[i] = &verification.VerifiableChunkData{
   848  			IsSystemChunk:     isSystemChunk,
   849  			Chunk:             chunk,
   850  			Header:            executableBlock.Block.Header,
   851  			Result:            er,
   852  			ChunkDataPack:     chdps[i],
   853  			EndState:          chunk.EndState,
   854  			TransactionOffset: offsetForChunk,
   855  			// returns the same RandomSource used by the computer
   856  			Snapshot: stateForRandomSource.AtBlockID(chunk.BlockID),
   857  		}
   858  	}
   859  
   860  	require.Len(t, vcds, len(txs)+1) // +1 for system chunk
   861  
   862  	for _, vcd := range vcds {
   863  		spockSecret, err := verifier.Verify(vcd)
   864  		assert.NoError(t, err)
   865  		assert.NotNil(t, spockSecret)
   866  	}
   867  
   868  	return computationResult
   869  }
   870  
   871  func executeBlockAndVerify(t *testing.T,
   872  	txs [][]*flow.TransactionBody,
   873  	txFees fvm.BootstrapProcedureFeeParameters,
   874  	minStorageBalance cadence.UFix64) *execution.ComputationResult {
   875  	return executeBlockAndVerifyWithParameters(t,
   876  		txs,
   877  		[]fvm.Option{
   878  			fvm.WithTransactionFeesEnabled(true),
   879  			fvm.WithAccountStorageLimit(true),
   880  		}, []fvm.BootstrapProcedureOption{
   881  			fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply),
   882  			fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee),
   883  			fvm.WithMinimumStorageReservation(minStorageBalance),
   884  			fvm.WithTransactionFee(txFees),
   885  			fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW),
   886  		})
   887  }