Documentation
¶
Overview ¶
Package fee defines the unified FeePolicy interface that every Lux VM accepting user-submitted transactions MUST satisfy.
Background. A 2026-05 audit of vms/* found five chains accepting user txs while charging nothing (dexvm, bridgevm, keyvm, zkvm, aivm) and one charging 1,000x too little (quantumvm). The root cause was that fee policy lived as ad-hoc fields on each VM's Config — there was no shared surface to enforce a non-zero floor, and no sentinel for chains that legitimately accept no user txs (thresholdvm, oraclevm, relayvm — committee-driven, no user mempool).
This package provides exactly one way to declare a fee policy:
- vms.Policy is the interface every VM Manager calls into.
- FlatPolicy is the canonical implementation for "burn a fixed amount of LUX per tx".
- NoUserTxPolicy is the explicit sentinel for committee-only chains. Manager wiring uses this to distinguish "explicitly no user fees" from "forgot to set policy".
- Validate(p) is the boot-time check Manager runs against each VM's declared policy to refuse zero-fee user-facing chains before they ever accept a block.
Migration plan (per audit follow-up): each offending VM declares a FlatPolicy in its Config, the VM's mempool calls Policy.ValidateFee at the same point it currently does (or fails to do) the fee check, and Manager runs Validate at chain bootstrap. No new abstraction layers, no per-VM bespoke fee structs — one interface, one validator.
Index ¶
Constants ¶
const MinTxFeeFloor uint64 = 1_000_000
MinTxFeeFloor is the minimum tx fee, in nLUX (also called µLUX — 1e-6 LUX), that any user-facing chain SHOULD charge. Matches the P-Chain base fee (utils/units: 1 mLUX = 1_000_000 nLUX). Used as the lower bound when reviewing/upgrading per-VM policies; not enforced inside Validate (a VM is free to charge MORE), but every VM choosing less will be flagged by the migration checklist.
Variables ¶
var ( // ErrZeroMinFee is returned by Validate when a non-sentinel policy // declares a zero minimum fee. User-facing chains MUST charge > 0. ErrZeroMinFee = errors.New("fee policy declares zero min tx fee on a user-facing chain") // ErrWrongFeeAsset is returned by Policy.ValidateFee when the tx // pays in an asset other than the policy's FeeAssetID. ErrWrongFeeAsset = errors.New("tx pays fee in wrong asset") // ErrInsufficientFee is returned by Policy.ValidateFee when the // paid amount is below MinTxFee. ErrInsufficientFee = errors.New("tx fee below policy minimum") // ErrChainAcceptsNoUserTxs is returned by NoUserTxPolicy.ValidateFee // for any tx — committee-driven chains have no user mempool, so any // arrival at the fee gate is a wiring bug. ErrChainAcceptsNoUserTxs = errors.New("chain accepts no user-submitted txs") )
Sentinel errors returned by Policy implementations.
Functions ¶
Types ¶
type FlatPolicy ¶
type FlatPolicy struct {
// Fee is the per-tx burn amount, in nLUX. MUST be > 0.
Fee uint64
// AssetID is the fee asset. For primary-network burn, use
// constants.UTXOAssetIDFor(networkID).
AssetID ids.ID
}
FlatPolicy charges a fixed fee per user tx. The canonical implementation for VMs without dynamic gas pricing — dexvm, bridgevm, keyvm, zkvm, aivm, quantumvm all use this.
func (FlatPolicy) FeeAssetID ¶
func (p FlatPolicy) FeeAssetID() ids.ID
FeeAssetID returns the configured fee asset.
func (FlatPolicy) ValidateFee ¶
func (p FlatPolicy) ValidateFee(paid uint64, asset ids.ID) error
ValidateFee enforces the flat policy.
type NoUserTxPolicy ¶
type NoUserTxPolicy struct{}
NoUserTxPolicy is the sentinel policy for chains that accept no user-submitted txs — committee-driven only (thresholdvm, oraclevm, relayvm). Distinguishing this from "policy not set" is what makes Manager's boot-time Validate able to refuse zero-fee user-facing chains without false-positive-ing the committee chains.
func (NoUserTxPolicy) FeeAssetID ¶
func (NoUserTxPolicy) FeeAssetID() ids.ID
FeeAssetID returns ids.Empty — there is no fee asset.
func (NoUserTxPolicy) MinTxFee ¶
func (NoUserTxPolicy) MinTxFee() uint64
MinTxFee always returns 0 — there are no user txs to charge.
func (NoUserTxPolicy) ValidateFee ¶
func (NoUserTxPolicy) ValidateFee(uint64, ids.ID) error
ValidateFee always returns ErrChainAcceptsNoUserTxs — any caller reaching this gate is a wiring bug.
type Policy ¶
type Policy interface {
// MinTxFee returns the minimum fee, in nLUX, that any user tx must
// pay. MUST be > 0 for user-facing VMs.
MinTxFee() uint64
// FeeAssetID returns the asset ID used for fee payment. For
// primary-network burn, this is constants.UTXOAssetIDFor(networkID).
FeeAssetID() ids.ID
// ValidateFee returns nil if the paid amount and asset satisfy the
// policy. Returns ErrWrongFeeAsset, ErrInsufficientFee, or
// ErrChainAcceptsNoUserTxs as appropriate.
ValidateFee(paidNanoLux uint64, paidAsset ids.ID) error
}
Policy defines how a VM charges for user-submitted txs. Every chain that accepts user txs MUST declare a non-nil Policy whose MinTxFee() returns a value > 0. Chains that accept no user txs declare a NoUserTxPolicy sentinel instead — this is the only legal way to opt out of charging.