@sm-lab/recipes 0.2.0
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 +164 -0
- package/dist/cl-activate-CPqr6v_D.mjs +653 -0
- package/dist/cl-activate-CPqr6v_D.mjs.map +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +1353 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/cm.d.mts +87 -0
- package/dist/cm.d.mts.map +1 -0
- package/dist/cm.mjs +305 -0
- package/dist/cm.mjs.map +1 -0
- package/dist/context-BiYdAnKn.d.mts +8397 -0
- package/dist/context-BiYdAnKn.d.mts.map +1 -0
- package/dist/index.d.mts +393 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/topup-B2NzJV7_.mjs +437 -0
- package/dist/topup-B2NzJV7_.mjs.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# @sm-lab/recipes
|
|
2
|
+
|
|
3
|
+
TypeScript recipes that prepare Lido SM on-chain state on an **anvil fork** — the rewritten,
|
|
4
|
+
Foundry-free successor to the contracts repo's `fork.just`. Recipes **prepare state and
|
|
5
|
+
return what they did**; they do not assert (verification is the calling test's job).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
pnpm add @sm-lab/recipes
|
|
10
|
+
|
|
11
|
+
Peer runtime: a running anvil fork with the target staking module already deployed
|
|
12
|
+
(`anvil --fork-url <RPC>`).
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
import { connect, addKeys, operatorInfo } from '@sm-lab/recipes';
|
|
18
|
+
|
|
19
|
+
const ctx = await connect({ module: 'csm', rpcUrl: 'http://127.0.0.1:8545' });
|
|
20
|
+
const { publicKeys } = await addKeys(ctx, { noId: 0n, count: 3 });
|
|
21
|
+
const info = await operatorInfo(ctx, { noId: 0n });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`connect()` reads protocol addresses (`stakingRouter`, `vebo`, `lido`, `withdrawalQueue`,
|
|
25
|
+
`burner`) from `LidoLocator` on-chain and merges them onto a module-suite snapshot from
|
|
26
|
+
`@sm-lab/receipts`. Override the snapshot per call: `connect({ module, rpcUrl, addresses })`.
|
|
27
|
+
|
|
28
|
+
## CLI (`sm-recipes`)
|
|
29
|
+
|
|
30
|
+
A run-and-exit CLI over the recipe surface. Same `bin` underpins every route:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @sm-lab/recipes cm seed --rpc-url http://127.0.0.1:8545 # published
|
|
34
|
+
npm i -g @sm-lab/recipes && sm-recipes --help # global install
|
|
35
|
+
node tools/recipes/dist/cli.mjs --help # built dist (repo dev)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Global flags: `--rpc-url` (or `RPC_URL`, defaulting to anvil's `http://127.0.0.1:8545`),
|
|
39
|
+
`--module <csm|cm>`, `--cl-mock-url` (or `CL_MOCK_URL`), `--json`. Amounts (`--amount`,
|
|
40
|
+
`--exit-balance`, …) are in **ETH** (`0.000000000000000001` = 1 wei). `sm-recipes help [command]`
|
|
41
|
+
mirrors `--help`. `sm-recipes completion <bash|zsh|fish>` prints a static shell-completion
|
|
42
|
+
script (e.g. `sm-recipes completion fish | source`).
|
|
43
|
+
|
|
44
|
+
The `cm`/`csm` groups host their own recipes **and** mirror every shared recipe with the
|
|
45
|
+
module pre-bound — so a shared command works two ways: top-level with `--module`, or under
|
|
46
|
+
the group (no `--module` needed):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
sm-recipes --module csm operator-info --operator-id 0 # shared, module via flag
|
|
50
|
+
sm-recipes csm operator-info --operator-id 0 # same, module from the group
|
|
51
|
+
sm-recipes --module csm make-rewards --json
|
|
52
|
+
sm-recipes cm seed --seed 0x01 # cm-only recipe
|
|
53
|
+
sm-recipes csm set-gate --address 0xabc... --address 0xdef... # csm-only recipe
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Every **required, non-repeatable** option is also accepted **positionally**, in declaration
|
|
57
|
+
order — so the common case needs no flags. Flags still work and can be mixed with positionals;
|
|
58
|
+
optional flags (e.g. `--seed`, `--pair`) stay flag-only by default.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
sm-recipes csm operator-info 0 # == --operator-id 0
|
|
62
|
+
sm-recipes csm withdraw 0 1 32 # == --operator-id 0 --key-index 1 --exit-balance 32
|
|
63
|
+
sm-recipes csm add-keys 0 5 --seed 0x01 # positional id+count, optional seed via flag
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`set-gate` opts its `<selector>` (optional) and `<address...>` (repeatable, **variadic** —
|
|
67
|
+
must come last) into the positional form, so the selector leads and the addresses follow:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
sm-recipes csm set-gate idvtc 0xabc... 0xdef... # == --selector idvtc --address 0xabc... --address 0xdef...
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## The `actAs` model
|
|
74
|
+
|
|
75
|
+
Every privileged write runs through `actAs(ctx, who, fn)` — it funds `who`
|
|
76
|
+
(`anvil_setBalance`), impersonates it (`anvil_impersonateAccount`), runs the body, and stops.
|
|
77
|
+
This replaces the Solidity `broadcast*` modifiers.
|
|
78
|
+
|
|
79
|
+
## Subpaths & gate selectors
|
|
80
|
+
|
|
81
|
+
- `@sm-lab/recipes` — shared: `connect`, `actAs`, `addKeys`, `operatorInfo`, `warpBy`,
|
|
82
|
+
`snapshot`, `revert`, `clActivate`, plus the module-agnostic gate helpers `resolveGate`
|
|
83
|
+
and `setGateAddrs` (both dispatch on `ctx.module` — see gate selectors below).
|
|
84
|
+
- `@sm-lab/recipes/cm` — `createCuratedOperator` (cm gates `po/pto/pgo/do/eeo/iodc/iodcp` →
|
|
85
|
+
`CuratedGates[0..6]`), MetaRegistry group/curve recipes `createOperatorGroup`,
|
|
86
|
+
`resetOperatorGroup`, `setBondCurveWeight`, and `seedCm` — seed a realistic cm fork in one call
|
|
87
|
+
(3 gate operators, a 34/33/33 operator group, keyed/deposited/topped-up across rounds; pass `seed`
|
|
88
|
+
to make the operator addresses + keys deterministic).
|
|
89
|
+
|
|
90
|
+
**Gate selectors** (`resolveGate` / `setGateAddrs`, dispatched by `ctx.module`):
|
|
91
|
+
|
|
92
|
+
- csm — `ics` → `VettedGate` (default); `idvtc` → `IdentifiedDVTClusterGate` (v3-only, hoodi;
|
|
93
|
+
throws on mainnet/v2 snapshots that lack it).
|
|
94
|
+
- cm — `po/pto/pgo/do/eeo/iodc/iodcp` or a numeric index → `CuratedGates[0..6]` (`po` is the default).
|
|
95
|
+
- any — a raw `0x…` gate address is used as-is.
|
|
96
|
+
|
|
97
|
+
`setGateAddrs` builds the gate's address tree and installs it on whichever gate the selector
|
|
98
|
+
resolves to (VettedGate and CuratedGate share the same `setTreeParams` surface). It pins the tree to
|
|
99
|
+
IPFS (set `IPFS_API_URL` to a local `@sm-lab/ipfs`, or `PINATA_*`), or pass `cid` to skip pinning.
|
|
100
|
+
|
|
101
|
+
## Top-up (`allocateDeposits` as the StakingRouter)
|
|
102
|
+
|
|
103
|
+
- `increaseAllocatedBalance(ctx, { noId, keyIndex, amountWei })` — top up one deposited key's
|
|
104
|
+
allocated balance (validates the key exists and is not withdrawn). Returns `{ amountWei }`.
|
|
105
|
+
- `topUpActiveKeys(ctx, { noId })` — top up every not-yet-allocated, not-withdrawn deposited key,
|
|
106
|
+
one at a time in ascending key-index order (FIFO `TopUpQueueOps`, capped at 2016 ETH per key).
|
|
107
|
+
Returns `{ toppedUp }` (a no-op `{ toppedUp: 0 }` when nothing needs it).
|
|
108
|
+
|
|
109
|
+
## Rewards (`makeRewards` → `submitRewards`)
|
|
110
|
+
|
|
111
|
+
`makeRewards(ctx, opts?)` builds the cumulative FeeDistributor rewards tree off on-chain
|
|
112
|
+
operator state and a seeded mock reward per active key, pins the tree + report log to IPFS,
|
|
113
|
+
and returns a typed in-memory `RewardsReport` (`treeRoot`, `treeCid`, `logCid`, `distributed`,
|
|
114
|
+
`rebate`, `treeDump`, `cumulatives`). It builds the data half of an oracle report.
|
|
115
|
+
|
|
116
|
+
`submitRewards(ctx, report)` consumes that report and submits it on-chain: it funds the
|
|
117
|
+
FeeDistributor (if `pendingSharesToDistribute` can't cover the frame), warps to the next valid
|
|
118
|
+
consensus frame, builds the `ReportData` tuple, reaches consensus across the fast-lane members
|
|
119
|
+
(falling back to `getMembers` when the fast-lane set is empty), and submits the report data as
|
|
120
|
+
`members[0]`. A zero-root report is a graceful no-op (`{ submitted: false }`), so
|
|
121
|
+
`submitRewards(ctx, await makeRewards(ctx))` composes on an empty fork.
|
|
122
|
+
|
|
123
|
+
import { makeRewards, submitRewards } from '@sm-lab/recipes';
|
|
124
|
+
const report = await makeRewards(ctx, { seed: '0x…', treeCid: 'cid-t', logCid: 'cid-l' });
|
|
125
|
+
const { submitted, refSlot, reportHash } = await submitRewards(ctx, report);
|
|
126
|
+
|
|
127
|
+
- `seed` — deterministic per-key reward draw (keccak hash-chain, like `randomKeys`). Omit for
|
|
128
|
+
fresh randomness; pass a fixed `Hex` to pin `treeRoot`/`distributed` in tests.
|
|
129
|
+
- `previousCumulatives` — carry-forward input (`Map<bigint,bigint>` or `[bigint,bigint][]`).
|
|
130
|
+
Every prior leaf is carried forward (the `uint64`-max pad excluded) before this frame's deltas
|
|
131
|
+
are added. Chain frames by feeding a prior result's `cumulatives` back in.
|
|
132
|
+
- IPFS pin — same env switch as `setGateAddrs` (`IPFS_API_URL` → local mock, or `PINATA_*`).
|
|
133
|
+
The guard fails fast (before any network) when pinning is needed but unconfigured.
|
|
134
|
+
- Escape hatches — pass `treeCid` and/or `logCid` to skip that pin (hermetic tests do this).
|
|
135
|
+
An empty report (no active keys, no carry-forward) returns a zero root and pins nothing.
|
|
136
|
+
- `submitRewards` is fork-only in practice (it WRITES + warps over RPC). IPFS only matters via
|
|
137
|
+
`makeRewards` — `submitRewards` itself consumes the report's `treeCid`/`logCid` verbatim. The
|
|
138
|
+
consensus-frame wait uses `warpTo` (`setNextBlockTimestamp` + `mine`), the absolute counterpart
|
|
139
|
+
of `warpBy`.
|
|
140
|
+
|
|
141
|
+
## CL bridge (cl-mock)
|
|
142
|
+
|
|
143
|
+
`clActivate(ctx, { noId, keyIndex })` is the only chain-aware `@sm-lab/cl` bridge in
|
|
144
|
+
recipes. It requires `ctx.clMockUrl` (pass it via `connect({ module, rpcUrl, clMockUrl })`),
|
|
145
|
+
reads the key's pubkey + allocated balance on-chain, then marks the validator `active_ongoing`
|
|
146
|
+
on the running cl-mock with effective balance = `32 ETH + allocated`, in gwei (full precision,
|
|
147
|
+
flooring sub-gwei dust — diverging from the source's integer-ETH truncation). Raw `cl-set` /
|
|
148
|
+
`cl-list` stay in cl-mock's own CLI.
|
|
149
|
+
|
|
150
|
+
## Testing
|
|
151
|
+
|
|
152
|
+
Unit tests are **hermetic** — they inject a fake viem client (no network, no chain). One
|
|
153
|
+
opt-in integration smoke runs against a real fork:
|
|
154
|
+
|
|
155
|
+
anvil --fork-url <hoodi RPC> # in another shell
|
|
156
|
+
ANVIL_FORK_URL=http://127.0.0.1:8545 pnpm --filter @sm-lab/recipes test
|
|
157
|
+
|
|
158
|
+
Without `ANVIL_FORK_URL` the smoke is skipped and the suite stays offline-green.
|
|
159
|
+
|
|
160
|
+
## Keeping ABIs/addresses fresh
|
|
161
|
+
|
|
162
|
+
ABIs + addresses are vendored via `@sm-lab/receipts` (`pnpm --filter @sm-lab/receipts
|
|
163
|
+
refresh`). The SDK maintains a parallel set via its `/update-abis`; a cross-repo parity
|
|
164
|
+
check is a planned follow-up (not yet wired). Recipes never import ABIs from the SDK.
|