Skip to content

Commit

Permalink
Merge branch 'develop' into gates-contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
dgusakov authored Jan 10, 2025
2 parents cfd8209 + 72eace1 commit 78fa23e
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 480 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"gindex": "node script/gindex.mjs"
},
"dependencies": {
"@lodestar/types": "^1.18.1",
"@lodestar/types": "^1.23.1",
"@openzeppelin/contracts": "5.0.2",
"@openzeppelin/contracts-upgradeable": "5.0.2",
"@openzeppelin/merkle-tree": "^1.0.6",
Expand Down
106 changes: 106 additions & 0 deletions script/DeployCSVerifierElectra.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

// Usage: forge script ./script/DeployCSVerifierElectra.s.sol:DeployCSVerifier[Holesky|Mainnet]

pragma solidity 0.8.24;

import { Script } from "forge-std/Script.sol";
import { console2 as console } from "forge-std/console2.sol";

import { CSVerifier } from "../src/CSVerifier.sol";
import { GIndex } from "../src/lib/GIndex.sol";
import { Slot } from "../src/lib/Types.sol";

struct Config {
address withdrawalVault;
address module;
GIndex gIFirstWithdrawalPrev;
GIndex gIFirstWithdrawalCurr;
GIndex gIFirstValidatorPrev;
GIndex gIFirstValidatorCurr;
GIndex gIHistoricalSummariesPrev;
GIndex gIHistoricalSummariesCurr;
Slot firstSupportedSlot;
Slot pivotSlot;
uint64 slotsPerEpoch;
}

// Check the constants below via `yarn run gindex`.

GIndex constant HISTORICAL_SUMMARIES_DENEB = GIndex.wrap(
0x0000000000000000000000000000000000000000000000000000000000003b00
);
GIndex constant FIRST_WITHDRAWAL_DENEB = GIndex.wrap(
0x0000000000000000000000000000000000000000000000000000000000e1c004
);
GIndex constant FIRST_VALIDATOR_DENEB = GIndex.wrap(
0x0000000000000000000000000000000000000000000000000056000000000028
);

GIndex constant HISTORICAL_SUMMARIES_ELECTRA = GIndex.wrap(
0x0000000000000000000000000000000000000000000000000000000000005b00
);
GIndex constant FIRST_WITHDRAWAL_ELECTRA = GIndex.wrap(
0x000000000000000000000000000000000000000000000000000000000161c004
);
GIndex constant FIRST_VALIDATOR_ELECTRA = GIndex.wrap(
0x0000000000000000000000000000000000000000000000000096000000000028
);

abstract contract DeployCSVerifier is Script {
Config internal config;

function run() public {
CSVerifier verifier = new CSVerifier({
withdrawalAddress: config.withdrawalVault,
module: config.module,
slotsPerEpoch: config.slotsPerEpoch,
gIFirstWithdrawalPrev: config.gIFirstWithdrawalPrev,
gIFirstWithdrawalCurr: config.gIFirstWithdrawalCurr,
gIFirstValidatorPrev: config.gIFirstValidatorPrev,
gIFirstValidatorCurr: config.gIFirstValidatorCurr,
gIHistoricalSummariesPrev: config.gIHistoricalSummariesPrev,
gIHistoricalSummariesCurr: config.gIHistoricalSummariesCurr,
firstSupportedSlot: config.firstSupportedSlot,
pivotSlot: config.pivotSlot
});
console.log("CSVerifier deployed at:", address(verifier));
}
}

contract DeployCSVerifierHolesky is DeployCSVerifier {
constructor() {
config = Config({
withdrawalVault: 0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9,
module: 0x4562c3e63c2e586cD1651B958C22F88135aCAd4f,
slotsPerEpoch: 32,
gIFirstWithdrawalPrev: FIRST_WITHDRAWAL_DENEB,
gIFirstWithdrawalCurr: FIRST_WITHDRAWAL_ELECTRA,
gIFirstValidatorPrev: FIRST_VALIDATOR_DENEB,
gIFirstValidatorCurr: FIRST_VALIDATOR_ELECTRA,
gIHistoricalSummariesPrev: HISTORICAL_SUMMARIES_DENEB,
gIHistoricalSummariesCurr: HISTORICAL_SUMMARIES_ELECTRA,
firstSupportedSlot: Slot.wrap(950272), // 269_568 * 32, @see https://github.com/eth-clients/mainnet/blob/main/metadata/config.yaml#L52
pivotSlot: Slot.wrap(0) // TODO: Update with Electra slot.
});
}
}

contract DeployCSVerifierMainnet is DeployCSVerifier {
constructor() {
config = Config({
withdrawalVault: 0xB9D7934878B5FB9610B3fE8A5e441e8fad7E293f,
module: 0xdA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F,
slotsPerEpoch: 32,
gIFirstWithdrawalPrev: FIRST_WITHDRAWAL_DENEB,
gIFirstWithdrawalCurr: FIRST_WITHDRAWAL_ELECTRA,
gIFirstValidatorPrev: FIRST_VALIDATOR_DENEB,
gIFirstValidatorCurr: FIRST_VALIDATOR_ELECTRA,
gIHistoricalSummariesPrev: HISTORICAL_SUMMARIES_DENEB,
gIHistoricalSummariesCurr: HISTORICAL_SUMMARIES_ELECTRA,
firstSupportedSlot: Slot.wrap(8626176), // 29_696 * 32, @see https://github.com/eth-clients/holesky/blob/main/metadata/config.yaml#L38
pivotSlot: Slot.wrap(0) // TODO: Update with Electra slot.
});
}
}
5 changes: 0 additions & 5 deletions script/fork-helpers/NodeOperators.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,6 @@ contract NodeOperators is
);
}

