Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ce749be
fix: separate delegation and provision thaw request lists (TRST-H02)
Maikol Nov 28, 2024
d02f410
fix: separate delegation and delegationWithBeneficiary thaw requests …
Maikol Nov 29, 2024
167055a
fix: round down tokens thawing when slashing (TRST-H04)
Maikol Dec 2, 2024
abe3321
fix: add legacy slasher for transition period (TRST-H06)
Maikol Dec 3, 2024
f254897
fix: add missing legacy withdraw delegated (TRST-H07)
Maikol Dec 4, 2024
91cda56
fix: added minimum delegation for provisions (TRST-M03)
Maikol Dec 4, 2024
07ef418
fix: operator check in closeAllocation (TRST-M12)
Maikol Dec 4, 2024
26e4dc7
fix: getThawedTokens calculation (TRST-L03)
Maikol Dec 4, 2024
c9f8a2f
fix: documentation on unstake (TRST-M11)
Maikol Dec 4, 2024
9271b99
fix: remove unused value from event (TRST-R01)
Maikol Dec 4, 2024
151e63a
fix: round thawing shares up (TRST-R07)
Maikol Dec 5, 2024
6e5a295
fix: check shares are not zero when creating a thaw request (TRST-R14)
Maikol Dec 5, 2024
d9c6190
fix: move legacyWithdrawDelegated to withdrawDelegated (TRST-H07)
Maikol Dec 6, 2024
161f8a2
fix: added comment for stack too deep solution
Maikol Dec 6, 2024
43bc72b
fix: added comment to explain minimum delegation (TRST-M03)
Maikol Dec 6, 2024
c59c186
fix: new event for minimum delegation not met (TRST-M03)
Maikol Dec 6, 2024
d1c5cc7
fix: add a new mapping instead of splitting thaw requests (TRST-H02)
Maikol Dec 6, 2024
6e00d17
docs: fix documentation errors (TRST-R09)
tmigone Dec 9, 2024
d6d376c
fix: legacy slashing underflow (TRST-H08)
Maikol Dec 12, 2024
7d90ad2
fix: underflow in getIdleStake (TRST-L14)
Maikol Dec 12, 2024
57aea44
fix: added thaw request type to thaw request fulfilled event (TRST-R15)
Maikol Dec 12, 2024
bbd23f5
fix: add minimum tokens amount for undelegate with beneficiary (TRST-…
Maikol Dec 13, 2024
399b7a9
fix: natspec for new undelegate error
Maikol Dec 13, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -134,34 +134,46 @@ interface IHorizonStakingBase {

/**
* @notice Gets a thaw request.
* @param thawRequestType The type of thaw request.
* @param thawRequestId The id of the thaw request.
* @return The thaw request details.
*/
function getThawRequest(bytes32 thawRequestId) external view returns (IHorizonStakingTypes.ThawRequest memory);
function getThawRequest(
IHorizonStakingTypes.ThawRequestType thawRequestType,
bytes32 thawRequestId
) external view returns (IHorizonStakingTypes.ThawRequest memory);

/**
* @notice Gets the metadata of a thaw request list.
* Service provider and delegators each have their own thaw request list per provision.
* Metadata includes the head and tail of the list, plus the total number of thaw requests.
* @param thawRequestType The type of thaw request.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
* @param owner The owner of the thaw requests. Use either the service provider or delegator address.
* @return The thaw requests list metadata.
*/
function getThawRequestList(
IHorizonStakingTypes.ThawRequestType thawRequestType,
address serviceProvider,
address verifier,
address owner
) external view returns (LinkedList.List memory);

/**
* @notice Gets the amount of thawed tokens for a given provision.
* @param thawRequestType The type of thaw request.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
* @param owner The owner of the thaw requests. Use either the service provider or delegator address.
* @return The amount of thawed tokens.
*/
function getThawedTokens(address serviceProvider, address verifier, address owner) external view returns (uint256);
function getThawedTokens(
IHorizonStakingTypes.ThawRequestType thawRequestType,
address serviceProvider,
address verifier,
address owner
) external view returns (uint256);

/**
* @notice Gets the maximum allowed thawing period for a provision.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ interface IHorizonStakingTypes {
uint256 __DEPRECATED_tokensLockedUntil;
}

/**
* @dev Enum to specify the type of thaw request.
* @param Provision Represents a thaw request for a provision.
* @param Delegation Represents a thaw request for a delegation.
*/
enum ThawRequestType {
Provision,
Delegation
}

/**
* @notice Details of a stake thawing operation.
* @dev ThawRequests are stored in linked lists by service provider/delegator,
Expand All @@ -146,4 +156,26 @@ interface IHorizonStakingTypes {
// Used to invalidate unfulfilled thaw requests
uint256 thawingNonce;
}

/**
* @notice Parameters to fulfill thaw requests.
* @param requestType The type of thaw request (Provision or Delegation)
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param owner The address of the owner of the thaw request
* @param tokensThawing The current amount of tokens already thawing
* @param sharesThawing The current amount of shares already thawing
* @param nThawRequests The number of thaw requests to fulfill. If set to 0, all thaw requests are fulfilled.
* @param thawingNonce The current valid thawing nonce. Any thaw request with a different nonce is invalid and should be ignored.
*/
struct FulfillThawRequestsParams {
ThawRequestType requestType;
address serviceProvider;
address verifier;
address owner;
uint256 tokensThawing;
uint256 sharesThawing;
uint256 nThawRequests;
uint256 thawingNonce;
}
}
174 changes: 110 additions & 64 deletions packages/horizon/contracts/staking/HorizonStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
prov.tokensThawing = prov.tokensThawing + _tokens;

bytes32 thawRequestId = _createThawRequest(
ThawRequestType.Provision,
_serviceProvider,
_verifier,
_serviceProvider,
Expand All @@ -765,15 +766,18 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
uint256 tokensThawed_ = 0;
uint256 sharesThawing = prov.sharesThawing;
uint256 tokensThawing = prov.tokensThawing;
(tokensThawed_, tokensThawing, sharesThawing) = _fulfillThawRequests(
_serviceProvider,
_verifier,
_serviceProvider,
tokensThawing,
sharesThawing,
_nThawRequests,
prov.thawingNonce
);

FulfillThawRequestsParams memory params = FulfillThawRequestsParams({
requestType: ThawRequestType.Provision,
serviceProvider: _serviceProvider,
verifier: _verifier,
owner: _serviceProvider,
tokensThawing: tokensThawing,
sharesThawing: sharesThawing,
nThawRequests: _nThawRequests,
thawingNonce: prov.thawingNonce
});
(tokensThawed_, tokensThawing, sharesThawing) = _fulfillThawRequests(params);

prov.tokens = prov.tokens - tokensThawed_;
prov.sharesThawing = sharesThawing;
Expand Down Expand Up @@ -860,6 +864,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
delegation.shares = delegation.shares - _shares;

bytes32 thawRequestId = _createThawRequest(
ThawRequestType.Delegation,
_serviceProvider,
_verifier,
_beneficiary,
Expand Down Expand Up @@ -894,15 +899,18 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
uint256 tokensThawed = 0;
uint256 sharesThawing = pool.sharesThawing;
uint256 tokensThawing = pool.tokensThawing;
(tokensThawed, tokensThawing, sharesThawing) = _fulfillThawRequests(
_serviceProvider,
_verifier,
msg.sender,
tokensThawing,
sharesThawing,
_nThawRequests,
pool.thawingNonce
);

FulfillThawRequestsParams memory params = FulfillThawRequestsParams({
requestType: ThawRequestType.Delegation,
serviceProvider: _serviceProvider,
verifier: _verifier,
owner: msg.sender,
tokensThawing: tokensThawing,
sharesThawing: sharesThawing,
nThawRequests: _nThawRequests,
thawingNonce: pool.thawingNonce
});
(tokensThawed, tokensThawing, sharesThawing) = _fulfillThawRequests(params);

// The next subtraction should never revert becase: pool.tokens >= pool.tokensThawing and pool.tokensThawing >= tokensThawed
// In the event the pool gets completely slashed tokensThawed will fulfil to 0.
Expand Down Expand Up @@ -936,25 +944,30 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
* @return The ID of the thaw request
*/
function _createThawRequest(
ThawRequestType _requestType,
address _serviceProvider,
address _verifier,
address _owner,
uint256 _shares,
uint64 _thawingUntil,
uint256 _thawingNonce
) private returns (bytes32) {
LinkedList.List storage thawRequestList = _thawRequestLists[_serviceProvider][_verifier][_owner];
LinkedList.List storage thawRequestList = _getThawRequestList(
_requestType,
_serviceProvider,
_verifier,
_owner
);
require(thawRequestList.count < MAX_THAW_REQUESTS, HorizonStakingTooManyThawRequests());

bytes32 thawRequestId = keccak256(abi.encodePacked(_serviceProvider, _verifier, _owner, thawRequestList.nonce));
_thawRequests[thawRequestId] = ThawRequest({
shares: _shares,
thawingUntil: _thawingUntil,
next: bytes32(0),
thawingNonce: _thawingNonce
});
ThawRequest storage thawRequest = _getThawRequest(_requestType, thawRequestId);
thawRequest.shares = _shares;
thawRequest.thawingUntil = _thawingUntil;
thawRequest.next = bytes32(0);
thawRequest.thawingNonce = _thawingNonce;

if (thawRequestList.count != 0) _thawRequests[thawRequestList.tail].next = thawRequestId;
if (thawRequestList.count != 0) _getThawRequest(_requestType, thawRequestList.tail).next = thawRequestId;
thawRequestList.addTail(thawRequestId);

emit ThawRequestCreated(_serviceProvider, _verifier, _owner, _shares, _thawingUntil, thawRequestId);
Expand All @@ -964,41 +977,49 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
/**
* @notice Traverses a thaw request list and fulfills expired thaw requests.
* @dev Emits a {ThawRequestsFulfilled} event and a {ThawRequestFulfilled} event for each thaw request fulfilled.
* @param _serviceProvider The address of the service provider
* @param _verifier The address of the verifier
* @param _owner The address of the owner of the thaw request
* @param _tokensThawing The current amount of tokens already thawing
* @param _sharesThawing The current amount of shares already thawing
* @param _nThawRequests The number of thaw requests to fulfill. If set to 0, all thaw requests are fulfilled.
* @param _thawingNonce The current valid thawing nonce. Any thaw request with a different nonce is invalid and should be ignored.
* @param params The parameters for fulfilling thaw requests
* @return The amount of thawed tokens
* @return The amount of tokens still thawing
* @return The amount of shares still thawing
*/
function _fulfillThawRequests(
address _serviceProvider,
address _verifier,
address _owner,
uint256 _tokensThawing,
uint256 _sharesThawing,
uint256 _nThawRequests,
uint256 _thawingNonce
) private returns (uint256, uint256, uint256) {
LinkedList.List storage thawRequestList = _thawRequestLists[_serviceProvider][_verifier][_owner];
function _fulfillThawRequests(FulfillThawRequestsParams memory params) private returns (uint256, uint256, uint256) {
LinkedList.List storage thawRequestList = _getThawRequestList(
params.requestType,
params.serviceProvider,
params.verifier,
params.owner
);
require(thawRequestList.count > 0, HorizonStakingNothingThawing());

uint256 tokensThawed = 0;
function(bytes32) view returns (bytes32) getNextItem = _getNextThawRequest(params.requestType);
function(bytes32) deleteItem = _getDeleteThawRequest(params.requestType);
bytes memory acc = abi.encode(
params.requestType,
uint256(0),
params.tokensThawing,
params.sharesThawing,
params.thawingNonce
);
(uint256 thawRequestsFulfilled, bytes memory data) = thawRequestList.traverse(
_getNextThawRequest,
getNextItem,
_fulfillThawRequest,
_deleteThawRequest,
abi.encode(tokensThawed, _tokensThawing, _sharesThawing, _thawingNonce),
_nThawRequests
deleteItem,
acc,
params.nThawRequests
);

(tokensThawed, _tokensThawing, _sharesThawing) = abi.decode(data, (uint256, uint256, uint256));
emit ThawRequestsFulfilled(_serviceProvider, _verifier, _owner, thawRequestsFulfilled, tokensThawed);
return (tokensThawed, _tokensThawing, _sharesThawing);
(, uint256 tokensThawed, uint256 tokensThawing, uint256 sharesThawing) = abi.decode(
data,
(ThawRequestType, uint256, uint256, uint256)
);
emit ThawRequestsFulfilled(
params.serviceProvider,
params.verifier,
params.owner,
thawRequestsFulfilled,
tokensThawed
);
return (tokensThawed, tokensThawing, sharesThawing);
}

/**
Expand All @@ -1013,19 +1034,22 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
* @return The updated accumulator data
*/
function _fulfillThawRequest(bytes32 _thawRequestId, bytes memory _acc) private returns (bool, bytes memory) {
ThawRequest storage thawRequest = _thawRequests[_thawRequestId];
// decode
(
ThawRequestType requestType,
uint256 tokensThawed,
uint256 tokensThawing,
uint256 sharesThawing,
uint256 thawingNonce
) = abi.decode(_acc, (ThawRequestType, uint256, uint256, uint256, uint256));

ThawRequest storage thawRequest = _getThawRequest(requestType, _thawRequestId);

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

// decode
(uint256 tokensThawed, uint256 tokensThawing, uint256 sharesThawing, uint256 thawingNonce) = abi.decode(
_acc,
(uint256, uint256, uint256, uint256)
);

// process - only fulfill thaw requests for the current valid nonce
uint256 tokens = 0;
bool validThawRequest = thawRequest.thawingNonce == thawingNonce;
Expand All @@ -1044,17 +1068,39 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
);

// encode
_acc = abi.encode(tokensThawed, tokensThawing, sharesThawing, thawingNonce);
_acc = abi.encode(requestType, tokensThawed, tokensThawing, sharesThawing, thawingNonce);
return (false, _acc);
}

/**
* @notice Deletes a ThawRequest.
* @dev This function is used as a callback in the thaw requests linked list traversal.
* @param _thawRequestId The ID of the thaw request to delete
* @notice Determines the correct callback function for `deleteItem` based on the request type.
* @param _requestType The type of thaw request (Provision or Delegation).
* @return A function pointer to the appropriate `deleteItem` callback.
*/
function _getDeleteThawRequest(ThawRequestType _requestType) private pure returns (function(bytes32)) {
if (_requestType == ThawRequestType.Provision) {
return _deleteProvisionThawRequest;
} else if (_requestType == ThawRequestType.Delegation) {
return _deleteDelegationThawRequest;
} else {
revert("Unknown ThawRequestType");
}
}

/**
* @notice Deletes a thaw request for a provision.
* @param _thawRequestId The ID of the thaw request to delete.
*/
function _deleteProvisionThawRequest(bytes32 _thawRequestId) private {
delete _provisionThawRequests[_thawRequestId];
}

/**
* @notice Deletes a thaw request for a delegation.
* @param _thawRequestId The ID of the thaw request to delete.
*/
function _deleteThawRequest(bytes32 _thawRequestId) private {
delete _thawRequests[_thawRequestId];
function _deleteDelegationThawRequest(bytes32 _thawRequestId) private {
delete _delegationThawRequests[_thawRequestId];
}

/**
Expand Down
Loading