Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 45 additions & 0 deletions brownie/runlogs/2026_04_strategist.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,48 @@ def main():
print("Pool OETH ", "{:.6f}".format(oethPoolBalance / 10**18), oethPoolBalance * 100 / totalPool)
print("Pool Total ", "{:.6f}".format(totalPool / 10**18), totalPool)
print("OETH Curve prices before and after", "{:.6f}".format(weth_out_before / 10**18), "{:.6f}".format(weth_out_after / 10**18))

# -------------------------------------------------------------
# Apr 7, 2026 - Withdraw from Compounding Staking Strategy
# -------------------------------------------------------------

from world import *

def main():
with TemporaryForkForReallocations() as txs:
# Before
txs.append(vault_oeth_core.rebase(std))
txs.append(oeth_vault_value_checker.takeSnapshot(std))

# Withdraw ETH from Compounding Staking Strategy
txs.append(
vault_oeth_admin.withdrawFromStrategy(
COMPOUNDING_STAKING_SSV_STRAT,
[WETH],
[987.161129 * 10**18],
{'from': STRATEGIST}
)
)

# Deposit WETH back to Compounding Staking Strategy so it can be deposited to 6 validators
txs.append(
vault_oeth_admin.depositToStrategy(
COMPOUNDING_STAKING_SSV_STRAT,
[WETH],
[192 * 10**18],
std
)
)

# After
vault_change = vault_oeth_core.totalValue() - oeth_vault_value_checker.snapshots(STRATEGIST)[0]
supply_change = oeth.totalSupply() - oeth_vault_value_checker.snapshots(STRATEGIST)[1]
profit = vault_change - supply_change

txs.append(oeth_vault_value_checker.checkDelta(profit, (1 * 10**18), vault_change, (1 * 10**18), std))

