github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/txs/transfer_subnet_ownership_tx_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 txs
     5  
     6  import (
     7  	"encoding/json"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/require"
    11  	"go.uber.org/mock/gomock"
    12  
    13  	"github.com/MetalBlockchain/metalgo/ids"
    14  	"github.com/MetalBlockchain/metalgo/snow"
    15  	"github.com/MetalBlockchain/metalgo/utils"
    16  	"github.com/MetalBlockchain/metalgo/utils/constants"
    17  	"github.com/MetalBlockchain/metalgo/utils/units"
    18  	"github.com/MetalBlockchain/metalgo/vms/components/avax"
    19  	"github.com/MetalBlockchain/metalgo/vms/components/verify"
    20  	"github.com/MetalBlockchain/metalgo/vms/platformvm/fx"
    21  	"github.com/MetalBlockchain/metalgo/vms/platformvm/stakeable"
    22  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    23  	"github.com/MetalBlockchain/metalgo/vms/types"
    24  )
    25  
    26  func TestTransferSubnetOwnershipTxSerialization(t *testing.T) {
    27  	require := require.New(t)
    28  
    29  	addr := ids.ShortID{
    30  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
    31  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
    32  		0x44, 0x55, 0x66, 0x77,
    33  	}
    34  
    35  	avaxAssetID, err := ids.FromString("FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z")
    36  	require.NoError(err)
    37  
    38  	customAssetID := ids.ID{
    39  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
    40  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
    41  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
    42  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
    43  	}
    44  
    45  	txID := ids.ID{
    46  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
    47  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
    48  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
    49  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
    50  	}
    51  	subnetID := ids.ID{
    52  		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
    53  		0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
    54  		0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
    55  		0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
    56  	}
    57  
    58  	simpleTransferSubnetOwnershipTx := &TransferSubnetOwnershipTx{
    59  		BaseTx: BaseTx{
    60  			BaseTx: avax.BaseTx{
    61  				NetworkID:    constants.MainnetID,
    62  				BlockchainID: constants.PlatformChainID,
    63  				Outs:         []*avax.TransferableOutput{},
    64  				Ins: []*avax.TransferableInput{
    65  					{
    66  						UTXOID: avax.UTXOID{
    67  							TxID:        txID,
    68  							OutputIndex: 1,
    69  						},
    70  						Asset: avax.Asset{
    71  							ID: avaxAssetID,
    72  						},
    73  						In: &secp256k1fx.TransferInput{
    74  							Amt: units.MilliAvax,
    75  							Input: secp256k1fx.Input{
    76  								SigIndices: []uint32{5},
    77  							},
    78  						},
    79  					},
    80  				},
    81  				Memo: types.JSONByteSlice{},
    82  			},
    83  		},
    84  		Subnet: subnetID,
    85  		SubnetAuth: &secp256k1fx.Input{
    86  			SigIndices: []uint32{3},
    87  		},
    88  		Owner: &secp256k1fx.OutputOwners{
    89  			Locktime:  0,
    90  			Threshold: 1,
    91  			Addrs: []ids.ShortID{
    92  				addr,
    93  			},
    94  		},
    95  	}
    96  	require.NoError(simpleTransferSubnetOwnershipTx.SyntacticVerify(&snow.Context{
    97  		NetworkID:   1,
    98  		ChainID:     constants.PlatformChainID,
    99  		AVAXAssetID: avaxAssetID,
   100  	}))
   101  
   102  	expectedUnsignedSimpleTransferSubnetOwnershipTxBytes := []byte{
   103  		// Codec version
   104  		0x00, 0x00,
   105  		// TransferSubnetOwnershipTx Type ID
   106  		0x00, 0x00, 0x00, 0x21,
   107  		// Mainnet network ID
   108  		0x00, 0x00, 0x00, 0x01,
   109  		// P-chain blockchain ID
   110  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   111  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   112  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   113  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   114  		// Number of outputs
   115  		0x00, 0x00, 0x00, 0x00,
   116  		// Number of inputs
   117  		0x00, 0x00, 0x00, 0x01,
   118  		// Inputs[0]
   119  		// TxID
   120  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   121  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   122  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   123  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   124  		// Tx output index
   125  		0x00, 0x00, 0x00, 0x01,
   126  		// Mainnet AVAX assetID
   127  		0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a,
   128  		0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78,
   129  		0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf,
   130  		0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff,
   131  		// secp256k1fx transfer input type ID
   132  		0x00, 0x00, 0x00, 0x05,
   133  		// input amount = 1 MilliAvax
   134  		0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x40,
   135  		// number of signatures needed in input
   136  		0x00, 0x00, 0x00, 0x01,
   137  		// index of signer
   138  		0x00, 0x00, 0x00, 0x05,
   139  		// length of memo
   140  		0x00, 0x00, 0x00, 0x00,
   141  		// subnetID to modify
   142  		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
   143  		0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
   144  		0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
   145  		0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
   146  		// secp256k1fx authorization type ID
   147  		0x00, 0x00, 0x00, 0x0a,
   148  		// number of signatures needed in authorization
   149  		0x00, 0x00, 0x00, 0x01,
   150  		// index of signer
   151  		0x00, 0x00, 0x00, 0x03,
   152  		// secp256k1fx output owners type ID
   153  		0x00, 0x00, 0x00, 0x0b,
   154  		// locktime
   155  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   156  		// threshold
   157  		0x00, 0x00, 0x00, 0x01,
   158  		// number of addrs
   159  		0x00, 0x00, 0x00, 0x01,
   160  		// Addrs[0]
   161  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
   162  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
   163  		0x44, 0x55, 0x66, 0x77,
   164  	}
   165  	var unsignedSimpleTransferSubnetOwnershipTx UnsignedTx = simpleTransferSubnetOwnershipTx
   166  	unsignedSimpleTransferSubnetOwnershipTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleTransferSubnetOwnershipTx)
   167  	require.NoError(err)
   168  	require.Equal(expectedUnsignedSimpleTransferSubnetOwnershipTxBytes, unsignedSimpleTransferSubnetOwnershipTxBytes)
   169  
   170  	complexTransferSubnetOwnershipTx := &TransferSubnetOwnershipTx{
   171  		BaseTx: BaseTx{
   172  			BaseTx: avax.BaseTx{
   173  				NetworkID:    constants.MainnetID,
   174  				BlockchainID: constants.PlatformChainID,
   175  				Outs: []*avax.TransferableOutput{
   176  					{
   177  						Asset: avax.Asset{
   178  							ID: avaxAssetID,
   179  						},
   180  						Out: &stakeable.LockOut{
   181  							Locktime: 87654321,
   182  							TransferableOut: &secp256k1fx.TransferOutput{
   183  								Amt: 1,
   184  								OutputOwners: secp256k1fx.OutputOwners{
   185  									Locktime:  12345678,
   186  									Threshold: 0,
   187  									Addrs:     []ids.ShortID{},
   188  								},
   189  							},
   190  						},
   191  					},
   192  					{
   193  						Asset: avax.Asset{
   194  							ID: customAssetID,
   195  						},
   196  						Out: &stakeable.LockOut{
   197  							Locktime: 876543210,
   198  							TransferableOut: &secp256k1fx.TransferOutput{
   199  								Amt: 0xffffffffffffffff,
   200  								OutputOwners: secp256k1fx.OutputOwners{
   201  									Locktime:  0,
   202  									Threshold: 1,
   203  									Addrs: []ids.ShortID{
   204  										addr,
   205  									},
   206  								},
   207  							},
   208  						},
   209  					},
   210  				},
   211  				Ins: []*avax.TransferableInput{
   212  					{
   213  						UTXOID: avax.UTXOID{
   214  							TxID:        txID,
   215  							OutputIndex: 1,
   216  						},
   217  						Asset: avax.Asset{
   218  							ID: avaxAssetID,
   219  						},
   220  						In: &secp256k1fx.TransferInput{
   221  							Amt: units.Avax,
   222  							Input: secp256k1fx.Input{
   223  								SigIndices: []uint32{2, 5},
   224  							},
   225  						},
   226  					},
   227  					{
   228  						UTXOID: avax.UTXOID{
   229  							TxID:        txID,
   230  							OutputIndex: 2,
   231  						},
   232  						Asset: avax.Asset{
   233  							ID: customAssetID,
   234  						},
   235  						In: &stakeable.LockIn{
   236  							Locktime: 876543210,
   237  							TransferableIn: &secp256k1fx.TransferInput{
   238  								Amt: 0xefffffffffffffff,
   239  								Input: secp256k1fx.Input{
   240  									SigIndices: []uint32{0},
   241  								},
   242  							},
   243  						},
   244  					},
   245  					{
   246  						UTXOID: avax.UTXOID{
   247  							TxID:        txID,
   248  							OutputIndex: 3,
   249  						},
   250  						Asset: avax.Asset{
   251  							ID: customAssetID,
   252  						},
   253  						In: &secp256k1fx.TransferInput{
   254  							Amt: 0x1000000000000000,
   255  							Input: secp256k1fx.Input{
   256  								SigIndices: []uint32{},
   257  							},
   258  						},
   259  					},
   260  				},
   261  				Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"),
   262  			},
   263  		},
   264  		Subnet: subnetID,
   265  		SubnetAuth: &secp256k1fx.Input{
   266  			SigIndices: []uint32{},
   267  		},
   268  		Owner: &secp256k1fx.OutputOwners{
   269  			Locktime:  876543210,
   270  			Threshold: 1,
   271  			Addrs: []ids.ShortID{
   272  				addr,
   273  			},
   274  		},
   275  	}
   276  	avax.SortTransferableOutputs(complexTransferSubnetOwnershipTx.Outs, Codec)
   277  	utils.Sort(complexTransferSubnetOwnershipTx.Ins)
   278  	require.NoError(complexTransferSubnetOwnershipTx.SyntacticVerify(&snow.Context{
   279  		NetworkID:   1,
   280  		ChainID:     constants.PlatformChainID,
   281  		AVAXAssetID: avaxAssetID,
   282  	}))
   283  
   284  	expectedUnsignedComplexTransferSubnetOwnershipTxBytes := []byte{
   285  		// Codec version
   286  		0x00, 0x00,
   287  		// TransferSubnetOwnershipTx Type ID
   288  		0x00, 0x00, 0x00, 0x21,
   289  		// Mainnet network ID
   290  		0x00, 0x00, 0x00, 0x01,
   291  		// P-chain blockchain ID
   292  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   293  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   294  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   295  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   296  		// Number of outputs
   297  		0x00, 0x00, 0x00, 0x02,
   298  		// Outputs[0]
   299  		// Mainnet AVAX assetID
   300  		0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a,
   301  		0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78,
   302  		0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf,
   303  		0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff,
   304  		// Stakeable locked output type ID
   305  		0x00, 0x00, 0x00, 0x16,
   306  		// Locktime
   307  		0x00, 0x00, 0x00, 0x00, 0x05, 0x39, 0x7f, 0xb1,
   308  		// secp256k1fx transfer output type ID
   309  		0x00, 0x00, 0x00, 0x07,
   310  		// amount
   311  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
   312  		// secp256k1fx output locktime
   313  		0x00, 0x00, 0x00, 0x00, 0x00, 0xbc, 0x61, 0x4e,
   314  		// threshold
   315  		0x00, 0x00, 0x00, 0x00,
   316  		// number of addresses
   317  		0x00, 0x00, 0x00, 0x00,
   318  		// Outputs[1]
   319  		// custom asset ID
   320  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   321  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   322  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   323  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   324  		// Stakeable locked output type ID
   325  		0x00, 0x00, 0x00, 0x16,
   326  		// Locktime
   327  		0x00, 0x00, 0x00, 0x00, 0x34, 0x3e, 0xfc, 0xea,
   328  		// secp256k1fx transfer output type ID
   329  		0x00, 0x00, 0x00, 0x07,
   330  		// amount
   331  		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   332  		// secp256k1fx output locktime
   333  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   334  		// threshold
   335  		0x00, 0x00, 0x00, 0x01,
   336  		// number of addresses
   337  		0x00, 0x00, 0x00, 0x01,
   338  		// address[0]
   339  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
   340  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
   341  		0x44, 0x55, 0x66, 0x77,
   342  		// number of inputs
   343  		0x00, 0x00, 0x00, 0x03,
   344  		// inputs[0]
   345  		// TxID
   346  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   347  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   348  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   349  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   350  		// Tx output index
   351  		0x00, 0x00, 0x00, 0x01,
   352  		// Mainnet AVAX assetID
   353  		0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a,
   354  		0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78,
   355  		0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf,
   356  		0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff,
   357  		// secp256k1fx transfer input type ID
   358  		0x00, 0x00, 0x00, 0x05,
   359  		// input amount = 1 Avax
   360  		0x00, 0x00, 0x00, 0x00, 0x3b, 0x9a, 0xca, 0x00,
   361  		// number of signatures needed in input
   362  		0x00, 0x00, 0x00, 0x02,
   363  		// index of first signer
   364  		0x00, 0x00, 0x00, 0x02,
   365  		// index of second signer
   366  		0x00, 0x00, 0x00, 0x05,
   367  		// inputs[1]
   368  		// TxID
   369  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   370  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   371  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   372  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   373  		// Tx output index
   374  		0x00, 0x00, 0x00, 0x02,
   375  		// Custom asset ID
   376  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   377  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   378  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   379  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   380  		// Stakeable locked input type ID
   381  		0x00, 0x00, 0x00, 0x15,
   382  		// Locktime
   383  		0x00, 0x00, 0x00, 0x00, 0x34, 0x3e, 0xfc, 0xea,
   384  		// secp256k1fx transfer input type ID
   385  		0x00, 0x00, 0x00, 0x05,
   386  		// input amount
   387  		0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   388  		// number of signatures needed in input
   389  		0x00, 0x00, 0x00, 0x01,
   390  		// index of signer
   391  		0x00, 0x00, 0x00, 0x00,
   392  		// inputs[2]
   393  		// TxID
   394  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   395  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   396  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   397  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   398  		// Tx output index
   399  		0x00, 0x00, 0x00, 0x03,
   400  		// custom asset ID
   401  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   402  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   403  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   404  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   405  		// secp256k1fx transfer input type ID
   406  		0x00, 0x00, 0x00, 0x05,
   407  		// input amount
   408  		0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   409  		// number of signatures needed in input
   410  		0x00, 0x00, 0x00, 0x00,
   411  		// length of memo
   412  		0x00, 0x00, 0x00, 0x14,
   413  		// memo
   414  		0xf0, 0x9f, 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c,
   415  		0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73,
   416  		0x01, 0x23, 0x45, 0x21,
   417  		// subnetID to modify
   418  		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
   419  		0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
   420  		0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
   421  		0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
   422  		// secp256k1fx authorization type ID
   423  		0x00, 0x00, 0x00, 0x0a,
   424  		// number of signatures needed in authorization
   425  		0x00, 0x00, 0x00, 0x00,
   426  		// secp256k1fx output owners type ID
   427  		0x00, 0x00, 0x00, 0x0b,
   428  		// locktime
   429  		0x00, 0x00, 0x00, 0x00, 0x34, 0x3e, 0xfc, 0xea,
   430  		// threshold
   431  		0x00, 0x00, 0x00, 0x01,
   432  		// number of addrs
   433  		0x00, 0x00, 0x00, 0x01,
   434  		// Addrs[0]
   435  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
   436  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
   437  		0x44, 0x55, 0x66, 0x77,
   438  	}
   439  	var unsignedComplexTransferSubnetOwnershipTx UnsignedTx = complexTransferSubnetOwnershipTx
   440  	unsignedComplexTransferSubnetOwnershipTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexTransferSubnetOwnershipTx)
   441  	require.NoError(err)
   442  	require.Equal(expectedUnsignedComplexTransferSubnetOwnershipTxBytes, unsignedComplexTransferSubnetOwnershipTxBytes)
   443  
   444  	aliaser := ids.NewAliaser()
   445  	require.NoError(aliaser.Alias(constants.PlatformChainID, "P"))
   446  
   447  	unsignedComplexTransferSubnetOwnershipTx.InitCtx(&snow.Context{
   448  		NetworkID:   1,
   449  		ChainID:     constants.PlatformChainID,
   450  		AVAXAssetID: avaxAssetID,
   451  		BCLookup:    aliaser,
   452  	})
   453  
   454  	unsignedComplexTransferSubnetOwnershipTxJSONBytes, err := json.MarshalIndent(unsignedComplexTransferSubnetOwnershipTx, "", "\t")
   455  	require.NoError(err)
   456  	require.Equal(`{
   457  	"networkID": 1,
   458  	"blockchainID": "11111111111111111111111111111111LpoYY",
   459  	"outputs": [
   460  		{
   461  			"assetID": "FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z",
   462  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   463  			"output": {
   464  				"locktime": 87654321,
   465  				"output": {
   466  					"addresses": [],
   467  					"amount": 1,
   468  					"locktime": 12345678,
   469  					"threshold": 0
   470  				}
   471  			}
   472  		},
   473  		{
   474  			"assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc",
   475  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   476  			"output": {
   477  				"locktime": 876543210,
   478  				"output": {
   479  					"addresses": [
   480  						"P-metal1g32kvaugnx4tk3z4vemc3xd2hdz92enhqaj6ex"
   481  					],
   482  					"amount": 18446744073709551615,
   483  					"locktime": 0,
   484  					"threshold": 1
   485  				}
   486  			}
   487  		}
   488  	],
   489  	"inputs": [
   490  		{
   491  			"txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN",
   492  			"outputIndex": 1,
   493  			"assetID": "FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z",
   494  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   495  			"input": {
   496  				"amount": 1000000000,
   497  				"signatureIndices": [
   498  					2,
   499  					5
   500  				]
   501  			}
   502  		},
   503  		{
   504  			"txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN",
   505  			"outputIndex": 2,
   506  			"assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc",
   507  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   508  			"input": {
   509  				"locktime": 876543210,
   510  				"input": {
   511  					"amount": 17293822569102704639,
   512  					"signatureIndices": [
   513  						0
   514  					]
   515  				}
   516  			}
   517  		},
   518  		{
   519  			"txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN",
   520  			"outputIndex": 3,
   521  			"assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc",
   522  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   523  			"input": {
   524  				"amount": 1152921504606846976,
   525  				"signatureIndices": []
   526  			}
   527  		}
   528  	],
   529  	"memo": "0xf09f98850a77656c6c2074686174277301234521",
   530  	"subnetID": "SkB92YpWm4UpburLz9tEKZw2i67H3FF6YkjaU4BkFUDTG9Xm",
   531  	"subnetAuthorization": {
   532  		"signatureIndices": []
   533  	},
   534  	"newOwner": {
   535  		"addresses": [
   536  			"P-metal1g32kvaugnx4tk3z4vemc3xd2hdz92enhqaj6ex"
   537  		],
   538  		"locktime": 876543210,
   539  		"threshold": 1
   540  	}
   541  }`, string(unsignedComplexTransferSubnetOwnershipTxJSONBytes))
   542  }
   543  
   544  func TestTransferSubnetOwnershipTxSyntacticVerify(t *testing.T) {
   545  	type test struct {
   546  		name        string
   547  		txFunc      func(*gomock.Controller) *TransferSubnetOwnershipTx
   548  		expectedErr error
   549  	}
   550  
   551  	var (
   552  		networkID = uint32(1337)
   553  		chainID   = ids.GenerateTestID()
   554  	)
   555  
   556  	ctx := &snow.Context{
   557  		ChainID:   chainID,
   558  		NetworkID: networkID,
   559  	}
   560  
   561  	// A BaseTx that already passed syntactic verification.
   562  	verifiedBaseTx := BaseTx{
   563  		SyntacticallyVerified: true,
   564  	}
   565  	// Sanity check.
   566  	require.NoError(t, verifiedBaseTx.SyntacticVerify(ctx))
   567  
   568  	// A BaseTx that passes syntactic verification.
   569  	validBaseTx := BaseTx{
   570  		BaseTx: avax.BaseTx{
   571  			NetworkID:    networkID,
   572  			BlockchainID: chainID,
   573  		},
   574  	}
   575  	// Sanity check.
   576  	require.NoError(t, validBaseTx.SyntacticVerify(ctx))
   577  	// Make sure we're not caching the verification result.
   578  	require.False(t, validBaseTx.SyntacticallyVerified)
   579  
   580  	// A BaseTx that fails syntactic verification.
   581  	invalidBaseTx := BaseTx{}
   582  
   583  	tests := []test{
   584  		{
   585  			name: "nil tx",
   586  			txFunc: func(*gomock.Controller) *TransferSubnetOwnershipTx {
   587  				return nil
   588  			},
   589  			expectedErr: ErrNilTx,
   590  		},
   591  		{
   592  			name: "already verified",
   593  			txFunc: func(*gomock.Controller) *TransferSubnetOwnershipTx {
   594  				return &TransferSubnetOwnershipTx{BaseTx: verifiedBaseTx}
   595  			},
   596  			expectedErr: nil,
   597  		},
   598  		{
   599  			name: "invalid BaseTx",
   600  			txFunc: func(*gomock.Controller) *TransferSubnetOwnershipTx {
   601  				return &TransferSubnetOwnershipTx{
   602  					// Set subnetID so we don't error on that check.
   603  					Subnet: ids.GenerateTestID(),
   604  					BaseTx: invalidBaseTx,
   605  				}
   606  			},
   607  			expectedErr: avax.ErrWrongNetworkID,
   608  		},
   609  		{
   610  			name: "invalid subnetID",
   611  			txFunc: func(*gomock.Controller) *TransferSubnetOwnershipTx {
   612  				return &TransferSubnetOwnershipTx{
   613  					BaseTx: validBaseTx,
   614  					Subnet: constants.PrimaryNetworkID,
   615  				}
   616  			},
   617  			expectedErr: ErrTransferPermissionlessSubnet,
   618  		},
   619  		{
   620  			name: "invalid subnetAuth",
   621  			txFunc: func(ctrl *gomock.Controller) *TransferSubnetOwnershipTx {
   622  				// This SubnetAuth fails verification.
   623  				invalidSubnetAuth := verify.NewMockVerifiable(ctrl)
   624  				invalidSubnetAuth.EXPECT().Verify().Return(errInvalidSubnetAuth)
   625  				return &TransferSubnetOwnershipTx{
   626  					// Set subnetID so we don't error on that check.
   627  					Subnet:     ids.GenerateTestID(),
   628  					BaseTx:     validBaseTx,
   629  					SubnetAuth: invalidSubnetAuth,
   630  				}
   631  			},
   632  			expectedErr: errInvalidSubnetAuth,
   633  		},
   634  		{
   635  			name: "passes verification",
   636  			txFunc: func(ctrl *gomock.Controller) *TransferSubnetOwnershipTx {
   637  				// This SubnetAuth passes verification.
   638  				validSubnetAuth := verify.NewMockVerifiable(ctrl)
   639  				validSubnetAuth.EXPECT().Verify().Return(nil)
   640  				mockOwner := fx.NewMockOwner(ctrl)
   641  				mockOwner.EXPECT().Verify().Return(nil)
   642  				return &TransferSubnetOwnershipTx{
   643  					// Set subnetID so we don't error on that check.
   644  					Subnet:     ids.GenerateTestID(),
   645  					BaseTx:     validBaseTx,
   646  					SubnetAuth: validSubnetAuth,
   647  					Owner:      mockOwner,
   648  				}
   649  			},
   650  			expectedErr: nil,
   651  		},
   652  	}
   653  
   654  	for _, tt := range tests {
   655  		t.Run(tt.name, func(t *testing.T) {
   656  			require := require.New(t)
   657  			ctrl := gomock.NewController(t)
   658  
   659  			tx := tt.txFunc(ctrl)
   660  			err := tx.SyntacticVerify(ctx)
   661  			require.ErrorIs(err, tt.expectedErr)
   662  			if tt.expectedErr != nil {
   663  				return
   664  			}
   665  			require.True(tx.SyntacticallyVerified)
   666  		})
   667  	}
   668  }