Skip to content

Commit

Permalink
feat: deposit queue (CS-55) (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
madlabman authored Oct 26, 2023
1 parent 162d36e commit 1cb05ae
Show file tree
Hide file tree
Showing 10 changed files with 649 additions and 126 deletions.
Empty file modified .husky/pre-commit
100644 → 100755
Empty file.
449 changes: 338 additions & 111 deletions src/CommunityStakingModule.sol

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/interfaces/ICommunityStakingBondManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,6 @@ interface ICommunityStakingBondManager {
uint256 nodeOperatorId,
uint256 newKeysCount
) external view returns (uint256);

function penalize(uint256 nodeOperatorId, uint256 shares) external;
}
30 changes: 30 additions & 0 deletions src/lib/Batch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2023 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;

/// @author madlabman
library Batch {
/// @notice Serialize node operator id, batch start and count of keys into a single bytes32 value
function serialize(
uint128 nodeOperatorId,
uint64 start,
uint64 count
) internal pure returns (bytes32 s) {
return bytes32(abi.encodePacked(nodeOperatorId, start, count));
}

/// @notice Deserialize node operator id, batch start and count of keys from a single bytes32 value
function deserialize(
bytes32 b
) internal pure returns (uint128 nodeOperatorId, uint64 start, uint64 count) {
assembly {
nodeOperatorId := shr(128, b)
start := shr(64, b)
count := b
}
}

function isNil(bytes32 b) internal pure returns (bool) {
return b == bytes32(0);
}
}
81 changes: 81 additions & 0 deletions src/lib/QueueLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: 2023 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;


/// @author madlabman
library QueueLib {
bytes32 public constant NULL_POINTER = bytes32(0);

struct Queue {
mapping(bytes32 => bytes32) queue;
bytes32 front;
bytes32 back;
}

function enqueue(Queue storage self, bytes32 item) internal {
require(item != NULL_POINTER, "Queue: item is zero");
require(self.queue[item] == NULL_POINTER, "Queue: item already enqueued");

if (self.front == self.queue[self.front]) {
self.queue[self.front] = item;
}

self.queue[self.back] = item;
self.back = item;
}

function dequeue(Queue storage self) internal notEmpty(self) returns (bytes32 item) {
item = self.queue[self.front];
self.front = item;
}

function peek(Queue storage self) internal view returns (bytes32) {
return self.queue[self.front];
}

function at(Queue storage self, bytes32 pointer) internal view returns (bytes32) {
return self.queue[pointer];
}

function list(Queue storage self, bytes32 pointer, uint256 limit) internal notEmpty(self) view returns (
bytes32[] memory items,
bytes32 /* pointer */,
uint256 /* count */
) {
items = new bytes32[](limit);

uint256 i;
for (; i < limit; i++) {
bytes32 item = self.queue[pointer];
if (item == NULL_POINTER) {
break;
}

items[i] = item;
pointer = item;
}

return (items, pointer, i);
}

function isEmpty(Queue storage self) internal view returns (bool) {
return self.front == self.back;
}

function remove(Queue storage self, bytes32 pointerToItem, bytes32 item) internal {
require(self.queue[pointerToItem] == item, "Queue: wrong pointer given");

self.queue[pointerToItem] = self.queue[item];
self.queue[item] = NULL_POINTER;

if (self.back == item) {
self.back = pointerToItem;
}
}

modifier notEmpty(Queue storage self) {
require(!isEmpty(self), "Queue: empty");
_;
}
}
54 changes: 54 additions & 0 deletions test/Batch.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2023 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;

import "forge-std/Test.sol";

import { Batch } from "../src/lib/Batch.sol";

