@sm-lab/recipes 0.2.0 → 0.3.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/dist/cli.mjs +201 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/cm.d.mts +2 -2
- package/dist/cm.mjs +1 -1
- package/dist/cm.mjs.map +1 -1
- package/dist/{context-BiYdAnKn.d.mts → context-7_EMmCZw.d.mts} +6 -6
- package/dist/{context-BiYdAnKn.d.mts.map → context-7_EMmCZw.d.mts.map} +1 -1
- package/dist/{cl-activate-CPqr6v_D.mjs → exit-request-c0CS5wKu.mjs} +417 -9
- package/dist/exit-request-c0CS5wKu.mjs.map +1 -0
- package/dist/index.d.mts +178 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/{topup-B2NzJV7_.mjs → topup-CuqmB14u.mjs} +117 -11
- package/dist/topup-CuqmB14u.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/cl-activate-CPqr6v_D.mjs.map +0 -1
- package/dist/topup-B2NzJV7_.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { encodeAbiParameters, keccak256, maxUint256, parseAbiParameters, parseEther, toBytes, toHex } from "viem";
|
|
3
|
-
import { curatedGateAbi, feeDistributorAbi, feeOracleAbi, hashConsensusAbi, lidoAbi, vettedGateAbi } from "@sm-lab/receipts";
|
|
1
|
+
import { C as roleMember, E as resolveGate, S as actAs, T as contract, _ as SETTLE_GENERAL_DELAYED_PENALTY_ROLE, c as getPubkey, g as RESUME_ROLE, h as REPORT_GENERAL_DELAYED_PENALTY_ROLE, m as PAUSE_ROLE, o as getKeyBalance, p as DEFAULT_ADMIN_ROLE, v as SET_TREE_ROLE, x as randomSeed } from "./topup-CuqmB14u.mjs";
|
|
2
|
+
import { encodeAbiParameters, encodePacked, keccak256, maxUint256, numberToHex, parseAbiParameters, parseEther, toBytes, toHex } from "viem";
|
|
3
|
+
import { curatedGateAbi, feeDistributorAbi, feeOracleAbi, hashConsensusAbi, lidoAbi, stakingRouterAbi, vEBOAbi, vettedGateAbi } from "@sm-lab/receipts";
|
|
4
4
|
import { buildAddressesTree, ipfsOptionsFromEnv, makeRewards, pinJsonToIpfs, shouldAttemptPin } from "@sm-lab/merkle";
|
|
5
5
|
//#region src/recipes/operator-info.ts
|
|
6
6
|
/** Typed read of a node operator's on-chain record. (Port of `NodeOperators.operatorInfo`.) */
|
|
@@ -37,6 +37,48 @@ function snapshot(ctx) {
|
|
|
37
37
|
async function revert(ctx, id) {
|
|
38
38
|
await ctx.client.revert({ id });
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Fund an account on the fork by setting its balance (anvil_setBalance). `amountWei` defaults to
|
|
42
|
+
* 100 ETH. Port of `NodeOperators` `topup`.
|
|
43
|
+
*/
|
|
44
|
+
async function topUpAccount(ctx, opts) {
|
|
45
|
+
const amountWei = opts.amountWei ?? 100n * 10n ** 18n;
|
|
46
|
+
await ctx.client.setBalance({
|
|
47
|
+
address: opts.address,
|
|
48
|
+
value: amountWei
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
address: opts.address,
|
|
52
|
+
amountWei
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/recipes/target-limit.ts
|
|
57
|
+
/**
|
|
58
|
+
* Set an operator's target validator limit (StakingRouter-gated). Port of
|
|
59
|
+
* `NodeOperators.targetLimit` → `updateTargetValidatorsLimits(noId, mode, limit)`.
|
|
60
|
+
*/
|
|
61
|
+
async function setTargetLimit(ctx, opts) {
|
|
62
|
+
if (opts.mode !== 0 && opts.mode !== 1 && opts.mode !== 2) throw new Error(`@sm-lab/recipes: target limit mode must be 0, 1, or 2 (got ${opts.mode})`);
|
|
63
|
+
const limit = opts.mode === 0 ? 0n : opts.limit ?? 0n;
|
|
64
|
+
const m = contract(ctx, "module");
|
|
65
|
+
await actAs(ctx, ctx.addresses.stakingRouter, (from) => ctx.client.writeContract({
|
|
66
|
+
...m,
|
|
67
|
+
functionName: "updateTargetValidatorsLimits",
|
|
68
|
+
args: [
|
|
69
|
+
opts.noId,
|
|
70
|
+
BigInt(opts.mode),
|
|
71
|
+
limit
|
|
72
|
+
],
|
|
73
|
+
account: from,
|
|
74
|
+
chain: null
|
|
75
|
+
}));
|
|
76
|
+
return {
|
|
77
|
+
noId: opts.noId,
|
|
78
|
+
mode: opts.mode,
|
|
79
|
+
limit
|
|
80
|
+
};
|
|
81
|
+
}
|
|
40
82
|
//#endregion
|
|
41
83
|
//#region src/recipes/address-changes.ts
|
|
42
84
|
/** Propose a new manager address for an operator (signed by the current manager). */
|
|
@@ -133,6 +175,32 @@ async function exit(ctx, opts) {
|
|
|
133
175
|
chain: null
|
|
134
176
|
}));
|
|
135
177
|
}
|
|
178
|
+
/** Remove `count` keys (default 1) from operator `noId` starting at `keyIndex`, as the operator's manager. */
|
|
179
|
+
async function removeKey(ctx, opts) {
|
|
180
|
+
const count = opts.count ?? 1n;
|
|
181
|
+
const m = contract(ctx, "module");
|
|
182
|
+
const manager = (await ctx.client.readContract({
|
|
183
|
+
...m,
|
|
184
|
+
functionName: "getNodeOperator",
|
|
185
|
+
args: [opts.noId]
|
|
186
|
+
})).managerAddress;
|
|
187
|
+
await actAs(ctx, manager, (from) => ctx.client.writeContract({
|
|
188
|
+
...m,
|
|
189
|
+
functionName: "removeKeys",
|
|
190
|
+
args: [
|
|
191
|
+
opts.noId,
|
|
192
|
+
opts.keyIndex,
|
|
193
|
+
count
|
|
194
|
+
],
|
|
195
|
+
account: from,
|
|
196
|
+
chain: null
|
|
197
|
+
}));
|
|
198
|
+
return {
|
|
199
|
+
noId: opts.noId,
|
|
200
|
+
keyIndex: opts.keyIndex,
|
|
201
|
+
count
|
|
202
|
+
};
|
|
203
|
+
}
|
|
136
204
|
//#endregion
|
|
137
205
|
//#region src/recipes/validators.ts
|
|
138
206
|
/** Report a validator slashing for an operator's key (Verifier-gated). `keyIndex` is the storage index. */
|
|
@@ -165,6 +233,89 @@ async function withdraw(ctx, opts) {
|
|
|
165
233
|
chain: null
|
|
166
234
|
}));
|
|
167
235
|
}
|
|
236
|
+
/** Effective balance the source reports to mark a key active: 32 ETH + 1 gwei. */
|
|
237
|
+
const ACTIVE_BALANCE = 32n * 10n ** 18n + 10n ** 9n;
|
|
238
|
+
/**
|
|
239
|
+
* Activate `count` deposited-but-not-yet-active keys of an operator (Verifier-gated) by reporting
|
|
240
|
+
* an effective balance of 32 ETH + 1 gwei on each. Skips keys that already have a confirmed
|
|
241
|
+
* balance or are withdrawn. Port of `NodeOperators.activateKeys`. Returns the count activated.
|
|
242
|
+
*/
|
|
243
|
+
async function activateKeys(ctx, opts) {
|
|
244
|
+
const m = contract(ctx, "module");
|
|
245
|
+
const { noId, count } = opts;
|
|
246
|
+
const total = (await ctx.client.readContract({
|
|
247
|
+
...m,
|
|
248
|
+
functionName: "getNodeOperator",
|
|
249
|
+
args: [noId]
|
|
250
|
+
})).totalDepositedKeys;
|
|
251
|
+
const eligible = (await Promise.all(Array.from({ length: total }, (_, i) => i).map(async (i) => ({
|
|
252
|
+
i,
|
|
253
|
+
confirmed: await ctx.client.readContract({
|
|
254
|
+
...m,
|
|
255
|
+
functionName: "getKeyConfirmedBalances",
|
|
256
|
+
args: [
|
|
257
|
+
noId,
|
|
258
|
+
BigInt(i),
|
|
259
|
+
1n
|
|
260
|
+
]
|
|
261
|
+
}),
|
|
262
|
+
withdrawn: await ctx.client.readContract({
|
|
263
|
+
...m,
|
|
264
|
+
functionName: "isValidatorWithdrawn",
|
|
265
|
+
args: [noId, BigInt(i)]
|
|
266
|
+
})
|
|
267
|
+
})))).filter((s) => s.confirmed[0] === 0n && !s.withdrawn).slice(0, count);
|
|
268
|
+
if (eligible.length < count) throw new Error(`@sm-lab/recipes: operator ${noId} has only ${eligible.length} activatable key(s), need ${count}`);
|
|
269
|
+
await actAs(ctx, ctx.addresses.Verifier, async (from) => {
|
|
270
|
+
for (const { i } of eligible) await ctx.client.writeContract({
|
|
271
|
+
...m,
|
|
272
|
+
functionName: "reportValidatorBalance",
|
|
273
|
+
args: [
|
|
274
|
+
noId,
|
|
275
|
+
BigInt(i),
|
|
276
|
+
ACTIVE_BALANCE
|
|
277
|
+
],
|
|
278
|
+
account: from,
|
|
279
|
+
chain: null
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
return { activated: eligible.length };
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Report an arbitrary CL balance (wei) for one deposited key (Verifier-gated). Validates the key
|
|
286
|
+
* index is in range and not withdrawn. Port of `NodeOperators.reportBalance`.
|
|
287
|
+
*/
|
|
288
|
+
async function reportBalance(ctx, opts) {
|
|
289
|
+
const m = contract(ctx, "module");
|
|
290
|
+
const { noId, keyIndex, balanceWei } = opts;
|
|
291
|
+
const total = (await ctx.client.readContract({
|
|
292
|
+
...m,
|
|
293
|
+
functionName: "getNodeOperator",
|
|
294
|
+
args: [noId]
|
|
295
|
+
})).totalDepositedKeys;
|
|
296
|
+
if (keyIndex >= BigInt(total)) throw new Error(`@sm-lab/recipes: key index ${keyIndex} out of bounds (operator ${noId} has ${total} deposited keys)`);
|
|
297
|
+
if (await ctx.client.readContract({
|
|
298
|
+
...m,
|
|
299
|
+
functionName: "isValidatorWithdrawn",
|
|
300
|
+
args: [noId, keyIndex]
|
|
301
|
+
})) throw new Error(`@sm-lab/recipes: key ${keyIndex} of operator ${noId} is withdrawn`);
|
|
302
|
+
await actAs(ctx, ctx.addresses.Verifier, (from) => ctx.client.writeContract({
|
|
303
|
+
...m,
|
|
304
|
+
functionName: "reportValidatorBalance",
|
|
305
|
+
args: [
|
|
306
|
+
noId,
|
|
307
|
+
keyIndex,
|
|
308
|
+
balanceWei
|
|
309
|
+
],
|
|
310
|
+
account: from,
|
|
311
|
+
chain: null
|
|
312
|
+
}));
|
|
313
|
+
return {
|
|
314
|
+
noId,
|
|
315
|
+
keyIndex,
|
|
316
|
+
balanceWei
|
|
317
|
+
};
|
|
318
|
+
}
|
|
168
319
|
//#endregion
|
|
169
320
|
//#region src/recipes/penalties.ts
|
|
170
321
|
/** Report a general delayed penalty against an operator (penalty-reporter role). */
|
|
@@ -268,7 +419,7 @@ async function createBondDebt(ctx, opts) {
|
|
|
268
419
|
}
|
|
269
420
|
//#endregion
|
|
270
421
|
//#region src/recipes/set-gate.ts
|
|
271
|
-
/** Default gate selector per module: cm → 'po' (
|
|
422
|
+
/** Default gate selector per module: cm → 'po' (CuratedGatePO); csm → 'ics' (IcsGate). */
|
|
272
423
|
function defaultSelector(ctx) {
|
|
273
424
|
return ctx.module === "cm" ? "po" : "ics";
|
|
274
425
|
}
|
|
@@ -444,7 +595,7 @@ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
|
444
595
|
* into 9 top-level params — that drops the tuple offset and changes the hash. This is the exact
|
|
445
596
|
* reportHash transcription trap.
|
|
446
597
|
*/
|
|
447
|
-
const REPORT_DATA_PARAMS = parseAbiParameters("(uint256 consensusVersion, uint256 refSlot, bytes32 treeRoot, string treeCid, string logCid, uint256 distributed, uint256 rebate, bytes32 strikesTreeRoot, string strikesTreeCid)");
|
|
598
|
+
const REPORT_DATA_PARAMS$1 = parseAbiParameters("(uint256 consensusVersion, uint256 refSlot, bytes32 treeRoot, string treeCid, string logCid, uint256 distributed, uint256 rebate, bytes32 strikesTreeRoot, string strikesTreeCid)");
|
|
448
599
|
/**
|
|
449
600
|
* Submit a `RewardsReport` (from `makeRewards`) on-chain: fund the FeeDistributor, warp to the next
|
|
450
601
|
* consensus frame, build the `ReportData` tuple, reach consensus across the fast-lane members (with
|
|
@@ -581,7 +732,7 @@ async function submitRewards(ctx, report) {
|
|
|
581
732
|
}
|
|
582
733
|
/** keccak256(abi.encode(data)) — encode the 9-field struct as ONE tuple param (see REPORT_DATA_PARAMS). */
|
|
583
734
|
function reportHash(data) {
|
|
584
|
-
return keccak256(encodeAbiParameters(REPORT_DATA_PARAMS, [data]));
|
|
735
|
+
return keccak256(encodeAbiParameters(REPORT_DATA_PARAMS$1, [data]));
|
|
585
736
|
}
|
|
586
737
|
/**
|
|
587
738
|
* Mock strikes root: `keccak256(abi.encode("mock-strikes", refSlot))` — TWO top-level params
|
|
@@ -626,6 +777,14 @@ async function setClValidator(clMockUrl, input) {
|
|
|
626
777
|
const GWEI_PER_ETH = 1000000000n;
|
|
627
778
|
const BASE_ETH_GWEI = 32n * GWEI_PER_ETH;
|
|
628
779
|
/**
|
|
780
|
+
* Effective balance of an active validator, in gwei: 32 ETH + the key's on-chain allocated balance
|
|
781
|
+
* (sub-gwei dust floored). Shared by `clActivate` (`active_ongoing`) and `exitRequest`'s optional
|
|
782
|
+
* cl-mock flip (`active_exiting`) so both bridges report the same balance for the same key.
|
|
783
|
+
*/
|
|
784
|
+
async function activeEffectiveBalanceGwei(ctx, opts) {
|
|
785
|
+
return BASE_ETH_GWEI + await getKeyBalance(ctx, opts) / GWEI_PER_ETH;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
629
788
|
* Read a key's pubkey + allocated balance on-chain, then mark it `active_ongoing` on a running
|
|
630
789
|
* cl-mock with effective balance = 32 ETH + allocated balance, in gwei. Port of
|
|
631
790
|
* `cl-mock.just:cl-activate`. Diverges from the Solidity helper's integer-ETH truncation
|
|
@@ -635,7 +794,7 @@ const BASE_ETH_GWEI = 32n * GWEI_PER_ETH;
|
|
|
635
794
|
async function clActivate(ctx, opts) {
|
|
636
795
|
if (!ctx.clMockUrl) throw new Error("@sm-lab/recipes: clActivate needs ctx.clMockUrl (a running cl-mock)");
|
|
637
796
|
const pubkey = await getPubkey(ctx, opts);
|
|
638
|
-
const effectiveBalanceGwei =
|
|
797
|
+
const effectiveBalanceGwei = await activeEffectiveBalanceGwei(ctx, opts);
|
|
639
798
|
await setClValidator(ctx.clMockUrl, {
|
|
640
799
|
pubkey,
|
|
641
800
|
status: "active_ongoing",
|
|
@@ -648,6 +807,255 @@ async function clActivate(ctx, opts) {
|
|
|
648
807
|
};
|
|
649
808
|
}
|
|
650
809
|
//#endregion
|
|
651
|
-
|
|
810
|
+
//#region src/recipes/pause.ts
|
|
811
|
+
/**
|
|
812
|
+
* Resolve a pause target keyword to a contract handle. `module` and `accounting` are reserved;
|
|
813
|
+
* anything else is a gate selector resolved via `resolveGate` (ics/idvtc for csm; po…iodcp/index
|
|
814
|
+
* for cm; 0x… for either).
|
|
815
|
+
*/
|
|
816
|
+
function resolveTarget(ctx, target) {
|
|
817
|
+
if (target === "module") {
|
|
818
|
+
const m = contract(ctx, "module");
|
|
819
|
+
return {
|
|
820
|
+
address: m.address,
|
|
821
|
+
abi: m.abi
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
if (target === "accounting") {
|
|
825
|
+
const a = contract(ctx, "Accounting");
|
|
826
|
+
return {
|
|
827
|
+
address: a.address,
|
|
828
|
+
abi: a.abi
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
const abi = ctx.module === "cm" ? curatedGateAbi : vettedGateAbi;
|
|
832
|
+
return {
|
|
833
|
+
address: resolveGate(ctx, target),
|
|
834
|
+
abi
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
/** Pause a target (module | accounting | gate selector). Idempotent: no-op if already paused. */
|
|
838
|
+
async function pause(ctx, opts) {
|
|
839
|
+
const t = resolveTarget(ctx, opts.target);
|
|
840
|
+
if (await ctx.client.readContract({
|
|
841
|
+
...t,
|
|
842
|
+
functionName: "isPaused"
|
|
843
|
+
})) return {
|
|
844
|
+
target: opts.target,
|
|
845
|
+
address: t.address,
|
|
846
|
+
paused: true
|
|
847
|
+
};
|
|
848
|
+
const admin = await roleMember(ctx, t, DEFAULT_ADMIN_ROLE);
|
|
849
|
+
await actAs(ctx, admin, async (from) => {
|
|
850
|
+
await ctx.client.writeContract({
|
|
851
|
+
...t,
|
|
852
|
+
functionName: "grantRole",
|
|
853
|
+
args: [PAUSE_ROLE, admin],
|
|
854
|
+
account: from,
|
|
855
|
+
chain: null
|
|
856
|
+
});
|
|
857
|
+
await ctx.client.writeContract({
|
|
858
|
+
...t,
|
|
859
|
+
functionName: "pauseFor",
|
|
860
|
+
args: [maxUint256],
|
|
861
|
+
account: from,
|
|
862
|
+
chain: null
|
|
863
|
+
});
|
|
864
|
+
});
|
|
865
|
+
return {
|
|
866
|
+
target: opts.target,
|
|
867
|
+
address: t.address,
|
|
868
|
+
paused: true
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
/** Resume a target (module | accounting | gate selector). Idempotent: no-op if not paused. */
|
|
872
|
+
async function resume(ctx, opts) {
|
|
873
|
+
const t = resolveTarget(ctx, opts.target);
|
|
874
|
+
if (!await ctx.client.readContract({
|
|
875
|
+
...t,
|
|
876
|
+
functionName: "isPaused"
|
|
877
|
+
})) return {
|
|
878
|
+
target: opts.target,
|
|
879
|
+
address: t.address,
|
|
880
|
+
paused: false
|
|
881
|
+
};
|
|
882
|
+
const admin = await roleMember(ctx, t, DEFAULT_ADMIN_ROLE);
|
|
883
|
+
await actAs(ctx, admin, async (from) => {
|
|
884
|
+
await ctx.client.writeContract({
|
|
885
|
+
...t,
|
|
886
|
+
functionName: "grantRole",
|
|
887
|
+
args: [RESUME_ROLE, admin],
|
|
888
|
+
account: from,
|
|
889
|
+
chain: null
|
|
890
|
+
});
|
|
891
|
+
await ctx.client.writeContract({
|
|
892
|
+
...t,
|
|
893
|
+
functionName: "resume",
|
|
894
|
+
account: from,
|
|
895
|
+
chain: null
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
return {
|
|
899
|
+
target: opts.target,
|
|
900
|
+
address: t.address,
|
|
901
|
+
paused: false
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
//#endregion
|
|
905
|
+
//#region src/recipes/exit-request.ts
|
|
906
|
+
/** DATA_FORMAT_LIST — the single supported VEBO exit-request data format. */
|
|
907
|
+
const DATA_FORMAT = 1n;
|
|
908
|
+
/** processing deadline offset — `block.timestamp + 1 days` (matches the source). */
|
|
909
|
+
const ONE_DAY = 86400n;
|
|
910
|
+
/**
|
|
911
|
+
* The VEBO `ReportData` struct as ONE tuple parameter (components in declaration order, verified
|
|
912
|
+
* against `fixtures/receipts/src/abi/VEBO.ts`). `abi.encode(report)` for a single struct ==
|
|
913
|
+
* ABI-encoding one tuple parameter — do NOT flatten into 5 top-level params (that drops the tuple
|
|
914
|
+
* offset and changes the hash). Same trap `rewards.ts` documents for the fee-oracle report.
|
|
915
|
+
*/
|
|
916
|
+
const REPORT_DATA_PARAMS = parseAbiParameters("(uint256 consensusVersion, uint256 refSlot, uint256 requestsCount, uint256 dataFormat, bytes data)");
|
|
917
|
+
/**
|
|
918
|
+
* Request a single validator exit via the Validators Exit Bus Oracle. Port of
|
|
919
|
+
* `NodeOperators.s.sol:_exitRequest`. Reads the key pubkey + discovers the module id, packs the
|
|
920
|
+
* 64-byte exit request, then fakes VEBO consensus by impersonating the consensus contract
|
|
921
|
+
* (`submitConsensusReport`) and submits the data as the VEBO admin (`grantRole` + `submitReportData`).
|
|
922
|
+
*
|
|
923
|
+
* Module-agnostic: `contract(ctx,'module')` picks CSModule/CuratedModule by `ctx.module`, and the
|
|
924
|
+
* module-id scan matches that same address — no csm/cm branching.
|
|
925
|
+
*
|
|
926
|
+
* Optional cl-mock reflection (beyond the source): when `ctx.clMockUrl` is set, the validator is
|
|
927
|
+
* additionally marked `active_exiting` on the running cl-mock (mirroring `clActivate`); skipped
|
|
928
|
+
* silently otherwise.
|
|
929
|
+
*
|
|
930
|
+
* Deliberate divergences from the source (identical on-chain effect):
|
|
931
|
+
* - reuses the VEBO admin as the `submitReportData` submitter (source grants a fresh address);
|
|
932
|
+
* `grantRole` is idempotent so re-granting the admin never reverts.
|
|
933
|
+
* - scans ALL staking-module ids (source's `for (i=len-1; i>0; i--)` skips index 0).
|
|
934
|
+
*/
|
|
935
|
+
async function exitRequest(ctx, opts) {
|
|
936
|
+
const validatorIndex = opts.validatorIndex ?? 900000n;
|
|
937
|
+
const m = contract(ctx, "module");
|
|
938
|
+
const vebo = {
|
|
939
|
+
address: ctx.addresses.vebo,
|
|
940
|
+
abi: vEBOAbi
|
|
941
|
+
};
|
|
942
|
+
const pubkey = await ctx.client.readContract({
|
|
943
|
+
...m,
|
|
944
|
+
functionName: "getSigningKeys",
|
|
945
|
+
args: [
|
|
946
|
+
opts.noId,
|
|
947
|
+
opts.keyIndex,
|
|
948
|
+
1n
|
|
949
|
+
]
|
|
950
|
+
});
|
|
951
|
+
const moduleId = await resolveModuleId(ctx, m.address);
|
|
952
|
+
const data = encodePacked([
|
|
953
|
+
"bytes3",
|
|
954
|
+
"bytes5",
|
|
955
|
+
"bytes8",
|
|
956
|
+
"bytes"
|
|
957
|
+
], [
|
|
958
|
+
numberToHex(moduleId, { size: 3 }),
|
|
959
|
+
numberToHex(opts.noId, { size: 5 }),
|
|
960
|
+
numberToHex(validatorIndex, { size: 8 }),
|
|
961
|
+
pubkey
|
|
962
|
+
]);
|
|
963
|
+
const refSlot = (await ctx.client.readContract({
|
|
964
|
+
...vebo,
|
|
965
|
+
functionName: "getConsensusReport"
|
|
966
|
+
}))[1] + 1n;
|
|
967
|
+
const report = {
|
|
968
|
+
consensusVersion: await ctx.client.readContract({
|
|
969
|
+
...vebo,
|
|
970
|
+
functionName: "getConsensusVersion"
|
|
971
|
+
}),
|
|
972
|
+
refSlot,
|
|
973
|
+
requestsCount: 1n,
|
|
974
|
+
dataFormat: DATA_FORMAT,
|
|
975
|
+
data
|
|
976
|
+
};
|
|
977
|
+
const reportHash = keccak256(encodeAbiParameters(REPORT_DATA_PARAMS, [report]));
|
|
978
|
+
const consensus = await ctx.client.readContract({
|
|
979
|
+
...vebo,
|
|
980
|
+
functionName: "getConsensusContract"
|
|
981
|
+
});
|
|
982
|
+
const deadline = (await ctx.client.getBlock()).timestamp + ONE_DAY;
|
|
983
|
+
await actAs(ctx, consensus, (from) => ctx.client.writeContract({
|
|
984
|
+
...vebo,
|
|
985
|
+
functionName: "submitConsensusReport",
|
|
986
|
+
args: [
|
|
987
|
+
reportHash,
|
|
988
|
+
refSlot,
|
|
989
|
+
deadline
|
|
990
|
+
],
|
|
991
|
+
account: from,
|
|
992
|
+
chain: null
|
|
993
|
+
}));
|
|
994
|
+
const admin = await roleMember(ctx, vebo, DEFAULT_ADMIN_ROLE);
|
|
995
|
+
const submitRole = await ctx.client.readContract({
|
|
996
|
+
...vebo,
|
|
997
|
+
functionName: "SUBMIT_DATA_ROLE"
|
|
998
|
+
});
|
|
999
|
+
const contractVersion = await ctx.client.readContract({
|
|
1000
|
+
...vebo,
|
|
1001
|
+
functionName: "getContractVersion"
|
|
1002
|
+
});
|
|
1003
|
+
await actAs(ctx, admin, async (from) => {
|
|
1004
|
+
await ctx.client.writeContract({
|
|
1005
|
+
...vebo,
|
|
1006
|
+
functionName: "grantRole",
|
|
1007
|
+
args: [submitRole, admin],
|
|
1008
|
+
account: from,
|
|
1009
|
+
chain: null
|
|
1010
|
+
});
|
|
1011
|
+
await ctx.client.writeContract({
|
|
1012
|
+
...vebo,
|
|
1013
|
+
functionName: "submitReportData",
|
|
1014
|
+
args: [report, contractVersion],
|
|
1015
|
+
account: from,
|
|
1016
|
+
chain: null
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
let clStatus;
|
|
1020
|
+
if (ctx.clMockUrl) {
|
|
1021
|
+
const effectiveBalanceGwei = await activeEffectiveBalanceGwei(ctx, opts);
|
|
1022
|
+
await setClValidator(ctx.clMockUrl, {
|
|
1023
|
+
pubkey,
|
|
1024
|
+
status: "active_exiting",
|
|
1025
|
+
effectiveBalanceGwei
|
|
1026
|
+
});
|
|
1027
|
+
clStatus = "active_exiting";
|
|
1028
|
+
}
|
|
1029
|
+
return {
|
|
1030
|
+
noId: opts.noId,
|
|
1031
|
+
keyIndex: opts.keyIndex,
|
|
1032
|
+
validatorIndex,
|
|
1033
|
+
moduleId,
|
|
1034
|
+
refSlot,
|
|
1035
|
+
reportHash,
|
|
1036
|
+
pubkey,
|
|
1037
|
+
clStatus
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
/** Find the staking-module id whose registered address is `moduleAddress` (scans ALL ids). */
|
|
1041
|
+
async function resolveModuleId(ctx, moduleAddress) {
|
|
1042
|
+
const sr = {
|
|
1043
|
+
address: ctx.addresses.stakingRouter,
|
|
1044
|
+
abi: stakingRouterAbi
|
|
1045
|
+
};
|
|
1046
|
+
const ids = await ctx.client.readContract({
|
|
1047
|
+
...sr,
|
|
1048
|
+
functionName: "getStakingModuleIds"
|
|
1049
|
+
});
|
|
1050
|
+
const idx = (await Promise.all(ids.map((id) => ctx.client.readContract({
|
|
1051
|
+
...sr,
|
|
1052
|
+
functionName: "getStakingModule",
|
|
1053
|
+
args: [id]
|
|
1054
|
+
})))).findIndex((mod) => mod.stakingModuleAddress.toLowerCase() === moduleAddress.toLowerCase());
|
|
1055
|
+
if (idx === -1) throw new Error(`@sm-lab/recipes: module ${moduleAddress} not registered in the StakingRouter`);
|
|
1056
|
+
return ids[idx];
|
|
1057
|
+
}
|
|
1058
|
+
//#endregion
|
|
1059
|
+
export { snapshot as A, nodeOperatorIdBytes as C, proposeReward as D, proposeManager as E, warpBy as M, warpTo as N, setTargetLimit as O, operatorInfo as P, keyCountBytes as S, confirmReward as T, slash as _, setClValidator as a, removeKey as b, setGateAddrs as c, cancelPenalty as d, compensatePenalty as f, reportBalance as g, activateKeys as h, clActivate as i, topUpAccount as j, revert as k, addBond as l, settlePenalty as m, pause as n, makeRewards$1 as o, reportPenalty as p, resume as r, submitRewards as s, exitRequest as t, createBondDebt as u, withdraw as v, confirmManager as w, unvet as x, exit as y };
|
|
652
1060
|
|
|
653
|
-
//# sourceMappingURL=
|
|
1061
|
+
//# sourceMappingURL=exit-request-c0CS5wKu.mjs.map
|