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