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