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