gitlab.com/flarenetwork/coreth@v0.1.1/plugin/evm/export_tx_test.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package evm
     5  
     6  import (
     7  	"math/big"
     8  	"testing"
     9  
    10  	"gitlab.com/flarenetwork/coreth/params"
    11  
    12  	"github.com/ava-labs/avalanchego/chains/atomic"
    13  	"github.com/ava-labs/avalanchego/ids"
    14  	"github.com/ava-labs/avalanchego/utils/crypto"
    15  	"github.com/ava-labs/avalanchego/utils/units"
    16  	"github.com/ava-labs/avalanchego/vms/components/avax"
    17  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    18  )
    19  
    20  func TestExportTxVerifyNil(t *testing.T) {
    21  	var exportTx *UnsignedExportTx
    22  	if err := exportTx.Verify(testXChainID, NewContext(), apricotRulesPhase0); err == nil {
    23  		t.Fatal("Verify should have failed due to nil transaction")
    24  	}
    25  }
    26  
    27  func TestExportTxVerify(t *testing.T) {
    28  	var exportAmount uint64 = 10000000
    29  	exportTx := &UnsignedExportTx{
    30  		NetworkID:        testNetworkID,
    31  		BlockchainID:     testCChainID,
    32  		DestinationChain: testXChainID,
    33  		Ins: []EVMInput{
    34  			{
    35  				Address: testEthAddrs[0],
    36  				Amount:  exportAmount,
    37  				AssetID: testAvaxAssetID,
    38  				Nonce:   0,
    39  			},
    40  			{
    41  				Address: testEthAddrs[2],
    42  				Amount:  exportAmount,
    43  				AssetID: testAvaxAssetID,
    44  				Nonce:   0,
    45  			},
    46  		},
    47  		ExportedOutputs: []*avax.TransferableOutput{
    48  			{
    49  				Asset: avax.Asset{ID: testAvaxAssetID},
    50  				Out: &secp256k1fx.TransferOutput{
    51  					Amt: exportAmount,
    52  					OutputOwners: secp256k1fx.OutputOwners{
    53  						Locktime:  0,
    54  						Threshold: 1,
    55  						Addrs:     []ids.ShortID{testShortIDAddrs[0]},
    56  					},
    57  				},
    58  			},
    59  			{
    60  				Asset: avax.Asset{ID: testAvaxAssetID},
    61  				Out: &secp256k1fx.TransferOutput{
    62  					Amt: exportAmount,
    63  					OutputOwners: secp256k1fx.OutputOwners{
    64  						Locktime:  0,
    65  						Threshold: 1,
    66  						Addrs:     []ids.ShortID{testShortIDAddrs[1]},
    67  					},
    68  				},
    69  			},
    70  		},
    71  	}
    72  
    73  	// Sort the inputs and outputs to ensure the transaction is canonical
    74  	avax.SortTransferableOutputs(exportTx.ExportedOutputs, Codec)
    75  	// Pass in a list of signers here with the appropriate length
    76  	// to avoid causing a nil-pointer error in the helper method
    77  	emptySigners := make([][]*crypto.PrivateKeySECP256K1R, 2)
    78  	SortEVMInputsAndSigners(exportTx.Ins, emptySigners)
    79  
    80  	ctx := NewContext()
    81  
    82  	// Test Valid Export Tx
    83  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase1); err != nil {
    84  		t.Fatalf("Failed to verify valid ExportTx: %s", err)
    85  	}
    86  
    87  	exportTx.NetworkID = testNetworkID + 1
    88  
    89  	// Test Incorrect Network ID Errors
    90  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase1); err == nil {
    91  		t.Fatal("ExportTx should have failed verification due to incorrect network ID")
    92  	}
    93  
    94  	exportTx.NetworkID = testNetworkID
    95  	exportTx.BlockchainID = nonExistentID
    96  	// Test Incorrect Blockchain ID Errors
    97  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase1); err == nil {
    98  		t.Fatal("ExportTx should have failed verification due to incorrect blockchain ID")
    99  	}
   100  
   101  	exportTx.BlockchainID = testCChainID
   102  	exportTx.DestinationChain = nonExistentID
   103  	// Test Incorrect Destination Chain ID Errors
   104  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase1); err == nil {
   105  		t.Fatal("ExportTx should have failed verification due to incorrect destination chain")
   106  	}
   107  
   108  	exportTx.DestinationChain = testXChainID
   109  	exportedOuts := exportTx.ExportedOutputs
   110  	exportTx.ExportedOutputs = nil
   111  	evmInputs := exportTx.Ins
   112  	// Test No Exported Outputs Errors
   113  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase1); err == nil {
   114  		t.Fatal("ExportTx should have failed verification due to no exported outputs")
   115  	}
   116  
   117  	exportTx.ExportedOutputs = []*avax.TransferableOutput{exportedOuts[1], exportedOuts[0]}
   118  	// Test Unsorted outputs Errors
   119  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase1); err == nil {
   120  		t.Fatal("ExportTx should have failed verification due to no unsorted exported outputs")
   121  	}
   122  
   123  	exportTx.ExportedOutputs = []*avax.TransferableOutput{exportedOuts[0], nil}
   124  	// Test invalid exported output
   125  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase1); err == nil {
   126  		t.Fatal("ExportTx should have failed verification due to invalid output")
   127  	}
   128  
   129  	exportTx.ExportedOutputs = []*avax.TransferableOutput{exportedOuts[0], exportedOuts[1]}
   130  	exportTx.Ins = []EVMInput{evmInputs[1], evmInputs[0]}
   131  	// Test unsorted EVM Inputs passes before AP1
   132  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase0); err != nil {
   133  		t.Fatalf("ExportTx should have passed verification before AP1, but failed due to %s", err)
   134  	}
   135  	// Test unsorted EVM Inputs fails after AP1
   136  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase1); err == nil {
   137  		t.Fatal("ExportTx should have failed verification due to unsorted EVM Inputs")
   138  	}
   139  	exportTx.Ins = []EVMInput{
   140  		{
   141  			Address: testEthAddrs[0],
   142  			Amount:  0,
   143  			AssetID: testAvaxAssetID,
   144  			Nonce:   0,
   145  		},
   146  	}
   147  	// Test ExportTx with invalid EVM Input amount 0 fails verification
   148  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase1); err == nil {
   149  		t.Fatal("ExportTx should have failed verification due to 0 value amount")
   150  	}
   151  	exportTx.Ins = []EVMInput{evmInputs[0], evmInputs[0]}
   152  	// Test non-unique EVM Inputs passes verification before AP1
   153  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase0); err != nil {
   154  		t.Fatalf("ExportTx with non-unique EVM Inputs should have passed verification prior to AP1, but failed due to %s", err)
   155  	}
   156  	exportTx.Ins = []EVMInput{evmInputs[0], evmInputs[0]}
   157  	// Test non-unique EVM Inputs fails verification after AP1
   158  	if err := exportTx.Verify(testXChainID, ctx, apricotRulesPhase1); err == nil {
   159  		t.Fatal("ExportTx should have failed verification due to non-unique inputs")
   160  	}
   161  }
   162  
   163  // Note: this is a brittle test to ensure that the gas cost of a transaction does
   164  // not change
   165  func TestExportTxGasCost(t *testing.T) {
   166  	avaxAssetID := ids.GenerateTestID()
   167  	chainID := ids.GenerateTestID()
   168  	xChainID := ids.GenerateTestID()
   169  	networkID := uint32(5)
   170  	exportAmount := uint64(5000000)
   171  
   172  	tests := map[string]struct {
   173  		UnsignedExportTx *UnsignedExportTx
   174  		Keys             [][]*crypto.PrivateKeySECP256K1R
   175  
   176  		BaseFee      *big.Int
   177  		ExpectedCost uint64
   178  		ExpectedFee  uint64
   179  	}{
   180  		"simple export 1wei BaseFee": {
   181  			UnsignedExportTx: &UnsignedExportTx{
   182  				NetworkID:        networkID,
   183  				BlockchainID:     chainID,
   184  				DestinationChain: xChainID,
   185  				Ins: []EVMInput{
   186  					{
   187  						Address: testEthAddrs[0],
   188  						Amount:  exportAmount,
   189  						AssetID: avaxAssetID,
   190  						Nonce:   0,
   191  					},
   192  				},
   193  				ExportedOutputs: []*avax.TransferableOutput{
   194  					{
   195  						Asset: avax.Asset{ID: avaxAssetID},
   196  						Out: &secp256k1fx.TransferOutput{
   197  							Amt: exportAmount,
   198  							OutputOwners: secp256k1fx.OutputOwners{
   199  								Locktime:  0,
   200  								Threshold: 1,
   201  								Addrs:     []ids.ShortID{testShortIDAddrs[0]},
   202  							},
   203  						},
   204  					},
   205  				},
   206  			},
   207  			Keys:         [][]*crypto.PrivateKeySECP256K1R{{testKeys[0]}},
   208  			ExpectedCost: 1230,
   209  			ExpectedFee:  1,
   210  			BaseFee:      big.NewInt(1),
   211  		},
   212  		"simple export 25Gwei BaseFee": {
   213  			UnsignedExportTx: &UnsignedExportTx{
   214  				NetworkID:        networkID,
   215  				BlockchainID:     chainID,
   216  				DestinationChain: xChainID,
   217  				Ins: []EVMInput{
   218  					{
   219  						Address: testEthAddrs[0],
   220  						Amount:  exportAmount,
   221  						AssetID: avaxAssetID,
   222  						Nonce:   0,
   223  					},
   224  				},
   225  				ExportedOutputs: []*avax.TransferableOutput{
   226  					{
   227  						Asset: avax.Asset{ID: avaxAssetID},
   228  						Out: &secp256k1fx.TransferOutput{
   229  							Amt: exportAmount,
   230  							OutputOwners: secp256k1fx.OutputOwners{
   231  								Locktime:  0,
   232  								Threshold: 1,
   233  								Addrs:     []ids.ShortID{testShortIDAddrs[0]},
   234  							},
   235  						},
   236  					},
   237  				},
   238  			},
   239  			Keys:         [][]*crypto.PrivateKeySECP256K1R{{testKeys[0]}},
   240  			ExpectedCost: 1230,
   241  			ExpectedFee:  30750,
   242  			BaseFee:      big.NewInt(25 * params.GWei),
   243  		},
   244  		"simple export 225Gwei BaseFee": {
   245  			UnsignedExportTx: &UnsignedExportTx{
   246  				NetworkID:        networkID,
   247  				BlockchainID:     chainID,
   248  				DestinationChain: xChainID,
   249  				Ins: []EVMInput{
   250  					{
   251  						Address: testEthAddrs[0],
   252  						Amount:  exportAmount,
   253  						AssetID: avaxAssetID,
   254  						Nonce:   0,
   255  					},
   256  				},
   257  				ExportedOutputs: []*avax.TransferableOutput{
   258  					{
   259  						Asset: avax.Asset{ID: avaxAssetID},
   260  						Out: &secp256k1fx.TransferOutput{
   261  							Amt: exportAmount,
   262  							OutputOwners: secp256k1fx.OutputOwners{
   263  								Locktime:  0,
   264  								Threshold: 1,
   265  								Addrs:     []ids.ShortID{testShortIDAddrs[0]},
   266  							},
   267  						},
   268  					},
   269  				},
   270  			},
   271  			Keys:         [][]*crypto.PrivateKeySECP256K1R{{testKeys[0]}},
   272  			ExpectedCost: 1230,
   273  			ExpectedFee:  276750,
   274  			BaseFee:      big.NewInt(225 * params.GWei),
   275  		},
   276  		"complex export 25Gwei BaseFee": {
   277  			UnsignedExportTx: &UnsignedExportTx{
   278  				NetworkID:        networkID,
   279  				BlockchainID:     chainID,
   280  				DestinationChain: xChainID,
   281  				Ins: []EVMInput{
   282  					{
   283  						Address: testEthAddrs[0],
   284  						Amount:  exportAmount,
   285  						AssetID: avaxAssetID,
   286  						Nonce:   0,
   287  					},
   288  					{
   289  						Address: testEthAddrs[1],
   290  						Amount:  exportAmount,
   291  						AssetID: avaxAssetID,
   292  						Nonce:   0,
   293  					},
   294  					{
   295  						Address: testEthAddrs[2],
   296  						Amount:  exportAmount,
   297  						AssetID: avaxAssetID,
   298  						Nonce:   0,
   299  					},
   300  				},
   301  				ExportedOutputs: []*avax.TransferableOutput{
   302  					{
   303  						Asset: avax.Asset{ID: avaxAssetID},
   304  						Out: &secp256k1fx.TransferOutput{
   305  							Amt: exportAmount * 3,
   306  							OutputOwners: secp256k1fx.OutputOwners{
   307  								Locktime:  0,
   308  								Threshold: 1,
   309  								Addrs:     []ids.ShortID{testShortIDAddrs[0]},
   310  							},
   311  						},
   312  					},
   313  				},
   314  			},
   315  			Keys:         [][]*crypto.PrivateKeySECP256K1R{{testKeys[0], testKeys[0], testKeys[0]}},
   316  			ExpectedCost: 3366,
   317  			ExpectedFee:  84150,
   318  			BaseFee:      big.NewInt(25 * params.GWei),
   319  		},
   320  		"complex export 225Gwei BaseFee": {
   321  			UnsignedExportTx: &UnsignedExportTx{
   322  				NetworkID:        networkID,
   323  				BlockchainID:     chainID,
   324  				DestinationChain: xChainID,
   325  				Ins: []EVMInput{
   326  					{
   327  						Address: testEthAddrs[0],
   328  						Amount:  exportAmount,
   329  						AssetID: avaxAssetID,
   330  						Nonce:   0,
   331  					},
   332  					{
   333  						Address: testEthAddrs[1],
   334  						Amount:  exportAmount,
   335  						AssetID: avaxAssetID,
   336  						Nonce:   0,
   337  					},
   338  					{
   339  						Address: testEthAddrs[2],
   340  						Amount:  exportAmount,
   341  						AssetID: avaxAssetID,
   342  						Nonce:   0,
   343  					},
   344  				},
   345  				ExportedOutputs: []*avax.TransferableOutput{
   346  					{
   347  						Asset: avax.Asset{ID: avaxAssetID},
   348  						Out: &secp256k1fx.TransferOutput{
   349  							Amt: exportAmount * 3,
   350  							OutputOwners: secp256k1fx.OutputOwners{
   351  								Locktime:  0,
   352  								Threshold: 1,
   353  								Addrs:     []ids.ShortID{testShortIDAddrs[0]},
   354  							},
   355  						},
   356  					},
   357  				},
   358  			},
   359  			Keys:         [][]*crypto.PrivateKeySECP256K1R{{testKeys[0], testKeys[0], testKeys[0]}},
   360  			ExpectedCost: 3366,
   361  			ExpectedFee:  757350,
   362  			BaseFee:      big.NewInt(225 * params.GWei),
   363  		},
   364  	}
   365  
   366  	for name, test := range tests {
   367  		t.Run(name, func(t *testing.T) {
   368  			tx := &Tx{UnsignedAtomicTx: test.UnsignedExportTx}
   369  
   370  			// Sign with the correct key
   371  			if err := tx.Sign(Codec, test.Keys); err != nil {
   372  				t.Fatal(err)
   373  			}
   374  
   375  			cost, err := tx.Cost()
   376  			if err != nil {
   377  				t.Fatal(err)
   378  			}
   379  			if cost != test.ExpectedCost {
   380  				t.Fatalf("Expected cost to be %d, but found %d", test.ExpectedCost, cost)
   381  			}
   382  
   383  			fee, err := calculateDynamicFee(cost, test.BaseFee)
   384  			if err != nil {
   385  				t.Fatal(err)
   386  			}
   387  			if fee != test.ExpectedFee {
   388  				t.Fatalf("Expected fee to be %d, but found %d", test.ExpectedFee, fee)
   389  			}
   390  		})
   391  	}
   392  }
   393  
   394  func TestNewExportTx(t *testing.T) {
   395  	tests := []struct {
   396  		name    string
   397  		genesis string
   398  		rules   params.Rules
   399  		bal     uint64
   400  	}{
   401  		{
   402  			name:    "apricot phase 0",
   403  			genesis: genesisJSONApricotPhase0,
   404  			rules:   apricotRulesPhase0,
   405  			bal:     44000000,
   406  		},
   407  		{
   408  			name:    "apricot phase 1",
   409  			genesis: genesisJSONApricotPhase1,
   410  			rules:   apricotRulesPhase1,
   411  			bal:     44000000,
   412  		},
   413  		{
   414  			name:    "apricot phase 2",
   415  			genesis: genesisJSONApricotPhase2,
   416  			rules:   apricotRulesPhase2,
   417  			bal:     43000000,
   418  		},
   419  		{
   420  			name:    "apricot phase 3",
   421  			genesis: genesisJSONApricotPhase3,
   422  			rules:   apricotRulesPhase3,
   423  			bal:     44446500,
   424  		},
   425  	}
   426  	for _, test := range tests {
   427  		t.Run(test.name, func(t *testing.T) {
   428  			issuer, vm, _, sharedMemory := GenesisVM(t, true, test.genesis, "", "")
   429  
   430  			defer func() {
   431  				if err := vm.Shutdown(); err != nil {
   432  					t.Fatal(err)
   433  				}
   434  			}()
   435  
   436  			parent := vm.LastAcceptedBlockInternal().(*Block)
   437  			importAmount := uint64(50000000)
   438  			utxoID := avax.UTXOID{
   439  				TxID: ids.ID{
   440  					0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
   441  					0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
   442  					0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea,
   443  					0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8,
   444  				},
   445  			}
   446  
   447  			utxo := &avax.UTXO{
   448  				UTXOID: utxoID,
   449  				Asset:  avax.Asset{ID: vm.ctx.AVAXAssetID},
   450  				Out: &secp256k1fx.TransferOutput{
   451  					Amt: importAmount,
   452  					OutputOwners: secp256k1fx.OutputOwners{
   453  						Threshold: 1,
   454  						Addrs:     []ids.ShortID{testKeys[0].PublicKey().Address()},
   455  					},
   456  				},
   457  			}
   458  			utxoBytes, err := vm.codec.Marshal(codecVersion, utxo)
   459  			if err != nil {
   460  				t.Fatal(err)
   461  			}
   462  
   463  			xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID)
   464  			inputID := utxo.InputID()
   465  			if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{{
   466  				Key:   inputID[:],
   467  				Value: utxoBytes,
   468  				Traits: [][]byte{
   469  					testKeys[0].PublicKey().Address().Bytes(),
   470  				},
   471  			}}}}); err != nil {
   472  				t.Fatal(err)
   473  			}
   474  
   475  			tx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*crypto.PrivateKeySECP256K1R{testKeys[0]})
   476  			if err != nil {
   477  				t.Fatal(err)
   478  			}
   479  
   480  			if err := vm.issueTx(tx); err != nil {
   481  				t.Fatal(err)
   482  			}
   483  
   484  			<-issuer
   485  
   486  			blk, err := vm.BuildBlock()
   487  			if err != nil {
   488  				t.Fatal(err)
   489  			}
   490  
   491  			if err := blk.Verify(); err != nil {
   492  				t.Fatal(err)
   493  			}
   494  
   495  			if err := vm.SetPreference(blk.ID()); err != nil {
   496  				t.Fatal(err)
   497  			}
   498  
   499  			if err := blk.Accept(); err != nil {
   500  				t.Fatal(err)
   501  			}
   502  
   503  			parent = vm.LastAcceptedBlockInternal().(*Block)
   504  			exportAmount := uint64(5000000)
   505  
   506  			testKeys0Addr := GetEthAddress(testKeys[0])
   507  			exportId, err := ids.ToShortID(testKeys0Addr[:])
   508  			if err != nil {
   509  				t.Fatal(err)
   510  			}
   511  
   512  			tx, err = vm.newExportTx(vm.ctx.AVAXAssetID, exportAmount, vm.ctx.XChainID, exportId, initialBaseFee, []*crypto.PrivateKeySECP256K1R{testKeys[0]})
   513  			if err != nil {
   514  				t.Fatal(err)
   515  			}
   516  
   517  			exportTx := tx.UnsignedAtomicTx
   518  
   519  			if err := exportTx.SemanticVerify(vm, tx, parent, parent.ethBlock.BaseFee(), test.rules); err != nil {
   520  				t.Fatal("newExportTx created an invalid transaction", err)
   521  			}
   522  
   523  			commitBatch, err := vm.db.CommitBatch()
   524  			if err != nil {
   525  				t.Fatalf("Failed to create commit batch for VM due to %s", err)
   526  			}
   527  			if err := exportTx.Accept(vm.ctx, commitBatch); err != nil {
   528  				t.Fatalf("Failed to accept export transaction due to: %s", err)
   529  			}
   530  
   531  			stdb, err := vm.chain.CurrentState()
   532  			if err != nil {
   533  				t.Fatal(err)
   534  			}
   535  			err = exportTx.EVMStateTransfer(vm.ctx, stdb)
   536  			if err != nil {
   537  				t.Fatal(err)
   538  			}
   539  
   540  			addr := GetEthAddress(testKeys[0])
   541  			if stdb.GetBalance(addr).Cmp(new(big.Int).SetUint64(test.bal*units.Avax)) != 0 {
   542  				t.Fatalf("address balance %s equal %s not %s", addr.String(), stdb.GetBalance(addr), new(big.Int).SetUint64(test.bal*units.Avax))
   543  			}
   544  		})
   545  	}
   546  }