Files
node/vms/xvm/txs/executor/syntactic_verifier_test.go
T
zeekayandHanzo Dev ddb3fbca93 xvm -> native ZAP struct-is-wire: node-side codec kill COMPLETE
X-Chain fully off pcodecs (the last node consumer). kind.go (xkind 1-5), tx.go
(wire.SignedTx envelope), parser (reflect-map + dual LinearCodec DELETED, fx
dispatch by envelope TypeKind/ShapeKind), base/create_asset/operation/import/
export tx, initial_state, operation, block/{standard,parser,builder}, genesis
(native genesis_wire.go: marshalGenesis/parseGenesis + txs.ParseUnsignedTx),
vm/service/static_service/wallet_service/utxo-spender, metrics (pcodecs.Errs ->
errors.Join). ALL xvm test codec threading removed; xvm tests green.

Block-build invariant: writeTxList requires Initialized txs (mempool/parse hand
the builder canonical bytes) — errors on empty tx-bytes rather than a hidden
Initialize side-effect; state_test updated to Initialize its fixtures.

Transport foundation: rpcchainvm NewListener unix-socket fast path gated
LUXD_VM_UNIX_SOCKET=1 (default TCP, non-breaking; api/zap 159b27a infers unix
from socket-path addr). Consumes upstream utxo TransferableOut/In (4ce6a6e).

ZERO real node-side pcodecs consumers remain. vms/pcodecs kept only for 3
external luxfi/chains VMs (Wave B: zkvm/bridgevm/mpcvm) pending their migration.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-12 11:26:36 -07:00

