Skip to content

Commit 0f5ba68

Browse files
authored
chore(Horizon): add redelegate option (#1059)
1 parent 7035403 commit 0f5ba68

File tree

5 files changed

+269
-94
lines changed

5 files changed

+269
-94
lines changed

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

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,16 @@ interface IHorizonStakingMain {
420420
*/
421421
error HorizonStakingInvalidBeneficiaryZeroAddress();
422422

423+
/**
424+
* @notice Thrown when attempting to redelegate with a serivce provider that is the zero address.
425+
*/
426+
error HorizonStakingInvalidServiceProviderZeroAddress();
427+
428+
/**
429+
* @notice Thrown when attempting to redelegate with a verifier that is the zero address.
430+
*/
431+
error HorizonStakingInvalidVerifierZeroAddress();
432+
423433
// -- Errors: thaw requests --
424434

425435
error HorizonStakingNothingThawing();
@@ -746,28 +756,46 @@ interface IHorizonStakingMain {
746756

747757
/**
748758
* @notice Withdraw undelegated tokens from a provision after thawing.
749-
* Tokens can be automatically re-delegated to another provision by setting `newServiceProvider`.
750759
* @dev The parameter `nThawRequests` can be set to a non zero value to fulfill a specific number of thaw
751760
* requests in the event that fulfilling all of them results in a gas limit error.
752761
* @dev If the delegation pool was completely slashed before withdrawing, calling this function will fulfill
753762
* the thaw requests with an amount equal to zero.
754763
*
755764
* Requirements:
756765
* - Must have previously initiated a thaw request using {undelegate}.
757-
* - `newServiceProvider` must either be zero address or have previously provisioned stake to `verifier`.
758766
*
759767
* Emits {ThawRequestFulfilled}, {ThawRequestsFulfilled} and {DelegatedTokensWithdrawn} events.
760768
*
761769
* @param serviceProvider The service provider address
762770
* @param verifier The verifier address
763-
* @param newServiceProvider The address of a new service provider, if the delegator wants to re-delegate
771+
* @param nThawRequests The number of thaw requests to fulfill. Set to 0 to fulfill all thaw requests.
772+
*/
773+
function withdrawDelegated(address serviceProvider, address verifier, uint256 nThawRequests) external;
774+
775+
/**
776+
* @notice Re-delegate undelegated tokens from a provision after thawing to a `newServiceProvider` and `newVerifier`.
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+
*
780+
* Requirements:
781+
* - Must have previously initiated a thaw request using {undelegate}.
782+
* - `newServiceProvider` and `newVerifier` must not be the zero address.
783+
* - `newServiceProvider` must have previously provisioned stake to `newVerifier`.
784+
*
785+
* Emits {ThawRequestFulfilled}, {ThawRequestsFulfilled} and {DelegatedTokensWithdrawn} events.
786+
*
787+
* @param oldServiceProvider The old service provider address
788+
* @param oldVerifier The old verifier address
789+
* @param newServiceProvider The address of a new service provider
790+
* @param newVerifier The address of a new verifier
764791
* @param minSharesForNewProvider The minimum amount of shares to accept for the new service provider
765792
* @param nThawRequests The number of thaw requests to fulfill. Set to 0 to fulfill all thaw requests.
766793
*/
767-
function withdrawDelegated(
768-
address serviceProvider,
769-
address verifier,
794+
function redelegate(
795+
address oldServiceProvider,
796+
address oldVerifier,
770797
address newServiceProvider,
798+
address newVerifier,
771799
uint256 minSharesForNewProvider,
772800
uint256 nThawRequests
773801
) external;

packages/horizon/contracts/staking/HorizonStaking.sol

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,11 +319,32 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
319319
function withdrawDelegated(
320320
address serviceProvider,
321321
address verifier,
322+
uint256 nThawRequests
323+
) external override notPaused {
324+
_withdrawDelegated(serviceProvider, verifier, address(0), address(0), 0, nThawRequests);
325+
}
326+
327+
/**
328+
* @notice See {IHorizonStakingMain-redelegate}.
329+
*/
330+
function redelegate(
331+
address oldServiceProvider,
332+
address oldVerifier,
322333
address newServiceProvider,
334+
address newVerifier,
323335
uint256 minSharesForNewProvider,
324336
uint256 nThawRequests
325337
) external override notPaused {
326-
_withdrawDelegated(serviceProvider, verifier, newServiceProvider, minSharesForNewProvider, nThawRequests);
338+
require(newServiceProvider != address(0), HorizonStakingInvalidServiceProviderZeroAddress());
339+
require(newVerifier != address(0), HorizonStakingInvalidVerifierZeroAddress());
340+
_withdrawDelegated(
341+
oldServiceProvider,
342+
oldVerifier,
343+
newServiceProvider,
344+
newVerifier,
345+
minSharesForNewProvider,
346+
nThawRequests
347+
);
327348
}
328349

329350
/**
@@ -360,7 +381,14 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
360381
* @notice See {IHorizonStakingMain-withdrawDelegated}.
361382
*/
362383
function withdrawDelegated(address serviceProvider, address newServiceProvider) external override notPaused {
363-
_withdrawDelegated(serviceProvider, SUBGRAPH_DATA_SERVICE_ADDRESS, newServiceProvider, 0, 0);
384+
_withdrawDelegated(
385+
serviceProvider,
386+
SUBGRAPH_DATA_SERVICE_ADDRESS,
387+
newServiceProvider,
388+
SUBGRAPH_DATA_SERVICE_ADDRESS,
389+
0,
390+
0
391+
);
364392
}
365393

366394
/*
@@ -851,6 +879,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
851879
address _serviceProvider,
852880
address _verifier,
853881
address _newServiceProvider,
882+
address _newVerifier,
854883
uint256 _minSharesForNewProvider,
855884
uint256 _nThawRequests
856885
) private {
@@ -882,8 +911,8 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
882911
pool.tokensThawing = tokensThawing;
883912

884913
if (tokensThawed != 0) {
885-
if (_newServiceProvider != address(0)) {
886-
_delegate(_newServiceProvider, _verifier, tokensThawed, _minSharesForNewProvider);
914+
if (_newServiceProvider != address(0) && _newVerifier != address(0)) {
915+
_delegate(_newServiceProvider, _newVerifier, tokensThawed, _minSharesForNewProvider);
887916
} else {
888917
_graphToken().pushTokens(msg.sender, tokensThawed);
889918
}

packages/horizon/test/shared/horizon-staking/HorizonStakingShared.t.sol

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,24 +1004,42 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
10041004
}
10051005

10061006
function _withdrawDelegated(
1007+
address serviceProvider,
1008+
address verifier,
1009+
uint256 nThawRequests
1010+
) internal {
1011+
__withdrawDelegated(
1012+
serviceProvider,
1013+
verifier,
1014+
address(0),
1015+
address(0),
1016+
0,
1017+
nThawRequests,
1018+
false
1019+
);
1020+
}
1021+
1022+
function _redelegate(
10071023
address serviceProvider,
10081024
address verifier,
10091025
address newServiceProvider,
1026+
address newVerifier,
10101027
uint256 minSharesForNewProvider,
10111028
uint256 nThawRequests
10121029
) internal {
10131030
__withdrawDelegated(
10141031
serviceProvider,
10151032
verifier,
10161033
newServiceProvider,
1034+
newVerifier,
10171035
minSharesForNewProvider,
10181036
nThawRequests,
10191037
false
10201038
);
10211039
}
10221040

10231041
function _withdrawDelegated(address serviceProvider, address newServiceProvider) internal {
1024-
__withdrawDelegated(serviceProvider, subgraphDataServiceLegacyAddress, newServiceProvider, 0, 0, true);
1042+
__withdrawDelegated(serviceProvider, subgraphDataServiceLegacyAddress, newServiceProvider, subgraphDataServiceLegacyAddress, 0, 0, true);
10251043
}
10261044

10271045
struct BeforeValues_WithdrawDelegated {
@@ -1045,19 +1063,20 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
10451063
address _serviceProvider,
10461064
address _verifier,
10471065
address _newServiceProvider,
1066+
address _newVerifier,
10481067
uint256 _minSharesForNewProvider,
10491068
uint256 _nThawRequests,
10501069
bool legacy
10511070
) private {
10521071
(, address msgSender, ) = vm.readCallers();
10531072

1054-
bool reDelegate = _newServiceProvider != address(0);
1073+
bool reDelegate = _newServiceProvider != address(0) && _newVerifier != address(0);
10551074

10561075
// before
10571076
BeforeValues_WithdrawDelegated memory beforeValues;
10581077
beforeValues.pool = _getStorage_DelegationPoolInternal(_serviceProvider, _verifier, legacy);
1059-
beforeValues.newPool = _getStorage_DelegationPoolInternal(_newServiceProvider, _verifier, legacy);
1060-
beforeValues.newDelegation = _getStorage_Delegation(_serviceProvider, _verifier, msgSender, legacy);
1078+
beforeValues.newPool = _getStorage_DelegationPoolInternal(_newServiceProvider, _newVerifier, legacy);
1079+
beforeValues.newDelegation = _getStorage_Delegation(_newServiceProvider, _newVerifier, msgSender, legacy);
10611080
beforeValues.thawRequestList = staking.getThawRequestList(_serviceProvider, _verifier, msgSender);
10621081
beforeValues.senderBalance = token.balanceOf(msgSender);
10631082
beforeValues.stakingBalance = token.balanceOf(address(staking));
@@ -1095,7 +1114,7 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
10951114
if (reDelegate) {
10961115
emit IHorizonStakingMain.TokensDelegated(
10971116
_newServiceProvider,
1098-
_verifier,
1117+
_newVerifier,
10991118
msgSender,
11001119
calcValues.tokensThawed
11011120
);
@@ -1104,25 +1123,33 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
11041123
}
11051124
}
11061125
vm.expectEmit();
1126+
11071127
emit IHorizonStakingMain.DelegatedTokensWithdrawn(
11081128
_serviceProvider,
11091129
_verifier,
11101130
msgSender,
11111131
calcValues.tokensThawed
11121132
);
1113-
staking.withdrawDelegated(
1114-
_serviceProvider,
1115-
_verifier,
1116-
_newServiceProvider,
1117-
_minSharesForNewProvider,
1118-
_nThawRequests
1119-
);
1133+
if (legacy) {
1134+
staking.withdrawDelegated(_serviceProvider, _newServiceProvider);
1135+
} else if (reDelegate) {
1136+
staking.redelegate(
1137+
_serviceProvider,
1138+
_verifier,
1139+
_newServiceProvider,
1140+
_newVerifier,
1141+
_minSharesForNewProvider,
1142+
_nThawRequests
1143+
);
1144+
} else {
1145+
staking.withdrawDelegated(_serviceProvider, _verifier, _nThawRequests);
1146+
}
11201147

11211148
// after
11221149
AfterValues_WithdrawDelegated memory afterValues;
11231150
afterValues.pool = _getStorage_DelegationPoolInternal(_serviceProvider, _verifier, legacy);
1124-
afterValues.newPool = _getStorage_DelegationPoolInternal(_newServiceProvider, _verifier, legacy);
1125-
afterValues.newDelegation = _getStorage_Delegation(_newServiceProvider, _verifier, msgSender, legacy);
1151+
afterValues.newPool = _getStorage_DelegationPoolInternal(_newServiceProvider, _newVerifier, legacy);
1152+
afterValues.newDelegation = _getStorage_Delegation(_newServiceProvider, _newVerifier, msgSender, legacy);
11261153
afterValues.thawRequestList = staking.getThawRequestList(_serviceProvider, _verifier, msgSender);
11271154
afterValues.senderBalance = token.balanceOf(msgSender);
11281155
afterValues.stakingBalance = token.balanceOf(address(staking));

0 commit comments

Comments
 (0)