lots
gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/lots on onyx-1 testnet
pure packageprimitiveaccountinglive on onyx-1
Work out the cost of the goods you have used.
Draws down inventory lot by lot, so the cost of what left the shelf follows from what was bought rather than from an estimate.
In detail curated
Cost of goods consumed as a pure function of cumulative units consumed. A caller says how MANY units left the shelf and never what they cost, so a realm cannot post a cost of goods sold that disagrees with the inventory it holds -- not by being careful, but because no argument exists through which the two could disagree. The move is p/depreciation's, sharpened: a depreciation schedule is fixed at acquisition and the caller supplies only time, while an inventory pool is mutated by every receipt and the function still holds. Lot.CostAt(k) == Cost*k/Qty is the whole arithmetic and Lot.Take is its DIFFERENCE at two points, so charges telescope by construction -- Take(a->b) + Take(b->c) == Take(a->c) -- and no stored remaining cost exists that could drift. The naive running-average form (charge remainingCost*n/remainingQty) is wrong in a specific and quiet way: the truncated remainder is retained rather than charged, so the cost of the first k units DEPENDS ON HOW THEY WERE SLICED INTO SHIPMENTS. It mis-times cost and does not lose it -- over a lot's whole life it charges exactly Cost, because the exhausting shipment takes the whole remainder -- and saying it loses money would be the stronger false claim a checking reader would correctly dismiss. What it misstates is a PERIOD's cost of goods sold and the inventory carried at any moment before exhaustion, both for a quantity nobody chose. Three methods: FIFO, LIFO, WAVG. WAVG re-anchors at every receipt -- the merged lot's consumed count resets, which is forced rather than convenient, because CostAt is a function of the lot's own Qty and Cost and a receipt changes both -- so telescoping holds within an anchor epoch and what holds across the re-anchor is exact cost neutrality, which CrossFootCost proves. The asymmetry is worth knowing before choosing: WAVG can REFUSE a receipt FIFO and LIFO accept, because the merged lot must satisfy Cost <= maxInt64/Qty where two separate lots each would. Every mutating method stages its whole effect on a copy and commits only once the last step succeeds, because AN ERROR RETURN DOES NOT REVERT A GNO TRANSACTION -- only a panic does -- and a half-mutated pool is undetectable to the realm short of a cross-foot. Specific identification is deliberately absent: it would make the consumed amount depend on a caller-supplied choice, the one thing the thesis rules out. No units, no currency, no SKU identity, no negative inventory; consuming more than is on hand is ErrInsufficient. Nothing here reads the chain, holds coins, or knows what a ledger is.
Use it
A pure package has no state and no transactions: you use it by importing it.
Import it
import "gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/lots"The path is the address: there is no package manager, nothing to add to a manifest and no version to pin, so the package you compile against is the one the chain runs. It differs per chain because the namespace in it is the deploying account's.
Called from real code
Not an example written for this page. These lines are quoted from inventory, a deployed package in this portfolio that imports this one as lots, out of the committed file whose sha256 onyx-1 testnet attested. They compile because they are compiled.
p, err := lots.NewPool(m, MaxLotsPerItem)Deployments
onyx-1 testnet onyx-1
chain-attested| Import path | gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/lots |
|---|---|
| Namespace | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Deployed at height | 355,260 |
| Deploy transaction | 08f79ac265f9d59887f9332e1c416d7861cf84d130c7306096b9ba98b6eecbc0 look it up on the RPC |
| Deployer | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Gas used | 44,750,936 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | gnomod.toml lots.gno |
| Deployed bytes | lots.gno — 33,062 bytes |
| sha256 | 5ab3f19ad9e64cd5791bbb408e1e4d272d6fb844f5a85526e1c7bfa46dfe74ac |
| Realm address | none — A /p/ package is not a realm: it holds no state, custodies no coins and is never a transaction sender. The pkgPath derivation would still produce a value; recording one would name nothing. |
| Used by | r/inventory |
|---|
Do not take the hash above on trust. $download returns the bytes onyx-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:
curl -sS 'https://onyx.testnets.gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/lots$download&file=lots.gno' | shasum -a 256Expected: 5ab3f19ad9e64cd5791bbb408e1e4d272d6fb844f5a85526e1c7bfa46dfe74ac — 33,062 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
API
chain-derived 3 exported functions, 3 types, 25 methods.
This is a /p/ package: you import it, you do not call it in a transaction. gnoweb's $help shows only exported top-level functions, so the types and methods below do not appear there at all — which is why this reference exists.
Overview
Package lots is the cost of goods consumed as a pure function of how much of a lot has been consumed.
A realm stores receipts of a stock-keeping unit — each a quantity and what was paid for it — and asks this package what the goods it just shipped cost. Nothing here reads the chain, holds coins, or knows what a ledger is, so a realm can test a year of its own inventory costing without a node.
p, _ := NewPool(FIFO, 64) p.Receive(100, 2500) // 100 units for 2500 in total p.Receive(50, 1400) // a second, dearer receipt cogs, _ := p.Consume(120) // what the shipment cost: DERIVED onHand, _ := p.CostOnHand() // what is left is worth
The amount is never a parameter
This is the thesis the rest of the package serves. A caller says how *many* units left the shelf; it never says what they cost. The cost is computed from the lots the units came out of, so a realm cannot post a cost of goods sold that disagrees with the inventory it holds — not because it is careful, but because there is no argument through which the two could be made to disagree.
[p/.../depreciation] makes the same move for a capital asset, where the charge is a function of periods elapsed. Here it is sharpened: a depreciation schedule is fixed at acquisition and a caller supplies only time, while an inventory pool is mutated by every receipt, and the function still holds.
Why the cost is a difference of a pure function, and not a running average
The obvious implementation keeps a remaining quantity and a remaining cost and, for a shipment of n units, charges
remainingCost * n / remainingQuantity
It is wrong in a way that is specific and quiet. The division truncates, and what it truncates is retained in the lot rather than charged, so the cost charged for the first k units out of a lot **depends on how those k units were sliced into shipments**. Measured, with the whole lot's life evaluated:
lot(Qty=7, Cost=10) consumed as [1,1,1] naive -> [1, 1, 1] total 3 telescoping -> [1, 1, 2] total 4
Nothing here is an accumulator. Lot.CostAt is a pure function of the cumulative units consumed,
CostAt(k) == Cost*k / Qty
and Lot.Take is its difference at two points. So charges **telescope** —
Take(a→b) + Take(b→c) == Take(a→c)
— by construction rather than by careful remainder arithmetic, and there is no stored remaining cost that could drift from the lot at all. A realm that loses track of what it has charged can recompute it from the quantities.
What the naive form actually gets wrong, stated precisely
It **mis-times** cost; it does not lose it. Over a lot's whole life the naive form charges exactly Cost, because the shipment that exhausts the lot has `n == remainingQuantity` and so charges the whole remainder. Saying "it loses money" would be a stronger and false claim, and a reader who checks the totals would correctly conclude the warning was overstated and ignore the real defect. The real defect is that a *period's* cost of goods sold, and the inventory value carried at any moment before exhaustion, both depend on shipment slicing — which is a misstatement of both the income statement and the balance sheet, for a quantity nobody chose.
Measured by differential sweep against exact arithmetic, over quantities 1 to 12 and costs 1 to 50, evaluating **every ordered composition** of the quantity (so every way of slicing a lot's whole life into shipments):
compositions evaluated 204750 naive path-dependent 108320 over 260 of 600 (Qty,Cost) pairs telescoping path-dependent 0 naive conservation failures 0 telescoping conservation failures 0 worst naive mis-timing 3 units of cost, at Qty=12 Cost=6
"Path-dependent" has a definition, because the count is worthless without one: a composition is path-dependent if the cumulative cost charged after some prefix of it differs from the cumulative cost the same number of units would have carried had they shipped one at a time. The two zeroes on the conservation lines are the evidence for the paragraph above — the naive form reaches the right total every time and gets there wrongly — and the worst case is half of a small lot's cost, not a rounding crumb.
The weighted-average method re-anchors at every receipt, and must
Under WAVG a receipt merges into the single live lot: the merged lot's quantity is what remained plus what arrived, its cost is what remained *was worth* plus what arrived cost, and its consumed count resets to zero. The reset is not bookkeeping convenience and is not a defect — it is forced. CostAt is a function of the lot's own Qty and Cost, and a receipt changes both, so a consumed count measured against the old pair means nothing against the new one. Telescoping therefore holds **within an anchor epoch**, and across the re-anchor what holds instead is exact cost neutrality: the merged lot's cost is the outgoing lot's remaining cost plus the receipt's, with nothing dropped and nothing invented. Pool.CrossFootCost is what proves it, and it is asserted over receipt/consume interleavings in the suite.
The asymmetry this creates is worth knowing before choosing a method: WAVG can **refuse** a receipt that FIFO and LIFO accept, because the merged lot must satisfy the overflow precondition `Cost <= maxInt64/Qty` that two separate lots would each satisfy on their own. See Pool.Receive.
An error return does not revert a Gno transaction
Only a panic reverts. So every mutating method here stages its whole effect — a copy of the lots it will touch, and the total it will charge — and commits only once the last step has succeeded. A method that returned an error after mutating half the pool would leave a realm holding inventory whose cost had been partly charged, with the realm's own error handling unable to undo it, and the realm would have no way to detect it short of a cross-foot. This package does not panic on caller error; it returns errors and keeps the pool untouched when it does.
What this package does not claim
- **No novelty for FIFO, LIFO or weighted average.** They are textbook and older than software. What was not found in the searched sources is an on-chain integer implementation with a path-independent cost function; see DISCOVERY.md.
- **Nothing about money.** A lot cost is a number in a record. This package never sees a coin, never holds value, and a realm using it must not infer custody from it.
- **No specific identification.** The four methods a textbook lists are three here. Specific identification — the shipper naming which lot a unit came from — would make the consumed amount depend on a caller-supplied choice, which is the one thing the thesis above rules out. A realm that needs it should hold one pool per identified lot.
- **No units of measure, no currency, no SKU identity.** A quantity is an integer and a cost is an integer. Deciding that they are kilograms and thousandths of a ugnot, and that this pool is the one for SKU "A-1", is the realm's job.
- **No negative inventory, no write-offs, no revaluation.** Consuming more than is on hand is ErrInsufficient, never a negative lot. Shrinkage and lower-of-cost-or-market are not here; a realm that needs them needs a fourth and fifth account and should say so in its own chart.
[p/.../depreciation]: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/depreciation
Imports
errors
Constants and variables
MaxLotsLimit bounds the live lots a single pool may hold, and so bounds every walk in this package: Pool.CostOnHand, Pool.QtyOnHand, Pool.Consume and both cross-foots are O(live lots).
What the bound costs is stated rather than waved at. A pool at the limit makes Consume scan up to 1024 lots and the cross-foots walk them all, and a realm that renders many pools in one query multiplies that. The limit is the ceiling a caller may ask for, not a recommendation: a caller passes its own smaller cap to NewPool and should, because exhausted lots are dropped but live ones are not, and a pool fed many small receipts and consumed in large shipments is the shape that fills up.
const MaxLotsLimit = 1024
var (
// ErrBadQty is returned when a quantity is not positive. A zero-quantity
// receipt is not a lot and a zero-quantity shipment is not an event;
// accepting either would have a realm post an entry for nothing.
ErrBadQty = errors.New("lots: quantity must be positive")
// ErrBadCost is returned when a cost is negative. Zero is allowed: goods
// received at no cost are ordinary — donated stock, samples, a
// promotional allowance that zeroed the invoice — and they consume
// exactly zero cost for their whole life, which is the right answer and
// not a degenerate one.
ErrBadCost = errors.New("lots: cost must not be negative")
// ErrBadConsumed is returned when a lot's consumed count is negative or
// exceeds its quantity. It is reachable only on a lot a caller built by
// hand; lots this package produces always satisfy it.
ErrBadConsumed = errors.New("lots: consumed must be in [0, qty]")
// ErrBadSeq is returned when a lot's receipt sequence is negative.
ErrBadSeq = errors.New("lots: seq must not be negative")
// ErrBadMethod is returned for a method outside the declared set.
ErrBadMethod = errors.New("lots: unknown method")
// ErrBadMaxLots is returned when a pool's lot cap is not positive or
// exceeds MaxLotsLimit.
ErrBadMaxLots = errors.New("lots: maxLots must be in [1, MaxLotsLimit]")
// ErrPoolFull is returned when a receipt would exceed the pool's own lot
// cap. It is fail-closed: the receipt is refused and the pool is
// untouched, so a realm sees a refused receipt rather than silently
// merged or dropped stock.
ErrPoolFull = errors.New("lots: lot cap reached")
// ErrInsufficient is returned when a shipment exceeds the quantity on
// hand. There is no partial fill and no negative inventory: a realm that
// wants to ship what it can must ask QtyOnHand first and decide.
ErrInsufficient = errors.New("lots: quantity exceeds quantity on hand")
// ErrSeqExhausted is returned when the receipt sequence space is spent.
// Unreachable in practice and checked anyway, because the alternative is
// two lots sharing a sequence and a silently non-deterministic order.
ErrSeqExhausted = errors.New("lots: receipt sequence space exhausted")
// ErrOverflow is returned when the arithmetic would wrap. Most overflow
// is excluded by a precondition established when a lot is constructed;
// see Lot.check and Pool.Receive.
ErrOverflow = errors.New("lots: int64 overflow")
)
const (
// FIFO consumes the earliest-received live lot first.
FIFO Method = 1
// LIFO consumes the latest-received live lot first.
LIFO Method = 2
// WAVG keeps one lot, merging every receipt into it at a weighted
// average. See the package doc on why the merge re-anchors.
WAVG Method = 3
)
Types
type Lot
type Lot struct {
// Qty is the quantity received. It never changes: consumption moves
// Consumed, not Qty, because CostAt is a function of the original pair
// and reducing Qty would redefine the function under its own callers.
Qty int64
// Cost is what the whole of Qty cost. It never changes either, for the
// same reason — except under [WAVG], where a receipt replaces the lot
// with a freshly anchored one rather than editing this field.
Cost int64
// Consumed is the cumulative quantity consumed from this lot, in
// [0, Qty]. It is the sole argument of the cost function.
Consumed int64
// Seq is the receipt order within a pool, assigned by [Pool.Receive].
// It orders lots for FIFO and LIFO and **never enters a cost
// computation**: two lots with the same Qty and Cost cost the same to
// consume whatever their sequence, which is why the ordering decision
// and the costing arithmetic can be reviewed separately.
Seq int64
}
Lot is one receipt of stock: a quantity, what was paid for all of it, and how much of it has since been consumed. It is a value, is safe to copy, and every method validates its receiver, so a hand-built or zero Lot fails rather than returning a plausible number.
Construct with NewLot so the overflow precondition is checked once rather than on every call. The fields are exported because a realm must be able to render a lot and persist it, and because Pool.Lots hands out copies.
| Exported field | Type | Doc |
|---|---|---|
Qty | int64 | Qty is the quantity received. It never changes: consumption moves Consumed, not Qty, because CostAt is a function of the original pair and reducing Qty would redefine the function under its own callers. |
Cost | int64 | Cost is what the whole of Qty cost. It never changes either, for the same reason — except under [WAVG], where a receipt replaces the lot with a freshly anchored one rather than editing this field. |
Consumed | int64 | Consumed is the cumulative quantity consumed from this lot, in [0, Qty]. It is the sole argument of the cost function. |
Seq | int64 | Seq is the receipt order within a pool, assigned by [Pool.Receive]. It orders lots for FIFO and LIFO and never enters a cost computation: two lots with the same Qty and Cost cost the same to consume whatever their sequence, which is why the ordering decision and the costing arithmetic can be reviewed separately. |
Lot.ConsumedCost
func (l Lot) ConsumedCost() (int64, error)
ConsumedCost returns the cost consumed from this lot so far: CostAt(Consumed).
Lot.CostAt
func (l Lot) CostAt(k int64) (int64, error)
CostAt returns the cumulative cost consumed from this lot once k units in total have been consumed from it:
CostAt(k) == Cost*k / Qty
It is the one function in this package, and everything else is defined from it. It is monotone non-decreasing in k, zero at k == 0, and **exactly Cost at k == Qty** — that endpoint is not an approximation, because Cost*Qty/Qty is exact in integers, and it is what makes a lot's whole life charge exactly what the lot cost and makes dropping an exhausted lot cost-neutral.
k outside [0, Qty] is ErrBadConsumed rather than a clamp: a clamp would let a caller's off-by-one read as a valid cost at the boundary.
Lot.Exhausted
func (l Lot) Exhausted() bool
Exhausted reports whether a well-formed lot has nothing left. An invalid lot is not exhausted — it is invalid, and conflating the two is how a bad lot gets silently dropped by Pool.Consume's housekeeping.
Lot.Remaining
func (l Lot) Remaining() int64
Remaining is Qty - Consumed: the quantity still on the shelf from this lot.
It does not validate, and it is the only method here that does not. On an ill-formed lot it returns an arbitrary number — Lot{Qty: 1, Consumed: minInt64} wraps. A zero result means "exhausted, or invalid", never "exhausted" alone; use Lot.Exhausted, which does validate. Guard a caller-supplied lot with Lot.Valid before quoting Remaining from it.
Lot.RemainingCost
func (l Lot) RemainingCost() (int64, error)
RemainingCost returns what the units still on the shelf are carried at: Cost - ConsumedCost. It is defined in terms of CostAt so the two can never disagree, which is the same reason Lot.Take is. It is zero exactly when the lot is exhausted.
Lot.Take
func (l Lot) Take(n int64) (Lot, int64, error)
Take consumes n units from the lot, returning the advanced lot and the cost of those units. The receiver is a value, so a failed Take cannot have mutated anything the caller holds — the atomicity the package doc describes is structural at this level and has to be built by hand one level up, in Pool.Consume.
The charge is the difference of Lot.CostAt at the two consumed counts, which makes charges telescope exactly:
Take(a→b) + Take(b→c) == Take(a→c) for a <= b <= c
So how a realm slices its shipments cannot change what a lot charges in total, nor what it has charged by any point in its life. The charge is never negative; n must be positive and within Lot.Remaining.
Lot.Valid
func (l Lot) Valid() bool
Valid reports whether the lot is well-formed. A zero Lot is not.
type Method
type Method uint8
Method is the order in which lots are consumed.
Method.String
func (m Method) String() string
String names the method. The zero Method is "invalid" rather than empty, so a caller that renders it without checking Valid prints something that reads as wrong instead of something that reads as missing.
Method.Valid
func (m Method) Valid() bool
Valid reports whether m is a method this package implements.
type Pool
type Pool struct {
method Method
maxLots int
lots []Lot
nextSeq int64
// The counters. Each is monotone non-decreasing and each is maintained
// independently of the lots, which is the whole point: a cross-foot
// compares a counter against a walk of the lots, so a bug in either one
// is visible rather than cancelling.
recvQty int64
recvCost int64
consQty int64
chgCost int64
}
Pool is the live lots of one stock-keeping unit under one consumption method, plus the four cumulative counters the cross-foots compare against.
It is a pointer type with unexported state because the counters and the lots must move together — a realm holding a mutable view of one without the other could leave the pool stating a cost on hand its own receipts contradict. Pool.Lots therefore returns copies.
Pool.ChargedCost
func (p *Pool) ChargedCost() int64
ChargedCost is the cumulative cost charged out by Pool.Consume. This is the figure a realm posts as cost of goods sold, and it is derived: no caller has ever supplied it.
Pool.Consume
func (p *Pool) Consume(qty int64) (int64, error)
Consume ships qty units and returns what they cost.
The cost is derived from the lots the units came out of, in the pool's declared order, and is never a parameter. Lots that the shipment exhausts are dropped, which is cost-neutral because Lot.CostAt is exact at Qty.
It is all-or-nothing in both senses. A shipment larger than Pool.QtyOnHand is ErrInsufficient with the pool untouched — there is no partial fill — and the whole effect is staged on a copy and committed only after the last lot has been taken from successfully, because an error return does not revert a Gno transaction. A pool left half-consumed with its counters unmoved would state a cost on hand its lots contradict, detectable only by a cross-foot.
Pool.ConsumedQty
func (p *Pool) ConsumedQty() int64
ConsumedQty is the cumulative quantity consumed.
Pool.CostOnHand
func (p *Pool) CostOnHand() (int64, error)
CostOnHand walks the live lots and sums what they are carried at: the inventory value a realm reports on its balance sheet. Derived, for the same reason as Pool.QtyOnHand.
It returns an error because a lot that failed validation makes the sum meaningless, and because the running total is overflow-checked. That check is believed unreachable — the sum is bounded by recvCost, whose own accumulation Receive guards — and it is written anyway, because "unreachable" is an argument about code that may be edited and the cost of being wrong here is a negative inventory value.
Pool.CrossFootCost
func (p *Pool) CrossFootCost() (received, charged, onHand int64, foots bool)
CrossFootCost reports the pool's cost identity:
cost received == cost charged out + cost on hand
The left side and the first right-hand term are cumulative counters; the second is a walk of the live lots. So this compares two independent derivations, and it is the strong one of the pair: it is what proves the WAVG re-anchor loses nothing, that dropping exhausted lots is cost-neutral, and that no shipment charged cost that was never received.
foots is false if the identity fails **or if the walk failed**, and onHand is zero in the second case. A caller that wants to tell those apart calls Pool.CostOnHand directly; a caller that only wants to know whether the sub-ledger is sound does not need to, because neither is sound.
Pool.CrossFootQty
func (p *Pool) CrossFootQty() (received, consumed, onHand int64, foots bool)
CrossFootQty reports the pool's quantity identity:
quantity received == quantity consumed + quantity on hand
It is the weaker of the pair and is **not implied** by Pool.CrossFootCost, which is why both exist. Costs are derived from quantities, so a quantity that went missing in a way that preserved its own lot's cost arithmetic — a lot dropped while still holding units it had charged nothing for, say — foots perfectly on cost and fails here. A realm's invariant should assert both.
Pool.Lot
func (p *Pool) Lot(i int) (Lot, bool)
Lot returns the i'th live lot in receipt order, and whether i was in range.
Pool.LotCount
func (p *Pool) LotCount() int
LotCount is the number of live lots. Exhausted lots are dropped by Pool.Consume, so this is not the number of receipts; that is Pool.ReceiptCount.
Pool.Lots
func (p *Pool) Lots() []Lot
Lots returns the live lots in receipt order, as copies. Mutating the result does not affect the pool.
Pool.MaxLots
func (p *Pool) MaxLots() int
MaxLots is the pool's own live-lot cap.
Pool.Method
func (p *Pool) Method() Method
Method is the consumption order, fixed at construction. It is deliberately immutable: changing it mid-life would recost nothing already charged but would change what the next shipment costs, which is a restatement a sub-ledger must not be able to make silently. A realm that must switch methods opens a new pool and discloses it.
Pool.QtyOnHand
func (p *Pool) QtyOnHand() int64
QtyOnHand walks the live lots and sums what remains. It is derived from the lots rather than read from a counter, which is what makes Pool.CrossFootQty a comparison of two things rather than a tautology.
It cannot overflow: every lot's remaining quantity is non-negative and their sum is bounded by recvQty, which Pool.Receive refused to let overflow.
Pool.ReceiptCount
func (p *Pool) ReceiptCount() int64
ReceiptCount is how many receipts the pool has taken, including those whose lots are exhausted and dropped, and including WAVG merges.
The clamp is not defensive noise. nextSeq is 1 on a constructed pool, so a zero-value Pool — one a realm declared rather than built with NewPool — would otherwise report **minus one receipt**, an affirmatively wrong number rendered as fact. That is the opposite of the choice Method.String makes twenty lines up, where the zero value deliberately reads "invalid" so a caller who skipped the check prints something that reads as wrong rather than as missing. A count has no such word, so the honest answer for a pool that has taken no receipts is zero. Transacting on that pool is still refused: Pool.Receive and Pool.Consume both return ErrBadMethod.
Pool.Receive
func (p *Pool) Receive(qty, cost int64) error
Receive records a receipt of qty units costing cost in total.
Under FIFO and LIFO it appends a lot, refusing with ErrPoolFull at the pool's cap. Under WAVG it merges into the single live lot, re-anchoring it: the merged lot carries the remaining quantity plus qty, the remaining cost plus cost, and a consumed count of zero. See the package doc on why the re-anchor is forced rather than chosen.
The merge is where WAVG can refuse what the other two methods accept. A merged lot must satisfy the same precondition every lot does, `Cost <= maxInt64/Qty`, and two separately-held lots each satisfying it do not imply their merge does. That returns ErrOverflow and leaves the pool exactly as it was, so the receipt is refused rather than partly applied — but a realm must handle it, because under WAVG it is a property of the *history* and not only of the arguments.
Nothing is committed until every check has passed: on any error the pool is untouched, including its counters.
Pool.ReceivedCost
func (p *Pool) ReceivedCost() int64
ReceivedCost is the cumulative cost received.
Pool.ReceivedQty
func (p *Pool) ReceivedQty() int64
ReceivedQty is the cumulative quantity received.
Functions
MustNewPool
func MustNewPool(method Method, maxLots int) *Pool
MustNewPool is NewPool, panicking on an invalid configuration. It is for a realm's init, where a configuration error is a deployment bug and should abort the transaction rather than be handled.
NewLot
func NewLot(qty, cost int64) (Lot, error)
NewLot validates and returns a lot of qty units costing cost in total. Seq is zero; Pool.Receive assigns it.
NewPool
func NewPool(method Method, maxLots int) (*Pool, error)
NewPool validates and returns an empty pool consuming lots in the given order and holding at most maxLots live lots.
Doc text is reproduced as vm/qdoc returns it. The node markdown-escapes doc comments, so a bracket or angle bracket may carry a backslash the committed source does not have. The source itself is at source and in this repository.
Dependencies
chain-attested| Imports | errors |
|---|---|
| First-party dependencies | none |
Known limitations
Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.
curated- A BARE /p/ CAN NEVER BE PROBED ON CHAIN, SO THE BYTE-MATCH IS THE WHOLE OF THE LIVE VERIFICATION. vm/qeval needs a realm to evaluate in, and this package declares no realm, so there is no live call to design and claiming one would be fabrication. What the chain attests is that 33,062 bytes with sha256 5ab3f19a... are at this path, read back through vm/qfile and matched EXACTLY against the committed blob. Every correctness claim about those bytes comes from the committed suite under the pinned onyx toolchain, from the mutation sweep, and from the audit -- none of it from this chain. DEPLOY_READINESS.md said so before the broadcast and the broadcast did not change it.
- NOTHING ON CHAIN CALLS THESE BYTES YET. r/inventory is the consumer and is composed, tested and staged but NOT DEPLOYED (task #76), so the live state of this package is bytes at a path with zero callers. The Gate A dependency ordering this product preserved -- p/journal, then p/lots, then r/inventory -- is now two thirds complete, and the third step is what will produce the first execution evidence for any of this arithmetic on any chain.
- THE PATH-INDEPENDENCE FIGURES ARE LOCAL MEASUREMENTS AND ARE NOT CHAIN-ATTESTED. The differential sweep against exact arithmetic over quantities 1-12 and costs 1-50, evaluating every ordered composition of the quantity, is in the package doc and in the committed suite. It ran under the pinned onyx toolchain on this machine. The chain executed none of it, and a reader who wants it re-established runs the suite rather than querying the node.
- THE MaxLotsLimit COST CEILING IS DOCUMENTED AND WAS NEVER MEASURED, AT THE CAP OR ANYWHERE. MaxLotsLimit = 1024 bounds Consume's scan and both cross-foots at O(live lots), and the package doc states what the bound costs rather than waving at it. No gas figure was taken: gno test reports one GAS line per test FUNCTION, which cannot be read as a per-call cost. A realm rendering many pools in one query must carry its own cap and measure its own gas. Named open in AUDIT.md section 7, discharged by r/inventory measuring a Render over many pools.
- THE DEPOSIT RATIO BROKE THE BAND A SECOND CONSECUTIVE TIME, DOWNWARD, AND THE MECHANISM IS UNKNOWN. 0.5344x here against r/assets' 1.237x two deploys ago, either side of a five-deploy 0.7064x-1.009x band. The comment-fraction hypothesis was measured and refuted (7,131 non-comment bytes, not 17,711). See the storage_measurement_note. This is recorded as a limitation and not as a finding because the deposit is sized from the simulate dry run and the deploy succeeded; what is lost is any ability to PREDICT a deposit from source bytes, which the band was the last remaining pretence of.
- THE AUDIT'S FALSE-POSITIVE FILTER WAS SELF-ADMINISTERED, NOT A FRESH DISPATCH. Both first-pass candidates were downgraded by the same context that raised them, which is the weaker of the two available procedures. It is mitigated rather than discharged by both downgrades resting on G6's enumerable write surface, and it is named open in AUDIT.md section 7 with the harness limitation that causes it.
- A MALFORMED Lot CAN ONLY ARRIVE FROM A REALM, AND THAT AUDIT IS THE CONSUMER'S. Lot's fields are exported SPECIFICALLY so a realm can persist them, which makes realm-side rehydration the one route to a malformed Lot outside this package. G6 shows Pool is fail-closed against it and G8 shows the hazard is documented, so it is not a p/lots finding -- it is an audit question carried to r/inventory, on the budgets/spendbook precedent.
Source
| Source file | onyx/p/lots/lots.gno at commit c2e10d17ffbf in the project repository (not public — the digest command above is the check that needs no repository) |
|---|---|
| Matches the deployed bytes | yes — byte-identical |