Skip to content

Commit ce749be

Browse files
committed
fix: separate delegation and provision thaw request lists (TRST-H02)
1 parent 0c0d090 commit ce749be

File tree

7 files changed

+353
-108
lines changed

7 files changed

+353
-108
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,34 +134,46 @@ interface IHorizonStakingBase {
134134

135135
/**
136136
* @notice Gets a thaw request.
137+
* @param thawRequestType The type of thaw request.
137138
* @param thawRequestId The id of the thaw request.
138139
* @return The thaw request details.
139140
*/
140-
function getThawRequest(bytes32 thawRequestId) external view returns (IHorizonStakingTypes.ThawRequest memory);
141+
function getThawRequest(
142+
IHorizonStakingTypes.ThawRequestType thawRequestType,
143+
bytes32 thawRequestId
144+
) external view returns (IHorizonStakingTypes.ThawRequest memory);
141145

142146
/**
143147
* @notice Gets the metadata of a thaw request list.
144148
* Service provider and delegators each have their own thaw request list per provision.
145149
* Metadata includes the head and tail of the list, plus the total number of thaw requests.
150+
* @param thawRequestType The type of thaw request.
146151
* @param serviceProvider The address of the service provider.
147152
* @param verifier The address of the verifier.
148153
* @param owner The owner of the thaw requests. Use either the service provider or delegator address.
149154
* @return The thaw requests list metadata.
150155
*/
151156
function getThawRequestList(
157+
IHorizonStakingTypes.ThawRequestType thawRequestType,
152158
address serviceProvider,
153159
address verifier,
154160
address owner
155161
) external view returns (LinkedList.List memory);
156162

157163
/**
158164
* @notice Gets the amount of thawed tokens for a given provision.
165+
* @param thawRequestType The type of thaw request.
159166
* @param serviceProvider The address of the service provider.
160167
* @param verifier The address of the verifier.
161168
* @param owner The owner of the thaw requests. Use either the service provider or delegator address.
162169
* @return The amount of thawed tokens.
163170
*/
164-
function getThawedTokens(address serviceProvider, address verifier, address owner) external view returns (uint256);
171+
function getThawedTokens(
172+
IHorizonStakingTypes.ThawRequestType thawRequestType,
173+
address serviceProvider,
174+
address verifier,
175+
address owner
176+
) external view returns (uint256);
165177

166178
/**
167179
* @notice Gets the maximum allowed thawing period for a provision.

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,16 @@ interface IHorizonStakingTypes {
131131
uint256 __DEPRECATED_tokensLockedUntil;
132132
}
133133

134+
/**
135+
* @dev Enum to specify the type of thaw request.
136+
* @param Provision Represents a thaw request for a provision.
137+
* @param Delegation Represents a thaw request for a delegation.
138+
*/
139+
enum ThawRequestType {
140+
Provision,
141+
Delegation
142+
}
143+
134144
/**
135145
* @notice Details of a stake thawing operation.
136146
* @dev ThawRequests are stored in linked lists by service provider/delegator,
@@ -146,4 +156,26 @@ interface IHorizonStakingTypes {
146156
// Used to invalidate unfulfilled thaw requests
147157
uint256 thawingNonce;
148158
}
159+
160+
/**
161+
* @notice Parameters to fulfill thaw requests.
162+
* @param requestType The type of thaw request (Provision or Delegation)
163+
* @param serviceProvider The address of the service provider
164+
* @param verifier The address of the verifier
165+
* @param owner The address of the owner of the thaw request
166+
* @param tokensThawing The current amount of tokens already thawing
167+
* @param sharesThawing The current amount of shares already thawing
168+
* @param nThawRequests The number of thaw requests to fulfill. If set to 0, all thaw requests are fulfilled.
169+
* @param thawingNonce The current valid thawing nonce. Any thaw request with a different nonce is invalid and should be ignored.
170+
*/
171+
struct FulfillThawRequestsParams {
172+
ThawRequestType requestType;
173+
address serviceProvider;
174+
address verifier;
175+
address owner;
176+
uint256 tokensThawing;
177+
uint256 sharesThawing;
178+
uint256 nThawRequests;
179+
uint256 thawingNonce;
180+
}
149181
}

