Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

inventory package

Overview

Package inventory is a per-owner inventory sub-ledger whose cost of goods sold is DERIVED from the lots on hand rather than asserted by a caller.

What this realm is for

A general ledger takes a debit code, a credit code and an amount from whoever is posting. That is the right design for a ledger, and it means gno.land/r/.../books structurally CANNOT know that an entry was a shipment, or that the cost charged for that shipment is the cost the goods were actually bought at. Nothing stops a shipper from booking a flattering number, and nothing in the book can tell afterwards.

This realm closes that gap for one class of entry. It holds each item's cost lots as state, and when goods ship it computes the charge from those lots by the item's declared costing method. The amount is never a parameter: Ship takes a QUANTITY and returns the cost it derived.

The resulting claims are narrow and checkable by anyone, which is the point. Per item, from p/lots' own cross-foot:

Example
1receivedCost == chargedCost + costOnHand

and, across a whole register, against the ledger the realm posted to:

Example
1balance(1300) == sum of costOnHand over items
2balance(5000) == sum of chargedCost over items
3balance(3900) == sum of receivedCost over items

All four are exported — ItemCrossFoot, CrossFootInventory, CrossFootCOGS, CrossFootContributed — and all are FREE reads, deliberately; see "Why the invariants are free reads" below.

Note what the three ledger identities are NOT. Each compares a walk of the lots against an account balance, and the two are maintained by different code: the pool counts for itself inside p/lots, while this realm posts to the book. A disagreement is therefore a real signal and not a tautology. That is the same reason p/lots keeps counters beside its lots.

The chart is three accounts and is fixed

Example
11300  Inventory                (asset,   debit-normal)
25000  Cost of goods sold       (expense, debit-normal)
33900  Inventory contributed    (equity,  credit-normal)

A receipt is debit 1300 / credit 3900. A shipment is debit 5000 / credit 1300 for the DERIVED cost. There is no OpenAccount entrypoint and the underlying book is created with a hard cap of three accounts, so the chart cannot grow. The accounting equation offsets at 0 = 0 on an empty register and after every posting, because 1300 + 5000 == 3900 is the register-wide restatement of each pool's own cross-foot.

3900 IS A PLUG, AND THIS REALM IS FORCED TO PICK ONE

This is the design's one unavoidable assertion about the world, so it is stated here rather than buried.

gno.land/r/.../assets faced the same question about an asset's cost basis and answered by NOT BOOKING IT: the only honest credit legs were a liability ("2000 Acquisition payable") or an equity plug ("3000 Contributed capital"), both of which assert how the asset was funded, which that realm does not know. It kept the cost in register state as data instead.

That answer is not available here, and the difference is structural rather than a matter of taste. The product IS cost of goods sold. A shipment debits 5000 and credits 1300, so 1300 must carry a balance, so the receipt must reach the book — and a debit to 1300 needs a credit leg. The choice is therefore not "book it or not" but "which plug", and every candidate asserts something unobservable:

  • 2000 Accounts payable asserts the goods were bought on credit and that a liability is outstanding. This realm cannot see a payment, so the liability would never be relieved and would grow without bound.
  • 1000 Cash asserts the goods were paid for in cash from a balance this realm does not hold and cannot verify. It holds no coins at all.
  • 3900 Inventory contributed asserts the goods were contributed by the owner. It is wrong in exactly the same way the other two are, and it is the only one of the three that is SELF-CONSISTENT without an unobservable second event: a contribution is complete on arrival.

So 3900 was chosen because it needs no follow-up transaction to stay coherent, not because it is true. Read it as "goods arrived from somewhere this ledger does not model". A register whose goods were in fact purchased on credit is understating liabilities and overstating equity by exactly balance(3900), and no code path here can detect that. Pairing this sub-ledger with a real payables ledger is out of scope for v1 and is the honest fix.

Costing method is per item and is FIXED at creation

An item is added by AddFIFO, AddLIFO or AddWAVG — three entrypoints rather than one taking a method code, following r/assets' Place / PlaceDeclining precedent, so a caller never has to know an enum's numbering.

p/lots fixes the method at pool construction and documents why: 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. This realm inherits that and adds nothing: there is no entrypoint that changes an item's method. An owner who must switch opens a new SKU and discloses it.

The three methods differ observably, which is the point of having them: FIFO and LIFO charge different amounts whenever unit costs differ, and WAVG re-anchors every receipt into a single merged lot so it holds exactly one lot and can never hit the per-item lot cap.

That exemption is not a free win, and the trade is the other way round from how it reads. WAVG escapes the LOT cap and in exchange acquires a refusal FIFO and LIFO do not have: a merged lot must satisfy p/lots' per-lot overflow precondition `Cost <= maxInt64/Qty`, and two lots that each satisfy it separately can merge into one that does not. So under WAVG a receipt can be refused for the pool's ACCUMULATED HISTORY rather than for anything about the receipt — the same receipt a FIFO item would have taken. It is a far corner of the arithmetic and not a practical bound, but it is the reason LotCapacity is the wrong thing to watch for a WAVG item: its figure is a bound WAVG will never approach, so a caller sizing headroom from it is reading the one limit that does not apply. See p/lots' Pool.Receive.

A ZERO-COST POSTING IS SKIPPED, AND THAT IS NOT A SHORTCUT

