github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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/boxo/blockstore"
    10  	"github.com/ipfs/go-datastore"
    11  	dssync "github.com/ipfs/go-datastore/sync"
    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/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  			`access(all) contract Foo {
    70  				access(all) event FooEvent(x: Int, y: Int)
    71  
    72  				access(all) fun emitEvent() { 
    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.emitEvent()
    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  			`access(all) contract Foo {
   114  				access(all) event FooEvent(x: Int, y: Int)
   115  
   116  				access(all) fun emitEvent() { 
   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.emitEvent()
   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(systemcontracts.LastSystemAccountIndex + 1)
   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.00011661")
   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(), 14)
   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(), 5)
   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(systemcontracts.LastSystemAccountIndex + 1)
   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.SetComputeLimit(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, 14, 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, 5, 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, 1)
   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, 1)
   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, 1)
   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, 1)
   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, 1)
   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: auth(BorrowValue) &Account) {
   598  							
   599  									// Get a reference to the signer's stored vault
   600  									let vaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &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.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
   614  										?? panic("Could not borrow receiver reference to the recipient's Vault")
   615  							
   616  									// Deposit the withdrawn tokens in the recipient's receiver
   617  									receiverRef.deposit(from: <-self.sentVault)
   618  								}
   619  							}`, sc.FungibleToken.Address, sc.FlowToken.Address)),
   620  			)
   621  	}
   622  
   623  	runTx := func(tc testCase,
   624  		opts []fvm.Option,
   625  		bootstrapOpts []fvm.BootstrapProcedureOption) func(t *testing.T) {
   626  		return func(t *testing.T) {
   627  			// ==== Create an account ====
   628  			privateKey, createAccountTx := testutil.CreateAccountCreationTransaction(t, chain)
   629  
   630  			// this should return the address of newly created account
   631  			address, err := chain.AddressAtIndex(systemcontracts.LastSystemAccountIndex + 1)
   632  			require.NoError(t, err)
   633  
   634  			err = testutil.SignTransactionAsServiceAccount(createAccountTx, 0, chain)
   635  			require.NoError(t, err)
   636  
   637  			// ==== Transfer tokens to new account ====
   638  			transferTx := transferTokensTx(chain).
   639  				AddAuthorizer(chain.ServiceAddress()).
   640  				AddArgument(jsoncdc.MustEncode(cadence.UFix64(tc.fundWith))).
   641  				AddArgument(jsoncdc.MustEncode(cadence.NewAddress(address)))
   642  
   643  			transferTx.SetProposalKey(chain.ServiceAddress(), 0, 1)
   644  			transferTx.SetPayer(chain.ServiceAddress())
   645  
   646  			err = testutil.SignEnvelope(
   647  				transferTx,
   648  				chain.ServiceAddress(),
   649  				unittest.ServiceAccountPrivateKey,
   650  			)
   651  			require.NoError(t, err)
   652  
   653  			// ==== Transfer tokens from new account ====
   654  
   655  			transferTx2 := transferTokensTx(chain).
   656  				AddAuthorizer(address).
   657  				AddArgument(jsoncdc.MustEncode(cadence.UFix64(tc.tryToTransfer))).
   658  				AddArgument(jsoncdc.MustEncode(cadence.NewAddress(chain.ServiceAddress())))
   659  
   660  			transferTx2.SetProposalKey(address, 0, 0)
   661  			transferTx2.SetPayer(address)
   662  
   663  			err = testutil.SignEnvelope(
   664  				transferTx2,
   665  				address,
   666  				privateKey,
   667  			)
   668  			require.NoError(t, err)
   669  
   670  			cr := executeBlockAndVerifyWithParameters(t, [][]*flow.TransactionBody{
   671  				{
   672  					createAccountTx,
   673  				},
   674  				{
   675  					transferTx,
   676  				},
   677  				{
   678  					transferTx2,
   679  				},
   680  			}, opts, bootstrapOpts)
   681  
   682  			tc.checkResult(t, cr)
   683  		}
   684  	}
   685  
   686  	for i, tc := range testCases {
   687  		t.Run(fmt.Sprintf("Transaction Fees without storage %d: %s", i, tc.name), runTx(tc, []fvm.Option{
   688  			fvm.WithTransactionFeesEnabled(true),
   689  			fvm.WithAccountStorageLimit(false),
   690  		}, []fvm.BootstrapProcedureOption{
   691  			fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply),
   692  			fvm.WithTransactionFee(fvm.DefaultTransactionFees),
   693  		}))
   694  	}
   695  
   696  	for i, tc := range testCasesWithStorageEnabled {
   697  		t.Run(fmt.Sprintf("Transaction Fees with storage %d: %s", i, tc.name), runTx(tc, []fvm.Option{
   698  			fvm.WithTransactionFeesEnabled(true),
   699  			fvm.WithAccountStorageLimit(true),
   700  		}, []fvm.BootstrapProcedureOption{
   701  			fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply),
   702  			fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee),
   703  			fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation),
   704  			fvm.WithTransactionFee(fvm.DefaultTransactionFees),
   705  			fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW),
   706  		}))
   707  	}
   708  }
   709  
   710  func executeBlockAndVerifyWithParameters(t *testing.T,
   711  	txs [][]*flow.TransactionBody,
   712  	opts []fvm.Option,
   713  	bootstrapOpts []fvm.BootstrapProcedureOption) *execution.ComputationResult {
   714  	vm := fvm.NewVirtualMachine()
   715  
   716  	logger := zerolog.Nop()
   717  
   718  	opts = append(opts, fvm.WithChain(chain))
   719  	opts = append(opts, fvm.WithLogger(logger))
   720  	opts = append(opts, fvm.WithBlocks(&environment.NoopBlockFinder{}))
   721  
   722  	fvmContext :=
   723  		fvm.NewContext(
   724  			opts...,
   725  		)
   726  
   727  	collector := metrics.NewNoopCollector()
   728  	tracer := trace.NewNoopTracer()
   729  
   730  	wal := &fixtures.NoopWAL{}
   731  
   732  	ledger, err := completeLedger.NewLedger(wal, 100, collector, logger, completeLedger.DefaultPathFinderVersion)
   733  	require.NoError(t, err)
   734  
   735  	compactor := fixtures.NewNoopCompactor(ledger)
   736  	<-compactor.Ready()
   737  	defer func() {
   738  		<-ledger.Done()
   739  		<-compactor.Done()
   740  	}()
   741  
   742  	bootstrapper := bootstrapexec.NewBootstrapper(logger)
   743  
   744  	initialCommit, err := bootstrapper.BootstrapLedger(
   745  		ledger,
   746  		unittest.ServiceAccountPublicKey,
   747  		chain,
   748  		bootstrapOpts...,
   749  	)
   750  
   751  	require.NoError(t, err)
   752  
   753  	ledgerCommiter := committer.NewLedgerViewCommitter(ledger, tracer)
   754  
   755  	bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore())))
   756  	trackerStorage := mocktracker.NewMockStorage()
   757  
   758  	prov := exedataprovider.NewProvider(
   759  		zerolog.Nop(),
   760  		metrics.NewNoopCollector(),
   761  		execution_data.DefaultSerializer,
   762  		bservice,
   763  		trackerStorage,
   764  	)
   765  
   766  	// generates signing identity including staking key for signing
   767  	myIdentity := unittest.IdentityFixture()
   768  	seed := make([]byte, crypto.KeyGenSeedMinLen)
   769  	n, err := rand.Read(seed)
   770  	require.Equal(t, n, crypto.KeyGenSeedMinLen)
   771  	require.NoError(t, err)
   772  	sk, err := crypto.GeneratePrivateKey(crypto.BLSBLS12381, seed)
   773  	require.NoError(t, err)
   774  	myIdentity.StakingPubKey = sk.PublicKey()
   775  	me := mocklocal.NewMockLocal(sk, myIdentity.ID(), t)
   776  
   777  	// used by computer to generate the prng used in the service tx
   778  	stateForRandomSource := testutil.ProtocolStateWithSourceFixture(nil)
   779  
   780  	blockComputer, err := computer.NewBlockComputer(
   781  		vm,
   782  		fvmContext,
   783  		collector,
   784  		tracer,
   785  		logger,
   786  		ledgerCommiter,
   787  		me,
   788  		prov,
   789  		nil,
   790  		stateForRandomSource,
   791  		testVerifyMaxConcurrency)
   792  	require.NoError(t, err)
   793  
   794  	executableBlock := unittest.ExecutableBlockFromTransactions(chain.ChainID(), txs)
   795  	executableBlock.StartState = &initialCommit
   796  
   797  	prevResultId := unittest.IdentifierFixture()
   798  	computationResult, err := blockComputer.ExecuteBlock(
   799  		context.Background(),
   800  		prevResultId,
   801  		executableBlock,
   802  		state.NewLedgerStorageSnapshot(
   803  			ledger,
   804  			initialCommit),
   805  		derived.NewEmptyDerivedBlockData(0))
   806  	require.NoError(t, err)
   807  
   808  	spockHasher := utils.NewSPOCKHasher()
   809  
   810  	for i := 0; i < computationResult.BlockExecutionResult.Size(); i++ {
   811  		res := computationResult.CollectionExecutionResultAt(i)
   812  		snapshot := res.ExecutionSnapshot()
   813  		valid, err := crypto.SPOCKVerifyAgainstData(
   814  			myIdentity.StakingPubKey,
   815  			computationResult.Spocks[i],
   816  			snapshot.SpockSecret,
   817  			spockHasher)
   818  		require.NoError(t, err)
   819  		require.True(t, valid)
   820  	}
   821  
   822  	receipt := computationResult.ExecutionReceipt
   823  	receiptID := receipt.ID()
   824  	valid, err := myIdentity.StakingPubKey.Verify(
   825  		receipt.ExecutorSignature,
   826  		receiptID[:],
   827  		utils.NewExecutionReceiptHasher())
   828  
   829  	require.NoError(t, err)
   830  	require.True(t, valid)
   831  
   832  	chdps := computationResult.AllChunkDataPacks()
   833  	require.Equal(t, len(chdps), len(receipt.Spocks))
   834  
   835  	er := &computationResult.ExecutionResult
   836  
   837  	verifier := chunks.NewChunkVerifier(vm, fvmContext, logger)
   838  
   839  	vcds := make([]*verification.VerifiableChunkData, er.Chunks.Len())
   840  
   841  	for i, chunk := range er.Chunks {
   842  		isSystemChunk := i == er.Chunks.Len()-1
   843  		offsetForChunk, err := fetcher.TransactionOffsetForChunk(er.Chunks, chunk.Index)
   844  		require.NoError(t, err)
   845  
   846  		vcds[i] = &verification.VerifiableChunkData{
   847  			IsSystemChunk:     isSystemChunk,
   848  			Chunk:             chunk,
   849  			Header:            executableBlock.Block.Header,
   850  			Result:            er,
   851  			ChunkDataPack:     chdps[i],
   852  			EndState:          chunk.EndState,
   853  			TransactionOffset: offsetForChunk,
   854  			// returns the same RandomSource used by the computer
   855  			Snapshot: stateForRandomSource.AtBlockID(chunk.BlockID),
   856  		}
   857  	}
   858  
   859  	require.Len(t, vcds, len(txs)+1) // +1 for system chunk
   860  
   861  	for _, vcd := range vcds {
   862  		spockSecret, err := verifier.Verify(vcd)
   863  		assert.NoError(t, err)
   864  		assert.NotNil(t, spockSecret)
   865  	}
   866  
   867  	return computationResult
   868  }
   869  
   870  func executeBlockAndVerify(t *testing.T,
   871  	txs [][]*flow.TransactionBody,
   872  	txFees fvm.BootstrapProcedureFeeParameters,
   873  	minStorageBalance cadence.UFix64) *execution.ComputationResult {
   874  	return executeBlockAndVerifyWithParameters(t,
   875  		txs,
   876  		[]fvm.Option{
   877  			fvm.WithTransactionFeesEnabled(true),
   878  			fvm.WithAccountStorageLimit(true),
   879  		}, []fvm.BootstrapProcedureOption{
   880  			fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply),
   881  			fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee),
   882  			fvm.WithMinimumStorageReservation(minStorageBalance),
   883  			fvm.WithTransactionFee(txFees),
   884  			fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW),
   885  		})
   886  }