github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/environment/programs_test.go (about)

     1  package environment_test
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/onflow/cadence/runtime/common"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/onflow/flow-go/fvm"
    13  	"github.com/onflow/flow-go/fvm/environment"
    14  	"github.com/onflow/flow-go/fvm/storage"
    15  	"github.com/onflow/flow-go/fvm/storage/derived"
    16  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    17  	"github.com/onflow/flow-go/fvm/storage/state"
    18  	"github.com/onflow/flow-go/model/flow"
    19  )
    20  
    21  var (
    22  	addressA = flow.HexToAddress("0a")
    23  	addressB = flow.HexToAddress("0b")
    24  	addressC = flow.HexToAddress("0c")
    25  
    26  	contractALocation = common.AddressLocation{
    27  		Address: common.MustBytesToAddress(addressA.Bytes()),
    28  		Name:    "A",
    29  	}
    30  	contractA2Location = common.AddressLocation{
    31  		Address: common.MustBytesToAddress(addressA.Bytes()),
    32  		Name:    "A2",
    33  	}
    34  
    35  	contractBLocation = common.AddressLocation{
    36  		Address: common.MustBytesToAddress(addressB.Bytes()),
    37  		Name:    "B",
    38  	}
    39  
    40  	contractCLocation = common.AddressLocation{
    41  		Address: common.MustBytesToAddress(addressC.Bytes()),
    42  		Name:    "C",
    43  	}
    44  
    45  	contractA0Code = `
    46  		access(all) contract A {
    47  			access(all) struct interface Foo{}
    48  			
    49  			access(all) fun hello(): String {
    50          		return "bad version"
    51      		}
    52  		}
    53  	`
    54  
    55  	contractACode = `
    56  		access(all) contract A {
    57  			access(all) struct interface Foo{}
    58  
    59  			access(all) fun hello(): String {
    60          		return "hello from A"
    61      		}
    62  		}
    63  	`
    64  
    65  	contractA2Code = `
    66  		access(all) contract A2 {
    67  			access(all) struct interface Foo{}
    68  
    69  			access(all) fun hello(): String {
    70          		return "hello from A2"
    71      		}
    72  		}
    73  	`
    74  
    75  	contractABreakingCode = `
    76  		access(all) contract A {
    77  			access(all) struct interface Foo{
    78  				access(all) fun hello()
    79  			}
    80  	
    81  			access(all) fun hello(): String {
    82  	  		return "hello from A with breaking change"
    83  			}
    84  		}
    85  	`
    86  
    87  	contractBCode = `
    88  		import 0xa
    89  
    90  		access(all) contract B {
    91  			access(all) struct Bar : A.Foo {}
    92  
    93  			access(all) fun hello(): String {
    94         			return "hello from B but also ".concat(A.hello())
    95      		}
    96  		}
    97  	`
    98  
    99  	contractCCode = `
   100  		import B from 0xb
   101  		import A from 0xa
   102  
   103  		access(all) contract C {
   104              access(all) struct Bar : A.Foo {}
   105  
   106  			access(all) fun hello(): String {
   107  	   			return "hello from C, ".concat(B.hello())
   108  			}
   109  		}
   110  	`
   111  )
   112  
   113  func setupProgramsTest(t *testing.T) snapshot.SnapshotTree {
   114  	blockDatabase := storage.NewBlockDatabase(nil, 0, nil)
   115  	txnState, err := blockDatabase.NewTransaction(0, state.DefaultParameters())
   116  	require.NoError(t, err)
   117  
   118  	accounts := environment.NewAccounts(txnState)
   119  
   120  	err = accounts.Create(nil, addressA)
   121  	require.NoError(t, err)
   122  
   123  	err = accounts.Create(nil, addressB)
   124  	require.NoError(t, err)
   125  
   126  	err = accounts.Create(nil, addressC)
   127  	require.NoError(t, err)
   128  
   129  	executionSnapshot, err := txnState.FinalizeMainTransaction()
   130  	require.NoError(t, err)
   131  
   132  	return snapshot.NewSnapshotTree(nil).Append(executionSnapshot)
   133  }
   134  
   135  func getTestContract(
   136  	snapshot snapshot.StorageSnapshot,
   137  	location common.AddressLocation,
   138  ) (
   139  	[]byte,
   140  	error,
   141  ) {
   142  	env := environment.NewScriptEnvironmentFromStorageSnapshot(
   143  		environment.DefaultEnvironmentParams(),
   144  		snapshot)
   145  	return env.GetAccountContractCode(location)
   146  }
   147  
   148  func Test_Programs(t *testing.T) {
   149  	vm := fvm.NewVirtualMachine()
   150  	derivedBlockData := derived.NewEmptyDerivedBlockData(0)
   151  
   152  	mainSnapshot := setupProgramsTest(t)
   153  
   154  	context := fvm.NewContext(
   155  		fvm.WithContractDeploymentRestricted(false),
   156  		fvm.WithAuthorizationChecksEnabled(false),
   157  		fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
   158  		fvm.WithCadenceLogging(true),
   159  		fvm.WithDerivedBlockData(derivedBlockData))
   160  
   161  	var contractASnapshot *snapshot.ExecutionSnapshot
   162  	var contractBSnapshot *snapshot.ExecutionSnapshot
   163  	var txASnapshot *snapshot.ExecutionSnapshot
   164  
   165  	t.Run("contracts can be updated", func(t *testing.T) {
   166  		retrievedContractA, err := getTestContract(
   167  			mainSnapshot,
   168  			contractALocation)
   169  		require.NoError(t, err)
   170  		require.Empty(t, retrievedContractA)
   171  
   172  		// deploy contract A0
   173  		executionSnapshot, output, err := vm.Run(
   174  			context,
   175  			fvm.Transaction(
   176  				contractDeployTx("A", contractA0Code, addressA),
   177  				derivedBlockData.NextTxIndexForTestingOnly()),
   178  			mainSnapshot)
   179  		require.NoError(t, err)
   180  		require.NoError(t, output.Err)
   181  
   182  		mainSnapshot = mainSnapshot.Append(executionSnapshot)
   183  
   184  		retrievedContractA, err = getTestContract(
   185  			mainSnapshot,
   186  			contractALocation)
   187  		require.NoError(t, err)
   188  
   189  		require.Equal(t, contractA0Code, string(retrievedContractA))
   190  
   191  		// deploy contract A
   192  		executionSnapshot, output, err = vm.Run(
   193  			context,
   194  			fvm.Transaction(
   195  				updateContractTx("A", contractACode, addressA),
   196  				derivedBlockData.NextTxIndexForTestingOnly()),
   197  			mainSnapshot)
   198  		require.NoError(t, err)
   199  		require.NoError(t, output.Err)
   200  
   201  		mainSnapshot = mainSnapshot.Append(executionSnapshot)
   202  
   203  		retrievedContractA, err = getTestContract(
   204  			mainSnapshot,
   205  			contractALocation)
   206  		require.NoError(t, err)
   207  
   208  		require.Equal(t, contractACode, string(retrievedContractA))
   209  
   210  	})
   211  	t.Run("register touches are captured for simple contract A", func(t *testing.T) {
   212  		t.Log("---------- Real transaction here ------------")
   213  
   214  		// run a TX using contract A
   215  
   216  		loadedCode := false
   217  		execASnapshot := snapshot.NewReadFuncStorageSnapshot(
   218  			func(id flow.RegisterID) (flow.RegisterValue, error) {
   219  				expectedId := flow.ContractRegisterID(
   220  					flow.BytesToAddress([]byte(id.Owner)),
   221  					"A")
   222  				if id == expectedId {
   223  					loadedCode = true
   224  				}
   225  
   226  				return mainSnapshot.Get(id)
   227  			})
   228  
   229  		executionSnapshotA, output, err := vm.Run(
   230  			context,
   231  			fvm.Transaction(
   232  				callTx("A", addressA),
   233  				derivedBlockData.NextTxIndexForTestingOnly()),
   234  			execASnapshot)
   235  		require.NoError(t, err)
   236  		require.NoError(t, output.Err)
   237  
   238  		mainSnapshot = mainSnapshot.Append(executionSnapshotA)
   239  
   240  		// make sure tx was really run
   241  		require.Contains(t, output.Logs, "\"hello from A\"")
   242  
   243  		// Make sure the code has been loaded from storage
   244  		require.True(t, loadedCode)
   245  
   246  		entry := derivedBlockData.GetProgramForTestingOnly(contractALocation)
   247  		require.NotNil(t, entry)
   248  		cached := derivedBlockData.CachedPrograms()
   249  		require.Equal(t, 1, cached)
   250  
   251  		// assert dependencies are correct
   252  		require.Equal(t, 1, entry.Value.Dependencies.Count())
   253  		require.True(t, entry.Value.Dependencies.ContainsLocation(contractALocation))
   254  
   255  		// assert some reads were recorded (at least loading of code)
   256  		require.NotEmpty(t, entry.ExecutionSnapshot.ReadSet)
   257  
   258  		contractASnapshot = entry.ExecutionSnapshot
   259  		txASnapshot = executionSnapshotA
   260  
   261  		// execute transaction again, this time make sure it doesn't load code
   262  		execA2Snapshot := snapshot.NewReadFuncStorageSnapshot(
   263  			func(id flow.RegisterID) (flow.RegisterValue, error) {
   264  				notId := flow.ContractRegisterID(
   265  					flow.BytesToAddress([]byte(id.Owner)),
   266  					"A")
   267  				// this time we fail if a read of code occurs
   268  				require.NotEqual(t, id, notId)
   269  
   270  				return mainSnapshot.Get(id)
   271  			})
   272  
   273  		executionSnapshotA2, output, err := vm.Run(
   274  			context,
   275  			fvm.Transaction(
   276  				callTx("A", addressA),
   277  				derivedBlockData.NextTxIndexForTestingOnly()),
   278  			execA2Snapshot)
   279  		require.NoError(t, err)
   280  		require.NoError(t, output.Err)
   281  
   282  		mainSnapshot = mainSnapshot.Append(executionSnapshotA2)
   283  
   284  		require.Contains(t, output.Logs, "\"hello from A\"")
   285  
   286  		// same transaction should produce the exact same execution snapshots
   287  		// but only because we don't do any conditional update in a tx
   288  		compareExecutionSnapshots(t, executionSnapshotA, executionSnapshotA2)
   289  	})
   290  
   291  	t.Run("deploying another contract invalidates dependant programs", func(t *testing.T) {
   292  		// deploy contract B
   293  		executionSnapshot, output, err := vm.Run(
   294  			context,
   295  			fvm.Transaction(
   296  				contractDeployTx("B", contractBCode, addressB),
   297  				derivedBlockData.NextTxIndexForTestingOnly()),
   298  			mainSnapshot)
   299  		require.NoError(t, err)
   300  		require.NoError(t, output.Err)
   301  
   302  		mainSnapshot = mainSnapshot.Append(executionSnapshot)
   303  
   304  		// b and c are invalid
   305  		entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation)
   306  		entryC := derivedBlockData.GetProgramForTestingOnly(contractCLocation)
   307  		// a is still valid
   308  		entryA := derivedBlockData.GetProgramForTestingOnly(contractALocation)
   309  
   310  		require.Nil(t, entryB)
   311  		require.Nil(t, entryC)
   312  		require.NotNil(t, entryA)
   313  
   314  		cached := derivedBlockData.CachedPrograms()
   315  		require.Equal(t, 1, cached)
   316  	})
   317  
   318  	t.Run("contract B imports contract A", func(t *testing.T) {
   319  
   320  		// programs should have no entries for A and B, as per previous test
   321  
   322  		// run a TX using contract B
   323  
   324  		executionSnapshotB, output, err := vm.Run(
   325  			context,
   326  			fvm.Transaction(
   327  				callTx("B", addressB),
   328  				derivedBlockData.NextTxIndexForTestingOnly()),
   329  			mainSnapshot)
   330  		require.NoError(t, err)
   331  
   332  		mainSnapshot = mainSnapshot.Append(executionSnapshotB)
   333  
   334  		require.Contains(t, output.Logs, "\"hello from B but also hello from A\"")
   335  
   336  		entry := derivedBlockData.GetProgramForTestingOnly(contractALocation)
   337  		require.NotNil(t, entry)
   338  
   339  		// state should be essentially the same as one which we got in tx with contract A
   340  		require.Equal(t, contractASnapshot, entry.ExecutionSnapshot)
   341  
   342  		entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation)
   343  		require.NotNil(t, entryB)
   344  
   345  		// assert dependencies are correct
   346  		require.Equal(t, 2, entryB.Value.Dependencies.Count())
   347  		require.NotNil(t, entryB.Value.Dependencies.ContainsLocation(contractALocation))
   348  		require.NotNil(t, entryB.Value.Dependencies.ContainsLocation(contractBLocation))
   349  
   350  		// program B should contain all the registers used by program A, as it depends on it
   351  		contractBSnapshot = entryB.ExecutionSnapshot
   352  
   353  		require.Empty(t, contractASnapshot.WriteSet)
   354  
   355  		for id := range contractASnapshot.ReadSet {
   356  			_, ok := contractBSnapshot.ReadSet[id]
   357  			require.True(t, ok)
   358  		}
   359  
   360  		// rerun transaction
   361  
   362  		// execute transaction again, this time make sure it doesn't load code
   363  		execB2Snapshot := snapshot.NewReadFuncStorageSnapshot(
   364  			func(id flow.RegisterID) (flow.RegisterValue, error) {
   365  				idA := flow.ContractRegisterID(
   366  					flow.BytesToAddress([]byte(id.Owner)),
   367  					"A")
   368  				idB := flow.ContractRegisterID(
   369  					flow.BytesToAddress([]byte(id.Owner)),
   370  					"B")
   371  				// this time we fail if a read of code occurs
   372  				require.NotEqual(t, id.Key, idA.Key)
   373  				require.NotEqual(t, id.Key, idB.Key)
   374  
   375  				return mainSnapshot.Get(id)
   376  			})
   377  
   378  		executionSnapshotB2, output, err := vm.Run(
   379  			context,
   380  			fvm.Transaction(
   381  				callTx("B", addressB),
   382  				derivedBlockData.NextTxIndexForTestingOnly()),
   383  			execB2Snapshot)
   384  		require.NoError(t, err)
   385  		require.NoError(t, output.Err)
   386  
   387  		require.Contains(t, output.Logs, "\"hello from B but also hello from A\"")
   388  
   389  		mainSnapshot = mainSnapshot.Append(executionSnapshotB2)
   390  
   391  		compareExecutionSnapshots(t, executionSnapshotB, executionSnapshotB2)
   392  	})
   393  
   394  	t.Run("deploying new contract A2 invalidates B because of * imports", func(t *testing.T) {
   395  		// deploy contract A2
   396  		executionSnapshot, output, err := vm.Run(
   397  			context,
   398  			fvm.Transaction(
   399  				contractDeployTx("A2", contractA2Code, addressA),
   400  				derivedBlockData.NextTxIndexForTestingOnly()),
   401  			mainSnapshot)
   402  		require.NoError(t, err)
   403  		require.NoError(t, output.Err)
   404  
   405  		mainSnapshot = mainSnapshot.Append(executionSnapshot)
   406  
   407  		// a, b and c are invalid
   408  		entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation)
   409  		entryC := derivedBlockData.GetProgramForTestingOnly(contractCLocation)
   410  		entryA := derivedBlockData.GetProgramForTestingOnly(contractALocation)
   411  
   412  		require.Nil(t, entryB) // B could have star imports to 0xa, so it's invalidated
   413  		require.Nil(t, entryC) // still invalid
   414  		require.Nil(t, entryA) // A could have star imports to 0xa, so it's invalidated
   415  
   416  		cached := derivedBlockData.CachedPrograms()
   417  		require.Equal(t, 0, cached)
   418  	})
   419  
   420  	t.Run("contract B imports contract A and A2 because of * import", func(t *testing.T) {
   421  
   422  		// programs should have no entries for A and B, as per previous test
   423  
   424  		// run a TX using contract B
   425  
   426  		executionSnapshotB, output, err := vm.Run(
   427  			context,
   428  			fvm.Transaction(
   429  				callTx("B", addressB),
   430  				derivedBlockData.NextTxIndexForTestingOnly()),
   431  			mainSnapshot)
   432  		require.NoError(t, err)
   433  		require.NoError(t, output.Err)
   434  
   435  		require.Contains(t, output.Logs, "\"hello from B but also hello from A\"")
   436  
   437  		mainSnapshot = mainSnapshot.Append(executionSnapshotB)
   438  
   439  		entry := derivedBlockData.GetProgramForTestingOnly(contractALocation)
   440  		require.NotNil(t, entry)
   441  
   442  		// state should be essentially the same as one which we got in tx with contract A
   443  		require.Equal(t, contractASnapshot, entry.ExecutionSnapshot)
   444  
   445  		entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation)
   446  		require.NotNil(t, entryB)
   447  
   448  		// assert dependencies are correct
   449  		require.Equal(t, 3, entryB.Value.Dependencies.Count())
   450  		require.NotNil(t, entryB.Value.Dependencies.ContainsLocation(contractALocation))
   451  		require.NotNil(t, entryB.Value.Dependencies.ContainsLocation(contractBLocation))
   452  		require.NotNil(t, entryB.Value.Dependencies.ContainsLocation(contractA2Location))
   453  
   454  		// program B should contain all the registers used by program A, as it depends on it
   455  		contractBSnapshot = entryB.ExecutionSnapshot
   456  
   457  		require.Empty(t, contractASnapshot.WriteSet)
   458  
   459  		for id := range contractASnapshot.ReadSet {
   460  			_, ok := contractBSnapshot.ReadSet[id]
   461  			require.True(t, ok)
   462  		}
   463  
   464  		// rerun transaction
   465  
   466  		// execute transaction again, this time make sure it doesn't load code
   467  		execB2Snapshot := snapshot.NewReadFuncStorageSnapshot(
   468  			func(id flow.RegisterID) (flow.RegisterValue, error) {
   469  				idA := flow.ContractRegisterID(
   470  					flow.BytesToAddress([]byte(id.Owner)),
   471  					"A")
   472  				idA2 := flow.ContractRegisterID(
   473  					flow.BytesToAddress([]byte(id.Owner)),
   474  					"A2")
   475  				idB := flow.ContractRegisterID(
   476  					flow.BytesToAddress([]byte(id.Owner)),
   477  					"B")
   478  				// this time we fail if a read of code occurs
   479  				require.NotEqual(t, id.Key, idA.Key)
   480  				require.NotEqual(t, id.Key, idA2.Key)
   481  				require.NotEqual(t, id.Key, idB.Key)
   482  
   483  				return mainSnapshot.Get(id)
   484  			})
   485  
   486  		executionSnapshotB2, output, err := vm.Run(
   487  			context,
   488  			fvm.Transaction(
   489  				callTx("B", addressB),
   490  				derivedBlockData.NextTxIndexForTestingOnly()),
   491  			execB2Snapshot)
   492  		require.NoError(t, err)
   493  		require.NoError(t, output.Err)
   494  
   495  		require.Contains(t, output.Logs, "\"hello from B but also hello from A\"")
   496  
   497  		mainSnapshot = mainSnapshot.Append(executionSnapshotB2)
   498  
   499  		compareExecutionSnapshots(t, executionSnapshotB, executionSnapshotB2)
   500  	})
   501  
   502  	t.Run("contract A runs from cache after program B has been loaded", func(t *testing.T) {
   503  
   504  		// at this point programs cache should contain data for contract A
   505  		// only because contract B has been called
   506  
   507  		execASnapshot := snapshot.NewReadFuncStorageSnapshot(
   508  			func(id flow.RegisterID) (flow.RegisterValue, error) {
   509  				notId := flow.ContractRegisterID(
   510  					flow.BytesToAddress([]byte(id.Owner)),
   511  					"A")
   512  				require.NotEqual(t, id, notId)
   513  				return mainSnapshot.Get(id)
   514  			})
   515  
   516  		// run a TX using contract A
   517  		executionSnapshot, output, err := vm.Run(
   518  			context,
   519  			fvm.Transaction(
   520  				callTx("A", addressA),
   521  				derivedBlockData.NextTxIndexForTestingOnly()),
   522  			execASnapshot)
   523  		require.NoError(t, err)
   524  		require.NoError(t, output.Err)
   525  
   526  		require.Contains(t, output.Logs, "\"hello from A\"")
   527  
   528  		mainSnapshot = mainSnapshot.Append(executionSnapshot)
   529  
   530  		compareExecutionSnapshots(t, txASnapshot, executionSnapshot)
   531  	})
   532  
   533  	t.Run("deploying contract C invalidates C", func(t *testing.T) {
   534  		require.NotNil(t, contractBSnapshot)
   535  
   536  		// deploy contract C
   537  		executionSnapshot, output, err := vm.Run(
   538  			context,
   539  			fvm.Transaction(
   540  				contractDeployTx("C", contractCCode, addressC),
   541  				derivedBlockData.NextTxIndexForTestingOnly()),
   542  			mainSnapshot)
   543  		require.NoError(t, err)
   544  		require.NoError(t, output.Err)
   545  
   546  		mainSnapshot = mainSnapshot.Append(executionSnapshot)
   547  
   548  		entryA := derivedBlockData.GetProgramForTestingOnly(contractALocation)
   549  		entryA2 := derivedBlockData.GetProgramForTestingOnly(contractA2Location)
   550  		entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation)
   551  		entryC := derivedBlockData.GetProgramForTestingOnly(contractCLocation)
   552  
   553  		require.NotNil(t, entryA)
   554  		require.NotNil(t, entryA2)
   555  		require.NotNil(t, entryB)
   556  		require.Nil(t, entryC)
   557  
   558  		cached := derivedBlockData.CachedPrograms()
   559  		require.Equal(t, 3, cached)
   560  	})
   561  
   562  	t.Run("importing C should chain-import B and A", func(t *testing.T) {
   563  		executionSnapshot, output, err := vm.Run(
   564  			context,
   565  			fvm.Transaction(
   566  				callTx("C", addressC),
   567  				derivedBlockData.NextTxIndexForTestingOnly()),
   568  			mainSnapshot)
   569  		require.NoError(t, err)
   570  		require.NoError(t, output.Err)
   571  
   572  		require.Contains(t, output.Logs, "\"hello from C, hello from B but also hello from A\"")
   573  
   574  		mainSnapshot = mainSnapshot.Append(executionSnapshot)
   575  
   576  		// program A is the same
   577  		entryA := derivedBlockData.GetProgramForTestingOnly(contractALocation)
   578  		require.NotNil(t, entryA)
   579  
   580  		require.Equal(t, contractASnapshot, entryA.ExecutionSnapshot)
   581  
   582  		// program B is the same
   583  		entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation)
   584  		require.NotNil(t, entryB)
   585  
   586  		require.Equal(t, contractBSnapshot, entryB.ExecutionSnapshot)
   587  
   588  		// program C assertions
   589  		entryC := derivedBlockData.GetProgramForTestingOnly(contractCLocation)
   590  		require.NotNil(t, entryC)
   591  
   592  		// assert dependencies are correct
   593  		require.Equal(t, 4, entryC.Value.Dependencies.Count())
   594  		require.NotNil(t, entryC.Value.Dependencies.ContainsLocation(contractALocation))
   595  		require.NotNil(t, entryC.Value.Dependencies.ContainsLocation(contractBLocation))
   596  		require.NotNil(t, entryC.Value.Dependencies.ContainsLocation(contractCLocation))
   597  
   598  		cached := derivedBlockData.CachedPrograms()
   599  		require.Equal(t, 4, cached)
   600  	})
   601  }
   602  
   603  func Test_ProgramsDoubleCounting(t *testing.T) {
   604  	snapshotTree := setupProgramsTest(t)
   605  
   606  	vm := fvm.NewVirtualMachine()
   607  	derivedBlockData := derived.NewEmptyDerivedBlockData(0)
   608  
   609  	metrics := &metricsReporter{}
   610  	context := fvm.NewContext(
   611  		fvm.WithContractDeploymentRestricted(false),
   612  		fvm.WithAuthorizationChecksEnabled(false),
   613  		fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
   614  		fvm.WithCadenceLogging(true),
   615  		fvm.WithDerivedBlockData(derivedBlockData),
   616  		fvm.WithMetricsReporter(metrics))
   617  
   618  	t.Run("deploy contracts and ensure cache is empty", func(t *testing.T) {
   619  		// deploy contract A
   620  		executionSnapshot, output, err := vm.Run(
   621  			context,
   622  			fvm.Transaction(
   623  				contractDeployTx("A", contractACode, addressA),
   624  				derivedBlockData.NextTxIndexForTestingOnly()),
   625  			snapshotTree)
   626  		require.NoError(t, err)
   627  		require.NoError(t, output.Err)
   628  
   629  		snapshotTree = snapshotTree.Append(executionSnapshot)
   630  
   631  		// deploy contract B
   632  		executionSnapshot, output, err = vm.Run(
   633  			context,
   634  			fvm.Transaction(
   635  				contractDeployTx("B", contractBCode, addressB),
   636  				derivedBlockData.NextTxIndexForTestingOnly()),
   637  			snapshotTree)
   638  		require.NoError(t, err)
   639  		require.NoError(t, output.Err)
   640  
   641  		snapshotTree = snapshotTree.Append(executionSnapshot)
   642  
   643  		// deploy contract C
   644  		executionSnapshot, output, err = vm.Run(
   645  			context,
   646  			fvm.Transaction(
   647  				contractDeployTx("C", contractCCode, addressC),
   648  				derivedBlockData.NextTxIndexForTestingOnly()),
   649  			snapshotTree)
   650  		require.NoError(t, err)
   651  		require.NoError(t, output.Err)
   652  
   653  		snapshotTree = snapshotTree.Append(executionSnapshot)
   654  
   655  		// deploy contract A2 last to clear any cache so far
   656  		executionSnapshot, output, err = vm.Run(
   657  			context,
   658  			fvm.Transaction(
   659  				contractDeployTx("A2", contractA2Code, addressA),
   660  				derivedBlockData.NextTxIndexForTestingOnly()),
   661  			snapshotTree)
   662  		require.NoError(t, err)
   663  		require.NoError(t, output.Err)
   664  
   665  		snapshotTree = snapshotTree.Append(executionSnapshot)
   666  
   667  		entryA := derivedBlockData.GetProgramForTestingOnly(contractALocation)
   668  		entryA2 := derivedBlockData.GetProgramForTestingOnly(contractA2Location)
   669  		entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation)
   670  		entryC := derivedBlockData.GetProgramForTestingOnly(contractCLocation)
   671  
   672  		require.Nil(t, entryA)
   673  		require.Nil(t, entryA2)
   674  		require.Nil(t, entryB)
   675  		require.Nil(t, entryC)
   676  
   677  		cached := derivedBlockData.CachedPrograms()
   678  		require.Equal(t, 0, cached)
   679  	})
   680  
   681  	callC := func(snapshotTree snapshot.SnapshotTree) snapshot.SnapshotTree {
   682  		procCallC := fvm.Transaction(
   683  			flow.NewTransactionBody().SetScript(
   684  				[]byte(
   685  					`
   686  					import A from 0xa
   687  					import B from 0xb
   688  					import C from 0xc
   689  					transaction {
   690  						prepare() {
   691  							log(C.hello())
   692  						}
   693  					}`,
   694  				)),
   695  			derivedBlockData.NextTxIndexForTestingOnly())
   696  
   697  		executionSnapshot, output, err := vm.Run(
   698  			context,
   699  			procCallC,
   700  			snapshotTree)
   701  		require.NoError(t, err)
   702  		require.NoError(t, output.Err)
   703  
   704  		require.Equal(
   705  			t,
   706  			uint(
   707  				1+ // import A
   708  					3+ // import B (import A, import A2)
   709  					4, // import C (import B (3), import A (already imported in this scope))
   710  			),
   711  			output.ComputationIntensities[environment.ComputationKindGetCode])
   712  
   713  		entryA := derivedBlockData.GetProgramForTestingOnly(contractALocation)
   714  		entryA2 := derivedBlockData.GetProgramForTestingOnly(contractA2Location)
   715  		entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation)
   716  		entryC := derivedBlockData.GetProgramForTestingOnly(contractCLocation)
   717  
   718  		require.NotNil(t, entryA)
   719  		require.NotNil(t, entryA2) // loaded due to "*" import
   720  		require.NotNil(t, entryB)
   721  		require.NotNil(t, entryC)
   722  
   723  		cached := derivedBlockData.CachedPrograms()
   724  		require.Equal(t, 4, cached)
   725  
   726  		return snapshotTree.Append(executionSnapshot)
   727  	}
   728  
   729  	t.Run("Call C", func(t *testing.T) {
   730  		metrics.Reset()
   731  		snapshotTree = callC(snapshotTree)
   732  
   733  		// miss A because loading transaction
   734  		// hit A because loading B because loading transaction
   735  		// miss A2 because loading B because loading transaction
   736  		// miss B because loading transaction
   737  		// hit B because loading C because loading transaction
   738  		// hit A because loading C because loading transaction
   739  		// miss C because loading transaction
   740  		//
   741  		// hit C because interpreting transaction
   742  		// hit B because interpreting C because interpreting transaction
   743  		// hit A because interpreting B because interpreting C because interpreting transaction
   744  		// hit A2 because interpreting B because interpreting C because interpreting transaction
   745  		require.Equal(t, 7, metrics.CacheHits)
   746  		require.Equal(t, 4, metrics.CacheMisses)
   747  	})
   748  
   749  	t.Run("Call C Again", func(t *testing.T) {
   750  		metrics.Reset()
   751  		snapshotTree = callC(snapshotTree)
   752  
   753  		// hit A because loading transaction
   754  		// hit B because loading transaction
   755  		// hit C because loading transaction
   756  		//
   757  		// hit C because interpreting transaction
   758  		// hit B because interpreting C because interpreting transaction
   759  		// hit A because interpreting B because interpreting C because interpreting transaction
   760  		// hit A2 because interpreting B because interpreting C because interpreting transaction
   761  		require.Equal(t, 7, metrics.CacheHits)
   762  		require.Equal(t, 0, metrics.CacheMisses)
   763  	})
   764  
   765  	t.Run("update A to breaking change and ensure cache state", func(t *testing.T) {
   766  		// deploy contract A
   767  		executionSnapshot, output, err := vm.Run(
   768  			context,
   769  			fvm.Transaction(
   770  				updateContractTx("A", contractABreakingCode, addressA),
   771  				derivedBlockData.NextTxIndexForTestingOnly()),
   772  			snapshotTree)
   773  		require.NoError(t, err)
   774  		require.NoError(t, output.Err)
   775  
   776  		snapshotTree = snapshotTree.Append(executionSnapshot)
   777  
   778  		entryA := derivedBlockData.GetProgramForTestingOnly(contractALocation)
   779  		entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation)
   780  		entryC := derivedBlockData.GetProgramForTestingOnly(contractCLocation)
   781  
   782  		require.Nil(t, entryA)
   783  		require.Nil(t, entryB)
   784  		require.Nil(t, entryC)
   785  
   786  		cached := derivedBlockData.CachedPrograms()
   787  		require.Equal(t, 1, cached)
   788  	})
   789  
   790  	callCAfterItsBroken := func(snapshotTree snapshot.SnapshotTree) snapshot.SnapshotTree {
   791  		procCallC := fvm.Transaction(
   792  			flow.NewTransactionBody().SetScript(
   793  				[]byte(
   794  					`
   795  					import A from 0xa
   796  					import B from 0xb
   797  					import C from 0xc
   798  					transaction {
   799  						prepare() {
   800  							log(C.hello())
   801  						}
   802  					}`,
   803  				)),
   804  			derivedBlockData.NextTxIndexForTestingOnly())
   805  
   806  		executionSnapshot, output, err := vm.Run(
   807  			context,
   808  			procCallC,
   809  			snapshotTree)
   810  		require.NoError(t, err)
   811  		require.Error(t, output.Err)
   812  
   813  		entryA := derivedBlockData.GetProgramForTestingOnly(contractALocation)
   814  		entryA2 := derivedBlockData.GetProgramForTestingOnly(contractA2Location)
   815  		entryB := derivedBlockData.GetProgramForTestingOnly(contractBLocation)
   816  		entryC := derivedBlockData.GetProgramForTestingOnly(contractCLocation)
   817  
   818  		require.NotNil(t, entryA)
   819  		require.NotNil(t, entryA2) // loaded due to "*" import in B
   820  		require.Nil(t, entryB)     // failed to load
   821  		require.Nil(t, entryC)     // failed to load
   822  
   823  		cached := derivedBlockData.CachedPrograms()
   824  		require.Equal(t, 2, cached)
   825  
   826  		return snapshotTree.Append(executionSnapshot)
   827  	}
   828  
   829  	t.Run("Call C when broken", func(t *testing.T) {
   830  		metrics.Reset()
   831  		snapshotTree = callCAfterItsBroken(snapshotTree)
   832  
   833  		// miss A, hit A, hit A2, hit A, hit A2, hit A
   834  		require.Equal(t, 5, metrics.CacheHits)
   835  		require.Equal(t, 1, metrics.CacheMisses)
   836  	})
   837  
   838  }
   839  
   840  func callTx(name string, address flow.Address) *flow.TransactionBody {
   841  
   842  	return flow.NewTransactionBody().SetScript(
   843  		[]byte(fmt.Sprintf(`
   844  			import %s from %s
   845  			transaction {
   846                prepare() {
   847                  log(%s.hello())
   848                }
   849              }`, name, address.HexWithPrefix(), name)),
   850  	)
   851  }
   852  
   853  func contractDeployTx(name, code string, address flow.Address) *flow.TransactionBody {
   854  	encoded := hex.EncodeToString([]byte(code))
   855  
   856  	return flow.NewTransactionBody().SetScript(
   857  		[]byte(fmt.Sprintf(`transaction {
   858                prepare(signer: auth(AddContract) &Account) {
   859                  signer.contracts.add(name: "%s", code: "%s".decodeHex())
   860                }
   861              }`, name, encoded)),
   862  	).AddAuthorizer(address)
   863  }
   864  
   865  func updateContractTx(name, code string, address flow.Address) *flow.TransactionBody {
   866  	encoded := hex.EncodeToString([]byte(code))
   867  
   868  	return flow.NewTransactionBody().SetScript([]byte(
   869  		fmt.Sprintf(`transaction {
   870               prepare(signer: auth(UpdateContract) &Account) {
   871                 signer.contracts.update(name: "%s", code: "%s".decodeHex())
   872               }
   873             }`, name, encoded)),
   874  	).AddAuthorizer(address)
   875  }
   876  
   877  func compareExecutionSnapshots(t *testing.T, a, b *snapshot.ExecutionSnapshot) {
   878  	require.Equal(t, a.WriteSet, b.WriteSet)
   879  	require.Equal(t, a.ReadSet, b.ReadSet)
   880  	require.Equal(t, a.SpockSecret, b.SpockSecret)
   881  }
   882  
   883  type metricsReporter struct {
   884  	CacheHits   int
   885  	CacheMisses int
   886  }
   887  
   888  func (m *metricsReporter) RuntimeTransactionParsed(duration time.Duration) {}
   889  
   890  func (m *metricsReporter) RuntimeTransactionChecked(duration time.Duration) {}
   891  
   892  func (m *metricsReporter) RuntimeTransactionInterpreted(duration time.Duration) {}
   893  
   894  func (m *metricsReporter) RuntimeSetNumberOfAccounts(count uint64) {}
   895  
   896  func (m *metricsReporter) RuntimeTransactionProgramsCacheMiss() {
   897  	m.CacheMisses++
   898  }
   899  
   900  func (m *metricsReporter) RuntimeTransactionProgramsCacheHit() {
   901  	m.CacheHits++
   902  }
   903  
   904  func (m *metricsReporter) Reset() {
   905  	m.CacheHits = 0
   906  	m.CacheMisses = 0
   907  }
   908  
   909  var _ environment.MetricsReporter = (*metricsReporter)(nil)