p/journal rejects a posting of zero (ErrInvalidAmount) — correctly; a zero entry is not an event. But both of this realm's postings can legitimately be zero:

  • Receive with cost 0 is free goods, which p/lots accepts (ErrBadCost is only for a NEGATIVE cost).
  • Ship out of lots that cost nothing derives a charge of 0.

In both cases the pool is updated and NOTHING is posted. The identities above are preserved exactly, because a zero contribution to the ledger side is matched by a zero contribution to the walk side. The same choice r/assets makes for a degenerate declining schedule, for the same reason.

What this realm does NOT do

  • No custody. It holds no coins, has no withdrawal path, and refuses a user call that arrives carrying any. A cost is a number an owner typed.
  • No write-off, shrinkage or revaluation. Shrinkage is a real inventory event and its absence is a real limitation: there is currently no way to remove goods without charging them to 5000, so a loss is indistinguish- able from a sale. The posting it needs is a debit to a 5-series loss account against a credit to 1300, which means a fourth account and a fourth entrypoint. Deliberately deferred to keep the chart fixed at three for v1, and named here rather than discovered later.
  • No revenue. A shipment books its COST, never a sale price. Revenue belongs to a receivables ledger (r/invoices) and tying the two together would require this realm to learn what was sold for how much.
  • No period close. p/journal can seal a book up to a HEIGHT; sealing a derived book could only make the next derivation unpostable. The capability exists in the engine and is deliberately not exposed.
  • No bulk receive or ship. One item per call — see the bound below.
  • No truth. A sub-ledger is a set of claims by its owner. With no custody there is nothing tying "received 100 units at cost 5000" to a real holding. The invariants here prove that the postings follow from the stored lots; they prove nothing about the world.

Why the invariants are free reads, and what the bound costs

r/books exposes Rederive as an owner-only crossing function, to contain the cost of a full re-walk. This realm diverges, deliberately and for the same reason r/assets does: the derivation IS the product, and a claim only its owner can check is not a claim worth making. Rederive, ItemCrossFoot, the three register cross-foots and Render are all free reads, so any third party can verify a register from vm/qeval with no key.

MaxLotsPerItem is 64 against p/lots' own MaxLotsLimit of 1024, and the gap is not timidity. p/lots' audit left the cost of its O(live lots) walks explicitly UNPRICED and recorded that a realm rendering many pools must measure its own gas. This realm is that realm, so the figures are measured rather than reasoned about, under the pinned onyx toolchain.

There are TWO cost axes, not one, and this paragraph used to name only the first: MaxItemsPerRegister*MaxLotsPerItem live lots, and separately MaxEntriesPerRegister postings. It called 64 items of 64 live lots "the worst case this realm can hold", which is false — a register can sit at the lot cap and the entry cap AT ONCE. It was also claimed in review that the two are mutually exclusive by construction; that is wrong too. A one-unit FIFO shipment draws from the oldest lot, which holds 100 units, so 64 of them never exhaust it: 64 items x 64 receipts then 64 items x 64 one-unit shipments leaves 4096 live lots and exactly 8192 entries. The figures below were taken on a register asserted in-fixture to sit at all three caps simultaneously, so the combined shape is measured and not argued about.

Free reads, net of a trivial-read baseline on the same register:

Example
1Rederive                 506,486,523
2Render, register page    186,179,474
3CrossFoot                 96,213,741
4Render, item page          6,467,621
5Render, index                192,716

The dearest is 16.9% of the 3,000,000,000 maxGasQuery. The two axes do not compose in any single read's price, because no single read walks both: Rederive walks postings and reads no lot, while CrossFoot and Render walk lots and read three accounts plus EntryCount. Measured rather than inferred — Rederive at the lot cap agrees with the same read on a lot-cheap register (506,495,345) to 0.002%, which is what proves the lot axis does not price it. So the ceiling is the MAX of the two axes and never their product.

A bound is not a price, and one item per call is what keeps the write side cheap: Receive and Ship each touch exactly one pool. Measured on the same toolchain, net of the same kind of baseline:

Example
1Receive creating the 64th lot        930,676
2Ship of all OnHand, dropping all 64 lots  11,837,593
3Receive refused at the lot cap       304,454

The dearest write is 2.3% of the dearest read, so the read side is what the caps exist for. See DEPLOY_READINESS.md for the transcript, the per-header GAS pairing rule, and the two limits of these figures: they came from throwaway test files that were deleted rather than committed, so they are reproducible but NOT guarded by the suite; and gno test's GAS line covers a whole test function, so a figure is only a call's own price when the fixture that enables it lives in a separate test. Both halves bit: the refused receipt first measured at 53,123,019 — 174x its real price — because its 64-lot fixture shared its GAS line.

Deploy order

This realm imports p/journal and p/lots, so on any chain the order is p/journal, p/lots, then this realm. The order is stated and the deployment STATE of either import deliberately is not: a sentence about what is deployed is true at signing time at best, and addpkg freezes it — r/assets carries exactly that fossil. The import relation is the durable fact.

Function

Capacity

func Capacity(owner address) (itemsLeft, entriesLeft int)
Ask AI Claude ChatGPT

Capacity reports how much room an owner's register has left.

Param

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/inventory.Capacity()"

Result