github.com/MetalBlockchain/metalgo@v1.11.9/vms/avm/vm_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package avm 5 6 import ( 7 "context" 8 "math" 9 "testing" 10 11 "github.com/stretchr/testify/require" 12 13 "github.com/MetalBlockchain/metalgo/chains/atomic" 14 "github.com/MetalBlockchain/metalgo/codec" 15 "github.com/MetalBlockchain/metalgo/database" 16 "github.com/MetalBlockchain/metalgo/database/memdb" 17 "github.com/MetalBlockchain/metalgo/ids" 18 "github.com/MetalBlockchain/metalgo/snow/engine/common" 19 "github.com/MetalBlockchain/metalgo/snow/snowtest" 20 "github.com/MetalBlockchain/metalgo/utils/constants" 21 "github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1" 22 "github.com/MetalBlockchain/metalgo/vms/avm/txs" 23 "github.com/MetalBlockchain/metalgo/vms/components/avax" 24 "github.com/MetalBlockchain/metalgo/vms/components/verify" 25 "github.com/MetalBlockchain/metalgo/vms/nftfx" 26 "github.com/MetalBlockchain/metalgo/vms/propertyfx" 27 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 28 ) 29 30 func TestInvalidGenesis(t *testing.T) { 31 require := require.New(t) 32 33 vm := &VM{} 34 ctx := snowtest.Context(t, snowtest.XChainID) 35 ctx.Lock.Lock() 36 defer ctx.Lock.Unlock() 37 38 err := vm.Initialize( 39 context.Background(), 40 ctx, // context 41 memdb.New(), // database 42 nil, // genesisState 43 nil, // upgradeBytes 44 nil, // configBytes 45 make(chan common.Message, 1), // engineMessenger 46 nil, // fxs 47 nil, // AppSender 48 ) 49 require.ErrorIs(err, codec.ErrCantUnpackVersion) 50 } 51 52 func TestInvalidFx(t *testing.T) { 53 require := require.New(t) 54 55 vm := &VM{} 56 ctx := snowtest.Context(t, snowtest.XChainID) 57 ctx.Lock.Lock() 58 defer func() { 59 require.NoError(vm.Shutdown(context.Background())) 60 ctx.Lock.Unlock() 61 }() 62 63 genesisBytes := buildGenesisTest(t) 64 err := vm.Initialize( 65 context.Background(), 66 ctx, // context 67 memdb.New(), // database 68 genesisBytes, // genesisState 69 nil, // upgradeBytes 70 nil, // configBytes 71 make(chan common.Message, 1), // engineMessenger 72 []*common.Fx{ // fxs 73 nil, 74 }, 75 nil, 76 ) 77 require.ErrorIs(err, errIncompatibleFx) 78 } 79 80 func TestFxInitializationFailure(t *testing.T) { 81 require := require.New(t) 82 83 vm := &VM{} 84 ctx := snowtest.Context(t, snowtest.XChainID) 85 ctx.Lock.Lock() 86 defer func() { 87 require.NoError(vm.Shutdown(context.Background())) 88 ctx.Lock.Unlock() 89 }() 90 91 genesisBytes := buildGenesisTest(t) 92 err := vm.Initialize( 93 context.Background(), 94 ctx, // context 95 memdb.New(), // database 96 genesisBytes, // genesisState 97 nil, // upgradeBytes 98 nil, // configBytes 99 make(chan common.Message, 1), // engineMessenger 100 []*common.Fx{{ // fxs 101 ID: ids.Empty, 102 Fx: &FxTest{ 103 InitializeF: func(interface{}) error { 104 return errUnknownFx 105 }, 106 }, 107 }}, 108 nil, 109 ) 110 require.ErrorIs(err, errUnknownFx) 111 } 112 113 func TestIssueTx(t *testing.T) { 114 require := require.New(t) 115 116 env := setup(t, &envConfig{ 117 fork: latest, 118 }) 119 env.vm.ctx.Lock.Unlock() 120 121 tx := newTx(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.parser, "AVAX") 122 issueAndAccept(require, env.vm, env.issuer, tx) 123 } 124 125 // Test issuing a transaction that creates an NFT family 126 func TestIssueNFT(t *testing.T) { 127 require := require.New(t) 128 129 env := setup(t, &envConfig{ 130 fork: latest, 131 }) 132 env.vm.ctx.Lock.Unlock() 133 134 var ( 135 key = keys[0] 136 kc = secp256k1fx.NewKeychain(key) 137 ) 138 139 // Create the asset 140 initialStates := map[uint32][]verify.State{ 141 1: { 142 &nftfx.MintOutput{ 143 GroupID: 1, 144 OutputOwners: secp256k1fx.OutputOwners{ 145 Threshold: 1, 146 Addrs: []ids.ShortID{key.PublicKey().Address()}, 147 }, 148 }, 149 }, 150 } 151 152 createAssetTx, err := env.txBuilder.CreateAssetTx( 153 "Team Rocket", // name 154 "TR", // symbol 155 0, // denomination 156 initialStates, 157 kc, 158 key.Address(), 159 ) 160 require.NoError(err) 161 issueAndAccept(require, env.vm, env.issuer, createAssetTx) 162 163 // Mint the NFT 164 mintNFTTx, err := env.txBuilder.MintNFT( 165 createAssetTx.ID(), 166 []byte{'h', 'e', 'l', 'l', 'o'}, // payload 167 []*secp256k1fx.OutputOwners{{ 168 Threshold: 1, 169 Addrs: []ids.ShortID{key.Address()}, 170 }}, 171 kc, 172 key.Address(), 173 ) 174 require.NoError(err) 175 issueAndAccept(require, env.vm, env.issuer, mintNFTTx) 176 177 // Move the NFT 178 utxos, err := avax.GetAllUTXOs(env.vm.state, kc.Addresses()) 179 require.NoError(err) 180 transferOp, _, err := env.vm.SpendNFT( 181 utxos, 182 kc, 183 createAssetTx.ID(), 184 1, 185 keys[2].Address(), 186 ) 187 require.NoError(err) 188 189 transferNFTTx, err := env.txBuilder.Operation( 190 transferOp, 191 kc, 192 key.Address(), 193 ) 194 require.NoError(err) 195 issueAndAccept(require, env.vm, env.issuer, transferNFTTx) 196 } 197 198 // Test issuing a transaction that creates an Property family 199 func TestIssueProperty(t *testing.T) { 200 require := require.New(t) 201 202 env := setup(t, &envConfig{ 203 fork: latest, 204 additionalFxs: []*common.Fx{{ 205 ID: propertyfx.ID, 206 Fx: &propertyfx.Fx{}, 207 }}, 208 }) 209 env.vm.ctx.Lock.Unlock() 210 211 var ( 212 key = keys[0] 213 kc = secp256k1fx.NewKeychain(key) 214 ) 215 216 // create the asset 217 initialStates := map[uint32][]verify.State{ 218 2: { 219 &propertyfx.MintOutput{ 220 OutputOwners: secp256k1fx.OutputOwners{ 221 Threshold: 1, 222 Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, 223 }, 224 }, 225 }, 226 } 227 228 createAssetTx, err := env.txBuilder.CreateAssetTx( 229 "Team Rocket", // name 230 "TR", // symbol 231 0, // denomination 232 initialStates, 233 kc, 234 key.Address(), 235 ) 236 require.NoError(err) 237 issueAndAccept(require, env.vm, env.issuer, createAssetTx) 238 239 // mint the property 240 mintPropertyOp := &txs.Operation{ 241 Asset: avax.Asset{ID: createAssetTx.ID()}, 242 UTXOIDs: []*avax.UTXOID{{ 243 TxID: createAssetTx.ID(), 244 OutputIndex: 1, 245 }}, 246 Op: &propertyfx.MintOperation{ 247 MintInput: secp256k1fx.Input{ 248 SigIndices: []uint32{0}, 249 }, 250 MintOutput: propertyfx.MintOutput{ 251 OutputOwners: secp256k1fx.OutputOwners{ 252 Threshold: 1, 253 Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, 254 }, 255 }, 256 OwnedOutput: propertyfx.OwnedOutput{}, 257 }, 258 } 259 260 mintPropertyTx, err := env.txBuilder.Operation( 261 []*txs.Operation{mintPropertyOp}, 262 kc, 263 key.Address(), 264 ) 265 require.NoError(err) 266 issueAndAccept(require, env.vm, env.issuer, mintPropertyTx) 267 268 // burn the property 269 burnPropertyOp := &txs.Operation{ 270 Asset: avax.Asset{ID: createAssetTx.ID()}, 271 UTXOIDs: []*avax.UTXOID{{ 272 TxID: mintPropertyTx.ID(), 273 OutputIndex: 2, 274 }}, 275 Op: &propertyfx.BurnOperation{Input: secp256k1fx.Input{}}, 276 } 277 278 burnPropertyTx, err := env.txBuilder.Operation( 279 []*txs.Operation{burnPropertyOp}, 280 kc, 281 key.Address(), 282 ) 283 require.NoError(err) 284 issueAndAccept(require, env.vm, env.issuer, burnPropertyTx) 285 } 286 287 func TestIssueTxWithFeeAsset(t *testing.T) { 288 require := require.New(t) 289 290 env := setup(t, &envConfig{ 291 fork: latest, 292 isCustomFeeAsset: true, 293 }) 294 env.vm.ctx.Lock.Unlock() 295 296 // send first asset 297 tx := newTx(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.parser, feeAssetName) 298 issueAndAccept(require, env.vm, env.issuer, tx) 299 } 300 301 func TestIssueTxWithAnotherAsset(t *testing.T) { 302 require := require.New(t) 303 304 env := setup(t, &envConfig{ 305 fork: latest, 306 isCustomFeeAsset: true, 307 }) 308 env.vm.ctx.Lock.Unlock() 309 310 // send second asset 311 var ( 312 key = keys[0] 313 kc = secp256k1fx.NewKeychain(key) 314 315 feeAssetCreateTx = getCreateTxFromGenesisTest(t, env.genesisBytes, feeAssetName) 316 createTx = getCreateTxFromGenesisTest(t, env.genesisBytes, otherAssetName) 317 ) 318 319 tx, err := env.txBuilder.BaseTx( 320 []*avax.TransferableOutput{ 321 { // fee asset 322 Asset: avax.Asset{ID: feeAssetCreateTx.ID()}, 323 Out: &secp256k1fx.TransferOutput{ 324 Amt: startBalance - env.vm.TxFee, 325 OutputOwners: secp256k1fx.OutputOwners{ 326 Threshold: 1, 327 Addrs: []ids.ShortID{key.PublicKey().Address()}, 328 }, 329 }, 330 }, 331 { // issued asset 332 Asset: avax.Asset{ID: createTx.ID()}, 333 Out: &secp256k1fx.TransferOutput{ 334 Amt: startBalance - env.vm.TxFee, 335 OutputOwners: secp256k1fx.OutputOwners{ 336 Threshold: 1, 337 Addrs: []ids.ShortID{key.PublicKey().Address()}, 338 }, 339 }, 340 }, 341 }, 342 nil, // memo 343 kc, 344 key.Address(), 345 ) 346 require.NoError(err) 347 issueAndAccept(require, env.vm, env.issuer, tx) 348 } 349 350 func TestVMFormat(t *testing.T) { 351 env := setup(t, &envConfig{ 352 fork: latest, 353 }) 354 defer env.vm.ctx.Lock.Unlock() 355 356 tests := []struct { 357 in ids.ShortID 358 expected string 359 }{ 360 { 361 in: ids.ShortEmpty, 362 expected: "X-testing1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtu2yas", 363 }, 364 } 365 for _, test := range tests { 366 t.Run(test.in.String(), func(t *testing.T) { 367 require := require.New(t) 368 addrStr, err := env.vm.FormatLocalAddress(test.in) 369 require.NoError(err) 370 require.Equal(test.expected, addrStr) 371 }) 372 } 373 } 374 375 func TestTxAcceptAfterParseTx(t *testing.T) { 376 require := require.New(t) 377 378 env := setup(t, &envConfig{ 379 fork: latest, 380 notLinearized: true, 381 }) 382 defer env.vm.ctx.Lock.Unlock() 383 384 var ( 385 key = keys[0] 386 kc = secp256k1fx.NewKeychain(key) 387 ) 388 389 firstTx, err := env.txBuilder.BaseTx( 390 []*avax.TransferableOutput{{ 391 Asset: avax.Asset{ID: env.genesisTx.ID()}, 392 Out: &secp256k1fx.TransferOutput{ 393 Amt: startBalance - env.vm.TxFee, 394 OutputOwners: secp256k1fx.OutputOwners{ 395 Threshold: 1, 396 Addrs: []ids.ShortID{key.PublicKey().Address()}, 397 }, 398 }, 399 }}, 400 nil, // memo 401 kc, 402 key.Address(), 403 ) 404 require.NoError(err) 405 406 // let secondTx spend firstTx outputs 407 secondTx := &txs.Tx{Unsigned: &txs.BaseTx{ 408 BaseTx: avax.BaseTx{ 409 NetworkID: constants.UnitTestID, 410 BlockchainID: env.vm.ctx.XChainID, 411 Ins: []*avax.TransferableInput{{ 412 UTXOID: avax.UTXOID{ 413 TxID: firstTx.ID(), 414 OutputIndex: 0, 415 }, 416 Asset: avax.Asset{ID: env.genesisTx.ID()}, 417 In: &secp256k1fx.TransferInput{ 418 Amt: startBalance - env.vm.TxFee, 419 Input: secp256k1fx.Input{ 420 SigIndices: []uint32{ 421 0, 422 }, 423 }, 424 }, 425 }}, 426 }, 427 }} 428 require.NoError(secondTx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) 429 430 parsedFirstTx, err := env.vm.ParseTx(context.Background(), firstTx.Bytes()) 431 require.NoError(err) 432 433 require.NoError(parsedFirstTx.Verify(context.Background())) 434 require.NoError(parsedFirstTx.Accept(context.Background())) 435 436 parsedSecondTx, err := env.vm.ParseTx(context.Background(), secondTx.Bytes()) 437 require.NoError(err) 438 439 require.NoError(parsedSecondTx.Verify(context.Background())) 440 require.NoError(parsedSecondTx.Accept(context.Background())) 441 442 _, err = env.vm.state.GetTx(firstTx.ID()) 443 require.NoError(err) 444 445 _, err = env.vm.state.GetTx(secondTx.ID()) 446 require.NoError(err) 447 } 448 449 // Test issuing an import transaction. 450 func TestIssueImportTx(t *testing.T) { 451 require := require.New(t) 452 453 env := setup(t, &envConfig{ 454 fork: durango, 455 }) 456 defer env.vm.ctx.Lock.Unlock() 457 458 peerSharedMemory := env.sharedMemory.NewSharedMemory(constants.PlatformChainID) 459 460 genesisTx := getCreateTxFromGenesisTest(t, env.genesisBytes, "AVAX") 461 avaxID := genesisTx.ID() 462 463 var ( 464 key = keys[0] 465 kc = secp256k1fx.NewKeychain(key) 466 467 utxoID = avax.UTXOID{ 468 TxID: ids.ID{ 469 0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee, 470 0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec, 471 0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea, 472 0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8, 473 }, 474 } 475 txAssetID = avax.Asset{ID: avaxID} 476 importedUtxo = &avax.UTXO{ 477 UTXOID: utxoID, 478 Asset: txAssetID, 479 Out: &secp256k1fx.TransferOutput{ 480 Amt: 1010, 481 OutputOwners: secp256k1fx.OutputOwners{ 482 Threshold: 1, 483 Addrs: []ids.ShortID{key.PublicKey().Address()}, 484 }, 485 }, 486 } 487 ) 488 489 // Provide the platform UTXO: 490 utxoBytes, err := env.vm.parser.Codec().Marshal(txs.CodecVersion, importedUtxo) 491 require.NoError(err) 492 493 inputID := importedUtxo.InputID() 494 require.NoError(peerSharedMemory.Apply(map[ids.ID]*atomic.Requests{ 495 env.vm.ctx.ChainID: { 496 PutRequests: []*atomic.Element{{ 497 Key: inputID[:], 498 Value: utxoBytes, 499 Traits: [][]byte{ 500 key.PublicKey().Address().Bytes(), 501 }, 502 }}, 503 }, 504 })) 505 506 tx, err := env.txBuilder.ImportTx( 507 constants.PlatformChainID, // source chain 508 key.Address(), 509 kc, 510 ) 511 require.NoError(err) 512 513 env.vm.ctx.Lock.Unlock() 514 515 issueAndAccept(require, env.vm, env.issuer, tx) 516 517 env.vm.ctx.Lock.Lock() 518 519 assertIndexedTX(t, env.vm.db, 0, key.PublicKey().Address(), txAssetID.AssetID(), tx.ID()) 520 assertLatestIdx(t, env.vm.db, key.PublicKey().Address(), avaxID, 1) 521 522 id := utxoID.InputID() 523 _, err = env.vm.ctx.SharedMemory.Get(constants.PlatformChainID, [][]byte{id[:]}) 524 require.ErrorIs(err, database.ErrNotFound) 525 } 526 527 // Test force accepting an import transaction. 528 func TestForceAcceptImportTx(t *testing.T) { 529 require := require.New(t) 530 531 env := setup(t, &envConfig{ 532 fork: durango, 533 notLinearized: true, 534 }) 535 defer env.vm.ctx.Lock.Unlock() 536 537 genesisTx := getCreateTxFromGenesisTest(t, env.genesisBytes, "AVAX") 538 avaxID := genesisTx.ID() 539 540 key := keys[0] 541 utxoID := avax.UTXOID{ 542 TxID: ids.ID{ 543 0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee, 544 0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec, 545 0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea, 546 0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8, 547 }, 548 } 549 550 txAssetID := avax.Asset{ID: avaxID} 551 tx := &txs.Tx{Unsigned: &txs.ImportTx{ 552 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 553 NetworkID: constants.UnitTestID, 554 BlockchainID: env.vm.ctx.XChainID, 555 Outs: []*avax.TransferableOutput{{ 556 Asset: txAssetID, 557 Out: &secp256k1fx.TransferOutput{ 558 Amt: 10, 559 OutputOwners: secp256k1fx.OutputOwners{ 560 Threshold: 1, 561 Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, 562 }, 563 }, 564 }}, 565 }}, 566 SourceChain: constants.PlatformChainID, 567 ImportedIns: []*avax.TransferableInput{{ 568 UTXOID: utxoID, 569 Asset: txAssetID, 570 In: &secp256k1fx.TransferInput{ 571 Amt: 1010, 572 Input: secp256k1fx.Input{ 573 SigIndices: []uint32{0}, 574 }, 575 }, 576 }}, 577 }} 578 require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) 579 580 parsedTx, err := env.vm.ParseTx(context.Background(), tx.Bytes()) 581 require.NoError(err) 582 583 require.NoError(parsedTx.Verify(context.Background())) 584 require.NoError(parsedTx.Accept(context.Background())) 585 586 assertIndexedTX(t, env.vm.db, 0, key.PublicKey().Address(), txAssetID.AssetID(), tx.ID()) 587 assertLatestIdx(t, env.vm.db, key.PublicKey().Address(), avaxID, 1) 588 589 id := utxoID.InputID() 590 _, err = env.vm.ctx.SharedMemory.Get(constants.PlatformChainID, [][]byte{id[:]}) 591 require.ErrorIs(err, database.ErrNotFound) 592 } 593 594 func TestImportTxNotState(t *testing.T) { 595 require := require.New(t) 596 597 intf := interface{}(&txs.ImportTx{}) 598 _, ok := intf.(verify.State) 599 require.False(ok) 600 } 601 602 // Test issuing an export transaction. 603 func TestIssueExportTx(t *testing.T) { 604 require := require.New(t) 605 606 env := setup(t, &envConfig{fork: durango}) 607 defer env.vm.ctx.Lock.Unlock() 608 609 genesisTx := getCreateTxFromGenesisTest(t, env.genesisBytes, "AVAX") 610 611 var ( 612 avaxID = genesisTx.ID() 613 key = keys[0] 614 kc = secp256k1fx.NewKeychain(key) 615 to = key.PublicKey().Address() 616 changeAddr = to 617 ) 618 619 tx, err := env.txBuilder.ExportTx( 620 constants.PlatformChainID, 621 to, // to 622 avaxID, 623 startBalance-env.vm.TxFee, 624 kc, 625 changeAddr, 626 ) 627 require.NoError(err) 628 629 peerSharedMemory := env.sharedMemory.NewSharedMemory(constants.PlatformChainID) 630 utxoBytes, _, _, err := peerSharedMemory.Indexed( 631 env.vm.ctx.ChainID, 632 [][]byte{ 633 key.PublicKey().Address().Bytes(), 634 }, 635 nil, 636 nil, 637 math.MaxInt32, 638 ) 639 require.NoError(err) 640 require.Empty(utxoBytes) 641 642 env.vm.ctx.Lock.Unlock() 643 644 issueAndAccept(require, env.vm, env.issuer, tx) 645 646 env.vm.ctx.Lock.Lock() 647 648 utxoBytes, _, _, err = peerSharedMemory.Indexed( 649 env.vm.ctx.ChainID, 650 [][]byte{ 651 key.PublicKey().Address().Bytes(), 652 }, 653 nil, 654 nil, 655 math.MaxInt32, 656 ) 657 require.NoError(err) 658 require.Len(utxoBytes, 1) 659 } 660 661 func TestClearForceAcceptedExportTx(t *testing.T) { 662 require := require.New(t) 663 664 env := setup(t, &envConfig{ 665 fork: latest, 666 }) 667 defer env.vm.ctx.Lock.Unlock() 668 669 genesisTx := getCreateTxFromGenesisTest(t, env.genesisBytes, "AVAX") 670 671 var ( 672 avaxID = genesisTx.ID() 673 assetID = avax.Asset{ID: avaxID} 674 key = keys[0] 675 kc = secp256k1fx.NewKeychain(key) 676 to = key.PublicKey().Address() 677 changeAddr = to 678 ) 679 680 tx, err := env.txBuilder.ExportTx( 681 constants.PlatformChainID, 682 to, // to 683 avaxID, 684 startBalance-env.vm.TxFee, 685 kc, 686 changeAddr, 687 ) 688 require.NoError(err) 689 690 utxo := avax.UTXOID{ 691 TxID: tx.ID(), 692 OutputIndex: 0, 693 } 694 utxoID := utxo.InputID() 695 696 peerSharedMemory := env.sharedMemory.NewSharedMemory(constants.PlatformChainID) 697 require.NoError(peerSharedMemory.Apply(map[ids.ID]*atomic.Requests{ 698 env.vm.ctx.ChainID: { 699 RemoveRequests: [][]byte{utxoID[:]}, 700 }, 701 })) 702 703 _, err = peerSharedMemory.Get(env.vm.ctx.ChainID, [][]byte{utxoID[:]}) 704 require.ErrorIs(err, database.ErrNotFound) 705 706 env.vm.ctx.Lock.Unlock() 707 708 issueAndAccept(require, env.vm, env.issuer, tx) 709 710 env.vm.ctx.Lock.Lock() 711 712 _, err = peerSharedMemory.Get(env.vm.ctx.ChainID, [][]byte{utxoID[:]}) 713 require.ErrorIs(err, database.ErrNotFound) 714 715 assertIndexedTX(t, env.vm.db, 0, key.PublicKey().Address(), assetID.AssetID(), tx.ID()) 716 assertLatestIdx(t, env.vm.db, key.PublicKey().Address(), assetID.AssetID(), 1) 717 }