print("-----")
print("Profit", "{:.6f}".format(profit / 10**18), profit)
print("OETH supply change", "{:.6f}".format(supply_change / 10**18), supply_change)
print("Vault Change", "{:.6f}".format(vault_change / 10**18), vault_change)
print("-----")
23 changes: 23 additions & 0 deletions contracts/tasks/tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -1424,6 +1424,12 @@ subtask("exitValidators", "Starts the exit process from a list of validators")
30,
types.int
)
.addOptionalParam(
"consol",
"Call the consolidation controller instead of the strategy",
false,
types.boolean
)
.setAction(async (taskArgs) => {
const signer = await getSigner();

Expand Down Expand Up @@ -2458,8 +2464,19 @@ subtask(
false,
types.boolean
)
.addOptionalParam(
"consol",
"Call the consolidation controller instead of the strategy",
false,
types.boolean
)
.setAction(async (taskArgs) => {
const signer = await getSigner();
if (taskArgs.direct && taskArgs.consol) {
throw new Error(
"The consol option can not be used with direct withdrawals"
);
}
if (taskArgs.direct) {
await requestValidatorWithdraw({ ...taskArgs, signer });
} else {
Expand Down Expand Up @@ -2579,6 +2596,12 @@ subtask(
"10000910",
types.string
)
.addOptionalParam(
"consol",
"Call the consolidation controller instead of the strategy",
false,
types.boolean
)
.addOptionalParam(
"dryrun",
"Do not call stakeEth on the strategy contract. Just log the params and verify the deposit signature",
Expand Down
25 changes: 20 additions & 5 deletions contracts/tasks/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,35 @@ async function snapValidators({ pubkeys }) {
}
}

async function exitValidator({ index, pubkey, operatorids, signer }) {
async function exitValidator({
index,
pubkey,
operatorids,
signer,
consol = false,
}) {
await verifyMinActivationTime({ pubkey });

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

const strategy = await resolveNativeStakingStrategyProxy(index);
const contract = consol
? await resolveContract("ConsolidationController")
: strategy;

pubkey = checkPubkeyFormat(pubkey);

log(`About to exit validator ${pubkey}`);
const tx = await strategy
.connect(signer)
.exitSsvValidator(pubkey, operatorIds);
log(
`About to exit validator ${pubkey} via ${
consol ? "ConsolidationController" : "strategy"
}`
);
const tx = consol
? await contract
.connect(signer)
.exitSsvValidator(strategy.address, pubkey, operatorIds)
: await contract.connect(signer).exitSsvValidator(pubkey, operatorIds);
await logTxDetails(tx, "exitSsvValidator");
}

Expand Down
37 changes: 23 additions & 14 deletions contracts/tasks/validatorCompound.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ async function stakeValidator({
depositMessageRoot,
forkVersion,
uuid,
consol = false,
}) {
const signer = await getSigner();

Expand All @@ -152,6 +153,9 @@ async function stakeValidator({
"CompoundingStakingSSVStrategyProxy",
"CompoundingStakingSSVStrategy"
);
const contract = consol
? await resolveContract("ConsolidationController")
: strategy;

if (!withdrawalCredentials) {
withdrawalCredentials = calcWithdrawalCredential("0x02", strategy.address);
Expand Down Expand Up @@ -196,9 +200,11 @@ async function stakeValidator({
}

log(
`About to stake ${amount} ETH to validator with pubkey ${pubkey}, deposit root ${depositDataRoot} and signature ${sig}`
`About to stake ${amount} ETH to validator with pubkey ${pubkey}, deposit root ${depositDataRoot} and signature ${sig} via ${
consol ? "ConsolidationController" : "strategy"
}`
);
const tx = await strategy
const tx = await contract
.connect(signer)
.stakeEth({ pubkey, signature: sig, depositDataRoot }, amountGwei);
const receipt = await logTxDetails(tx, "stakeETH");
Expand Down Expand Up @@ -410,16 +416,23 @@ async function autoValidatorDeposits({
}
}

async function withdrawValidator({ pubkey, amount, signer }) {
const strategy = await resolveContract(
"CompoundingStakingSSVStrategyProxy",
"CompoundingStakingSSVStrategy"
);
async function withdrawValidator({ pubkey, amount, signer, consol = false }) {
const strategy = consol
? await resolveContract("ConsolidationController")
: await resolveContract(
"CompoundingStakingSSVStrategyProxy",
"CompoundingStakingSSVStrategy"
);

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

const isFullExit = amount === undefined || amount === 0;
if (consol && isFullExit) {
throw new Error(
"The ConsolidationController only supports partial withdrawals. Set a non-zero amount."
);
}
const amountGwei = isFullExit ? 0 : parseUnits(amount.toString(), 9);
if (isFullExit) {
log(
Expand All @@ -430,13 +443,9 @@ async function withdrawValidator({ pubkey, amount, signer }) {
);
} else {
log(
`About to partially withdraw ${formatUnits(
amountGwei,
9
)} ETH from validator with balance ${formatUnits(
balance,
9
)} ETH and pubkey ${pubkey}`
`About to partially withdraw ${formatUnits(amountGwei, 9)} ETH from ${
consol ? "ConsolidationController" : "validator"
} with balance ${formatUnits(balance, 9)} ETH and pubkey ${pubkey}`
);
}
// Send 1 wei of value to cover the request withdrawal fee
Expand Down
16 changes: 8 additions & 8 deletions contracts/utils/beacon.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ const getValidatorsIndividually = async (client, validatorIds, stateId) => {
return validators;
};

const getValidatorsByPost = async (
const getValidatorsByGet = async (
client,
validatorIds,
stateId,
Expand All @@ -237,31 +237,31 @@ const getValidatorsByPost = async (

for (let attempt = 1; attempt <= attempts; attempt++) {
try {
const postValidatorsRes = await client.beacon.postStateValidators({
const getValidatorsRes = await client.beacon.getStateValidators({
stateId,
validatorIds,
});

if (postValidatorsRes.ok) {
return postValidatorsRes.value();
if (getValidatorsRes.ok) {
return getValidatorsRes.value();
}

lastError = new Error(
`Bulk validator POST failed with status ${postValidatorsRes.status} ${postValidatorsRes.statusText}`
`Bulk validator GET failed with status ${getValidatorsRes.status} ${getValidatorsRes.statusText}`
);
log(`${lastError.message}. Attempt ${attempt} of ${attempts}.`);
} catch (err) {
lastError = err;
log(
`Bulk validator POST threw ${err.name || "Error"}: ${
`Bulk validator GET threw ${err.name || "Error"}: ${
err.message
}. Attempt ${attempt} of ${attempts}.`
);
}
}

if (lastError) {
log(`Bulk validator POST failed after ${attempts} attempts.`);
log(`Bulk validator GET failed after ${attempts} attempts.`);
}

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

if (!validators) {
validators = await getValidatorsIndividually(client, validatorIds, stateId);
Expand Down
Loading