github.com/ava-labs/avalanchego@v1.11.11/vms/avm/txs/executor/syntactic_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 executor
     5  
     6  import (
     7  	"math"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/ava-labs/avalanchego/ids"
    14  	"github.com/ava-labs/avalanchego/snow/snowtest"
    15  	"github.com/ava-labs/avalanchego/upgrade/upgradetest"
    16  	"github.com/ava-labs/avalanchego/utils/constants"
    17  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    18  	"github.com/ava-labs/avalanchego/vms/avm/config"
    19  	"github.com/ava-labs/avalanchego/vms/avm/fxs"
    20  	"github.com/ava-labs/avalanchego/vms/avm/txs"
    21  	"github.com/ava-labs/avalanchego/vms/components/avax"
    22  	"github.com/ava-labs/avalanchego/vms/components/verify"
    23  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    24  
    25  	safemath "github.com/ava-labs/avalanchego/utils/math"
    26  )
    27  
    28  var (
    29  	keys      = secp256k1.TestKeys()
    30  	feeConfig = config.Config{
    31  		Upgrades:         upgradetest.GetConfig(upgradetest.Durango),
    32  		TxFee:            2,
    33  		CreateAssetTxFee: 3,
    34  	}
    35  )
    36  
    37  func TestSyntacticVerifierBaseTx(t *testing.T) {
    38  	ctx := snowtest.Context(t, snowtest.XChainID)
    39  
    40  	fx := &secp256k1fx.Fx{}
    41  	parser, err := txs.NewParser(
    42  		[]fxs.Fx{
    43  			fx,
    44  		},
    45  	)
    46  	require.NoError(t, err)
    47  
    48  	feeAssetID := ids.GenerateTestID()
    49  	asset := avax.Asset{
    50  		ID: feeAssetID,
    51  	}
    52  	outputOwners := secp256k1fx.OutputOwners{
    53  		Threshold: 1,
    54  		Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
    55  	}
    56  	fxOutput := secp256k1fx.TransferOutput{
    57  		Amt:          12345,
    58  		OutputOwners: outputOwners,
    59  	}
    60  	output := avax.TransferableOutput{
    61  		Asset: asset,
    62  		Out:   &fxOutput,
    63  	}
    64  	inputTxID := ids.GenerateTestID()
    65  	utxoID := avax.UTXOID{
    66  		TxID:        inputTxID,
    67  		OutputIndex: 0,
    68  	}
    69  	inputSigners := secp256k1fx.Input{
    70  		SigIndices: []uint32{2},
    71  	}
    72  	fxInput := secp256k1fx.TransferInput{
    73  		Amt:   54321,
    74  		Input: inputSigners,
    75  	}
    76  	input := avax.TransferableInput{
    77  		UTXOID: utxoID,
    78  		Asset:  asset,
    79  		In:     &fxInput,
    80  	}
    81  	baseTx := avax.BaseTx{
    82  		NetworkID:    constants.UnitTestID,
    83  		BlockchainID: ctx.ChainID,
    84  		Outs: []*avax.TransferableOutput{
    85  			&output,
    86  		},
    87  		Ins: []*avax.TransferableInput{
    88  			&input,
    89  		},
    90  	}
    91  	cred := fxs.FxCredential{
    92  		Credential: &secp256k1fx.Credential{},
    93  	}
    94  	creds := []*fxs.FxCredential{
    95  		&cred,
    96  	}
    97  
    98  	codec := parser.Codec()
    99  	backend := &Backend{
   100  		Ctx:    ctx,
   101  		Config: &feeConfig,
   102  		Fxs: []*fxs.ParsedFx{
   103  			{
   104  				ID: secp256k1fx.ID,
   105  				Fx: fx,
   106  			},
   107  		},
   108  		Codec:      codec,
   109  		FeeAssetID: feeAssetID,
   110  	}
   111  
   112  	tests := []struct {
   113  		name   string
   114  		txFunc func() *txs.Tx
   115  		err    error
   116  	}{
   117  		{
   118  			name: "valid",
   119  			txFunc: func() *txs.Tx {
   120  				return &txs.Tx{
   121  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   122  					Creds:    creds,
   123  				}
   124  			},
   125  			err: nil,
   126  		},
   127  		{
   128  			name: "wrong networkID",
   129  			txFunc: func() *txs.Tx {
   130  				baseTx := baseTx
   131  				baseTx.NetworkID++
   132  				return &txs.Tx{
   133  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   134  					Creds:    creds,
   135  				}
   136  			},
   137  			err: avax.ErrWrongNetworkID,
   138  		},
   139  		{
   140  			name: "wrong chainID",
   141  			txFunc: func() *txs.Tx {
   142  				baseTx := baseTx
   143  				baseTx.BlockchainID = ids.GenerateTestID()
   144  				return &txs.Tx{
   145  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   146  					Creds:    creds,
   147  				}
   148  			},
   149  			err: avax.ErrWrongChainID,
   150  		},
   151  		{
   152  			name: "memo too large",
   153  			txFunc: func() *txs.Tx {
   154  				baseTx := baseTx
   155  				baseTx.Memo = make([]byte, avax.MaxMemoSize+1)
   156  				return &txs.Tx{
   157  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   158  					Creds:    creds,
   159  				}
   160  			},
   161  			err: avax.ErrMemoTooLarge,
   162  		},
   163  		{
   164  			name: "invalid output",
   165  			txFunc: func() *txs.Tx {
   166  				output := output
   167  				output.Out = &secp256k1fx.TransferOutput{
   168  					Amt:          0,
   169  					OutputOwners: outputOwners,
   170  				}
   171  
   172  				baseTx := baseTx
   173  				baseTx.Outs = []*avax.TransferableOutput{
   174  					&output,
   175  				}
   176  				return &txs.Tx{
   177  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   178  					Creds:    creds,
   179  				}
   180  			},
   181  			err: secp256k1fx.ErrNoValueOutput,
   182  		},
   183  		{
   184  			name: "unsorted outputs",
   185  			txFunc: func() *txs.Tx {
   186  				output0 := output
   187  				output0.Out = &secp256k1fx.TransferOutput{
   188  					Amt:          1,
   189  					OutputOwners: outputOwners,
   190  				}
   191  
   192  				output1 := output
   193  				output1.Out = &secp256k1fx.TransferOutput{
   194  					Amt:          2,
   195  					OutputOwners: outputOwners,
   196  				}
   197  
   198  				outputs := []*avax.TransferableOutput{
   199  					&output0,
   200  					&output1,
   201  				}
   202  				avax.SortTransferableOutputs(outputs, codec)
   203  				outputs[0], outputs[1] = outputs[1], outputs[0]
   204  
   205  				baseTx := baseTx
   206  				baseTx.Outs = outputs
   207  				return &txs.Tx{
   208  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   209  					Creds:    creds,
   210  				}
   211  			},
   212  			err: avax.ErrOutputsNotSorted,
   213  		},
   214  		{
   215  			name: "invalid input",
   216  			txFunc: func() *txs.Tx {
   217  				input := input
   218  				input.In = &secp256k1fx.TransferInput{
   219  					Amt:   0,
   220  					Input: inputSigners,
   221  				}
   222  
   223  				baseTx := baseTx
   224  				baseTx.Ins = []*avax.TransferableInput{
   225  					&input,
   226  				}
   227  				return &txs.Tx{
   228  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   229  					Creds:    creds,
   230  				}
   231  			},
   232  			err: secp256k1fx.ErrNoValueInput,
   233  		},
   234  		{
   235  			name: "duplicate inputs",
   236  			txFunc: func() *txs.Tx {
   237  				baseTx := baseTx
   238  				baseTx.Ins = []*avax.TransferableInput{
   239  					&input,
   240  					&input,
   241  				}
   242  				return &txs.Tx{
   243  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   244  					Creds: []*fxs.FxCredential{
   245  						&cred,
   246  						&cred,
   247  					},
   248  				}
   249  			},
   250  			err: avax.ErrInputsNotSortedUnique,
   251  		},
   252  		{
   253  			name: "input overflow",
   254  			txFunc: func() *txs.Tx {
   255  				input0 := input
   256  				input0.In = &secp256k1fx.TransferInput{
   257  					Amt:   1,
   258  					Input: inputSigners,
   259  				}
   260  
   261  				input1 := input
   262  				input1.UTXOID.OutputIndex++
   263  				input1.In = &secp256k1fx.TransferInput{
   264  					Amt:   math.MaxUint64,
   265  					Input: inputSigners,
   266  				}
   267  
   268  				baseTx := baseTx
   269  				baseTx.Ins = []*avax.TransferableInput{
   270  					&input0,
   271  					&input1,
   272  				}
   273  				avax.SortTransferableInputsWithSigners(baseTx.Ins, make([][]*secp256k1.PrivateKey, 2))
   274  				return &txs.Tx{
   275  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   276  					Creds: []*fxs.FxCredential{
   277  						&cred,
   278  						&cred,
   279  					},
   280  				}
   281  			},
   282  			err: safemath.ErrOverflow,
   283  		},
   284  		{
   285  			name: "output overflow",
   286  			txFunc: func() *txs.Tx {
   287  				output0 := output
   288  				output0.Out = &secp256k1fx.TransferOutput{
   289  					Amt:          1,
   290  					OutputOwners: outputOwners,
   291  				}
   292  
   293  				output1 := output
   294  				output1.Out = &secp256k1fx.TransferOutput{
   295  					Amt:          math.MaxUint64,
   296  					OutputOwners: outputOwners,
   297  				}
   298  
   299  				outputs := []*avax.TransferableOutput{
   300  					&output0,
   301  					&output1,
   302  				}
   303  				avax.SortTransferableOutputs(outputs, codec)
   304  
   305  				baseTx := baseTx
   306  				baseTx.Outs = outputs
   307  				return &txs.Tx{
   308  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   309  					Creds:    creds,
   310  				}
   311  			},
   312  			err: safemath.ErrOverflow,
   313  		},
   314  		{
   315  			name: "insufficient funds",
   316  			txFunc: func() *txs.Tx {
   317  				input := input
   318  				input.In = &secp256k1fx.TransferInput{
   319  					Amt:   1,
   320  					Input: inputSigners,
   321  				}
   322  
   323  				baseTx := baseTx
   324  				baseTx.Ins = []*avax.TransferableInput{
   325  					&input,
   326  				}
   327  				return &txs.Tx{
   328  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   329  					Creds:    creds,
   330  				}
   331  			},
   332  			err: avax.ErrInsufficientFunds,
   333  		},
   334  		{
   335  			name: "invalid credential",
   336  			txFunc: func() *txs.Tx {
   337  				return &txs.Tx{
   338  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   339  					Creds: []*fxs.FxCredential{{
   340  						Credential: (*secp256k1fx.Credential)(nil),
   341  					}},
   342  				}
   343  			},
   344  			err: secp256k1fx.ErrNilCredential,
   345  		},
   346  		{
   347  			name: "wrong number of credentials",
   348  			txFunc: func() *txs.Tx {
   349  				return &txs.Tx{
   350  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   351  				}
   352  			},
   353  			err: errWrongNumberOfCredentials,
   354  		},
   355  		{
   356  			name: "barely sufficient funds",
   357  			txFunc: func() *txs.Tx {
   358  				input := input
   359  				input.In = &secp256k1fx.TransferInput{
   360  					Amt:   fxOutput.Amt + feeConfig.TxFee,
   361  					Input: inputSigners,
   362  				}
   363  
   364  				baseTx := baseTx
   365  				baseTx.Ins = []*avax.TransferableInput{
   366  					&input,
   367  				}
   368  				return &txs.Tx{
   369  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   370  					Creds:    creds,
   371  				}
   372  			},
   373  			err: nil,
   374  		},
   375  		{
   376  			name: "barely insufficient funds",
   377  			txFunc: func() *txs.Tx {
   378  				input := input
   379  				input.In = &secp256k1fx.TransferInput{
   380  					Amt:   fxOutput.Amt + feeConfig.TxFee - 1,
   381  					Input: inputSigners,
   382  				}
   383  
   384  				baseTx := baseTx
   385  				baseTx.Ins = []*avax.TransferableInput{
   386  					&input,
   387  				}
   388  				return &txs.Tx{
   389  					Unsigned: &txs.BaseTx{BaseTx: baseTx},
   390  					Creds:    creds,
   391  				}
   392  			},
   393  			err: avax.ErrInsufficientFunds,
   394  		},
   395  	}
   396  	for _, test := range tests {
   397  		t.Run(test.name, func(t *testing.T) {
   398  			tx := test.txFunc()
   399  			verifier := &SyntacticVerifier{
   400  				Backend: backend,
   401  				Tx:      tx,
   402  			}
   403  			err := tx.Unsigned.Visit(verifier)
   404  			require.ErrorIs(t, err, test.err)
   405  		})
   406  	}
   407  }
   408  
   409  func TestSyntacticVerifierCreateAssetTx(t *testing.T) {
   410  	ctx := snowtest.Context(t, snowtest.XChainID)
   411  
   412  	fx := &secp256k1fx.Fx{}
   413  	parser, err := txs.NewParser(
   414  		[]fxs.Fx{
   415  			fx,
   416  		},
   417  	)
   418  	require.NoError(t, err)
   419  
   420  	feeAssetID := ids.GenerateTestID()
   421  	asset := avax.Asset{
   422  		ID: feeAssetID,
   423  	}
   424  	outputOwners := secp256k1fx.OutputOwners{
   425  		Threshold: 1,
   426  		Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
   427  	}
   428  	fxOutput := secp256k1fx.TransferOutput{
   429  		Amt:          12345,
   430  		OutputOwners: outputOwners,
   431  	}
   432  	output := avax.TransferableOutput{
   433  		Asset: asset,
   434  		Out:   &fxOutput,
   435  	}
   436  	inputTxID := ids.GenerateTestID()
   437  	utxoID := avax.UTXOID{
   438  		TxID:        inputTxID,
   439  		OutputIndex: 0,
   440  	}
   441  	inputSigners := secp256k1fx.Input{
   442  		SigIndices: []uint32{2},
   443  	}
   444  	fxInput := secp256k1fx.TransferInput{
   445  		Amt:   54321,
   446  		Input: inputSigners,
   447  	}
   448  	input := avax.TransferableInput{
   449  		UTXOID: utxoID,
   450  		Asset:  asset,
   451  		In:     &fxInput,
   452  	}
   453  	baseTx := avax.BaseTx{
   454  		NetworkID:    constants.UnitTestID,
   455  		BlockchainID: ctx.ChainID,
   456  		Outs: []*avax.TransferableOutput{
   457  			&output,
   458  		},
   459  		Ins: []*avax.TransferableInput{
   460  			&input,
   461  		},
   462  	}
   463  	initialState := txs.InitialState{
   464  		FxIndex: 0,
   465  		Outs: []verify.State{
   466  			&fxOutput,
   467  		},
   468  	}
   469  	tx := txs.CreateAssetTx{
   470  		BaseTx:       txs.BaseTx{BaseTx: baseTx},
   471  		Name:         "NormalName",
   472  		Symbol:       "TICK",
   473  		Denomination: byte(2),
   474  		States: []*txs.InitialState{
   475  			&initialState,
   476  		},
   477  	}
   478  	cred := fxs.FxCredential{
   479  		Credential: &secp256k1fx.Credential{},
   480  	}
   481  	creds := []*fxs.FxCredential{
   482  		&cred,
   483  	}
   484  
   485  	codec := parser.Codec()
   486  	backend := &Backend{
   487  		Ctx:    ctx,
   488  		Config: &feeConfig,
   489  		Fxs: []*fxs.ParsedFx{
   490  			{
   491  				ID: secp256k1fx.ID,
   492  				Fx: fx,
   493  			},
   494  		},
   495  		Codec:      codec,
   496  		FeeAssetID: feeAssetID,
   497  	}
   498  
   499  	tests := []struct {
   500  		name   string
   501  		txFunc func() *txs.Tx
   502  		err    error
   503  	}{
   504  		{
   505  			name: "valid",
   506  			txFunc: func() *txs.Tx {
   507  				return &txs.Tx{
   508  					Unsigned: &tx,
   509  					Creds:    creds,
   510  				}
   511  			},
   512  			err: nil,
   513  		},
   514  		{
   515  			name: "name too short",
   516  			txFunc: func() *txs.Tx {
   517  				tx := tx
   518  				tx.Name = ""
   519  				return &txs.Tx{
   520  					Unsigned: &tx,
   521  					Creds:    creds,
   522  				}
   523  			},
   524  			err: errNameTooShort,
   525  		},
   526  		{
   527  			name: "name too long",
   528  			txFunc: func() *txs.Tx {
   529  				tx := tx
   530  				tx.Name = strings.Repeat("X", maxNameLen+1)
   531  				return &txs.Tx{
   532  					Unsigned: &tx,
   533  					Creds:    creds,
   534  				}
   535  			},
   536  			err: errNameTooLong,
   537  		},
   538  		{
   539  			name: "symbol too short",
   540  			txFunc: func() *txs.Tx {
   541  				tx := tx
   542  				tx.Symbol = ""
   543  				return &txs.Tx{
   544  					Unsigned: &tx,
   545  					Creds:    creds,
   546  				}
   547  			},
   548  			err: errSymbolTooShort,
   549  		},
   550  		{
   551  			name: "symbol too long",
   552  			txFunc: func() *txs.Tx {
   553  				tx := tx
   554  				tx.Symbol = strings.Repeat("X", maxSymbolLen+1)
   555  				return &txs.Tx{
   556  					Unsigned: &tx,
   557  					Creds:    creds,
   558  				}
   559  			},
   560  			err: errSymbolTooLong,
   561  		},
   562  		{
   563  			name: "no feature extensions",
   564  			txFunc: func() *txs.Tx {
   565  				tx := tx
   566  				tx.States = nil
   567  				return &txs.Tx{
   568  					Unsigned: &tx,
   569  					Creds:    creds,
   570  				}
   571  			},
   572  			err: errNoFxs,
   573  		},
   574  		{
   575  			name: "denomination too large",
   576  			txFunc: func() *txs.Tx {
   577  				tx := tx
   578  				tx.Denomination = maxDenomination + 1
   579  				return &txs.Tx{
   580  					Unsigned: &tx,
   581  					Creds:    creds,
   582  				}
   583  			},
   584  			err: errDenominationTooLarge,
   585  		},
   586  		{
   587  			name: "bounding whitespace in name",
   588  			txFunc: func() *txs.Tx {
   589  				tx := tx
   590  				tx.Name = " AVAX"
   591  				return &txs.Tx{
   592  					Unsigned: &tx,
   593  					Creds:    creds,
   594  				}
   595  			},
   596  			err: errUnexpectedWhitespace,
   597  		},
   598  		{
   599  			name: "illegal character in name",
   600  			txFunc: func() *txs.Tx {
   601  				tx := tx
   602  				tx.Name = "h8*32"
   603  				return &txs.Tx{
   604  					Unsigned: &tx,
   605  					Creds:    creds,
   606  				}
   607  			},
   608  			err: errIllegalNameCharacter,
   609  		},
   610  		{
   611  			name: "illegal character in ticker",
   612  			txFunc: func() *txs.Tx {
   613  				tx := tx
   614  				tx.Symbol = "H I"
   615  				return &txs.Tx{
   616  					Unsigned: &tx,
   617  					Creds:    creds,
   618  				}
   619  			},
   620  			err: errIllegalSymbolCharacter,
   621  		},
   622  		{
   623  			name: "wrong networkID",
   624  			txFunc: func() *txs.Tx {
   625  				tx := tx
   626  				tx.NetworkID++
   627  				return &txs.Tx{
   628  					Unsigned: &tx,
   629  					Creds:    creds,
   630  				}
   631  			},
   632  			err: avax.ErrWrongNetworkID,
   633  		},
   634  		{
   635  			name: "wrong chainID",
   636  			txFunc: func() *txs.Tx {
   637  				tx := tx
   638  				tx.BlockchainID = ids.GenerateTestID()
   639  				return &txs.Tx{
   640  					Unsigned: &tx,
   641  					Creds:    creds,
   642  				}
   643  			},
   644  			err: avax.ErrWrongChainID,
   645  		},
   646  		{
   647  			name: "memo too large",
   648  			txFunc: func() *txs.Tx {
   649  				tx := tx
   650  				tx.Memo = make([]byte, avax.MaxMemoSize+1)
   651  				return &txs.Tx{
   652  					Unsigned: &tx,
   653  					Creds:    creds,
   654  				}
   655  			},
   656  			err: avax.ErrMemoTooLarge,
   657  		},
   658  		{
   659  			name: "invalid output",
   660  			txFunc: func() *txs.Tx {
   661  				output := output
   662  				output.Out = &secp256k1fx.TransferOutput{
   663  					Amt:          0,
   664  					OutputOwners: outputOwners,
   665  				}
   666  
   667  				tx := tx
   668  				tx.Outs = []*avax.TransferableOutput{
   669  					&output,
   670  				}
   671  				return &txs.Tx{
   672  					Unsigned: &tx,
   673  					Creds:    creds,
   674  				}
   675  			},
   676  			err: secp256k1fx.ErrNoValueOutput,
   677  		},
   678  		{
   679  			name: "unsorted outputs",
   680  			txFunc: func() *txs.Tx {
   681  				output0 := output
   682  				output0.Out = &secp256k1fx.TransferOutput{
   683  					Amt:          1,
   684  					OutputOwners: outputOwners,
   685  				}
   686  
   687  				output1 := output
   688  				output1.Out = &secp256k1fx.TransferOutput{
   689  					Amt:          2,
   690  					OutputOwners: outputOwners,
   691  				}
   692  
   693  				outputs := []*avax.TransferableOutput{
   694  					&output0,
   695  					&output1,
   696  				}
   697  				avax.SortTransferableOutputs(outputs, codec)
   698  				outputs[0], outputs[1] = outputs[1], outputs[0]
   699  
   700  				tx := tx
   701  				tx.Outs = outputs
   702  				return &txs.Tx{
   703  					Unsigned: &tx,
   704  					Creds:    creds,
   705  				}
   706  			},
   707  			err: avax.ErrOutputsNotSorted,
   708  		},
   709  		{
   710  			name: "invalid input",
   711  			txFunc: func() *txs.Tx {
   712  				input := input
   713  				input.In = &secp256k1fx.TransferInput{
   714  					Amt:   0,
   715  					Input: inputSigners,
   716  				}
   717  
   718  				tx := tx
   719  				tx.Ins = []*avax.TransferableInput{
   720  					&input,
   721  				}
   722  				return &txs.Tx{
   723  					Unsigned: &tx,
   724  					Creds:    creds,
   725  				}
   726  			},
   727  			err: secp256k1fx.ErrNoValueInput,
   728  		},
   729  		{
   730  			name: "duplicate inputs",
   731  			txFunc: func() *txs.Tx {
   732  				tx := tx
   733  				tx.Ins = []*avax.TransferableInput{
   734  					&input,
   735  					&input,
   736  				}
   737  				return &txs.Tx{
   738  					Unsigned: &tx,
   739  					Creds: []*fxs.FxCredential{
   740  						&cred,
   741  						&cred,
   742  					},
   743  				}
   744  			},
   745  			err: avax.ErrInputsNotSortedUnique,
   746  		},
   747  		{
   748  			name: "input overflow",
   749  			txFunc: func() *txs.Tx {
   750  				input0 := input
   751  				input0.In = &secp256k1fx.TransferInput{
   752  					Amt:   1,
   753  					Input: inputSigners,
   754  				}
   755  
   756  				input1 := input
   757  				input1.UTXOID.OutputIndex++
   758  				input1.In = &secp256k1fx.TransferInput{
   759  					Amt:   math.MaxUint64,
   760  					Input: inputSigners,
   761  				}
   762  
   763  				tx := tx
   764  				tx.Ins = []*avax.TransferableInput{
   765  					&input0,
   766  					&input1,
   767  				}
   768  				avax.SortTransferableInputsWithSigners(baseTx.Ins, make([][]*secp256k1.PrivateKey, 2))
   769  				return &txs.Tx{
   770  					Unsigned: &tx,
   771  					Creds: []*fxs.FxCredential{
   772  						&cred,
   773  						&cred,
   774  					},
   775  				}
   776  			},
   777  			err: safemath.ErrOverflow,
   778  		},
   779  		{
   780  			name: "output overflow",
   781  			txFunc: func() *txs.Tx {
   782  				output0 := output
   783  				output0.Out = &secp256k1fx.TransferOutput{
   784  					Amt:          1,
   785  					OutputOwners: outputOwners,
   786  				}
   787  
   788  				output1 := output
   789  				output1.Out = &secp256k1fx.TransferOutput{
   790  					Amt:          math.MaxUint64,
   791  					OutputOwners: outputOwners,
   792  				}
   793  
   794  				outputs := []*avax.TransferableOutput{
   795  					&output0,
   796  					&output1,
   797  				}
   798  				avax.SortTransferableOutputs(outputs, codec)
   799  
   800  				tx := tx
   801  				tx.Outs = outputs
   802  				return &txs.Tx{
   803  					Unsigned: &tx,
   804  					Creds:    creds,
   805  				}
   806  			},
   807  			err: safemath.ErrOverflow,
   808  		},
   809  		{
   810  			name: "insufficient funds",
   811  			txFunc: func() *txs.Tx {
   812  				input := input
   813  				input.In = &secp256k1fx.TransferInput{
   814  					Amt:   1,
   815  					Input: inputSigners,
   816  				}
   817  
   818  				tx := tx
   819  				tx.Ins = []*avax.TransferableInput{
   820  					&input,
   821  				}
   822  				return &txs.Tx{
   823  					Unsigned: &tx,
   824  					Creds:    creds,
   825  				}
   826  			},
   827  			err: avax.ErrInsufficientFunds,
   828  		},
   829  		{
   830  			name: "invalid nil state",
   831  			txFunc: func() *txs.Tx {
   832  				tx := tx
   833  				tx.States = []*txs.InitialState{
   834  					nil,
   835  				}
   836  				return &txs.Tx{
   837  					Unsigned: &tx,
   838  					Creds:    creds,
   839  				}
   840  			},
   841  			err: txs.ErrNilInitialState,
   842  		},
   843  		{
   844  			name: "invalid fx",
   845  			txFunc: func() *txs.Tx {
   846  				initialState := initialState
   847  				initialState.FxIndex = 1
   848  
   849  				tx := tx
   850  				tx.States = []*txs.InitialState{
   851  					&initialState,
   852  				}
   853  				return &txs.Tx{
   854  					Unsigned: &tx,
   855  					Creds:    creds,
   856  				}
   857  			},
   858  			err: txs.ErrUnknownFx,
   859  		},
   860  		{
   861  			name: "invalid nil state output",
   862  			txFunc: func() *txs.Tx {
   863  				initialState := initialState
   864  				initialState.Outs = []verify.State{
   865  					nil,
   866  				}
   867  
   868  				tx := tx
   869  				tx.States = []*txs.InitialState{
   870  					&initialState,
   871  				}
   872  				return &txs.Tx{
   873  					Unsigned: &tx,
   874  					Creds:    creds,
   875  				}
   876  			},
   877  			err: txs.ErrNilFxOutput,
   878  		},
   879  		{
   880  			name: "invalid state output",
   881  			txFunc: func() *txs.Tx {
   882  				fxOutput := fxOutput
   883  				fxOutput.Amt = 0
   884  
   885  				initialState := initialState
   886  				initialState.Outs = []verify.State{
   887  					&fxOutput,
   888  				}
   889  
   890  				tx := tx
   891  				tx.States = []*txs.InitialState{
   892  					&initialState,
   893  				}
   894  				return &txs.Tx{
   895  					Unsigned: &tx,
   896  					Creds:    creds,
   897  				}
   898  			},
   899  			err: secp256k1fx.ErrNoValueOutput,
   900  		},
   901  		{
   902  			name: "unsorted initial state",
   903  			txFunc: func() *txs.Tx {
   904  				fxOutput0 := fxOutput
   905  
   906  				fxOutput1 := fxOutput
   907  				fxOutput1.Amt++
   908  
   909  				initialState := initialState
   910  				initialState.Outs = []verify.State{
   911  					&fxOutput0,
   912  					&fxOutput1,
   913  				}
   914  				initialState.Sort(codec)
   915  				initialState.Outs[0], initialState.Outs[1] = initialState.Outs[1], initialState.Outs[0]
   916  
   917  				tx := tx
   918  				tx.States = []*txs.InitialState{
   919  					&initialState,
   920  				}
   921  				return &txs.Tx{
   922  					Unsigned: &tx,
   923  					Creds:    creds,
   924  				}
   925  			},
   926  			err: txs.ErrOutputsNotSorted,
   927  		},
   928  		{
   929  			name: "non-unique initial states",
   930  			txFunc: func() *txs.Tx {
   931  				tx := tx
   932  				tx.States = []*txs.InitialState{
   933  					&initialState,
   934  					&initialState,
   935  				}
   936  				return &txs.Tx{
   937  					Unsigned: &tx,
   938  					Creds:    creds,
   939  				}
   940  			},
   941  			err: errInitialStatesNotSortedUnique,
   942  		},
   943  		{
   944  			name: "invalid credential",
   945  			txFunc: func() *txs.Tx {
   946  				return &txs.Tx{
   947  					Unsigned: &tx,
   948  					Creds: []*fxs.FxCredential{{
   949  						Credential: (*secp256k1fx.Credential)(nil),
   950  					}},
   951  				}
   952  			},
   953  			err: secp256k1fx.ErrNilCredential,
   954  		},
   955  		{
   956  			name: "wrong number of credentials",
   957  			txFunc: func() *txs.Tx {
   958  				return &txs.Tx{
   959  					Unsigned: &tx,
   960  				}
   961  			},
   962  			err: errWrongNumberOfCredentials,
   963  		},
   964  		{
   965  			name: "barely sufficient funds",
   966  			txFunc: func() *txs.Tx {
   967  				input := input
   968  				input.In = &secp256k1fx.TransferInput{
   969  					Amt:   fxOutput.Amt + feeConfig.CreateAssetTxFee,
   970  					Input: inputSigners,
   971  				}
   972  
   973  				tx := tx
   974  				tx.Ins = []*avax.TransferableInput{
   975  					&input,
   976  				}
   977  				return &txs.Tx{
   978  					Unsigned: &tx,
   979  					Creds:    creds,
   980  				}
   981  			},
   982  			err: nil,
   983  		},
   984  		{
   985  			name: "barely insufficient funds",
   986  			txFunc: func() *txs.Tx {
   987  				input := input
   988  				input.In = &secp256k1fx.TransferInput{
   989  					Amt:   fxOutput.Amt + feeConfig.CreateAssetTxFee - 1,
   990  					Input: inputSigners,
   991  				}
   992  
   993  				tx := tx
   994  				tx.Ins = []*avax.TransferableInput{
   995  					&input,
   996  				}
   997  				return &txs.Tx{
   998  					Unsigned: &tx,
   999  					Creds:    creds,
  1000  				}
  1001  			},
  1002  			err: avax.ErrInsufficientFunds,
  1003  		},
  1004  	}
  1005  	for _, test := range tests {
  1006  		t.Run(test.name, func(t *testing.T) {
  1007  			tx := test.txFunc()
  1008  			verifier := &SyntacticVerifier{
  1009  				Backend: backend,
  1010  				Tx:      tx,
  1011  			}
  1012  			err := tx.Unsigned.Visit(verifier)
  1013  			require.ErrorIs(t, err, test.err)
  1014  		})
  1015  	}
  1016  }
  1017  
  1018  func TestSyntacticVerifierOperationTx(t *testing.T) {
  1019  	ctx := snowtest.Context(t, snowtest.XChainID)
  1020  
  1021  	fx := &secp256k1fx.Fx{}
  1022  	parser, err := txs.NewParser(
  1023  		[]fxs.Fx{
  1024  			fx,
  1025  		},
  1026  	)
  1027  	require.NoError(t, err)
  1028  
  1029  	feeAssetID := ids.GenerateTestID()
  1030  	asset := avax.Asset{
  1031  		ID: feeAssetID,
  1032  	}
  1033  	outputOwners := secp256k1fx.OutputOwners{
  1034  		Threshold: 1,
  1035  		Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
  1036  	}
  1037  	fxOutput := secp256k1fx.TransferOutput{
  1038  		Amt:          12345,
  1039  		OutputOwners: outputOwners,
  1040  	}
  1041  	output := avax.TransferableOutput{
  1042  		Asset: asset,
  1043  		Out:   &fxOutput,
  1044  	}
  1045  	inputTxID := ids.GenerateTestID()
  1046  	utxoID := avax.UTXOID{
  1047  		TxID:        inputTxID,
  1048  		OutputIndex: 0,
  1049  	}
  1050  	inputSigners := secp256k1fx.Input{
  1051  		SigIndices: []uint32{2},
  1052  	}
  1053  	fxInput := secp256k1fx.TransferInput{
  1054  		Amt:   54321,
  1055  		Input: inputSigners,
  1056  	}
  1057  	input := avax.TransferableInput{
  1058  		UTXOID: utxoID,
  1059  		Asset:  asset,
  1060  		In:     &fxInput,
  1061  	}
  1062  	baseTx := avax.BaseTx{
  1063  		NetworkID:    constants.UnitTestID,
  1064  		BlockchainID: ctx.ChainID,
  1065  		Ins: []*avax.TransferableInput{
  1066  			&input,
  1067  		},
  1068  		Outs: []*avax.TransferableOutput{
  1069  			&output,
  1070  		},
  1071  	}
  1072  	opUTXOID := utxoID
  1073  	opUTXOID.OutputIndex++
  1074  	fxOp := secp256k1fx.MintOperation{
  1075  		MintInput: inputSigners,
  1076  		MintOutput: secp256k1fx.MintOutput{
  1077  			OutputOwners: outputOwners,
  1078  		},
  1079  		TransferOutput: fxOutput,
  1080  	}
  1081  	op := txs.Operation{
  1082  		Asset: asset,
  1083  		UTXOIDs: []*avax.UTXOID{
  1084  			&opUTXOID,
  1085  		},
  1086  		Op: &fxOp,
  1087  	}
  1088  	tx := txs.OperationTx{
  1089  		BaseTx: txs.BaseTx{BaseTx: baseTx},
  1090  		Ops: []*txs.Operation{
  1091  			&op,
  1092  		},
  1093  	}
  1094  	cred := fxs.FxCredential{
  1095  		Credential: &secp256k1fx.Credential{},
  1096  	}
  1097  	creds := []*fxs.FxCredential{
  1098  		&cred,
  1099  		&cred,
  1100  	}
  1101  
  1102  	codec := parser.Codec()
  1103  	backend := &Backend{
  1104  		Ctx:    ctx,
  1105  		Config: &feeConfig,
  1106  		Fxs: []*fxs.ParsedFx{
  1107  			{
  1108  				ID: secp256k1fx.ID,
  1109  				Fx: fx,
  1110  			},
  1111  		},
  1112  		Codec:      codec,
  1113  		FeeAssetID: feeAssetID,
  1114  	}
  1115  
  1116  	tests := []struct {
  1117  		name   string
  1118  		txFunc func() *txs.Tx
  1119  		err    error
  1120  	}{
  1121  		{
  1122  			name: "valid",
  1123  			txFunc: func() *txs.Tx {
  1124  				return &txs.Tx{
  1125  					Unsigned: &tx,
  1126  					Creds:    creds,
  1127  				}
  1128  			},
  1129  			err: nil,
  1130  		},
  1131  		{
  1132  			name: "no operation",
  1133  			txFunc: func() *txs.Tx {
  1134  				tx := tx
  1135  				tx.Ops = nil
  1136  				return &txs.Tx{
  1137  					Unsigned: &tx,
  1138  					Creds:    creds,
  1139  				}
  1140  			},
  1141  			err: errNoOperations,
  1142  		},
  1143  		{
  1144  			name: "wrong networkID",
  1145  			txFunc: func() *txs.Tx {
  1146  				tx := tx
  1147  				tx.NetworkID++
  1148  				return &txs.Tx{
  1149  					Unsigned: &tx,
  1150  					Creds:    creds,
  1151  				}
  1152  			},
  1153  			err: avax.ErrWrongNetworkID,
  1154  		},
  1155  		{
  1156  			name: "wrong chainID",
  1157  			txFunc: func() *txs.Tx {
  1158  				tx := tx
  1159  				tx.BlockchainID = ids.GenerateTestID()
  1160  				return &txs.Tx{
  1161  					Unsigned: &tx,
  1162  					Creds:    creds,
  1163  				}
  1164  			},
  1165  			err: avax.ErrWrongChainID,
  1166  		},
  1167  		{
  1168  			name: "memo too large",
  1169  			txFunc: func() *txs.Tx {
  1170  				tx := tx
  1171  				tx.Memo = make([]byte, avax.MaxMemoSize+1)
  1172  				return &txs.Tx{
  1173  					Unsigned: &tx,
  1174  					Creds:    creds,
  1175  				}
  1176  			},
  1177  			err: avax.ErrMemoTooLarge,
  1178  		},
  1179  		{
  1180  			name: "invalid output",
  1181  			txFunc: func() *txs.Tx {
  1182  				output := output
  1183  				output.Out = &secp256k1fx.TransferOutput{
  1184  					Amt:          0,
  1185  					OutputOwners: outputOwners,
  1186  				}
  1187  
  1188  				tx := tx
  1189  				tx.Outs = []*avax.TransferableOutput{
  1190  					&output,
  1191  				}
  1192  				return &txs.Tx{
  1193  					Unsigned: &tx,
  1194  					Creds:    creds,
  1195  				}
  1196  			},
  1197  			err: secp256k1fx.ErrNoValueOutput,
  1198  		},
  1199  		{
  1200  			name: "unsorted outputs",
  1201  			txFunc: func() *txs.Tx {
  1202  				output0 := output
  1203  				output0.Out = &secp256k1fx.TransferOutput{
  1204  					Amt:          1,
  1205  					OutputOwners: outputOwners,
  1206  				}
  1207  
  1208  				output1 := output
  1209  				output1.Out = &secp256k1fx.TransferOutput{
  1210  					Amt:          2,
  1211  					OutputOwners: outputOwners,
  1212  				}
  1213  
  1214  				outputs := []*avax.TransferableOutput{
  1215  					&output0,
  1216  					&output1,
  1217  				}
  1218  				avax.SortTransferableOutputs(outputs, codec)
  1219  				outputs[0], outputs[1] = outputs[1], outputs[0]
  1220  
  1221  				tx := tx
  1222  				tx.Outs = outputs
  1223  				return &txs.Tx{
  1224  					Unsigned: &tx,
  1225  					Creds:    creds,
  1226  				}
  1227  			},
  1228  			err: avax.ErrOutputsNotSorted,
  1229  		},
  1230  		{
  1231  			name: "invalid input",
  1232  			txFunc: func() *txs.Tx {
  1233  				input := input
  1234  				input.In = &secp256k1fx.TransferInput{
  1235  					Amt:   0,
  1236  					Input: inputSigners,
  1237  				}
  1238  
  1239  				tx := tx
  1240  				tx.Ins = []*avax.TransferableInput{
  1241  					&input,
  1242  				}
  1243  				return &txs.Tx{
  1244  					Unsigned: &tx,
  1245  					Creds:    creds,
  1246  				}
  1247  			},
  1248  			err: secp256k1fx.ErrNoValueInput,
  1249  		},
  1250  		{
  1251  			name: "duplicate inputs",
  1252  			txFunc: func() *txs.Tx {
  1253  				tx := tx
  1254  				tx.Ins = []*avax.TransferableInput{
  1255  					&input,
  1256  					&input,
  1257  				}
  1258  				return &txs.Tx{
  1259  					Unsigned: &tx,
  1260  					Creds: []*fxs.FxCredential{
  1261  						&cred,
  1262  						&cred,
  1263  					},
  1264  				}
  1265  			},
  1266  			err: avax.ErrInputsNotSortedUnique,
  1267  		},
  1268  		{
  1269  			name: "input overflow",
  1270  			txFunc: func() *txs.Tx {
  1271  				input0 := input
  1272  				input0.In = &secp256k1fx.TransferInput{
  1273  					Amt:   1,
  1274  					Input: inputSigners,
  1275  				}
  1276  
  1277  				input1 := input
  1278  				input1.UTXOID.OutputIndex++
  1279  				input1.In = &secp256k1fx.TransferInput{
  1280  					Amt:   math.MaxUint64,
  1281  					Input: inputSigners,
  1282  				}
  1283  
  1284  				tx := tx
  1285  				tx.Ins = []*avax.TransferableInput{
  1286  					&input0,
  1287  					&input1,
  1288  				}
  1289  				avax.SortTransferableInputsWithSigners(tx.Ins, make([][]*secp256k1.PrivateKey, 2))
  1290  				return &txs.Tx{
  1291  					Unsigned: &tx,
  1292  					Creds: []*fxs.FxCredential{
  1293  						&cred,
  1294  						&cred,
  1295  					},
  1296  				}
  1297  			},
  1298  			err: safemath.ErrOverflow,
  1299  		},
  1300  		{
  1301  			name: "output overflow",
  1302  			txFunc: func() *txs.Tx {
  1303  				output := output
  1304  				output.Out = &secp256k1fx.TransferOutput{
  1305  					Amt:          math.MaxUint64,
  1306  					OutputOwners: outputOwners,
  1307  				}
  1308  
  1309  				outputs := []*avax.TransferableOutput{
  1310  					&output,
  1311  				}
  1312  				avax.SortTransferableOutputs(outputs, codec)
  1313  
  1314  				tx := tx
  1315  				tx.Outs = outputs
  1316  				return &txs.Tx{
  1317  					Unsigned: &tx,
  1318  					Creds:    creds,
  1319  				}
  1320  			},
  1321  			err: safemath.ErrOverflow,
  1322  		},
  1323  		{
  1324  			name: "insufficient funds",
  1325  			txFunc: func() *txs.Tx {
  1326  				input := input
  1327  				input.In = &secp256k1fx.TransferInput{
  1328  					Amt:   1,
  1329  					Input: inputSigners,
  1330  				}
  1331  
  1332  				tx := tx
  1333  				tx.Ins = []*avax.TransferableInput{
  1334  					&input,
  1335  				}
  1336  				return &txs.Tx{
  1337  					Unsigned: &tx,
  1338  					Creds:    creds,
  1339  				}
  1340  			},
  1341  			err: avax.ErrInsufficientFunds,
  1342  		},
  1343  		{
  1344  			name: "invalid nil op",
  1345  			txFunc: func() *txs.Tx {
  1346  				tx := tx
  1347  				tx.Ops = []*txs.Operation{
  1348  					nil,
  1349  				}
  1350  				return &txs.Tx{
  1351  					Unsigned: &tx,
  1352  					Creds:    creds,
  1353  				}
  1354  			},
  1355  			err: txs.ErrNilOperation,
  1356  		},
  1357  		{
  1358  			name: "invalid nil fx op",
  1359  			txFunc: func() *txs.Tx {
  1360  				op := op
  1361  				op.Op = nil
  1362  
  1363  				tx := tx
  1364  				tx.Ops = []*txs.Operation{
  1365  					&op,
  1366  				}
  1367  				return &txs.Tx{
  1368  					Unsigned: &tx,
  1369  					Creds:    creds,
  1370  				}
  1371  			},
  1372  			err: txs.ErrNilFxOperation,
  1373  		},
  1374  		{
  1375  			name: "invalid duplicated op UTXOs",
  1376  			txFunc: func() *txs.Tx {
  1377  				op := op
  1378  				op.UTXOIDs = []*avax.UTXOID{
  1379  					&opUTXOID,
  1380  					&opUTXOID,
  1381  				}
  1382  
  1383  				tx := tx
  1384  				tx.Ops = []*txs.Operation{
  1385  					&op,
  1386  				}
  1387  				return &txs.Tx{
  1388  					Unsigned: &tx,
  1389  					Creds:    creds,
  1390  				}
  1391  			},
  1392  			err: txs.ErrNotSortedAndUniqueUTXOIDs,
  1393  		},
  1394  		{
  1395  			name: "invalid duplicated UTXOs across ops",
  1396  			txFunc: func() *txs.Tx {
  1397  				newOp := op
  1398  				op.Asset.ID = ids.GenerateTestID()
  1399  
  1400  				tx := tx
  1401  				tx.Ops = []*txs.Operation{
  1402  					&op,
  1403  					&newOp,
  1404  				}
  1405  				txs.SortOperations(tx.Ops, codec)
  1406  				return &txs.Tx{
  1407  					Unsigned: &tx,
  1408  					Creds:    creds,
  1409  				}
  1410  			},
  1411  			err: errDoubleSpend,
  1412  		},
  1413  		{
  1414  			name: "invalid duplicated op",
  1415  			txFunc: func() *txs.Tx {
  1416  				op := op
  1417  				op.UTXOIDs = nil
  1418  
  1419  				tx := tx
  1420  				tx.Ops = []*txs.Operation{
  1421  					&op,
  1422  					&op,
  1423  				}
  1424  				txs.SortOperations(tx.Ops, codec)
  1425  				return &txs.Tx{
  1426  					Unsigned: &tx,
  1427  					Creds:    creds,
  1428  				}
  1429  			},
  1430  			err: errOperationsNotSortedUnique,
  1431  		},
  1432  		{
  1433  			name: "invalid credential",
  1434  			txFunc: func() *txs.Tx {
  1435  				return &txs.Tx{
  1436  					Unsigned: &tx,
  1437  					Creds: []*fxs.FxCredential{{
  1438  						Credential: (*secp256k1fx.Credential)(nil),
  1439  					}},
  1440  				}
  1441  			},
  1442  			err: secp256k1fx.ErrNilCredential,
  1443  		},
  1444  		{
  1445  			name: "wrong number of credentials",
  1446  			txFunc: func() *txs.Tx {
  1447  				return &txs.Tx{
  1448  					Unsigned: &tx,
  1449  				}
  1450  			},
  1451  			err: errWrongNumberOfCredentials,
  1452  		},
  1453  		{
  1454  			name: "barely sufficient funds",
  1455  			txFunc: func() *txs.Tx {
  1456  				input := input
  1457  				input.In = &secp256k1fx.TransferInput{
  1458  					Amt:   fxOutput.Amt + feeConfig.TxFee,
  1459  					Input: inputSigners,
  1460  				}
  1461  
  1462  				tx := tx
  1463  				tx.Ins = []*avax.TransferableInput{
  1464  					&input,
  1465  				}
  1466  				return &txs.Tx{
  1467  					Unsigned: &tx,
  1468  					Creds:    creds,
  1469  				}
  1470  			},
  1471  			err: nil,
  1472  		},
  1473  		{
  1474  			name: "barely insufficient funds",
  1475  			txFunc: func() *txs.Tx {
  1476  				input := input
  1477  				input.In = &secp256k1fx.TransferInput{
  1478  					Amt:   fxOutput.Amt + feeConfig.TxFee - 1,
  1479  					Input: inputSigners,
  1480  				}
  1481  
  1482  				tx := tx
  1483  				tx.Ins = []*avax.TransferableInput{
  1484  					&input,
  1485  				}
  1486  				return &txs.Tx{
  1487  					Unsigned: &tx,
  1488  					Creds:    creds,
  1489  				}
  1490  			},
  1491  			err: avax.ErrInsufficientFunds,
  1492  		},
  1493  	}
  1494  	for _, test := range tests {
  1495  		t.Run(test.name, func(t *testing.T) {
  1496  			tx := test.txFunc()
  1497  			verifier := &SyntacticVerifier{
  1498  				Backend: backend,
  1499  				Tx:      tx,
  1500  			}
  1501  			err := tx.Unsigned.Visit(verifier)
  1502  			require.ErrorIs(t, err, test.err)
  1503  		})
  1504  	}
  1505  }
  1506  
  1507  func TestSyntacticVerifierImportTx(t *testing.T) {
  1508  	ctx := snowtest.Context(t, snowtest.XChainID)
  1509  
  1510  	fx := &secp256k1fx.Fx{}
  1511  	parser, err := txs.NewParser(
  1512  		[]fxs.Fx{
  1513  			fx,
  1514  		},
  1515  	)
  1516  	require.NoError(t, err)
  1517  
  1518  	feeAssetID := ids.GenerateTestID()
  1519  	asset := avax.Asset{
  1520  		ID: feeAssetID,
  1521  	}
  1522  	outputOwners := secp256k1fx.OutputOwners{
  1523  		Threshold: 1,
  1524  		Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
  1525  	}
  1526  	fxOutput := secp256k1fx.TransferOutput{
  1527  		Amt:          12345,
  1528  		OutputOwners: outputOwners,
  1529  	}
  1530  	output := avax.TransferableOutput{
  1531  		Asset: asset,
  1532  		Out:   &fxOutput,
  1533  	}
  1534  	inputTxID := ids.GenerateTestID()
  1535  	utxoID := avax.UTXOID{
  1536  		TxID:        inputTxID,
  1537  		OutputIndex: 0,
  1538  	}
  1539  	inputSigners := secp256k1fx.Input{
  1540  		SigIndices: []uint32{2},
  1541  	}
  1542  	fxInput := secp256k1fx.TransferInput{
  1543  		Amt:   54321,
  1544  		Input: inputSigners,
  1545  	}
  1546  	input := avax.TransferableInput{
  1547  		UTXOID: utxoID,
  1548  		Asset:  asset,
  1549  		In:     &fxInput,
  1550  	}
  1551  	baseTx := avax.BaseTx{
  1552  		NetworkID:    constants.UnitTestID,
  1553  		BlockchainID: ctx.ChainID,
  1554  		Outs: []*avax.TransferableOutput{
  1555  			&output,
  1556  		},
  1557  	}
  1558  	tx := txs.ImportTx{
  1559  		BaseTx:      txs.BaseTx{BaseTx: baseTx},
  1560  		SourceChain: ctx.CChainID,
  1561  		ImportedIns: []*avax.TransferableInput{
  1562  			&input,
  1563  		},
  1564  	}
  1565  	cred := fxs.FxCredential{
  1566  		Credential: &secp256k1fx.Credential{},
  1567  	}
  1568  	creds := []*fxs.FxCredential{
  1569  		&cred,
  1570  	}
  1571  
  1572  	codec := parser.Codec()
  1573  	backend := &Backend{
  1574  		Ctx:    ctx,
  1575  		Config: &feeConfig,
  1576  		Fxs: []*fxs.ParsedFx{
  1577  			{
  1578  				ID: secp256k1fx.ID,
  1579  				Fx: fx,
  1580  			},
  1581  		},
  1582  		Codec:      codec,
  1583  		FeeAssetID: feeAssetID,
  1584  	}
  1585  
  1586  	tests := []struct {
  1587  		name   string
  1588  		txFunc func() *txs.Tx
  1589  		err    error
  1590  	}{
  1591  		{
  1592  			name: "valid",
  1593  			txFunc: func() *txs.Tx {
  1594  				return &txs.Tx{
  1595  					Unsigned: &tx,
  1596  					Creds:    creds,
  1597  				}
  1598  			},
  1599  			err: nil,
  1600  		},
  1601  		{
  1602  			name: "no imported inputs",
  1603  			txFunc: func() *txs.Tx {
  1604  				tx := tx
  1605  				tx.ImportedIns = nil
  1606  				return &txs.Tx{
  1607  					Unsigned: &tx,
  1608  					Creds:    creds,
  1609  				}
  1610  			},
  1611  			err: errNoImportInputs,
  1612  		},
  1613  		{
  1614  			name: "wrong networkID",
  1615  			txFunc: func() *txs.Tx {
  1616  				tx := tx
  1617  				tx.NetworkID++
  1618  				return &txs.Tx{
  1619  					Unsigned: &tx,
  1620  					Creds:    creds,
  1621  				}
  1622  			},
  1623  			err: avax.ErrWrongNetworkID,
  1624  		},
  1625  		{
  1626  			name: "wrong chainID",
  1627  			txFunc: func() *txs.Tx {
  1628  				tx := tx
  1629  				tx.BlockchainID = ids.GenerateTestID()
  1630  				return &txs.Tx{
  1631  					Unsigned: &tx,
  1632  					Creds:    creds,
  1633  				}
  1634  			},
  1635  			err: avax.ErrWrongChainID,
  1636  		},
  1637  		{
  1638  			name: "memo too large",
  1639  			txFunc: func() *txs.Tx {
  1640  				tx := tx
  1641  				tx.Memo = make([]byte, avax.MaxMemoSize+1)
  1642  				return &txs.Tx{
  1643  					Unsigned: &tx,
  1644  					Creds:    creds,
  1645  				}
  1646  			},
  1647  			err: avax.ErrMemoTooLarge,
  1648  		},
  1649  		{
  1650  			name: "invalid output",
  1651  			txFunc: func() *txs.Tx {
  1652  				output := output
  1653  				output.Out = &secp256k1fx.TransferOutput{
  1654  					Amt:          0,
  1655  					OutputOwners: outputOwners,
  1656  				}
  1657  
  1658  				tx := tx
  1659  				tx.Outs = []*avax.TransferableOutput{
  1660  					&output,
  1661  				}
  1662  				return &txs.Tx{
  1663  					Unsigned: &tx,
  1664  					Creds:    creds,
  1665  				}
  1666  			},
  1667  			err: secp256k1fx.ErrNoValueOutput,
  1668  		},
  1669  		{
  1670  			name: "unsorted outputs",
  1671  			txFunc: func() *txs.Tx {
  1672  				output0 := output
  1673  				output0.Out = &secp256k1fx.TransferOutput{
  1674  					Amt:          1,
  1675  					OutputOwners: outputOwners,
  1676  				}
  1677  
  1678  				output1 := output
  1679  				output1.Out = &secp256k1fx.TransferOutput{
  1680  					Amt:          2,
  1681  					OutputOwners: outputOwners,
  1682  				}
  1683  
  1684  				outputs := []*avax.TransferableOutput{
  1685  					&output0,
  1686  					&output1,
  1687  				}
  1688  				avax.SortTransferableOutputs(outputs, codec)
  1689  				outputs[0], outputs[1] = outputs[1], outputs[0]
  1690  
  1691  				tx := tx
  1692  				tx.Outs = outputs
  1693  				return &txs.Tx{
  1694  					Unsigned: &tx,
  1695  					Creds:    creds,
  1696  				}
  1697  			},
  1698  			err: avax.ErrOutputsNotSorted,
  1699  		},
  1700  		{
  1701  			name: "invalid input",
  1702  			txFunc: func() *txs.Tx {
  1703  				input := input
  1704  				input.In = &secp256k1fx.TransferInput{
  1705  					Amt:   0,
  1706  					Input: inputSigners,
  1707  				}
  1708  
  1709  				tx := tx
  1710  				tx.Ins = []*avax.TransferableInput{
  1711  					&input,
  1712  				}
  1713  				return &txs.Tx{
  1714  					Unsigned: &tx,
  1715  					Creds:    creds,
  1716  				}
  1717  			},
  1718  			err: secp256k1fx.ErrNoValueInput,
  1719  		},
  1720  		{
  1721  			name: "duplicate inputs",
  1722  			txFunc: func() *txs.Tx {
  1723  				tx := tx
  1724  				tx.Ins = []*avax.TransferableInput{
  1725  					&input,
  1726  					&input,
  1727  				}
  1728  				return &txs.Tx{
  1729  					Unsigned: &tx,
  1730  					Creds: []*fxs.FxCredential{
  1731  						&cred,
  1732  						&cred,
  1733  						&cred,
  1734  					},
  1735  				}
  1736  			},
  1737  			err: avax.ErrInputsNotSortedUnique,
  1738  		},
  1739  		{
  1740  			name: "duplicate imported inputs",
  1741  			txFunc: func() *txs.Tx {
  1742  				tx := tx
  1743  				tx.ImportedIns = []*avax.TransferableInput{
  1744  					&input,
  1745  					&input,
  1746  				}
  1747  				return &txs.Tx{
  1748  					Unsigned: &tx,
  1749  					Creds: []*fxs.FxCredential{
  1750  						&cred,
  1751  						&cred,
  1752  					},
  1753  				}
  1754  			},
  1755  			err: avax.ErrInputsNotSortedUnique,
  1756  		},
  1757  		{
  1758  			name: "input overflow",
  1759  			txFunc: func() *txs.Tx {
  1760  				input0 := input
  1761  				input0.In = &secp256k1fx.TransferInput{
  1762  					Amt:   1,
  1763  					Input: inputSigners,
  1764  				}
  1765  
  1766  				input1 := input
  1767  				input1.UTXOID.OutputIndex++
  1768  				input1.In = &secp256k1fx.TransferInput{
  1769  					Amt:   math.MaxUint64,
  1770  					Input: inputSigners,
  1771  				}
  1772  
  1773  				tx := tx
  1774  				tx.Ins = []*avax.TransferableInput{
  1775  					&input0,
  1776  					&input1,
  1777  				}
  1778  				avax.SortTransferableInputsWithSigners(tx.Ins, make([][]*secp256k1.PrivateKey, 2))
  1779  				return &txs.Tx{
  1780  					Unsigned: &tx,
  1781  					Creds: []*fxs.FxCredential{
  1782  						&cred,
  1783  						&cred,
  1784  					},
  1785  				}
  1786  			},
  1787  			err: safemath.ErrOverflow,
  1788  		},
  1789  		{
  1790  			name: "output overflow",
  1791  			txFunc: func() *txs.Tx {
  1792  				output := output
  1793  				output.Out = &secp256k1fx.TransferOutput{
  1794  					Amt:          math.MaxUint64,
  1795  					OutputOwners: outputOwners,
  1796  				}
  1797  
  1798  				outputs := []*avax.TransferableOutput{
  1799  					&output,
  1800  				}
  1801  				avax.SortTransferableOutputs(outputs, codec)
  1802  
  1803  				tx := tx
  1804  				tx.Outs = outputs
  1805  				return &txs.Tx{
  1806  					Unsigned: &tx,
  1807  					Creds:    creds,
  1808  				}
  1809  			},
  1810  			err: safemath.ErrOverflow,
  1811  		},
  1812  		{
  1813  			name: "insufficient funds",
  1814  			txFunc: func() *txs.Tx {
  1815  				input := input
  1816  				input.In = &secp256k1fx.TransferInput{
  1817  					Amt:   1,
  1818  					Input: inputSigners,
  1819  				}
  1820  
  1821  				tx := tx
  1822  				tx.ImportedIns = []*avax.TransferableInput{
  1823  					&input,
  1824  				}
  1825  				return &txs.Tx{
  1826  					Unsigned: &tx,
  1827  					Creds:    creds,
  1828  				}
  1829  			},
  1830  			err: avax.ErrInsufficientFunds,
  1831  		},
  1832  		{
  1833  			name: "invalid credential",
  1834  			txFunc: func() *txs.Tx {
  1835  				return &txs.Tx{
  1836  					Unsigned: &tx,
  1837  					Creds: []*fxs.FxCredential{{
  1838  						Credential: (*secp256k1fx.Credential)(nil),
  1839  					}},
  1840  				}
  1841  			},
  1842  			err: secp256k1fx.ErrNilCredential,
  1843  		},
  1844  		{
  1845  			name: "wrong number of credentials",
  1846  			txFunc: func() *txs.Tx {
  1847  				return &txs.Tx{
  1848  					Unsigned: &tx,
  1849  				}
  1850  			},
  1851  			err: errWrongNumberOfCredentials,
  1852  		},
  1853  		{
  1854  			name: "barely sufficient funds",
  1855  			txFunc: func() *txs.Tx {
  1856  				input := input
  1857  				input.In = &secp256k1fx.TransferInput{
  1858  					Amt:   fxOutput.Amt + feeConfig.TxFee,
  1859  					Input: inputSigners,
  1860  				}
  1861  
  1862  				tx := tx
  1863  				tx.ImportedIns = []*avax.TransferableInput{
  1864  					&input,
  1865  				}
  1866  				return &txs.Tx{
  1867  					Unsigned: &tx,
  1868  					Creds:    creds,
  1869  				}
  1870  			},
  1871  			err: nil,
  1872  		},
  1873  		{
  1874  			name: "barely insufficient funds",
  1875  			txFunc: func() *txs.Tx {
  1876  				input := input
  1877  				input.In = &secp256k1fx.TransferInput{
  1878  					Amt:   fxOutput.Amt + feeConfig.TxFee - 1,
  1879  					Input: inputSigners,
  1880  				}
  1881  
  1882  				tx := tx
  1883  				tx.ImportedIns = []*avax.TransferableInput{
  1884  					&input,
  1885  				}
  1886  				return &txs.Tx{
  1887  					Unsigned: &tx,
  1888  					Creds:    creds,
  1889  				}
  1890  			},
  1891  			err: avax.ErrInsufficientFunds,
  1892  		},
  1893  	}
  1894  	for _, test := range tests {
  1895  		t.Run(test.name, func(t *testing.T) {
  1896  			tx := test.txFunc()
  1897  			verifier := &SyntacticVerifier{
  1898  				Backend: backend,
  1899  				Tx:      tx,
  1900  			}
  1901  			err := tx.Unsigned.Visit(verifier)
  1902  			require.ErrorIs(t, err, test.err)
  1903  		})
  1904  	}
  1905  }
  1906  
  1907  func TestSyntacticVerifierExportTx(t *testing.T) {
  1908  	ctx := snowtest.Context(t, snowtest.XChainID)
  1909  
  1910  	fx := &secp256k1fx.Fx{}
  1911  	parser, err := txs.NewParser(
  1912  		[]fxs.Fx{
  1913  			fx,
  1914  		},
  1915  	)
  1916  	require.NoError(t, err)
  1917  
  1918  	feeAssetID := ids.GenerateTestID()
  1919  	asset := avax.Asset{
  1920  		ID: feeAssetID,
  1921  	}
  1922  	outputOwners := secp256k1fx.OutputOwners{
  1923  		Threshold: 1,
  1924  		Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
  1925  	}
  1926  	fxOutput := secp256k1fx.TransferOutput{
  1927  		Amt:          12345,
  1928  		OutputOwners: outputOwners,
  1929  	}
  1930  	output := avax.TransferableOutput{
  1931  		Asset: asset,
  1932  		Out:   &fxOutput,
  1933  	}
  1934  	inputTxID := ids.GenerateTestID()
  1935  	utxoID := avax.UTXOID{
  1936  		TxID:        inputTxID,
  1937  		OutputIndex: 0,
  1938  	}
  1939  	inputSigners := secp256k1fx.Input{
  1940  		SigIndices: []uint32{2},
  1941  	}
  1942  	fxInput := secp256k1fx.TransferInput{
  1943  		Amt:   54321,
  1944  		Input: inputSigners,
  1945  	}
  1946  	input := avax.TransferableInput{
  1947  		UTXOID: utxoID,
  1948  		Asset:  asset,
  1949  		In:     &fxInput,
  1950  	}
  1951  	baseTx := avax.BaseTx{
  1952  		NetworkID:    constants.UnitTestID,
  1953  		BlockchainID: ctx.ChainID,
  1954  		Ins: []*avax.TransferableInput{
  1955  			&input,
  1956  		},
  1957  	}
  1958  	tx := txs.ExportTx{
  1959  		BaseTx:           txs.BaseTx{BaseTx: baseTx},
  1960  		DestinationChain: ctx.CChainID,
  1961  		ExportedOuts: []*avax.TransferableOutput{
  1962  			&output,
  1963  		},
  1964  	}
  1965  	cred := fxs.FxCredential{
  1966  		Credential: &secp256k1fx.Credential{},
  1967  	}
  1968  	creds := []*fxs.FxCredential{
  1969  		&cred,
  1970  	}
  1971  
  1972  	codec := parser.Codec()
  1973  	backend := &Backend{
  1974  		Ctx:    ctx,
  1975  		Config: &feeConfig,
  1976  		Fxs: []*fxs.ParsedFx{
  1977  			{
  1978  				ID: secp256k1fx.ID,
  1979  				Fx: fx,
  1980  			},
  1981  		},
  1982  		Codec:      codec,
  1983  		FeeAssetID: feeAssetID,
  1984  	}
  1985  
  1986  	tests := []struct {
  1987  		name   string
  1988  		txFunc func() *txs.Tx
  1989  		err    error
  1990  	}{
  1991  		{
  1992  			name: "valid",
  1993  			txFunc: func() *txs.Tx {
  1994  				return &txs.Tx{
  1995  					Unsigned: &tx,
  1996  					Creds:    creds,
  1997  				}
  1998  			},
  1999  			err: nil,
  2000  		},
  2001  		{
  2002  			name: "no exported outputs",
  2003  			txFunc: func() *txs.Tx {
  2004  				tx := tx
  2005  				tx.ExportedOuts = nil
  2006  				return &txs.Tx{
  2007  					Unsigned: &tx,
  2008  					Creds:    creds,
  2009  				}
  2010  			},
  2011  			err: errNoExportOutputs,
  2012  		},
  2013  		{
  2014  			name: "wrong networkID",
  2015  			txFunc: func() *txs.Tx {
  2016  				tx := tx
  2017  				tx.NetworkID++
  2018  				return &txs.Tx{
  2019  					Unsigned: &tx,
  2020  					Creds:    creds,
  2021  				}
  2022  			},
  2023  			err: avax.ErrWrongNetworkID,
  2024  		},
  2025  		{
  2026  			name: "wrong chainID",
  2027  			txFunc: func() *txs.Tx {
  2028  				tx := tx
  2029  				tx.BlockchainID = ids.GenerateTestID()
  2030  				return &txs.Tx{
  2031  					Unsigned: &tx,
  2032  					Creds:    creds,
  2033  				}
  2034  			},
  2035  			err: avax.ErrWrongChainID,
  2036  		},
  2037  		{
  2038  			name: "memo too large",
  2039  			txFunc: func() *txs.Tx {
  2040  				tx := tx
  2041  				tx.Memo = make([]byte, avax.MaxMemoSize+1)
  2042  				return &txs.Tx{
  2043  					Unsigned: &tx,
  2044  					Creds:    creds,
  2045  				}
  2046  			},
  2047  			err: avax.ErrMemoTooLarge,
  2048  		},
  2049  		{
  2050  			name: "invalid output",
  2051  			txFunc: func() *txs.Tx {
  2052  				output := output
  2053  				output.Out = &secp256k1fx.TransferOutput{
  2054  					Amt:          0,
  2055  					OutputOwners: outputOwners,
  2056  				}
  2057  
  2058  				tx := tx
  2059  				tx.Outs = []*avax.TransferableOutput{
  2060  					&output,
  2061  				}
  2062  				return &txs.Tx{
  2063  					Unsigned: &tx,
  2064  					Creds:    creds,
  2065  				}
  2066  			},
  2067  			err: secp256k1fx.ErrNoValueOutput,
  2068  		},
  2069  		{
  2070  			name: "unsorted outputs",
  2071  			txFunc: func() *txs.Tx {
  2072  				output0 := output
  2073  				output0.Out = &secp256k1fx.TransferOutput{
  2074  					Amt:          1,
  2075  					OutputOwners: outputOwners,
  2076  				}
  2077  
  2078  				output1 := output
  2079  				output1.Out = &secp256k1fx.TransferOutput{
  2080  					Amt:          2,
  2081  					OutputOwners: outputOwners,
  2082  				}
  2083  
  2084  				outputs := []*avax.TransferableOutput{
  2085  					&output0,
  2086  					&output1,
  2087  				}
  2088  				avax.SortTransferableOutputs(outputs, codec)
  2089  				outputs[0], outputs[1] = outputs[1], outputs[0]
  2090  
  2091  				tx := tx
  2092  				tx.Outs = outputs
  2093  				return &txs.Tx{
  2094  					Unsigned: &tx,
  2095  					Creds:    creds,
  2096  				}
  2097  			},
  2098  			err: avax.ErrOutputsNotSorted,
  2099  		},
  2100  		{
  2101  			name: "unsorted exported outputs",
  2102  			txFunc: func() *txs.Tx {
  2103  				output0 := output
  2104  				output0.Out = &secp256k1fx.TransferOutput{
  2105  					Amt:          1,
  2106  					OutputOwners: outputOwners,
  2107  				}
  2108  
  2109  				output1 := output
  2110  				output1.Out = &secp256k1fx.TransferOutput{
  2111  					Amt:          2,
  2112  					OutputOwners: outputOwners,
  2113  				}
  2114  
  2115  				outputs := []*avax.TransferableOutput{
  2116  					&output0,
  2117  					&output1,
  2118  				}
  2119  				avax.SortTransferableOutputs(outputs, codec)
  2120  				outputs[0], outputs[1] = outputs[1], outputs[0]
  2121  
  2122  				tx := tx
  2123  				tx.ExportedOuts = outputs
  2124  				return &txs.Tx{
  2125  					Unsigned: &tx,
  2126  					Creds:    creds,
  2127  				}
  2128  			},
  2129  			err: avax.ErrOutputsNotSorted,
  2130  		},
  2131  		{
  2132  			name: "invalid input",
  2133  			txFunc: func() *txs.Tx {
  2134  				input := input
  2135  				input.In = &secp256k1fx.TransferInput{
  2136  					Amt:   0,
  2137  					Input: inputSigners,
  2138  				}
  2139  
  2140  				tx := tx
  2141  				tx.Ins = []*avax.TransferableInput{
  2142  					&input,
  2143  				}
  2144  				return &txs.Tx{
  2145  					Unsigned: &tx,
  2146  					Creds:    creds,
  2147  				}
  2148  			},
  2149  			err: secp256k1fx.ErrNoValueInput,
  2150  		},
  2151  		{
  2152  			name: "duplicate inputs",
  2153  			txFunc: func() *txs.Tx {
  2154  				tx := tx
  2155  				tx.Ins = []*avax.TransferableInput{
  2156  					&input,
  2157  					&input,
  2158  				}
  2159  				return &txs.Tx{
  2160  					Unsigned: &tx,
  2161  					Creds: []*fxs.FxCredential{
  2162  						&cred,
  2163  						&cred,
  2164  					},
  2165  				}
  2166  			},
  2167  			err: avax.ErrInputsNotSortedUnique,
  2168  		},
  2169  		{
  2170  			name: "input overflow",
  2171  			txFunc: func() *txs.Tx {
  2172  				input0 := input
  2173  				input0.In = &secp256k1fx.TransferInput{
  2174  					Amt:   1,
  2175  					Input: inputSigners,
  2176  				}
  2177  
  2178  				input1 := input
  2179  				input1.UTXOID.OutputIndex++
  2180  				input1.In = &secp256k1fx.TransferInput{
  2181  					Amt:   math.MaxUint64,
  2182  					Input: inputSigners,
  2183  				}
  2184  
  2185  				tx := tx
  2186  				tx.Ins = []*avax.TransferableInput{
  2187  					&input0,
  2188  					&input1,
  2189  				}
  2190  				avax.SortTransferableInputsWithSigners(tx.Ins, make([][]*secp256k1.PrivateKey, 2))
  2191  				return &txs.Tx{
  2192  					Unsigned: &tx,
  2193  					Creds: []*fxs.FxCredential{
  2194  						&cred,
  2195  						&cred,
  2196  					},
  2197  				}
  2198  			},
  2199  			err: safemath.ErrOverflow,
  2200  		},
  2201  		{
  2202  			name: "output overflow",
  2203  			txFunc: func() *txs.Tx {
  2204  				output := output
  2205  				output.Out = &secp256k1fx.TransferOutput{
  2206  					Amt:          math.MaxUint64,
  2207  					OutputOwners: outputOwners,
  2208  				}
  2209  
  2210  				outputs := []*avax.TransferableOutput{
  2211  					&output,
  2212  				}
  2213  				avax.SortTransferableOutputs(outputs, codec)
  2214  
  2215  				tx := tx
  2216  				tx.Outs = outputs
  2217  				return &txs.Tx{
  2218  					Unsigned: &tx,
  2219  					Creds:    creds,
  2220  				}
  2221  			},
  2222  			err: safemath.ErrOverflow,
  2223  		},
  2224  		{
  2225  			name: "insufficient funds",
  2226  			txFunc: func() *txs.Tx {
  2227  				input := input
  2228  				input.In = &secp256k1fx.TransferInput{
  2229  					Amt:   1,
  2230  					Input: inputSigners,
  2231  				}
  2232  
  2233  				tx := tx
  2234  				tx.Ins = []*avax.TransferableInput{
  2235  					&input,
  2236  				}
  2237  				return &txs.Tx{
  2238  					Unsigned: &tx,
  2239  					Creds:    creds,
  2240  				}
  2241  			},
  2242  			err: avax.ErrInsufficientFunds,
  2243  		},
  2244  		{
  2245  			name: "invalid credential",
  2246  			txFunc: func() *txs.Tx {
  2247  				return &txs.Tx{
  2248  					Unsigned: &tx,
  2249  					Creds: []*fxs.FxCredential{{
  2250  						Credential: (*secp256k1fx.Credential)(nil),
  2251  					}},
  2252  				}
  2253  			},
  2254  			err: secp256k1fx.ErrNilCredential,
  2255  		},
  2256  		{
  2257  			name: "wrong number of credentials",
  2258  			txFunc: func() *txs.Tx {
  2259  				return &txs.Tx{
  2260  					Unsigned: &tx,
  2261  				}
  2262  			},
  2263  			err: errWrongNumberOfCredentials,
  2264  		},
  2265  		{
  2266  			name: "barely sufficient funds",
  2267  			txFunc: func() *txs.Tx {
  2268  				input := input
  2269  				input.In = &secp256k1fx.TransferInput{
  2270  					Amt:   fxOutput.Amt + feeConfig.TxFee,
  2271  					Input: inputSigners,
  2272  				}
  2273  
  2274  				tx := tx
  2275  				tx.Ins = []*avax.TransferableInput{
  2276  					&input,
  2277  				}
  2278  				return &txs.Tx{
  2279  					Unsigned: &tx,
  2280  					Creds:    creds,
  2281  				}
  2282  			},
  2283  			err: nil,
  2284  		},
  2285  		{
  2286  			name: "barely insufficient funds",
  2287  			txFunc: func() *txs.Tx {
  2288  				input := input
  2289  				input.In = &secp256k1fx.TransferInput{
  2290  					Amt:   fxOutput.Amt + feeConfig.TxFee - 1,
  2291  					Input: inputSigners,
  2292  				}
  2293  
  2294  				tx := tx
  2295  				tx.Ins = []*avax.TransferableInput{
  2296  					&input,
  2297  				}
  2298  				return &txs.Tx{
  2299  					Unsigned: &tx,
  2300  					Creds:    creds,
  2301  				}
  2302  			},
  2303  			err: avax.ErrInsufficientFunds,
  2304  		},
  2305  	}
  2306  	for _, test := range tests {
  2307  		t.Run(test.name, func(t *testing.T) {
  2308  			tx := test.txFunc()
  2309  			verifier := &SyntacticVerifier{
  2310  				Backend: backend,
  2311  				Tx:      tx,
  2312  			}
  2313  			err := tx.Unsigned.Visit(verifier)
  2314  			require.ErrorIs(t, err, test.err)
  2315  		})
  2316  	}
  2317  }