Skip to content

Commit 07bbede

Browse files
authored
Added consol option to withdrawValidator, exitValidators and stakeValidator Hardhat tasks (#2871)
* Added consol option to withdrawValidator Hardhat task * Add consol option to exitValidators Hardhat task * Added consol option to the stakeValidator Hardhat task * Added run log to withdraw from Compounding Staking Strategy * getValidators to use GET rather than POST beacon chain API
1 parent 78e4669 commit 07bbede

5 files changed

Lines changed: 119 additions & 27 deletions

File tree

brownie/runlogs/2026_04_strategist.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,48 @@ def main():
6565
print("Pool OETH ", "{:.6f}".format(oethPoolBalance / 10**18), oethPoolBalance * 100 / totalPool)
6666
print("Pool Total ", "{:.6f}".format(totalPool / 10**18), totalPool)
6767
print("OETH Curve prices before and after", "{:.6f}".format(weth_out_before / 10**18), "{:.6f}".format(weth_out_after / 10**18))
68+
69+
# -------------------------------------------------------------
70+
# Apr 7, 2026 - Withdraw from Compounding Staking Strategy
71+
# -------------------------------------------------------------
72+
73+
from world import *
74+
75+
def main():
76+
with TemporaryForkForReallocations() as txs:
77+
# Before
78+
txs.append(vault_oeth_core.rebase(std))
79+
txs.append(oeth_vault_value_checker.takeSnapshot(std))
80+
81+
# Withdraw ETH from Compounding Staking Strategy
82+
txs.append(
83+
vault_oeth_admin.withdrawFromStrategy(
84+
COMPOUNDING_STAKING_SSV_STRAT,
85+
[WETH],
86+
[987.161129 * 10**18],
87+
{'from': STRATEGIST}
88+
)
89+
)
90+
91+
# Deposit WETH back to Compounding Staking Strategy so it can be deposited to 6 validators
92+
txs.append(
93+
vault_oeth_admin.depositToStrategy(
94+
COMPOUNDING_STAKING_SSV_STRAT,
95+
[WETH],
96+
[192 * 10**18],
97+
std
98+
)
99+
)
100+
101+
# After
102+
vault_change = vault_oeth_core.totalValue() - oeth_vault_value_checker.snapshots(STRATEGIST)[0]
103+
supply_change = oeth.totalSupply() - oeth_vault_value_checker.snapshots(STRATEGIST)[1]
104+
profit = vault_change - supply_change
105+
106+
txs.append(oeth_vault_value_checker.checkDelta(profit, (1 * 10**18), vault_change, (1 * 10**18), std))
107+
108+
print("-----")
109+
print("Profit", "{:.6f}".format(profit / 10**18), profit)
110+
print("OETH supply change", "{:.6f}".format(supply_change / 10**18), supply_change)
111+
print("Vault Change", "{:.6f}".format(vault_change / 10**18), vault_change)
112+
print("-----")

contracts/tasks/tasks.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,12 @@ subtask("exitValidators", "Starts the exit process from a list of validators")
14671467
30,
14681468
types.int
14691469
)
1470+
.addOptionalParam(
1471+
"consol",
1472+
"Call the consolidation controller instead of the strategy",
1473+
false,
1474+
types.boolean
1475+
)
14701476
.setAction(async (taskArgs) => {
14711477
const signer = await getSigner();
14721478

@@ -2501,8 +2507,19 @@ subtask(
25012507
false,
25022508
types.boolean
25032509
)
2510+
.addOptionalParam(
2511+
"consol",
2512+
"Call the consolidation controller instead of the strategy",
2513+
false,
2514+
types.boolean
2515+
)
25042516
.setAction(async (taskArgs) => {
25052517
const signer = await getSigner();
2518+
if (taskArgs.direct && taskArgs.consol) {
2519+
throw new Error(
2520+
"The consol option can not be used with direct withdrawals"
2521+
);
2522+
}
25062523
if (taskArgs.direct) {
25072524
await requestValidatorWithdraw({ ...taskArgs, signer });
25082525
} else {
@@ -2622,6 +2639,12 @@ subtask(
26222639
"10000910",
26232640
types.string
26242641
)
2642+
.addOptionalParam(
2643+
"consol",
2644+
"Call the consolidation controller instead of the strategy",
2645+
false,
2646+
types.boolean
2647+
)
26252648
.addOptionalParam(
26262649
"dryrun",
26272650
"Do not call stakeEth on the strategy contract. Just log the params and verify the deposit signature",

contracts/tasks/validator.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,20 +152,35 @@ async function snapValidators({ pubkeys }) {
152152
}
153153
}
154154

155-
async function exitValidator({ index, pubkey, operatorids, signer }) {
155+
async function exitValidator({
156+
index,
157+
pubkey,
158+
operatorids,
159+
signer,
160+
consol = false,
161+
}) {
156162
await verifyMinActivationTime({ pubkey });
157163

158164
log(`Splitting operator IDs ${operatorids}`);
159165
const operatorIds = operatorids.split(",").map((id) => parseInt(id));
160166

161167
const strategy = await resolveNativeStakingStrategyProxy(index);
168+
const contract = consol
169+
? await resolveContract("ConsolidationController")
170+
: strategy;
162171

163172
pubkey = checkPubkeyFormat(pubkey);
164173

165-
log(`About to exit validator ${pubkey}`);
166-
const tx = await strategy
167-
.connect(signer)
168-
.exitSsvValidator(pubkey, operatorIds);
174+
log(
175+
`About to exit validator ${pubkey} via ${
176+
consol ? "ConsolidationController" : "strategy"
177+
}`
178+
);
179+
const tx = consol
180+
? await contract
181+
.connect(signer)
182+
.exitSsvValidator(strategy.address, pubkey, operatorIds)
183+
: await contract.connect(signer).exitSsvValidator(pubkey, operatorIds);
169184
await logTxDetails(tx, "exitSsvValidator");
170185
}
171186

contracts/tasks/validatorCompound.js

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ async function stakeValidator({
139139
depositMessageRoot,
140140
forkVersion,
141141
uuid,
142+
consol = false,
142143
}) {
143144
const signer = await getSigner();
144145

@@ -163,6 +164,9 @@ async function stakeValidator({
163164
"CompoundingStakingSSVStrategyProxy",
164165
"CompoundingStakingSSVStrategy"
165166
);
167+
const contract = consol
168+
? await resolveContract("ConsolidationController")
169+
: strategy;
166170

167171
if (!withdrawalCredentials) {
168172
withdrawalCredentials = calcWithdrawalCredential("0x02", strategy.address);
@@ -207,9 +211,11 @@ async function stakeValidator({
207211
}
208212

209213
log(
210-
`About to stake ${amount} ETH to validator with pubkey ${pubkey}, deposit root ${depositDataRoot} and signature ${sig}`
214+
`About to stake ${amount} ETH to validator with pubkey ${pubkey}, deposit root ${depositDataRoot} and signature ${sig} via ${
215+
consol ? "ConsolidationController" : "strategy"
216+
}`
211217
);
212-
const tx = await strategy
218+
const tx = await contract
213219
.connect(signer)
214220
.stakeEth({ pubkey, signature: sig, depositDataRoot }, amountGwei);
215221
const receipt = await logTxDetails(tx, "stakeETH");
@@ -421,16 +427,23 @@ async function autoValidatorDeposits({
421427
}
422428
}
423429

424-
async function withdrawValidator({ pubkey, amount, signer }) {
425-
const strategy = await resolveContract(
426-
"CompoundingStakingSSVStrategyProxy",
427-
"CompoundingStakingSSVStrategy"
428-
);
430+
async function withdrawValidator({ pubkey, amount, signer, consol = false }) {
431+
const strategy = consol
432+
? await resolveContract("ConsolidationController")
433+
: await resolveContract(
434+
"CompoundingStakingSSVStrategyProxy",
435+
"CompoundingStakingSSVStrategy"
436+
);
429437

430438
/// Get the validator's balance
431439
const balance = await getValidatorBalance(pubkey);
432440

433441
const isFullExit = amount === undefined || amount === 0;
442+
if (consol && isFullExit) {
443+
throw new Error(
444+
"The ConsolidationController only supports partial withdrawals. Set a non-zero amount."
445+
);
446+
}
434447
const amountGwei = isFullExit ? 0 : parseUnits(amount.toString(), 9);
435448
if (isFullExit) {
436449
log(
@@ -441,13 +454,9 @@ async function withdrawValidator({ pubkey, amount, signer }) {
441454
);
442455
} else {
443456
log(
444-
`About to partially withdraw ${formatUnits(
445-
amountGwei,
446-
9
447-
)} ETH from validator with balance ${formatUnits(
448-
balance,
449-
9
450-
)} ETH and pubkey ${pubkey}`
457+
`About to partially withdraw ${formatUnits(amountGwei, 9)} ETH from ${
458+
consol ? "ConsolidationController" : "validator"
459+
} with balance ${formatUnits(balance, 9)} ETH and pubkey ${pubkey}`
451460
);
452461
}
453462
// Send 1 wei of value to cover the request withdrawal fee

contracts/utils/beacon.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ const getValidatorsIndividually = async (client, validatorIds, stateId) => {
227227
return validators;
228228
};
229229

230-
const getValidatorsByPost = async (
230+
const getValidatorsByGet = async (
231231
client,
232232
validatorIds,
233233
stateId,
@@ -237,31 +237,31 @@ const getValidatorsByPost = async (
237237

238238
for (let attempt = 1; attempt <= attempts; attempt++) {
239239
try {
240-
const postValidatorsRes = await client.beacon.postStateValidators({
240+
const getValidatorsRes = await client.beacon.getStateValidators({
241241
stateId,
242242
validatorIds,
243243
});
244244

245-
if (postValidatorsRes.ok) {
246-
return postValidatorsRes.value();
245+
if (getValidatorsRes.ok) {
246+
return getValidatorsRes.value();
247247
}
248248

249249
lastError = new Error(
250-
`Bulk validator POST failed with status ${postValidatorsRes.status} ${postValidatorsRes.statusText}`
250+
`Bulk validator GET failed with status ${getValidatorsRes.status} ${getValidatorsRes.statusText}`
251251
);
252252
log(`${lastError.message}. Attempt ${attempt} of ${attempts}.`);
253253
} catch (err) {
254254
lastError = err;
255255
log(
256-
`Bulk validator POST threw ${err.name || "Error"}: ${
256+
`Bulk validator GET threw ${err.name || "Error"}: ${
257257
err.message
258258
}. Attempt ${attempt} of ${attempts}.`
259259
);
260260
}
261261
}
262262

263263
if (lastError) {
264-
log(`Bulk validator POST failed after ${attempts} attempts.`);
264+
log(`Bulk validator GET failed after ${attempts} attempts.`);
265265
}
266266

267267
return null;
@@ -274,7 +274,7 @@ const getValidators = async (pubkeys, stateId = "head") => {
274274
log(
275275
`Fetching ${validatorIds.length} validator details at state ${stateId} from the beacon node`
276276
);
277-
let validators = await getValidatorsByPost(client, validatorIds, stateId);
277+
let validators = await getValidatorsByGet(client, validatorIds, stateId);
278278

279279
if (!validators) {
280280
validators = await getValidatorsIndividually(client, validatorIds, stateId);

0 commit comments

Comments
 (0)