github.com/MetalBlockchain/metalgo@v1.11.9/wallet/chain/x/builder_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 x
     5  
     6  import (
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/MetalBlockchain/metalgo/ids"
    12  	"github.com/MetalBlockchain/metalgo/utils/constants"
    13  	"github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1"
    14  	"github.com/MetalBlockchain/metalgo/utils/set"
    15  	"github.com/MetalBlockchain/metalgo/utils/units"
    16  	"github.com/MetalBlockchain/metalgo/vms/components/avax"
    17  	"github.com/MetalBlockchain/metalgo/vms/components/verify"
    18  	"github.com/MetalBlockchain/metalgo/vms/nftfx"
    19  	"github.com/MetalBlockchain/metalgo/vms/propertyfx"
    20  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    21  	"github.com/MetalBlockchain/metalgo/wallet/chain/x/builder"
    22  	"github.com/MetalBlockchain/metalgo/wallet/subnet/primary/common"
    23  )
    24  
    25  var (
    26  	testKeys = secp256k1.TestKeys()
    27  
    28  	// We hard-code [avaxAssetID] and [subnetAssetID] to make
    29  	// ordering of UTXOs generated by [testUTXOsList] is reproducible
    30  	avaxAssetID     = ids.Empty.Prefix(1789)
    31  	xChainID        = ids.Empty.Prefix(2021)
    32  	nftAssetID      = ids.Empty.Prefix(2022)
    33  	propertyAssetID = ids.Empty.Prefix(2023)
    34  
    35  	testContext = &builder.Context{
    36  		NetworkID:        constants.UnitTestID,
    37  		BlockchainID:     xChainID,
    38  		AVAXAssetID:      avaxAssetID,
    39  		BaseTxFee:        units.MicroAvax,
    40  		CreateAssetTxFee: 99 * units.MilliAvax,
    41  	}
    42  )
    43  
    44  // These tests create and sign a tx, then verify that utxos included
    45  // in the tx are exactly necessary to pay fees for it
    46  
    47  func TestBaseTx(t *testing.T) {
    48  	var (
    49  		require = require.New(t)
    50  
    51  		// backend
    52  		utxosKey       = testKeys[1]
    53  		utxos          = makeTestUTXOs(utxosKey)
    54  		genericBackend = common.NewDeterministicChainUTXOs(
    55  			require,
    56  			map[ids.ID][]*avax.UTXO{
    57  				xChainID: utxos,
    58  			},
    59  		)
    60  		backend = NewBackend(testContext, genericBackend)
    61  
    62  		// builder
    63  		utxoAddr = utxosKey.Address()
    64  		builder  = builder.New(set.Of(utxoAddr), testContext, backend)
    65  
    66  		// data to build the transaction
    67  		outputsToMove = []*avax.TransferableOutput{{
    68  			Asset: avax.Asset{ID: avaxAssetID},
    69  			Out: &secp256k1fx.TransferOutput{
    70  				Amt: 7 * units.Avax,
    71  				OutputOwners: secp256k1fx.OutputOwners{
    72  					Threshold: 1,
    73  					Addrs:     []ids.ShortID{utxoAddr},
    74  				},
    75  			},
    76  		}}
    77  	)
    78  
    79  	utx, err := builder.NewBaseTx(
    80  		outputsToMove,
    81  	)
    82  	require.NoError(err)
    83  
    84  	// check UTXOs selection and fee financing
    85  	ins := utx.Ins
    86  	outs := utx.Outs
    87  	require.Len(ins, 2)
    88  	require.Len(outs, 2)
    89  
    90  	expectedConsumed := testContext.BaseTxFee
    91  	consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount() - outs[1].Out.Amount()
    92  	require.Equal(expectedConsumed, consumed)
    93  	require.Equal(outputsToMove[0], outs[1])
    94  }
    95  
    96  func TestCreateAssetTx(t *testing.T) {
    97  	require := require.New(t)
    98  
    99  	var (
   100  		// backend
   101  		utxosKey       = testKeys[1]
   102  		utxos          = makeTestUTXOs(utxosKey)
   103  		genericBackend = common.NewDeterministicChainUTXOs(
   104  			require,
   105  			map[ids.ID][]*avax.UTXO{
   106  				xChainID: utxos,
   107  			},
   108  		)
   109  		backend = NewBackend(testContext, genericBackend)
   110  
   111  		// builder
   112  		utxoAddr = utxosKey.Address()
   113  		builder  = builder.New(set.Of(utxoAddr), testContext, backend)
   114  
   115  		// data to build the transaction
   116  		assetName          = "Team Rocket"
   117  		symbol             = "TR"
   118  		denomination uint8 = 0
   119  		initialState       = map[uint32][]verify.State{
   120  			0: {
   121  				&secp256k1fx.MintOutput{
   122  					OutputOwners: secp256k1fx.OutputOwners{
   123  						Threshold: 1,
   124  						Addrs:     []ids.ShortID{testKeys[0].PublicKey().Address()},
   125  					},
   126  				}, &secp256k1fx.MintOutput{
   127  					OutputOwners: secp256k1fx.OutputOwners{
   128  						Threshold: 1,
   129  						Addrs:     []ids.ShortID{testKeys[0].PublicKey().Address()},
   130  					},
   131  				},
   132  			},
   133  			1: {
   134  				&nftfx.MintOutput{
   135  					GroupID: 1,
   136  					OutputOwners: secp256k1fx.OutputOwners{
   137  						Threshold: 1,
   138  						Addrs:     []ids.ShortID{testKeys[1].PublicKey().Address()},
   139  					},
   140  				},
   141  				&nftfx.MintOutput{
   142  					GroupID: 2,
   143  					OutputOwners: secp256k1fx.OutputOwners{
   144  						Threshold: 1,
   145  						Addrs:     []ids.ShortID{testKeys[1].PublicKey().Address()},
   146  					},
   147  				},
   148  			},
   149  			2: {
   150  				&propertyfx.MintOutput{
   151  					OutputOwners: secp256k1fx.OutputOwners{
   152  						Threshold: 1,
   153  						Addrs:     []ids.ShortID{testKeys[2].PublicKey().Address()},
   154  					},
   155  				},
   156  				&propertyfx.MintOutput{
   157  					OutputOwners: secp256k1fx.OutputOwners{
   158  						Threshold: 1,
   159  						Addrs:     []ids.ShortID{testKeys[2].PublicKey().Address()},
   160  					},
   161  				},
   162  			},
   163  		}
   164  	)
   165  
   166  	utx, err := builder.NewCreateAssetTx(
   167  		assetName,
   168  		symbol,
   169  		denomination,
   170  		initialState,
   171  	)
   172  	require.NoError(err)
   173  
   174  	// check UTXOs selection and fee financing
   175  	ins := utx.Ins
   176  	outs := utx.Outs
   177  	require.Len(ins, 2)
   178  	require.Len(outs, 1)
   179  
   180  	expectedConsumed := testContext.CreateAssetTxFee
   181  	consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount()
   182  	require.Equal(expectedConsumed, consumed)
   183  }
   184  
   185  func TestMintNFTOperation(t *testing.T) {
   186  	require := require.New(t)
   187  
   188  	var (
   189  		// backend
   190  		utxosKey       = testKeys[1]
   191  		utxos          = makeTestUTXOs(utxosKey)
   192  		genericBackend = common.NewDeterministicChainUTXOs(
   193  			require,
   194  			map[ids.ID][]*avax.UTXO{
   195  				xChainID: utxos,
   196  			},
   197  		)
   198  		backend = NewBackend(testContext, genericBackend)
   199  
   200  		// builder
   201  		utxoAddr = utxosKey.Address()
   202  		builder  = builder.New(set.Of(utxoAddr), testContext, backend)
   203  
   204  		// data to build the transaction
   205  		payload  = []byte{'h', 'e', 'l', 'l', 'o'}
   206  		NFTOwner = &secp256k1fx.OutputOwners{
   207  			Threshold: 1,
   208  			Addrs:     []ids.ShortID{utxoAddr},
   209  		}
   210  	)
   211  
   212  	utx, err := builder.NewOperationTxMintNFT(
   213  		nftAssetID,
   214  		payload,
   215  		[]*secp256k1fx.OutputOwners{NFTOwner},
   216  	)
   217  	require.NoError(err)
   218  
   219  	// check UTXOs selection and fee financing
   220  	ins := utx.Ins
   221  	outs := utx.Outs
   222  	require.Len(ins, 1)
   223  	require.Len(outs, 1)
   224  
   225  	expectedConsumed := testContext.BaseTxFee
   226  	consumed := ins[0].In.Amount() - outs[0].Out.Amount()
   227  	require.Equal(expectedConsumed, consumed)
   228  }
   229  
   230  func TestMintFTOperation(t *testing.T) {
   231  	require := require.New(t)
   232  
   233  	var (
   234  		// backend
   235  		utxosKey       = testKeys[1]
   236  		utxos          = makeTestUTXOs(utxosKey)
   237  		genericBackend = common.NewDeterministicChainUTXOs(
   238  			require,
   239  			map[ids.ID][]*avax.UTXO{
   240  				xChainID: utxos,
   241  			},
   242  		)
   243  		backend = NewBackend(testContext, genericBackend)
   244  
   245  		// builder
   246  		utxoAddr = utxosKey.Address()
   247  		builder  = builder.New(set.Of(utxoAddr), testContext, backend)
   248  
   249  		// data to build the transaction
   250  		outputs = map[ids.ID]*secp256k1fx.TransferOutput{
   251  			nftAssetID: {
   252  				Amt: 1,
   253  				OutputOwners: secp256k1fx.OutputOwners{
   254  					Threshold: 1,
   255  					Addrs:     []ids.ShortID{utxoAddr},
   256  				},
   257  			},
   258  		}
   259  	)
   260  
   261  	utx, err := builder.NewOperationTxMintFT(
   262  		outputs,
   263  	)
   264  	require.NoError(err)
   265  
   266  	// check UTXOs selection and fee financing
   267  	ins := utx.Ins
   268  	outs := utx.Outs
   269  	require.Len(ins, 1)
   270  	require.Len(outs, 1)
   271  
   272  	expectedConsumed := testContext.BaseTxFee
   273  	consumed := ins[0].In.Amount() - outs[0].Out.Amount()
   274  	require.Equal(expectedConsumed, consumed)
   275  }
   276  
   277  func TestMintPropertyOperation(t *testing.T) {
   278  	require := require.New(t)
   279  
   280  	var (
   281  		// backend
   282  		utxosKey       = testKeys[1]
   283  		utxos          = makeTestUTXOs(utxosKey)
   284  		genericBackend = common.NewDeterministicChainUTXOs(
   285  			require,
   286  			map[ids.ID][]*avax.UTXO{
   287  				xChainID: utxos,
   288  			},
   289  		)
   290  		backend = NewBackend(testContext, genericBackend)
   291  
   292  		// builder
   293  		utxoAddr = utxosKey.Address()
   294  		builder  = builder.New(set.Of(utxoAddr), testContext, backend)
   295  
   296  		// data to build the transaction
   297  		propertyOwner = &secp256k1fx.OutputOwners{
   298  			Threshold: 1,
   299  			Addrs:     []ids.ShortID{utxoAddr},
   300  		}
   301  	)
   302  
   303  	utx, err := builder.NewOperationTxMintProperty(
   304  		propertyAssetID,
   305  		propertyOwner,
   306  	)
   307  	require.NoError(err)
   308  
   309  	// check UTXOs selection and fee financing
   310  	ins := utx.Ins
   311  	outs := utx.Outs
   312  	require.Len(ins, 1)
   313  	require.Len(outs, 1)
   314  
   315  	expectedConsumed := testContext.BaseTxFee
   316  	consumed := ins[0].In.Amount() - outs[0].Out.Amount()
   317  	require.Equal(expectedConsumed, consumed)
   318  }
   319  
   320  func TestBurnPropertyOperation(t *testing.T) {
   321  	require := require.New(t)
   322  
   323  	var (
   324  		// backend
   325  		utxosKey       = testKeys[1]
   326  		utxos          = makeTestUTXOs(utxosKey)
   327  		genericBackend = common.NewDeterministicChainUTXOs(
   328  			require,
   329  			map[ids.ID][]*avax.UTXO{
   330  				xChainID: utxos,
   331  			},
   332  		)
   333  		backend = NewBackend(testContext, genericBackend)
   334  
   335  		// builder
   336  		utxoAddr = utxosKey.Address()
   337  		builder  = builder.New(set.Of(utxoAddr), testContext, backend)
   338  	)
   339  
   340  	utx, err := builder.NewOperationTxBurnProperty(
   341  		propertyAssetID,
   342  	)
   343  	require.NoError(err)
   344  
   345  	// check UTXOs selection and fee financing
   346  	ins := utx.Ins
   347  	outs := utx.Outs
   348  	require.Len(ins, 1)
   349  	require.Len(outs, 1)
   350  
   351  	expectedConsumed := testContext.BaseTxFee
   352  	consumed := ins[0].In.Amount() - outs[0].Out.Amount()
   353  	require.Equal(expectedConsumed, consumed)
   354  }
   355  
   356  func TestImportTx(t *testing.T) {
   357  	var (
   358  		require = require.New(t)
   359  
   360  		// backend
   361  		utxosKey       = testKeys[1]
   362  		utxos          = makeTestUTXOs(utxosKey)
   363  		sourceChainID  = ids.GenerateTestID()
   364  		importedUTXOs  = utxos[:1]
   365  		genericBackend = common.NewDeterministicChainUTXOs(
   366  			require,
   367  			map[ids.ID][]*avax.UTXO{
   368  				xChainID:      utxos,
   369  				sourceChainID: importedUTXOs,
   370  			},
   371  		)
   372  
   373  		backend = NewBackend(testContext, genericBackend)
   374  
   375  		// builder
   376  		utxoAddr = utxosKey.Address()
   377  		builder  = builder.New(set.Of(utxoAddr), testContext, backend)
   378  
   379  		// data to build the transaction
   380  		importKey = testKeys[0]
   381  		importTo  = &secp256k1fx.OutputOwners{
   382  			Threshold: 1,
   383  			Addrs: []ids.ShortID{
   384  				importKey.Address(),
   385  			},
   386  		}
   387  	)
   388  
   389  	utx, err := builder.NewImportTx(
   390  		sourceChainID,
   391  		importTo,
   392  	)
   393  	require.NoError(err)
   394  
   395  	// check UTXOs selection and fee financing
   396  	ins := utx.Ins
   397  	outs := utx.Outs
   398  	importedIns := utx.ImportedIns
   399  	require.Empty(ins)
   400  	require.Len(importedIns, 1)
   401  	require.Len(outs, 1)
   402  
   403  	expectedConsumed := testContext.BaseTxFee
   404  	consumed := importedIns[0].In.Amount() - outs[0].Out.Amount()
   405  	require.Equal(expectedConsumed, consumed)
   406  }
   407  
   408  func TestExportTx(t *testing.T) {
   409  	var (
   410  		require = require.New(t)
   411  
   412  		// backend
   413  		utxosKey       = testKeys[1]
   414  		utxos          = makeTestUTXOs(utxosKey)
   415  		genericBackend = common.NewDeterministicChainUTXOs(
   416  			require,
   417  			map[ids.ID][]*avax.UTXO{
   418  				xChainID: utxos,
   419  			},
   420  		)
   421  		backend = NewBackend(testContext, genericBackend)
   422  
   423  		// builder
   424  		utxoAddr = utxosKey.Address()
   425  		builder  = builder.New(set.Of(utxoAddr), testContext, backend)
   426  
   427  		// data to build the transaction
   428  		subnetID        = ids.GenerateTestID()
   429  		exportedOutputs = []*avax.TransferableOutput{{
   430  			Asset: avax.Asset{ID: avaxAssetID},
   431  			Out: &secp256k1fx.TransferOutput{
   432  				Amt: 7 * units.Avax,
   433  				OutputOwners: secp256k1fx.OutputOwners{
   434  					Threshold: 1,
   435  					Addrs:     []ids.ShortID{utxoAddr},
   436  				},
   437  			},
   438  		}}
   439  	)
   440  
   441  	utx, err := builder.NewExportTx(
   442  		subnetID,
   443  		exportedOutputs,
   444  	)
   445  	require.NoError(err)
   446  
   447  	// check UTXOs selection and fee financing
   448  	ins := utx.Ins
   449  	outs := utx.Outs
   450  	require.Len(ins, 2)
   451  	require.Len(outs, 1)
   452  
   453  	expectedConsumed := testContext.BaseTxFee + exportedOutputs[0].Out.Amount()
   454  	consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount()
   455  	require.Equal(expectedConsumed, consumed)
   456  	require.Equal(utx.ExportedOuts, exportedOutputs)
   457  }
   458  
   459  func makeTestUTXOs(utxosKey *secp256k1.PrivateKey) []*avax.UTXO {
   460  	// Note: we avoid ids.GenerateTestNodeID here to make sure that UTXO IDs won't change
   461  	// run by run. This simplifies checking what utxos are included in the built txs.
   462  	const utxosOffset uint64 = 2024
   463  
   464  	return []*avax.UTXO{ // currently, the wallet scans UTXOs in the order provided here
   465  		{ // a small UTXO first, which should not be enough to pay fees
   466  			UTXOID: avax.UTXOID{
   467  				TxID:        ids.Empty.Prefix(utxosOffset),
   468  				OutputIndex: uint32(utxosOffset),
   469  			},
   470  			Asset: avax.Asset{ID: avaxAssetID},
   471  			Out: &secp256k1fx.TransferOutput{
   472  				Amt: 2 * units.MilliAvax,
   473  				OutputOwners: secp256k1fx.OutputOwners{
   474  					Locktime:  0,
   475  					Addrs:     []ids.ShortID{utxosKey.PublicKey().Address()},
   476  					Threshold: 1,
   477  				},
   478  			},
   479  		},
   480  		{
   481  			UTXOID: avax.UTXOID{
   482  				TxID:        ids.Empty.Prefix(utxosOffset + 2),
   483  				OutputIndex: uint32(utxosOffset + 2),
   484  			},
   485  			Asset: avax.Asset{ID: nftAssetID},
   486  			Out: &nftfx.MintOutput{
   487  				GroupID: 1,
   488  				OutputOwners: secp256k1fx.OutputOwners{
   489  					Threshold: 1,
   490  					Addrs:     []ids.ShortID{utxosKey.PublicKey().Address()},
   491  				},
   492  			},
   493  		},
   494  		{
   495  			UTXOID: avax.UTXOID{
   496  				TxID:        ids.Empty.Prefix(utxosOffset + 3),
   497  				OutputIndex: uint32(utxosOffset + 3),
   498  			},
   499  			Asset: avax.Asset{ID: nftAssetID},
   500  			Out: &secp256k1fx.MintOutput{
   501  				OutputOwners: secp256k1fx.OutputOwners{
   502  					Threshold: 1,
   503  					Addrs:     []ids.ShortID{utxosKey.PublicKey().Address()},
   504  				},
   505  			},
   506  		},
   507  		{
   508  			UTXOID: avax.UTXOID{
   509  				TxID:        ids.Empty.Prefix(utxosOffset + 4),
   510  				OutputIndex: uint32(utxosOffset + 4),
   511  			},
   512  			Asset: avax.Asset{ID: propertyAssetID},
   513  			Out: &propertyfx.MintOutput{
   514  				OutputOwners: secp256k1fx.OutputOwners{
   515  					Locktime:  0,
   516  					Addrs:     []ids.ShortID{utxosKey.PublicKey().Address()},
   517  					Threshold: 1,
   518  				},
   519  			},
   520  		},
   521  		{
   522  			UTXOID: avax.UTXOID{
   523  				TxID:        ids.Empty.Prefix(utxosOffset + 5),
   524  				OutputIndex: uint32(utxosOffset + 5),
   525  			},
   526  			Asset: avax.Asset{ID: propertyAssetID},
   527  			Out: &propertyfx.OwnedOutput{
   528  				OutputOwners: secp256k1fx.OutputOwners{
   529  					Locktime:  0,
   530  					Addrs:     []ids.ShortID{utxosKey.PublicKey().Address()},
   531  					Threshold: 1,
   532  				},
   533  			},
   534  		},
   535  		{ // a large UTXO last, which should be enough to pay any fee by itself
   536  			UTXOID: avax.UTXOID{
   537  				TxID:        ids.Empty.Prefix(utxosOffset + 6),
   538  				OutputIndex: uint32(utxosOffset + 6),
   539  			},
   540  			Asset: avax.Asset{ID: avaxAssetID},
   541  			Out: &secp256k1fx.TransferOutput{
   542  				Amt: 9 * units.Avax,
   543  				OutputOwners: secp256k1fx.OutputOwners{
   544  					Locktime:  0,
   545  					Addrs:     []ids.ShortID{utxosKey.PublicKey().Address()},
   546  					Threshold: 1,
   547  				},
   548  			},
   549  		},
   550  	}
   551  }