Skip to content

Commit

Permalink
feat: allow GIndex change via slot proof
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeday committed Feb 13, 2025
1 parent e79014d commit d077613
Show file tree
Hide file tree
Showing 9 changed files with 528 additions and 266 deletions.
113 changes: 108 additions & 5 deletions contracts/0.8.25/lib/SSZ.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
// SPDX-FileCopyrightText: 2024 Lido <info@lido.fi>
// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0

// See contracts/COMPILERS.md
pragma solidity 0.8.25;

import {GIndex} from "./GIndex.sol";

struct BeaconBlockHeader {
uint64 slot;
uint64 proposerIndex;
bytes32 parentRoot;
bytes32 stateRoot;
bytes32 bodyRoot;
}

/*
Cut and modified version of SSZ library from CSM only has methods for merkilized SSZ proof validation
original: https://github.com/lidofinance/community-staking-module/blob/7071c2096983a7780a5f147963aaa5405c0badb1/src/lib/SSZ.sol
Expand All @@ -15,6 +23,83 @@ library SSZ {
error BranchHasExtraItem();
error InvalidProof();
error InvalidPubkeyLength();
error InvalidBlockHeader();

/// @notice Modified version of `hashTreeRoot` from CSM to verify beacon block header against beacon root
/// @dev Reverts with InvalidBlockHeader` if calculated root doesn't match expected root
function verifyBeaconBlockHeader(BeaconBlockHeader calldata header, bytes32 expectedRoot) internal view {
bytes32[8] memory nodes = [
toLittleEndian(header.slot),
toLittleEndian(header.proposerIndex),
header.parentRoot,
header.stateRoot,
header.bodyRoot,
bytes32(0),
bytes32(0),
bytes32(0)
];

bytes32 root;

/// @solidity memory-safe-assembly
assembly {
// Count of nodes to hash
let count := 8

// Loop over levels
// prettier-ignore
for { } 1 { } {
// Loop over nodes at the given depth

// Initialize `offset` to the offset of `proof` elements in memory.
let target := nodes
let source := nodes
let end := add(source, shl(5, count))

// prettier-ignore
for { } 1 { } {
// Read next two hashes to hash
mcopy(0x00, source, 0x40)

// Call sha256 precompile
let result := staticcall(
gas(),
0x02,
0x00,
0x40,
0x00,
0x20
)

if iszero(result) {
// Precompiles returns no data on OutOfGas error.
revert(0, 0)
}

// Store the resulting hash at the target location
mstore(target, mload(0x00))

// Advance the pointers
target := add(target, 0x20)
source := add(source, 0x40)

if iszero(lt(source, end)) {
break
}
}

count := shr(1, count)
if eq(count, 1) {
root := mload(0x00)
break
}
}
}

if (root != expectedRoot) {
revert InvalidProof();
}
}

/// @notice Modified version of `verify` from Solady `MerkleProofLib` to support generalized indices and sha256 precompile.
/// @dev Reverts if `leaf` doesn't exist in the Merkle tree with `root`, given `proof`.
Expand Down Expand Up @@ -114,12 +199,12 @@ library SSZ {

/// @solidity memory-safe-assembly
assembly {
// Copy 48 bytes of `pubkey` to memory at 0x00
// write 32 bytes to 32-64 bytes of scratch space
// to ensure last 49-64 bytes of pubkey are zeroed
mstore(0x20, 0)
// Copy 48 bytes of `pubkey` to start of scratch space
calldatacopy(0x00, pubkey.offset, 48)

// Zero the remaining 16 bytes to form a 64-byte input block
mstore(0x30, 0)

// Call the SHA-256 precompile (0x02) with the 64-byte input
if iszero(staticcall(gas(), 0x02, 0x00, 0x40, 0x00, 0x20)) {
revert(0, 0)
Expand All @@ -129,4 +214,22 @@ library SSZ {
_pubkeyRoot := mload(0x00)
}
}

// See https://github.com/succinctlabs/telepathy-contracts/blob/5aa4bb7/src/libraries/SimpleSerialize.sol#L17-L28
function toLittleEndian(uint256 v) public pure returns (bytes32) {
v =
((v & 0xFF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00) >> 8) |
((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);
v =
((v & 0xFFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000) >> 16) |
((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);
v =
((v & 0xFFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000) >> 32) |
((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);
v =
((v & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF0000000000000000) >> 64) |
((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);
v = (v >> 128) | (v << 128);
return bytes32(v);
}
}
4 changes: 2 additions & 2 deletions contracts/0.8.25/vaults/Permissions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {AccessControlVoteable} from "contracts/0.8.25/utils/AccessControlVoteabl
import {OwnableUpgradeable} from "contracts/openzeppelin/5.2/upgradeable/access/OwnableUpgradeable.sol";

import {IStakingVault} from "./interfaces/IStakingVault.sol";
import {IPredepositGuarantee} from "./interfaces/IPredepositGuarantee.sol";
import {PredepositGuarantee} from "./predeposit_guarantee/PredepositGuarantee.sol";
import {VaultHub} from "./VaultHub.sol";

/**
Expand Down Expand Up @@ -164,7 +164,7 @@ abstract contract Permissions is AccessControlVoteable {
bytes calldata _pubkey,
address _recipient
) internal onlyRole(PDG_WITHDRAWAL_ROLE) returns (uint128) {
return IPredepositGuarantee(stakingVault().depositGuardian()).withdrawDisprovenPredeposit(_pubkey, _recipient);
return PredepositGuarantee(stakingVault().depositGuardian()).withdrawDisprovenPredeposit(_pubkey, _recipient);
}

function _transferStakingVaultOwnership(address _newOwner) internal onlyIfVotedBy(_votingCommittee()) {
Expand Down
94 changes: 0 additions & 94 deletions contracts/0.8.25/vaults/interfaces/IPredepositGuarantee.sol

This file was deleted.

Loading

0 comments on commit d077613

Please sign in to comment.