function slash(uint256 noId, uint256 keyIndex) external broadcastVerifier {
csm.submitInitialSlashing(noId, keyIndex);
assertTrue(csm.isValidatorSlashed(noId, keyIndex));
}

function targetLimit(
uint256 noId,
uint256 targetLimitMode,
Expand Down
4 changes: 2 additions & 2 deletions script/gindex.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import { concatGindices } from "@chainsafe/persistent-merkle-tree";
import { ssz } from "@lodestar/types";

for (const fork of ["deneb"]) {
/** @type ssz.deneb */
for (const fork of ["deneb", "electra"]) {
/** @type ssz.deneb|ssz.electra */
const Fork = ssz[fork];

{
Expand Down
40 changes: 5 additions & 35 deletions src/CSModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ contract CSModule is

uint256 private _nonce;
mapping(uint256 => NodeOperator) private _nodeOperators;
// @dev see _keyPointer function for details of noKeyIndexPacked structure
/// @dev see _keyPointer function for details of noKeyIndexPacked structure
mapping(uint256 noKeyIndexPacked => bool) private _isValidatorWithdrawn;
/// @dev DEPRECATED! No writes expected after Pectra hard-fork
mapping(uint256 noKeyIndexPacked => bool) private _isValidatorSlashed;

uint64 private _totalDepositedValidators;
Expand Down Expand Up @@ -865,13 +866,13 @@ contract CSModule is
emit WithdrawalSubmitted(nodeOperatorId, keyIndex, amount, pubkey);

if (isSlashed) {
// NOTE: Can't remove the check so far to avoid double-accounting of penalty. Make sure
// we decided to go with CSVerifier with no processSalshingProof function deployed first
// with some meaningful grace period.
if (_isValidatorSlashed[pointer]) {
// Account already burned value
unchecked {
amount += INITIAL_SLASHING_PENALTY;
}
} else {
_isValidatorSlashed[pointer] = true;
}
// Bond curve should be reset to default in case of slashing. See https://hackmd.io/@lido/SygBLW5ja
accounting.resetBondCurve(nodeOperatorId);
Expand All @@ -890,37 +891,6 @@ contract CSModule is
});
}

/// @inheritdoc ICSModule
function submitInitialSlashing(
uint256 nodeOperatorId,
uint256 keyIndex
) external onlyRole(VERIFIER_ROLE) {
_onlyExistingNodeOperator(nodeOperatorId);
NodeOperator storage no = _nodeOperators[nodeOperatorId];
if (keyIndex >= no.totalDepositedKeys) {
revert SigningKeysInvalidOffset();
}

uint256 pointer = _keyPointer(nodeOperatorId, keyIndex);

if (_isValidatorSlashed[pointer]) {
revert AlreadySubmitted();
}

_isValidatorSlashed[pointer] = true;

bytes memory pubkey = SigningKeys.loadKeys(nodeOperatorId, keyIndex, 1);
emit InitialSlashingSubmitted(nodeOperatorId, keyIndex, pubkey);

accounting.penalize(nodeOperatorId, INITIAL_SLASHING_PENALTY);

// Nonce should be updated if depositableValidators change
_updateDepositableValidatorsCount({
nodeOperatorId: nodeOperatorId,
incrementNonceIfUpdated: true
});
}

/// @inheritdoc IStakingModule
/// @dev Resets the key removal charge
/// @dev Changing the WC means that the current deposit data in the queue is not valid anymore and can't be deposited
Expand Down
47 changes: 0 additions & 47 deletions src/CSVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,53 +103,6 @@ contract CSVerifier is ICSVerifier {
PIVOT_SLOT = pivotSlot;
}

/// @inheritdoc ICSVerifier
function processSlashingProof(
ProvableBeaconBlockHeader calldata beaconBlock,
SlashingWitness calldata witness,
uint256 nodeOperatorId,
uint256 keyIndex
) external {
if (beaconBlock.header.slot < FIRST_SUPPORTED_SLOT) {
revert UnsupportedSlot(beaconBlock.header.slot);
}

{
bytes32 trustedHeaderRoot = _getParentBlockRoot(
beaconBlock.rootsTimestamp
);
if (trustedHeaderRoot != beaconBlock.header.hashTreeRoot()) {
revert InvalidBlockHeader();
}
}

bytes memory pubkey = MODULE.getSigningKeys(
nodeOperatorId,
keyIndex,
1
);

Validator memory validator = Validator({
pubkey: pubkey,
withdrawalCredentials: witness.withdrawalCredentials,
effectiveBalance: witness.effectiveBalance,
slashed: true,
activationEligibilityEpoch: witness.activationEligibilityEpoch,
activationEpoch: witness.activationEpoch,
exitEpoch: witness.exitEpoch,
withdrawableEpoch: witness.withdrawableEpoch
});

SSZ.verifyProof({
proof: witness.validatorProof,
root: beaconBlock.header.stateRoot,
leaf: validator.hashTreeRoot(),
gI: _getValidatorGI(witness.validatorIndex, beaconBlock.header.slot)
});

MODULE.submitInitialSlashing(nodeOperatorId, keyIndex);
}

/// @inheritdoc ICSVerifier
function processWithdrawalProof(
ProvableBeaconBlockHeader calldata beaconBlock,
Expand Down
19 changes: 3 additions & 16 deletions src/interfaces/ICSModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,6 @@ interface ICSModule is IQueueLib, INOAddresses, IAssetRecovererLib {
uint256 amount,
bytes pubkey
);
event InitialSlashingSubmitted(
uint256 indexed nodeOperatorId,
uint256 keyIndex,
bytes pubkey
);

event PublicRelease();
event KeyRemovalChargeSet(uint256 amount);
Expand Down Expand Up @@ -474,16 +469,6 @@ interface ICSModule is IQueueLib, INOAddresses, IAssetRecovererLib {
uint256 keysCount
) external view returns (bytes memory keys, bytes memory signatures);

/// @notice Report Node Operator's key as slashed and apply the initial slashing penalty
/// @notice Called by the Verifier contract.
/// See `CSVerifier.processSlashingProof` to use this method permissionless
/// @param nodeOperatorId ID of the Node Operator
/// @param keyIndex Index of the slashed key in the Node Operator's keys storage
function submitInitialSlashing(
uint256 nodeOperatorId,
uint256 keyIndex
) external;

/// @notice Report Node Operator's key as withdrawn and settle withdrawn amount
/// @notice Called by the Verifier contract.
/// See `CSVerifier.processWithdrawalProof` to use this method permissionless
Expand All @@ -498,7 +483,9 @@ interface ICSModule is IQueueLib, INOAddresses, IAssetRecovererLib {
bool isSlashed
) external;

/// @notice Check if the given Node Operator's key is reported as slashed
/// @notice DEPRECATED! Check if the given Node Operator's key is reported as slashed
/// @notice Since pectra update the contract doesn't store slashing flag of a withdrawn
/// validator
/// @param nodeOperatorId ID of the Node Operator
/// @param keyIndex Index of the key to check
/// @return Validator reported as slashed flag
Expand Down
12 changes: 0 additions & 12 deletions src/interfaces/ICSVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,6 @@ interface ICSVerifier {

function MODULE() external view returns (ICSModule);

/// @notice Verify slashing proof and report slashing to the module for valid proofs
/// @param beaconBlock Beacon block header
/// @param witness Slashing witness against the `beaconBlock`'s state root.
/// @param nodeOperatorId ID of the Node Operator
/// @param keyIndex Index of the validator key in the Node Operator's key storage
function processSlashingProof(
ProvableBeaconBlockHeader calldata beaconBlock,
SlashingWitness calldata witness,
uint256 nodeOperatorId,
uint256 keyIndex
) external;

/// @notice Verify withdrawal proof and report withdrawal to the module for valid proofs
/// @param beaconBlock Beacon block header
/// @param witness Withdrawal witness against the `beaconBlock`'s state root.
Expand Down
Loading

0 comments on commit 78fa23e

Please sign in to comment.