2345 lines
44 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package executor
import (
"context"
"math"
"strings"
"testing"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/constants"
"github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/vms/xvm/config"
"github.com/luxfi/node/vms/xvm/fxs"
"github.com/luxfi/node/vms/xvm/txs"
"github.com/luxfi/utxo/secp256k1fx"
safemath "github.com/luxfi/math"
)
var (
keys = secp256k1.TestKeys()
feeConfig = config.Config{
TxFee: 2,
CreateAssetTxFee: 3,
}
)
func TestSyntacticVerifierBaseTx(t *testing.T) {
chainID := consensustest.XChainID
cChainID := ids.GenerateTestID()
luxRT := consensustest.Runtime(t, chainID)
ctx := context.Background()
fx := &secp256k1fx.Fx{}
_, err := txs.NewParser(
[]fxs.Fx{
fx,
},
)
require.NoError(t, err)
feeAssetID := ids.GenerateTestID()
asset := lux.Asset{
ID: feeAssetID,
}
outputOwners := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{keys[0].PublicKey().Address()},
}
fxOutput := secp256k1fx.TransferOutput{
Amt: 12345,
OutputOwners: outputOwners,
}
output := lux.TransferableOutput{
Asset: asset,
Out: &fxOutput,
}
inputTxID := ids.GenerateTestID()
utxoID := lux.UTXOID{
TxID: inputTxID,
OutputIndex: 0,
}
inputSigners := secp256k1fx.Input{
SigIndices: []uint32{2},
}
fxInput := secp256k1fx.TransferInput{
Amt: 54321,
Input: inputSigners,
}
input := lux.TransferableInput{
UTXOID: utxoID,
Asset: asset,
In: &fxInput,
}
baseTx := lux.BaseTx{
NetworkID: constants.UnitTestID,
BlockchainID: chainID,
Outs: []*lux.TransferableOutput{
&output,
},
Ins: []*lux.TransferableInput{
&input,
},
}
cred := fxs.FxCredential{
Credential: &secp256k1fx.Credential{},
}
creds := []*fxs.FxCredential{
&cred,
}
// Override Runtime to match baseTx's NetworkID and BlockchainID
luxRT.NetworkID = constants.UnitTestID
luxRT.ChainID = chainID
backend := &Backend{
Ctx: ctx,
Runtime: luxRT,
CChainID: cChainID,
Config: &feeConfig,
Fxs: []*fxs.ParsedFx{
{
ID: secp256k1fx.ID,
Fx: fx,
},
},
FeeAssetID: feeAssetID,
}
tests := []struct {
name string
txFunc func() *txs.Tx
err error
}{
{
name: "valid",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: creds,
}
},
err: nil,
},
{
name: "wrong networkID",
txFunc: func() *txs.Tx {
baseTx := baseTx
baseTx.NetworkID++
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: creds,
}
},
err: lux.ErrWrongNetworkID,
},
{
name: "wrong chainID",
txFunc: func() *txs.Tx {
baseTx := baseTx
baseTx.BlockchainID = ids.GenerateTestID()
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: creds,
}
},
err: lux.ErrWrongChainID,
},
{
name: "memo too large",
txFunc: func() *txs.Tx {
baseTx := baseTx
baseTx.Memo = make([]byte, lux.MaxMemoSize+1)
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: creds,
}
},
err: lux.ErrMemoTooLarge,
},
{
name: "invalid output",
txFunc: func() *txs.Tx {
output := output
output.Out = &secp256k1fx.TransferOutput{
Amt: 0,
OutputOwners: outputOwners,
}
baseTx := baseTx
baseTx.Outs = []*lux.TransferableOutput{
&output,
}
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: creds,
}
},
err: secp256k1fx.ErrNoValueOutput,
},
{
name: "unsorted outputs",
txFunc: func() *txs.Tx {
output0 := output
output0.Out = &secp256k1fx.TransferOutput{
Amt: 1,
OutputOwners: outputOwners,
}
output1 := output
output1.Out = &secp256k1fx.TransferOutput{
Amt: 2,
OutputOwners: outputOwners,
}
outputs := []*lux.TransferableOutput{
&output0,
&output1,
}
lux.SortTransferableOutputs(outputs)
outputs[0], outputs[1] = outputs[1], outputs[0]
baseTx := baseTx
baseTx.Outs = outputs
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: creds,
}
},
err: lux.ErrOutputsNotSorted,
},
{
name: "invalid input",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: 0,
Input: inputSigners,
}
baseTx := baseTx
baseTx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: creds,
}
},
err: secp256k1fx.ErrNoValueInput,
},
{
name: "duplicate inputs",
txFunc: func() *txs.Tx {
baseTx := baseTx
baseTx.Ins = []*lux.TransferableInput{
&input,
&input,
}
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: []*fxs.FxCredential{
&cred,
&cred,
},
}
},
err: lux.ErrInputsNotSortedUnique,
},
{
name: "input overflow",
txFunc: func() *txs.Tx {
input0 := input
input0.In = &secp256k1fx.TransferInput{
Amt: 1,
Input: inputSigners,
}
input1 := input
input1.UTXOID.OutputIndex++
input1.In = &secp256k1fx.TransferInput{
Amt: math.MaxUint64,
Input: inputSigners,
}
baseTx := baseTx
baseTx.Ins = []*lux.TransferableInput{
&input0,
&input1,
}
lux.SortTransferableInputsWithSigners(baseTx.Ins, make([][]*secp256k1.PrivateKey, 2))
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: []*fxs.FxCredential{
&cred,
&cred,
},
}
},
err: safemath.ErrOverflow,
},
{
name: "output overflow",
txFunc: func() *txs.Tx {
output0 := output
output0.Out = &secp256k1fx.TransferOutput{
Amt: 1,
OutputOwners: outputOwners,
}
output1 := output
output1.Out = &secp256k1fx.TransferOutput{
Amt: math.MaxUint64,
OutputOwners: outputOwners,
}
outputs := []*lux.TransferableOutput{
&output0,
&output1,
}
lux.SortTransferableOutputs(outputs)
baseTx := baseTx
baseTx.Outs = outputs
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: creds,
}
},
err: safemath.ErrOverflow,
},
{
name: "insufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: 1,
Input: inputSigners,
}
baseTx := baseTx
baseTx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: creds,
}
},
err: lux.ErrInsufficientFunds,
},
{
name: "invalid credential",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: []*fxs.FxCredential{{
Credential: (*secp256k1fx.Credential)(nil),
}},
}
},
err: secp256k1fx.ErrNilCredential,
},
{
name: "wrong number of credentials",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
}
},
err: errWrongNumberOfCredentials,
},
{
name: "barely sufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: fxOutput.Amt + feeConfig.TxFee,
Input: inputSigners,
}
baseTx := baseTx
baseTx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: creds,
}
},
err: nil,
},
{
name: "barely insufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: fxOutput.Amt + feeConfig.TxFee - 1,
Input: inputSigners,
}
baseTx := baseTx
baseTx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &txs.BaseTx{BaseTx: baseTx},
Creds: creds,
}
},
err: lux.ErrInsufficientFunds,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tx := test.txFunc()
verifier := &SyntacticVerifier{
Backend: backend,
Tx: tx,
}
err := tx.Unsigned.Visit(verifier)
require.ErrorIs(t, err, test.err)
})
}
}
func TestSyntacticVerifierCreateAssetTx(t *testing.T) {
chainID := consensustest.XChainID
cChainID := ids.GenerateTestID()
luxRT := consensustest.Runtime(t, chainID)
ctx := context.Background()
fx := &secp256k1fx.Fx{}
_, err := txs.NewParser(
[]fxs.Fx{
fx,
},
)
require.NoError(t, err)
feeAssetID := ids.GenerateTestID()
asset := lux.Asset{
ID: feeAssetID,
}
outputOwners := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{keys[0].PublicKey().Address()},
}
fxOutput := secp256k1fx.TransferOutput{
Amt: 12345,
OutputOwners: outputOwners,
}
output := lux.TransferableOutput{
Asset: asset,
Out: &fxOutput,
}
inputTxID := ids.GenerateTestID()
utxoID := lux.UTXOID{
TxID: inputTxID,
OutputIndex: 0,
}
inputSigners := secp256k1fx.Input{
SigIndices: []uint32{2},
}
fxInput := secp256k1fx.TransferInput{
Amt: 54321,
Input: inputSigners,
}
input := lux.TransferableInput{
UTXOID: utxoID,
Asset: asset,
In: &fxInput,
}
baseTx := lux.BaseTx{
NetworkID: constants.UnitTestID,
BlockchainID: chainID,
Outs: []*lux.TransferableOutput{
&output,
},
Ins: []*lux.TransferableInput{
&input,
},
}
initialState := txs.InitialState{
FxIndex: 0,
Outs: []verify.State{
&fxOutput,
},
}
tx := txs.CreateAssetTx{
BaseTx: txs.BaseTx{BaseTx: baseTx},
Name: "NormalName",
Symbol: "TICK",
Denomination: byte(2),
States: []*txs.InitialState{
&initialState,
},
}
cred := fxs.FxCredential{
Credential: &secp256k1fx.Credential{},
}
creds := []*fxs.FxCredential{
&cred,
}
// Override Runtime to match baseTx's NetworkID and BlockchainID
luxRT.NetworkID = constants.UnitTestID
luxRT.ChainID = chainID
backend := &Backend{
Ctx: ctx,
Runtime: luxRT,
CChainID: cChainID,
Config: &feeConfig,
Fxs: []*fxs.ParsedFx{
{
ID: secp256k1fx.ID,
Fx: fx,
},
},
FeeAssetID: feeAssetID,
}
tests := []struct {
name string
txFunc func() *txs.Tx
err error
}{
{
name: "valid",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: nil,
},
{
name: "name too short",
txFunc: func() *txs.Tx {
tx := tx
tx.Name = ""
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errNameTooShort,
},
{
name: "name too long",
txFunc: func() *txs.Tx {
tx := tx
tx.Name = strings.Repeat("X", maxNameLen+1)
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errNameTooLong,
},
{
name: "symbol too short",
txFunc: func() *txs.Tx {
tx := tx
tx.Symbol = ""
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errSymbolTooShort,
},
{
name: "symbol too long",
txFunc: func() *txs.Tx {
tx := tx
tx.Symbol = strings.Repeat("X", maxSymbolLen+1)
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errSymbolTooLong,
},
{
name: "no feature extensions",
txFunc: func() *txs.Tx {
tx := tx
tx.States = nil
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errNoFxs,
},
{
name: "denomination too large",
txFunc: func() *txs.Tx {
tx := tx
tx.Denomination = maxDenomination + 1
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errDenominationTooLarge,
},
{
name: "bounding whitespace in name",
txFunc: func() *txs.Tx {
tx := tx
tx.Name = " LUX"
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errUnexpectedWhitespace,
},
{
name: "illegal character in name",
txFunc: func() *txs.Tx {
tx := tx
tx.Name = "h8*32"
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errIllegalNameCharacter,
},
{
name: "illegal character in ticker",
txFunc: func() *txs.Tx {
tx := tx
tx.Symbol = "H I"
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errIllegalSymbolCharacter,
},
{
name: "wrong networkID",
txFunc: func() *txs.Tx {
tx := tx
tx.NetworkID++
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrWrongNetworkID,
},
{
name: "wrong chainID",
txFunc: func() *txs.Tx {
tx := tx
tx.BlockchainID = ids.GenerateTestID()
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrWrongChainID,
},
{
name: "memo too large",
txFunc: func() *txs.Tx {
tx := tx
tx.Memo = make([]byte, lux.MaxMemoSize+1)
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrMemoTooLarge,
},
{
name: "invalid output",
txFunc: func() *txs.Tx {
output := output
output.Out = &secp256k1fx.TransferOutput{
Amt: 0,
OutputOwners: outputOwners,
}
tx := tx
tx.Outs = []*lux.TransferableOutput{
&output,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: secp256k1fx.ErrNoValueOutput,
},
{
name: "unsorted outputs",
txFunc: func() *txs.Tx {
output0 := output
output0.Out = &secp256k1fx.TransferOutput{
Amt: 1,
OutputOwners: outputOwners,
}
output1 := output
output1.Out = &secp256k1fx.TransferOutput{
Amt: 2,
OutputOwners: outputOwners,
}
outputs := []*lux.TransferableOutput{
&output0,
&output1,
}
lux.SortTransferableOutputs(outputs)
outputs[0], outputs[1] = outputs[1], outputs[0]
tx := tx
tx.Outs = outputs
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrOutputsNotSorted,
},
{
name: "invalid input",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: 0,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: secp256k1fx.ErrNoValueInput,
},
{
name: "duplicate inputs",
txFunc: func() *txs.Tx {
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{
&cred,
&cred,
},
}
},
err: lux.ErrInputsNotSortedUnique,
},
{
name: "input overflow",
txFunc: func() *txs.Tx {
input0 := input
input0.In = &secp256k1fx.TransferInput{
Amt: 1,
Input: inputSigners,
}
input1 := input
input1.UTXOID.OutputIndex++
input1.In = &secp256k1fx.TransferInput{
Amt: math.MaxUint64,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input0,
&input1,
}
lux.SortTransferableInputsWithSigners(baseTx.Ins, make([][]*secp256k1.PrivateKey, 2))
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{
&cred,
&cred,
},
}
},
err: safemath.ErrOverflow,
},
{
name: "output overflow",
txFunc: func() *txs.Tx {
output0 := output
output0.Out = &secp256k1fx.TransferOutput{
Amt: 1,
OutputOwners: outputOwners,
}
output1 := output
output1.Out = &secp256k1fx.TransferOutput{
Amt: math.MaxUint64,
OutputOwners: outputOwners,
}
outputs := []*lux.TransferableOutput{
&output0,
&output1,
}
lux.SortTransferableOutputs(outputs)
tx := tx
tx.Outs = outputs
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: safemath.ErrOverflow,
},
{
name: "insufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: 1,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrInsufficientFunds,
},
{
name: "invalid nil state",
txFunc: func() *txs.Tx {
tx := tx
tx.States = []*txs.InitialState{
nil,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: txs.ErrNilInitialState,
},
{
name: "invalid fx",
txFunc: func() *txs.Tx {
initialState := initialState
initialState.FxIndex = 1
tx := tx
tx.States = []*txs.InitialState{
&initialState,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: txs.ErrUnknownFx,
},
{
name: "invalid nil state output",
txFunc: func() *txs.Tx {
initialState := initialState
initialState.Outs = []verify.State{
nil,
}
tx := tx
tx.States = []*txs.InitialState{
&initialState,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: txs.ErrNilFxOutput,
},
{
name: "invalid state output",
txFunc: func() *txs.Tx {
fxOutput := fxOutput
fxOutput.Amt = 0
initialState := initialState
initialState.Outs = []verify.State{
&fxOutput,
}
tx := tx
tx.States = []*txs.InitialState{
&initialState,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: secp256k1fx.ErrNoValueOutput,
},
{
name: "unsorted initial state",
txFunc: func() *txs.Tx {
fxOutput0 := fxOutput
fxOutput1 := fxOutput
fxOutput1.Amt++
initialState := initialState
initialState.Outs = []verify.State{
&fxOutput0,
&fxOutput1,
}
initialState.Sort()
initialState.Outs[0], initialState.Outs[1] = initialState.Outs[1], initialState.Outs[0]
tx := tx
tx.States = []*txs.InitialState{
&initialState,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: txs.ErrOutputsNotSorted,
},
{
name: "non-unique initial states",
txFunc: func() *txs.Tx {
tx := tx
tx.States = []*txs.InitialState{
&initialState,
&initialState,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errInitialStatesNotSortedUnique,
},
{
name: "invalid credential",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{{
Credential: (*secp256k1fx.Credential)(nil),
}},
}
},
err: secp256k1fx.ErrNilCredential,
},
{
name: "wrong number of credentials",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
}
},
err: errWrongNumberOfCredentials,
},
{
name: "barely sufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: fxOutput.Amt + feeConfig.CreateAssetTxFee,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: nil,
},
{
name: "barely insufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: fxOutput.Amt + feeConfig.CreateAssetTxFee - 1,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrInsufficientFunds,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tx := test.txFunc()
verifier := &SyntacticVerifier{
Backend: backend,
Tx: tx,
}
err := tx.Unsigned.Visit(verifier)
require.ErrorIs(t, err, test.err)
})
}
}
func TestSyntacticVerifierOperationTx(t *testing.T) {
chainID := consensustest.XChainID
cChainID := ids.GenerateTestID()
luxRT := consensustest.Runtime(t, chainID)
ctx := context.Background()
// Override Runtime to match baseTx's NetworkID and BlockchainID
luxRT.NetworkID = constants.UnitTestID
fx := &secp256k1fx.Fx{}
_, err := txs.NewParser(
[]fxs.Fx{
fx,
},
)
require.NoError(t, err)
feeAssetID := ids.GenerateTestID()
asset := lux.Asset{
ID: feeAssetID,
}
outputOwners := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{keys[0].PublicKey().Address()},
}
fxOutput := secp256k1fx.TransferOutput{
Amt: 12345,
OutputOwners: outputOwners,
}
output := lux.TransferableOutput{
Asset: asset,
Out: &fxOutput,
}
inputTxID := ids.GenerateTestID()
utxoID := lux.UTXOID{
TxID: inputTxID,
OutputIndex: 0,
}
inputSigners := secp256k1fx.Input{
SigIndices: []uint32{2},
}
fxInput := secp256k1fx.TransferInput{
Amt: 54321,
Input: inputSigners,
}
input := lux.TransferableInput{
UTXOID: utxoID,
Asset: asset,
In: &fxInput,
}
baseTx := lux.BaseTx{
NetworkID: constants.UnitTestID,
BlockchainID: chainID,
Ins: []*lux.TransferableInput{
&input,
},
Outs: []*lux.TransferableOutput{
&output,
},
}
opUTXOID := utxoID
opUTXOID.OutputIndex++
fxOp := secp256k1fx.MintOperation{
MintInput: inputSigners,
MintOutput: secp256k1fx.MintOutput{
OutputOwners: outputOwners,
},
TransferOutput: fxOutput,
}
op := txs.Operation{
Asset: asset,
UTXOIDs: []*lux.UTXOID{
&opUTXOID,
},
Op: &fxOp,
}
tx := txs.OperationTx{
BaseTx: txs.BaseTx{BaseTx: baseTx},
Ops: []*txs.Operation{
&op,
},
}
cred := fxs.FxCredential{
Credential: &secp256k1fx.Credential{},
}
creds := []*fxs.FxCredential{
&cred,
&cred,
}
backend := &Backend{
Ctx: ctx,
Runtime: luxRT,
CChainID: cChainID,
Config: &feeConfig,
Fxs: []*fxs.ParsedFx{
{
ID: secp256k1fx.ID,
Fx: fx,
},
},
FeeAssetID: feeAssetID,
}
tests := []struct {
name string
txFunc func() *txs.Tx
err error
}{
{
name: "valid",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: nil,
},
{
name: "no operation",
txFunc: func() *txs.Tx {
tx := tx
tx.Ops = nil
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errNoOperations,
},
{
name: "wrong networkID",
txFunc: func() *txs.Tx {
tx := tx
tx.NetworkID++
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrWrongNetworkID,
},
{
name: "wrong chainID",
txFunc: func() *txs.Tx {
tx := tx
tx.BlockchainID = ids.GenerateTestID()
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrWrongChainID,
},
{
name: "memo too large",
txFunc: func() *txs.Tx {
tx := tx
tx.Memo = make([]byte, lux.MaxMemoSize+1)
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrMemoTooLarge,
},
{
name: "invalid output",
txFunc: func() *txs.Tx {
output := output
output.Out = &secp256k1fx.TransferOutput{
Amt: 0,
OutputOwners: outputOwners,
}
tx := tx
tx.Outs = []*lux.TransferableOutput{
&output,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: secp256k1fx.ErrNoValueOutput,
},
{
name: "unsorted outputs",
txFunc: func() *txs.Tx {
output0 := output
output0.Out = &secp256k1fx.TransferOutput{
Amt: 1,
OutputOwners: outputOwners,
}
output1 := output
output1.Out = &secp256k1fx.TransferOutput{
Amt: 2,
OutputOwners: outputOwners,
}
outputs := []*lux.TransferableOutput{
&output0,
&output1,
}
lux.SortTransferableOutputs(outputs)
outputs[0], outputs[1] = outputs[1], outputs[0]
tx := tx
tx.Outs = outputs
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrOutputsNotSorted,
},
{
name: "invalid input",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: 0,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: secp256k1fx.ErrNoValueInput,
},
{
name: "duplicate inputs",
txFunc: func() *txs.Tx {
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{
&cred,
&cred,
},
}
},
err: lux.ErrInputsNotSortedUnique,
},
{
name: "input overflow",
txFunc: func() *txs.Tx {
input0 := input
input0.In = &secp256k1fx.TransferInput{
Amt: 1,
Input: inputSigners,
}
input1 := input
input1.UTXOID.OutputIndex++
input1.In = &secp256k1fx.TransferInput{
Amt: math.MaxUint64,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input0,
&input1,
}
lux.SortTransferableInputsWithSigners(tx.Ins, make([][]*secp256k1.PrivateKey, 2))
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{
&cred,
&cred,
},
}
},
err: safemath.ErrOverflow,
},
{
name: "output overflow",
txFunc: func() *txs.Tx {
output := output
output.Out = &secp256k1fx.TransferOutput{
Amt: math.MaxUint64,
OutputOwners: outputOwners,
}
outputs := []*lux.TransferableOutput{
&output,
}
lux.SortTransferableOutputs(outputs)
tx := tx
tx.Outs = outputs
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: safemath.ErrOverflow,
},
{
name: "insufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: 1,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrInsufficientFunds,
},
{
name: "invalid nil op",
txFunc: func() *txs.Tx {
tx := tx
tx.Ops = []*txs.Operation{
nil,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: txs.ErrNilOperation,
},
{
name: "invalid nil fx op",
txFunc: func() *txs.Tx {
op := op
op.Op = nil
tx := tx
tx.Ops = []*txs.Operation{
&op,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: txs.ErrNilFxOperation,
},
{
name: "invalid duplicated op UTXOs",
txFunc: func() *txs.Tx {
op := op
op.UTXOIDs = []*lux.UTXOID{
&opUTXOID,
&opUTXOID,
}
tx := tx
tx.Ops = []*txs.Operation{
&op,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: txs.ErrNotSortedAndUniqueUTXOIDs,
},
{
name: "invalid duplicated UTXOs across ops",
txFunc: func() *txs.Tx {
newOp := op
op.Asset.ID = ids.GenerateTestID()
tx := tx
tx.Ops = []*txs.Operation{
&op,
&newOp,
}
txs.SortOperations(tx.Ops)
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errDoubleSpend,
},
{
name: "invalid duplicated op",
txFunc: func() *txs.Tx {
op := op
op.UTXOIDs = nil
tx := tx
tx.Ops = []*txs.Operation{
&op,
&op,
}
txs.SortOperations(tx.Ops)
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errOperationsNotSortedUnique,
},
{
name: "invalid credential",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{{
Credential: (*secp256k1fx.Credential)(nil),
}},
}
},
err: secp256k1fx.ErrNilCredential,
},
{
name: "wrong number of credentials",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
}
},
err: errWrongNumberOfCredentials,
},
{
name: "barely sufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: fxOutput.Amt + feeConfig.TxFee,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: nil,
},
{
name: "barely insufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: fxOutput.Amt + feeConfig.TxFee - 1,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrInsufficientFunds,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tx := test.txFunc()
verifier := &SyntacticVerifier{
Backend: backend,
Tx: tx,
}
err := tx.Unsigned.Visit(verifier)
require.ErrorIs(t, err, test.err)
})
}
}
func TestSyntacticVerifierImportTx(t *testing.T) {
ctx := context.Background()
chainID := consensustest.XChainID
cChainID := ids.GenerateTestID()
fx := &secp256k1fx.Fx{}
_, err := txs.NewParser(
[]fxs.Fx{
fx,
},
)
require.NoError(t, err)
feeAssetID := ids.GenerateTestID()
asset := lux.Asset{
ID: feeAssetID,
}
outputOwners := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{keys[0].PublicKey().Address()},
}
fxOutput := secp256k1fx.TransferOutput{
Amt: 12345,
OutputOwners: outputOwners,
}
output := lux.TransferableOutput{
Asset: asset,
Out: &fxOutput,
}
inputTxID := ids.GenerateTestID()
utxoID := lux.UTXOID{
TxID: inputTxID,
OutputIndex: 0,
}
inputSigners := secp256k1fx.Input{
SigIndices: []uint32{2},
}
fxInput := secp256k1fx.TransferInput{
Amt: 54321,
Input: inputSigners,
}
input := lux.TransferableInput{
UTXOID: utxoID,
Asset: asset,
In: &fxInput,
}
baseTx := lux.BaseTx{
NetworkID: constants.UnitTestID,
BlockchainID: chainID,
Outs: []*lux.TransferableOutput{
&output,
},
}
tx := txs.ImportTx{
BaseTx: txs.BaseTx{BaseTx: baseTx},
SourceChain: cChainID,
ImportedIns: []*lux.TransferableInput{
&input,
},
}
cred := fxs.FxCredential{
Credential: &secp256k1fx.Credential{},
}
creds := []*fxs.FxCredential{
&cred,
}
luxRT := consensustest.Runtime(t, chainID)
// Override Runtime to match baseTx's NetworkID
luxRT.NetworkID = constants.UnitTestID
backend := &Backend{
Ctx: ctx,
Runtime: luxRT,
CChainID: cChainID,
Config: &feeConfig,
Fxs: []*fxs.ParsedFx{
{
ID: secp256k1fx.ID,
Fx: fx,
},
},
FeeAssetID: feeAssetID,
}
tests := []struct {
name string
txFunc func() *txs.Tx
err error
}{
{
name: "valid",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: nil,
},
{
name: "no imported inputs",
txFunc: func() *txs.Tx {
tx := tx
tx.ImportedIns = nil
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errNoImportInputs,
},
{
name: "wrong networkID",
txFunc: func() *txs.Tx {
tx := tx
tx.NetworkID++
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrWrongNetworkID,
},
{
name: "wrong chainID",
txFunc: func() *txs.Tx {
tx := tx
tx.BlockchainID = ids.GenerateTestID()
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrWrongChainID,
},
{
name: "memo too large",
txFunc: func() *txs.Tx {
tx := tx
tx.Memo = make([]byte, lux.MaxMemoSize+1)
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrMemoTooLarge,
},
{
name: "invalid output",
txFunc: func() *txs.Tx {
output := output
output.Out = &secp256k1fx.TransferOutput{
Amt: 0,
OutputOwners: outputOwners,
}
tx := tx
tx.Outs = []*lux.TransferableOutput{
&output,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: secp256k1fx.ErrNoValueOutput,
},
{
name: "unsorted outputs",
txFunc: func() *txs.Tx {
output0 := output
output0.Out = &secp256k1fx.TransferOutput{
Amt: 1,
OutputOwners: outputOwners,
}
output1 := output
output1.Out = &secp256k1fx.TransferOutput{
Amt: 2,
OutputOwners: outputOwners,
}
outputs := []*lux.TransferableOutput{
&output0,
&output1,
}
lux.SortTransferableOutputs(outputs)
outputs[0], outputs[1] = outputs[1], outputs[0]
tx := tx
tx.Outs = outputs
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrOutputsNotSorted,
},
{
name: "invalid input",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: 0,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: secp256k1fx.ErrNoValueInput,
},
{
name: "duplicate inputs",
txFunc: func() *txs.Tx {
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{
&cred,
&cred,
&cred,
},
}
},
err: lux.ErrInputsNotSortedUnique,
},
{
name: "duplicate imported inputs",
txFunc: func() *txs.Tx {
tx := tx
tx.ImportedIns = []*lux.TransferableInput{
&input,
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{
&cred,
&cred,
},
}
},
err: lux.ErrInputsNotSortedUnique,
},
{
name: "input overflow",
txFunc: func() *txs.Tx {
input0 := input
input0.In = &secp256k1fx.TransferInput{
Amt: 1,
Input: inputSigners,
}
input1 := input
input1.UTXOID.OutputIndex++
input1.In = &secp256k1fx.TransferInput{
Amt: math.MaxUint64,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input0,
&input1,
}
lux.SortTransferableInputsWithSigners(tx.Ins, make([][]*secp256k1.PrivateKey, 2))
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{
&cred,
&cred,
},
}
},
err: safemath.ErrOverflow,
},
{
name: "output overflow",
txFunc: func() *txs.Tx {
output := output
output.Out = &secp256k1fx.TransferOutput{
Amt: math.MaxUint64,
OutputOwners: outputOwners,
}
outputs := []*lux.TransferableOutput{
&output,
}
lux.SortTransferableOutputs(outputs)
tx := tx
tx.Outs = outputs
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: safemath.ErrOverflow,
},
{
name: "insufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: 1,
Input: inputSigners,
}
tx := tx
tx.ImportedIns = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrInsufficientFunds,
},
{
name: "invalid credential",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{{
Credential: (*secp256k1fx.Credential)(nil),
}},
}
},
err: secp256k1fx.ErrNilCredential,
},
{
name: "wrong number of credentials",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
}
},
err: errWrongNumberOfCredentials,
},
{
name: "barely sufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: fxOutput.Amt + feeConfig.TxFee,
Input: inputSigners,
}
tx := tx
tx.ImportedIns = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: nil,
},
{
name: "barely insufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: fxOutput.Amt + feeConfig.TxFee - 1,
Input: inputSigners,
}
tx := tx
tx.ImportedIns = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrInsufficientFunds,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tx := test.txFunc()
verifier := &SyntacticVerifier{
Backend: backend,
Tx: tx,
}
err := tx.Unsigned.Visit(verifier)
require.ErrorIs(t, err, test.err)
})
}
}
func TestSyntacticVerifierExportTx(t *testing.T) {
ctx := context.Background()
chainID := consensustest.XChainID
cChainID := ids.GenerateTestID()
fx := &secp256k1fx.Fx{}
_, err := txs.NewParser(
[]fxs.Fx{
fx,
},
)
require.NoError(t, err)
feeAssetID := ids.GenerateTestID()
asset := lux.Asset{
ID: feeAssetID,
}
outputOwners := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{keys[0].PublicKey().Address()},
}
fxOutput := secp256k1fx.TransferOutput{
Amt: 12345,
OutputOwners: outputOwners,
}
output := lux.TransferableOutput{
Asset: asset,
Out: &fxOutput,
}
inputTxID := ids.GenerateTestID()
utxoID := lux.UTXOID{
TxID: inputTxID,
OutputIndex: 0,
}
inputSigners := secp256k1fx.Input{
SigIndices: []uint32{2},
}
fxInput := secp256k1fx.TransferInput{
Amt: 54321,
Input: inputSigners,
}
input := lux.TransferableInput{
UTXOID: utxoID,
Asset: asset,
In: &fxInput,
}
baseTx := lux.BaseTx{
NetworkID: constants.UnitTestID,
BlockchainID: chainID,
Ins: []*lux.TransferableInput{
&input,
},
}
tx := txs.ExportTx{
BaseTx: txs.BaseTx{BaseTx: baseTx},
DestinationChain: cChainID,
ExportedOuts: []*lux.TransferableOutput{
&output,
},
}
cred := fxs.FxCredential{
Credential: &secp256k1fx.Credential{},
}
creds := []*fxs.FxCredential{
&cred,
}
luxRT := consensustest.Runtime(t, chainID)
// Override Runtime to match baseTx's NetworkID
luxRT.NetworkID = constants.UnitTestID
backend := &Backend{
Ctx: ctx,
Runtime: luxRT,
CChainID: cChainID,
Config: &feeConfig,
Fxs: []*fxs.ParsedFx{
{
ID: secp256k1fx.ID,
Fx: fx,
},
},
FeeAssetID: feeAssetID,
}
tests := []struct {
name string
txFunc func() *txs.Tx
err error
}{
{
name: "valid",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: nil,
},
{
name: "no exported outputs",
txFunc: func() *txs.Tx {
tx := tx
tx.ExportedOuts = nil
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: errNoExportOutputs,
},
{
name: "wrong networkID",
txFunc: func() *txs.Tx {
tx := tx
tx.NetworkID++
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrWrongNetworkID,
},
{
name: "wrong chainID",
txFunc: func() *txs.Tx {
tx := tx
tx.BlockchainID = ids.GenerateTestID()
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrWrongChainID,
},
{
name: "memo too large",
txFunc: func() *txs.Tx {
tx := tx
tx.Memo = make([]byte, lux.MaxMemoSize+1)
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrMemoTooLarge,
},
{
name: "invalid output",
txFunc: func() *txs.Tx {
output := output
output.Out = &secp256k1fx.TransferOutput{
Amt: 0,
OutputOwners: outputOwners,
}
tx := tx
tx.Outs = []*lux.TransferableOutput{
&output,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: secp256k1fx.ErrNoValueOutput,
},
{
name: "unsorted outputs",
txFunc: func() *txs.Tx {
output0 := output
output0.Out = &secp256k1fx.TransferOutput{
Amt: 1,
OutputOwners: outputOwners,
}
output1 := output
output1.Out = &secp256k1fx.TransferOutput{
Amt: 2,
OutputOwners: outputOwners,
}
outputs := []*lux.TransferableOutput{
&output0,
&output1,
}
lux.SortTransferableOutputs(outputs)
outputs[0], outputs[1] = outputs[1], outputs[0]
tx := tx
tx.Outs = outputs
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrOutputsNotSorted,
},
{
name: "unsorted exported outputs",
txFunc: func() *txs.Tx {
output0 := output
output0.Out = &secp256k1fx.TransferOutput{
Amt: 1,
OutputOwners: outputOwners,
}
output1 := output
output1.Out = &secp256k1fx.TransferOutput{
Amt: 2,
OutputOwners: outputOwners,
}
outputs := []*lux.TransferableOutput{
&output0,
&output1,
}
lux.SortTransferableOutputs(outputs)
outputs[0], outputs[1] = outputs[1], outputs[0]
tx := tx
tx.ExportedOuts = outputs
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrOutputsNotSorted,
},
{
name: "invalid input",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: 0,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: secp256k1fx.ErrNoValueInput,
},
{
name: "duplicate inputs",
txFunc: func() *txs.Tx {
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{
&cred,
&cred,
},
}
},
err: lux.ErrInputsNotSortedUnique,
},
{
name: "input overflow",
txFunc: func() *txs.Tx {
input0 := input
input0.In = &secp256k1fx.TransferInput{
Amt: 1,
Input: inputSigners,
}
input1 := input
input1.UTXOID.OutputIndex++
input1.In = &secp256k1fx.TransferInput{
Amt: math.MaxUint64,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input0,
&input1,
}
lux.SortTransferableInputsWithSigners(tx.Ins, make([][]*secp256k1.PrivateKey, 2))
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{
&cred,
&cred,
},
}
},
err: safemath.ErrOverflow,
},
{
name: "output overflow",
txFunc: func() *txs.Tx {
output := output
output.Out = &secp256k1fx.TransferOutput{
Amt: math.MaxUint64,
OutputOwners: outputOwners,
}
outputs := []*lux.TransferableOutput{
&output,
}
lux.SortTransferableOutputs(outputs)
tx := tx
tx.Outs = outputs
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: safemath.ErrOverflow,
},
{
name: "insufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: 1,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrInsufficientFunds,
},
{
name: "invalid credential",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
Creds: []*fxs.FxCredential{{
Credential: (*secp256k1fx.Credential)(nil),
}},
}
},
err: secp256k1fx.ErrNilCredential,
},
{
name: "wrong number of credentials",
txFunc: func() *txs.Tx {
return &txs.Tx{
Unsigned: &tx,
}
},
err: errWrongNumberOfCredentials,
},
{
name: "barely sufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: fxOutput.Amt + feeConfig.TxFee,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: nil,
},
{
name: "barely insufficient funds",
txFunc: func() *txs.Tx {
input := input
input.In = &secp256k1fx.TransferInput{
Amt: fxOutput.Amt + feeConfig.TxFee - 1,
Input: inputSigners,
}
tx := tx
tx.Ins = []*lux.TransferableInput{
&input,
}
return &txs.Tx{
Unsigned: &tx,
Creds: creds,
}
},
err: lux.ErrInsufficientFunds,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tx := test.txFunc()
verifier := &SyntacticVerifier{
Backend: backend,
Tx: tx,
}
err := tx.Unsigned.Visit(verifier)
require.ErrorIs(t, err, test.err)
})
}
}