Skip to content
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

feat: deposit queue (CS-55) #18

Merged
merged 29 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6455abc
feat: deposit queue
madlabman Sep 18, 2023
036a1b4
test: extent batch deserialize case
madlabman Oct 2, 2023
e4ef894
chore: more descriptive revert messages for vetted keys
madlabman Oct 2, 2023
43adb1c
refator: get rid of prevVettedKeysCount
madlabman Oct 2, 2023
f4fcd56
refactor: for loop
madlabman Oct 2, 2023
a0d163a
docs: upd comment for _batchSize
madlabman Oct 2, 2023
7038f0e
chore: remove redundant type conversion
madlabman Oct 2, 2023
aa85cd3
refactor: rename _allKeysInBatchVetted
madlabman Oct 2, 2023
09d80b9
feat: check the queue continously
madlabman Oct 3, 2023
101f2cc
refactor: Queue
madlabman Oct 9, 2023
5eaa51f
Merge remote-tracking branch 'origin/main' into feat/deposit-queue
madlabman Oct 9, 2023
d700b31
refactor: make Queue contract a library
madlabman Oct 9, 2023
7aa3910
refactor: obtainDepositData and Batch stucture
madlabman Oct 10, 2023
9dc457a
refactor: interfaces instead of addresses
madlabman Oct 10, 2023
d3fd026
feat: emit StakingModuleTypeSet
madlabman Oct 10, 2023
917931a
chore: TODOs cleanup
madlabman Oct 10, 2023
29af784
docs: fix Batch comments
madlabman Oct 10, 2023
d2b149f
feat: notify user about missing queue walkthrough limit
madlabman Oct 10, 2023
5147960
feat: unvetKeys with unvettingFee
madlabman Oct 11, 2023
25c9fcd
refactor: _batchDepositableKeys
madlabman Oct 12, 2023
192a27c
fix: unvetKeys should accept calls from NO
madlabman Oct 12, 2023
24d4657
refactor: move events to base contract
madlabman Oct 12, 2023
770ddd0
fix: align events
madlabman Oct 25, 2023
457d153
fix: unbonded loop at getStakingModuleSummary
madlabman Oct 25, 2023
7c15053
fix: setNodeOperatorStakingLimit batch pointer
madlabman Oct 25, 2023
f7ae11d
fix: increment nonce
madlabman Oct 25, 2023
018417c
fix: add sanity check for keys at obtainDepositData
madlabman Oct 25, 2023
c29bccc
feat: add a view method for a queue with pagination
madlabman Oct 25, 2023
9fd9694
Merge branch 'main' into feat/deposit-queue
madlabman Oct 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
361 changes: 268 additions & 93 deletions src/CommunityStakingModule.sol
madlabman marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -53,4 +53,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
}
madlabman marked this conversation as resolved.
Show resolved Hide resolved
}

function isNil(bytes32 b) internal pure returns (bool) {
return b == bytes32(0);
}
}
56 changes: 56 additions & 0 deletions src/lib/QueueLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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 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(self.front != self.back, "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");
}
}
6 changes: 6 additions & 0 deletions test/CSMAddValidator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ contract CSMAddNodeOperator is
keys,
signatures
);

{
// Pretend to be a key validation oracle
csm.setNodeOperatorStakingLimit(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));
}
}
89 changes: 89 additions & 0 deletions test/QueueLib.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: 2023 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;

import "forge-std/Test.sol";
import "forge-std/console.sol";

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

contract QueueLibTest is Test {
bytes32 p0 = keccak256("0x00"); // 0x27489e20a0060b723a1748bdff5e44570ee9fae64141728105692eac6031e8a4
bytes32 p1 = keccak256("0x01"); // 0xe127292c8f7eb20e1ae830ed6055b6eb36e261836100610d12677231d0791f7f
bytes32 p2 = keccak256("0x02"); // 0xd3974deccfd8aa6b77f0fcc2c0014e6e0574d32e56c1d75717d2667b529cd073

bytes32 nil = bytes32(0);
bytes32 buf;

using QueueLib for QueueLib.Queue;
QueueLib.Queue q;

function test_enqueue() public {
assertEq(q.peek(), nil);

q.enqueue(p0);
q.enqueue(p1);

assertEq(q.peek(), p0);
assertEq(q.at(p0), p1);
}

function test_dequeue() public {
{
vm.expectRevert("Queue: empty");
q.dequeue();
}

q.enqueue(p0);
q.enqueue(p1);
q.enqueue(p2);

buf = q.dequeue();
assertEq(buf, p0);
assertEq(q.peek(), p1);

buf = q.dequeue();
assertEq(buf, p1);
assertEq(q.peek(), p2);

q.dequeue();
assertEq(q.peek(), nil);

{
vm.expectRevert("Queue: empty");
q.dequeue();
}
}

function test_remove() public {
q.enqueue(p0);
q.enqueue(p1);
q.enqueue(p2);
// [+*p0, p1, p2]

q.remove(p0, p1);
// [+*p0, p2]

q.dequeue();
// [+p0, *p2]
buf = q.dequeue();
// [p0, +*p2]
assertEq(buf, p2);

q.enqueue(p1);
// [p0, +p2, *p1]
assertEq(q.peek(), p1);

q.remove(p2, p1);
// [p0, +*p2]
assertEq(q.peek(), nil);
{
vm.expectRevert("Queue: empty");
q.dequeue();
}

q.remove(p0, p2);
// [+*p0]
assertEq(q.peek(), nil);
}
}
13 changes: 9 additions & 4 deletions test/integration/StakingRouter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,23 @@ contract StakingRouterIntegrationTest is Test, Utilities {
_treasuryFee: 500
});
uint256[] memory ids = stakingRouter.getStakingModuleIds();
(bytes memory keys, bytes memory signatures) = keysSignatures(1);
(bytes memory keys, bytes memory signatures) = keysSignatures(2);
address nodeOperator = address(2);
vm.deal(nodeOperator, 2 ether);
vm.deal(nodeOperator, 4 ether);
vm.prank(nodeOperator);
csm.addNodeOperatorETH{ value: 2 ether }(
csm.addNodeOperatorETH{ value: 4 ether }(
"test",
nodeOperator,
1,
2,
keys,
signatures
);

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

// It's impossible to process deposits if withdrawal requests amount is more than the buffered ether,
// so we need to make sure that the buffered ether is enough by submitting this tremendous amount.
address whale = nextAddress();
Expand Down