Skip to content

Commit 4a0dcec

Browse files
committed
withdraw tests added
1 parent 2c0dec1 commit 4a0dcec

File tree

2 files changed

+158
-0
lines changed

2 files changed

+158
-0
lines changed

test/common/events.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const Events = {
44
CLUSTER_LIQUIDATED: "ClusterLiquidated",
55
CLUSTER_REACTIVATED: "ClusterReactivated",
66
CLUSTER_DEPOSITED: "ClusterDeposited",
7+
CLUSTER_WITHDRAWN: "ClusterWithdrawn",
78
OPERATOR_ADDED: "OperatorAdded",
89
OPERATOR_PRIVACY_STATUS_UPDATED: "OperatorPrivacyStatusUpdated",
910
OPERATOR_REMOVED: "OperatorRemoved",
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { expect } from "chai";
2+
import type { NetworkConnection } from "hardhat/types/network";
3+
import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/types";
4+
import { getTestConnection } from "../../setup/connection.ts";
5+
import { ssvClustersHarnessFixture } from "../../setup/fixtures.ts";
6+
import type { NetworkHelpersType } from "../../common/types.ts";
7+
import { makePublicKey } from "../../common/helpers.ts";
8+
import { DEFAULT_ETH_REGISTER_VALUE, DEFAULT_SHARES, EMPTY_CLUSTER } from "../../common/constants.ts";
9+
import { Events } from "../../common/events.ts";
10+
import { Errors } from "../../common/errors.ts";
11+
12+
type ClusterType = typeof EMPTY_CLUSTER;
13+
14+
const createCluster = (overrides: Partial<ClusterType> = {}): ClusterType => ({
15+
...EMPTY_CLUSTER,
16+
active: true,
17+
...overrides,
18+
});
19+
20+
const parseClusterFromEvent = (contract: any, receipt: any, eventName: string): ClusterType => {
21+
for (const log of receipt.logs ?? []) {
22+
let parsed;
23+
try {
24+
parsed = contract.interface.parseLog(log);
25+
} catch {
26+
continue;
27+
}
28+
29+
if (parsed?.name === eventName) {
30+
const clusterTuple = parsed.args[parsed.args.length - 1];
31+
const [validatorCount, networkFeeIndex, index, active, balance] = clusterTuple;
32+
33+
return {
34+
validatorCount: BigInt(validatorCount),
35+
networkFeeIndex: BigInt(networkFeeIndex),
36+
index: BigInt(index),
37+
active,
38+
balance: BigInt(balance),
39+
};
40+
}
41+
}
42+
43+
throw new Error(`Event ${eventName} not found`);
44+
};
45+
46+
describe("SSVClusters function `withdraw()`", async () => {
47+
let connection: NetworkConnection<"generic">;
48+
let networkHelpers: NetworkHelpersType;
49+
50+
let clusterOwner: HardhatEthersSigner;
51+
let otherAccount: HardhatEthersSigner;
52+
53+
before(async function () {
54+
({ connection, networkHelpers } = await getTestConnection());
55+
56+
[clusterOwner, otherAccount] = await connection.ethers.getSigners();
57+
});
58+
59+
const deploySSVClustersAndPrepareOperatorsFixture = async () => {
60+
return ssvClustersHarnessFixture(connection);
61+
};
62+
63+
const registerCluster = async (clusters: any, operatorIds: bigint[]) => {
64+
const registerTx = await clusters.registerValidator(
65+
makePublicKey(1),
66+
operatorIds,
67+
DEFAULT_SHARES,
68+
0,
69+
createCluster(),
70+
{ value: DEFAULT_ETH_REGISTER_VALUE }
71+
);
72+
const receipt = await registerTx.wait();
73+
return parseClusterFromEvent(clusters, receipt, Events.VALIDATOR_ADDED);
74+
};
75+
76+
it("Withdraws from an existing cluster, updates balance and emits correct event", async function () {
77+
const { clusters, operatorIds } =
78+
await networkHelpers.loadFixture(deploySSVClustersAndPrepareOperatorsFixture);
79+
80+
const clusterBeforeWithdraw = await registerCluster(clusters, operatorIds);
81+
const withdrawAmount = 1n;
82+
83+
const withdrawTx = await clusters.withdraw(operatorIds, withdrawAmount, clusterBeforeWithdraw);
84+
const withdrawReceipt = await withdrawTx.wait();
85+
const clusterAfterWithdraw = parseClusterFromEvent(clusters, withdrawReceipt, Events.CLUSTER_WITHDRAWN);
86+
87+
await expect(withdrawTx).to.emit(clusters, Events.CLUSTER_WITHDRAWN);
88+
expect(clusterAfterWithdraw.balance).to.equal(clusterBeforeWithdraw.balance - withdrawAmount);
89+
});
90+
91+
it("Is reverted with 'InsufficientBalance' when withdrawing more than the cluster balance", async function () {
92+
const { clusters, operatorIds } =
93+
await networkHelpers.loadFixture(deploySSVClustersAndPrepareOperatorsFixture);
94+
95+
const clusterBeforeWithdraw = await registerCluster(clusters, operatorIds);
96+
const excessiveAmount = clusterBeforeWithdraw.balance + 1n;
97+
98+
await expect(clusters.withdraw(
99+
operatorIds,
100+
excessiveAmount,
101+
clusterBeforeWithdraw
102+
)).to.be.revertedWithCustomError(clusters, Errors.INSUFFICIENT_BALANCE);
103+
});
104+
105+
it("Is reverted with 'IncorrectClusterState' when provided cluster data is stale or mismatched", async function () {
106+
const { clusters, operatorIds } =
107+
await networkHelpers.loadFixture(deploySSVClustersAndPrepareOperatorsFixture);
108+
109+
const clusterBeforeWithdraw = await registerCluster(clusters, operatorIds);
110+
111+
const mismatchedCluster = {
112+
...clusterBeforeWithdraw,
113+
balance: clusterBeforeWithdraw.balance + 1n,
114+
};
115+
116+
await expect(clusters.withdraw(
117+
operatorIds,
118+
1n,
119+
mismatchedCluster
120+
)).to.be.revertedWithCustomError(clusters, Errors.INCORRECT_CLUSTER_STATE);
121+
});
122+
123+
it("Is reverted with 'ClusterDoesNotExists' when a non-owner tries to withdraw", async function () {
124+
const { clusters, operatorIds } =
125+
await networkHelpers.loadFixture(deploySSVClustersAndPrepareOperatorsFixture);
126+
127+
const clusterBeforeWithdraw = await registerCluster(clusters, operatorIds);
128+
129+
await expect(clusters.connect(otherAccount).withdraw(
130+
operatorIds,
131+
1n,
132+
clusterBeforeWithdraw
133+
)).to.be.revertedWithCustomError(clusters, Errors.CLUSTER_DOES_NOT_EXISTS);
134+
});
135+
136+
it("Is reverted with 'ClusterIsLiquidated' when attempting to withdraw from a liquidated cluster", async function () {
137+
const { clusters, operatorIds } =
138+
await networkHelpers.loadFixture(deploySSVClustersAndPrepareOperatorsFixture);
139+
140+
const clusterBeforeWithdraw = await registerCluster(clusters, operatorIds);
141+
await clusters.mockSetClusterLiquidated(clusterOwner.address, operatorIds);
142+
143+
const liquidatedCluster = {
144+
validatorCount: 0n,
145+
networkFeeIndex: 0n,
146+
index: 0n,
147+
balance: 0n,
148+
active: false,
149+
};
150+
151+
await expect(clusters.withdraw(
152+
operatorIds,
153+
1n,
154+
liquidatedCluster
155+
)).to.be.revertedWithCustomError(clusters, Errors.CLUSTER_IS_LIQUIDATED);
156+
});
157+
});

0 commit comments

Comments
 (0)