diff options
Diffstat (limited to 'plugin/evm/import_tx_test.go')
-rw-r--r-- | plugin/evm/import_tx_test.go | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/plugin/evm/import_tx_test.go b/plugin/evm/import_tx_test.go new file mode 100644 index 0000000..fcd18ac --- /dev/null +++ b/plugin/evm/import_tx_test.go @@ -0,0 +1,349 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package evm + +import ( + "testing" + + "github.com/ava-labs/avalanchego/chains/atomic" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" +) + +func TestImportTxVerifyNil(t *testing.T) { + var importTx *UnsignedImportTx + if err := importTx.Verify(testXChainID, NewContext(), testTxFee, testAvaxAssetID); err == nil { + t.Fatal("Verify should have failed due to nil transaction") + } +} + +func TestImportTxVerify(t *testing.T) { + var importAmount uint64 = 10000000 + txID := ids.NewID([32]byte{0xff}) + importTx := &UnsignedImportTx{ + NetworkID: testNetworkID, + BlockchainID: testCChainID, + SourceChain: testXChainID, + ImportedInputs: []*avax.TransferableInput{ + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(0), + }, + Asset: avax.Asset{ID: testAvaxAssetID}, + In: &secp256k1fx.TransferInput{ + Amt: importAmount, + Input: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + }, + }, + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(1), + }, + Asset: avax.Asset{ID: testAvaxAssetID}, + In: &secp256k1fx.TransferInput{ + Amt: importAmount, + Input: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + }, + }, + }, + Outs: []EVMOutput{ + { + Address: testEthAddrs[0], + Amount: importAmount, + AssetID: testAvaxAssetID, + }, + { + Address: testEthAddrs[1], + Amount: importAmount, + AssetID: testAvaxAssetID, + }, + }, + } + + ctx := NewContext() + + // // Sort the inputs and outputs to ensure the transaction is canonical + avax.SortTransferableInputs(importTx.ImportedInputs) + SortEVMOutputs(importTx.Outs) + + // Test Valid ImportTx + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err != nil { + t.Fatalf("Failed to verify ImportTx: %w", err) + } + + importTx.syntacticallyVerified = false + importTx.NetworkID = testNetworkID + 1 + + // // Test Incorrect Network ID Errors + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to incorrect network ID") + } + + importTx.syntacticallyVerified = false + importTx.NetworkID = testNetworkID + importTx.BlockchainID = nonExistentID + // // Test Incorrect Blockchain ID Errors + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to incorrect blockchain ID") + } + + importTx.syntacticallyVerified = false + importTx.BlockchainID = testCChainID + importTx.SourceChain = nonExistentID + // // Test Incorrect Destination Chain ID Errors + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to incorrect source chain") + } + + importTx.syntacticallyVerified = false + importTx.SourceChain = testXChainID + importedIns := importTx.ImportedInputs + importTx.ImportedInputs = nil + // // Test No Exported Outputs Errors + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to no imported inputs") + } + + importTx.syntacticallyVerified = false + importTx.ImportedInputs = []*avax.TransferableInput{importedIns[1], importedIns[0]} + // // Test Unsorted Imported Inputs Errors + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to unsorted import inputs") + } + + importTx.syntacticallyVerified = false + importTx.ImportedInputs = []*avax.TransferableInput{importedIns[0], nil} + if err := importTx.Verify(testXChainID, ctx, testTxFee, testAvaxAssetID); err == nil { + t.Fatal("ImportTx should have failed verification due to invalid input") + } +} + +func TestImportTxSemanticVerify(t *testing.T) { + _, vm, _, sharedMemory := GenesisVM(t, false) + + xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) + + importAmount := uint64(1000000) + utxoID := avax.UTXOID{ + TxID: ids.NewID([32]byte{ + 0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee, + 0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec, + 0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea, + 0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8, + }), + } + + utxo := &avax.UTXO{ + UTXOID: utxoID, + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: importAmount, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + }, + }, + } + utxoBytes, err := vm.codec.Marshal(utxo) + if err != nil { + t.Fatal(err) + } + + evmOutput := EVMOutput{ + Address: testEthAddrs[0], + Amount: importAmount, + AssetID: vm.ctx.AVAXAssetID, + } + unsignedImportTx := &UnsignedImportTx{ + NetworkID: vm.ctx.NetworkID, + BlockchainID: vm.ctx.ChainID, + SourceChain: vm.ctx.XChainID, + ImportedInputs: []*avax.TransferableInput{{ + UTXOID: utxoID, + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, + In: &secp256k1fx.TransferInput{ + Amt: importAmount, + Input: secp256k1fx.Input{SigIndices: []uint32{0}}, + }, + }}, + Outs: []EVMOutput{evmOutput}, + } + + state, err := vm.chain.BlockState(vm.lastAccepted.ethBlock) + if err != nil { + t.Fatalf("Failed to get last accepted stateDB due to: %s", err) + } + + if empty := state.Empty(testEthAddrs[0]); !empty { + t.Fatalf("Expected ethereum address to have empty starting balance.") + } + + if err := unsignedImportTx.Verify(vm.ctx.XChainID, vm.ctx, vm.txFee, vm.ctx.AVAXAssetID); err != nil { + t.Fatal(err) + } + unsignedImportTx.syntacticallyVerified = false + + tx := &Tx{UnsignedTx: unsignedImportTx} + + // Sign with the correct key + if err := tx.Sign(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{testKeys[0]}}); err != nil { + t.Fatal(err) + } + + // Check that SemanticVerify passes without the UTXO being present during bootstrapping + if err := unsignedImportTx.SemanticVerify(vm, tx); err != nil { + t.Fatal("Should have failed to import non-existent UTXO") + } + + if err := xChainSharedMemory.Put(vm.ctx.ChainID, []*atomic.Element{{ + Key: utxo.InputID().Bytes(), + Value: utxoBytes, + Traits: [][]byte{ + testKeys[0].PublicKey().Address().Bytes(), + }, + }}); err != nil { + t.Fatal(err) + } + + // Check that SemanticVerify passes when the UTXO is present during bootstrapping + if err := unsignedImportTx.SemanticVerify(vm, tx); err != nil { + t.Fatalf("Semantic verification should have passed during bootstrapping when the UTXO was present") + } + + // Check that SemanticVerify does not pass if an additional output is added in + unsignedImportTx.Outs = append(unsignedImportTx.Outs, EVMOutput{ + Address: testEthAddrs[1], + Amount: importAmount, + AssetID: vm.ctx.AVAXAssetID, + }) + + if err := unsignedImportTx.SemanticVerify(vm, tx); err == nil { + t.Fatal("Semantic verification should have failed due to insufficient funds") + } + + unsignedImportTx.Outs = []EVMOutput{evmOutput} + + if err := vm.Bootstrapping(); err != nil { + t.Fatal(err) + } + + if err := vm.Bootstrapped(); err != nil { + t.Fatal(err) + } + + vm.ctx.Bootstrapped() + + // Remove the signature + tx.Creds = nil + if err := unsignedImportTx.SemanticVerify(vm, tx); err == nil { + t.Fatalf("SemanticVerify should have failed due to no signatures") + } + + // Sign with the incorrect key + if err := tx.Sign(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{testKeys[1]}}); err != nil { + t.Fatal(err) + } + if err := unsignedImportTx.SemanticVerify(vm, tx); err == nil { + t.Fatalf("SemanticVerify should have failed due to an invalid signature") + } + + // Re-sign with the correct key + tx.Creds = nil + if err := tx.Sign(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{testKeys[0]}}); err != nil { + t.Fatal(err) + } + + // Check that SemanticVerify passes when the UTXO is present after bootstrapping + if err := unsignedImportTx.SemanticVerify(vm, tx); err != nil { + t.Fatalf("Semantic verification should have passed after bootstrapping with the UTXO present") + } + + if err := unsignedImportTx.Accept(vm.ctx, nil); err != nil { + t.Fatalf("Accept failed due to: %w", err) + } + + if err := unsignedImportTx.EVMStateTransfer(vm, state); err != nil { + t.Fatalf("EVM State Transfer failed due to: %s", err) + } + + balance := state.GetBalance(testEthAddrs[0]) + if balance == nil { + t.Fatal("Found nil balance for address receiving imported funds") + } else if balance.Uint64() != importAmount*x2cRate.Uint64() { + t.Fatalf("Balance was %d, but expected balance of: %d", balance.Uint64(), importAmount*x2cRate.Uint64()) + } + + // Check that SemanticVerify fails when the UTXO is not present after bootstrapping + if err := unsignedImportTx.SemanticVerify(vm, tx); err == nil { + t.Fatalf("Semantic verification should have failed after the UTXO removed from shared memory") + } +} + +func TestNewImportTx(t *testing.T) { + _, vm, _, sharedMemory := GenesisVM(t, true) + + importAmount := uint64(1000000) + utxoID := avax.UTXOID{ + TxID: ids.NewID([32]byte{ + 0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee, + 0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec, + 0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea, + 0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8, + }), + } + + utxo := &avax.UTXO{ + UTXOID: utxoID, + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: importAmount, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + }, + }, + } + utxoBytes, err := vm.codec.Marshal(utxo) + if err != nil { + t.Fatal(err) + } + + xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) + + if err := xChainSharedMemory.Put(vm.ctx.ChainID, []*atomic.Element{{ + Key: utxo.InputID().Bytes(), + Value: utxoBytes, + Traits: [][]byte{ + testKeys[0].PublicKey().Address().Bytes(), + }, + }}); err != nil { + t.Fatal(err) + } + + tx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], []*crypto.PrivateKeySECP256K1R{testKeys[0]}) + if err != nil { + t.Fatal(err) + } + + importTx, ok := tx.UnsignedTx.(UnsignedAtomicTx) + if !ok { + t.Fatal("newImportTx did not return an atomic transaction") + } + + if err := importTx.SemanticVerify(vm, tx); err != nil { + t.Fatalf("newImportTx created an invalid transaction") + } + + if err := importTx.Accept(vm.ctx, nil); err != nil { + t.Fatalf("Failed to accept import transaction due to: %s", err) + } +} |