Skip to content

Commit

Permalink
feat: add submitProofOfDelinquent method
Browse files Browse the repository at this point in the history
  • Loading branch information
mkurayan committed Feb 26, 2025
1 parent 5d646fa commit 6197d38
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 1 deletion.
59 changes: 59 additions & 0 deletions contracts/0.8.9/interfaces/ICLProofVerifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.9;

type Slot is uint64;
type GIndex is bytes32;

// As defined in phase0/beacon-chain.md:356
struct Validator {
bytes pubkey;
bytes32 withdrawalCredentials;
uint64 effectiveBalance;
bool slashed;
uint64 activationEligibilityEpoch;
uint64 activationEpoch;
uint64 exitEpoch;
uint64 withdrawableEpoch;
}

// As defined in phase0/beacon-chain.md:436
struct BeaconBlockHeader {
Slot slot;
uint64 proposerIndex;
bytes32 parentRoot;
bytes32 stateRoot;
bytes32 bodyRoot;
}

struct ValidatorWitness {
uint64 validatorIndex;
Validator validator;
bytes32[] validatorProof;
}

struct ProvableBeaconBlockHeader {
BeaconBlockHeader header; // Header of a block which root is a root at rootsTimestamp.
uint64 rootsTimestamp; // To be passed to the EIP-4788 block roots contract.
}

// A witness for a block header which root is accessible via `historical_summaries` field.
struct HistoricalHeaderWitness {
BeaconBlockHeader header;
GIndex rootGIndex;
bytes32[] proof;
}

interface ICLProofVerifier {
function verifyValidatorProof(
ProvableBeaconBlockHeader calldata beaconBlock,
ValidatorWitness calldata witness
) external view;

function verifyHistoricalValidatorProof(
ProvableBeaconBlockHeader calldata beaconBlock,
HistoricalHeaderWitness calldata oldBlock,
ValidatorWitness calldata witness
) external view;
}
2 changes: 1 addition & 1 deletion contracts/0.8.9/oracle/ValidatorsExitBus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ contract ValidatorsExitBus is AccessControlEnumerable {
bytes32 internal constant EXIT_REQUESTS_HASHES_POSITION =
keccak256("lido.ValidatorsExitBus.reportHashes");

bytes32 private constant LOCATOR_CONTRACT_POSITION = keccak256("lido.ValidatorsExitBus.locatorContract");
bytes32 internal constant LOCATOR_CONTRACT_POSITION = keccak256("lido.ValidatorsExitBus.locatorContract");

constructor(address locatorAddr) {
_setLocatorAddress(locatorAddr);
Expand Down
59 changes: 59 additions & 0 deletions contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { UnstructuredStorage } from "../lib/UnstructuredStorage.sol";

import { BaseOracle } from "./BaseOracle.sol";
import { ValidatorsExitBus } from "./ValidatorsExitBus.sol";
import {ICLProofVerifier, ProvableBeaconBlockHeader, ValidatorWitness} from "../interfaces/ICLProofVerifier.sol";


interface IOracleReportSanityChecker {
Expand All @@ -36,6 +37,9 @@ contract ValidatorsExitBusOracle is BaseOracle, PausableUntil, ValidatorsExitBus
uint256 prevRequestedValidatorIndex,
uint256 requestedValidatorIndex
);
error MismatchedArrayLengths(uint256 keysCount, uint256 witnessesCount);
error PubkeyMismatch(bytes exitReportPubkey, bytes witnessPubkey);


event ValidatorExitRequest(
uint256 indexed stakingModuleId,
Expand Down Expand Up @@ -472,6 +476,61 @@ contract ValidatorsExitBusOracle is BaseOracle, PausableUntil, ValidatorsExitBus
emit StoredOracleTWExitRequestHash(exitRequestHash);
}

function submitProofOfDelinquent(
bytes calldata data,
ProvableBeaconBlockHeader calldata beaconBlock,
uint256[] calldata keyIndexes,
ValidatorWitness[] calldata validatorWitnesses
) external {
if (validatorWitnesses.length != keyIndexes.length) {
revert MismatchedArrayLengths(keyIndexes.length, validatorWitnesses.length);
}

bytes32 dataHash = keccak256(data);
RequestStatus storage requestStatus = _storageExitRequestsHashes()[dataHash];

if (requestStatus.contractVersion == 0) {
revert ExitHashWasNotSubmitted();
}

ICLProofVerifier clProofVerifier = ICLProofVerifier(getLocator().clProofVerifier());

uint256 lastDeliveredKeyIndex = requestStatus.deliveredItemsCount - 1;

bytes memory pubkey = new bytes(PUBLIC_KEY_LENGTH);
for (uint256 i = 0; i < keyIndexes.length; i++) {
if (keyIndexes[i] >= requestStatus.totalItemsCount) {
revert KeyIndexOutOfRange(keyIndexes[i], requestStatus.totalItemsCount);
}

if (keyIndexes[i] > lastDeliveredKeyIndex) {
revert KeyWasNotUnpacked(keyIndexes[i], lastDeliveredKeyIndex);
}

_copyPubkeyToMemory(data, pubkey, i);

if (keccak256(pubkey) != keccak256(validatorWitnesses[i].validator.pubkey)) {
revert PubkeyMismatch(pubkey, validatorWitnesses[i].validator.pubkey);
}

clProofVerifier.verifyValidatorProof(beaconBlock, validatorWitnesses[i]);
}
}

function _copyPubkeyToMemory(bytes calldata exitReport, bytes memory target, uint256 keyIndex) private pure {
assembly {
calldatacopy(
add(target, 32),
add(exitReport.offset, mul(keyIndex, PACKED_REQUEST_LENGTH)),
PUBLIC_KEY_LENGTH
)
}
}

function getLocator() public view returns (ILidoLocator) {
return ILidoLocator(LOCATOR_CONTRACT_POSITION.getStorageAddress());
}

///
/// Storage helpers
///
Expand Down

0 comments on commit 6197d38

Please sign in to comment.