github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/utxo/verifier_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 utxo 5 6 import ( 7 "math" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/require" 12 13 "github.com/MetalBlockchain/metalgo/ids" 14 "github.com/MetalBlockchain/metalgo/snow/snowtest" 15 "github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1" 16 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 17 "github.com/MetalBlockchain/metalgo/vms/components/avax" 18 "github.com/MetalBlockchain/metalgo/vms/components/verify" 19 "github.com/MetalBlockchain/metalgo/vms/platformvm/stakeable" 20 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs" 21 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 22 23 safemath "github.com/MetalBlockchain/metalgo/utils/math" 24 ) 25 26 var _ txs.UnsignedTx = (*dummyUnsignedTx)(nil) 27 28 type dummyUnsignedTx struct { 29 txs.BaseTx 30 } 31 32 func (*dummyUnsignedTx) Visit(txs.Visitor) error { 33 return nil 34 } 35 36 func TestVerifySpendUTXOs(t *testing.T) { 37 fx := &secp256k1fx.Fx{} 38 39 require.NoError(t, fx.InitializeVM(&secp256k1fx.TestVM{})) 40 require.NoError(t, fx.Bootstrapped()) 41 42 ctx := snowtest.Context(t, snowtest.PChainID) 43 44 h := &verifier{ 45 ctx: ctx, 46 clk: &mockable.Clock{}, 47 fx: fx, 48 } 49 50 // The handler time during a test, unless [chainTimestamp] is set 51 now := time.Unix(1607133207, 0) 52 53 unsignedTx := dummyUnsignedTx{ 54 BaseTx: txs.BaseTx{}, 55 } 56 unsignedTx.SetBytes([]byte{0}) 57 58 customAssetID := ids.GenerateTestID() 59 60 // Note that setting [chainTimestamp] also set's the handler's clock. 61 // Adjust input/output locktimes accordingly. 62 tests := []struct { 63 description string 64 utxos []*avax.UTXO 65 ins []*avax.TransferableInput 66 outs []*avax.TransferableOutput 67 creds []verify.Verifiable 68 producedAmounts map[ids.ID]uint64 69 expectedErr error 70 }{ 71 { 72 description: "no inputs, no outputs, no fee", 73 utxos: []*avax.UTXO{}, 74 ins: []*avax.TransferableInput{}, 75 outs: []*avax.TransferableOutput{}, 76 creds: []verify.Verifiable{}, 77 producedAmounts: map[ids.ID]uint64{}, 78 expectedErr: nil, 79 }, 80 { 81 description: "no inputs, no outputs, positive fee", 82 utxos: []*avax.UTXO{}, 83 ins: []*avax.TransferableInput{}, 84 outs: []*avax.TransferableOutput{}, 85 creds: []verify.Verifiable{}, 86 producedAmounts: map[ids.ID]uint64{ 87 h.ctx.AVAXAssetID: 1, 88 }, 89 expectedErr: ErrInsufficientUnlockedFunds, 90 }, 91 { 92 description: "wrong utxo assetID, one input, no outputs, no fee", 93 utxos: []*avax.UTXO{{ 94 Asset: avax.Asset{ID: customAssetID}, 95 Out: &secp256k1fx.TransferOutput{ 96 Amt: 1, 97 }, 98 }}, 99 ins: []*avax.TransferableInput{{ 100 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 101 In: &secp256k1fx.TransferInput{ 102 Amt: 1, 103 }, 104 }}, 105 outs: []*avax.TransferableOutput{}, 106 creds: []verify.Verifiable{ 107 &secp256k1fx.Credential{}, 108 }, 109 producedAmounts: map[ids.ID]uint64{}, 110 expectedErr: errAssetIDMismatch, 111 }, 112 { 113 description: "one wrong assetID input, no outputs, no fee", 114 utxos: []*avax.UTXO{{ 115 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 116 Out: &secp256k1fx.TransferOutput{ 117 Amt: 1, 118 }, 119 }}, 120 ins: []*avax.TransferableInput{{ 121 Asset: avax.Asset{ID: customAssetID}, 122 In: &secp256k1fx.TransferInput{ 123 Amt: 1, 124 }, 125 }}, 126 outs: []*avax.TransferableOutput{}, 127 creds: []verify.Verifiable{ 128 &secp256k1fx.Credential{}, 129 }, 130 producedAmounts: map[ids.ID]uint64{}, 131 expectedErr: errAssetIDMismatch, 132 }, 133 { 134 description: "one input, one wrong assetID output, no fee", 135 utxos: []*avax.UTXO{{ 136 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 137 Out: &secp256k1fx.TransferOutput{ 138 Amt: 1, 139 }, 140 }}, 141 ins: []*avax.TransferableInput{{ 142 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 143 In: &secp256k1fx.TransferInput{ 144 Amt: 1, 145 }, 146 }}, 147 outs: []*avax.TransferableOutput{ 148 { 149 Asset: avax.Asset{ID: customAssetID}, 150 Out: &secp256k1fx.TransferOutput{ 151 Amt: 1, 152 }, 153 }, 154 }, 155 creds: []verify.Verifiable{ 156 &secp256k1fx.Credential{}, 157 }, 158 producedAmounts: map[ids.ID]uint64{}, 159 expectedErr: ErrInsufficientUnlockedFunds, 160 }, 161 { 162 description: "attempt to consume locked output as unlocked", 163 utxos: []*avax.UTXO{{ 164 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 165 Out: &stakeable.LockOut{ 166 Locktime: uint64(now.Add(time.Second).Unix()), 167 TransferableOut: &secp256k1fx.TransferOutput{ 168 Amt: 1, 169 }, 170 }, 171 }}, 172 ins: []*avax.TransferableInput{{ 173 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 174 In: &secp256k1fx.TransferInput{ 175 Amt: 1, 176 }, 177 }}, 178 outs: []*avax.TransferableOutput{}, 179 creds: []verify.Verifiable{ 180 &secp256k1fx.Credential{}, 181 }, 182 producedAmounts: map[ids.ID]uint64{}, 183 expectedErr: errLockedFundsNotMarkedAsLocked, 184 }, 185 { 186 description: "attempt to modify locktime", 187 utxos: []*avax.UTXO{{ 188 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 189 Out: &stakeable.LockOut{ 190 Locktime: uint64(now.Add(time.Second).Unix()), 191 TransferableOut: &secp256k1fx.TransferOutput{ 192 Amt: 1, 193 }, 194 }, 195 }}, 196 ins: []*avax.TransferableInput{{ 197 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 198 In: &stakeable.LockIn{ 199 Locktime: uint64(now.Unix()), 200 TransferableIn: &secp256k1fx.TransferInput{ 201 Amt: 1, 202 }, 203 }, 204 }}, 205 outs: []*avax.TransferableOutput{}, 206 creds: []verify.Verifiable{ 207 &secp256k1fx.Credential{}, 208 }, 209 producedAmounts: map[ids.ID]uint64{}, 210 expectedErr: errLocktimeMismatch, 211 }, 212 { 213 description: "one input, no outputs, positive fee", 214 utxos: []*avax.UTXO{{ 215 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 216 Out: &secp256k1fx.TransferOutput{ 217 Amt: 1, 218 }, 219 }}, 220 ins: []*avax.TransferableInput{{ 221 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 222 In: &secp256k1fx.TransferInput{ 223 Amt: 1, 224 }, 225 }}, 226 outs: []*avax.TransferableOutput{}, 227 creds: []verify.Verifiable{ 228 &secp256k1fx.Credential{}, 229 }, 230 producedAmounts: map[ids.ID]uint64{ 231 h.ctx.AVAXAssetID: 1, 232 }, 233 expectedErr: nil, 234 }, 235 { 236 description: "wrong number of credentials", 237 utxos: []*avax.UTXO{{ 238 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 239 Out: &secp256k1fx.TransferOutput{ 240 Amt: 1, 241 }, 242 }}, 243 ins: []*avax.TransferableInput{{ 244 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 245 In: &secp256k1fx.TransferInput{ 246 Amt: 1, 247 }, 248 }}, 249 outs: []*avax.TransferableOutput{}, 250 creds: []verify.Verifiable{}, 251 producedAmounts: map[ids.ID]uint64{ 252 h.ctx.AVAXAssetID: 1, 253 }, 254 expectedErr: errWrongNumberCredentials, 255 }, 256 { 257 description: "wrong number of UTXOs", 258 utxos: []*avax.UTXO{}, 259 ins: []*avax.TransferableInput{{ 260 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 261 In: &secp256k1fx.TransferInput{ 262 Amt: 1, 263 }, 264 }}, 265 outs: []*avax.TransferableOutput{}, 266 creds: []verify.Verifiable{ 267 &secp256k1fx.Credential{}, 268 }, 269 producedAmounts: map[ids.ID]uint64{ 270 h.ctx.AVAXAssetID: 1, 271 }, 272 expectedErr: errWrongNumberUTXOs, 273 }, 274 { 275 description: "invalid credential", 276 utxos: []*avax.UTXO{{ 277 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 278 Out: &secp256k1fx.TransferOutput{ 279 Amt: 1, 280 }, 281 }}, 282 ins: []*avax.TransferableInput{{ 283 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 284 In: &secp256k1fx.TransferInput{ 285 Amt: 1, 286 }, 287 }}, 288 outs: []*avax.TransferableOutput{}, 289 creds: []verify.Verifiable{ 290 (*secp256k1fx.Credential)(nil), 291 }, 292 producedAmounts: map[ids.ID]uint64{ 293 h.ctx.AVAXAssetID: 1, 294 }, 295 expectedErr: secp256k1fx.ErrNilCredential, 296 }, 297 { 298 description: "invalid signature", 299 utxos: []*avax.UTXO{{ 300 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 301 Out: &secp256k1fx.TransferOutput{ 302 Amt: 1, 303 OutputOwners: secp256k1fx.OutputOwners{ 304 Threshold: 1, 305 Addrs: []ids.ShortID{ 306 ids.GenerateTestShortID(), 307 }, 308 }, 309 }, 310 }}, 311 ins: []*avax.TransferableInput{{ 312 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 313 In: &secp256k1fx.TransferInput{ 314 Amt: 1, 315 Input: secp256k1fx.Input{ 316 SigIndices: []uint32{0}, 317 }, 318 }, 319 }}, 320 outs: []*avax.TransferableOutput{}, 321 creds: []verify.Verifiable{ 322 &secp256k1fx.Credential{ 323 Sigs: [][secp256k1.SignatureLen]byte{ 324 {}, 325 }, 326 }, 327 }, 328 producedAmounts: map[ids.ID]uint64{ 329 h.ctx.AVAXAssetID: 1, 330 }, 331 expectedErr: secp256k1.ErrInvalidSig, 332 }, 333 { 334 description: "one input, no outputs, positive fee", 335 utxos: []*avax.UTXO{{ 336 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 337 Out: &secp256k1fx.TransferOutput{ 338 Amt: 1, 339 }, 340 }}, 341 ins: []*avax.TransferableInput{{ 342 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 343 In: &secp256k1fx.TransferInput{ 344 Amt: 1, 345 }, 346 }}, 347 outs: []*avax.TransferableOutput{}, 348 creds: []verify.Verifiable{ 349 &secp256k1fx.Credential{}, 350 }, 351 producedAmounts: map[ids.ID]uint64{ 352 h.ctx.AVAXAssetID: 1, 353 }, 354 expectedErr: nil, 355 }, 356 { 357 description: "locked one input, no outputs, no fee", 358 utxos: []*avax.UTXO{{ 359 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 360 Out: &stakeable.LockOut{ 361 Locktime: uint64(now.Unix()) + 1, 362 TransferableOut: &secp256k1fx.TransferOutput{ 363 Amt: 1, 364 }, 365 }, 366 }}, 367 ins: []*avax.TransferableInput{{ 368 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 369 In: &stakeable.LockIn{ 370 Locktime: uint64(now.Unix()) + 1, 371 TransferableIn: &secp256k1fx.TransferInput{ 372 Amt: 1, 373 }, 374 }, 375 }}, 376 outs: []*avax.TransferableOutput{}, 377 creds: []verify.Verifiable{ 378 &secp256k1fx.Credential{}, 379 }, 380 producedAmounts: map[ids.ID]uint64{}, 381 expectedErr: nil, 382 }, 383 { 384 description: "locked one input, no outputs, positive fee", 385 utxos: []*avax.UTXO{{ 386 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 387 Out: &stakeable.LockOut{ 388 Locktime: uint64(now.Unix()) + 1, 389 TransferableOut: &secp256k1fx.TransferOutput{ 390 Amt: 1, 391 }, 392 }, 393 }}, 394 ins: []*avax.TransferableInput{{ 395 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 396 In: &stakeable.LockIn{ 397 Locktime: uint64(now.Unix()) + 1, 398 TransferableIn: &secp256k1fx.TransferInput{ 399 Amt: 1, 400 }, 401 }, 402 }}, 403 outs: []*avax.TransferableOutput{}, 404 creds: []verify.Verifiable{ 405 &secp256k1fx.Credential{}, 406 }, 407 producedAmounts: map[ids.ID]uint64{ 408 h.ctx.AVAXAssetID: 1, 409 }, 410 expectedErr: ErrInsufficientUnlockedFunds, 411 }, 412 { 413 description: "one locked and one unlocked input, one locked output, positive fee", 414 utxos: []*avax.UTXO{ 415 { 416 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 417 Out: &stakeable.LockOut{ 418 Locktime: uint64(now.Unix()) + 1, 419 TransferableOut: &secp256k1fx.TransferOutput{ 420 Amt: 1, 421 }, 422 }, 423 }, 424 { 425 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 426 Out: &secp256k1fx.TransferOutput{ 427 Amt: 1, 428 }, 429 }, 430 }, 431 ins: []*avax.TransferableInput{ 432 { 433 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 434 In: &stakeable.LockIn{ 435 Locktime: uint64(now.Unix()) + 1, 436 TransferableIn: &secp256k1fx.TransferInput{ 437 Amt: 1, 438 }, 439 }, 440 }, 441 { 442 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 443 In: &secp256k1fx.TransferInput{ 444 Amt: 1, 445 }, 446 }, 447 }, 448 outs: []*avax.TransferableOutput{ 449 { 450 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 451 Out: &stakeable.LockOut{ 452 Locktime: uint64(now.Unix()) + 1, 453 TransferableOut: &secp256k1fx.TransferOutput{ 454 Amt: 1, 455 }, 456 }, 457 }, 458 }, 459 creds: []verify.Verifiable{ 460 &secp256k1fx.Credential{}, 461 &secp256k1fx.Credential{}, 462 }, 463 producedAmounts: map[ids.ID]uint64{ 464 h.ctx.AVAXAssetID: 1, 465 }, 466 expectedErr: nil, 467 }, 468 { 469 description: "one locked and one unlocked input, one locked output, positive fee, partially locked", 470 utxos: []*avax.UTXO{ 471 { 472 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 473 Out: &stakeable.LockOut{ 474 Locktime: uint64(now.Unix()) + 1, 475 TransferableOut: &secp256k1fx.TransferOutput{ 476 Amt: 1, 477 }, 478 }, 479 }, 480 { 481 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 482 Out: &secp256k1fx.TransferOutput{ 483 Amt: 2, 484 }, 485 }, 486 }, 487 ins: []*avax.TransferableInput{ 488 { 489 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 490 In: &stakeable.LockIn{ 491 Locktime: uint64(now.Unix()) + 1, 492 TransferableIn: &secp256k1fx.TransferInput{ 493 Amt: 1, 494 }, 495 }, 496 }, 497 { 498 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 499 In: &secp256k1fx.TransferInput{ 500 Amt: 2, 501 }, 502 }, 503 }, 504 outs: []*avax.TransferableOutput{ 505 { 506 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 507 Out: &stakeable.LockOut{ 508 Locktime: uint64(now.Unix()) + 1, 509 TransferableOut: &secp256k1fx.TransferOutput{ 510 Amt: 2, 511 }, 512 }, 513 }, 514 }, 515 creds: []verify.Verifiable{ 516 &secp256k1fx.Credential{}, 517 &secp256k1fx.Credential{}, 518 }, 519 producedAmounts: map[ids.ID]uint64{ 520 h.ctx.AVAXAssetID: 1, 521 }, 522 expectedErr: nil, 523 }, 524 { 525 description: "one unlocked input, one locked output, zero fee", 526 utxos: []*avax.UTXO{ 527 { 528 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 529 Out: &stakeable.LockOut{ 530 Locktime: uint64(now.Unix()) - 1, 531 TransferableOut: &secp256k1fx.TransferOutput{ 532 Amt: 1, 533 }, 534 }, 535 }, 536 }, 537 ins: []*avax.TransferableInput{ 538 { 539 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 540 In: &secp256k1fx.TransferInput{ 541 Amt: 1, 542 }, 543 }, 544 }, 545 outs: []*avax.TransferableOutput{ 546 { 547 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 548 Out: &secp256k1fx.TransferOutput{ 549 Amt: 1, 550 }, 551 }, 552 }, 553 creds: []verify.Verifiable{ 554 &secp256k1fx.Credential{}, 555 }, 556 producedAmounts: map[ids.ID]uint64{}, 557 expectedErr: nil, 558 }, 559 { 560 description: "attempted overflow", 561 utxos: []*avax.UTXO{ 562 { 563 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 564 Out: &secp256k1fx.TransferOutput{ 565 Amt: 1, 566 }, 567 }, 568 }, 569 ins: []*avax.TransferableInput{ 570 { 571 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 572 In: &secp256k1fx.TransferInput{ 573 Amt: 1, 574 }, 575 }, 576 }, 577 outs: []*avax.TransferableOutput{ 578 { 579 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 580 Out: &secp256k1fx.TransferOutput{ 581 Amt: 2, 582 }, 583 }, 584 { 585 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 586 Out: &secp256k1fx.TransferOutput{ 587 Amt: math.MaxUint64, 588 }, 589 }, 590 }, 591 creds: []verify.Verifiable{ 592 &secp256k1fx.Credential{}, 593 }, 594 producedAmounts: map[ids.ID]uint64{}, 595 expectedErr: safemath.ErrOverflow, 596 }, 597 { 598 description: "attempted mint", 599 utxos: []*avax.UTXO{ 600 { 601 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 602 Out: &secp256k1fx.TransferOutput{ 603 Amt: 1, 604 }, 605 }, 606 }, 607 ins: []*avax.TransferableInput{ 608 { 609 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 610 In: &secp256k1fx.TransferInput{ 611 Amt: 1, 612 }, 613 }, 614 }, 615 outs: []*avax.TransferableOutput{ 616 { 617 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 618 Out: &stakeable.LockOut{ 619 Locktime: 1, 620 TransferableOut: &secp256k1fx.TransferOutput{ 621 Amt: 2, 622 }, 623 }, 624 }, 625 }, 626 creds: []verify.Verifiable{ 627 &secp256k1fx.Credential{}, 628 }, 629 producedAmounts: map[ids.ID]uint64{}, 630 expectedErr: ErrInsufficientLockedFunds, 631 }, 632 { 633 description: "attempted mint through locking", 634 utxos: []*avax.UTXO{ 635 { 636 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 637 Out: &secp256k1fx.TransferOutput{ 638 Amt: 1, 639 }, 640 }, 641 }, 642 ins: []*avax.TransferableInput{ 643 { 644 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 645 In: &secp256k1fx.TransferInput{ 646 Amt: 1, 647 }, 648 }, 649 }, 650 outs: []*avax.TransferableOutput{ 651 { 652 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 653 Out: &stakeable.LockOut{ 654 Locktime: 1, 655 TransferableOut: &secp256k1fx.TransferOutput{ 656 Amt: 2, 657 }, 658 }, 659 }, 660 { 661 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 662 Out: &stakeable.LockOut{ 663 Locktime: 1, 664 TransferableOut: &secp256k1fx.TransferOutput{ 665 Amt: math.MaxUint64, 666 }, 667 }, 668 }, 669 }, 670 creds: []verify.Verifiable{ 671 &secp256k1fx.Credential{}, 672 }, 673 producedAmounts: map[ids.ID]uint64{}, 674 expectedErr: safemath.ErrOverflow, 675 }, 676 { 677 description: "attempted mint through mixed locking (low then high)", 678 utxos: []*avax.UTXO{ 679 { 680 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 681 Out: &secp256k1fx.TransferOutput{ 682 Amt: 1, 683 }, 684 }, 685 }, 686 ins: []*avax.TransferableInput{ 687 { 688 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 689 In: &secp256k1fx.TransferInput{ 690 Amt: 1, 691 }, 692 }, 693 }, 694 outs: []*avax.TransferableOutput{ 695 { 696 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 697 Out: &secp256k1fx.TransferOutput{ 698 Amt: 2, 699 }, 700 }, 701 { 702 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 703 Out: &stakeable.LockOut{ 704 Locktime: 1, 705 TransferableOut: &secp256k1fx.TransferOutput{ 706 Amt: math.MaxUint64, 707 }, 708 }, 709 }, 710 }, 711 creds: []verify.Verifiable{ 712 &secp256k1fx.Credential{}, 713 }, 714 producedAmounts: map[ids.ID]uint64{}, 715 expectedErr: ErrInsufficientLockedFunds, 716 }, 717 { 718 description: "attempted mint through mixed locking (high then low)", 719 utxos: []*avax.UTXO{ 720 { 721 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 722 Out: &secp256k1fx.TransferOutput{ 723 Amt: 1, 724 }, 725 }, 726 }, 727 ins: []*avax.TransferableInput{ 728 { 729 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 730 In: &secp256k1fx.TransferInput{ 731 Amt: 1, 732 }, 733 }, 734 }, 735 outs: []*avax.TransferableOutput{ 736 { 737 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 738 Out: &secp256k1fx.TransferOutput{ 739 Amt: math.MaxUint64, 740 }, 741 }, 742 { 743 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 744 Out: &stakeable.LockOut{ 745 Locktime: 1, 746 TransferableOut: &secp256k1fx.TransferOutput{ 747 Amt: 2, 748 }, 749 }, 750 }, 751 }, 752 creds: []verify.Verifiable{ 753 &secp256k1fx.Credential{}, 754 }, 755 producedAmounts: map[ids.ID]uint64{}, 756 expectedErr: ErrInsufficientLockedFunds, 757 }, 758 { 759 description: "transfer non-avax asset", 760 utxos: []*avax.UTXO{ 761 { 762 Asset: avax.Asset{ID: customAssetID}, 763 Out: &secp256k1fx.TransferOutput{ 764 Amt: 1, 765 }, 766 }, 767 }, 768 ins: []*avax.TransferableInput{ 769 { 770 Asset: avax.Asset{ID: customAssetID}, 771 In: &secp256k1fx.TransferInput{ 772 Amt: 1, 773 }, 774 }, 775 }, 776 outs: []*avax.TransferableOutput{ 777 { 778 Asset: avax.Asset{ID: customAssetID}, 779 Out: &secp256k1fx.TransferOutput{ 780 Amt: 1, 781 }, 782 }, 783 }, 784 creds: []verify.Verifiable{ 785 &secp256k1fx.Credential{}, 786 }, 787 producedAmounts: map[ids.ID]uint64{}, 788 expectedErr: nil, 789 }, 790 { 791 description: "lock non-avax asset", 792 utxos: []*avax.UTXO{ 793 { 794 Asset: avax.Asset{ID: customAssetID}, 795 Out: &secp256k1fx.TransferOutput{ 796 Amt: 1, 797 }, 798 }, 799 }, 800 ins: []*avax.TransferableInput{ 801 { 802 Asset: avax.Asset{ID: customAssetID}, 803 In: &secp256k1fx.TransferInput{ 804 Amt: 1, 805 }, 806 }, 807 }, 808 outs: []*avax.TransferableOutput{ 809 { 810 Asset: avax.Asset{ID: customAssetID}, 811 Out: &stakeable.LockOut{ 812 Locktime: uint64(now.Add(time.Second).Unix()), 813 TransferableOut: &secp256k1fx.TransferOutput{ 814 Amt: 1, 815 }, 816 }, 817 }, 818 }, 819 creds: []verify.Verifiable{ 820 &secp256k1fx.Credential{}, 821 }, 822 producedAmounts: map[ids.ID]uint64{}, 823 expectedErr: nil, 824 }, 825 { 826 description: "attempted asset conversion", 827 utxos: []*avax.UTXO{ 828 { 829 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 830 Out: &secp256k1fx.TransferOutput{ 831 Amt: 1, 832 }, 833 }, 834 }, 835 ins: []*avax.TransferableInput{ 836 { 837 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 838 In: &secp256k1fx.TransferInput{ 839 Amt: 1, 840 }, 841 }, 842 }, 843 outs: []*avax.TransferableOutput{ 844 { 845 Asset: avax.Asset{ID: customAssetID}, 846 Out: &secp256k1fx.TransferOutput{ 847 Amt: 1, 848 }, 849 }, 850 }, 851 creds: []verify.Verifiable{ 852 &secp256k1fx.Credential{}, 853 }, 854 producedAmounts: map[ids.ID]uint64{}, 855 expectedErr: ErrInsufficientUnlockedFunds, 856 }, 857 { 858 description: "attempted asset conversion with burn", 859 utxos: []*avax.UTXO{ 860 { 861 Asset: avax.Asset{ID: customAssetID}, 862 Out: &secp256k1fx.TransferOutput{ 863 Amt: 1, 864 }, 865 }, 866 }, 867 ins: []*avax.TransferableInput{ 868 { 869 Asset: avax.Asset{ID: customAssetID}, 870 In: &secp256k1fx.TransferInput{ 871 Amt: 1, 872 }, 873 }, 874 }, 875 outs: []*avax.TransferableOutput{}, 876 creds: []verify.Verifiable{ 877 &secp256k1fx.Credential{}, 878 }, 879 producedAmounts: map[ids.ID]uint64{ 880 h.ctx.AVAXAssetID: 1, 881 }, 882 expectedErr: ErrInsufficientUnlockedFunds, 883 }, 884 { 885 description: "two inputs, one output with custom asset, with fee", 886 utxos: []*avax.UTXO{ 887 { 888 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 889 Out: &secp256k1fx.TransferOutput{ 890 Amt: 1, 891 }, 892 }, 893 { 894 Asset: avax.Asset{ID: customAssetID}, 895 Out: &secp256k1fx.TransferOutput{ 896 Amt: 1, 897 }, 898 }, 899 }, 900 ins: []*avax.TransferableInput{ 901 { 902 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 903 In: &secp256k1fx.TransferInput{ 904 Amt: 1, 905 }, 906 }, 907 { 908 Asset: avax.Asset{ID: customAssetID}, 909 In: &secp256k1fx.TransferInput{ 910 Amt: 1, 911 }, 912 }, 913 }, 914 outs: []*avax.TransferableOutput{ 915 { 916 Asset: avax.Asset{ID: customAssetID}, 917 Out: &secp256k1fx.TransferOutput{ 918 Amt: 1, 919 }, 920 }, 921 }, 922 creds: []verify.Verifiable{ 923 &secp256k1fx.Credential{}, 924 &secp256k1fx.Credential{}, 925 }, 926 producedAmounts: map[ids.ID]uint64{ 927 h.ctx.AVAXAssetID: 1, 928 }, 929 expectedErr: nil, 930 }, 931 { 932 description: "one input, fee, custom asset", 933 utxos: []*avax.UTXO{ 934 { 935 Asset: avax.Asset{ID: customAssetID}, 936 Out: &secp256k1fx.TransferOutput{ 937 Amt: 1, 938 }, 939 }, 940 }, 941 ins: []*avax.TransferableInput{ 942 { 943 Asset: avax.Asset{ID: customAssetID}, 944 In: &secp256k1fx.TransferInput{ 945 Amt: 1, 946 }, 947 }, 948 }, 949 outs: []*avax.TransferableOutput{}, 950 creds: []verify.Verifiable{ 951 &secp256k1fx.Credential{}, 952 }, 953 producedAmounts: map[ids.ID]uint64{ 954 h.ctx.AVAXAssetID: 1, 955 }, 956 expectedErr: ErrInsufficientUnlockedFunds, 957 }, 958 { 959 description: "one input, custom fee", 960 utxos: []*avax.UTXO{ 961 { 962 Asset: avax.Asset{ID: customAssetID}, 963 Out: &secp256k1fx.TransferOutput{ 964 Amt: 1, 965 }, 966 }, 967 }, 968 ins: []*avax.TransferableInput{ 969 { 970 Asset: avax.Asset{ID: customAssetID}, 971 In: &secp256k1fx.TransferInput{ 972 Amt: 1, 973 }, 974 }, 975 }, 976 outs: []*avax.TransferableOutput{}, 977 creds: []verify.Verifiable{ 978 &secp256k1fx.Credential{}, 979 }, 980 producedAmounts: map[ids.ID]uint64{ 981 customAssetID: 1, 982 }, 983 expectedErr: nil, 984 }, 985 { 986 description: "one input, custom fee, wrong burn", 987 utxos: []*avax.UTXO{ 988 { 989 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 990 Out: &secp256k1fx.TransferOutput{ 991 Amt: 1, 992 }, 993 }, 994 }, 995 ins: []*avax.TransferableInput{ 996 { 997 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 998 In: &secp256k1fx.TransferInput{ 999 Amt: 1, 1000 }, 1001 }, 1002 }, 1003 outs: []*avax.TransferableOutput{}, 1004 creds: []verify.Verifiable{ 1005 &secp256k1fx.Credential{}, 1006 }, 1007 producedAmounts: map[ids.ID]uint64{ 1008 customAssetID: 1, 1009 }, 1010 expectedErr: ErrInsufficientUnlockedFunds, 1011 }, 1012 { 1013 description: "two inputs, multiple fee", 1014 utxos: []*avax.UTXO{ 1015 { 1016 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 1017 Out: &secp256k1fx.TransferOutput{ 1018 Amt: 1, 1019 }, 1020 }, 1021 { 1022 Asset: avax.Asset{ID: customAssetID}, 1023 Out: &secp256k1fx.TransferOutput{ 1024 Amt: 1, 1025 }, 1026 }, 1027 }, 1028 ins: []*avax.TransferableInput{ 1029 { 1030 Asset: avax.Asset{ID: h.ctx.AVAXAssetID}, 1031 In: &secp256k1fx.TransferInput{ 1032 Amt: 1, 1033 }, 1034 }, 1035 { 1036 Asset: avax.Asset{ID: customAssetID}, 1037 In: &secp256k1fx.TransferInput{ 1038 Amt: 1, 1039 }, 1040 }, 1041 }, 1042 outs: []*avax.TransferableOutput{}, 1043 creds: []verify.Verifiable{ 1044 &secp256k1fx.Credential{}, 1045 &secp256k1fx.Credential{}, 1046 }, 1047 producedAmounts: map[ids.ID]uint64{ 1048 h.ctx.AVAXAssetID: 1, 1049 customAssetID: 1, 1050 }, 1051 expectedErr: nil, 1052 }, 1053 { 1054 description: "one unlock input, one locked output, zero fee, unlocked, custom asset", 1055 utxos: []*avax.UTXO{ 1056 { 1057 Asset: avax.Asset{ID: customAssetID}, 1058 Out: &stakeable.LockOut{ 1059 Locktime: uint64(now.Unix()) - 1, 1060 TransferableOut: &secp256k1fx.TransferOutput{ 1061 Amt: 1, 1062 }, 1063 }, 1064 }, 1065 }, 1066 ins: []*avax.TransferableInput{ 1067 { 1068 Asset: avax.Asset{ID: customAssetID}, 1069 In: &secp256k1fx.TransferInput{ 1070 Amt: 1, 1071 }, 1072 }, 1073 }, 1074 outs: []*avax.TransferableOutput{ 1075 { 1076 Asset: avax.Asset{ID: customAssetID}, 1077 Out: &secp256k1fx.TransferOutput{ 1078 Amt: 1, 1079 }, 1080 }, 1081 }, 1082 creds: []verify.Verifiable{ 1083 &secp256k1fx.Credential{}, 1084 }, 1085 producedAmounts: make(map[ids.ID]uint64), 1086 expectedErr: nil, 1087 }, 1088 } 1089 1090 for _, test := range tests { 1091 h.clk.Set(now) 1092 1093 t.Run(test.description, func(t *testing.T) { 1094 err := h.VerifySpendUTXOs( 1095 &unsignedTx, 1096 test.utxos, 1097 test.ins, 1098 test.outs, 1099 test.creds, 1100 test.producedAmounts, 1101 ) 1102 require.ErrorIs(t, err, test.expectedErr) 1103 }) 1104 } 1105 }