packages/horizon/contracts/staking/HorizonStaking.sol

Lines changed: 110 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
741741
prov.tokensThawing = prov.tokensThawing + _tokens;
742742

743743
bytes32 thawRequestId = _createThawRequest(
744+
ThawRequestType.Provision,
744745
_serviceProvider,
745746
_verifier,
746747
_serviceProvider,
@@ -765,15 +766,18 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
765766
uint256 tokensThawed_ = 0;
766767
uint256 sharesThawing = prov.sharesThawing;
767768
uint256 tokensThawing = prov.tokensThawing;
768-
(tokensThawed_, tokensThawing, sharesThawing) = _fulfillThawRequests(
769-
_serviceProvider,
770-
_verifier,
771-
_serviceProvider,
772-
tokensThawing,
773-
sharesThawing,
774-
_nThawRequests,
775-
prov.thawingNonce
776-
);
769+
770+
FulfillThawRequestsParams memory params = FulfillThawRequestsParams({
771+
requestType: ThawRequestType.Provision,
772+
serviceProvider: _serviceProvider,
773+
verifier: _verifier,
774+
owner: _serviceProvider,
775+
tokensThawing: tokensThawing,
776+
sharesThawing: sharesThawing,
777+
nThawRequests: _nThawRequests,
778+
thawingNonce: prov.thawingNonce
779+
});
780+
(tokensThawed_, tokensThawing, sharesThawing) = _fulfillThawRequests(params);
777781

778782
prov.tokens = prov.tokens - tokensThawed_;
779783
prov.sharesThawing = sharesThawing;
@@ -860,6 +864,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
860864
delegation.shares = delegation.shares - _shares;
861865

862866
bytes32 thawRequestId = _createThawRequest(
867+
ThawRequestType.Delegation,
863868
_serviceProvider,
864869
_verifier,
865870
_beneficiary,
@@ -894,15 +899,18 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
894899
uint256 tokensThawed = 0;
895900
uint256 sharesThawing = pool.sharesThawing;
896901
uint256 tokensThawing = pool.tokensThawing;
897-
(tokensThawed, tokensThawing, sharesThawing) = _fulfillThawRequests(
898-
_serviceProvider,
899-
_verifier,
900-
msg.sender,
901-
tokensThawing,
902-
sharesThawing,
903-
_nThawRequests,
904-
pool.thawingNonce
905-
);
902+
903+
FulfillThawRequestsParams memory params = FulfillThawRequestsParams({
904+
requestType: ThawRequestType.Delegation,
905+
serviceProvider: _serviceProvider,
906+
verifier: _verifier,
907+
owner: msg.sender,
908+
tokensThawing: tokensThawing,
909+
sharesThawing: sharesThawing,
910+
nThawRequests: _nThawRequests,
911+
thawingNonce: pool.thawingNonce
912+
});
913+
(tokensThawed, tokensThawing, sharesThawing) = _fulfillThawRequests(params);
906914

907915
// The next subtraction should never revert becase: pool.tokens >= pool.tokensThawing and pool.tokensThawing >= tokensThawed
908916
// In the event the pool gets completely slashed tokensThawed will fulfil to 0.
@@ -936,25 +944,30 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
936944
* @return The ID of the thaw request
937945
*/
938946
function _createThawRequest(
947+
ThawRequestType _requestType,
939948
address _serviceProvider,
940949
address _verifier,
941950
address _owner,
942951
uint256 _shares,
943952
uint64 _thawingUntil,
944953
uint256 _thawingNonce
945954
) private returns (bytes32) {
946-
LinkedList.List storage thawRequestList = _thawRequestLists[_serviceProvider][_verifier][_owner];
955+
LinkedList.List storage thawRequestList = _getThawRequestList(
956+
_requestType,
957+
_serviceProvider,
958+
_verifier,
959+
_owner
960+
);
947961
require(thawRequestList.count < MAX_THAW_REQUESTS, HorizonStakingTooManyThawRequests());
948962

949963
bytes32 thawRequestId = keccak256(abi.encodePacked(_serviceProvider, _verifier, _owner, thawRequestList.nonce));
950-
_thawRequests[thawRequestId] = ThawRequest({
951-
shares: _shares,
952-
thawingUntil: _thawingUntil,
953-
next: bytes32(0),
954-
thawingNonce: _thawingNonce
955-
});
964+
ThawRequest storage thawRequest = _getThawRequest(_requestType, thawRequestId);
965+
thawRequest.shares = _shares;
966+
thawRequest.thawingUntil = _thawingUntil;
967+
thawRequest.next = bytes32(0);
968+
thawRequest.thawingNonce = _thawingNonce;
956969

957-
if (thawRequestList.count != 0) _thawRequests[thawRequestList.tail].next = thawRequestId;
970+
if (thawRequestList.count != 0) _getThawRequest(_requestType, thawRequestList.tail).next = thawRequestId;
958971
thawRequestList.addTail(thawRequestId);
959972

960973
emit ThawRequestCreated(_serviceProvider, _verifier, _owner, _shares, _thawingUntil, thawRequestId);
@@ -964,41 +977,49 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
964977
/**
965978
* @notice Traverses a thaw request list and fulfills expired thaw requests.
966979
* @dev Emits a {ThawRequestsFulfilled} event and a {ThawRequestFulfilled} event for each thaw request fulfilled.
967-
* @param _serviceProvider The address of the service provider
968-
* @param _verifier The address of the verifier
969-
* @param _owner The address of the owner of the thaw request
970-
* @param _tokensThawing The current amount of tokens already thawing
971-
* @param _sharesThawing The current amount of shares already thawing
972-
* @param _nThawRequests The number of thaw requests to fulfill. If set to 0, all thaw requests are fulfilled.
973-
* @param _thawingNonce The current valid thawing nonce. Any thaw request with a different nonce is invalid and should be ignored.
980+
* @param params The parameters for fulfilling thaw requests
974981
* @return The amount of thawed tokens
975982
* @return The amount of tokens still thawing
976983
* @return The amount of shares still thawing
977984
*/
978-
function _fulfillThawRequests(
979-
address _serviceProvider,
980-
address _verifier,
981-
address _owner,
982-
uint256 _tokensThawing,
983-
uint256 _sharesThawing,
984-
uint256 _nThawRequests,
985-
uint256 _thawingNonce
986-
) private returns (uint256, uint256, uint256) {
987-
LinkedList.List storage thawRequestList = _thawRequestLists[_serviceProvider][_verifier][_owner];
985+
function _fulfillThawRequests(FulfillThawRequestsParams memory params) private returns (uint256, uint256, uint256) {
986+
LinkedList.List storage thawRequestList = _getThawRequestList(
987+
params.requestType,
988+
params.serviceProvider,
989+
params.verifier,
990+
params.owner
991+
);
988992
require(thawRequestList.count > 0, HorizonStakingNothingThawing());
989993

990-
uint256 tokensThawed = 0;
994+
function(bytes32) view returns (bytes32) getNextItem = _getNextThawRequest(params.requestType);
995+
function(bytes32) deleteItem = _getDeleteThawRequest(params.requestType);
996+
bytes memory acc = abi.encode(
997+
params.requestType,
998+
uint256(0),
999+
params.tokensThawing,
1000+
params.sharesThawing,
1001+
params.thawingNonce
1002+
);
9911003
(uint256 thawRequestsFulfilled, bytes memory data) = thawRequestList.traverse(
992-
_getNextThawRequest,
1004+
getNextItem,
9931005
_fulfillThawRequest,
994-
_deleteThawRequest,
995-
abi.encode(tokensThawed, _tokensThawing, _sharesThawing, _thawingNonce),
996-
_nThawRequests
1006+
deleteItem,
1007+
acc,
1008+
params.nThawRequests
9971009
);
9981010

999-
(tokensThawed, _tokensThawing, _sharesThawing) = abi.decode(data, (uint256, uint256, uint256));
1000-
emit ThawRequestsFulfilled(_serviceProvider, _verifier, _owner, thawRequestsFulfilled, tokensThawed);
1001-
return (tokensThawed, _tokensThawing, _sharesThawing);
1011+
(, uint256 tokensThawed, uint256 tokensThawing, uint256 sharesThawing) = abi.decode(
1012+
data,
1013+
(ThawRequestType, uint256, uint256, uint256)
1014+
);
1015+
emit ThawRequestsFulfilled(
1016+
params.serviceProvider,
1017+
params.verifier,
1018+
params.owner,
1019+
thawRequestsFulfilled,
1020+
tokensThawed
1021+
);
1022+
return (tokensThawed, tokensThawing, sharesThawing);
10021023
}
10031024

10041025
/**
@@ -1013,19 +1034,22 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
10131034
* @return The updated accumulator data
10141035
*/
10151036
function _fulfillThawRequest(bytes32 _thawRequestId, bytes memory _acc) private returns (bool, bytes memory) {
1016-
ThawRequest storage thawRequest = _thawRequests[_thawRequestId];
1037+
// decode
1038+
(
1039+
ThawRequestType requestType,
1040+
uint256 tokensThawed,
1041+
uint256 tokensThawing,
1042+
uint256 sharesThawing,
1043+
uint256 thawingNonce
1044+
) = abi.decode(_acc, (ThawRequestType, uint256, uint256, uint256, uint256));
1045+
1046+
ThawRequest storage thawRequest = _getThawRequest(requestType, _thawRequestId);
10171047

10181048
// early exit
10191049
if (thawRequest.thawingUntil > block.timestamp) {
10201050
return (true, LinkedList.NULL_BYTES);
10211051
}
10221052

1023-
// decode
1024-
(uint256 tokensThawed, uint256 tokensThawing, uint256 sharesThawing, uint256 thawingNonce) = abi.decode(
1025-
_acc,
1026-
(uint256, uint256, uint256, uint256)
1027-
);
1028-
10291053
// process - only fulfill thaw requests for the current valid nonce
10301054
uint256 tokens = 0;
10311055
bool validThawRequest = thawRequest.thawingNonce == thawingNonce;
@@ -1044,17 +1068,39 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
10441068
);
10451069

10461070
// encode
1047-
_acc = abi.encode(tokensThawed, tokensThawing, sharesThawing, thawingNonce);
1071+
_acc = abi.encode(requestType, tokensThawed, tokensThawing, sharesThawing, thawingNonce);
10481072
return (false, _acc);
10491073
}
10501074

10511075
/**
1052-
* @notice Deletes a ThawRequest.
1053-
* @dev This function is used as a callback in the thaw requests linked list traversal.
1054-
* @param _thawRequestId The ID of the thaw request to delete
1076+
* @notice Determines the correct callback function for `deleteItem` based on the request type.
1077+
* @param _requestType The type of thaw request (Provision or Delegation).
1078+
* @return A function pointer to the appropriate `deleteItem` callback.
1079+
*/
1080+
function _getDeleteThawRequest(ThawRequestType _requestType) private pure returns (function(bytes32)) {
1081+
if (_requestType == ThawRequestType.Provision) {
1082+
return _deleteProvisionThawRequest;
1083+
} else if (_requestType == ThawRequestType.Delegation) {
1084+
return _deleteDelegationThawRequest;
1085+
} else {
1086+
revert("Unknown ThawRequestType");
1087+
}
1088+
}
1089+
1090+
/**
1091+
* @notice Deletes a thaw request for a provision.
1092+
* @param _thawRequestId The ID of the thaw request to delete.
1093+
*/
1094+
function _deleteProvisionThawRequest(bytes32 _thawRequestId) private {
1095+
delete _provisionThawRequests[_thawRequestId];
1096+
}
1097+
1098+
/**
1099+
* @notice Deletes a thaw request for a delegation.
1100+
* @param _thawRequestId The ID of the thaw request to delete.
10551101
*/
1056-
function _deleteThawRequest(bytes32 _thawRequestId) private {
1057-
delete _thawRequests[_thawRequestId];
1102+
function _deleteDelegationThawRequest(bytes32 _thawRequestId) private {
1103+
delete _delegationThawRequests[_thawRequestId];
10581104
}
10591105

10601106
/**

0 commit comments

Comments
 (0)