diff --git a/codec/manager.go b/codec/manager.go index 96d763520b0..3a5e9eb174f 100644 --- a/codec/manager.go +++ b/codec/manager.go @@ -23,14 +23,13 @@ const ( ) var ( - ErrUnknownVersion = errors.New("unknown codec version") - - errMarshalNil = errors.New("can't marshal nil pointer or interface") - errUnmarshalNil = errors.New("can't unmarshal nil") - errUnmarshalTooBig = errors.New("byte array exceeds maximum length") - errCantPackVersion = errors.New("couldn't pack codec version") - errCantUnpackVersion = errors.New("couldn't unpack codec version") - errDuplicatedVersion = errors.New("duplicated codec version") + ErrUnknownVersion = errors.New("unknown codec version") + ErrMarshalNil = errors.New("can't marshal nil pointer or interface") + ErrUnmarshalNil = errors.New("can't unmarshal nil") + ErrUnmarshalTooBig = errors.New("byte array exceeds maximum length") + ErrCantPackVersion = errors.New("couldn't pack codec version") + ErrCantUnpackVersion = errors.New("couldn't unpack codec version") + ErrDuplicatedVersion = errors.New("duplicated codec version") ) var _ Manager = (*manager)(nil) @@ -43,7 +42,7 @@ type Manager interface { // Size returns the size, in bytes, of [value] when it's marshaled // using the codec with the given version. // RegisterCodec must have been called with that version. - // If [value] is nil, returns [errMarshalNil] + // If [value] is nil, returns [ErrMarshalNil] Size(version uint16, value interface{}) (int, error) // Marshal the given value using the codec with the given version. @@ -82,7 +81,7 @@ func (m *manager) RegisterCodec(version uint16, codec Codec) error { defer m.lock.Unlock() if _, exists := m.codecs[version]; exists { - return errDuplicatedVersion + return ErrDuplicatedVersion } m.codecs[version] = codec return nil @@ -90,7 +89,7 @@ func (m *manager) RegisterCodec(version uint16, codec Codec) error { func (m *manager) Size(version uint16, value interface{}) (int, error) { if value == nil { - return 0, errMarshalNil // can't marshal nil + return 0, ErrMarshalNil // can't marshal nil } m.lock.RLock() @@ -110,7 +109,7 @@ func (m *manager) Size(version uint16, value interface{}) (int, error) { // To marshal an interface, [value] must be a pointer to the interface. func (m *manager) Marshal(version uint16, value interface{}) ([]byte, error) { if value == nil { - return nil, errMarshalNil // can't marshal nil + return nil, ErrMarshalNil // can't marshal nil } m.lock.RLock() @@ -126,7 +125,7 @@ func (m *manager) Marshal(version uint16, value interface{}) ([]byte, error) { } p.PackShort(version) if p.Errored() { - return nil, errCantPackVersion // Should never happen + return nil, ErrCantPackVersion // Should never happen } return p.Bytes, c.MarshalInto(value, &p) } @@ -135,11 +134,11 @@ func (m *manager) Marshal(version uint16, value interface{}) ([]byte, error) { // interface. func (m *manager) Unmarshal(bytes []byte, dest interface{}) (uint16, error) { if dest == nil { - return 0, errUnmarshalNil + return 0, ErrUnmarshalNil } if byteLen := len(bytes); byteLen > m.maxSize { - return 0, fmt.Errorf("%w: %d > %d", errUnmarshalTooBig, byteLen, m.maxSize) + return 0, fmt.Errorf("%w: %d > %d", ErrUnmarshalTooBig, byteLen, m.maxSize) } p := wrappers.Packer{ @@ -147,7 +146,7 @@ func (m *manager) Unmarshal(bytes []byte, dest interface{}) (uint16, error) { } version := p.UnpackShort() if p.Errored() { // Make sure the codec version is correct - return 0, errCantUnpackVersion + return 0, ErrCantUnpackVersion } m.lock.RLock() diff --git a/codec/test_codec.go b/codec/test_codec.go index 13d81d1eca6..8571c38a844 100644 --- a/codec/test_codec.go +++ b/codec/test_codec.go @@ -746,7 +746,7 @@ func TestTooLargeUnmarshal(codec GeneralCodec, t testing.TB) { s := inner{} _, err := manager.Unmarshal(bytes, &s) - require.ErrorIs(err, errUnmarshalTooBig) + require.ErrorIs(err, ErrUnmarshalTooBig) } type outerInterface interface { diff --git a/vms/avm/blocks/block_test.go b/vms/avm/blocks/block_test.go index 4a20080391c..1dad208ffd5 100644 --- a/vms/avm/blocks/block_test.go +++ b/vms/avm/blocks/block_test.go @@ -25,6 +25,18 @@ var ( assetID = ids.GenerateTestID() ) +func TestInvalidBlock(t *testing.T) { + require := require.New(t) + + parser, err := NewParser([]fxs.Fx{ + &secp256k1fx.Fx{}, + }) + require.NoError(err) + + _, err = parser.ParseBlock(nil) + require.ErrorIs(err, codec.ErrCantUnpackVersion) +} + func TestStandardBlocks(t *testing.T) { // check standard block can be built and parsed require := require.New(t) diff --git a/vms/avm/txs/executor/executor_test.go b/vms/avm/txs/executor/executor_test.go new file mode 100644 index 00000000000..7bbcbbaa262 --- /dev/null +++ b/vms/avm/txs/executor/executor_test.go @@ -0,0 +1,450 @@ +// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package executor + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/database/versiondb" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/avm/blocks" + "github.com/ava-labs/avalanchego/vms/avm/fxs" + "github.com/ava-labs/avalanchego/vms/avm/states" + "github.com/ava-labs/avalanchego/vms/avm/txs" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" +) + +var ( + chainID = ids.ID{5, 4, 3, 2, 1} + assetID = ids.ID{1, 2, 3} +) + +func TestBaseTxExecutor(t *testing.T) { + require := require.New(t) + + secpFx := &secp256k1fx.Fx{} + parser, err := blocks.NewParser([]fxs.Fx{secpFx}) + require.NoError(err) + codec := parser.Codec() + + db := memdb.New() + vdb := versiondb.New(db) + registerer := prometheus.NewRegistry() + state, err := states.New(vdb, parser, registerer) + require.NoError(err) + + utxoID := avax.UTXOID{ + TxID: ids.GenerateTestID(), + OutputIndex: 1, + } + + addr := keys[0].Address() + utxo := &avax.UTXO{ + UTXOID: utxoID, + Asset: avax.Asset{ID: assetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 20 * units.KiloAvax, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + addr, + }, + }, + }, + } + + // Populate the UTXO that we will be consuming + state.AddUTXO(utxo) + require.NoError(state.Commit()) + + baseTx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{ + NetworkID: constants.UnitTestID, + BlockchainID: chainID, + Ins: []*avax.TransferableInput{{ + UTXOID: utxoID, + Asset: avax.Asset{ID: assetID}, + In: &secp256k1fx.TransferInput{ + Amt: 20 * units.KiloAvax, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, + }, + }, + }, + }}, + Outs: []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: assetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 10 * units.KiloAvax, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{addr}, + }, + }, + }}, + }}} + require.NoError(baseTx.SignSECP256K1Fx(codec, [][]*secp256k1.PrivateKey{{keys[0]}})) + + executor := &Executor{ + Codec: codec, + State: state, + Tx: baseTx, + } + + // Execute baseTx + require.NoError(baseTx.Unsigned.Visit(executor)) + + // Verify the consumed UTXO was removed from the state + _, err = executor.State.GetUTXO(utxoID.InputID()) + require.ErrorIs(err, database.ErrNotFound) + + // Verify the produced UTXO was added to the state + expectedOutputUTXO := &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: baseTx.TxID, + OutputIndex: 0, + }, + Asset: avax.Asset{ + ID: assetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: 10 * units.KiloAvax, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{addr}, + }, + }, + } + expectedOutputUTXOID := expectedOutputUTXO.InputID() + outputUTXO, err := executor.State.GetUTXO(expectedOutputUTXOID) + require.NoError(err) + + outputUTXOID := outputUTXO.InputID() + require.Equal(expectedOutputUTXOID, outputUTXOID) + require.Equal(expectedOutputUTXO, outputUTXO) +} + +func TestCreateAssetTxExecutor(t *testing.T) { + require := require.New(t) + + secpFx := &secp256k1fx.Fx{} + parser, err := blocks.NewParser([]fxs.Fx{secpFx}) + require.NoError(err) + codec := parser.Codec() + + db := memdb.New() + vdb := versiondb.New(db) + registerer := prometheus.NewRegistry() + state, err := states.New(vdb, parser, registerer) + require.NoError(err) + + utxoID := avax.UTXOID{ + TxID: ids.GenerateTestID(), + OutputIndex: 1, + } + + addr := keys[0].Address() + utxo := &avax.UTXO{ + UTXOID: utxoID, + Asset: avax.Asset{ID: assetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 20 * units.KiloAvax, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + addr, + }, + }, + }, + } + + // Populate the UTXO that we will be consuming + state.AddUTXO(utxo) + require.NoError(state.Commit()) + + createAssetTx := &txs.Tx{Unsigned: &txs.CreateAssetTx{ + BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ + NetworkID: constants.UnitTestID, + BlockchainID: chainID, + Ins: []*avax.TransferableInput{{ + UTXOID: utxoID, + Asset: avax.Asset{ID: assetID}, + In: &secp256k1fx.TransferInput{ + Amt: 20 * units.KiloAvax, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, + }, + }, + }, + }}, + Outs: []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: assetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 10 * units.KiloAvax, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{addr}, + }, + }, + }}, + }}, + Name: "name", + Symbol: "symb", + Denomination: 0, + States: []*txs.InitialState{ + { + FxIndex: 0, + Outs: []verify.State{ + &secp256k1fx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{addr}, + }, + }, + }, + }, + }, + }} + require.NoError(createAssetTx.SignSECP256K1Fx(codec, [][]*secp256k1.PrivateKey{{keys[0]}})) + + executor := &Executor{ + Codec: codec, + State: state, + Tx: createAssetTx, + } + + // Execute createAssetTx + require.NoError(createAssetTx.Unsigned.Visit(executor)) + + // Verify the consumed UTXO was removed from the state + _, err = executor.State.GetUTXO(utxoID.InputID()) + require.ErrorIs(err, database.ErrNotFound) + + // Verify the produced UTXOs were added to the state + txID := createAssetTx.ID() + expectedOutputUTXOs := []*avax.UTXO{ + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: 0, + }, + Asset: avax.Asset{ + ID: assetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: 10 * units.KiloAvax, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{addr}, + }, + }, + }, + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: 1, + }, + Asset: avax.Asset{ + ID: txID, + }, + Out: &secp256k1fx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{addr}, + }, + }, + }, + } + for _, expectedOutputUTXO := range expectedOutputUTXOs { + expectedOutputUTXOID := expectedOutputUTXO.InputID() + outputUTXO, err := executor.State.GetUTXO(expectedOutputUTXOID) + require.NoError(err) + + outputUTXOID := outputUTXO.InputID() + require.Equal(expectedOutputUTXOID, outputUTXOID) + require.Equal(expectedOutputUTXO, outputUTXO) + } +} + +func TestOperationTxExecutor(t *testing.T) { + require := require.New(t) + + secpFx := &secp256k1fx.Fx{} + parser, err := blocks.NewParser([]fxs.Fx{secpFx}) + require.NoError(err) + codec := parser.Codec() + + db := memdb.New() + vdb := versiondb.New(db) + registerer := prometheus.NewRegistry() + state, err := states.New(vdb, parser, registerer) + require.NoError(err) + + outputOwners := secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + keys[0].Address(), + }, + } + + utxoID := avax.UTXOID{ + TxID: ids.GenerateTestID(), + OutputIndex: 1, + } + utxo := &avax.UTXO{ + UTXOID: utxoID, + Asset: avax.Asset{ID: assetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 20 * units.KiloAvax, + OutputOwners: outputOwners, + }, + } + + opUTXOID := avax.UTXOID{ + TxID: ids.GenerateTestID(), + OutputIndex: 1, + } + opUTXO := &avax.UTXO{ + UTXOID: opUTXOID, + Asset: avax.Asset{ID: assetID}, + Out: &secp256k1fx.MintOutput{ + OutputOwners: outputOwners, + }, + } + + // Populate the UTXOs that we will be consuming + state.AddUTXO(utxo) + state.AddUTXO(opUTXO) + require.NoError(state.Commit()) + + operationTx := &txs.Tx{Unsigned: &txs.OperationTx{ + BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ + NetworkID: constants.UnitTestID, + BlockchainID: chainID, + Ins: []*avax.TransferableInput{{ + UTXOID: utxoID, + Asset: avax.Asset{ID: assetID}, + In: &secp256k1fx.TransferInput{ + Amt: 20 * units.KiloAvax, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, + }, + }, + }, + }}, + Outs: []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: assetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 10 * units.KiloAvax, + OutputOwners: outputOwners, + }, + }}, + }}, + Ops: []*txs.Operation{{ + Asset: avax.Asset{ID: assetID}, + UTXOIDs: []*avax.UTXOID{ + &opUTXOID, + }, + Op: &secp256k1fx.MintOperation{ + MintInput: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + MintOutput: secp256k1fx.MintOutput{ + OutputOwners: outputOwners, + }, + TransferOutput: secp256k1fx.TransferOutput{ + Amt: 12345, + OutputOwners: outputOwners, + }, + }, + }}, + }} + require.NoError(operationTx.SignSECP256K1Fx( + codec, + [][]*secp256k1.PrivateKey{ + {keys[0]}, + {keys[0]}, + }, + )) + + executor := &Executor{ + Codec: codec, + State: state, + Tx: operationTx, + } + + // Execute operationTx + require.NoError(operationTx.Unsigned.Visit(executor)) + + // Verify the consumed UTXOs were removed from the state + _, err = executor.State.GetUTXO(utxo.InputID()) + require.ErrorIs(err, database.ErrNotFound) + _, err = executor.State.GetUTXO(opUTXO.InputID()) + require.ErrorIs(err, database.ErrNotFound) + + // Verify the produced UTXOs were added to the state + txID := operationTx.ID() + expectedOutputUTXOs := []*avax.UTXO{ + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: 0, + }, + Asset: avax.Asset{ + ID: assetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: 10 * units.KiloAvax, + OutputOwners: outputOwners, + }, + }, + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: 1, + }, + Asset: avax.Asset{ + ID: assetID, + }, + Out: &secp256k1fx.MintOutput{ + OutputOwners: outputOwners, + }, + }, + { + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: 2, + }, + Asset: avax.Asset{ + ID: assetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: 12345, + OutputOwners: outputOwners, + }, + }, + } + for _, expectedOutputUTXO := range expectedOutputUTXOs { + expectedOutputUTXOID := expectedOutputUTXO.InputID() + outputUTXO, err := executor.State.GetUTXO(expectedOutputUTXOID) + require.NoError(err) + + outputUTXOID := outputUTXO.InputID() + require.Equal(expectedOutputUTXOID, outputUTXOID) + require.Equal(expectedOutputUTXO, outputUTXO) + } +} diff --git a/vms/avm/txs/executor/semantic_verifier_test.go b/vms/avm/txs/executor/semantic_verifier_test.go index 1eb3d2e3501..63c2fa5cee5 100644 --- a/vms/avm/txs/executor/semantic_verifier_test.go +++ b/vms/avm/txs/executor/semantic_verifier_test.go @@ -11,12 +11,17 @@ import ( "github.com/stretchr/testify/require" + "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/database/manager" + "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/timer/mockable" + "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms/avm/fxs" "github.com/ava-labs/avalanchego/vms/avm/states" "github.com/ava-labs/avalanchego/vms/avm/txs" @@ -873,3 +878,267 @@ func TestSemanticVerifierExportTxDifferentSubnet(t *testing.T) { }) require.ErrorIs(err, verify.ErrMismatchedSubnetIDs) } + +func TestSemanticVerifierImportTx(t *testing.T) { + ctx := newContext(t) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + validatorState := validators.NewMockState(ctrl) + validatorState.EXPECT().GetSubnetID(gomock.Any(), ctx.CChainID).AnyTimes().Return(ctx.SubnetID, nil) + ctx.ValidatorState = validatorState + + baseDBManager := manager.NewMemDB(version.Semantic1_0_0) + m := atomic.NewMemory(prefixdb.New([]byte{0}, baseDBManager.Current().Database)) + ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID) + + typeToFxIndex := make(map[reflect.Type]int) + fx := &secp256k1fx.Fx{} + parser, err := txs.NewCustomParser( + typeToFxIndex, + new(mockable.Clock), + logging.NoWarn{}, + []fxs.Fx{ + fx, + }, + ) + require.NoError(t, err) + + codec := parser.Codec() + utxoID := avax.UTXOID{ + TxID: ids.GenerateTestID(), + OutputIndex: 2, + } + + asset := avax.Asset{ + ID: ids.GenerateTestID(), + } + outputOwners := secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + keys[0].Address(), + }, + } + baseTx := txs.BaseTx{ + BaseTx: avax.BaseTx{ + NetworkID: constants.UnitTestID, + BlockchainID: ctx.ChainID, + Outs: []*avax.TransferableOutput{{ + Asset: asset, + Out: &secp256k1fx.TransferOutput{ + Amt: 1000, + OutputOwners: outputOwners, + }, + }}, + }, + } + input := avax.TransferableInput{ + UTXOID: utxoID, + Asset: asset, + In: &secp256k1fx.TransferInput{ + Amt: 12345, + Input: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + }, + } + unsignedImportTx := txs.ImportTx{ + BaseTx: baseTx, + SourceChain: ctx.CChainID, + ImportedIns: []*avax.TransferableInput{ + &input, + }, + } + importTx := &txs.Tx{ + Unsigned: &unsignedImportTx, + } + require.NoError(t, importTx.SignSECP256K1Fx( + codec, + [][]*secp256k1.PrivateKey{ + {keys[0]}, + }, + )) + + backend := &Backend{ + Ctx: ctx, + Config: &feeConfig, + Fxs: []*fxs.ParsedFx{ + { + ID: secp256k1fx.ID, + Fx: fx, + }, + }, + TypeToFxIndex: typeToFxIndex, + Codec: codec, + FeeAssetID: ids.GenerateTestID(), + Bootstrapped: true, + } + require.NoError(t, fx.Bootstrapped()) + + output := secp256k1fx.TransferOutput{ + Amt: 12345, + OutputOwners: outputOwners, + } + utxo := avax.UTXO{ + UTXOID: utxoID, + Asset: asset, + Out: &output, + } + utxoBytes, err := codec.Marshal(txs.CodecVersion, utxo) + require.NoError(t, err) + + peerSharedMemory := m.NewSharedMemory(ctx.CChainID) + inputID := utxo.InputID() + require.NoError(t, peerSharedMemory.Apply(map[ids.ID]*atomic.Requests{ctx.ChainID: {PutRequests: []*atomic.Element{{ + Key: inputID[:], + Value: utxoBytes, + Traits: [][]byte{ + keys[0].PublicKey().Address().Bytes(), + }, + }}}})) + + unsignedCreateAssetTx := txs.CreateAssetTx{ + States: []*txs.InitialState{{ + FxIndex: 0, + }}, + } + createAssetTx := txs.Tx{ + Unsigned: &unsignedCreateAssetTx, + } + tests := []struct { + name string + stateFunc func(*gomock.Controller) states.Chain + txFunc func(*require.Assertions) *txs.Tx + expectedErr error + }{ + { + name: "valid", + stateFunc: func(ctrl *gomock.Controller) states.Chain { + state := states.NewMockChain(ctrl) + state.EXPECT().GetUTXOFromID(&utxoID).Return(&utxo, nil).AnyTimes() + state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes() + return state + }, + txFunc: func(*require.Assertions) *txs.Tx { + return importTx + }, + expectedErr: nil, + }, + { + name: "not allowed input feature extension", + stateFunc: func(ctrl *gomock.Controller) states.Chain { + state := states.NewMockChain(ctrl) + unsignedCreateAssetTx := unsignedCreateAssetTx + unsignedCreateAssetTx.States = nil + createAssetTx := txs.Tx{ + Unsigned: &unsignedCreateAssetTx, + } + state.EXPECT().GetUTXOFromID(&utxoID).Return(&utxo, nil).AnyTimes() + state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes() + return state + }, + txFunc: func(*require.Assertions) *txs.Tx { + return importTx + }, + expectedErr: errIncompatibleFx, + }, + { + name: "invalid signature", + stateFunc: func(ctrl *gomock.Controller) states.Chain { + state := states.NewMockChain(ctrl) + state.EXPECT().GetUTXOFromID(&utxoID).Return(&utxo, nil).AnyTimes() + state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes() + return state + }, + txFunc: func(require *require.Assertions) *txs.Tx { + tx := &txs.Tx{ + Unsigned: &unsignedImportTx, + } + require.NoError(tx.SignSECP256K1Fx( + codec, + [][]*secp256k1.PrivateKey{ + {keys[1]}, + }, + )) + return tx + }, + expectedErr: secp256k1fx.ErrWrongSig, + }, + { + name: "not allowed output feature extension", + stateFunc: func(ctrl *gomock.Controller) states.Chain { + state := states.NewMockChain(ctrl) + unsignedCreateAssetTx := unsignedCreateAssetTx + unsignedCreateAssetTx.States = nil + createAssetTx := txs.Tx{ + Unsigned: &unsignedCreateAssetTx, + } + state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes() + return state + }, + txFunc: func(require *require.Assertions) *txs.Tx { + importTx := unsignedImportTx + importTx.Ins = nil + importTx.ImportedIns = []*avax.TransferableInput{ + &input, + } + tx := &txs.Tx{ + Unsigned: &importTx, + } + require.NoError(tx.SignSECP256K1Fx( + codec, + nil, + )) + return tx + }, + expectedErr: errIncompatibleFx, + }, + { + name: "unknown asset", + stateFunc: func(ctrl *gomock.Controller) states.Chain { + state := states.NewMockChain(ctrl) + state.EXPECT().GetUTXOFromID(&utxoID).Return(&utxo, nil).AnyTimes() + state.EXPECT().GetTx(asset.ID).Return(nil, database.ErrNotFound) + return state + }, + txFunc: func(*require.Assertions) *txs.Tx { + return importTx + }, + expectedErr: database.ErrNotFound, + }, + { + name: "not an asset", + stateFunc: func(ctrl *gomock.Controller) states.Chain { + state := states.NewMockChain(ctrl) + tx := txs.Tx{ + Unsigned: &baseTx, + } + state.EXPECT().GetUTXOFromID(&utxoID).Return(&utxo, nil).AnyTimes() + state.EXPECT().GetTx(asset.ID).Return(&tx, nil) + return state + }, + txFunc: func(*require.Assertions) *txs.Tx { + return importTx + }, + expectedErr: errNotAnAsset, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + state := test.stateFunc(ctrl) + tx := test.txFunc(require) + err := tx.Unsigned.Visit(&SemanticVerifier{ + Backend: backend, + State: state, + Tx: tx, + }) + require.ErrorIs(err, test.expectedErr) + }) + } +}