Skip to content

Commit d02f410

Browse files
committed
fix: separate delegation and delegationWithBeneficiary thaw requests (TRST-H03)
1 parent ce749be commit d02f410

File tree

9 files changed

+327
-191
lines changed

9 files changed

+327
-191
lines changed

packages/horizon/contracts/interfaces/internal/IHorizonStakingBase.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ interface IHorizonStakingBase {
2424
*/
2525
event StakeDeposited(address indexed serviceProvider, uint256 tokens);
2626

27+
/**
28+
* @notice Thrown when using an invalid thaw request type.
29+
*/
30+
error HorizonStakingInvalidThawRequestType();
31+
2732
/**
2833
* @notice Gets the details of a service provider.
2934
* @param serviceProvider The address of the service provider.

packages/horizon/contracts/interfaces/internal/IHorizonStakingMain.sol

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ interface IHorizonStakingMain {
747747
* @param beneficiary The address where the tokens will be withdrawn after thawing
748748
* @return The ID of the thaw request
749749
*/
750-
function undelegate(
750+
function undelegateWithBeneficiary(
751751
address serviceProvider,
752752
address verifier,
753753
uint256 shares,
@@ -772,6 +772,28 @@ interface IHorizonStakingMain {
772772
*/
773773
function withdrawDelegated(address serviceProvider, address verifier, uint256 nThawRequests) external;
774774

775+
/**
776+
* @notice Withdraw undelegated with beneficiary tokens from a provision after thawing.
777+
* @dev The parameter `nThawRequests` can be set to a non zero value to fulfill a specific number of thaw
778+
* requests in the event that fulfilling all of them results in a gas limit error.
779+
* @dev If the delegation pool was completely slashed before withdrawing, calling this function will fulfill
780+
* the thaw requests with an amount equal to zero.
781+
*
782+
* Requirements:
783+
* - Must have previously initiated a thaw request using {undelegateWithBeneficiary}.
784+
*
785+
* Emits {ThawRequestFulfilled}, {ThawRequestsFulfilled} and {DelegatedTokensWithdrawn} events.
786+
*
787+
* @param serviceProvider The service provider address
788+
* @param verifier The verifier address
789+
* @param nThawRequests The number of thaw requests to fulfill. Set to 0 to fulfill all thaw requests.
790+
*/
791+
function withdrawDelegatedWithBeneficiary(
792+
address serviceProvider,
793+
address verifier,
794+
uint256 nThawRequests
795+
) external;
796+
775797
/**
776798
* @notice Re-delegate undelegated tokens from a provision after thawing to a `newServiceProvider` and `newVerifier`.
777799
* @dev The parameter `nThawRequests` can be set to a non zero value to fulfill a specific number of thaw

packages/horizon/contracts/interfaces/internal/IHorizonStakingTypes.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ interface IHorizonStakingTypes {
138138
*/
139139
enum ThawRequestType {
140140
Provision,
141-
Delegation
141+
Delegation,
142+
DelegationWithBeneficiary
142143
}
143144

144145
/**

packages/horizon/contracts/staking/HorizonStaking.sol

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -297,20 +297,20 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
297297
address verifier,
298298
uint256 shares
299299
) external override notPaused returns (bytes32) {
300-
return _undelegate(serviceProvider, verifier, shares, msg.sender);
300+
return _undelegate(ThawRequestType.Delegation, serviceProvider, verifier, shares, msg.sender);
301301
}
302302

303303
/**
304304
* @notice See {IHorizonStakingMain-undelegate}.
305305
*/
306-
function undelegate(
306+
function undelegateWithBeneficiary(
307307
address serviceProvider,
308308
address verifier,
309309
uint256 shares,
310310
address beneficiary
311311
) external override notPaused returns (bytes32) {
312312
require(beneficiary != address(0), HorizonStakingInvalidBeneficiaryZeroAddress());
313-
return _undelegate(serviceProvider, verifier, shares, beneficiary);
313+
return _undelegate(ThawRequestType.DelegationWithBeneficiary, serviceProvider, verifier, shares, beneficiary);
314314
}
315315

316316
/**
@@ -321,7 +321,34 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
321321
address verifier,
322322
uint256 nThawRequests
323323
) external override notPaused {
324-
_withdrawDelegated(serviceProvider, verifier, address(0), address(0), 0, nThawRequests);
324+
_withdrawDelegated(
325+
ThawRequestType.Delegation,
326+
serviceProvider,
327+
verifier,
328+
address(0),
329+
address(0),
330+
0,
331+
nThawRequests
332+
);
333+
}
334+
335+
/**
336+
* @notice See {IHorizonStakingMain-withdrawDelegatedWithBeneficiary}.
337+
*/
338+
function withdrawDelegatedWithBeneficiary(
339+
address serviceProvider,
340+
address verifier,
341+
uint256 nThawRequests
342+
) external override notPaused {
343+
_withdrawDelegated(
344+
ThawRequestType.DelegationWithBeneficiary,
345+
serviceProvider,
346+
verifier,
347+
address(0),
348+
address(0),
349+
0,
350+
nThawRequests
351+
);
325352
}
326353

327354
/**
@@ -338,6 +365,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
338365
require(newServiceProvider != address(0), HorizonStakingInvalidServiceProviderZeroAddress());
339366
require(newVerifier != address(0), HorizonStakingInvalidVerifierZeroAddress());
340367
_withdrawDelegated(
368+
ThawRequestType.Delegation,
341369
oldServiceProvider,
342370
oldVerifier,
343371
newServiceProvider,
@@ -374,14 +402,15 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
374402
* @notice See {IHorizonStakingMain-undelegate}.
375403
*/
376404
function undelegate(address serviceProvider, uint256 shares) external override notPaused {
377-
_undelegate(serviceProvider, SUBGRAPH_DATA_SERVICE_ADDRESS, shares, msg.sender);
405+
_undelegate(ThawRequestType.Delegation, serviceProvider, SUBGRAPH_DATA_SERVICE_ADDRESS, shares, msg.sender);
378406
}
379407

380408
/**
381409
* @notice See {IHorizonStakingMain-withdrawDelegated}.
382410
*/
383411
function withdrawDelegated(address serviceProvider, address newServiceProvider) external override notPaused {
384412
_withdrawDelegated(
413+
ThawRequestType.Delegation,
385414
serviceProvider,
386415
SUBGRAPH_DATA_SERVICE_ADDRESS,
387416
newServiceProvider,
@@ -837,6 +866,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
837866
* that were not thawing will be preserved.
838867
*/
839868
function _undelegate(
869+
ThawRequestType _requestType,
840870
address _serviceProvider,
841871
address _verifier,
842872
uint256 _shares,
@@ -864,7 +894,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
864894
delegation.shares = delegation.shares - _shares;
865895

866896
bytes32 thawRequestId = _createThawRequest(
867-
ThawRequestType.Delegation,
897+
_requestType,
868898
_serviceProvider,
869899
_verifier,
870900
_beneficiary,
@@ -881,6 +911,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
881911
* @notice See {IHorizonStakingMain-withdrawDelegated}.
882912
*/
883913
function _withdrawDelegated(
914+
ThawRequestType _requestType,
884915
address _serviceProvider,
885916
address _verifier,
886917
address _newServiceProvider,
@@ -901,7 +932,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
901932
uint256 tokensThawing = pool.tokensThawing;
902933

903934
FulfillThawRequestsParams memory params = FulfillThawRequestsParams({
904-
requestType: ThawRequestType.Delegation,
935+
requestType: _requestType,
905936
serviceProvider: _serviceProvider,
906937
verifier: _verifier,
907938
owner: msg.sender,
@@ -1082,8 +1113,10 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
10821113
return _deleteProvisionThawRequest;
10831114
} else if (_requestType == ThawRequestType.Delegation) {
10841115
return _deleteDelegationThawRequest;
1116+
} else if (_requestType == ThawRequestType.DelegationWithBeneficiary) {
1117+
return _deleteDelegationWithBeneficiaryThawRequest;
10851118
} else {
1086-
revert("Unknown ThawRequestType");
1119+
revert HorizonStakingInvalidThawRequestType();
10871120
}
10881121
}
10891122

@@ -1103,6 +1136,14 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
11031136
delete _delegationThawRequests[_thawRequestId];
11041137
}
11051138

1139+
/**
1140+
* @notice Deletes a thaw request for a delegation with a beneficiary.
1141+
* @param _thawRequestId The ID of the thaw request to delete.
1142+
*/
1143+
function _deleteDelegationWithBeneficiaryThawRequest(bytes32 _thawRequestId) private {
1144+
delete _delegationWithBeneficiaryThawRequests[_thawRequestId];
1145+
}
1146+
11061147
/**
11071148
* @notice See {IHorizonStakingMain-setOperator}.
11081149
* @dev Note that this function handles the special case where the verifier is the subgraph data service,

packages/horizon/contracts/staking/HorizonStakingBase.sol

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,10 @@ abstract contract HorizonStakingBase is
305305
return _getNextProvisionThawRequest;
306306
} else if (_requestType == ThawRequestType.Delegation) {
307307
return _getNextDelegationThawRequest;
308+
} else if (_requestType == ThawRequestType.DelegationWithBeneficiary) {
309+
return _getNextDelegationWithBeneficiaryThawRequest;
308310
} else {
309-
revert("Unknown ThawRequestType");
311+
revert HorizonStakingInvalidThawRequestType();
310312
}
311313
}
312314

@@ -328,6 +330,15 @@ abstract contract HorizonStakingBase is
328330
return _delegationThawRequests[_thawRequestId].next;
329331
}
330332

333+
/**
334+
* @notice Retrieves the next thaw request for a delegation with a beneficiary.
335+
* @param _thawRequestId The ID of the current thaw request.
336+
* @return The ID of the next thaw request in the list.
337+
*/
338+
function _getNextDelegationWithBeneficiaryThawRequest(bytes32 _thawRequestId) internal view returns (bytes32) {
339+
return _delegationWithBeneficiaryThawRequests[_thawRequestId].next;
340+
}
341+
331342
/**
332343
* @notice Retrieves the thaw request list for the given request type.
333344
* @dev Uses the `ThawRequestType` to determine which mapping to access.
@@ -348,8 +359,10 @@ abstract contract HorizonStakingBase is
348359
return _provisionThawRequestLists[_serviceProvider][_verifier][_owner];
349360
} else if (_requestType == ThawRequestType.Delegation) {
350361
return _delegationThawRequestLists[_serviceProvider][_verifier][_owner];
362+
} else if (_requestType == ThawRequestType.DelegationWithBeneficiary) {
363+
return _delegationWithBeneficiaryThawRequestLists[_serviceProvider][_verifier][_owner];
351364
} else {
352-
revert("Unknown ThawRequestType");
365+
revert HorizonStakingInvalidThawRequestType();
353366
}
354367
}
355368

@@ -369,6 +382,8 @@ abstract contract HorizonStakingBase is
369382
return _provisionThawRequests[_thawRequestId];
370383
} else if (_requestType == ThawRequestType.Delegation) {
371384
return _delegationThawRequests[_thawRequestId];
385+
} else if (_requestType == ThawRequestType.DelegationWithBeneficiary) {
386+
return _delegationWithBeneficiaryThawRequests[_thawRequestId];
372387
} else {
373388
revert("Unknown ThawRequestType");
374389
}

packages/horizon/contracts/staking/HorizonStakingStorage.sol

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,23 +148,33 @@ abstract contract HorizonStakingV1Storage {
148148
internal _delegationFeeCut;
149149

150150
/// @dev Thaw requests
151-
/// Details for each thawing operation in the staking contract (for both service providers and delegators).
151+
/// Details for each thawing operation in the staking contract for both service providers.
152152
mapping(bytes32 thawRequestId => IHorizonStakingTypes.ThawRequest thawRequest) internal _provisionThawRequests;
153153

154154
/// @dev Thaw request lists
155-
/// Metadata defining linked lists of thaw requests for each service provider or delegator (owner)
155+
/// Metadata defining linked lists of thaw requests for each service provider (owner).
156156
mapping(address serviceProvider => mapping(address verifier => mapping(address owner => LinkedList.List list)))
157157
internal _provisionThawRequestLists;
158158

159159
/// @dev Thaw requests
160-
/// Details for each thawing operation in the staking contract (for both service providers and delegators).
160+
/// Details for each thawing operation in the staking contract for delegators.
161161
mapping(bytes32 thawRequestId => IHorizonStakingTypes.ThawRequest thawRequest) internal _delegationThawRequests;
162162

163163
/// @dev Thaw request lists
164-
/// Metadata defining linked lists of thaw requests for each service provider or delegator (owner)
164+
/// Metadata defining linked lists of thaw requests for each delegator (owner).
165165
mapping(address serviceProvider => mapping(address verifier => mapping(address owner => LinkedList.List list)))
166166
internal _delegationThawRequestLists;
167167

168+
/// @dev Thaw requests
169+
/// Details for each thawing operation in the staking contract for both delegators undelegating to a beneficiary.
170+
mapping(bytes32 thawRequestId => IHorizonStakingTypes.ThawRequest thawRequest)
171+
internal _delegationWithBeneficiaryThawRequests;
172+
173+
/// @dev Thaw request lists
174+
/// Metadata defining linked lists of thaw requests for each delegator (owner) undelegating to a beneficiary.
175+
mapping(address serviceProvider => mapping(address verifier => mapping(address owner => LinkedList.List list)))
176+
internal _delegationWithBeneficiaryThawRequestLists;
177+
168178
/// @dev Operator allow list
169179
/// Used for all verifiers except the subgraph data service.
170180
mapping(address serviceProvider => mapping(address verifier => mapping(address operator => bool authorized)))

0 commit comments

Comments
 (0)