Restore+convert remaining txs tests to struct-is-wire + dispatch-safety guard

Converts the per-type P-chain tx tests to native New*Tx + accessor methods +
roundTrip/SyntacticVerify (agents, verified): add_validator, add_chain_validator,
create_blockchain, disable/increase/register/set_weight/remove_chain/
transfer_ownership L1, add_permissionless_delegator, transform_chain. Every
error-path sentinel preserved by driving the bad value THROUGH the constructor
(pure byte-writer). Mock-based cases (fxmock/luxmock/verifymock, impossible on
an immutable zap buffer) reproduced with REAL unspendable owners / unsorted
inputs → real sentinels (ErrOutputUnspendable, ErrInputIndicesNotSortedUnique).
Only the un-reproducible 'already verified' cached-flag case dropped per file.

NEW staker_dispatch_guard_test.go pins the interface-satisfaction invariant the
staker type-switches depend on: struct-is-wire made *AddValidatorTx satisfy both
ValidatorTx+DelegatorTx (safe — ValidatorTx checked first everywhere), but
*AddDelegatorTx must NOT satisfy ValidatorTx (verified: lacks
ValidationRewardsOwner/Shares) or delegators would mis-route. Guard fails loudly
if a future edit breaks either property.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
zeekay
2026-07-11 05:46:10 -07:00
co-authored by Hanzo Dev
parent 43cbbac511
commit 7123b7399e
12 changed files with 1343 additions and 0 deletions
@@ -0,0 +1,109 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"testing"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/secp256k1fx"
)
// TestAddChainValidatorTxSyntacticVerify pins the (deprecated) per-chain
// validator-registration invariants. Every (in)valid case is expressed by
// passing values THROUGH the NewAddChainValidatorTx constructor.
func TestAddChainValidatorTxSyntacticVerify(t *testing.T) {
require := require.New(t)
rt := consensustest.Runtime(t, ids.GenerateTestID())
weight := uint64(2022)
chain := ids.ID{'s', 'u', 'b', 'n', 'e', 't', 'I', 'D'}
goodAuth := func() *secp256k1fx.Input { return &secp256k1fx.Input{SigIndices: []uint32{0, 1}} }
validator := func(wght uint64) Validator {
return Validator{NodeID: ids.GenerateTestNodeID(), Start: 0, End: 3600, Wght: wght}
}
base := func(networkID uint32) *lux.BaseTx {
return &lux.BaseTx{NetworkID: networkID, BlockchainID: rt.ChainID}
}
// Case: signed tx is nil
var stx *Tx
require.ErrorIs(stx.SyntacticVerify(rt), ErrNilSignedTx)
// Case: unsigned tx is nil
var nilTx *AddChainValidatorTx
require.ErrorIs(nilTx.SyntacticVerify(rt), ErrNilTx)
// Case: valid tx
valid, err := NewAddChainValidatorTx(base(rt.NetworkID), validator(weight), chain, goodAuth())
require.NoError(err)
require.NoError(valid.SyntacticVerify(rt))
// Case: wrong network ID
wrongNet, err := NewAddChainValidatorTx(base(rt.NetworkID+1), validator(weight), chain, goodAuth())
require.NoError(err)
require.ErrorIs(wrongNet.SyntacticVerify(rt), lux.ErrWrongNetworkID)
// Case: specifies primary network ChainID (empty)
primaryEmpty, err := NewAddChainValidatorTx(base(rt.NetworkID), validator(weight), ids.Empty, goodAuth())
require.NoError(err)
require.ErrorIs(primaryEmpty.SyntacticVerify(rt), errAddPrimaryNetworkValidator)
// Case: no weight
noWeight, err := NewAddChainValidatorTx(base(rt.NetworkID), validator(0), chain, goodAuth())
require.NoError(err)
require.ErrorIs(noWeight.SyntacticVerify(rt), ErrWeightTooSmall)
// Case: chain auth indices not sorted+unique ([1,1])
badAuth, err := NewAddChainValidatorTx(base(rt.NetworkID), validator(weight), chain, &secp256k1fx.Input{SigIndices: []uint32{1, 1}})
require.NoError(err)
require.ErrorIs(badAuth.SyntacticVerify(rt), secp256k1fx.ErrInputIndicesNotSortedUnique)
// Case: adding to Primary Network (explicit PrimaryNetworkID)
primary, err := NewAddChainValidatorTx(base(rt.NetworkID), validator(weight), constants.PrimaryNetworkID, goodAuth())
require.NoError(err)
require.ErrorIs(primary.SyntacticVerify(rt), errAddPrimaryNetworkValidator)
}
// TestAddChainValidatorTx_RoundTrip replaces the deleted linearcodec
// Marshal/Parse golden test: the delta fields (Validator, Chain, ChainAuth)
// survive the struct-is-wire serialize→Parse path.
func TestAddChainValidatorTx_RoundTrip(t *testing.T) {
require := require.New(t)
vdr := Validator{NodeID: ids.GenerateTestNodeID(), Start: 0, End: 3600, Wght: 2022}
chain := ids.GenerateTestID()
auth := &secp256k1fx.Input{SigIndices: []uint32{0, 1}}
utx, err := NewAddChainValidatorTx(spendBase(), vdr, chain, auth)
require.NoError(err)
got := roundTrip(t, utx).(*AddChainValidatorTx)
require.Equal(vdr, got.Validator())
require.Equal(chain, got.Chain())
require.Equal(utx.ChainAuth(), got.ChainAuth())
}
func TestAddChainValidatorTxNotValidatorTx(t *testing.T) {
txIntf := any((*AddChainValidatorTx)(nil))
_, ok := txIntf.(ValidatorTx)
require.False(t, ok)
}
func TestAddChainValidatorTxNotDelegatorTx(t *testing.T) {
txIntf := any((*AddChainValidatorTx)(nil))
_, ok := txIntf.(DelegatorTx)
require.False(t, ok)
}
func TestAddChainValidatorTxNotPermissionlessStaker(t *testing.T) {
txIntf := any((*AddChainValidatorTx)(nil))
_, ok := txIntf.(PermissionlessStaker)
require.False(t, ok)
}
@@ -0,0 +1,199 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"math"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
safemath "github.com/luxfi/math"
"github.com/luxfi/utxo/secp256k1fx"
consensustest "github.com/luxfi/consensus/test/helpers"
lux "github.com/luxfi/utxo"
)
// TestAddPermissionlessDelegatorTx_RoundTrip exercises the struct-is-wire path
// for both a chain (non-primary) and a primary-network delegator: the delta
// fields round-trip through Parse. This replaces the deleted linearcodec
// golden-byte + JSON serialization tests.
func TestAddPermissionlessDelegatorTx_RoundTrip(t *testing.T) {
require := require.New(t)
assetID := ids.GenerateTestID()
stakeOuts := []*lux.TransferableOutput{
{Asset: lux.Asset{ID: assetID}, Out: &secp256k1fx.TransferOutput{
Amt: 3,
OutputOwners: secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{ids.GenerateTestShortID()}},
}},
}
rewardsOwner := &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{ids.GenerateTestShortID()}}
vdr := Validator{NodeID: ids.GenerateTestNodeID(), Start: 1, End: 100, Wght: 3}
// Chain (non-primary) delegator.
netTx, err := NewAddPermissionlessDelegatorTx(spendBase(), vdr, ids.GenerateTestID(), stakeOuts, rewardsOwner)
require.NoError(err)
gotNet := roundTrip(t, netTx).(*AddPermissionlessDelegatorTx)
require.Equal(vdr, gotNet.Validator())
require.Equal(netTx.Chain(), gotNet.Chain())
require.Equal(stakeOuts, gotNet.StakeOuts())
require.Equal(rewardsOwner, gotNet.DelegationRewardsOwner())
// Primary-network delegator: same delta surface, primary chain id.
primTx, err := NewAddPermissionlessDelegatorTx(spendBase(), vdr, constants.PrimaryNetworkID, stakeOuts, rewardsOwner)
require.NoError(err)
gotPrim := roundTrip(t, primTx).(*AddPermissionlessDelegatorTx)
require.Equal(constants.PrimaryNetworkID, gotPrim.Chain())
require.Equal(vdr, gotPrim.Validator())
require.Equal(stakeOuts, gotPrim.StakeOuts())
require.Equal(rewardsOwner, gotPrim.DelegationRewardsOwner())
}
func TestAddPermissionlessDelegatorTxSyntacticVerify(t *testing.T) {
rt := consensustest.Runtime(t, ids.GenerateTestID())
// Local builders — struct-is-wire has no post-hoc field mutation, so every
// (in)valid case is expressed by passing values THROUGH the constructor.
validBase := func() *lux.BaseTx {
return &lux.BaseTx{NetworkID: rt.NetworkID, BlockchainID: rt.ChainID}
}
invalidBase := func() *lux.BaseTx {
return &lux.BaseTx{NetworkID: 0, BlockchainID: rt.ChainID} // wrong networkID
}
goodOwner := func() *secp256k1fx.OutputOwners {
return &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{ids.GenerateTestShortID()}}
}
out := func(assetID ids.ID, amt uint64) *lux.TransferableOutput {
return &lux.TransferableOutput{Asset: lux.Asset{ID: assetID}, Out: &secp256k1fx.TransferOutput{Amt: amt}}
}
tests := []struct {
name string
build func(t *testing.T) *AddPermissionlessDelegatorTx
err error
}{
{
name: "nil tx",
build: func(*testing.T) *AddPermissionlessDelegatorTx { return nil },
err: ErrNilTx,
},
{
name: "no provided stake",
build: func(t *testing.T) *AddPermissionlessDelegatorTx {
tx, err := NewAddPermissionlessDelegatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID()}, ids.GenerateTestID(), nil, goodOwner())
require.NoError(t, err)
return tx
},
err: errNoStake,
},
{
name: "invalid BaseTx",
build: func(t *testing.T) *AddPermissionlessDelegatorTx {
tx, err := NewAddPermissionlessDelegatorTx(invalidBase(), Validator{NodeID: ids.GenerateTestNodeID()}, ids.GenerateTestID(), []*lux.TransferableOutput{out(ids.GenerateTestID(), 1)}, goodOwner())
require.NoError(t, err)
return tx
},
err: lux.ErrWrongNetworkID,
},
{
// Was: fxmock owner returns errCustom. Reproduced with a real
// unspendable owner (Threshold > #Addrs) → ErrOutputUnspendable.
name: "invalid rewards owner",
build: func(t *testing.T) *AddPermissionlessDelegatorTx {
badOwner := &secp256k1fx.OutputOwners{Threshold: 1}
tx, err := NewAddPermissionlessDelegatorTx(validBase(), Validator{Wght: 1}, ids.GenerateTestID(), []*lux.TransferableOutput{out(ids.GenerateTestID(), 1)}, badOwner)
require.NoError(t, err)
return tx
},
err: secp256k1fx.ErrOutputUnspendable,
},
{
// Was: luxmock stake-out Verify errCustom. Reproduced with a real
// unspendable stake output → ErrOutputUnspendable.
name: "invalid stake output",
build: func(t *testing.T) *AddPermissionlessDelegatorTx {
badOut := &lux.TransferableOutput{Asset: lux.Asset{ID: ids.GenerateTestID()}, Out: &secp256k1fx.TransferOutput{Amt: 1, OutputOwners: secp256k1fx.OutputOwners{Threshold: 1}}}
tx, err := NewAddPermissionlessDelegatorTx(validBase(), Validator{Wght: 1}, ids.GenerateTestID(), []*lux.TransferableOutput{badOut}, goodOwner())
require.NoError(t, err)
return tx
},
err: secp256k1fx.ErrOutputUnspendable,
},
{
name: "multiple staked assets",
build: func(t *testing.T) *AddPermissionlessDelegatorTx {
tx, err := NewAddPermissionlessDelegatorTx(validBase(), Validator{Wght: 1}, ids.GenerateTestID(), []*lux.TransferableOutput{out(ids.GenerateTestID(), 1), out(ids.GenerateTestID(), 1)}, goodOwner())
require.NoError(t, err)
return tx
},
err: errMultipleStakedAssets,
},
{
name: "stake not sorted",
build: func(t *testing.T) *AddPermissionlessDelegatorTx {
assetID := ids.GenerateTestID()
tx, err := NewAddPermissionlessDelegatorTx(validBase(), Validator{Wght: 1}, ids.GenerateTestID(), []*lux.TransferableOutput{out(assetID, 2), out(assetID, 1)}, goodOwner())
require.NoError(t, err)
return tx
},
err: errOutputsNotSorted,
},
{
name: "stake overflow",
build: func(t *testing.T) *AddPermissionlessDelegatorTx {
assetID := ids.GenerateTestID()
tx, err := NewAddPermissionlessDelegatorTx(validBase(), Validator{NodeID: ids.GenerateTestNodeID(), Wght: 1}, ids.GenerateTestID(), []*lux.TransferableOutput{out(assetID, math.MaxUint64), out(assetID, 2)}, goodOwner())
require.NoError(t, err)
return tx
},
err: safemath.ErrOverflow,
},
{
name: "weight mismatch",
build: func(t *testing.T) *AddPermissionlessDelegatorTx {
assetID := ids.GenerateTestID()
tx, err := NewAddPermissionlessDelegatorTx(validBase(), Validator{Wght: 1}, ids.GenerateTestID(), []*lux.TransferableOutput{out(assetID, 1), out(assetID, 1)}, goodOwner())
require.NoError(t, err)
return tx
},
err: errDelegatorWeightMismatch,
},
{
name: "valid net validator",
build: func(t *testing.T) *AddPermissionlessDelegatorTx {
assetID := ids.GenerateTestID()
tx, err := NewAddPermissionlessDelegatorTx(validBase(), Validator{Wght: 2}, ids.GenerateTestID(), []*lux.TransferableOutput{out(assetID, 1), out(assetID, 1)}, goodOwner())
require.NoError(t, err)
return tx
},
err: nil,
},
{
name: "valid primary network validator",
build: func(t *testing.T) *AddPermissionlessDelegatorTx {
assetID := ids.GenerateTestID()
tx, err := NewAddPermissionlessDelegatorTx(validBase(), Validator{Wght: 2}, constants.PrimaryNetworkID, []*lux.TransferableOutput{out(assetID, 1), out(assetID, 1)}, goodOwner())
require.NoError(t, err)
return tx
},
err: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.ErrorIs(t, tt.build(t).SyntacticVerify(rt), tt.err)
})
}
}
func TestAddPermissionlessDelegatorTxNotValidatorTx(t *testing.T) {
txIntf := any((*AddPermissionlessDelegatorTx)(nil))
_, ok := txIntf.(ValidatorTx)
require.False(t, ok)
}
+163
View File
@@ -0,0 +1,163 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/ids"
"github.com/luxfi/node/vms/platformvm/reward"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/secp256k1fx"
)
// TestAddValidatorTxSyntacticVerify pins the AddValidatorTx invariants. Struct-
// is-wire has no post-hoc field mutation, so every (in)valid case is expressed
// by passing values THROUGH the NewAddValidatorTx constructor.
func TestAddValidatorTxSyntacticVerify(t *testing.T) {
require := require.New(t)
rt := consensustest.Runtime(t, ids.GenerateTestID())
weight := uint64(2022)
goodOwner := func() *secp256k1fx.OutputOwners {
return &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{preFundedKeys[0].Address()}}
}
validator := func() Validator {
return Validator{NodeID: ids.GenerateTestNodeID(), Start: 0, End: 3600, Wght: weight}
}
stakeOut := func(assetID ids.ID, owner *secp256k1fx.OutputOwners) []*lux.TransferableOutput {
return []*lux.TransferableOutput{{
Asset: lux.Asset{ID: assetID},
Out: &secp256k1fx.TransferOutput{Amt: weight, OutputOwners: *owner},
}}
}
base := func(networkID uint32) *lux.BaseTx {
return &lux.BaseTx{NetworkID: networkID, BlockchainID: rt.ChainID}
}
// Case: signed tx is nil
var stx *Tx
require.ErrorIs(stx.SyntacticVerify(rt), ErrNilSignedTx)
// Case: unsigned tx is nil
var nilTx *AddValidatorTx
require.ErrorIs(nilTx.SyntacticVerify(rt), ErrNilTx)
// Case: valid tx
valid, err := NewAddValidatorTx(base(rt.NetworkID), validator(), stakeOut(rt.UTXOAssetID, goodOwner()), goodOwner(), reward.PercentDenominator)
require.NoError(err)
require.NoError(valid.SyntacticVerify(rt))
// Case: wrong network ID
wrongNet, err := NewAddValidatorTx(base(rt.NetworkID+1), validator(), stakeOut(rt.UTXOAssetID, goodOwner()), goodOwner(), reward.PercentDenominator)
require.NoError(err)
require.ErrorIs(wrongNet.SyntacticVerify(rt), lux.ErrWrongNetworkID)
// Case: stake owner has no addresses (unspendable stake output)
stakeNoAddr, err := NewAddValidatorTx(base(rt.NetworkID), validator(), stakeOut(rt.UTXOAssetID, &secp256k1fx.OutputOwners{Threshold: 1}), goodOwner(), reward.PercentDenominator)
require.NoError(err)
require.ErrorIs(stakeNoAddr.SyntacticVerify(rt), secp256k1fx.ErrOutputUnspendable)
// Case: rewards owner has no addresses (unspendable rewards owner)
rewardsNoAddr, err := NewAddValidatorTx(base(rt.NetworkID), validator(), stakeOut(rt.UTXOAssetID, goodOwner()), &secp256k1fx.OutputOwners{Threshold: 1}, reward.PercentDenominator)
require.NoError(err)
require.ErrorIs(rewardsNoAddr.SyntacticVerify(rt), secp256k1fx.ErrOutputUnspendable)
// Case: too many shares (1 more than max)
tooManyShares, err := NewAddValidatorTx(base(rt.NetworkID), validator(), stakeOut(rt.UTXOAssetID, goodOwner()), goodOwner(), reward.PercentDenominator+1)
require.NoError(err)
require.ErrorIs(tooManyShares.SyntacticVerify(rt), errTooManyShares)
}
func TestAddValidatorTxSyntacticVerifyNotLUX(t *testing.T) {
require := require.New(t)
rt := consensustest.Runtime(t, ids.GenerateTestID())
owner := &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{preFundedKeys[0].Address()}}
weight := uint64(2022)
validator := Validator{NodeID: ids.GenerateTestNodeID(), Start: 0, End: 3600, Wght: weight}
// Stake asset is not the primary-network UTXO asset.
stakeOuts := []*lux.TransferableOutput{{
Asset: lux.Asset{ID: ids.GenerateTestID()},
Out: &secp256k1fx.TransferOutput{Amt: weight, OutputOwners: *owner},
}}
u, err := NewAddValidatorTx(&lux.BaseTx{NetworkID: rt.NetworkID, BlockchainID: rt.ChainID}, validator, stakeOuts, owner, reward.PercentDenominator)
require.NoError(err)
require.ErrorIs(u.SyntacticVerify(rt), errStakeMustBeLUX)
}
// NOTE: the codec-era TestAddValidatorTxNotDelegatorTx (asserting
// *AddValidatorTx does NOT satisfy DelegatorTx) is intentionally dropped. It
// exercised deleted behavior: under struct-is-wire, RewardsOwner changed from a
// struct FIELD to an accessor METHOD, and RewardsOwner() is the only member
// DelegatorTx needs beyond what AddValidatorTx already exposes (UnsignedTx +
// PermissionlessStaker). So *AddValidatorTx now structurally satisfies both
// ValidatorTx and DelegatorTx. This is harmless: the sole production dispatch
// (service.go getStakerAttributes) type-switches `case ValidatorTx` BEFORE
// `case DelegatorTx`, and Go evaluates cases in order, so an AddValidatorTx is
// always routed through the ValidatorTx branch (correct rewards/shares).
// TestAddValidatorTxSyntacticVerify_ArbitraryPrimaryNetworkIDs asserts that
// AddValidatorTx — the canonical post-LP-018 add-validator-to-a-network tx —
// accepts arbitrary primary networkIDs, not just the Lux primary values
// (1/2/3/1337). A sovereign L1 IS a primary network at its own networkID;
// downstream consumers may operate primary networks at any uint32 they choose.
// The tx body has no per-chain "Chain" field, so SyntacticVerify must succeed
// against any valid primary networkID.
func TestAddValidatorTxSyntacticVerify_ArbitraryPrimaryNetworkIDs(t *testing.T) {
// Lux primaries (1/2/3/1337) plus four arbitrary synthetic IDs covering
// low / mid / high uint32 ranges, to demonstrate the tx is networkID-
// agnostic without baking any downstream value into this upstream test.
primaryNetworkIDs := []uint32{
1, // Lux mainnet
2, // Lux testnet
3, // Lux local
1337, // Lux dev/localnet
9001,
123456,
7_777_777,
4_000_000_000,
}
for _, networkID := range primaryNetworkIDs {
t.Run(formatPrimaryNetworkID(networkID), func(t *testing.T) {
require := require.New(t)
rt := consensustest.Runtime(t, ids.GenerateTestID())
rt.NetworkID = networkID // operate the primary network at an arbitrary ID
weight := uint64(2026)
owner := &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{preFundedKeys[0].Address()}}
validator := Validator{NodeID: ids.GenerateTestNodeID(), Start: 0, End: 3600, Wght: weight}
stakeOuts := []*lux.TransferableOutput{{
Asset: lux.Asset{ID: rt.UTXOAssetID},
Out: &secp256k1fx.TransferOutput{Amt: weight, OutputOwners: *owner},
}}
u, err := NewAddValidatorTx(&lux.BaseTx{NetworkID: rt.NetworkID, BlockchainID: rt.ChainID}, validator, stakeOuts, owner, reward.PercentDenominator)
require.NoError(err)
require.NoError(u.SyntacticVerify(rt))
})
}
}
func formatPrimaryNetworkID(id uint32) string {
switch id {
case 1:
return "lux-mainnet-1"
case 2:
return "lux-testnet-2"
case 3:
return "lux-local-3"
case 1337:
return "lux-dev-1337"
default:
return fmt.Sprintf("primary-networkID-%d", id)
}
}
@@ -0,0 +1,91 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"testing"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/secp256k1fx"
)
// TestUnsignedCreateChainTxVerify pins the CreateChainTx syntactic invariants.
// Struct-is-wire has no post-hoc field mutation, so every invalid case is built
// THROUGH the NewCreateChainTx constructor with the offending value baked in.
func TestUnsignedCreateChainTxVerify(t *testing.T) {
rt := consensustest.Runtime(t, ids.GenerateTestID())
validChain := ids.GenerateTestID()
auth := &secp256k1fx.Input{SigIndices: []uint32{0, 1}}
base := func() *lux.BaseTx {
return &lux.BaseTx{NetworkID: rt.NetworkID, BlockchainID: rt.ChainID}
}
tests := []struct {
description string
build func(t *testing.T) *CreateChainTx
expectedErr error
}{
{
description: "tx is nil",
build: func(*testing.T) *CreateChainTx { return nil },
expectedErr: ErrNilTx,
},
{
description: "vm ID is empty",
build: func(t *testing.T) *CreateChainTx {
tx, err := NewCreateChainTx(base(), validChain, "yeet", ids.Empty, nil, nil, auth)
require.NoError(t, err)
return tx
},
expectedErr: errInvalidVMID,
},
{
description: "chain ID is primary network ID",
build: func(t *testing.T) *CreateChainTx {
tx, err := NewCreateChainTx(base(), constants.PrimaryNetworkID, "yeet", constants.XVMID, nil, nil, auth)
require.NoError(t, err)
return tx
},
expectedErr: ErrCantValidatePrimaryNetwork,
},
{
description: "chain name is too long",
build: func(t *testing.T) *CreateChainTx {
tx, err := NewCreateChainTx(base(), validChain, string(make([]byte, MaxNameLen+1)), constants.XVMID, nil, nil, auth)
require.NoError(t, err)
return tx
},
expectedErr: errNameTooLong,
},
{
description: "chain name has invalid character",
build: func(t *testing.T) *CreateChainTx {
tx, err := NewCreateChainTx(base(), validChain, "⌘", constants.XVMID, nil, nil, auth)
require.NoError(t, err)
return tx
},
expectedErr: errIllegalNameCharacter,
},
{
description: "genesis data is too long",
build: func(t *testing.T) *CreateChainTx {
tx, err := NewCreateChainTx(base(), validChain, "yeet", constants.XVMID, nil, make([]byte, MaxGenesisLen+1), auth)
require.NoError(t, err)
return tx
},
expectedErr: errGenesisTooLong,
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
require.ErrorIs(t, test.build(t).SyntacticVerify(rt), test.expectedErr)
})
}
}
@@ -0,0 +1,65 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"testing"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/secp256k1fx"
)
// TestDisableL1ValidatorTxRoundTrip builds the tx through its constructor,
// round-trips it through the struct-is-wire path, and confirms the delta fields
// (ValidationID, DisableAuth) and the spending envelope survive encode→decode.
func TestDisableL1ValidatorTxRoundTrip(t *testing.T) {
require := require.New(t)
base := spendBase()
validationID := ids.GenerateTestID()
auth := &secp256k1fx.Input{SigIndices: []uint32{9}}
in, err := NewDisableL1ValidatorTx(base, validationID, auth)
require.NoError(err)
got := roundTrip(t, in).(*DisableL1ValidatorTx)
require.Equal(validationID, got.ValidationID())
require.Equal(auth, got.DisableAuth())
require.Equal(base.NetworkID, got.NetworkID())
require.Equal(base.BlockchainID, got.BlockchainID())
require.Equal([]byte(base.Memo), got.Memo())
}
func TestDisableL1ValidatorTxSyntacticVerify(t *testing.T) {
require := require.New(t)
rt := consensustest.Runtime(t, ids.GenerateTestID())
validationID := ids.GenerateTestID()
base := func(networkID uint32) *lux.BaseTx {
return &lux.BaseTx{NetworkID: networkID, BlockchainID: rt.ChainID}
}
// Case: nil tx
var nilTx *DisableL1ValidatorTx
require.ErrorIs(nilTx.SyntacticVerify(rt), ErrNilTx)
// Case: invalid BaseTx (wrong network ID)
wrongNet, err := NewDisableL1ValidatorTx(base(rt.NetworkID+1), validationID, &secp256k1fx.Input{})
require.NoError(err)
require.ErrorIs(wrongNet.SyntacticVerify(rt), lux.ErrWrongNetworkID)
// Case: invalid disable auth (sig indices not sorted and unique)
badAuth, err := NewDisableL1ValidatorTx(base(rt.NetworkID), validationID, &secp256k1fx.Input{SigIndices: []uint32{1, 0}})
require.NoError(err)
require.ErrorIs(badAuth.SyntacticVerify(rt), secp256k1fx.ErrInputIndicesNotSortedUnique)
// Case: passes verification
valid, err := NewDisableL1ValidatorTx(base(rt.NetworkID), validationID, &secp256k1fx.Input{})
require.NoError(err)
require.NoError(valid.SyntacticVerify(rt))
}
@@ -0,0 +1,64 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"testing"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
)
// TestIncreaseL1ValidatorBalanceTxRoundTrip builds the tx through its
// constructor, round-trips it, and confirms the delta fields (ValidationID,
// Balance) and the spending envelope survive encode→decode.
func TestIncreaseL1ValidatorBalanceTxRoundTrip(t *testing.T) {
require := require.New(t)
base := spendBase()
validationID := ids.GenerateTestID()
const balance uint64 = 0xfedcba9876543210
in, err := NewIncreaseL1ValidatorBalanceTx(base, validationID, balance)
require.NoError(err)
got := roundTrip(t, in).(*IncreaseL1ValidatorBalanceTx)
require.Equal(validationID, got.ValidationID())
require.Equal(balance, got.Balance())
require.Equal(base.NetworkID, got.NetworkID())
require.Equal(base.BlockchainID, got.BlockchainID())
require.Equal([]byte(base.Memo), got.Memo())
}
func TestIncreaseL1ValidatorBalanceTxSyntacticVerify(t *testing.T) {
require := require.New(t)
rt := consensustest.Runtime(t, ids.GenerateTestID())
validationID := ids.GenerateTestID()
base := func(networkID uint32) *lux.BaseTx {
return &lux.BaseTx{NetworkID: networkID, BlockchainID: rt.ChainID}
}
// Case: nil tx
var nilTx *IncreaseL1ValidatorBalanceTx
require.ErrorIs(nilTx.SyntacticVerify(rt), ErrNilTx)
// Case: zero balance
zeroBal, err := NewIncreaseL1ValidatorBalanceTx(base(rt.NetworkID), validationID, 0)
require.NoError(err)
require.ErrorIs(zeroBal.SyntacticVerify(rt), ErrZeroBalance)
// Case: invalid BaseTx (wrong network ID)
wrongNet, err := NewIncreaseL1ValidatorBalanceTx(base(rt.NetworkID+1), validationID, 1)
require.NoError(err)
require.ErrorIs(wrongNet.SyntacticVerify(rt), lux.ErrWrongNetworkID)
// Case: passes verification
valid, err := NewIncreaseL1ValidatorBalanceTx(base(rt.NetworkID), validationID, 1)
require.NoError(err)
require.NoError(valid.SyntacticVerify(rt))
}
@@ -0,0 +1,66 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"testing"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
)
// TestRegisterL1ValidatorTxRoundTrip builds the tx through its constructor,
// round-trips it, and confirms the delta fields (Balance, ProofOfPossession,
// Message) and the spending envelope survive encode→decode.
func TestRegisterL1ValidatorTxRoundTrip(t *testing.T) {
require := require.New(t)
base := spendBase()
const balance uint64 = 1_000_000
var pop [bls.SignatureLen]byte
for i := range pop {
pop[i] = byte(i)
}
message := []byte("message")
in, err := NewRegisterL1ValidatorTx(base, balance, pop, message)
require.NoError(err)
got := roundTrip(t, in).(*RegisterL1ValidatorTx)
require.Equal(balance, got.Balance())
require.Equal(pop, got.ProofOfPossession())
require.Equal(message, got.Message())
require.Equal(base.NetworkID, got.NetworkID())
require.Equal(base.BlockchainID, got.BlockchainID())
require.Equal([]byte(base.Memo), got.Memo())
}
func TestRegisterL1ValidatorTxSyntacticVerify(t *testing.T) {
require := require.New(t)
rt := consensustest.Runtime(t, ids.GenerateTestID())
var pop [bls.SignatureLen]byte
message := []byte("message")
base := func(networkID uint32) *lux.BaseTx {
return &lux.BaseTx{NetworkID: networkID, BlockchainID: rt.ChainID}
}
// Case: nil tx
var nilTx *RegisterL1ValidatorTx
require.ErrorIs(nilTx.SyntacticVerify(rt), ErrNilTx)
// Case: invalid BaseTx (wrong network ID)
wrongNet, err := NewRegisterL1ValidatorTx(base(rt.NetworkID+1), 1, pop, message)
require.NoError(err)
require.ErrorIs(wrongNet.SyntacticVerify(rt), lux.ErrWrongNetworkID)
// Case: passes verification
valid, err := NewRegisterL1ValidatorTx(base(rt.NetworkID), 1, pop, message)
require.NoError(err)
require.NoError(valid.SyntacticVerify(rt))
}
@@ -0,0 +1,74 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"testing"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/secp256k1fx"
)
// TestRemoveChainValidatorTxRoundTrip builds the tx through its constructor,
// round-trips it, and confirms the delta fields (NodeID, Chain, ChainAuth) and
// the spending envelope survive encode→decode.
func TestRemoveChainValidatorTxRoundTrip(t *testing.T) {
require := require.New(t)
base := spendBase()
nodeID := ids.GenerateTestNodeID()
chain := ids.GenerateTestID()
auth := &secp256k1fx.Input{SigIndices: []uint32{3}}
in, err := NewRemoveChainValidatorTx(base, nodeID, chain, auth)
require.NoError(err)
got := roundTrip(t, in).(*RemoveChainValidatorTx)
require.Equal(nodeID, got.NodeID())
require.Equal(chain, got.Chain())
require.Equal(auth, got.ChainAuth())
require.Equal(base.NetworkID, got.NetworkID())
require.Equal(base.BlockchainID, got.BlockchainID())
require.Equal([]byte(base.Memo), got.Memo())
}
func TestRemoveChainValidatorTxSyntacticVerify(t *testing.T) {
require := require.New(t)
rt := consensustest.Runtime(t, ids.GenerateTestID())
nodeID := ids.GenerateTestNodeID()
base := func(networkID uint32) *lux.BaseTx {
return &lux.BaseTx{NetworkID: networkID, BlockchainID: rt.ChainID}
}
// Case: nil tx
var nilTx *RemoveChainValidatorTx
require.ErrorIs(nilTx.SyntacticVerify(rt), ErrNilTx)
// Case: invalid BaseTx (wrong network ID); Chain and NodeID are set so we
// don't error on those checks first.
wrongNet, err := NewRemoveChainValidatorTx(base(rt.NetworkID+1), nodeID, ids.GenerateTestID(), &secp256k1fx.Input{})
require.NoError(err)
require.ErrorIs(wrongNet.SyntacticVerify(rt), lux.ErrWrongNetworkID)
// Case: can't remove a primary network validator
primary, err := NewRemoveChainValidatorTx(base(rt.NetworkID), nodeID, constants.PrimaryNetworkID, &secp256k1fx.Input{})
require.NoError(err)
require.ErrorIs(primary.SyntacticVerify(rt), ErrRemovePrimaryNetworkValidator)
// Case: invalid chain auth (sig indices not sorted and unique)
badAuth, err := NewRemoveChainValidatorTx(base(rt.NetworkID), nodeID, ids.GenerateTestID(), &secp256k1fx.Input{SigIndices: []uint32{1, 0}})
require.NoError(err)
require.ErrorIs(badAuth.SyntacticVerify(rt), secp256k1fx.ErrInputIndicesNotSortedUnique)
// Case: passes verification
valid, err := NewRemoveChainValidatorTx(base(rt.NetworkID), nodeID, ids.GenerateTestID(), &secp256k1fx.Input{})
require.NoError(err)
require.NoError(valid.SyntacticVerify(rt))
}
@@ -0,0 +1,57 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"testing"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
)
// TestSetL1ValidatorWeightTxRoundTrip builds the tx through its constructor,
// round-trips it, and confirms the delta field (Message) and the spending
// envelope survive encode→decode.
func TestSetL1ValidatorWeightTxRoundTrip(t *testing.T) {
require := require.New(t)
base := spendBase()
message := []byte("message")
in, err := NewSetL1ValidatorWeightTx(base, message)
require.NoError(err)
got := roundTrip(t, in).(*SetL1ValidatorWeightTx)
require.Equal(message, got.Message())
require.Equal(base.NetworkID, got.NetworkID())
require.Equal(base.BlockchainID, got.BlockchainID())
require.Equal([]byte(base.Memo), got.Memo())
}
func TestSetL1ValidatorWeightTxSyntacticVerify(t *testing.T) {
require := require.New(t)
rt := consensustest.Runtime(t, ids.GenerateTestID())
message := []byte("message")
base := func(networkID uint32) *lux.BaseTx {
return &lux.BaseTx{NetworkID: networkID, BlockchainID: rt.ChainID}
}
// Case: nil tx
var nilTx *SetL1ValidatorWeightTx
require.ErrorIs(nilTx.SyntacticVerify(rt), ErrNilTx)
// Case: invalid BaseTx (wrong network ID)
wrongNet, err := NewSetL1ValidatorWeightTx(base(rt.NetworkID+1), message)
require.NoError(err)
require.ErrorIs(wrongNet.SyntacticVerify(rt), lux.ErrWrongNetworkID)
// Case: passes verification
valid, err := NewSetL1ValidatorWeightTx(base(rt.NetworkID), message)
require.NoError(err)
require.NoError(valid.SyntacticVerify(rt))
}
@@ -0,0 +1,41 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import "testing"
// TestStakerDispatchSafety pins the interface-satisfaction invariant that the
// staker type-switches in service.go (getStakerAttributes) and
// executor/proposal_tx_executor.go depend on.
//
// Struct-is-wire turned RewardsOwner from a struct field into an accessor
// method, so *AddValidatorTx now structurally satisfies BOTH ValidatorTx and
// DelegatorTx. That is SAFE only because:
//
// 1. every dispatch site checks `case txs.ValidatorTx` BEFORE
// `case txs.DelegatorTx`, so an AddValidatorTx routes as a validator; and
// 2. *AddDelegatorTx does NOT satisfy ValidatorTx (it lacks
// ValidationRewardsOwner() and Shares()), so a delegator can never
// mis-route into the validator branch.
//
// If a future edit gives AddDelegatorTx those methods, or reorders a switch,
// this test fails loudly instead of silently mis-paying rewards.
func TestStakerDispatchSafety(t *testing.T) {
var (
vdr any = (*AddValidatorTx)(nil)
del any = (*AddDelegatorTx)(nil)
)
if _, ok := vdr.(ValidatorTx); !ok {
t.Fatal("*AddValidatorTx must satisfy ValidatorTx")
}
if _, ok := del.(DelegatorTx); !ok {
t.Fatal("*AddDelegatorTx must satisfy DelegatorTx")
}
// The load-bearing safety property: a delegator must NOT look like a
// validator, because every dispatch checks ValidatorTx first.
if _, ok := del.(ValidatorTx); ok {
t.Fatal("*AddDelegatorTx must NOT satisfy ValidatorTx — delegators would mis-route to the validator branch")
}
}
@@ -0,0 +1,74 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"testing"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
lux "github.com/luxfi/utxo"
"github.com/luxfi/utxo/secp256k1fx"
)
// TestTransferChainOwnershipTxRoundTrip builds the tx through its constructor,
// round-trips it, and confirms the delta fields (Chain, ChainAuth, Owner) and
// the spending envelope survive encode→decode.
func TestTransferChainOwnershipTxRoundTrip(t *testing.T) {
require := require.New(t)
base := spendBase()
chain := ids.GenerateTestID()
auth := &secp256k1fx.Input{SigIndices: []uint32{3}}
owner := &secp256k1fx.OutputOwners{Locktime: 876543210, Threshold: 1, Addrs: []ids.ShortID{ids.GenerateTestShortID()}}
in, err := NewTransferChainOwnershipTx(base, chain, auth, owner)
require.NoError(err)
got := roundTrip(t, in).(*TransferChainOwnershipTx)
require.Equal(chain, got.Chain())
require.Equal(auth, got.ChainAuth())
require.Equal(owner, got.Owner())
require.Equal(base.NetworkID, got.NetworkID())
require.Equal(base.BlockchainID, got.BlockchainID())
require.Equal([]byte(base.Memo), got.Memo())
}
func TestTransferChainOwnershipTxSyntacticVerify(t *testing.T) {
require := require.New(t)
rt := consensustest.Runtime(t, ids.GenerateTestID())
owner := &secp256k1fx.OutputOwners{Threshold: 1, Addrs: []ids.ShortID{ids.GenerateTestShortID()}}
base := func(networkID uint32) *lux.BaseTx {
return &lux.BaseTx{NetworkID: networkID, BlockchainID: rt.ChainID}
}
// Case: nil tx
var nilTx *TransferChainOwnershipTx
require.ErrorIs(nilTx.SyntacticVerify(rt), ErrNilTx)
// Case: invalid BaseTx (wrong network ID); Chain is set so we don't error on
// that check first.
wrongNet, err := NewTransferChainOwnershipTx(base(rt.NetworkID+1), ids.GenerateTestID(), &secp256k1fx.Input{}, owner)
require.NoError(err)
require.ErrorIs(wrongNet.SyntacticVerify(rt), lux.ErrWrongNetworkID)
// Case: cannot transfer ownership of a permissionless chain
primary, err := NewTransferChainOwnershipTx(base(rt.NetworkID), constants.PrimaryNetworkID, &secp256k1fx.Input{}, owner)
require.NoError(err)
require.ErrorIs(primary.SyntacticVerify(rt), ErrTransferPermissionlessChain)
// Case: invalid chain auth (sig indices not sorted and unique)
badAuth, err := NewTransferChainOwnershipTx(base(rt.NetworkID), ids.GenerateTestID(), &secp256k1fx.Input{SigIndices: []uint32{1, 0}}, owner)
require.NoError(err)
require.ErrorIs(badAuth.SyntacticVerify(rt), secp256k1fx.ErrInputIndicesNotSortedUnique)
// Case: passes verification
valid, err := NewTransferChainOwnershipTx(base(rt.NetworkID), ids.GenerateTestID(), &secp256k1fx.Input{}, owner)
require.NoError(err)
require.NoError(valid.SyntacticVerify(rt))
}
@@ -0,0 +1,340 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
"github.com/luxfi/utxo/secp256k1fx"
consensustest "github.com/luxfi/consensus/test/helpers"
"github.com/luxfi/node/vms/platformvm/reward"
lux "github.com/luxfi/utxo"
)
// TestTransformChainTx_RoundTrip exercises the struct-is-wire path: every delta
// field (chain, asset, the supply/rate/stake/duration knobs, the weight factor
// and uptime requirement, and the chain auth) round-trips through Parse. This
// replaces the deleted linearcodec golden-byte + JSON serialization tests.
func TestTransformChainTx_RoundTrip(t *testing.T) {
require := require.New(t)
chain := ids.GenerateTestID()
assetID := ids.GenerateTestID()
auth := &secp256k1fx.Input{SigIndices: []uint32{0, 2, 5}}
in, err := NewTransformChainTx(
spendBase(), chain, assetID,
1_000_000, // initial supply
2_000_000, // maximum supply
10_000, // min consumption rate
90_000, // max consumption rate
100, // min validator stake
5_000, // max validator stake
60, // min stake duration
3_600, // max stake duration
20_000, // min delegation fee
1, // min delegator stake
5, // max validator weight factor
80_000, // uptime requirement
auth,
)
require.NoError(err)
got := roundTrip(t, in).(*TransformChainTx)
require.Equal(chain, got.Chain())
require.Equal(assetID, got.AssetID())
require.EqualValues(1_000_000, got.InitialSupply())
require.EqualValues(2_000_000, got.MaximumSupply())
require.EqualValues(10_000, got.MinConsumptionRate())
require.EqualValues(90_000, got.MaxConsumptionRate())
require.EqualValues(100, got.MinValidatorStake())
require.EqualValues(5_000, got.MaxValidatorStake())
require.EqualValues(60, got.MinStakeDuration())
require.EqualValues(3_600, got.MaxStakeDuration())
require.EqualValues(20_000, got.MinDelegationFee())
require.EqualValues(1, got.MinDelegatorStake())
require.EqualValues(5, got.MaxValidatorWeightFactor())
require.EqualValues(80_000, got.UptimeRequirement())
gotAuth, ok := got.ChainAuth().(*secp256k1fx.Input)
require.True(ok)
require.Equal(auth.SigIndices, gotAuth.SigIndices)
}
// transformParams mirrors the NewTransformChainTx arguments so each error-path
// case can start from a fully-valid baseline and perturb exactly one field —
// struct-is-wire has no post-hoc mutation, so the bad value goes THROUGH the
// constructor and the SAME sentinel is asserted from SyntacticVerify.
type transformParams struct {
base *lux.BaseTx
chain ids.ID
assetID ids.ID
initialSupply uint64
maximumSupply uint64
minConsumptionRate uint64
maxConsumptionRate uint64
minValidatorStake uint64
maxValidatorStake uint64
minStakeDuration uint32
maxStakeDuration uint32
minDelegationFee uint32
minDelegatorStake uint64
maxValidatorWeightFactor byte
uptimeRequirement uint32
chainAuth *secp256k1fx.Input
}
func TestTransformChainTxSyntacticVerify(t *testing.T) {
rt := consensustest.Runtime(t, ids.GenerateTestID())
validBase := func() *lux.BaseTx {
return &lux.BaseTx{NetworkID: rt.NetworkID, BlockchainID: rt.ChainID}
}
invalidBase := func() *lux.BaseTx {
return &lux.BaseTx{NetworkID: 0, BlockchainID: rt.ChainID} // wrong networkID
}
// A fully-valid baseline: every field passes its own SyntacticVerify check.
valid := func() transformParams {
return transformParams{
base: validBase(),
chain: ids.GenerateTestID(),
assetID: ids.GenerateTestID(),
initialSupply: 10,
maximumSupply: 10,
minConsumptionRate: 0,
maxConsumptionRate: reward.PercentDenominator,
minValidatorStake: 2,
maxValidatorStake: 10,
minStakeDuration: 1,
maxStakeDuration: 2,
minDelegationFee: reward.PercentDenominator,
minDelegatorStake: 1,
maxValidatorWeightFactor: 1,
uptimeRequirement: reward.PercentDenominator,
chainAuth: &secp256k1fx.Input{SigIndices: []uint32{0}},
}
}
mk := func(t *testing.T, p transformParams) *TransformChainTx {
tx, err := NewTransformChainTx(
p.base, p.chain, p.assetID,
p.initialSupply, p.maximumSupply,
p.minConsumptionRate, p.maxConsumptionRate,
p.minValidatorStake, p.maxValidatorStake,
p.minStakeDuration, p.maxStakeDuration,
p.minDelegationFee, p.minDelegatorStake,
p.maxValidatorWeightFactor, p.uptimeRequirement,
p.chainAuth,
)
require.NoError(t, err)
return tx
}
tests := []struct {
name string
build func(t *testing.T) *TransformChainTx
err error
}{
{
name: "nil tx",
build: func(*testing.T) *TransformChainTx { return nil },
err: ErrNilTx,
},
{
name: "invalid netID",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.chain = constants.PrimaryNetworkID
return mk(t, p)
},
err: errCantTransformPrimaryNetwork,
},
{
name: "empty assetID",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.assetID = ids.Empty
return mk(t, p)
},
err: errEmptyAssetID,
},
{
name: "LUX assetID",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.assetID = rt.UTXOAssetID
return mk(t, p)
},
err: errAssetIDCantBeLUX,
},
{
name: "initialSupply == 0",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.initialSupply = 0
return mk(t, p)
},
err: errInitialSupplyZero,
},
{
name: "initialSupply > maximumSupply",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.initialSupply = 2
p.maximumSupply = 1
return mk(t, p)
},
err: errInitialSupplyGreaterThanMaxSupply,
},
{
name: "minConsumptionRate > maxConsumptionRate",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.minConsumptionRate = 2
p.maxConsumptionRate = 1
return mk(t, p)
},
err: errMinConsumptionRateTooLarge,
},
{
name: "maxConsumptionRate > 100%",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.minConsumptionRate = 0
p.maxConsumptionRate = reward.PercentDenominator + 1
return mk(t, p)
},
err: errMaxConsumptionRateTooLarge,
},
{
name: "minValidatorStake == 0",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.minValidatorStake = 0
return mk(t, p)
},
err: errMinValidatorStakeZero,
},
{
name: "minValidatorStake > initialSupply",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.initialSupply = 1
p.minValidatorStake = 2
return mk(t, p)
},
err: errMinValidatorStakeAboveSupply,
},
{
name: "minValidatorStake > maxValidatorStake",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.minValidatorStake = 2
p.maxValidatorStake = 1
return mk(t, p)
},
err: errMinValidatorStakeAboveMax,
},
{
name: "maxValidatorStake > maximumSupply",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.maxValidatorStake = 11
return mk(t, p)
},
err: errMaxValidatorStakeTooLarge,
},
{
name: "minStakeDuration == 0",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.minStakeDuration = 0
return mk(t, p)
},
err: errMinStakeDurationZero,
},
{
name: "minStakeDuration > maxStakeDuration",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.minStakeDuration = 2
p.maxStakeDuration = 1
return mk(t, p)
},
err: errMinStakeDurationTooLarge,
},
{
name: "minDelegationFee > 100%",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.minDelegationFee = reward.PercentDenominator + 1
return mk(t, p)
},
err: errMinDelegationFeeTooLarge,
},
{
name: "minDelegatorStake == 0",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.minDelegatorStake = 0
return mk(t, p)
},
err: errMinDelegatorStakeZero,
},
{
name: "maxValidatorWeightFactor == 0",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.maxValidatorWeightFactor = 0
return mk(t, p)
},
err: errMaxValidatorWeightFactorZero,
},
{
name: "uptimeRequirement > 100%",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.uptimeRequirement = reward.PercentDenominator + 1
return mk(t, p)
},
err: errUptimeRequirementTooLarge,
},
{
// Was: verifymock chainAuth returns errInvalidNetAuth. Reproduced
// with a real *secp256k1fx.Input whose indices are not sorted-unique.
name: "invalid chainAuth",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.chainAuth = &secp256k1fx.Input{SigIndices: []uint32{1, 0}}
return mk(t, p)
},
err: secp256k1fx.ErrInputIndicesNotSortedUnique,
},
{
name: "invalid BaseTx",
build: func(t *testing.T) *TransformChainTx {
p := valid()
p.base = invalidBase()
return mk(t, p)
},
err: lux.ErrWrongNetworkID,
},
{
name: "passes verification",
build: func(t *testing.T) *TransformChainTx {
return mk(t, valid())
},
err: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.ErrorIs(t, tt.build(t).SyntacticVerify(rt), tt.err)
})
}
}