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 }