Skip to content

Commit b0c3278

Browse files
authored
Merge pull request #1752 from lidofinance/feat/staking-router-3.0-consolidation-group-requests
feat: wrap consolidation requests to struct
2 parents 69f47d7 + e70aec6 commit b0c3278

16 files changed

+727
-556
lines changed

contracts/0.8.25/consolidation/ConsolidationBus.sol

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@ import {
1111
import {IPredepositGuarantee} from "contracts/0.8.25/vaults/interfaces/IPredepositGuarantee.sol";
1212

1313
interface IConsolidationGateway {
14+
struct ConsolidationWitnessGroup {
15+
bytes[] sourcePubkeys;
16+
IPredepositGuarantee.ValidatorWitness targetWitness;
17+
}
18+
1419
function addConsolidationRequests(
15-
bytes[][] calldata sourcePubkeysGroups,
16-
IPredepositGuarantee.ValidatorWitness[] calldata targetWitnesses,
20+
ConsolidationWitnessGroup[] calldata groups,
1721
address refundRecipient
1822
) external payable;
1923
}
@@ -43,13 +47,6 @@ contract ConsolidationBus is AccessControlEnumerableUpgradeable {
4347
*/
4448
error AdminCannotBeZero();
4549

46-
/**
47-
* @notice Thrown when source groups and target arrays have different lengths
48-
* @param sourceGroupsLength Length of source pubkeys groups array
49-
* @param targetLength Length of target pubkeys array
50-
*/
51-
error ArraysLengthMismatch(uint256 sourceGroupsLength, uint256 targetLength);
52-
5350
/**
5451
* @notice Thrown when batch is empty
5552
*/
@@ -142,7 +139,7 @@ contract ConsolidationBus is AccessControlEnumerableUpgradeable {
142139
/**
143140
* @notice Emitted when consolidation requests are added
144141
* @param publisher Address of the publisher who added the requests
145-
* @param batchData Encoded batch data (abi.encode(sourcePubkeysGroups, targetPubkeys))
142+
* @param batchData Encoded batch data (abi.encode(groups))
146143
*/
147144
event RequestsAdded(address indexed publisher, bytes batchData);
148145

@@ -169,6 +166,11 @@ contract ConsolidationBus is AccessControlEnumerableUpgradeable {
169166
bytes32 public constant PUBLISH_ROLE = keccak256("PUBLISH_ROLE");
170167
bytes32 public constant REMOVE_ROLE = keccak256("REMOVE_ROLE");
171168

169+
struct ConsolidationGroup {
170+
bytes[] sourcePubkeys;
171+
bytes targetPubkey;
172+
}
173+
172174
struct BatchInfo {
173175
address publisher;
174176
uint64 addedAt;
@@ -310,34 +312,27 @@ contract ConsolidationBus is AccessControlEnumerableUpgradeable {
310312

311313
/**
312314
* @notice Adds grouped consolidation requests to the queue
313-
* @param sourcePubkeysGroups Array of groups, where each group is an array of 48-byte source validator public keys
314-
* consolidating to the corresponding target
315-
* @param targetPubkeys Array of 48-byte target validator public keys, one per group
315+
* @param groups Array of consolidation groups, where each group contains source pubkeys and a target pubkey
316316
* @dev The same batch can be submitted again after it has been executed.
317317
* @dev Reverts if:
318318
* - Caller does not have PUBLISH_ROLE
319-
* - Arrays have different lengths
320319
* - Batch is empty
321320
* - Any group is empty
322321
* - Total batch size exceeds limit
323322
* - Any source or target pubkey length is not 48 bytes
324323
* - Any source pubkey equals its corresponding target pubkey
325324
* - Batch already exists
326325
*/
327-
function addConsolidationRequests(
328-
bytes[][] calldata sourcePubkeysGroups,
329-
bytes[] calldata targetPubkeys
330-
) external onlyRole(PUBLISH_ROLE) {
331-
uint256 groupsCount = sourcePubkeysGroups.length;
326+
function addConsolidationRequests(ConsolidationGroup[] calldata groups) external onlyRole(PUBLISH_ROLE) {
327+
uint256 groupsCount = groups.length;
332328
if (groupsCount == 0) revert EmptyBatch();
333-
if (groupsCount != targetPubkeys.length) revert ArraysLengthMismatch(groupsCount, targetPubkeys.length);
334329

335330
uint256 maxGroups = _maxGroupsInBatch;
336331
if (groupsCount > maxGroups) revert TooManyGroups(groupsCount, maxGroups);
337332

338333
uint256 totalCount = 0;
339334
for (uint256 i = 0; i < groupsCount; ++i) {
340-
uint256 groupSize = sourcePubkeysGroups[i].length;
335+
uint256 groupSize = groups[i].sourcePubkeys.length;
341336
if (groupSize == 0) revert EmptyGroup(i);
342337
totalCount += groupSize;
343338
}
@@ -346,13 +341,13 @@ contract ConsolidationBus is AccessControlEnumerableUpgradeable {
346341
if (totalCount > limit) revert BatchTooLarge(totalCount, limit);
347342

348343
for (uint256 i = 0; i < groupsCount; ++i) {
349-
bytes calldata targetPubkey = targetPubkeys[i];
344+
bytes calldata targetPubkey = groups[i].targetPubkey;
350345
if (targetPubkey.length != PUBKEY_LENGTH) {
351346
revert InvalidTargetPubkeyLength(i, targetPubkey.length);
352347
}
353348

354349
bytes32 targetHash = keccak256(targetPubkey);
355-
bytes[] calldata group = sourcePubkeysGroups[i];
350+
bytes[] calldata group = groups[i].sourcePubkeys;
356351
for (uint256 j = 0; j < group.length; ++j) {
357352
bytes calldata sourcePubkey = group[j];
358353
if (sourcePubkey.length != PUBKEY_LENGTH) {
@@ -365,7 +360,7 @@ contract ConsolidationBus is AccessControlEnumerableUpgradeable {
365360
}
366361
}
367362

368-
bytes memory encodedBatch = abi.encode(sourcePubkeysGroups, targetPubkeys);
363+
bytes memory encodedBatch = abi.encode(groups);
369364

370365
bytes32 batchHash = keccak256(encodedBatch);
371366

@@ -382,22 +377,22 @@ contract ConsolidationBus is AccessControlEnumerableUpgradeable {
382377

383378
/**
384379
* @notice Executes a batch of grouped consolidation requests
385-
* @param sourcePubkeysGroups Array of groups of 48-byte source validator public keys
386-
* @param targetWitnesses Array of ValidatorWitness structs, one per group; each witness.pubkey is the target pubkey
380+
* @param groups Array of consolidation witness groups, each containing source pubkeys and a target validator witness
387381
* @dev Forwards the batch to ConsolidationGateway with msg.value as fee
388382
* @dev Reverts if:
389383
* - Batch was not added or was already executed/removed
390384
*/
391-
function executeConsolidation(
392-
bytes[][] calldata sourcePubkeysGroups,
393-
IPredepositGuarantee.ValidatorWitness[] calldata targetWitnesses
394-
) external payable {
395-
bytes[] memory targetPubkeys = new bytes[](targetWitnesses.length);
396-
for (uint256 i = 0; i < targetWitnesses.length; ++i) {
397-
targetPubkeys[i] = targetWitnesses[i].pubkey;
385+
function executeConsolidation(IConsolidationGateway.ConsolidationWitnessGroup[] calldata groups) external payable {
386+
// Reconstruct ConsolidationGroup[] to compute the batch hash that matches the publisher's submission
387+
ConsolidationGroup[] memory publisherGroups = new ConsolidationGroup[](groups.length);
388+
for (uint256 i = 0; i < groups.length; ++i) {
389+
publisherGroups[i] = ConsolidationGroup({
390+
sourcePubkeys: groups[i].sourcePubkeys,
391+
targetPubkey: groups[i].targetWitness.pubkey
392+
});
398393
}
399394

400-
bytes32 batchHash = keccak256(abi.encode(sourcePubkeysGroups, targetPubkeys));
395+
bytes32 batchHash = keccak256(abi.encode(publisherGroups));
401396

402397
BatchInfo memory batch = _pendingBatches[batchHash];
403398
if (batch.publisher == address(0)) revert BatchNotFound(batchHash);
@@ -407,11 +402,7 @@ contract ConsolidationBus is AccessControlEnumerableUpgradeable {
407402

408403
delete _pendingBatches[batchHash];
409404

410-
CONSOLIDATION_GATEWAY.addConsolidationRequests{value: msg.value}(
411-
sourcePubkeysGroups,
412-
targetWitnesses,
413-
msg.sender
414-
);
405+
CONSOLIDATION_GATEWAY.addConsolidationRequests{value: msg.value}(groups, msg.sender);
415406

416407
emit RequestsExecuted(batchHash, msg.value);
417408
}

contracts/0.8.25/consolidation/ConsolidationGateway.sol

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,6 @@ contract ConsolidationGateway is AccessControlEnumerable, PausableUntil, CLProof
6565
*/
6666
error ConsolidationRequestsLimitExceeded(uint256 requestsCount, uint256 remainingLimit);
6767

68-
/**
69-
* @notice Thrown when source groups and target arrays have different lengths
70-
*/
71-
error ArraysLengthMismatch(uint256 firstArrayLength, uint256 secondArrayLength);
72-
7368
/**
7469
* @notice Thrown when a source group has zero elements
7570
* @param groupIndex Index of the empty group
@@ -112,6 +107,11 @@ contract ConsolidationGateway is AccessControlEnumerable, PausableUntil, CLProof
112107

113108
uint256 internal constant COMPOUNDING_PREFIX = uint256(0x02) << 248;
114109

110+
struct ConsolidationWitnessGroup {
111+
bytes[] sourcePubkeys;
112+
IPredepositGuarantee.ValidatorWitness targetWitness;
113+
}
114+
115115
ILidoLocator internal immutable LOCATOR;
116116

117117
/// @dev Ensures the contract's ETH balance is unchanged.
@@ -173,10 +173,8 @@ contract ConsolidationGateway is AccessControlEnumerable, PausableUntil, CLProof
173173
/**
174174
* @dev Submits grouped Consolidation Requests to the Withdrawal Vault.
175175
* Each group represents multiple source validators consolidating into a single target.
176-
* @param sourcePubkeysGroups An array of groups, where each group is an array of 48-byte source public keys
177-
* consolidating to the corresponding target.
178-
* @param targetWitnesses An array of validator targetWitnesses (one per group), each containing a target pubkey
179-
* and a CL proof of withdrawal credentials.
176+
* @param groups An array of consolidation groups, where each group contains source public keys
177+
* and a target validator witness with a CL proof of withdrawal credentials.
180178
* @param refundRecipient The address that will receive any excess ETH sent for fees.
181179
*
182180
* @notice Reverts if:
@@ -185,21 +183,17 @@ contract ConsolidationGateway is AccessControlEnumerable, PausableUntil, CLProof
185183
* - There is not enough limit quota left in the current frame to process all requests.
186184
*/
187185
function addConsolidationRequests(
188-
bytes[][] calldata sourcePubkeysGroups,
189-
IPredepositGuarantee.ValidatorWitness[] calldata targetWitnesses,
186+
ConsolidationWitnessGroup[] calldata groups,
190187
address refundRecipient
191188
) external payable onlyRole(ADD_CONSOLIDATION_REQUEST_ROLE) preservesEthBalance whenResumed {
192189
if (msg.value == 0) revert ZeroArgument("msg.value");
193-
uint256 groupsCount = sourcePubkeysGroups.length;
194-
if (groupsCount == 0) revert ZeroArgument("sourcePubkeysGroups");
195-
if (groupsCount != targetWitnesses.length) {
196-
revert ArraysLengthMismatch(groupsCount, targetWitnesses.length);
197-
}
190+
uint256 groupsCount = groups.length;
191+
if (groupsCount == 0) revert ZeroArgument("groups");
198192

199193
// Count total individual requests across all groups
200194
uint256 requestsCount = 0;
201195
for (uint256 i = 0; i < groupsCount; ++i) {
202-
uint256 groupSize = sourcePubkeysGroups[i].length;
196+
uint256 groupSize = groups[i].sourcePubkeys.length;
203197
if (groupSize == 0) revert EmptyGroup(i);
204198
requestsCount += groupSize;
205199
}
@@ -209,7 +203,7 @@ contract ConsolidationGateway is AccessControlEnumerable, PausableUntil, CLProof
209203
(IWithdrawalVault withdrawalVault, bytes32 withdrawalCredentials) = _getWithdrawalVaultData();
210204

211205
for (uint256 i = 0; i < groupsCount; ++i) {
212-
_validatePubKeyWCProof(targetWitnesses[i], withdrawalCredentials);
206+
_validatePubKeyWCProof(groups[i].targetWitness, withdrawalCredentials);
213207
}
214208

215209
_consumeConsolidationRequestLimit(requestsCount);
@@ -219,9 +213,8 @@ contract ConsolidationGateway is AccessControlEnumerable, PausableUntil, CLProof
219213
uint256 refund = _checkFee(totalFee);
220214

221215
// Expand grouped requests into flat pairs for WithdrawalVault
222-
(bytes[] memory sourcePubkeys, bytes[] memory targetPubkeys) = prepareConsolidationPairs(
223-
sourcePubkeysGroups,
224-
targetWitnesses,
216+
(bytes[] memory sourcePubkeys, bytes[] memory targetPubkeys) = _prepareConsolidationPairs(
217+
groups,
225218
requestsCount
226219
);
227220
withdrawalVault.addConsolidationRequests{value: totalFee}(sourcePubkeys, targetPubkeys);
@@ -352,18 +345,17 @@ contract ConsolidationGateway is AccessControlEnumerable, PausableUntil, CLProof
352345
}
353346

354347
/// Flattens grouped source pubkeys and repeats each group's target pubkey.
355-
function prepareConsolidationPairs(
356-
bytes[][] calldata sourcePubkeysGroups,
357-
IPredepositGuarantee.ValidatorWitness[] calldata targetWitnesses,
348+
function _prepareConsolidationPairs(
349+
ConsolidationWitnessGroup[] calldata groups,
358350
uint256 totalCount
359351
) internal pure returns (bytes[] memory sourcePubkeys, bytes[] memory targetPubkeys) {
360352
sourcePubkeys = new bytes[](totalCount);
361353
targetPubkeys = new bytes[](totalCount);
362354

363355
uint256 idx = 0;
364-
for (uint256 i = 0; i < sourcePubkeysGroups.length; ++i) {
365-
bytes[] calldata group = sourcePubkeysGroups[i];
366-
bytes calldata target = targetWitnesses[i].pubkey;
356+
for (uint256 i = 0; i < groups.length; ++i) {
357+
bytes[] calldata group = groups[i].sourcePubkeys;
358+
bytes calldata target = groups[i].targetWitness.pubkey;
367359
for (uint256 j = 0; j < group.length; ++j) {
368360
sourcePubkeys[idx] = group[j];
369361
targetPubkeys[idx] = target;

0 commit comments

Comments
 (0)