-
Notifications
You must be signed in to change notification settings - Fork 198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[VAULTS] PredepositGuarantee #932
base: feat/vaults
Are you sure you want to change the base?
Changes from 42 commits
c19a543
b710b58
86f80bf
a0de885
295ba6e
938a519
34d203d
21f475e
331a6e6
2c84783
ccde1b2
d0954f7
c5312c0
9d2b349
e4d3ebc
74917ee
a17c375
8674bba
87c7e01
ad9e476
5212738
0d5f663
ae6d487
2fc25b6
563d852
d2f5f24
4ef7550
6d6242b
ecc06c6
ef47aed
3bb45e8
500e8be
56b7752
364e1e7
04a70a9
3576bb9
16014b0
7b61c6a
6e3b647
8c45174
60b3157
3223ab5
917c685
966ab9c
e79014d
d077613
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// SPDX-FileCopyrightText: 2024 Lido <[email protected]> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
/* | ||
GIndex library from CSM | ||
original: https://github.com/lidofinance/community-staking-module/blob/7071c2096983a7780a5f147963aaa5405c0badb1/src/lib/GIndex.sol | ||
*/ | ||
|
||
pragma solidity 0.8.25; | ||
|
||
type GIndex is bytes32; | ||
|
||
using {isRoot, isParentOf, index, width, shr, shl, concat, unwrap, pow} for GIndex global; | ||
|
||
error IndexOutOfRange(); | ||
|
||
/// @param gI Is a generalized index of a node in a tree. | ||
/// @param p Is a power of a tree level the node belongs to. | ||
/// @return GIndex | ||
function pack(uint256 gI, uint8 p) pure returns (GIndex) { | ||
if (gI > type(uint248).max) { | ||
revert IndexOutOfRange(); | ||
} | ||
|
||
// NOTE: We can consider adding additional metadata like a fork version. | ||
return GIndex.wrap(bytes32((gI << 8) | p)); | ||
} | ||
|
||
function unwrap(GIndex self) pure returns (bytes32) { | ||
return GIndex.unwrap(self); | ||
} | ||
|
||
function isRoot(GIndex self) pure returns (bool) { | ||
return index(self) == 1; | ||
} | ||
|
||
function index(GIndex self) pure returns (uint256) { | ||
return uint256(unwrap(self)) >> 8; | ||
} | ||
|
||
function width(GIndex self) pure returns (uint256) { | ||
return 1 << pow(self); | ||
} | ||
|
||
function pow(GIndex self) pure returns (uint8) { | ||
return uint8(uint256(unwrap(self))); | ||
} | ||
|
||
/// @return Generalized index of the nth neighbor of the node to the right. | ||
function shr(GIndex self, uint256 n) pure returns (GIndex) { | ||
uint256 i = index(self); | ||
uint256 w = width(self); | ||
|
||
if ((i % w) + n >= w) { | ||
revert IndexOutOfRange(); | ||
} | ||
|
||
return pack(i + n, pow(self)); | ||
} | ||
|
||
/// @return Generalized index of the nth neighbor of the node to the left. | ||
function shl(GIndex self, uint256 n) pure returns (GIndex) { | ||
uint256 i = index(self); | ||
uint256 w = width(self); | ||
|
||
if (i % w < n) { | ||
revert IndexOutOfRange(); | ||
} | ||
|
||
return pack(i - n, pow(self)); | ||
} | ||
|
||
// See https://github.com/protolambda/remerkleable/blob/91ed092d08ef0ba5ab076f0a34b0b371623db728/remerkleable/tree.py#L46 | ||
function concat(GIndex lhs, GIndex rhs) pure returns (GIndex) { | ||
uint256 lhsMSbIndex = fls(index(lhs)); | ||
uint256 rhsMSbIndex = fls(index(rhs)); | ||
|
||
if (lhsMSbIndex + 1 + rhsMSbIndex > 248) { | ||
revert IndexOutOfRange(); | ||
} | ||
|
||
return pack((index(lhs) << rhsMSbIndex) | (index(rhs) ^ (1 << rhsMSbIndex)), pow(rhs)); | ||
} | ||
|
||
function isParentOf(GIndex self, GIndex child) pure returns (bool) { | ||
uint256 parentIndex = index(self); | ||
uint256 childIndex = index(child); | ||
|
||
if (parentIndex >= childIndex) { | ||
return false; | ||
} | ||
|
||
while (childIndex > 0) { | ||
if (childIndex == parentIndex) { | ||
return true; | ||
} | ||
|
||
childIndex = childIndex >> 1; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/// @dev From Solady LibBit, see https://github.com/Vectorized/solady/blob/main/src/utils/LibBit.sol. | ||
/// @dev Find last set. | ||
/// Returns the index of the most significant bit of `x`, | ||
/// counting from the least significant bit position. | ||
/// If `x` is zero, returns 256. | ||
function fls(uint256 x) pure returns (uint256 r) { | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
// prettier-ignore | ||
r := or(shl(8, iszero(x)), shl(7, lt(0xffffffffffffffffffffffffffffffff, x))) | ||
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) | ||
r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) | ||
r := or(r, shl(4, lt(0xffff, shr(r, x)))) | ||
r := or(r, shl(3, lt(0xff, shr(r, x)))) | ||
// prettier-ignore | ||
r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)), | ||
0x0706060506020504060203020504030106050205030304010505030400000000)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// SPDX-FileCopyrightText: 2024 Lido <[email protected]> | ||
Jeday marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
// See contracts/COMPILERS.md | ||
pragma solidity 0.8.25; | ||
|
||
import {GIndex} from "./GIndex.sol"; | ||
|
||
/* | ||
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 | ||
*/ | ||
library SSZ { | ||
error BranchHasMissingItem(); | ||
error BranchHasExtraItem(); | ||
error InvalidProof(); | ||
error InvalidPubkeyLength(); | ||
|
||
/// @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`. | ||
function verifyProof(bytes32[] calldata proof, bytes32 root, bytes32 leaf, GIndex gIndex) internal view { | ||
uint256 index = gIndex.index(); | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
// Check if `proof` is empty. | ||
if iszero(proof.length) { | ||
// revert InvalidProof() | ||
mstore(0x00, 0x09bde339) | ||
revert(0x1c, 0x04) | ||
} | ||
// Left shift by 5 is equivalent to multiplying by 0x20. | ||
let end := add(proof.offset, shl(5, proof.length)) | ||
// Initialize `offset` to the offset of `proof` in the calldata. | ||
let offset := proof.offset | ||
// Iterate over proof elements to compute root hash. | ||
// prettier-ignore | ||
for { } 1 { } { | ||
// Slot of `leaf` in scratch space. | ||
// If the condition is true: 0x20, otherwise: 0x00. | ||
let scratch := shl(5, and(index, 1)) | ||
index := shr(1, index) | ||
if iszero(index) { | ||
// revert BranchHasExtraItem() | ||
mstore(0x00, 0x5849603f) | ||
// 0x1c = 28 => offset in 32-byte word of a slot 0x00 | ||
revert(0x1c, 0x04) | ||
} | ||
// Store elements to hash contiguously in scratch space. | ||
// Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes. | ||
mstore(scratch, leaf) | ||
mstore(xor(scratch, 0x20), calldataload(offset)) | ||
// Call sha256 precompile. | ||
let result := staticcall( | ||
gas(), | ||
0x02, | ||
0x00, | ||
0x40, | ||
0x00, | ||
0x20 | ||
) | ||
|
||
if iszero(result) { | ||
// Precompile returns no data on OutOfGas error. | ||
revert(0, 0) | ||
} | ||
|
||
// Reuse `leaf` to store the hash to reduce stack operations. | ||
leaf := mload(0x00) | ||
offset := add(offset, 0x20) | ||
if iszero(lt(offset, end)) { | ||
break | ||
} | ||
} | ||
|
||
if iszero(eq(index, 1)) { | ||
// revert BranchHasMissingItem() | ||
mstore(0x00, 0x1b6661c3) | ||
revert(0x1c, 0x04) | ||
} | ||
|
||
if iszero(eq(leaf, root)) { | ||
// revert InvalidProof() | ||
mstore(0x00, 0x09bde339) | ||
revert(0x1c, 0x04) | ||
} | ||
} | ||
} | ||
|
||
/// @notice Extracted part from `verifyProof` for hashing two leaves | ||
/// @dev Combines 2 bytes32 in 64 bytes input for sha256 precompile | ||
function sha256Pair(bytes32 left, bytes32 right) internal view returns (bytes32 result) { | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
// Store `left` at memory position 0x00 | ||
mstore(0x00, left) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's revisit this later: 'The zero slot is used as initial value for dynamic memory arrays and should never be written to (the free memory pointer points to 0x80 initially)' https://docs.soliditylang.org/en/latest/internals/layout_in_memory.html There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's okay to use 64 bytes of scratch space to store precomile input, it will be overwritten by staticall. |
||
// Store `right` at memory position 0x20 | ||
mstore(0x20, right) | ||
|
||
// Call SHA-256 precompile (0x02) with 64-byte input at memory 0x00 | ||
let success := staticcall(gas(), 0x02, 0x00, 0x40, 0x00, 0x20) | ||
if iszero(success) { | ||
revert(0, 0) | ||
} | ||
|
||
// Load the resulting hash from memory | ||
result := mload(0x00) | ||
} | ||
} | ||
|
||
/// @notice Extracted and modified part from `hashTreeRoot` for hashing validator pubkey from calldata | ||
/// @dev Reverts if `pubkey` length is not 48 | ||
function pubkeyRoot(bytes calldata pubkey) internal view returns (bytes32 _pubkeyRoot) { | ||
if (pubkey.length != 48) revert InvalidPubkeyLength(); | ||
|
||
/// @solidity memory-safe-assembly | ||
assembly { | ||
// Copy 48 bytes of `pubkey` to memory at 0x00 | ||
calldatacopy(0x00, pubkey.offset, 48) | ||
|
||
// Zero the remaining 16 bytes to form a 64-byte input block | ||
mstore(0x30, 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
|
||
// Call the SHA-256 precompile (0x02) with the 64-byte input | ||
if iszero(staticcall(gas(), 0x02, 0x00, 0x40, 0x00, 0x20)) { | ||
revert(0, 0) | ||
} | ||
|
||
// Load the resulting SHA-256 hash | ||
_pubkeyRoot := mload(0x00) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks like it should be collected under a library
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but
using
allows it act as one