@midnight-ntwrk/midnight-js-protocol 5.0.0-beta.8 → 5.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -11
- package/dist/compact-js-v8-effect.d.ts +1 -0
- package/dist/compact-js-v8-effect.js +2 -0
- package/dist/compact-js-v8-effect.js.map +1 -0
- package/dist/engine.d.ts +214 -224
- package/dist/engine.js +516 -304
- package/dist/engine.js.map +1 -1
- package/dist/errors.d.ts +64 -38
- package/dist/errors.js +50 -33
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +309 -309
- package/dist/index.js +173 -121
- package/dist/index.js.map +1 -1
- package/dist/prove.d.ts +7 -5
- package/dist/prove.js +6 -4
- package/dist/prove.js.map +1 -1
- package/dist/shared/{deploy-D6yyn9P7.js → assemble-call-BB1yZMS6.js} +254 -252
- package/dist/shared/assemble-call-BB1yZMS6.js.map +1 -0
- package/package.json +11 -7
- package/dist/shared/deploy-D6yyn9P7.js.map +0 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Nothing under `src/lib/` is a build entry, so this layout is internal and no con
|
|
|
52
52
|
|
|
53
53
|
| Directory | Holds | Ledger reference |
|
|
54
54
|
|---|---|---|
|
|
55
|
-
| `lib/v8/` | the retained pre-fork era: `load.ts`, `engine.ts`, `load-engine.ts`, `
|
|
55
|
+
| `lib/v8/` | the retained pre-fork era: `load.ts`, `engine.ts`, `load-engine.ts`, `executable.ts`, `compose.ts`, `deploy.ts`, `prove.ts`, `adapt.ts` | acquires `ledger-v8`, `onchain-runtime-v3` and the 0.16 glue, always through a dynamic import |
|
|
56
56
|
| `lib/v9/` | the current era's composition arms: `compose.ts`, `wrap.ts` | links `ledger-v9` statically |
|
|
57
57
|
| `lib/shared/` | what both arms run: `ledger-version.ts`, `verifier-keys.ts`, `compose-options.ts`, `assemble-call.ts`, `compose-types.ts`, `unshielded.ts`, `contract-state.ts` | era passed in as a `LedgerVersion` parameter, never chosen here |
|
|
58
58
|
| `lib/era/` | the facade and the dispatch: `load-era.ts`, `era.ts`, `envelope.ts` | reaches both, which is the point of the layer |
|
|
@@ -61,9 +61,9 @@ The directory names say what a module is **about**, not what it links. Three con
|
|
|
61
61
|
|
|
62
62
|
- **`lib/v8/compose.ts`, `deploy.ts` and `adapt.ts` link no v8 at all.** They take the acquired module as a `ProtocolV8` parameter, which is a type. That injection is what keeps the v8 WASM out of the eager graph, and the guarantee is enforced by `dist-laziness.test.ts` and `v8-surface.test.ts` — not by this layout. Read the tests, not the directory, for the bundle boundary.
|
|
63
63
|
- **`lib/shared/` is not free of vendors.** `assemble-call.ts` links `@midnight-ntwrk/compact-js`, a post-fork package, for `hashVerifierKey` and `encodeContractKeyLocation`; `contract-state.ts` links it for `hashVerifierKey`. Both are called by both arms, so their subject is shared even though their linkage is not. This is safe in one direction only: v9 is the eagerly-linked baseline, so a shared module reaching for it never wakes v8, while the reverse would.
|
|
64
|
-
- **`lib/v9/wrap.ts` type-imports `lib/v8/
|
|
64
|
+
- **`lib/v9/wrap.ts` type-imports `lib/v8/executable.ts`.** `TranscriptPojo` is the v8 engine's output and `wrapKeepStateCall` binds it onto v9. The cross-era edge is the operation's whole purpose, and it is type-only.
|
|
65
65
|
|
|
66
|
-
`compose-types.ts` and `era.ts` name their shared types through `@midnightntwrk/ledger-v9` because some vendor has to name them. `EncodedStateValue`, `Op`, `AlignedValue` and `
|
|
66
|
+
`compose-types.ts` and `era.ts` name their shared types through `@midnightntwrk/ledger-v9` because some vendor has to name them. `EncodedStateValue`, `Op`, `AlignedValue`, `Transcript`, `CallContext` and `Effects` are pinned identical across `onchain-runtime-v3`, `ledger-v8` and `ledger-v9` by the compile-time assertions at the bottom of `v8-executable.test.ts`; the import names one era, the type belongs to neither. Those assertions are evaluated by `yarn typecheck:tests` — run by the pre-push hook AND by CI's `typecheck:tests:core` job. The test run itself does not evaluate them, because vitest transpiles test files without type-checking.
|
|
67
67
|
|
|
68
68
|
## Architecture Documents
|
|
69
69
|
|
|
@@ -82,6 +82,7 @@ docstring resolves to it.
|
|
|
82
82
|
| [Verifier keys](./docs/verifier-keys.md) | Registration rules, the refusals that stop a deploy landing at an address the caller's artifacts do not describe, and why the address cannot be recomputed |
|
|
83
83
|
| [Module graph and lazy loading](./docs/module-graph-and-lazy-loading.md) | Build entries, the `./v8` and `./engine` chunks, the import cycle avoided by a leaf module, and why a vendor's types are named with `import type` |
|
|
84
84
|
| [Injected vendor slices](./docs/injected-vendor-slices.md) | How a seam names the vendor class it takes by injection — derived from the vendor's own class, declared structurally, or narrowed — and what each choice buys |
|
|
85
|
+
| [Cross-era operation registry](./docs/cross-era-operation-registry.md) | What actually crosses the fork boundary when a dormant retained-era contract is called, why re-expressing its operations is faithful rather than a substitution, and why the primary state is left at its default |
|
|
85
86
|
| [Shared table discipline](./docs/shared-table-discipline.md) | Why the shared tables are frozen and null-prototyped, and why exhaustiveness is enforced at compile time as well as at run time |
|
|
86
87
|
|
|
87
88
|
Docstrings in `src/` carry the API contract: what a symbol does, its
|
|
@@ -117,12 +118,16 @@ import { loadLedger8Engine, loadLedgerEra, versionOfRecord } from '@midnight-ntw
|
|
|
117
118
|
const engine = await loadLedger8Engine();
|
|
118
119
|
const era = await loadLedgerEra(versionOfRecord(indexerRecord));
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
// The DECODED state, not the extracted one: the balances a circuit reads are
|
|
122
|
+
// not part of the primary state, and this value carries them alongside it.
|
|
123
|
+
// One read in, one value out -- there is no second argument that could
|
|
124
|
+
// describe a different block.
|
|
125
|
+
const contractState = era.decodeContractState(rawContractState);
|
|
126
|
+
const transcript = await engine.executeCircuit({
|
|
122
127
|
contract,
|
|
123
128
|
circuitId: 'increment',
|
|
124
129
|
args: [],
|
|
125
|
-
|
|
130
|
+
contractState,
|
|
126
131
|
address: contractAddress,
|
|
127
132
|
coinPk: coinPublicKey,
|
|
128
133
|
privateState
|
|
@@ -130,11 +135,17 @@ const transcript = engine.executeCircuit({
|
|
|
130
135
|
const prototype = engine.wrapKeepStateCall({ transcript, contractAddress, contractState: migratedV9ContractState });
|
|
131
136
|
```
|
|
132
137
|
|
|
133
|
-
|
|
138
|
+
`executeCircuit` and `executeConstructor` are **asynchronous** — `await` them. There is no separate down-convert step: pass the decoded contract state straight in, and the engine builds the state the retained runtime executes against.
|
|
139
|
+
|
|
140
|
+
Read it with the era the state's own envelope names, not with a fixed one. A contract an earlier post-fork call has MIGRATED carries a current-era envelope while its artifacts stay the retained toolchain's, so `era` above is the head's era for that contract and the retained era for one that has not migrated. Handing the retained reader a current-era envelope is refused on the tag.
|
|
141
|
+
|
|
142
|
+
`contractState` must carry a usable `balance`. It is required rather than defaulted, because an empty balance is a legitimate value — a contract holding nothing has one — and defaulting would make "holds nothing" indistinguishable from "the caller forgot", which is a circuit reading zero where the chain says otherwise. `decodeContractState` produces both halves for you; assembling the value by hand is what the refusal is there for.
|
|
143
|
+
|
|
144
|
+
The engine exposes `executeCircuit`, `executeConstructor` and `wrapKeepStateCall`, and they form a pipeline, each result being the next call's input. Reading a contract state and composing a call or a deploy are **not** here: both eras do those, so they live on the [ledger-era facade](#ledger-era-facade) instead.
|
|
134
145
|
|
|
135
146
|
`migratedV9ContractState` passed to `wrapKeepStateCall` must be the migrated v9 state **as read from chain**, which is not `rawContractState` above: it is where the deployed operation and its verifier key come from, and the key location the prototype carries is derived from that key. A blank or constructor-built state throws `ComposeFailedError` (code `MIDNIGHT_JS_P_COMPOSE_FAILED`) with `stage` naming which lookup failed and `version` naming the ledger era it was composing for.
|
|
136
147
|
|
|
137
|
-
Circuits with Zswap coin effects run on this leg like any other. The transcript carries `zswapLocalState` — the post-call Zswap local state, decoded into the runtime's public shape — which is what you turn into the transaction's segmented Zswap offer (`zswapStateToSegmentedOffer` in `@midnight-ntwrk/midnight-js-contracts`)
|
|
148
|
+
Circuits with Zswap coin effects run on this leg like any other. The transcript carries `zswapLocalState` — the post-call Zswap local state, decoded into the runtime's public shape — which is what you turn into the transaction's segmented Zswap offer (`zswapStateToSegmentedOffer` in `@midnight-ntwrk/midnight-js-contracts`). You do not hand that offer to `composeCallTx` as ready-made bytes: you hand it a `zswapOffer` factory, which the composer calls back with each call's guaranteed/fallible split once it has drawn it. That split is the fourth argument `zswapStateToSegmentedOffer` routes by — without it every movement lands in the guaranteed segment, and a circuit whose transcript is wholly fallible produces an offer the wallet cannot balance. Omitting the factory altogether leaves you composing a transaction missing the coin movements the circuit recorded.
|
|
138
149
|
|
|
139
150
|
The transcript also carries `partitionContext` — the block, the starting effects and the commitment indices the pre-fork query context recorded while the circuit ran. Pass it on unchanged: a transcript composed without it is partitioned against a context the circuit never ran on, and a circuit that RECEIVED a coin in-contract cannot be partitioned at all, because the index its commitment was registered at lives only in that context. `wrapKeepStateCall` carries it for you; a hand-built call entry has to supply it. A context the target era cannot read throws `ComposeFailedError` with `stage: 'call-partition-context'`.
|
|
140
151
|
|
|
@@ -151,7 +162,17 @@ const era = await loadLedgerEra(versionOfRecord(indexerRecord));
|
|
|
151
162
|
|
|
152
163
|
const state = era.extractState(rawContractState);
|
|
153
164
|
const decoded = era.decodeContractState(rawContractState);
|
|
154
|
-
const
|
|
165
|
+
const { transaction, partitions } = era.composeCallTx({
|
|
166
|
+
calls,
|
|
167
|
+
networkId,
|
|
168
|
+
ttl,
|
|
169
|
+
// Called back ONCE, with one `[guaranteed, fallible]` pair per call, in
|
|
170
|
+
// `calls` order -- cross-contract callees first, the root call last. Route
|
|
171
|
+
// against all of them: a transaction carries one offer per segment, so
|
|
172
|
+
// destructuring the first pair alone would route the whole transaction
|
|
173
|
+
// against a callee's split and drop the root call's.
|
|
174
|
+
zswapOffer: (partitions) => buildSegmentedOfferBytes(partitions)
|
|
175
|
+
});
|
|
155
176
|
const deploy = era.composeDeployTx({ contractState, verifierKeys, networkId, ttl });
|
|
156
177
|
```
|
|
157
178
|
|
|
@@ -160,7 +181,7 @@ const deploy = era.composeDeployTx({ contractState, verifierKeys, networkId, ttl
|
|
|
160
181
|
| `version` | The era this object is bound to — the value that was passed in |
|
|
161
182
|
| `extractState` | Reads the primary state out of a raw contract-state envelope |
|
|
162
183
|
| `decodeContractState` | Reads an envelope into its state plus the entry points it declares, each with its verifier key and that key's hash |
|
|
163
|
-
| `composeCallTx` | Composes an UNPROVEN call transaction and
|
|
184
|
+
| `composeCallTx` | Composes an UNPROVEN call transaction and answers with its bytes plus each call's guaranteed/fallible split |
|
|
164
185
|
| `composeDeployTx` | Composes an UNPROVEN deploy and returns it with the address it will have and the initial state that address came from |
|
|
165
186
|
|
|
166
187
|
Derive the era with `versionOfRecord` or `networkHeadVersion` (see [Version Module](#version-module)) rather than writing the string by hand. An era string that is not `'v8'` or `'v9'` rejects with `UnknownLedgerVersionError`; the offending value is on the error's `requestedVersion` field, not in its message.
|
|
@@ -179,7 +200,7 @@ The same method names mostly mean the same capabilities. One thing the v8 arm re
|
|
|
179
200
|
|
|
180
201
|
- **A call tree.** The v8 arm composes exactly one call. A cross-contract call is a ledger-9-only feature a pre-fork contract cannot emit, so that era has no call tree to express: a `calls` list longer than one throws `ComposeOptionError` with `option: 'calls'` rather than composing the first entry and dropping the rest.
|
|
181
202
|
|
|
182
|
-
**A Zswap offer is not one of them.** Both eras
|
|
203
|
+
**A Zswap offer is not one of them.** Both eras call the `zswapOffer` factory back with the split they resolved and carry the offer it answers with into the transaction; both throw `ComposeOptionError` with `option: 'zswapOffer'` for bytes their own decoder rejects, with the decoder's failure on `cause`. Both read that offer LAST, after the call's unshielded payout has been aggregated, so a call with two faults is refused the same way on either era. A coin-moving call composes on either era.
|
|
183
204
|
|
|
184
205
|
The v8 arm also *requires* `verifierKeys` on `composeDeployTx`, where the v9 arm accepts its omission in one case. The retained deploy leg registers the compiled contract's keys onto the initial state itself, so it always needs the map; omitting it throws `ComposeOptionError` with `option: 'verifierKeys'`. The v9 arm allows the omission only for a state that ALREADY carries its keys, and checks rather than assumes it: a state still declaring a blank-keyed entry point throws the same `ComposeOptionError` with the same `option`. So the two arms agree on every input except one — a pre-keyed state, which deploys as-is on v9 and needs its keys supplied again on v8.
|
|
185
206
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@midnight-ntwrk/compact-js/v8/effect';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compact-js-v8-effect.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
import * as ledgerV9 from '@midnightntwrk/ledger-v9';
|
|
2
|
-
import { CallContext, Effects, CoinCommitment,
|
|
2
|
+
import { TokenType, EncodedStateValue, CallContext, Effects, CoinCommitment, ContractCallPrototype } from '@midnightntwrk/ledger-v9';
|
|
3
3
|
export { EncodedStateValue } from '@midnightntwrk/ledger-v9';
|
|
4
|
-
import
|
|
5
|
-
import { EncodedZswapLocalState, ProofData, ZswapLocalState } from 'compact-runtime-ledger8';
|
|
4
|
+
import { ContractExecutable } from '@midnight-ntwrk/compact-js/v8/effect';
|
|
6
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The public balances a contract holds, keyed by token type.
|
|
8
|
+
*
|
|
9
|
+
* Derived from the vendor's own `TokenType` rather than restated, so a rename
|
|
10
|
+
* fails this build instead of leaving a mirror describing a shape neither
|
|
11
|
+
* runtime has.
|
|
12
|
+
*
|
|
13
|
+
* `shared-contract-state.test.ts` pins all three `TokenType` declarations —
|
|
14
|
+
* both eras' and the execution runtime's, which is where the map actually
|
|
15
|
+
* lands — mutually assignable. That pin is STRUCTURAL and can be no more:
|
|
16
|
+
* `raw` is an opaque `string` everywhere, so assignability says the handoff
|
|
17
|
+
* type-checks and says nothing about two eras reading a given key as the same
|
|
18
|
+
* colour.
|
|
19
|
+
*
|
|
20
|
+
* Plain data end to end — string-tagged objects and `bigint`s — so it crosses
|
|
21
|
+
* an era boundary like every other member of {@link ContractStatePojo}. That is
|
|
22
|
+
* a statement about `structuredClone`, which carries a `Map`; a JSON round trip
|
|
23
|
+
* does NOT, and leaves the plain object the balance guards refuse.
|
|
24
|
+
*/
|
|
25
|
+
type ContractBalance = ReadonlyMap<TokenType, bigint>;
|
|
7
26
|
/**
|
|
8
27
|
* One entry point a contract state declares, with the verifier key registered
|
|
9
28
|
* against it if there is one.
|
|
@@ -19,6 +38,27 @@ interface ContractEntryPointPojo {
|
|
|
19
38
|
readonly verifierKey: Uint8Array | undefined;
|
|
20
39
|
readonly verifierKeyHash: string | undefined;
|
|
21
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* A contract state as plain data: the primary state in its encoded form, the
|
|
43
|
+
* balances the contract holds, and the entry points the state declares.
|
|
44
|
+
*
|
|
45
|
+
* `entryPoints` is an ARRAY, not a map keyed by circuit id: two distinct byte
|
|
46
|
+
* entry points can decode to the same name, and a caller has to reconcile
|
|
47
|
+
* them.
|
|
48
|
+
*
|
|
49
|
+
* @see {@link FailClosedDecoding}
|
|
50
|
+
*/
|
|
51
|
+
interface ContractStatePojo {
|
|
52
|
+
readonly state: EncodedStateValue;
|
|
53
|
+
/**
|
|
54
|
+
* The balances the contract holds, which are NOT part of the primary state:
|
|
55
|
+
* the ledger keeps them beside it, so a caller reading only `state` cannot
|
|
56
|
+
* reach them. A retained-era call that executes without them runs every
|
|
57
|
+
* circuit against an empty balance — see {@link RetainedEraExecution}.
|
|
58
|
+
*/
|
|
59
|
+
readonly balance: ContractBalance;
|
|
60
|
+
readonly entryPoints: readonly ContractEntryPointPojo[];
|
|
61
|
+
}
|
|
22
62
|
|
|
23
63
|
/**
|
|
24
64
|
* The query-context state a call recorded while it ran, which its pre-call
|
|
@@ -67,174 +107,150 @@ declare const INITIAL_LEDGER_PARAMETERS = "initial";
|
|
|
67
107
|
*/
|
|
68
108
|
type LedgerParametersOption = Uint8Array | typeof INITIAL_LEDGER_PARAMETERS;
|
|
69
109
|
|
|
70
|
-
|
|
71
|
-
type
|
|
110
|
+
/** A retained-era signing key: 32 bytes of hex. @see {@link LEDGER8_SIGNING_KEY_PATTERN} */
|
|
111
|
+
type Ledger8SigningKey = string;
|
|
72
112
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* here puts a second copy of the retained runtime on any module graph — the
|
|
82
|
-
* loaders remain the only runtime path to it.
|
|
113
|
+
* A retained-era contract, as the caller holds it: the CONSTRUCTED artifact the
|
|
114
|
+
* previous toolchain generates.
|
|
115
|
+
*
|
|
116
|
+
* Deliberately not compact-js's `CompiledContract` container. That container is
|
|
117
|
+
* a recipe — a class plus witnesses, instantiated fresh per operation — and the
|
|
118
|
+
* retained era's callers hold an instance whose witnesses are already bound.
|
|
119
|
+
* {@link containerFor} adapts one to the other at this seam, which keeps the
|
|
120
|
+
* adaptation in one place instead of on every consumer.
|
|
83
121
|
*/
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
* The result of a down-convert: only the primary state data a pre-fork
|
|
90
|
-
* circuit reads during execution.
|
|
91
|
-
*
|
|
92
|
-
* Deliberately not a full pre-fork `ContractState`: it carries no
|
|
93
|
-
* `.operations`, `.maintenanceAuthority` or `.balance`, which remain the
|
|
94
|
-
* caller's to carry.
|
|
95
|
-
*
|
|
96
|
-
* @see {@link RetainedEraExecution}
|
|
97
|
-
*/
|
|
98
|
-
interface DownConvertedState {
|
|
99
|
-
readonly data: ChargedState;
|
|
122
|
+
interface RetainedContract {
|
|
123
|
+
/** The map compact-js indexes to find the circuit to run. */
|
|
124
|
+
readonly provableCircuits: Readonly<Record<string, unknown>>;
|
|
125
|
+
/** The constructor a deployment runs. */
|
|
126
|
+
initialState(...args: never[]): unknown;
|
|
100
127
|
}
|
|
101
128
|
|
|
102
|
-
type AlignedValue = OnchainRuntimeV3.AlignedValue;
|
|
103
|
-
type Op<T> = OnchainRuntimeV3.Op<T>;
|
|
104
|
-
|
|
105
129
|
/**
|
|
106
|
-
* The
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* partition the call's public transcript against the context it really ran on
|
|
110
|
-
* (see {@link PartitionContext}).
|
|
130
|
+
* The result of one retained-era circuit call: every artifact
|
|
131
|
+
* {@link wrapKeepStateCall} (`../v9/wrap.ts`) needs to assemble a v9-native
|
|
132
|
+
* `ContractCallPrototype`.
|
|
111
133
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* `QueryContext` is a WASM class with dozens of members, and this seam reads
|
|
117
|
-
* four — the narrowing is what lets the execution tests hand `executeCircuit` a
|
|
118
|
-
* plain object double instead of standing up real WASM.
|
|
119
|
-
*
|
|
120
|
-
* @see {@link RetainedEraExecution}
|
|
121
|
-
*/
|
|
122
|
-
type Ledger8QueryContext = Pick<OnchainRuntimeV3.QueryContext, 'state' | 'block' | 'effects' | 'comIndices'>;
|
|
123
|
-
/**
|
|
124
|
-
* The circuit-context slice {@link executeCircuit} needs from a pre-fork
|
|
125
|
-
* (`compact-runtime@0.16`) `createCircuitContext` call and a circuit's
|
|
126
|
-
* updated context after it runs.
|
|
127
|
-
*
|
|
128
|
-
* Not the glue's `CircuitContext` itself: that one carries a real
|
|
129
|
-
* `QueryContext`, a WASM class no test double can satisfy, and this seam reads
|
|
130
|
-
* a handful of properties off it. The narrowing is the difference between
|
|
131
|
-
* execution tests that check plumbing with a literal and tests that must stand
|
|
132
|
-
* up WASM to do it — see {@link Ledger8QueryContext}, which derives those
|
|
133
|
-
* properties from the vendor's class so the narrowing cannot drift from it.
|
|
134
|
-
*
|
|
135
|
-
* @see {@link RetainedEraExecution}
|
|
136
|
-
*/
|
|
137
|
-
interface Ledger8CircuitContext {
|
|
138
|
-
readonly currentQueryContext: Ledger8QueryContext;
|
|
139
|
-
readonly currentPrivateState: unknown;
|
|
140
|
-
readonly currentZswapLocalState: EncodedZswapLocalState;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* The proof-data slice of a pre-fork {@link Ledger8CircuitResult}.
|
|
144
|
-
*
|
|
145
|
-
* The vendor's own `ProofData`, not a copy of its four members: it is already
|
|
146
|
-
* plain data with no WASM handle in it, so there was nothing for a mirror to
|
|
147
|
-
* narrow — only a shape to drift out of sync. `Readonly` is this package's own
|
|
148
|
-
* addition, and the only one.
|
|
149
|
-
*
|
|
150
|
-
* @see {@link EraSeam}
|
|
151
|
-
*/
|
|
152
|
-
type Ledger8ProofData = Readonly<ProofData>;
|
|
153
|
-
/**
|
|
154
|
-
* What a pre-fork `impureCircuits[id](ctx, ...args)` call returns.
|
|
155
|
-
*
|
|
156
|
-
* Tracks the glue's `CircuitResults` but is not derived from it: `context` is
|
|
157
|
-
* the narrowed {@link Ledger8CircuitContext} for the reason given there, and
|
|
158
|
-
* `proofData` IS the vendor's own type — see {@link Ledger8ProofData}.
|
|
159
|
-
*
|
|
160
|
-
* @see {@link RetainedEraExecution}
|
|
161
|
-
*/
|
|
162
|
-
interface Ledger8CircuitResult {
|
|
163
|
-
readonly result: unknown;
|
|
164
|
-
readonly proofData: Ledger8ProofData;
|
|
165
|
-
readonly context: Ledger8CircuitContext;
|
|
166
|
-
}
|
|
167
|
-
/** One callable entry point on a compiled pre-fork contract's `impureCircuits` map. */
|
|
168
|
-
type Ledger8ImpureCircuit = (ctx: Ledger8CircuitContext, ...args: readonly unknown[]) => Ledger8CircuitResult;
|
|
169
|
-
/**
|
|
170
|
-
* The subset of a compiled pre-fork (`compact-runtime@0.16`) contract module
|
|
171
|
-
* {@link executeCircuit} needs: only `impureCircuits`, the map every
|
|
172
|
-
* generated contract exposes its callable entry points under.
|
|
173
|
-
*
|
|
174
|
-
* @see {@link RetainedEraExecution}
|
|
175
|
-
*/
|
|
176
|
-
interface Ledger8ContractLike {
|
|
177
|
-
readonly impureCircuits: Readonly<Record<string, Ledger8ImpureCircuit>>;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* The result of one impure circuit's invocation on a pre-fork
|
|
181
|
-
* (`compact-runtime@0.16`) contract instance: the primary result plus every
|
|
182
|
-
* artifact {@link wrapKeepStateCall} (`../v9/wrap.ts`) needs to assemble a
|
|
183
|
-
* v9-native `ContractCallPrototype`.
|
|
184
|
-
*
|
|
185
|
-
* `preContractState`/`postContractState` are {@link DownConvertedState}s, not
|
|
186
|
-
* full pre-fork `ContractState`s: they carry only `.data`. They are also the
|
|
187
|
-
* only members here that are LIVE HANDLES -- every other member of this type is
|
|
188
|
-
* plain data. `postContractStateEncoded` is the post-state's encoded form,
|
|
189
|
-
* carried beside the handle so a caller has a value that outlives the runtime
|
|
190
|
-
* instance; the pre-state's encoded form is the caller's own input, which
|
|
191
|
-
* `downConvertForExecution` already proves round-trips to it.
|
|
192
|
-
*
|
|
193
|
-
* `partitionContext` is the query-context state the call ran with, which the
|
|
194
|
-
* carried state bytes do not hold — see {@link PartitionContext}. A composition
|
|
195
|
-
* leg needs it to partition the call's transcript.
|
|
196
|
-
*
|
|
197
|
-
* `zswapLocalState` is the post-call Zswap local state, DECODED into the
|
|
198
|
-
* runtime's public shape: the coins the circuit spent and produced. A caller
|
|
199
|
-
* turns it into the transaction's segmented Zswap offer
|
|
200
|
-
* (`zswapStateToSegmentedOffer`, `packages/contracts/src/internal/utils/zswap-utils.ts`)
|
|
201
|
-
* and hands that offer to whichever composition leg it targets.
|
|
134
|
+
* `preContractState` and `postContractState` are LIVE HANDLES from the retained
|
|
135
|
+
* runtime; every other member is plain data. `postContractStateEncoded` is the
|
|
136
|
+
* post-state's encoded form, carried beside the handle so a caller has a value
|
|
137
|
+
* that outlives the runtime instance.
|
|
202
138
|
*
|
|
203
139
|
* @see {@link RetainedEraExecution}
|
|
204
140
|
*/
|
|
205
141
|
interface TranscriptPojo {
|
|
206
142
|
readonly circuitId: string;
|
|
207
143
|
readonly result: unknown;
|
|
208
|
-
readonly input:
|
|
209
|
-
readonly output:
|
|
210
|
-
readonly publicTranscript:
|
|
211
|
-
readonly privateTranscriptOutputs:
|
|
212
|
-
|
|
213
|
-
|
|
144
|
+
readonly input: ContractExecutable.ContractExecutable.ContractCallPrivate['input'];
|
|
145
|
+
readonly output: ContractExecutable.ContractExecutable.ContractCallPrivate['output'];
|
|
146
|
+
readonly publicTranscript: ContractExecutable.ContractExecutable.ContractCallPublic['publicTranscript'];
|
|
147
|
+
readonly privateTranscriptOutputs: ContractExecutable.ContractExecutable.ContractCallPrivate['privateTranscriptOutputs'];
|
|
148
|
+
/**
|
|
149
|
+
* The state the call BOUND to, i.e. `partitionInputs.state`.
|
|
150
|
+
*
|
|
151
|
+
* Indistinguishable from {@link TranscriptPojo.postContractState} BY TYPE:
|
|
152
|
+
* compact-js resolves both to the same declaration, so swapping the two is
|
|
153
|
+
* not a compile error. Partitioning against the wrong one rejects a state the
|
|
154
|
+
* transcript's reads do not fit, or silently mis-charges one that merely
|
|
155
|
+
* differs in value, so the distinction rests on the member name alone.
|
|
156
|
+
*/
|
|
157
|
+
readonly preContractState: ContractExecutable.ContractExecutable.CallPartitionInputs['state'];
|
|
158
|
+
/**
|
|
159
|
+
* The state the call LEFT, i.e. compact-js's own `contractState`.
|
|
160
|
+
*
|
|
161
|
+
* See {@link TranscriptPojo.preContractState}: the two are the same type.
|
|
162
|
+
*/
|
|
163
|
+
readonly postContractState: ContractExecutable.ContractExecutable.ContractCallPublic['contractState'];
|
|
214
164
|
/**
|
|
215
165
|
* The post-call state as an {@link EncodedStateValue}: the same value
|
|
216
166
|
* {@link postContractState} holds, in the form that survives this process.
|
|
217
167
|
*
|
|
218
|
-
* `EncodedStateValue` is pinned identical across
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* the member era-agnostic code reads, and the one that can be persisted,
|
|
222
|
-
* cloned or sent to a worker.
|
|
168
|
+
* `EncodedStateValue` is pinned identical across the retained and current
|
|
169
|
+
* runtimes, so this is the member era-agnostic code reads, and the one that
|
|
170
|
+
* can be persisted, cloned or sent to a worker.
|
|
223
171
|
*/
|
|
224
172
|
readonly postContractStateEncoded: EncodedStateValue;
|
|
225
173
|
readonly privateStateAfter: unknown;
|
|
226
174
|
readonly partitionContext: PartitionContext;
|
|
227
|
-
readonly zswapLocalState:
|
|
175
|
+
readonly zswapLocalState: ContractExecutable.ContractExecutable.DeployResultPrivate<unknown>['zswapLocalState'];
|
|
176
|
+
}
|
|
177
|
+
/** The freshly built state a retained-era constructor produced. */
|
|
178
|
+
interface ConstructorResultPojo {
|
|
179
|
+
readonly contractState: ContractExecutable.ContractExecutable.DeployResultPublic['contractState'];
|
|
180
|
+
readonly privateState: unknown;
|
|
181
|
+
readonly zswapLocalState: ContractExecutable.ContractExecutable.DeployResultPrivate<unknown>['zswapLocalState'];
|
|
182
|
+
/**
|
|
183
|
+
* The key the state's maintenance authority was built from: the caller's own
|
|
184
|
+
* when one was named, otherwise the one compact-js sampled.
|
|
185
|
+
*
|
|
186
|
+
* REQUIRED, unlike the request's, and that asymmetry is the point — a sampled
|
|
187
|
+
* key exists nowhere else, so a result that did not report it would leave the
|
|
188
|
+
* deployment as unmaintainable as the empty committee the retained
|
|
189
|
+
* constructor writes on its own.
|
|
190
|
+
*/
|
|
191
|
+
readonly signingKey: Ledger8SigningKey;
|
|
228
192
|
}
|
|
229
|
-
/**
|
|
230
|
-
|
|
231
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Reads verifier keys for the entry points a contract declares.
|
|
195
|
+
*
|
|
196
|
+
* A plain function rather than compact-js's `ZKConfiguration` service, so no
|
|
197
|
+
* Effect type reaches this package's callers; {@link zkConfigurationLayer}
|
|
198
|
+
* adapts it at the seam.
|
|
199
|
+
*/
|
|
200
|
+
type VerifierKeyReader = (provableCircuitId: string) => Promise<Uint8Array | undefined>;
|
|
201
|
+
/** Everything {@link runRetainedCircuit} needs to run one circuit. */
|
|
202
|
+
interface RunRetainedCircuitOptions<C extends RetainedContract, PS> {
|
|
203
|
+
readonly contract: C;
|
|
232
204
|
readonly circuitId: string;
|
|
233
205
|
readonly args: readonly unknown[];
|
|
234
|
-
|
|
206
|
+
/**
|
|
207
|
+
* The contract state the call runs against, DECODED — the primary state in
|
|
208
|
+
* its era-neutral encoded form, with the balances the contract holds beside
|
|
209
|
+
* it.
|
|
210
|
+
*
|
|
211
|
+
* Era-neutral rather than one era's serialized bytes, because a contract that
|
|
212
|
+
* an earlier post-fork call has already migrated carries a CURRENT-era
|
|
213
|
+
* envelope while still executing on the retained runtime. Chain bytes would
|
|
214
|
+
* have to be decoded by the era that wrote them; this shape is what both
|
|
215
|
+
* envelopes' readers produce, so one path serves a pre-fork contract and a
|
|
216
|
+
* migrated one alike.
|
|
217
|
+
*
|
|
218
|
+
* The balance travels on the same value, so the two halves cannot come off
|
|
219
|
+
* different reads. {@link executableStateFrom} writes it onto a whole
|
|
220
|
+
* `ContractState`, which is the only form the retained runtime reads a
|
|
221
|
+
* balance from — handed a bare state value it substitutes an empty map, and a
|
|
222
|
+
* circuit reading a balance then executes against nothing with every guard
|
|
223
|
+
* green. That substitution is what #1345 was.
|
|
224
|
+
*/
|
|
225
|
+
readonly contractState: ContractStatePojo;
|
|
235
226
|
readonly address: string;
|
|
236
227
|
readonly coinPk: string;
|
|
237
|
-
readonly privateState:
|
|
228
|
+
readonly privateState: PS;
|
|
229
|
+
/**
|
|
230
|
+
* The execution clock, in SECONDS since the epoch. Omitted, the wall clock is
|
|
231
|
+
* used.
|
|
232
|
+
*
|
|
233
|
+
* Lands on the query context's `block.secondsSinceEpoch`, which
|
|
234
|
+
* {@link TranscriptPojo.partitionContext} reports — so without pinning it a
|
|
235
|
+
* recorded fixture differs on every run
|
|
236
|
+
* (midnightntwrk/midnight-sdk#403).
|
|
237
|
+
*/
|
|
238
|
+
readonly nowSeconds?: number;
|
|
239
|
+
}
|
|
240
|
+
/** Everything {@link runRetainedConstructor} needs to run one constructor. */
|
|
241
|
+
interface RunRetainedConstructorOptions<C extends RetainedContract, PS> {
|
|
242
|
+
readonly contract: C;
|
|
243
|
+
readonly args: readonly unknown[];
|
|
244
|
+
readonly privateState: PS;
|
|
245
|
+
readonly coinPk: string;
|
|
246
|
+
/**
|
|
247
|
+
* The key the contract's maintenance authority is built from. Optional:
|
|
248
|
+
* compact-js samples one when it is absent, and the result reports whichever
|
|
249
|
+
* was used.
|
|
250
|
+
*/
|
|
251
|
+
readonly signingKey?: Ledger8SigningKey;
|
|
252
|
+
/** Reads the verifier key for each entry point the constructor registers. */
|
|
253
|
+
readonly verifierKeys: VerifierKeyReader;
|
|
238
254
|
}
|
|
239
255
|
|
|
240
256
|
/**
|
|
@@ -262,75 +278,48 @@ interface WrapKeepStateCallOptions {
|
|
|
262
278
|
}
|
|
263
279
|
|
|
264
280
|
/**
|
|
265
|
-
* The
|
|
266
|
-
*
|
|
267
|
-
* returns on its result, and `.serialize()` is how a caller turns that handle
|
|
268
|
-
* into the bytes every deploy leg takes.
|
|
281
|
+
* The public surface {@link createLedger8Engine} builds: the retained pre-fork
|
|
282
|
+
* EXECUTION capabilities.
|
|
269
283
|
*
|
|
270
|
-
*
|
|
284
|
+
* The two execution members are ASYNCHRONOUS. They used to be synchronous, and
|
|
285
|
+
* the docblock here used to promise it: compact-js builds a circuit call on
|
|
286
|
+
* `Effect.tryPromise`, so `runSync` cannot discharge it and the promise is not
|
|
287
|
+
* satisfiable. Nothing else about the surface changed shape -- every value
|
|
288
|
+
* crossing it is still plain data or an era handle, and no `Effect` reaches a
|
|
289
|
+
* caller.
|
|
271
290
|
*
|
|
272
|
-
* @see {@link DualInstantiationGuard} for why that crossing is the one a
|
|
273
|
-
* duplicate install cannot affect
|
|
274
291
|
* @see {@link EraSeam}
|
|
275
292
|
*/
|
|
276
|
-
|
|
277
|
-
/** What a pre-fork `contract.initialState(constructorContext, ...args)` call returns. */
|
|
278
|
-
interface Ledger8ConstructorResult {
|
|
279
|
-
readonly currentContractState: Ledger8DeployableContractState;
|
|
280
|
-
readonly currentPrivateState: unknown;
|
|
293
|
+
interface Ledger8Engine {
|
|
281
294
|
/**
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
295
|
+
* Runs one circuit against the decoded contract state the chain serves.
|
|
296
|
+
*
|
|
297
|
+
* Takes the state and the balances beside it as ONE value -- see
|
|
298
|
+
* {@link RunRetainedCircuitOptions.contractState} -- because the balances a
|
|
299
|
+
* circuit reads do not live inside the primary state, and two separate
|
|
300
|
+
* options could describe two different blocks.
|
|
301
|
+
*
|
|
302
|
+
* @throws DownConvertFailedError At stage `'state down-convert'` when the
|
|
303
|
+
* state cannot be decoded, does not re-encode to its source, or carries a
|
|
304
|
+
* balance the retained runtime cannot read.
|
|
305
|
+
* @throws Error When the contract declares no circuit of that name.
|
|
285
306
|
*/
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* The subset of a compiled pre-fork (`compact-runtime@0.16`) contract module
|
|
290
|
-
* {@link executeConstructor} needs: only `initialState`, the constructor
|
|
291
|
-
* every generated contract exposes to build its initial ledger state.
|
|
292
|
-
*/
|
|
293
|
-
interface Ledger8ConstructorContractLike {
|
|
294
|
-
readonly initialState: (constructorContext: unknown, ...args: readonly unknown[]) => Ledger8ConstructorResult;
|
|
295
|
-
}
|
|
296
|
-
/** Everything {@link executeConstructor} needs to run one contract constructor. */
|
|
297
|
-
interface ExecuteConstructorOptions {
|
|
298
|
-
readonly contract: Ledger8ConstructorContractLike;
|
|
299
|
-
readonly args: readonly unknown[];
|
|
300
|
-
readonly privateState: unknown;
|
|
301
|
-
readonly coinPk: string;
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* The result of running a pre-fork constructor: the freshly built contract
|
|
305
|
-
* state (still carrying blank verifier keys on every operation slot — see
|
|
306
|
-
* {@link composeV8DeployTx}) and the resulting private state.
|
|
307
|
-
*/
|
|
308
|
-
interface ConstructorResultPojo {
|
|
309
|
-
readonly contractState: Ledger8DeployableContractState;
|
|
310
|
-
readonly privateState: unknown;
|
|
307
|
+
executeCircuit<C extends RetainedContract, PS>(options: RunRetainedCircuitOptions<C, PS>): Promise<TranscriptPojo>;
|
|
311
308
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
309
|
+
* Runs one retained-era constructor and returns the state it built.
|
|
310
|
+
*
|
|
311
|
+
* ASYNCHRONOUS for the same reason {@link Ledger8Engine.executeCircuit} is.
|
|
312
|
+
* Stricter than the leg it replaced: compact-js registers a verifier key
|
|
313
|
+
* against every declared entry point and refuses a missing one, where the
|
|
314
|
+
* hand-written constructor left every slot blank.
|
|
315
|
+
*
|
|
316
|
+
* @throws ComposeOptionError Naming option `'signingKey'` when a supplied key
|
|
317
|
+
* is not the 32 bytes of hex the retained runtime reads.
|
|
318
|
+
* @throws ComposeFailedError At stage `'deploy-verifier-key-blob'` when the
|
|
319
|
+
* ledger refuses the bytes served for a circuit.
|
|
315
320
|
*/
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* The public surface {@link createLedger8Engine} builds: the retained pre-fork
|
|
321
|
-
* EXECUTION capabilities, with the 0.16 runtime instance already captured in
|
|
322
|
-
* closure — no method here takes a runtime or module parameter.
|
|
323
|
-
*
|
|
324
|
-
* Every method is synchronous: this object is handed over only after the
|
|
325
|
-
* retained toolchain has been acquired.
|
|
326
|
-
*
|
|
327
|
-
* @see {@link EraSeam}
|
|
328
|
-
*/
|
|
329
|
-
interface Ledger8Engine {
|
|
330
|
-
downConvertForExecution(state: EncodedStateValue): DownConvertedState;
|
|
331
|
-
executeCircuit(options: ExecuteCircuitOptions): TranscriptPojo;
|
|
321
|
+
executeConstructor<C extends RetainedContract, PS>(options: RunRetainedConstructorOptions<C, PS>): Promise<ConstructorResultPojo>;
|
|
332
322
|
wrapKeepStateCall(options: WrapKeepStateCallOptions): ContractCallPrototype;
|
|
333
|
-
executeConstructor(options: ExecuteConstructorOptions): ConstructorResultPojo;
|
|
334
323
|
/**
|
|
335
324
|
* Re-expresses a retained-era contract's entry points as a current-era contract state, so a
|
|
336
325
|
* keep-state call has an operation registry the current composer can read.
|
|
@@ -341,27 +330,28 @@ interface Ledger8Engine {
|
|
|
341
330
|
reexpressOperationsForCurrentEra(entryPoints: readonly ContractEntryPointPojo[]): Uint8Array;
|
|
342
331
|
}
|
|
343
332
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
* `
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
333
|
+
* Builds a {@link Ledger8Engine}.
|
|
334
|
+
*
|
|
335
|
+
* The retained toolchain is no longer acquired here: compact-js's era-pinned
|
|
336
|
+
* ledger-8 entries own it, and `lib/v8/executable.ts` reaches them. What this
|
|
337
|
+
* function still does is sit behind the dynamic `import('../../engine.js')` in
|
|
338
|
+
* `lib/v8/load-engine.ts`, so importing the package root never pulls the
|
|
339
|
+
* multi-megabyte retained WASM onto the module graph.
|
|
340
|
+
*
|
|
341
|
+
* It no longer runs a dual-instantiation guard either. That guard compared
|
|
342
|
+
* `onchain-runtime-v3` against the 0.16 glue at construction time. Both
|
|
343
|
+
* acquisition paths are still live — `lib/v8/executable.ts` imports the glue
|
|
344
|
+
* directly, and compact-js resolves the same specifier for itself — so the axis
|
|
345
|
+
* did not go away; only the runtime check did. The invariant is held at install
|
|
346
|
+
* time instead, by `src/test/single-instance.test.ts`, which pins one resolved
|
|
347
|
+
* copy of `onchain-runtime-v3` and of each compact-runtime era line. That is a
|
|
348
|
+
* check on THIS repo's lockfile, not on a consumer's process.
|
|
349
|
+
*
|
|
350
|
+
* @returns The engine surface.
|
|
360
351
|
* @see {@link EraSeam}
|
|
361
|
-
* @see {@link DualInstantiationGuard}
|
|
362
352
|
* @see {@link ModuleGraphAndLazyLoading}
|
|
363
353
|
*/
|
|
364
354
|
declare const createLedger8Engine: () => Promise<Ledger8Engine>;
|
|
365
355
|
|
|
366
356
|
export { createLedger8Engine };
|
|
367
|
-
export type { ConstructorResultPojo,
|
|
357
|
+
export type { ConstructorResultPojo, ContractBalance, ContractEntryPointPojo, ContractStatePojo, Ledger8Engine, Ledger8SigningKey, RetainedContract, RunRetainedCircuitOptions, RunRetainedConstructorOptions, TranscriptPojo, VerifierKeyReader, WrapKeepStateCallOptions };
|