Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4f5ab1d
fuzz: CAT-3-4 max EB (2048 ETH/validator) high burn rate accrual
venimir-ssv Apr 5, 2026
9f8db25
fuzz: CAT-3-5 EB update on SSV cluster stores snapshot only
venimir-ssv Apr 5, 2026
aab7e0c
fuzz: CAT-3-6 EB boundary validation — revert at limits, succeed at e…
venimir-ssv Apr 6, 2026
508c44e
fuzz: CAT-3-7 stale root rejection — MustUseLatestRoot revert with fu…
venimir-ssv Apr 6, 2026
8691300
fuzz: CAT-3-8 oracle quorum — partial votes, re-vote prevention, comp…
venimir-ssv Apr 6, 2026
c8f8a94
fuzz: CAT-3-9 EB update on liquidated cluster — snapshot stored, no a…
venimir-ssv Apr 6, 2026
b1beb3e
fuzz: CAT-3-10 multi-cluster EB isolation — shared operators, deviati…
venimir-ssv Apr 6, 2026
ddb7793
fuzz: activate after() hook in runner, add DAO earnings delta asserti…
venimir-ssv Apr 7, 2026
c11ce5f
Merge pull request #600 from ssvlabs/fuzz/cat-3-10-multi-cluster-shar…
venimir-ssv Apr 7, 2026
d3d1e0c
fuzz: harden CAT-3-9 with DAO earnings, snapshot metadata, and reacti…
venimir-ssv Apr 7, 2026
3c9e43f
Merge pull request #599 from ssvlabs/fuzz/cat-3-9-eb-liquidated-clust…
venimir-ssv Apr 7, 2026
8d1f3e7
fuzz: harden CAT-3-8 with StaleBlockNumber guard and ebRoots verifica…
venimir-ssv Apr 7, 2026
7e9c4f8
Merge pull request #598 from ssvlabs/fuzz/cat-3-8-oracle-quorum
venimir-ssv Apr 7, 2026
379e640
fuzz: harden CAT-3-7 with ebRoots storage proofs and assertEthConserv…
venimir-ssv Apr 7, 2026
90fd6d2
Merge pull request #597 from ssvlabs/fuzz/cat-3-7-stale-root-rejection
venimir-ssv Apr 7, 2026
0bb868f
fuzz: add deterministic 3-validator anchor for CAT-3-6
venimir-ssv Apr 7, 2026
0ba3e65
Merge pull request #596 from ssvlabs/fuzz/cat-3-6-eb-boundary-validation
venimir-ssv Apr 7, 2026
bf39e67
fix(fuzz): align CAT-3-5 EB earnings assertions with contract formulas
venimir-ssv Apr 7, 2026
7d43a9b
Merge pull request #595 from ssvlabs/fuzz/cat-3-5-ssv-eb-snapshot
venimir-ssv Apr 7, 2026
875d695
harden CAT-3-4 max-EB fuzz assertions
venimir-ssv Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion test/ssv-fuzz-engine/core/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ export async function assertOperatorEarningsWithEB<S extends { cluster: ClusterR

for (const op of operators) {
const packedFee = op.fee / ETH_DEDUCTED_DIGITS;
const expectedDelta = ((packedFee * prev.vUnits) / BPS_DENOMINATOR) * ETH_DEDUCTED_DIGITS * blocks;
const expectedDelta = ((blocks * packedFee * prev.vUnits) / BPS_DENOMINATOR) * ETH_DEDUCTED_DIGITS;
expect(currentEarnings.get(op.id)).to.equal(prev.earnings.get(op.id)! + expectedDelta);
}
}
Expand All @@ -384,6 +384,46 @@ export async function assertOperatorEarningsWithEB<S extends { cluster: ClusterR
ctx.state.lastEBOperatorEarnings = { block, earnings: currentEarnings, vUnits: currentVUnits };
}

export interface EBNetworkEarningsSnapshot {
block: bigint;
earnings: bigint;
vUnits: bigint;
}

export async function assertNetworkEarningsWithEB<S extends {
cluster: ClusterRecord;
operators: OperatorRecord[];
lastEBNetworkEarnings?: EBNetworkEarningsSnapshot;
}>(ctx: FuzzContext<S>): Promise<void> {
const block = BigInt(await ctx.provider.getBlockNumber());
const { cluster } = ctx.state;
const currentEarnings = BigInt(await ctx.views.getNetworkEarnings());
const networkFee = BigInt(await ctx.views.getNetworkFee());

let currentVUnits: bigint;
if (cluster.cluster.active && BigInt(cluster.cluster.validatorCount) > 0n) {
const eb = BigInt(await ctx.views.getEffectiveBalance(
cluster.owner.address, cluster.operatorIds, cluster.cluster,
));
currentVUnits = ebToVUnits(eb);
} else {
currentVUnits = 0n;
}

if (ctx.state.lastEBNetworkEarnings !== undefined) {
const prev = ctx.state.lastEBNetworkEarnings;
if (prev.vUnits === currentVUnits) {
const blocks = block - prev.block;
const packedNetFee = networkFee / ETH_DEDUCTED_DIGITS;
const expectedDelta =
((blocks * packedNetFee * prev.vUnits) / BPS_DENOMINATOR) * ETH_DEDUCTED_DIGITS;
expect(currentEarnings).to.equal(prev.earnings + expectedDelta);
}
}

ctx.state.lastEBNetworkEarnings = { block, earnings: currentEarnings, vUnits: currentVUnits };
}

export async function assertDaoVUnitsMatchCluster<S extends { cluster: ClusterRecord; operators: OperatorRecord[] }>(
ctx: FuzzContext<S>,
): Promise<void> {
Expand Down
13 changes: 13 additions & 0 deletions test/ssv-fuzz-engine/core/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,17 @@ export async function fuzz<S>(config: FuzzConfig<S>, seed: bigint): Promise<void
);
}
}

if (config.after) {
try {
await config.after(ctx);
} catch (err: any) {
const msg = err?.message ?? String(err);
throw new Error(
`after() hook failed:\n` +
` Reproduce: FUZZ_SEED=${seed} npx hardhat test <testfile>\n` +
` ${msg}`,
);
}
}
}
1 change: 1 addition & 0 deletions test/ssv-fuzz-engine/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface FuzzConfig<S> {
setup: SetupFn<S>;
steps: (StepFn<S> | NamedStep<S>)[];
expectedPhase?: string;
after?: (ctx: FuzzContext<S>) => Promise<void>;
}

export interface OperatorRecord {
Expand Down
Loading
Loading