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  }