From 6197d38cfde37f1edbe66fdddb7683c319d66818 Mon Sep 17 00:00:00 2001 From: Maksim Kuraian Date: Wed, 26 Feb 2025 14:56:23 +0100 Subject: [PATCH] feat: add submitProofOfDelinquent method --- .../0.8.9/interfaces/ICLProofVerifier.sol | 59 +++++++++++++++++++ contracts/0.8.9/oracle/ValidatorsExitBus.sol | 2 +- .../0.8.9/oracle/ValidatorsExitBusOracle.sol | 59 +++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 contracts/0.8.9/interfaces/ICLProofVerifier.sol diff --git a/contracts/0.8.9/interfaces/ICLProofVerifier.sol b/contracts/0.8.9/interfaces/ICLProofVerifier.sol new file mode 100644 index 000000000..89f0f15ee --- /dev/null +++ b/contracts/0.8.9/interfaces/ICLProofVerifier.sol @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2025 Lido +// 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; +} diff --git a/contracts/0.8.9/oracle/ValidatorsExitBus.sol b/contracts/0.8.9/oracle/ValidatorsExitBus.sol index cfe00610d..350bd9cc8 100644 --- a/contracts/0.8.9/oracle/ValidatorsExitBus.sol +++ b/contracts/0.8.9/oracle/ValidatorsExitBus.sol @@ -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); diff --git a/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol b/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol index fb23025ea..48145cae0 100644 --- a/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol +++ b/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol @@ -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 { @@ -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, @@ -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 ///