cashflow
gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow on onyx-1 testnet
realmapplicationreportinglive on onyx-1
Publish a cash flow statement over a ledger you do not own.
Reads a book it cannot write, so a statement can be shared without handing the publisher any power over the records.
In detail curated
A DIRECT-METHOD STATEMENT OF CASH FLOWS published over a general ledger this realm does not own and cannot write. An owner of a book in r/books declares with DeclarePolicy which of their account codes hold CASH and what every other code's movement is FOR -- operating, investing or financing; Publish then reads the entries in a [from, until) BLOCK-HEIGHT window straight out of that ledger, hands them to p/cashflow, and freezes the result at a sequence number: the per-category totals, the net change, the closing position, and a snapshot of the policy that classified it. THE GUARANTEE IS A LIST OF THREE AND IT IS DELIBERATELY NARROW -- the ENTRIES are read from r/books and are not supplied by the publisher, the CLASSIFICATION is the publisher's own declared policy frozen with the statement so a reader sees what it was, and the ARITHMETIC is p/cashflow's, which nobody here can parameterise. What is NOT claimed, and is not checkable from here, is that the chart describes anything real or that the codes called cash hold cash. p/cashflow states the consequence and this realm's package doc REPEATS rather than links it, because it is the single most important sentence about the product: a realm that declares a receivable as cash produces a plausible, wrong statement, and THE DECLARATION IS THE TRUST BOUNDARY. That makes a published statement a claim about a claim about a claim -- r/statements already says the first two layers of this of itself, and the third layer, which codes are cash at all, is added here. THERE IS NO OWNER PARAMETER, SO THERE IS NO ADDRESS TO FORGE: both mutators derive their subject as cur.Previous().Address() and neither takes an owner, so an attacker calling either publishes a statement about THEIR OWN book, which requires them to have one, and carries none of anyone else's figures; every other exported function takes owner address as a plain lookup key and is a read. EXACTLY ONE FIGURE IS SUPPLIED RATHER THAN DERIVED, and its check is conditional: p/cashflow cannot derive an OPENING cash position from a window-filtered entry slice because a window carries period MOVEMENT and not a cumulative POSITION, so opening is an argument to Publish and is echoed into the report unchanged, and Closing = Opening + NetChange inherits it. When the window runs PAST the last entry, Publish cross-checks the cumulative figure against the live cash balance in the book and a wrong opening is refused; when the window CLOSES BEFORE the last entry no such check exists, and OpeningReconciled(owner, seq) reads false in exactly that case -- a reader who ignores it is ignoring a disclosure rather than being misled. THE WINDOW CAP IS A REFUSAL, NEVER A TRUNCATION: at most MaxWindowEntries = 256 entries may fall inside one window, and exceeding it refuses while naming the count and the limit, because a statement over fewer entries than the window holds still BALANCES, so a silently partial read would be indistinguishable from a correct one. The range is located by binary search over a book of up to MaxEntriesPerBook = 4096 entries, so locating a window costs 12 reads per bound regardless of book size and the 256 cap bounds everything after it. WINDOW SOUNDNESS IS THREE CHECKS AND THE DESIGN RECORD CLAIMED FIVE: binary search presumes non-decreasing entry dates and r/books does not guarantee that, so this realm checks -- the endpoint check (entry 1 dated after entry n is a refusal, not a statement), at < prev inside the read loop, and !w.Contains(at) for the common case of a dip carrying an out-of-window entry into the located range. The two deleted 'maximality probes' that DISCOVERY.md advertised were negations of the binary search's OWN loop invariants, so neither could ever fire on any book under any ordering, and the invariants hold from what the loop ASSIGNS rather than from how the book is ORDERED -- vacuous in precisely the case they were written to catch. They were deleted on a MEASUREMENT WITH A SABOTAGE CONTROL, not on a derivation, and DISCOVERY.md Amendment A records the gate publishing a claim this realm's own suite later refuted. RESIDUAL AT ITS REAL STRENGTH: an EMPTY range produced by a dip leaves all three checks silent, because all three fire on an entry that was read, and the net is then Publish's cumulative cross-check, which is absent in a closed window -- the same hole the opening figure has, seen from the other side, with the same disclosure. Rescan(owner, seq) re-derives a frozen statement from the CURRENT book and reports whether the two still agree -- the drift detector for a statement frozen while its ledger kept moving. It is NON-CROSSING, so MsgCall cannot dispatch to it and it can never enter a block; it is reachable by vm/qeval alone, where gas is infinite. The envelope was measured anyway at both loops' maxima (256 window entries, a 128-account chart): ~318 M local-VM gas units, ~84 ms, about 10.6% of a block's gas limit on the counterfactual where it were a transaction. THIS REALM HOLDS NOTHING -- no coins, no banker, no withdrawal path, and refuseCoins rejects an attached payment rather than silently keeping it -- so the threat model is ACCOUNTING INTEGRITY AND AUTHORIZATION, NOT CUSTODY, and the monetary-invariant class governing every custodial realm in this portfolio does not apply. It is imported by nothing, holds authority over nothing, and removing it would break nothing, which is the correct shape for a publisher.
Use it
A realm holds state. Reading it is a free query; changing it is a transaction you sign yourself.
Read it — no key, no account, no GNOT
gnokey query vm/qeval \
--data 'gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow.Height()' \
--remote https://rpc.onyx.testnets.gno.land:443Height is read-only, so this is a query rather than a transaction: it costs nothing, signs nothing and needs no funded account. Every exported function is listed under the API below; swap the name and the arguments to read any of the others.
Change its state
Every state change is a transaction you sign with your own key — this site never asks for one and holds none. You do not have to hand-write the transaction: gnoweb generates the exact gnokey maketx call command for any of this realm's 2 crossing functions, with the argument names filled in.
Read the known limitations further down this page first. They record what was not verified on chain, which is usually the part that matters.
Import it from another realm
import "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow"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.
Deployments
onyx-1 testnet onyx-1
RenderedSourceCall builderState
chain-attested| Import path | gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow |
|---|---|
| Namespace | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Deployed at height | 474,634 |
| Deploy transaction | effe5554f8f79bcdaee2d10958f5aa86b4af7ebdbcf7efd979809bee0c66d332 look it up on the RPC |
| Deployer | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Gas used | 84,808,196 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | cashflow.gno gnomod.toml |
| Deployed bytes | cashflow.gno — 64,426 bytes |
| sha256 | 0598f1a17ef51ae07abeb80572fcb906e8503fe8c21ba918dd78439c94e83abe |
| Realm address | g1lpamh9kyhupmfrr7tv5nrxgg2enukj7807h236 |
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/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow$download&file=cashflow.gno' | shasum -a 256Expected: 0598f1a17ef51ae07abeb80572fcb906e8503fe8c21ba918dd78439c94e83abe — 64,426 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
API
chain-derived 30 exported functions.
Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it:
CompleteCountDeclarePolicyEntryRangeHasHasPolicyHeightLatestLedgerLineOfLiveCashNetZeroAtNetZeroCountOpeningReconciledPolicyCashPolicyDeclaredAtPolicyOfPolicyRulesPublishPublishedAtReconciledCashReconcilesRenderRescanScopeTotalTotalsUnresolvedAtUnresolvedCountWindow
Overview
Package cashflow publishes a direct-method statement of cash flows over a book this realm does not own, cannot write, and did not supply.
What it is, in one sentence
An owner of a book in r/books declares which of their account codes hold cash and what every other code's movement is for; this realm then reads the entries in a window straight out of that ledger, hands them to p/cashflow, and freezes the resulting statement at a sequence number.
A PUBLISHED STATEMENT IS A CLAIM ABOUT A CLAIM — and here, about TWO
r/statements already says this of itself and it is true here with one extra layer. A book is a set of claims by its owner; nothing ties an account reading `Cash 5000` to 5000 real ugnot anywhere. A cash-flow statement derived from such a book inherits that completely, and then adds a SECOND caller declaration on top: which codes are cash at all.
p/cashflow states the consequence exactly and it is repeated here rather than linked, because it is the single most important sentence about this realm: *"A realm that declares a receivable as cash produces a plausible, wrong statement. The declaration is the trust boundary."*
So what this realm guarantees is narrow:
- the ENTRIES are read from r/books, not supplied by the publisher;
- the CLASSIFICATION is the publisher's own declared policy, frozen with the statement so a reader sees what it was;
- the ARITHMETIC is p/cashflow's, which nobody here can parameterise.
That the chart describes anything real, and that the codes called cash hold cash, are not claimed here and are not checkable from here.
Derived, not supplied — and the one figure that is supplied
Every figure comes from r/books through its own read surface, except one. p/cashflow cannot derive an OPENING cash position from a window-filtered entry slice — a window carries period movement, not a cumulative position — so `opening` is an argument to Publish and is echoed into the report unchanged. [Closing] is `Opening + NetChange` and is therefore only as good as a number the publisher typed.
This realm does not leave it at that. See OpeningReconciled: when the window extends past every entry the book holds, the sum of the declared cash accounts' balances is the cumulative cash position, and that is comparable to [Closing]. Where the comparison is available, Publish REFUSES to publish a statement that fails it. Where it is not available, the report says so and the figure stands as a declaration.
The window is REFUSED when it is too wide, never chunked
p/cashflow is explicit that it does no paging and no merging, because merging reports across pages would make the unresolved set an artifact of the paging. Its entry cap is 4096, exactly books.MaxEntriesPerBook, so the primitive can always accept a whole book and the binding constraint is not its cap — it is the cross-realm call budget, two reads per entry.
So this realm declares MaxWindowEntries and refuses a window holding more, telling the caller to narrow it. A chunk-and-merge would be cheaper to use and would violate the primitive's own stated rule.
Window selection rests on a premise OWNED BY ANOTHER REALM
Entries are located by binary search over entry ID, which is correct only because `At` is non-decreasing in ID. That is not a property of p/journal, which refuses only a negative `at` and one at or before the close cutoff. It is a property of r/books, which dates every entry with `runtime.ChainHeight()` at all three of its posting call sites and exposes no `at`-taking entrypoint. r/books declares the obligation in its own package doc.
The failure mode if that premise ever breaks is the dangerous kind: a statement over FEWER entries than the window holds, which still balances and still reconciles. So the premise is checked rather than assumed — [scan] verifies `At` is non-decreasing across every entry it reads (free, since each `At` is already in hand) and probes the two entries immediately outside the located range to prove the range is maximal. A violation panics instead of publishing.
RESIDUAL, stated with its boundary: that makes a violation at or adjacent to the window edges loud. A violation deep outside the scanned range is invisible without a full scan, which is the cost this design exists to avoid. OpeningReconciled closes it for the one window shape where the cumulative cross-check applies, and nowhere else.
Entry IDs are relied upon to be dense, and the argument is structural
The binary search indexes entries 1..books.EntryCount. That is sound because p/journal allocates `id := nextID; nextID++` and writes the entry immediately, in one function with no error return between the two, into a tree that has no delete; a post that fails panics and reverts the whole transaction. So there are no gaps and no reuse. It is stated here because it is a premise, not an observation: [EntryInfo] panics on an absent ID, so a gap would abort the scan loudly rather than skew a figure.
A reversal lands in the period it was MADE
r/books dates a reversing entry at the current height, not at the reversed entry's date, and p/cashflow deliberately does not special-case reversals: a reversing entry that touches cash moved cash. So reversing a prior-period entry shows as movement in the CURRENT period and the corrected period's published statement does not change. That is right for a cash-flow statement and surprising to a reader expecting otherwise, which is why it is here.
NO CUSTODY, and no re-derivation seal
There is no coinio import, no payable path, no fee, no withdrawal, no admin. Publish and DeclarePolicy refuse a transaction carrying coins.
There is deliberately NO `Verify` accessor re-deriving a stored report and comparing. For a window that closed before the publication height such a check is a CONSTANT TRUE by construction: entries are dated at the chain height and the journal is append-only, so no entry can later fall into a closed window, and the chart cannot lose an account. Shipping it would be the fifth instance of the vacuous-true class this project keeps finding in its own work. What ships instead is Rescan, whose non-vacuity is exact and narrow: it re-locates the window NOW and so tests the selection premise above, not the ledger's contents.
No cross-owner aggregate beyond a count
r/books scopes every read to one owner, and a cash-flow statement mixing owners would assert a claim no single owner can make. Total is a count and there is no way to enumerate publishers.
Limits, stated plainly
- `opening` is caller-supplied, and verified only where OpeningReconciled says it was.
- The policy is caller-declared and is the trust boundary. A wrong cash declaration yields a plausible, wrong statement and no on-chain check here can detect it.
- The cost bound is reasoned against a live precedent (r/statements performs roughly 130 cross-realm reads per report), not measured in gas.
- Memos and account codes and names are an owner's free text in r/books and are unsanitized there. Every one of them goes through [cell] before it reaches Render.
- The chart reconstruction here is a second copy of r/statements' unexported `chartOf`. Promoting it would modify a live realm.
r/books: /r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books r/statements: /r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements p/cashflow: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow p/journal: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal p/periods: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods
Imports
chain/runtimechain/runtime/unsafegno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflowgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journalgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periodsgno.land/p/nt/avl/v0gno.land/p/nt/markdown/sanitize/v0gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/booksstrconvstrings
Constants and variables
BooksPath is the ledger realm every entry here is read from.
Taken BY VALUE from that realm's own exported constant rather than typed out again, so this is the compile-time pin itself and not a second copy that could drift. A reader checking which ledger a statement describes is reading the import.
const BooksPath = books.RealmPath
MaxWindowEntries bounds how many entries one Publish may read.
This is the §7.4 refusal bound and the number has a derivation rather than a feel. Reading one entry costs TWO cross-realm reads — books.EntryInfo for the header and books.EntryLegs for the postings — because r/books returns them separately. Add the chart reconstruction, which is one books.Accounts, one books.Codes at the chart cap and one books.AccountInfo per account, and the live precedent r/statements pays about 130 reads per report. Add the search: 2*ceil(log2(N+1)) + 4 header reads to locate and prove the range.
At 256 the whole publish is about 666 plain reads, roughly five times a report r/statements performs on this chain today. At r/books' own entry cap of 4096 it would be about 8,350, which is the figure the discovery gate for r/receivables used to defer this product.
It is a REFUSAL and not a truncation: a window holding more entries than this panics and names the count, and the caller narrows the window. Publishing a statement over a silently-truncated entry set is the one failure this product is least able to detect, because a short statement still balances.
const MaxWindowEntries = 256
PolicyIsReplaceable records a decision a reader will look for.
DeclarePolicy overwrites an owner's previous declaration and keeps no history of it. That is safe only because every Publish FREEZES a copy of the policy it used into the record: a published statement is readable with the classification that produced it forever, via PolicyOf, and changing the live policy cannot restate a published figure.
The alternative — versioned policies with reports pointing at a version — is the same information with an extra indirection and one more place for the two to disagree.
const PolicyIsReplaceable = true
PublicationsAreUncapped mirrors r/statements' decision for the same three reasons, which hold here unchanged: keys are per owner so an owner can only fill their own history; no accessor and no Render path walks a publisher's whole history; and there is no engine cap above this realm to mirror.
const PublicationsAreUncapped = true
RealmPath is this realm's own path, used to build Render links.
const RealmPath = "/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow"
RenderCensusLimit caps how many unresolved or net-zero rows one Render call emits. A window may hold MaxWindowEntries entries and in a book with no policy rules every one of them is unresolved.
const RenderCensusLimit = 25
RenderPublicationLimit caps how many of a publisher's statements one Render call lists.
const RenderPublicationLimit = 20
Functions
Complete
func Complete(owner address, seq int64) bool
Complete reports whether every in-window entry that moved cash was attributed to a category.
A complete statement is not a correct one. Completeness is a property of the POLICY's coverage, and a policy that classifies every account wrongly is complete. p/cashflow says this of its own accessor and it is worth repeating at the realm boundary, where the word will be quoted.
Count
func Count(owner address) int
Count is how many statements an owner has published.
DeclarePolicy
func DeclarePolicy(cur realm, cash, rules string)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asDeclarePolicy(cross(cur), ...).
DeclarePolicy records the caller's own classification policy for their own book, replacing any previous one.
`cash` is a comma-separated list of account codes that hold cash, strictly ascending — for example `1010,1020`. It must be non-empty and every code must be an asset in the caller's chart.
`rules` is a comma-separated list of `code:category` pairs, strictly ascending by code, where category is `operating`, `investing` or `financing` — for example `4000:operating,1500:investing,3000:financing`. A code absent from `rules` is NOT defaulted to anything: an entry touching it is reported unresolved, by name, because a default bucket turns a missing declaration into a figure nobody questions. `rules` may be empty.
The owner is `cur.Previous().Address()` and there is NO owner parameter. r/books derives a book's owner the same way and no mutating function there takes one either, so the only party who can declare a policy for a book is the only party who can write to it. An owner parameter would move the trust boundary to whoever called this.
The declaration is validated against the caller's LIVE chart by deriving over an empty entry set: cf.Derive validates the window, the chart and the policy before it reads an entry, so a nil entry slice exercises exactly the declaration's validity and nothing else. A policy that is valid now stays valid, because r/books has no way to remove an account from a chart.
EntryRange
func EntryRange(owner address, seq int64) (lo, hi uint64, ok bool)
EntryRange is the inclusive r/books entry-ID range this statement was derived from. `ok` is false when the window held no entries.
It is stored because it is the evidence for the statement's scope: a reader can take the two IDs to r/books and read the entries themselves. A statement is the only artifact here, and this is what makes it checkable against its source.
Has
func Has(owner address, seq int64) bool
Has reports whether a statement exists.
HasPolicy
func HasPolicy(owner address) bool
HasPolicy reports whether an owner has declared a policy.
Height
func Height() int64
Height is the current block height, so a reader can tell how old a statement is without a second query.
Latest
func Latest(owner address) (int64, bool)
Latest is the newest sequence number for an owner, and false if they have published none.
Ledger
func Ledger() string
Ledger is the realm every entry here is read from.
LineOf
func LineOf(owner address, seq int64, category string) (inflows, outflows, net int64, entries int)
LineOf is one category's gross flows. `category` is `operating`, `investing` or `financing`; anything else panics.
Inflows and outflows are GROSS and both non-negative. A category with 1,000 in and 1,000 out is not the same business as one with nothing happening, and a net of zero is all a netted figure could say about either.
LiveCash
func LiveCash(owner address) int64
LiveCash is the declared cash accounts' summed balance right now.
This is the book's cumulative net cash movement since its first posting, and it is a LIVE figure — it moves with every posting. It is not a published statement and must not be quoted as one.
NetZeroAt
func NetZeroAt(owner address, seq int64, i int) uint64
NetZeroAt is one net-zero entry's ID, by index.
NetZeroCount
func NetZeroCount(owner address, seq int64) int
NetZeroCount is how many in-window entries touched cash and moved none on net.
OpeningReconciled
func OpeningReconciled(owner address, seq int64) bool
OpeningReconciled reports whether this realm was able to check the publisher's declared opening position against the ledger — and, because a failed check REFUSES to publish, it being true means the check passed.
When it applies
Only when the window extended past every entry the book held at publication. Then the window's net change covers all movement from `from` onward, the declared cash accounts' summed balance covers all movement ever, and the difference between them is exactly the movement before `from` — which is what a correct opening is. An empty book qualifies too: with no postings every balance is zero, so the check requires an opening of zero.
It does NOT apply to a window that closed before later entries were posted. There the balances carry post-window movement the statement rightly excludes, and comparing them would be comparing two different periods.
What it proves, and the second thing it proves
First, that the declared opening is consistent with the ledger. Second — and this is why it is worth its cost — that THE ENTRY SCAN WAS COMPLETE. A scan that silently missed entries produces a short net change, and a short net change fails this comparison. That is the one mechanical control this realm has against the silent-under-reporting failure mode the package doc names, and it covers exactly the window shape where it is available.
What it does not prove
Nothing about whether the codes called cash hold cash. Both sides of the comparison are the same owner's self-report, read through the same realm. It is an internal consistency check, not a custody attestation.
PolicyCash
func PolicyCash(owner address) string
PolicyCash is the owner's CURRENT declared cash codes, comma-separated.
PolicyDeclaredAt
func PolicyDeclaredAt(owner address) int64
PolicyDeclaredAt is the height the owner's current policy was declared at.
PolicyOf
func PolicyOf(owner address, seq int64) (cash, rules string)
PolicyOf is the classification policy that produced a published statement, re-encoded in DeclarePolicy's own input format.
It is the statement's policy, frozen with it, NOT the owner's current one. PolicyIsReplaceable explains why both exist: a reader of a published figure needs the declaration that produced it, and an owner needs to be able to correct a declaration without restating history.
PolicyRules
func PolicyRules(owner address) string
PolicyRules is the owner's CURRENT declared rules, comma-separated `code:category` pairs.
Publish
func Publish(cur realm, from, until, opening int64) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asPublish(cross(cur), ...).
Publish freezes a statement of cash flows over [from, until) and returns its sequence number, which counts from 1 per publisher.
The window is half-open: `from` is included and `until` is not. The unit is whatever r/books dates with, which is block height.
`opening` is the caller's declared opening cash position. It cannot be derived from a windowed entry set and is echoed into the report unchanged; see OpeningReconciled for the one window shape where this realm can and does check it.
The owner is `cur.Previous().Address()`. There is NO owner parameter and no way to publish over someone else's book, for the reason given at DeclarePolicy.
It PANICS, rather than returning an error, on: no book, no declared policy, an invalid window, a window holding more than MaxWindowEntries entries, a violation of the entry-dating premise at either window edge, a disagreement between the chart's two reads or an entry's two reads, any cf.Derive error, and a failed cumulative cross-check. An error return would not revert a Gno transaction; only a panic does.
PublishedAt
func PublishedAt(owner address, seq int64) int64
PublishedAt is the block height a statement was frozen at.
ReconciledCash
func ReconciledCash(owner address, seq int64) (int64, bool)
ReconciledCash is the declared cash accounts' summed balance at publication, and false if OpeningReconciled does not apply to this statement. The figure is meaningless when ok is false and is returned as zero.
Reconciles
func Reconciles(owner address, seq int64) bool
Reconciles reports whether the attributed lines plus the unattributed residual equal the net change.
On a statement published here it is ALWAYS TRUE, because cf.Derive produces it that way and nothing between there and storage touches the figures. It is exposed anyway, and the reason is narrow: it is the one check a reader can run against a stored report without re-reading the ledger, and an exposed constant-true that is DOCUMENTED as one is better than a reader assuming the invariant holds without ever being told where it comes from. It must not be quoted as evidence that a statement is right.
Render
func Render(path string) string
Render serves the index at "", a publisher's page at "<address>", and one statement at "<address>/<seq>".
Every caller-supplied string reaching this page — an account code, an entry memo — is an owner's free text in r/books and arrives unsanitized, because that realm does not sanitize what it stores. Free text reaching a page unescaped is the one injection-shaped surface a realm with no custody still has, and the bytes coming from a THIRD realm's state does not make them safer. Everything goes through [cell].
Rescan
func Rescan(owner address, seq int64) (entries int, netChange int64, agrees bool)
Rescan re-locates a published statement's window in the ledger NOW and reports whether the entry count and the net change still agree with what was frozen.
Why this exists and the re-derivation accessor does not
The package doc says there is no `Verify` here, because re-deriving a statement over a window that has closed is a CONSTANT TRUE: entries are dated at the chain height and the journal is append-only, so nothing can later fall into a closed window.
This is the part of that comparison which is NOT constant. Window selection is a binary search resting on a premise owned by r/books — that entry dates are non-decreasing in entry ID — and nothing freezes that premise. A future change there could break it silently. Re-locating the range and comparing is therefore a real test of the selection, and it is the only check here whose answer can change for a reason that matters.
What it costs and what it does not cover
It is a plain read, so the caller pays. It re-reads the window's entries, which is up to 2*MaxWindowEntries cross-realm reads plus the search.
It compares the entry COUNT and the NET CHANGE, not every line. The net change is the figure that moves if the entry set changed, and it is derived from the postings rather than from the policy, so a disagreement points at the ledger or the selection rather than at a reclassification. A false return means do not trust the statement and does not say which of the two it is.
Scope
func Scope(owner address, seq int64) (entries, cashEntries, netZero, unresolved int)
Scope is the statement's own coverage line, and it is the thing to read before quoting any figure above.
- `entries` is how many entries fell in the window, including the ones that moved no cash. Zero is a quiet period, not an error.
- `cashEntries` is how many touched a declared cash account at all.
- `netZero` is how many touched cash and moved none on net — a transfer between two cash accounts. Classification is not attempted for them: there is nothing to attribute.
- `unresolved` is how many could not be classified. A statement with unresolved entries is still arithmetically whole — the residual is in `unattributed` — but its SPLIT is incomplete.
Total
func Total() int
Total is how many statements every publisher has published together. There is deliberately no way to enumerate the publishers; see the package doc.
Totals
func Totals(owner address, seq int64) (opening, netChange, unattributed, closing int64)
Totals is the four cumulative figures. `closing` is `opening + netChange`, and `opening` was supplied by the publisher — see OpeningReconciled for whether this realm was able to check it.
`unattributed` is the net movement of the entries in the unresolved census and is part of `netChange`, not additional to it.
UnresolvedAt
func UnresolvedAt(owner address, seq int64, i int) (id uint64, reason string, net int64, categories, codes string)
UnresolvedAt is one unresolved entry, by index into the census.
- `reason` is `conflict` (the non-cash legs classify to more than one category), `undeclared` (a non-cash leg's account has no policy rule) or `unbalanced`.
- `net` is the entry's net cash movement, which is real movement the statement could not attribute.
- `categories` is the conflicting categories, comma-separated, for a conflict.
- `codes` is the undeclared account codes, comma-separated, for an undeclared entry. Declaring them may resolve the entry, which is why `undeclared` takes precedence over `conflict` when both apply.
UnresolvedCount
func UnresolvedCount(owner address, seq int64) int
UnresolvedCount is how many entries could not be classified.
Window
func Window(owner address, seq int64) (from, until int64)
Window is the period a statement covers, half-open: `from` is in, `until` is out.
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 | chain/runtime, chain/runtime/unsafe, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books, strconv, strings |
|---|---|
| First-party dependencies | gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods, gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/books |
Known limitations
Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.
curated- THE DECLARATION IS THE TRUST BOUNDARY. A publisher who classifies a receivable as cash gets a plausible, wrong statement, and nothing here can detect it -- not the three window checks, not Publish's cumulative cross-check, not Rescan. This is the product's defining limitation and it is inherited from p/cashflow by design rather than by oversight.
- A CLOSED WINDOW'S OPENING IS UNVERIFIABLE, and Closing = Opening + NetChange inherits that. Publish cross-checks the cumulative figure against the book's live cash balance ONLY when the window runs past the last entry; otherwise opening is echoed unchanged. OpeningReconciled(owner, seq) reading false is the disclosure, and a reader who quotes Closing without it has quoted an unchecked figure.
- AN EMPTY RANGE INSIDE A CLOSED WINDOW IS UNDER-REPORTED. All three window-soundness checks fire on an entry that was READ, so a date dip producing an empty located range leaves all three silent, and the cumulative cross-check that would catch it is the one a closed window does not run. Same hole as the opening figure, seen from the other side.
- POLICY IS REPLACEABLE (PolicyIsReplaceable), so a later policy does not retroactively reclassify a published statement; each statement carries the policy snapshot that priced it. A reader comparing two statements published under different policies is comparing two different CLASSIFICATIONS, not two periods.
- PUBLICATIONS ARE UNCAPPED (PublicationsAreUncapped) and Render shows the most recent RenderPublicationLimit = 20. A reader of the rendered page is reading a window onto the set, not the set.
- THE ~318 M GAS / ~84 ms RESCAN FIGURE IS A LOCAL-VM MEASUREMENT ON A COUNTERFACTUAL. Rescan is non-crossing, so MsgCall cannot dispatch to it and it can never enter a block; the figure describes what it WOULD cost if it were a transaction, and the three ceilings on it are in AUDIT.md section 4. It is not a chain measurement.
- DISCOVERY.md PUBLISHED A FALSE CLAIM AND AMENDMENT A IS THE CORRECTION, NOT A FOOTNOTE. Section 7.1 advertised five window-soundness checks as proving the located range maximal; two of them were negations of the binary search's own loop invariants and could never fire, on any book, under any ordering. They were deleted on a measurement with a sabotage control. The record is kept with the false sentence standing above the amendment rather than edited away.
- storage_bytes and storage_deposit are null for the reason in storage_measurement_note -- the figures ARE chain-attested (57,928 B / 5,792,800ugnot at height 474638, carried by the approver's /vm.m_enable_pkg transaction) and are withheld from these fields only because verify_catalog C2's one-block window cannot see an event four blocks later. Do not read the nulls as an unmeasured deploy.
Source
| Source file | onyx/r/cashflow/cashflow.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 |