Skip to content

Commit 8d1f3e7

Browse files
committed
fuzz: harden CAT-3-8 with StaleBlockNumber guard and ebRoots verification, tighten CAT-2-8 balance assertion
1 parent 3c9e43f commit 8d1f3e7

2 files changed

Lines changed: 38 additions & 4 deletions

File tree

test/ssv-fuzz-engine/eb-oracle-quorum.fuzz.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ describe("Fuzz: Oracle quorum — partial votes, failed quorum, re-voting (CAT-3
129129
},
130130

131131
{
132-
name: "phase2-revote-prevention",
132+
name: "phase2-duplicate-vote-prevention",
133133
async fn(ctx) {
134134
const { allOracles, shuffledIndices, rootAEB, rootABlockNum, cluster } = ctx.state;
135135
const clusterId = computeClusterId(cluster.owner.address, cluster.operatorIds);
@@ -139,7 +139,7 @@ describe("Fuzz: Oracle quorum — partial votes, failed quorum, re-voting (CAT-3
139139
ctx.network.connect(allOracles[shuffledIndices[0]]).commitRoot(rootA, rootABlockNum),
140140
).to.be.revertedWithCustomError(ctx.network, Errors.ALREADY_VOTED);
141141

142-
ctx.state.phase = "revote-blocked";
142+
ctx.state.phase = "duplicate-blocked";
143143
},
144144
},
145145

@@ -155,6 +155,9 @@ describe("Fuzz: Oracle quorum — partial votes, failed quorum, re-voting (CAT-3
155155
expect(hasEvent(ctx.network, receipt3, Events.WEIGHTED_ROOT_PROPOSED)).to.equal(true);
156156
expect(hasEvent(ctx.network, receipt3, Events.ROOT_COMMITTED)).to.equal(true);
157157

158+
const storedRoot = await ctx.views.getCommittedRoot(rootABlockNum);
159+
expect(storedRoot).to.equal(rootA);
160+
158161
const tx = await ctx.network.updateClusterBalance(
159162
rootABlockNum,
160163
cluster.owner.address,
@@ -183,7 +186,22 @@ describe("Fuzz: Oracle quorum — partial votes, failed quorum, re-voting (CAT-3
183186
},
184187

185188
{
186-
name: "phase4-competing-roots",
189+
name: "phase4-post-commit-stale-retry",
190+
async fn(ctx) {
191+
const { allOracles, shuffledIndices, rootAEB, rootABlockNum, cluster } = ctx.state;
192+
const clusterId = computeClusterId(cluster.owner.address, cluster.operatorIds);
193+
const rootA = computeEBRoot(clusterId, rootAEB);
194+
195+
await expect(
196+
ctx.network.connect(allOracles[shuffledIndices[0]]).commitRoot(rootA, rootABlockNum),
197+
).to.be.revertedWithCustomError(ctx.network, Errors.STALE_BLOCK_NUMBER);
198+
199+
ctx.state.phase = "stale-blocked";
200+
},
201+
},
202+
203+
{
204+
name: "phase5-competing-roots",
187205
async fn(ctx) {
188206
const { cluster, allOracles, shuffledIndices, rootBEB, rootCEB } = ctx.state;
189207
const clusterId = computeClusterId(cluster.owner.address, cluster.operatorIds);
@@ -227,6 +245,9 @@ describe("Fuzz: Oracle quorum — partial votes, failed quorum, re-voting (CAT-3
227245
const receipt = await tx.wait();
228246
cluster.cluster = parseClusterFromEvent(ctx.network, receipt, Events.CLUSTER_BALANCE_UPDATED);
229247

248+
const storedRoot2 = await ctx.views.getCommittedRoot(ctx.state.rootBBlockNum);
249+
expect(storedRoot2).to.equal(rootB);
250+
230251
const eb = BigInt(
231252
await ctx.views.getEffectiveBalance(cluster.owner.address, cluster.operatorIds, cluster.cluster),
232253
);

test/ssv-fuzz-engine/eth-shared-operators.fuzz.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { expect } from "chai";
22
import { fuzz, generateSeeds } from "./core/runner.ts";
33
import { registerFuzzOperators, registerFuzzCluster, alignFee } from "./core/setup.ts";
4+
import { computeClusterBalance } from "./core/fuzz-helpers.ts";
45
import type { OperatorRecord, ClusterRecord } from "./core/types.ts";
56
import { parseClusterFromEvent } from "../helpers/cluster.ts";
67
import { makePublicKey } from "../helpers/keys.ts";
@@ -124,6 +125,11 @@ describe("Fuzz: ETH shared operators — multiple clusters (CAT-2-8)", function
124125
exclusiveBIndices, validatorCountB } = ctx.state;
125126
const thirdParty = ctx.signers[4];
126127

128+
const blockBefore = BigInt(await ctx.provider.getBlockNumber());
129+
const balanceBBefore = BigInt(
130+
await ctx.views.getBalance(clusterB.owner.address, clusterB.operatorIds, clusterB.cluster),
131+
);
132+
127133
const balance = BigInt(
128134
await ctx.views.getBalance(clusterA.owner.address, clusterA.operatorIds, clusterA.cluster),
129135
);
@@ -169,10 +175,17 @@ describe("Fuzz: ETH shared operators — multiple clusters (CAT-2-8)", function
169175
);
170176
}
171177

178+
const blockAfter = BigInt(await ctx.provider.getBlockNumber());
179+
const opFeesB = [...sharedIndices, ...exclusiveBIndices].map(i => operators[i].fee);
180+
const networkFee = BigInt(await ctx.views.getNetworkFee());
181+
const expectedBalanceB = computeClusterBalance(
182+
balanceBBefore, opFeesB, networkFee, BigInt(validatorCountB), blockAfter - blockBefore,
183+
);
184+
172185
const balanceB = BigInt(
173186
await ctx.views.getBalance(clusterB.owner.address, clusterB.operatorIds, clusterB.cluster),
174187
);
175-
expect(balanceB).to.be.greaterThan(0n, "Cluster B must still have balance");
188+
expect(balanceB).to.equal(expectedBalanceB);
176189

177190
const networkCount = BigInt(await ctx.views.getNetworkValidatorsCount());
178191
expect(networkCount).to.equal(expectedB);

0 commit comments

Comments
 (0)