# write-only-ledger

> Ordinary ledger Cell is explicitly written but has no Compact source read in the compilation unit.

Canonical: https://nightshade.dicksonwu.xyz/rules/write-only-ledger.html
Release: Nightshade 0.1.1 (v0.1.1)

| Field | Value |
| --- | --- |
| Rule ID | `write-only-ledger` |
| Severity | info |
| Confidence | high |
| Maturity | experimental |
| Selection | Opt-in |
| Policy intent | not required |
| Default enabled | no |
| Category | Other active opt-ins / State and collections |
| Engine facts used | resolved-model, ledger-read-write-usage, bounded-source-absence |
| Release | v0.1.1 |

## Selection and scope

Not selected by default. Select it with `--detectors` or the `detectors` list in nightshade.json. Either one replaces profile selection, so list every rule you want.

```sh
./.nightshade/node_modules/.bin/nightshade ./contracts --detectors write-only-ledger
./.nightshade/node_modules/.bin/nightshade detector show write-only-ledger
```

The first command runs only this rule; `--detectors` takes a comma-separated list. The second prints the rule's metadata from your installed version.

Severity is review priority. It does not express exploitability or impact. Confidence is detector metadata, not a probability. All built-in rules are experimental.

Compiler scope: the engine supports the pinned profiles 0.28.0, 0.29.0, 0.30.0, 0.31.1, 0.33.0-rc.2, 0.34.0, 0.35.0-rc.1 and 0.35.0, with ZKIR2 by default. Support for this rule can be narrower; the rule documentation below states any narrower scope.

## Corpus checks

Synthetic corpus checks for this rule in the 0.1.1 proof run. A check counts only with compiler acceptance and complete analysis.

| Positive detected | Corrected quiet | Lookalike quiet | Other negative quiet | Incomplete |
| ---: | ---: | ---: | ---: | ---: |
| 2/2 | 1/2 | 1/2 | 0/0 | 0 |

Deviations recorded for this rule:

| Case | Kind | Expected to observed lines | Issue |
| --- | --- | --- | --- |
| synthetic-regressions.json / write-only-ledger-70 | corrected | [3] to [3] | control hit |
| synthetic-regressions.json / write-only-ledger-70 | lookalike | [3] to [3] | control hit |

These are behavior checks on synthetic fixtures, not a precision or recall estimate. See [evaluation](https://nightshade.dicksonwu.xyz/evaluation.md#synthetic-corpus).

## Rule documentation

From https://github.com/DicksonWu654/nightshade/blob/v0.1.1/docs/detectors/write-only-ledger.md at v0.1.1.

Availability: **opt-in**; no policy required.
Severity **info**, confidence **high** for the source read/write observation.
Select with `--detectors write-only-ledger`.

Public ledger fields commonly serve DApps and indexers without a Compact source
read. The corpus confirms source facts, not actionable dead state; default
promotion requires a review showing that most matches are actionable.

Reports an ordinary Cell ledger field with explicit source writes and no source
read in the analyzed Compact compilation unit. Initialization or later writes
can make a declaration appear used while circuit logic ignores its stored value.
This extends `unused-declaration`, which treats mutation targets as references.

### Example and review scenario

For example, a configurable role can be stored while its getter returns a constant:

```compact
sealed ledger administratorRole: Bytes<32>;
constructor(role: Bytes<32>) { administratorRole = disclose(role); }
export circuit getRoleAdmin(): Bytes<32> {
  return default<Bytes<32>>;
}
```

Review whether the getter should return the setting.
This detector covers the
no-read subset; reading that field in another circuit withholds a finding even
if the getter still ignores it.

### Recommendation and limitations

Read the stored value where its circuit behavior is intended. Retain intentional
off-chain metadata and document its consumers. Off-chain indexers, wallets,
TypeScript ledger readers and external application logic are outside this scan;
the finding does not establish useless storage, a security defect, or that writes
can safely be deleted.

All represented helper and callback bodies count, including source-excluded
paths, zero-iteration callbacks and functions not reachable from an entrypoint.
Reads through snapshots, arguments, explicit methods and projections count.
Native ADTs such as Counter and Map are excluded. One finding groups concrete
canonical instances of the same declaration; any represented instance read
withholds it. Same names in distinct declarations do not merge.

Unbound generic/module bodies retain semantic instance and include context;
one instantiated sibling cannot certify another body's read absence. Defining
bindings distinguish unrelated same-name fields, and unknown references/import
aliases are screened conservatively. Uncertain source evidence or missing bodies is explicitly
rule-scoped incomplete, not a certified absence. Work/depth exhaustion discards
the pass's findings and reports incomplete; its allowance is separate from other
informational source rules and baseline transfer completion.

## Suppress a match

If a match is intentional, suppress it on the line before with the exact rule ID and a reason of at least eight characters:

```compact
// nightshade-disable-next-line write-only-ledger -- Reason this match is intentional
```

Diagnostics cannot be suppressed, and an unmatched directive for a selected rule makes analysis incomplete. See [configuration](https://nightshade.dicksonwu.xyz/configuration.md#suppress-an-intentional-match) for fingerprint suppressions and baselines.

## References

- [Rule source document at v0.1.1](https://github.com/DicksonWu654/nightshade/blob/v0.1.1/docs/detectors/write-only-ledger.md)
- [Catalog entry in rules.json](https://nightshade.dicksonwu.xyz/rules.json)
- [All rules](https://nightshade.dicksonwu.xyz/rules.md#state-and-collections)
- [Limitations](https://nightshade.dicksonwu.xyz/limitations.md)
