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

     1  package fvm_test
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"math/big"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/ipfs/boxo/blockstore"
    14  	"github.com/ipfs/go-datastore"
    15  	dssync "github.com/ipfs/go-datastore/sync"
    16  	"github.com/onflow/atree"
    17  	"github.com/onflow/cadence"
    18  	"github.com/onflow/cadence/encoding/ccf"
    19  	jsoncdc "github.com/onflow/cadence/encoding/json"
    20  	"github.com/onflow/cadence/runtime"
    21  	"github.com/onflow/cadence/runtime/stdlib"
    22  	"github.com/rs/zerolog"
    23  	"github.com/stretchr/testify/mock"
    24  	"github.com/stretchr/testify/require"
    25  
    26  	flow2 "github.com/onflow/flow-go-sdk"
    27  	"github.com/onflow/flow-go-sdk/templates"
    28  
    29  	"github.com/onflow/flow-go/engine/execution"
    30  	"github.com/onflow/flow-go/engine/execution/computation"
    31  	"github.com/onflow/flow-go/engine/execution/computation/committer"
    32  	"github.com/onflow/flow-go/engine/execution/computation/computer"
    33  	exeState "github.com/onflow/flow-go/engine/execution/state"
    34  	bootstrapexec "github.com/onflow/flow-go/engine/execution/state/bootstrap"
    35  	"github.com/onflow/flow-go/engine/execution/testutil"
    36  	"github.com/onflow/flow-go/fvm"
    37  	"github.com/onflow/flow-go/fvm/environment"
    38  	"github.com/onflow/flow-go/fvm/evm/testutils"
    39  	"github.com/onflow/flow-go/fvm/evm/types"
    40  	reusableRuntime "github.com/onflow/flow-go/fvm/runtime"
    41  	"github.com/onflow/flow-go/fvm/storage/derived"
    42  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    43  	"github.com/onflow/flow-go/fvm/storage/state"
    44  	"github.com/onflow/flow-go/fvm/systemcontracts"
    45  	"github.com/onflow/flow-go/fvm/tracing"
    46  	completeLedger "github.com/onflow/flow-go/ledger/complete"
    47  	"github.com/onflow/flow-go/ledger/complete/wal/fixtures"
    48  	"github.com/onflow/flow-go/model/flow"
    49  	"github.com/onflow/flow-go/module/executiondatasync/execution_data"
    50  	"github.com/onflow/flow-go/module/executiondatasync/provider"
    51  	mocktracker "github.com/onflow/flow-go/module/executiondatasync/tracker/mock"
    52  	"github.com/onflow/flow-go/module/metrics"
    53  	moduleMock "github.com/onflow/flow-go/module/mock"
    54  	requesterunit "github.com/onflow/flow-go/module/state_synchronization/requester/unittest"
    55  	"github.com/onflow/flow-go/module/trace"
    56  	"github.com/onflow/flow-go/utils/unittest"
    57  )
    58  
    59  // TODO (ramtin)
    60  // - move block computer logic to its own location inside exec node, so we embed a real
    61  // block computer, simplify this one to use an in-mem ledger
    62  // - time track different part of execution (execution, ledger update, ...)
    63  // - add metrics like number of registers touched, proof size
    64  //
    65  
    66  type TestBenchBlockExecutor interface {
    67  	ExecuteCollections(tb testing.TB, collections [][]*flow.TransactionBody) *execution.ComputationResult
    68  	Chain(tb testing.TB) flow.Chain
    69  	ServiceAccount(tb testing.TB) *TestBenchAccount
    70  }
    71  
    72  type TestBenchAccount struct {
    73  	SeqNumber  uint64
    74  	PrivateKey flow.AccountPrivateKey
    75  	Address    flow.Address
    76  }
    77  
    78  func (account *TestBenchAccount) RetAndIncSeqNumber() uint64 {
    79  	account.SeqNumber++
    80  	return account.SeqNumber - 1
    81  }
    82  
    83  func (account *TestBenchAccount) DeployContract(b *testing.B, blockExec TestBenchBlockExecutor, contractName string, contract string) {
    84  	serviceAccount := blockExec.ServiceAccount(b)
    85  	txBody := testutil.CreateContractDeploymentTransaction(
    86  		contractName,
    87  		contract,
    88  		account.Address,
    89  		blockExec.Chain(b))
    90  
    91  	txBody.SetProposalKey(serviceAccount.Address, 0, serviceAccount.RetAndIncSeqNumber())
    92  	txBody.SetPayer(serviceAccount.Address)
    93  
    94  	err := testutil.SignPayload(txBody, account.Address, account.PrivateKey)
    95  	require.NoError(b, err)
    96  
    97  	err = testutil.SignEnvelope(txBody, serviceAccount.Address, serviceAccount.PrivateKey)
    98  	require.NoError(b, err)
    99  
   100  	computationResult := blockExec.ExecuteCollections(b, [][]*flow.TransactionBody{{txBody}})
   101  	require.Empty(b, computationResult.AllTransactionResults()[0].ErrorMessage)
   102  }
   103  
   104  func (account *TestBenchAccount) AddArrayToStorage(b *testing.B, blockExec TestBenchBlockExecutor, list []string) {
   105  	serviceAccount := blockExec.ServiceAccount(b)
   106  	txBody := flow.NewTransactionBody().
   107  		SetScript([]byte(`
   108  		transaction(list: [String]) {
   109  		  prepare(acct: auth(Storage) &Account) {
   110  			acct.storage.load<[String]>(from: /storage/test)
   111  			acct.storage.save(list, to: /storage/test)
   112  		  }
   113  		  execute {}
   114  		}
   115  		`)).
   116  		AddAuthorizer(account.Address)
   117  
   118  	cadenceArrayValues := make([]cadence.Value, len(list))
   119  	for i, item := range list {
   120  		cadenceArrayValues[i] = cadence.String(item)
   121  	}
   122  	cadenceArray, err := jsoncdc.Encode(cadence.NewArray(cadenceArrayValues))
   123  	require.NoError(b, err)
   124  	txBody.AddArgument(cadenceArray)
   125  
   126  	txBody.SetProposalKey(serviceAccount.Address, 0, serviceAccount.RetAndIncSeqNumber())
   127  	txBody.SetPayer(serviceAccount.Address)
   128  
   129  	if account.Address != serviceAccount.Address {
   130  		err = testutil.SignPayload(txBody, account.Address, account.PrivateKey)
   131  		require.NoError(b, err)
   132  	}
   133  
   134  	err = testutil.SignEnvelope(txBody, serviceAccount.Address, serviceAccount.PrivateKey)
   135  	require.NoError(b, err)
   136  
   137  	computationResult := blockExec.ExecuteCollections(b, [][]*flow.TransactionBody{{txBody}})
   138  	require.Empty(b, computationResult.AllTransactionResults()[0].ErrorMessage)
   139  }
   140  
   141  // BasicBlockExecutor executes blocks in sequence and applies all changes (not fork aware)
   142  type BasicBlockExecutor struct {
   143  	blockComputer         computer.BlockComputer
   144  	derivedChainData      *derived.DerivedChainData
   145  	activeSnapshot        snapshot.SnapshotTree
   146  	activeStateCommitment flow.StateCommitment
   147  	chain                 flow.Chain
   148  	serviceAccount        *TestBenchAccount
   149  	onStopFunc            func()
   150  }
   151  
   152  func NewBasicBlockExecutor(tb testing.TB, chain flow.Chain, logger zerolog.Logger) *BasicBlockExecutor {
   153  	vm := fvm.NewVirtualMachine()
   154  
   155  	// a big interaction limit since that is not what's being tested.
   156  	interactionLimit := fvm.DefaultMaxInteractionSize * uint64(1000)
   157  
   158  	opts := []fvm.Option{
   159  		fvm.WithTransactionFeesEnabled(true),
   160  		fvm.WithAccountStorageLimit(true),
   161  		fvm.WithChain(chain),
   162  		fvm.WithLogger(logger),
   163  		fvm.WithMaxStateInteractionSize(interactionLimit),
   164  		fvm.WithReusableCadenceRuntimePool(
   165  			reusableRuntime.NewReusableCadenceRuntimePool(
   166  				computation.ReusableCadenceRuntimePoolSize,
   167  				runtime.Config{},
   168  			),
   169  		),
   170  		fvm.WithEVMEnabled(true),
   171  	}
   172  	fvmContext := fvm.NewContext(opts...)
   173  
   174  	collector := metrics.NewNoopCollector()
   175  	tracer := trace.NewNoopTracer()
   176  
   177  	wal := &fixtures.NoopWAL{}
   178  
   179  	ledger, err := completeLedger.NewLedger(wal, 100, collector, logger, completeLedger.DefaultPathFinderVersion)
   180  	require.NoError(tb, err)
   181  
   182  	compactor := fixtures.NewNoopCompactor(ledger)
   183  	<-compactor.Ready()
   184  
   185  	onStopFunc := func() {
   186  		<-ledger.Done()
   187  		<-compactor.Done()
   188  	}
   189  
   190  	bootstrapper := bootstrapexec.NewBootstrapper(logger)
   191  
   192  	serviceAccount := &TestBenchAccount{
   193  		SeqNumber:  0,
   194  		PrivateKey: unittest.ServiceAccountPrivateKey,
   195  		Address:    chain.ServiceAddress(),
   196  	}
   197  
   198  	initialCommit, err := bootstrapper.BootstrapLedger(
   199  		ledger,
   200  		unittest.ServiceAccountPublicKey,
   201  		chain,
   202  		fvm.WithInitialTokenSupply(unittest.GenesisTokenSupply),
   203  		fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee),
   204  		fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation),
   205  		fvm.WithTransactionFee(fvm.DefaultTransactionFees),
   206  		fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW),
   207  	)
   208  	require.NoError(tb, err)
   209  
   210  	bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore())))
   211  	trackerStorage := mocktracker.NewMockStorage()
   212  
   213  	prov := provider.NewProvider(
   214  		zerolog.Nop(),
   215  		metrics.NewNoopCollector(),
   216  		execution_data.DefaultSerializer,
   217  		bservice,
   218  		trackerStorage,
   219  	)
   220  
   221  	me := new(moduleMock.Local)
   222  	me.On("NodeID").Return(unittest.IdentifierFixture())
   223  	me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil)
   224  	me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything).
   225  		Return(nil, nil)
   226  
   227  	ledgerCommitter := committer.NewLedgerViewCommitter(ledger, tracer)
   228  	blockComputer, err := computer.NewBlockComputer(
   229  		vm,
   230  		fvmContext,
   231  		collector,
   232  		tracer,
   233  		logger,
   234  		ledgerCommitter,
   235  		me,
   236  		prov,
   237  		nil,
   238  		testutil.ProtocolStateWithSourceFixture(nil),
   239  		1) // We're interested in fvm's serial execution time
   240  	require.NoError(tb, err)
   241  
   242  	activeSnapshot := snapshot.NewSnapshotTree(
   243  		exeState.NewLedgerStorageSnapshot(ledger, initialCommit))
   244  
   245  	derivedChainData, err := derived.NewDerivedChainData(
   246  		derived.DefaultDerivedDataCacheSize)
   247  	require.NoError(tb, err)
   248  
   249  	return &BasicBlockExecutor{
   250  		blockComputer:         blockComputer,
   251  		derivedChainData:      derivedChainData,
   252  		activeStateCommitment: initialCommit,
   253  		activeSnapshot:        activeSnapshot,
   254  		chain:                 chain,
   255  		serviceAccount:        serviceAccount,
   256  		onStopFunc:            onStopFunc,
   257  	}
   258  }
   259  
   260  func (b *BasicBlockExecutor) Chain(_ testing.TB) flow.Chain {
   261  	return b.chain
   262  }
   263  
   264  func (b *BasicBlockExecutor) ServiceAccount(_ testing.TB) *TestBenchAccount {
   265  	return b.serviceAccount
   266  }
   267  
   268  func (b *BasicBlockExecutor) ExecuteCollections(tb testing.TB, collections [][]*flow.TransactionBody) *execution.ComputationResult {
   269  	executableBlock := unittest.ExecutableBlockFromTransactions(b.chain.ChainID(), collections)
   270  	executableBlock.StartState = &b.activeStateCommitment
   271  
   272  	derivedBlockData := b.derivedChainData.GetOrCreateDerivedBlockData(
   273  		executableBlock.ID(),
   274  		executableBlock.ParentID())
   275  
   276  	computationResult, err := b.blockComputer.ExecuteBlock(
   277  		context.Background(),
   278  		unittest.IdentifierFixture(),
   279  		executableBlock,
   280  		b.activeSnapshot,
   281  		derivedBlockData)
   282  	require.NoError(tb, err)
   283  
   284  	b.activeStateCommitment = computationResult.CurrentEndState()
   285  
   286  	for _, snapshot := range computationResult.AllExecutionSnapshots() {
   287  		b.activeSnapshot = b.activeSnapshot.Append(snapshot)
   288  	}
   289  
   290  	return computationResult
   291  }
   292  
   293  func (b *BasicBlockExecutor) RunWithLedger(tb testing.TB, f func(ledger atree.Ledger)) {
   294  	ts := state.NewTransactionState(b.activeSnapshot, state.DefaultParameters())
   295  
   296  	accounts := environment.NewAccounts(ts)
   297  	meter := environment.NewMeter(ts)
   298  
   299  	valueStore := environment.NewValueStore(
   300  		tracing.NewMockTracerSpan(),
   301  		meter,
   302  		accounts,
   303  	)
   304  
   305  	f(valueStore)
   306  
   307  	newSnapshot, err := ts.FinalizeMainTransaction()
   308  	require.NoError(tb, err)
   309  
   310  	b.activeSnapshot = b.activeSnapshot.Append(newSnapshot)
   311  }
   312  
   313  func (b *BasicBlockExecutor) SetupAccounts(tb testing.TB, privateKeys []flow.AccountPrivateKey) []TestBenchAccount {
   314  	accounts := make([]TestBenchAccount, 0)
   315  	serviceAddress := b.Chain(tb).ServiceAddress()
   316  
   317  	for _, privateKey := range privateKeys {
   318  		accountKey := flow2.NewAccountKey().
   319  			FromPrivateKey(privateKey.PrivateKey).
   320  			SetWeight(fvm.AccountKeyWeightThreshold).
   321  			SetHashAlgo(privateKey.HashAlgo).
   322  			SetSigAlgo(privateKey.SignAlgo)
   323  
   324  		sdkTX, err := templates.CreateAccount([]*flow2.AccountKey{accountKey}, []templates.Contract{}, flow2.BytesToAddress(serviceAddress.Bytes()))
   325  		require.NoError(tb, err)
   326  
   327  		txBody := flow.NewTransactionBody().
   328  			SetScript(sdkTX.Script).
   329  			SetArguments(sdkTX.Arguments).
   330  			AddAuthorizer(serviceAddress).
   331  			SetProposalKey(serviceAddress, 0, b.ServiceAccount(tb).RetAndIncSeqNumber()).
   332  			SetPayer(serviceAddress)
   333  
   334  		err = testutil.SignEnvelope(txBody, b.Chain(tb).ServiceAddress(), unittest.ServiceAccountPrivateKey)
   335  		require.NoError(tb, err)
   336  
   337  		computationResult := b.ExecuteCollections(tb, [][]*flow.TransactionBody{{txBody}})
   338  		require.Empty(tb, computationResult.AllTransactionResults()[0].ErrorMessage)
   339  
   340  		var addr flow.Address
   341  
   342  		for _, event := range computationResult.AllEvents() {
   343  			if event.Type == flow.EventAccountCreated {
   344  				data, err := ccf.Decode(nil, event.Payload)
   345  				if err != nil {
   346  					tb.Fatal("setup account failed, error decoding events")
   347  				}
   348  
   349  				address := cadence.SearchFieldByName(
   350  					data.(cadence.Event),
   351  					stdlib.AccountEventAddressParameter.Identifier,
   352  				).(cadence.Address)
   353  
   354  				addr = flow.ConvertAddress(address)
   355  				break
   356  			}
   357  		}
   358  		if addr == flow.EmptyAddress {
   359  			tb.Fatal("setup account failed, no account creation event emitted")
   360  		}
   361  		accounts = append(accounts, TestBenchAccount{Address: addr, PrivateKey: privateKey, SeqNumber: 0})
   362  	}
   363  
   364  	return accounts
   365  }
   366  
   367  type logExtractor struct {
   368  	TimeSpent       map[string]uint64
   369  	InteractionUsed map[string]uint64
   370  }
   371  
   372  type txWeights struct {
   373  	TXHash                string `json:"tx_id"`
   374  	LedgerInteractionUsed uint64 `json:"ledgerInteractionUsed"`
   375  	ComputationUsed       uint   `json:"computationUsed"`
   376  	MemoryEstimate        uint   `json:"memoryEstimate"`
   377  }
   378  
   379  type txSuccessfulLog struct {
   380  	TxID          string `json:"tx_id"`
   381  	TimeSpentInMS uint64 `json:"timeSpentInMS"`
   382  }
   383  
   384  func (l *logExtractor) Write(p []byte) (n int, err error) {
   385  	if strings.Contains(string(p), "transaction execution data") {
   386  		w := txWeights{}
   387  		err := json.Unmarshal(p, &w)
   388  
   389  		if err != nil {
   390  			fmt.Println(err)
   391  			return len(p), nil
   392  		}
   393  
   394  		l.InteractionUsed[w.TXHash] = w.LedgerInteractionUsed
   395  	}
   396  	if strings.Contains(string(p), "transaction executed successfully") {
   397  		w := txSuccessfulLog{}
   398  		err := json.Unmarshal(p, &w)
   399  
   400  		if err != nil {
   401  			fmt.Println(err)
   402  			return len(p), nil
   403  		}
   404  		l.TimeSpent[w.TxID] = w.TimeSpentInMS
   405  	}
   406  	return len(p), nil
   407  
   408  }
   409  
   410  var _ io.Writer = &logExtractor{}
   411  
   412  type benchTransactionContext struct {
   413  	EvmTestContract *testutils.TestContract
   414  	EvmTestAccount  *testutils.EOATestAccount
   415  }
   416  
   417  // BenchmarkRuntimeEmptyTransaction simulates executing blocks with `transactionsPerBlock`
   418  // where each transaction is an empty transaction
   419  func BenchmarkRuntimeTransaction(b *testing.B) {
   420  
   421  	transactionsPerBlock := 10
   422  
   423  	longString := strings.Repeat("0", 1000)
   424  
   425  	chain := flow.Testnet.Chain()
   426  
   427  	logE := &logExtractor{
   428  		TimeSpent:       map[string]uint64{},
   429  		InteractionUsed: map[string]uint64{},
   430  	}
   431  	sc := systemcontracts.SystemContractsForChain(chain.ChainID())
   432  
   433  	testContractAddress, err := chain.AddressAtIndex(systemcontracts.LastSystemAccountIndex + 1)
   434  	require.NoError(b, err)
   435  
   436  	benchTransaction := func(
   437  		b *testing.B,
   438  		txStringFunc func(b *testing.B, context benchTransactionContext) string,
   439  	) {
   440  
   441  		logger := zerolog.New(logE).Level(zerolog.DebugLevel)
   442  
   443  		blockExecutor := NewBasicBlockExecutor(b, chain, logger)
   444  		defer func() {
   445  			blockExecutor.onStopFunc()
   446  		}()
   447  
   448  		// Create an account private key.
   449  		privateKeys, err := testutil.GenerateAccountPrivateKeys(1)
   450  		require.NoError(b, err)
   451  
   452  		accounts := blockExecutor.SetupAccounts(b, privateKeys)
   453  
   454  		addrs := []flow.Address{}
   455  		for _, account := range accounts {
   456  			addrs = append(addrs, account.Address)
   457  		}
   458  		evmAddress, err := chain.AddressAtIndex(systemcontracts.EVMStorageAccountIndex)
   459  		require.NoError(b, err)
   460  		addrs = append(addrs, evmAddress)
   461  
   462  		// fund all accounts so not to run into storage problems
   463  		fundAccounts(b, blockExecutor, cadence.UFix64(1_000_000_000_000), addrs...)
   464  
   465  		accounts[0].DeployContract(b, blockExecutor, "TestContract", `
   466  			access(all) contract TestContract {
   467  				access(all) event SomeEvent()
   468  
   469  				access(all) fun empty() {
   470  				}
   471  
   472  				access(all) fun emit() {
   473  					emit SomeEvent()
   474  				}
   475  			}
   476  			`)
   477  		require.Equal(b, testContractAddress, accounts[0].Address,
   478  			"test contract should be deployed to first available account index")
   479  
   480  		accounts[0].AddArrayToStorage(b, blockExecutor, []string{longString, longString, longString, longString, longString})
   481  
   482  		tc := testutils.GetStorageTestContract(b)
   483  		var evmTestAccount *testutils.EOATestAccount
   484  		blockExecutor.RunWithLedger(b, func(ledger atree.Ledger) {
   485  			testutils.DeployContract(b, types.EmptyAddress, tc, ledger, chain.ServiceAddress())
   486  			evmTestAccount = testutils.FundAndGetEOATestAccount(b, ledger, chain.ServiceAddress())
   487  		})
   488  
   489  		benchTransactionContext := benchTransactionContext{
   490  			EvmTestContract: tc,
   491  			EvmTestAccount:  evmTestAccount,
   492  		}
   493  
   494  		benchmarkAccount := &accounts[0]
   495  
   496  		b.ResetTimer() // setup done, lets start measuring
   497  		b.StopTimer()
   498  		for i := 0; i < b.N; i++ {
   499  			transactions := make([]*flow.TransactionBody, transactionsPerBlock)
   500  			for j := 0; j < transactionsPerBlock; j++ {
   501  				tx := txStringFunc(b, benchTransactionContext)
   502  
   503  				btx := []byte(tx)
   504  				txBody := flow.NewTransactionBody().
   505  					SetScript(btx).
   506  					AddAuthorizer(benchmarkAccount.Address).
   507  					SetProposalKey(benchmarkAccount.Address, 0, benchmarkAccount.RetAndIncSeqNumber()).
   508  					SetPayer(benchmarkAccount.Address)
   509  
   510  				err = testutil.SignEnvelope(txBody, benchmarkAccount.Address, benchmarkAccount.PrivateKey)
   511  				require.NoError(b, err)
   512  
   513  				transactions[j] = txBody
   514  			}
   515  			b.StartTimer()
   516  			computationResult := blockExecutor.ExecuteCollections(b, [][]*flow.TransactionBody{transactions})
   517  			b.StopTimer()
   518  			totalInteractionUsed := uint64(0)
   519  			totalComputationUsed := uint64(0)
   520  			results := computationResult.AllTransactionResults()
   521  			// not interested in the system transaction
   522  			for _, txRes := range results[0 : len(results)-1] {
   523  				require.Empty(b, txRes.ErrorMessage)
   524  				totalInteractionUsed += logE.InteractionUsed[txRes.ID().String()]
   525  				totalComputationUsed += txRes.ComputationUsed
   526  			}
   527  			b.ReportMetric(float64(totalInteractionUsed/uint64(transactionsPerBlock)), "interactions")
   528  			b.ReportMetric(float64(totalComputationUsed/uint64(transactionsPerBlock)), "computation")
   529  		}
   530  	}
   531  
   532  	templateTx := func(rep int, prepare string) string {
   533  		return fmt.Sprintf(
   534  			`
   535  			import FungibleToken from 0x%s
   536  			import FlowToken from 0x%s
   537  			import TestContract from 0x%s
   538  			import EVM from 0x%s
   539  
   540  			transaction(){
   541  				prepare(signer: auth(Storage, Capabilities) &Account){
   542  					var i = 0
   543  					while i < %d {
   544  						i = i + 	1
   545  			%s
   546  					}
   547  				}
   548  			}`,
   549  			sc.FungibleToken.Address.Hex(),
   550  			sc.FlowToken.Address.Hex(),
   551  			testContractAddress,
   552  			sc.EVMContract.Address.Hex(),
   553  			rep,
   554  			prepare,
   555  		)
   556  	}
   557  
   558  	b.Run("reference tx", func(b *testing.B) {
   559  		benchTransaction(b,
   560  			func(b *testing.B, context benchTransactionContext) string {
   561  				return templateTx(100, "")
   562  			},
   563  		)
   564  	})
   565  	b.Run("convert int to string", func(b *testing.B) {
   566  		benchTransaction(b,
   567  			func(b *testing.B, context benchTransactionContext) string {
   568  				return templateTx(100, `i.toString()`)
   569  			},
   570  		)
   571  	})
   572  	b.Run("convert int to string and concatenate it", func(b *testing.B) {
   573  		benchTransaction(b,
   574  			func(b *testing.B, context benchTransactionContext) string {
   575  				return templateTx(100, `"x".concat(i.toString())`)
   576  			},
   577  		)
   578  	})
   579  	b.Run("get signer address", func(b *testing.B) {
   580  		benchTransaction(b,
   581  			func(b *testing.B, context benchTransactionContext) string {
   582  				return templateTx(100, `signer.address`)
   583  			},
   584  		)
   585  	})
   586  	b.Run("get public account", func(b *testing.B) {
   587  		benchTransaction(b,
   588  			func(b *testing.B, context benchTransactionContext) string {
   589  				return templateTx(100, `getAccount(signer.address)`)
   590  			},
   591  		)
   592  	})
   593  	b.Run("get account and get balance", func(b *testing.B) {
   594  		benchTransaction(b,
   595  			func(b *testing.B, context benchTransactionContext) string {
   596  				return templateTx(100, `getAccount(signer.address).balance`)
   597  			},
   598  		)
   599  	})
   600  	b.Run("get account and get available balance", func(b *testing.B) {
   601  		benchTransaction(b,
   602  			func(b *testing.B, context benchTransactionContext) string {
   603  				return templateTx(100, `getAccount(signer.address).availableBalance`)
   604  			},
   605  		)
   606  	})
   607  	b.Run("get account and get storage used", func(b *testing.B) {
   608  		benchTransaction(b,
   609  			func(b *testing.B, context benchTransactionContext) string {
   610  				return templateTx(100, `getAccount(signer.address).storageUsed`)
   611  			},
   612  		)
   613  	})
   614  	b.Run("get account and get storage capacity", func(b *testing.B) {
   615  		benchTransaction(b,
   616  			func(b *testing.B, context benchTransactionContext) string {
   617  				return templateTx(100, `getAccount(signer.address).storage.capacity`)
   618  			},
   619  		)
   620  	})
   621  	b.Run("get signer vault", func(b *testing.B) {
   622  		benchTransaction(
   623  			b,
   624  			func(b *testing.B, context benchTransactionContext) string {
   625  				return templateTx(100,
   626  					`let vaultRef = signer.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)!`)
   627  			},
   628  		)
   629  	})
   630  	b.Run("get signer receiver", func(b *testing.B) {
   631  		benchTransaction(
   632  			b,
   633  			func(b *testing.B, context benchTransactionContext) string {
   634  				return templateTx(100,
   635  					`let receiverRef =  getAccount(signer.address)
   636  						.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!`)
   637  			},
   638  		)
   639  	})
   640  	b.Run("transfer tokens", func(b *testing.B) {
   641  		benchTransaction(
   642  			b,
   643  			func(b *testing.B, context benchTransactionContext) string {
   644  				return templateTx(100, `
   645  					let receiverRef =  getAccount(signer.address)
   646  						.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!
   647  
   648  					let vaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)!
   649  
   650  					receiverRef.deposit(from: <-vaultRef.withdraw(amount: 0.00001))
   651  				`)
   652  			},
   653  		)
   654  	})
   655  	b.Run("load and save empty string on signers address", func(b *testing.B) {
   656  		benchTransaction(
   657  			b,
   658  			func(b *testing.B, context benchTransactionContext) string {
   659  				return templateTx(100, `
   660  					signer.storage.load<String>(from: /storage/testpath)
   661  					signer.storage.save("", to: /storage/testpath)
   662  				`)
   663  			},
   664  		)
   665  	})
   666  	b.Run("load and save long string on signers address", func(b *testing.B) {
   667  		benchTransaction(
   668  			b,
   669  			func(b *testing.B, context benchTransactionContext) string {
   670  				return templateTx(100, fmt.Sprintf(`
   671  					signer.storage.load<String>(from: /storage/testpath)
   672  					signer.storage.save("%s", to: /storage/testpath)
   673  				`, longString))
   674  			},
   675  		)
   676  	})
   677  	b.Run("create new account", func(b *testing.B) {
   678  		benchTransaction(b,
   679  			func(b *testing.B, context benchTransactionContext) string {
   680  				return templateTx(50, `let acct = Account(payer: signer)`)
   681  			},
   682  		)
   683  	})
   684  	b.Run("call empty contract function", func(b *testing.B) {
   685  		benchTransaction(b,
   686  			func(b *testing.B, context benchTransactionContext) string {
   687  				return templateTx(100, `TestContract.empty()`)
   688  			},
   689  		)
   690  	})
   691  	b.Run("emit event", func(b *testing.B) {
   692  		benchTransaction(b,
   693  			func(b *testing.B, context benchTransactionContext) string {
   694  				return templateTx(100, `TestContract.emit()`)
   695  			},
   696  		)
   697  	})
   698  	b.Run("borrow array from storage", func(b *testing.B) {
   699  		benchTransaction(
   700  			b,
   701  			func(b *testing.B, context benchTransactionContext) string {
   702  				return templateTx(100, `
   703  					let strings = signer.storage.borrow<&[String]>(from: /storage/test)!
   704  					var i = 0
   705  					while (i < strings.length) {
   706  					  log(strings[i])
   707  					  i = i +1
   708  					}
   709  				`)
   710  			},
   711  		)
   712  	})
   713  	b.Run("copy array from storage", func(b *testing.B) {
   714  		benchTransaction(
   715  			b,
   716  			func(b *testing.B, context benchTransactionContext) string {
   717  				return templateTx(100, `
   718  					let strings = signer.storage.copy<[String]>(from: /storage/test)!
   719  					var i = 0
   720  					while (i < strings.length) {
   721  					  log(strings[i])
   722  					  i = i +1
   723  					}
   724  				`)
   725  			},
   726  		)
   727  	})
   728  
   729  	benchEvm := func(b *testing.B, control bool) {
   730  		// This is the same as the evm benchmark but without the EVM.run call
   731  		// This way we can observe the cost of just the EVM.run call
   732  		benchTransaction(
   733  			b,
   734  			func(b *testing.B, context benchTransactionContext) string {
   735  				coinbaseBytes := context.EvmTestAccount.Address().Bytes()
   736  				transactionBody := fmt.Sprintf(`
   737  				                    let coinbaseBytesRaw = "%s".decodeHex()
   738  					let coinbaseBytes: [UInt8; 20] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
   739  					for j, v in coinbaseBytesRaw {
   740  						coinbaseBytes[j] = v
   741  					}
   742  					let coinbase = EVM.EVMAddress(bytes: coinbaseBytes)
   743  				`, hex.EncodeToString(coinbaseBytes))
   744  
   745  				num := int64(12)
   746  				gasLimit := uint64(100_000)
   747  
   748  				// add 10 EVM transactions to the Flow transaction body
   749  				for i := 0; i < 100; i++ {
   750  					txBytes := context.EvmTestAccount.PrepareSignAndEncodeTx(b,
   751  						context.EvmTestContract.DeployedAt.ToCommon(),
   752  						context.EvmTestContract.MakeCallData(b, "store", big.NewInt(num)),
   753  						big.NewInt(0),
   754  						gasLimit,
   755  						big.NewInt(0),
   756  					)
   757  					if control {
   758  						transactionBody += fmt.Sprintf(`
   759  						let txBytes%[1]d = "%[2]s".decodeHex()
   760  						EVM.run(tx: txBytes%[1]d, coinbase: coinbase)
   761  						`,
   762  							i,
   763  							hex.EncodeToString(txBytes),
   764  						)
   765  					} else {
   766  						// don't run the EVM transaction but do the hex conversion
   767  						transactionBody += fmt.Sprintf(`
   768  						let txBytes%[1]d = "%[2]s".decodeHex()
   769  						//EVM.run(tx: txBytes%[1]d, coinbase: coinbase)
   770  						`,
   771  							i,
   772  							hex.EncodeToString(txBytes),
   773  						)
   774  					}
   775  
   776  				}
   777  
   778  				return templateTx(1, transactionBody)
   779  			},
   780  		)
   781  	}
   782  
   783  	b.Run("evm", func(b *testing.B) {
   784  		benchEvm(b, false)
   785  	})
   786  
   787  	b.Run("evm control", func(b *testing.B) {
   788  		benchEvm(b, true)
   789  	})
   790  
   791  }
   792  
   793  const TransferTxTemplate = `
   794  		import NonFungibleToken from 0x%s
   795  		import BatchNFT from 0x%s
   796  
   797  		transaction(testTokenIDs: [UInt64], recipientAddress: Address) {
   798  			let transferTokens: @NonFungibleToken.Collection
   799  
   800  			prepare(acct: auth(BorrowValue) &Account) {
   801  				let ref = acct.storage.borrow<&BatchNFT.Collection>(from: /storage/TestTokenCollection)!
   802  				self.transferTokens <- ref.batchWithdraw(ids: testTokenIDs)
   803  			}
   804  
   805  			execute {
   806  				// get the recipient's public account object
   807  				let recipient = getAccount(recipientAddress)
   808  				// get the Collection reference for the receiver
   809  				let receiverRef = recipient.capabilities.borrow<&{BatchNFT.TestTokenCollectionPublic}>(/public/TestTokenCollection)!
   810  				// deposit the NFT in the receivers collection
   811  				receiverRef.batchDeposit(tokens: <-self.transferTokens)
   812  			}
   813  		}`
   814  
   815  // BenchmarkRuntimeNFTBatchTransfer runs BenchRunNFTBatchTransfer with BasicBlockExecutor
   816  func BenchmarkRuntimeNFTBatchTransfer(b *testing.B) {
   817  	blockExecutor := NewBasicBlockExecutor(b, flow.Testnet.Chain(), zerolog.Nop())
   818  	defer func() {
   819  		blockExecutor.onStopFunc()
   820  	}()
   821  
   822  	// Create an account private key.
   823  	privateKeys, err := testutil.GenerateAccountPrivateKeys(3)
   824  	require.NoError(b, err)
   825  
   826  	// Bootstrap a ledger, creating accounts with the provided private keys and the root account.
   827  	accounts := blockExecutor.SetupAccounts(b, privateKeys)
   828  
   829  	BenchRunNFTBatchTransfer(b, blockExecutor, accounts)
   830  }
   831  
   832  // BenchRunNFTBatchTransfer simulates executing blocks with `transactionsPerBlock`
   833  // where each transaction transfers `testTokensPerTransaction` testTokens (NFTs)
   834  func BenchRunNFTBatchTransfer(b *testing.B,
   835  	blockExecutor TestBenchBlockExecutor,
   836  	accounts []TestBenchAccount) {
   837  
   838  	transactionsPerBlock := 10
   839  	testTokensPerTransaction := 10
   840  
   841  	serviceAccount := blockExecutor.ServiceAccount(b)
   842  	// deploy NFT
   843  	nftAccount := accounts[0]
   844  	deployNFT(b, blockExecutor, &nftAccount)
   845  
   846  	// deploy NFT
   847  	batchNFTAccount := accounts[1]
   848  	deployBatchNFT(b, blockExecutor, &batchNFTAccount, nftAccount.Address)
   849  
   850  	// fund all accounts so not to run into storage problems
   851  	fundAccounts(b, blockExecutor, cadence.UFix64(10_0000_0000), nftAccount.Address, batchNFTAccount.Address, accounts[2].Address)
   852  
   853  	// mint nfts into the batchNFT account
   854  	mintNFTs(b, blockExecutor, &accounts[1], transactionsPerBlock*testTokensPerTransaction*b.N)
   855  
   856  	// set up receiver
   857  	setupReceiver(b, blockExecutor, &nftAccount, &batchNFTAccount, &accounts[2])
   858  
   859  	// Transfer NFTs
   860  	transferTx := []byte(fmt.Sprintf(TransferTxTemplate, accounts[0].Address.Hex(), accounts[1].Address.Hex()))
   861  
   862  	encodedAddress, err := jsoncdc.Encode(cadence.Address(accounts[2].Address))
   863  	require.NoError(b, err)
   864  
   865  	var computationResult *execution.ComputationResult
   866  
   867  	b.ResetTimer() // setup done, lets start measuring
   868  	for i := 0; i < b.N; i++ {
   869  		transactions := make([]*flow.TransactionBody, transactionsPerBlock)
   870  		for j := 0; j < transactionsPerBlock; j++ {
   871  			cadenceValues := make([]cadence.Value, testTokensPerTransaction)
   872  			startTestToken := (i*transactionsPerBlock+j)*testTokensPerTransaction + 1
   873  			for m := 0; m < testTokensPerTransaction; m++ {
   874  				cadenceValues[m] = cadence.NewUInt64(uint64(startTestToken + m))
   875  			}
   876  
   877  			encodedArg, err := jsoncdc.Encode(
   878  				cadence.NewArray(cadenceValues),
   879  			)
   880  			require.NoError(b, err)
   881  
   882  			txBody := flow.NewTransactionBody().
   883  				SetScript(transferTx).
   884  				SetProposalKey(serviceAccount.Address, 0, serviceAccount.RetAndIncSeqNumber()).
   885  				AddAuthorizer(accounts[1].Address).
   886  				AddArgument(encodedArg).
   887  				AddArgument(encodedAddress).
   888  				SetPayer(serviceAccount.Address)
   889  
   890  			err = testutil.SignPayload(txBody, accounts[1].Address, accounts[1].PrivateKey)
   891  			require.NoError(b, err)
   892  
   893  			err = testutil.SignEnvelope(txBody, serviceAccount.Address, serviceAccount.PrivateKey)
   894  			require.NoError(b, err)
   895  
   896  			transactions[j] = txBody
   897  		}
   898  
   899  		computationResult = blockExecutor.ExecuteCollections(b, [][]*flow.TransactionBody{transactions})
   900  		results := computationResult.AllTransactionResults()
   901  		// not interested in the system transaction
   902  		for _, txRes := range results[0 : len(results)-1] {
   903  			require.Empty(b, txRes.ErrorMessage)
   904  		}
   905  	}
   906  }
   907  
   908  func setupReceiver(b *testing.B, be TestBenchBlockExecutor, nftAccount, batchNFTAccount, targetAccount *TestBenchAccount) {
   909  	serviceAccount := be.ServiceAccount(b)
   910  
   911  	setUpReceiverTemplate := `
   912  	import NonFungibleToken from 0x%s
   913  	import BatchNFT from 0x%s
   914  	
   915  	transaction {
   916  		prepare(signer: auth(SaveValue) &Account) {
   917  			signer.save(
   918  				<-BatchNFT.createEmptyCollection(),
   919  				to: /storage/TestTokenCollection
   920  			)
   921  		}
   922  	}`
   923  
   924  	setupTx := []byte(fmt.Sprintf(setUpReceiverTemplate, nftAccount.Address.Hex(), batchNFTAccount.Address.Hex()))
   925  
   926  	txBody := flow.NewTransactionBody().
   927  		SetScript(setupTx).
   928  		SetProposalKey(serviceAccount.Address, 0, serviceAccount.RetAndIncSeqNumber()).
   929  		AddAuthorizer(targetAccount.Address).
   930  		SetPayer(serviceAccount.Address)
   931  
   932  	err := testutil.SignPayload(txBody, targetAccount.Address, targetAccount.PrivateKey)
   933  	require.NoError(b, err)
   934  
   935  	err = testutil.SignEnvelope(txBody, serviceAccount.Address, serviceAccount.PrivateKey)
   936  	require.NoError(b, err)
   937  
   938  	computationResult := be.ExecuteCollections(b, [][]*flow.TransactionBody{{txBody}})
   939  	require.Empty(b, computationResult.AllTransactionResults()[0].ErrorMessage)
   940  }
   941  
   942  func mintNFTs(b *testing.B, be TestBenchBlockExecutor, batchNFTAccount *TestBenchAccount, size int) {
   943  	serviceAccount := be.ServiceAccount(b)
   944  	mintScriptTemplate := `
   945  	import BatchNFT from 0x%s
   946  	transaction {
   947  		prepare(signer: auth(BorrowValue) &Account) {
   948  			let adminRef = signer.storage.borrow<&BatchNFT.Admin>(from: /storage/BatchNFTAdmin)!
   949  			let playID = adminRef.createPlay(metadata: {"name": "Test"})
   950  			let setID = BatchNFT.nextSetID
   951  			adminRef.createSet(name: "Test")
   952  			let setRef = adminRef.borrowSet(setID: setID)
   953  			setRef.addPlay(playID: playID)
   954  			let testTokens <- setRef.batchMintTestToken(playID: playID, quantity: %d)
   955  			signer.storage.borrow<&BatchNFT.Collection>(from: /storage/TestTokenCollection)!
   956  				.batchDeposit(tokens: <-testTokens)
   957  		}
   958  	}`
   959  	mintScript := []byte(fmt.Sprintf(mintScriptTemplate, batchNFTAccount.Address.Hex(), size))
   960  
   961  	txBody := flow.NewTransactionBody().
   962  		SetComputeLimit(999999).
   963  		SetScript(mintScript).
   964  		SetProposalKey(serviceAccount.Address, 0, serviceAccount.RetAndIncSeqNumber()).
   965  		AddAuthorizer(batchNFTAccount.Address).
   966  		SetPayer(serviceAccount.Address)
   967  
   968  	err := testutil.SignPayload(txBody, batchNFTAccount.Address, batchNFTAccount.PrivateKey)
   969  	require.NoError(b, err)
   970  
   971  	err = testutil.SignEnvelope(txBody, serviceAccount.Address, serviceAccount.PrivateKey)
   972  	require.NoError(b, err)
   973  
   974  	computationResult := be.ExecuteCollections(b, [][]*flow.TransactionBody{{txBody}})
   975  	require.Empty(b, computationResult.AllTransactionResults()[0].ErrorMessage)
   976  }
   977  
   978  func fundAccounts(b *testing.B, be TestBenchBlockExecutor, value cadence.UFix64, accounts ...flow.Address) {
   979  	serviceAccount := be.ServiceAccount(b)
   980  	for _, a := range accounts {
   981  		txBody := transferTokensTx(be.Chain(b))
   982  		txBody.SetProposalKey(serviceAccount.Address, 0, serviceAccount.RetAndIncSeqNumber())
   983  		txBody.AddArgument(jsoncdc.MustEncode(value))
   984  		txBody.AddArgument(jsoncdc.MustEncode(cadence.Address(a)))
   985  		txBody.AddAuthorizer(serviceAccount.Address)
   986  		txBody.SetPayer(serviceAccount.Address)
   987  
   988  		err := testutil.SignEnvelope(txBody, serviceAccount.Address, serviceAccount.PrivateKey)
   989  		require.NoError(b, err)
   990  
   991  		computationResult := be.ExecuteCollections(b, [][]*flow.TransactionBody{{txBody}})
   992  		require.Empty(b, computationResult.AllTransactionResults()[0].ErrorMessage)
   993  	}
   994  }
   995  
   996  func deployBatchNFT(b *testing.B, be TestBenchBlockExecutor, owner *TestBenchAccount, nftAddress flow.Address) {
   997  	batchNFTContract := func(nftAddress flow.Address) string {
   998  		return fmt.Sprintf(`
   999  			import NonFungibleToken from 0x%s
  1000  
  1001  			access(all) contract BatchNFT: NonFungibleToken {
  1002  				access(all) event ContractInitialized()
  1003  				access(all) event PlayCreated(id: UInt32, metadata: {String:String})
  1004  				access(all) event NewSeriesStarted(newCurrentSeries: UInt32)
  1005  				access(all) event SetCreated(setID: UInt32, series: UInt32)
  1006  				access(all) event PlayAddedToSet(setID: UInt32, playID: UInt32)
  1007  				access(all) event PlayRetiredFromSet(setID: UInt32, playID: UInt32, numTestTokens: UInt32)
  1008  				access(all) event SetLocked(setID: UInt32)
  1009  				access(all) event TestTokenMinted(testTokenID: UInt64, playID: UInt32, setID: UInt32, serialNumber: UInt32)
  1010  				access(all) event Withdraw(id: UInt64, from: Address?)
  1011  				access(all) event Deposit(id: UInt64, to: Address?)
  1012  				access(all) var currentSeries: UInt32
  1013  				access(self) var playDatas: {UInt32: Play}
  1014  				access(self) var setDatas: {UInt32: SetData}
  1015  				access(self) var sets: @{UInt32: Set}
  1016  				access(all) var nextPlayID: UInt32
  1017  				access(all) var nextSetID: UInt32
  1018  				access(all) var totalSupply: UInt64
  1019  
  1020  				access(all) struct Play {
  1021  					access(all) let playID: UInt32
  1022  					access(all) let metadata: {String: String}
  1023  
  1024  					init(metadata: {String: String}) {
  1025  						pre {
  1026  							metadata.length != 0: "New Play Metadata cannot be empty"
  1027  						}
  1028  						self.playID = BatchNFT.nextPlayID
  1029  						self.metadata = metadata
  1030  
  1031  						BatchNFT.nextPlayID = BatchNFT.nextPlayID + UInt32(1)
  1032  						emit PlayCreated(id: self.playID, metadata: metadata)
  1033  					}
  1034  				}
  1035  
  1036  				access(all) struct SetData {
  1037  					access(all) let setID: UInt32
  1038  					access(all) let name: String
  1039  					access(all) let series: UInt32
  1040  					init(name: String) {
  1041  						pre {
  1042  							name.length > 0: "New Set name cannot be empty"
  1043  						}
  1044  						self.setID = BatchNFT.nextSetID
  1045  						self.name = name
  1046  						self.series = BatchNFT.currentSeries
  1047  						BatchNFT.nextSetID = BatchNFT.nextSetID + UInt32(1)
  1048  						emit SetCreated(setID: self.setID, series: self.series)
  1049  					}
  1050  				}
  1051  
  1052  				access(all) resource Set {
  1053  					access(all) let setID: UInt32
  1054  					access(all) var plays: [UInt32]
  1055  					access(all) var retired: {UInt32: Bool}
  1056  					access(all) var locked: Bool
  1057  					access(all) var numberMintedPerPlay: {UInt32: UInt32}
  1058  
  1059  					init(name: String) {
  1060  						self.setID = BatchNFT.nextSetID
  1061  						self.plays = []
  1062  						self.retired = {}
  1063  						self.locked = false
  1064  						self.numberMintedPerPlay = {}
  1065  
  1066  						BatchNFT.setDatas[self.setID] = SetData(name: name)
  1067  					}
  1068  
  1069  					access(all) fun addPlay(playID: UInt32) {
  1070  						pre {
  1071  							BatchNFT.playDatas[playID] != nil: "Cannot add the Play to Set: Play doesn't exist"
  1072  							!self.locked: "Cannot add the play to the Set after the set has been locked"
  1073  							self.numberMintedPerPlay[playID] == nil: "The play has already beed added to the set"
  1074  						}
  1075  
  1076  						self.plays.append(playID)
  1077  						self.retired[playID] = false
  1078  						self.numberMintedPerPlay[playID] = 0
  1079  						emit PlayAddedToSet(setID: self.setID, playID: playID)
  1080  					}
  1081  
  1082  					access(all) fun addPlays(playIDs: [UInt32]) {
  1083  						for play in playIDs {
  1084  							self.addPlay(playID: play)
  1085  						}
  1086  					}
  1087  
  1088  					access(all) fun retirePlay(playID: UInt32) {
  1089  						pre {
  1090  							self.retired[playID] != nil: "Cannot retire the Play: Play doesn't exist in this set!"
  1091  						}
  1092  
  1093  						if !self.retired[playID]! {
  1094  							self.retired[playID] = true
  1095  
  1096  							emit PlayRetiredFromSet(setID: self.setID, playID: playID, numTestTokens: self.numberMintedPerPlay[playID]!)
  1097  						}
  1098  					}
  1099  
  1100  					access(all) fun retireAll() {
  1101  						for play in self.plays {
  1102  							self.retirePlay(playID: play)
  1103  						}
  1104  					}
  1105  
  1106  					access(all) fun lock() {
  1107  						if !self.locked {
  1108  							self.locked = true
  1109  							emit SetLocked(setID: self.setID)
  1110  						}
  1111  					}
  1112  
  1113  					access(all) fun mintTestToken(playID: UInt32): @NFT {
  1114  						pre {
  1115  							self.retired[playID] != nil: "Cannot mint the testToken: This play doesn't exist"
  1116  							!self.retired[playID]!: "Cannot mint the testToken from this play: This play has been retired"
  1117  						}
  1118  						let numInPlay = self.numberMintedPerPlay[playID]!
  1119  						let newTestToken: @NFT <- create NFT(serialNumber: numInPlay + UInt32(1),
  1120  														playID: playID,
  1121  														setID: self.setID)
  1122  
  1123  						self.numberMintedPerPlay[playID] = numInPlay + UInt32(1)
  1124  
  1125  						return <-newTestToken
  1126  					}
  1127  
  1128  					access(all) fun batchMintTestToken(playID: UInt32, quantity: UInt64): @Collection {
  1129  						let newCollection <- create Collection()
  1130  
  1131  						var i: UInt64 = 0
  1132  						while i < quantity {
  1133  							newCollection.deposit(token: <-self.mintTestToken(playID: playID))
  1134  							i = i + UInt64(1)
  1135  						}
  1136  
  1137  						return <-newCollection
  1138  					}
  1139  				}
  1140  
  1141  				access(all) struct TestTokenData {
  1142  					access(all) let setID: UInt32
  1143  					access(all) let playID: UInt32
  1144  					access(all) let serialNumber: UInt32
  1145  
  1146  					init(setID: UInt32, playID: UInt32, serialNumber: UInt32) {
  1147  						self.setID = setID
  1148  						self.playID = playID
  1149  						self.serialNumber = serialNumber
  1150  					}
  1151  
  1152  				}
  1153  
  1154  				access(all) resource NFT: NonFungibleToken.INFT {
  1155  					access(all) let id: UInt64
  1156  					access(all) let data: TestTokenData
  1157  
  1158  					init(serialNumber: UInt32, playID: UInt32, setID: UInt32) {
  1159  						BatchNFT.totalSupply = BatchNFT.totalSupply + UInt64(1)
  1160  
  1161  						self.id = BatchNFT.totalSupply
  1162  
  1163  						self.data = TestTokenData(setID: setID, playID: playID, serialNumber: serialNumber)
  1164  
  1165  						emit TestTokenMinted(testTokenID: self.id, playID: playID, setID: self.data.setID, serialNumber: self.data.serialNumber)
  1166  					}
  1167  				}
  1168  
  1169  				access(all) resource Admin {
  1170  					access(all) fun createPlay(metadata: {String: String}): UInt32 {
  1171  						var newPlay = Play(metadata: metadata)
  1172  						let newID = newPlay.playID
  1173  
  1174  						BatchNFT.playDatas[newID] = newPlay
  1175  
  1176  						return newID
  1177  					}
  1178  
  1179  					access(all) fun createSet(name: String) {
  1180  						var newSet <- create Set(name: name)
  1181  
  1182  						BatchNFT.sets[newSet.setID] <-! newSet
  1183  					}
  1184  
  1185  					access(all) fun borrowSet(setID: UInt32): &Set {
  1186  						pre {
  1187  							BatchNFT.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
  1188  						}
  1189  						return (&BatchNFT.sets[setID] as &Set?)!
  1190  					}
  1191  
  1192  					access(all) fun startNewSeries(): UInt32 {
  1193  						BatchNFT.currentSeries = BatchNFT.currentSeries + UInt32(1)
  1194  
  1195  						emit NewSeriesStarted(newCurrentSeries: BatchNFT.currentSeries)
  1196  
  1197  						return BatchNFT.currentSeries
  1198  					}
  1199  
  1200  					access(all) fun createNewAdmin(): @Admin {
  1201  						return <-create Admin()
  1202  					}
  1203  				}
  1204  
  1205  				access(all) resource interface TestTokenCollectionPublic {
  1206  					access(all) fun deposit(token: @NonFungibleToken.NFT)
  1207  					access(all) fun batchDeposit(tokens: @NonFungibleToken.Collection)
  1208  					access(all) fun getIDs(): [UInt64]
  1209  					access(all) fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
  1210  					access(all) fun borrowTestToken(id: UInt64): &BatchNFT.NFT? {
  1211  						post {
  1212  							(result == nil) || (result?.id == id):
  1213  								"Cannot borrow TestToken reference: The ID of the returned reference is incorrect"
  1214  						}
  1215  					}
  1216  				}
  1217  
  1218  				access(all) resource Collection: TestTokenCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic {
  1219  					access(all) var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
  1220  
  1221  					init() {
  1222  						self.ownedNFTs <- {}
  1223  					}
  1224  
  1225  					access(all) fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
  1226  						let token <- self.ownedNFTs.remove(key: withdrawID)
  1227  							?? panic("Cannot withdraw: TestToken does not exist in the collection")
  1228  
  1229  						emit Withdraw(id: token.id, from: self.owner?.address)
  1230  
  1231  						return <-token
  1232  					}
  1233  
  1234  					access(all) fun batchWithdraw(ids: [UInt64]): @NonFungibleToken.Collection {
  1235  						var batchCollection <- create Collection()
  1236  
  1237  						for id in ids {
  1238  							batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
  1239  						}
  1240  						return <-batchCollection
  1241  					}
  1242  
  1243  					access(all) fun deposit(token: @NonFungibleToken.NFT) {
  1244  						let token <- token as! @BatchNFT.NFT
  1245  
  1246  						let id = token.id
  1247  						let oldToken <- self.ownedNFTs[id] <- token
  1248  
  1249  						if self.owner?.address != nil {
  1250  							emit Deposit(id: id, to: self.owner?.address)
  1251  						}
  1252  
  1253  						destroy oldToken
  1254  					}
  1255  
  1256  					access(all) fun batchDeposit(tokens: @NonFungibleToken.Collection) {
  1257  						let keys = tokens.getIDs()
  1258  
  1259  						for key in keys {
  1260  							self.deposit(token: <-tokens.withdraw(withdrawID: key))
  1261  						}
  1262  						destroy tokens
  1263  					}
  1264  
  1265  					access(all) fun getIDs(): [UInt64] {
  1266  						return self.ownedNFTs.keys
  1267  					}
  1268  
  1269  					access(all) fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
  1270  						return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
  1271  					}
  1272  
  1273  					access(all) fun borrowTestToken(id: UInt64): &BatchNFT.NFT? {
  1274  						if self.ownedNFTs[id] != nil {
  1275  							let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
  1276  							return ref as! &BatchNFT.NFT
  1277  						} else {
  1278  							return nil
  1279  						}
  1280  					}
  1281  				}
  1282  
  1283  				access(all) fun createEmptyCollection(): @NonFungibleToken.Collection {
  1284  					return <-create BatchNFT.Collection()
  1285  				}
  1286  
  1287  				access(all) fun getAllPlays(): [BatchNFT.Play] {
  1288  					return BatchNFT.playDatas.values
  1289  				}
  1290  
  1291  				access(all) fun getPlayMetaData(playID: UInt32): {String: String}? {
  1292  					return self.playDatas[playID]?.metadata
  1293  				}
  1294  
  1295  				access(all) fun getPlayMetaDataByField(playID: UInt32, field: String): String? {
  1296  					if let play = BatchNFT.playDatas[playID] {
  1297  						return play.metadata[field]
  1298  					} else {
  1299  						return nil
  1300  					}
  1301  				}
  1302  
  1303  				access(all) fun getSetName(setID: UInt32): String? {
  1304  					return BatchNFT.setDatas[setID]?.name
  1305  				}
  1306  
  1307  				access(all) fun getSetSeries(setID: UInt32): UInt32? {
  1308  					return BatchNFT.setDatas[setID]?.series
  1309  				}
  1310  
  1311  				access(all) fun getSetIDsByName(setName: String): [UInt32]? {
  1312  					var setIDs: [UInt32] = []
  1313  
  1314  					for setData in BatchNFT.setDatas.values {
  1315  						if setName == setData.name {
  1316  							setIDs.append(setData.setID)
  1317  						}
  1318  					}
  1319  
  1320  					if setIDs.length == 0 {
  1321  						return nil
  1322  					} else {
  1323  						return setIDs
  1324  					}
  1325  				}
  1326  
  1327  				access(all) fun getPlaysInSet(setID: UInt32): [UInt32]? {
  1328  					return BatchNFT.sets[setID]?.plays
  1329  				}
  1330  
  1331  				access(all) fun isEditionRetired(setID: UInt32, playID: UInt32): Bool? {
  1332  					if let setToRead <- BatchNFT.sets.remove(key: setID) {
  1333  						let retired = setToRead.retired[playID]
  1334  						BatchNFT.sets[setID] <-! setToRead
  1335  						return retired
  1336  					} else {
  1337  						return nil
  1338  					}
  1339  				}
  1340  
  1341  				access(all) fun isSetLocked(setID: UInt32): Bool? {
  1342  					return BatchNFT.sets[setID]?.locked
  1343  				}
  1344  
  1345  				access(all) fun getNumTestTokensInEdition(setID: UInt32, playID: UInt32): UInt32? {
  1346  					if let setToRead <- BatchNFT.sets.remove(key: setID) {
  1347  						let amount = setToRead.numberMintedPerPlay[playID]
  1348  						BatchNFT.sets[setID] <-! setToRead
  1349  						return amount
  1350  					} else {
  1351  						return nil
  1352  					}
  1353  				}
  1354  
  1355  				init() {
  1356  					self.currentSeries = 0
  1357  					self.playDatas = {}
  1358  					self.setDatas = {}
  1359  					self.sets <- {}
  1360  					self.nextPlayID = 1
  1361  					self.nextSetID = 1
  1362  					self.totalSupply = 0
  1363  
  1364  					self.account.storage.save<@Collection>(<- create Collection(), to: /storage/TestTokenCollection)
  1365  
  1366  					let collectionCap = self.account.capabilities.storage.issue<&{TestTokenCollectionPublic}>(/storage/TestTokenCollection)
  1367  					self.account.capabilities.publish(collectionCap, at: /public/TestTokenCollection)
  1368  
  1369  					self.account.storage.save<@Admin>(<- create Admin(), to: /storage/BatchNFTAdmin)
  1370  					emit ContractInitialized()
  1371  				}
  1372  			}
  1373  		`, nftAddress.Hex())
  1374  	}
  1375  	owner.DeployContract(b, be, "BatchNFT", batchNFTContract(nftAddress))
  1376  }
  1377  
  1378  func deployNFT(b *testing.B, be TestBenchBlockExecutor, owner *TestBenchAccount) {
  1379  	const nftContract = `
  1380  		access(all) contract interface NonFungibleToken {
  1381  			access(all) var totalSupply: UInt64
  1382  			access(all) event ContractInitialized()
  1383  			access(all) event Withdraw(id: UInt64, from: Address?)
  1384  			access(all) event Deposit(id: UInt64, to: Address?)
  1385  			access(all) resource interface INFT {
  1386  				access(all) let id: UInt64
  1387  			}
  1388  			access(all) resource NFT: INFT {
  1389  				access(all) let id: UInt64
  1390  			}
  1391  			access(all) resource interface Provider {
  1392  				access(all) fun withdraw(withdrawID: UInt64): @NFT {
  1393  					post {
  1394  						result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID"
  1395  					}
  1396  				}
  1397  			}
  1398  			access(all) resource interface Receiver {
  1399  				access(all) fun deposit(token: @NFT)
  1400  			}
  1401  			access(all) resource interface CollectionPublic {
  1402  				access(all) fun deposit(token: @NFT)
  1403  				access(all) fun getIDs(): [UInt64]
  1404  				access(all) fun borrowNFT(id: UInt64): &NFT
  1405  			}
  1406  			access(all) resource Collection: Provider, Receiver, CollectionPublic {
  1407  				access(all) var ownedNFTs: @{UInt64: NFT}
  1408  				access(all) fun withdraw(withdrawID: UInt64): @NFT
  1409  				access(all) fun deposit(token: @NFT)
  1410  				access(all) fun getIDs(): [UInt64]
  1411  				access(all) fun borrowNFT(id: UInt64): &NFT {
  1412  					pre {
  1413  						self.ownedNFTs[id] != nil: "NFT does not exist in the collection!"
  1414  					}
  1415  				}
  1416  			}
  1417  			access(all) fun createEmptyCollection(): @Collection {
  1418  				post {
  1419  					result.getIDs().length == 0: "The created collection must be empty!"
  1420  				}
  1421  			}
  1422  		}`
  1423  
  1424  	owner.DeployContract(b, be, "NonFungibleToken", nftContract)
  1425  }