contract BatchTest is Test {
function test_serialize() public {
bytes32 b = Batch.serialize({
nodeOperatorId: 999,
start: 3,
count: 42
});

assertEq(
b,
// noIndex | start | count |
0x000000000000000000000000000003e70000000000000003000000000000002a
);
}

function test_deserialize() public {
(uint128 nodeOperatorId, uint64 start, uint64 count) = Batch
.deserialize(
0x0000000000000000000000000000000000000000000000000000000000000000
);

assertEq(nodeOperatorId, 0, "nodeOperatorId != 0");
assertEq(start, 0, "start != 0");
assertEq(count, 0, "count != 0");

(nodeOperatorId, start, count) = Batch.deserialize(
0x000000000000000000000000000003e70000000000000003000000000000002a
);

assertEq(nodeOperatorId, 999, "nodeOperatorId != 999");
assertEq(start, 3, "start != 3");
assertEq(count, 42, "count != 42");

(nodeOperatorId, start, count) = Batch.deserialize(
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
);

assertEq(
nodeOperatorId,
type(uint128).max,
"nodeOperatorId != uint128.max"
);
assertEq(start, type(uint64).max, "start != uint64.max");
assertEq(count, type(uint64).max, "count != uint64.max");
}
}
26 changes: 16 additions & 10 deletions test/CSMAddValidator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase {

{
vm.expectEmit(true, true, false, true, address(csm));
emit TotalKeysCountChanged(0, 1);
emit TotalSigningKeysCountChanged(0, 1);
vm.expectEmit(true, true, false, true, address(csm));
emit NodeOperatorAdded(0, "test", nodeOperator);
}
Expand All @@ -111,7 +111,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase {
vm.expectEmit(true, true, true, true, address(wstETH));
emit Approval(nodeOperator, address(bondManager), wstETHAmount);
vm.expectEmit(true, true, false, true, address(csm));
emit TotalKeysCountChanged(0, 1);
emit TotalSigningKeysCountChanged(0, 1);
vm.expectEmit(true, true, false, true, address(csm));
emit NodeOperatorAdded(0, "test", nodeOperator);
}
Expand Down Expand Up @@ -145,7 +145,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase {
(bytes memory keys, bytes memory signatures) = keysSignatures(1, 1);
{
vm.expectEmit(true, true, false, true, address(csm));
emit TotalKeysCountChanged(0, 2);
emit TotalSigningKeysCountChanged(0, 2);
}
csm.addValidatorKeysWstETH(noId, 1, keys, signatures);
}
Expand All @@ -170,7 +170,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase {
vm.expectEmit(true, true, true, true, address(wstETH));
emit Approval(nodeOperator, address(bondManager), wstETHAmount);
vm.expectEmit(true, true, false, true, address(csm));
emit TotalKeysCountChanged(0, 2);
emit TotalSigningKeysCountChanged(0, 2);
}
vm.prank(stranger);
csm.addValidatorKeysWstETHWithPermit(
Expand Down Expand Up @@ -198,7 +198,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase {

{
vm.expectEmit(true, true, false, true, address(csm));
emit TotalKeysCountChanged(0, 1);
emit TotalSigningKeysCountChanged(0, 1);
vm.expectEmit(true, true, false, true, address(csm));
emit NodeOperatorAdded(0, "test", nodeOperator);
}
Expand All @@ -218,7 +218,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase {
vm.expectEmit(true, true, true, true, address(stETH));
emit Approval(nodeOperator, address(bondManager), 2 ether);
vm.expectEmit(true, true, false, true, address(csm));
emit TotalKeysCountChanged(0, 1);
emit TotalSigningKeysCountChanged(0, 1);
vm.expectEmit(true, true, false, true, address(csm));
emit NodeOperatorAdded(0, "test", nodeOperator);
}
Expand Down Expand Up @@ -252,7 +252,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase {
stETH.submit{ value: 2 ether }(address(0));
{
vm.expectEmit(true, true, false, true, address(csm));
emit TotalKeysCountChanged(0, 2);
emit TotalSigningKeysCountChanged(0, 2);
}
csm.addValidatorKeysStETH(noId, 1, keys, signatures);
}
Expand All @@ -274,7 +274,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase {
vm.expectEmit(true, true, true, true, address(stETH));
emit Approval(nodeOperator, address(bondManager), required);
vm.expectEmit(true, true, false, true, address(csm));
emit TotalKeysCountChanged(0, 2);
emit TotalSigningKeysCountChanged(0, 2);
}
vm.prank(stranger);
csm.addValidatorKeysStETHWithPermit(
Expand Down Expand Up @@ -303,7 +303,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase {

{
vm.expectEmit(true, true, false, true, address(csm));
emit TotalKeysCountChanged(0, 1);
emit TotalSigningKeysCountChanged(0, 1);
vm.expectEmit(true, true, false, true, address(csm));
emit NodeOperatorAdded(0, "test", nodeOperator);
}
Expand All @@ -328,7 +328,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase {
vm.prank(nodeOperator);
{
vm.expectEmit(true, true, false, true, address(csm));
emit TotalKeysCountChanged(0, 2);
emit TotalSigningKeysCountChanged(0, 2);
}
csm.addValidatorKeysETH{ value: required }(noId, 1, keys, signatures);
}
Expand All @@ -349,6 +349,12 @@ contract CSMObtainDepositData is CSMCommon {
keys,
signatures
);

{
// Pretend to be a key validation oracle
csm.vetKeys(0, 1);
}

(bytes memory obtainedKeys, bytes memory obtainedSignatures) = csm
.obtainDepositData(1, "");
assertEq(obtainedKeys, keys);
Expand Down
2 changes: 1 addition & 1 deletion test/CSMInit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ contract CSMInitTest is Test, Fixtures {

function test_SetBondManager() public {
csm.setBondManager(address(bondManager));
assertEq(address(csm.bondManagerAddress()), address(bondManager));
assertEq(address(csm.bondManager()), address(bondManager));
}
}
Loading

0 comments on commit 1cb05ae

Please sign in to comment.