Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(Horizon): add redelegate option #1059

Merged
merged 1 commit into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -420,6 +420,16 @@ interface IHorizonStakingMain {
*/
error HorizonStakingInvalidBeneficiaryZeroAddress();

/**
* @notice Thrown when attempting to redelegate with a serivce provider that is the zero address.
*/
error HorizonStakingInvalidServiceProviderZeroAddress();

/**
* @notice Thrown when attempting to redelegate with a verifier that is the zero address.
*/
error HorizonStakingInvalidVerifierZeroAddress();

// -- Errors: thaw requests --

error HorizonStakingNothingThawing();
Expand Down Expand Up @@ -746,28 +756,46 @@ interface IHorizonStakingMain {

/**
* @notice Withdraw undelegated tokens from a provision after thawing.
* Tokens can be automatically re-delegated to another provision by setting `newServiceProvider`.
* @dev The parameter `nThawRequests` can be set to a non zero value to fulfill a specific number of thaw
* requests in the event that fulfilling all of them results in a gas limit error.
* @dev If the delegation pool was completely slashed before withdrawing, calling this function will fulfill
* the thaw requests with an amount equal to zero.
*
* Requirements:
* - Must have previously initiated a thaw request using {undelegate}.
* - `newServiceProvider` must either be zero address or have previously provisioned stake to `verifier`.
*
* Emits {ThawRequestFulfilled}, {ThawRequestsFulfilled} and {DelegatedTokensWithdrawn} events.
*
* @param serviceProvider The service provider address
* @param verifier The verifier address
* @param newServiceProvider The address of a new service provider, if the delegator wants to re-delegate
* @param nThawRequests The number of thaw requests to fulfill. Set to 0 to fulfill all thaw requests.
*/
function withdrawDelegated(address serviceProvider, address verifier, uint256 nThawRequests) external;

/**
* @notice Re-delegate undelegated tokens from a provision after thawing to a `newServiceProvider` and `newVerifier`.
* @dev The parameter `nThawRequests` can be set to a non zero value to fulfill a specific number of thaw
* requests in the event that fulfilling all of them results in a gas limit error.
*
* Requirements:
* - Must have previously initiated a thaw request using {undelegate}.
* - `newServiceProvider` and `newVerifier` must not be the zero address.
* - `newServiceProvider` must have previously provisioned stake to `newVerifier`.
*
* Emits {ThawRequestFulfilled}, {ThawRequestsFulfilled} and {DelegatedTokensWithdrawn} events.
*
* @param oldServiceProvider The old service provider address
* @param oldVerifier The old verifier address
* @param newServiceProvider The address of a new service provider
* @param newVerifier The address of a new verifier
* @param minSharesForNewProvider The minimum amount of shares to accept for the new service provider
* @param nThawRequests The number of thaw requests to fulfill. Set to 0 to fulfill all thaw requests.
*/
function withdrawDelegated(
address serviceProvider,
address verifier,
function redelegate(
address oldServiceProvider,
address oldVerifier,
address newServiceProvider,
address newVerifier,
uint256 minSharesForNewProvider,
uint256 nThawRequests
) external;
Expand Down
37 changes: 33 additions & 4 deletions packages/horizon/contracts/staking/HorizonStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,32 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
function withdrawDelegated(
address serviceProvider,
address verifier,
uint256 nThawRequests
) external override notPaused {
_withdrawDelegated(serviceProvider, verifier, address(0), address(0), 0, nThawRequests);
}

/**
* @notice See {IHorizonStakingMain-redelegate}.
*/
function redelegate(
address oldServiceProvider,
address oldVerifier,
address newServiceProvider,
address newVerifier,
uint256 minSharesForNewProvider,
uint256 nThawRequests
) external override notPaused {
_withdrawDelegated(serviceProvider, verifier, newServiceProvider, minSharesForNewProvider, nThawRequests);
require(newServiceProvider != address(0), HorizonStakingInvalidServiceProviderZeroAddress());
require(newVerifier != address(0), HorizonStakingInvalidVerifierZeroAddress());
_withdrawDelegated(
oldServiceProvider,
oldVerifier,
newServiceProvider,
newVerifier,
minSharesForNewProvider,
nThawRequests
);
}

/**
Expand Down Expand Up @@ -360,7 +381,14 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
* @notice See {IHorizonStakingMain-withdrawDelegated}.
*/
function withdrawDelegated(address serviceProvider, address newServiceProvider) external override notPaused {
_withdrawDelegated(serviceProvider, SUBGRAPH_DATA_SERVICE_ADDRESS, newServiceProvider, 0, 0);
_withdrawDelegated(
serviceProvider,
SUBGRAPH_DATA_SERVICE_ADDRESS,
newServiceProvider,
SUBGRAPH_DATA_SERVICE_ADDRESS,
0,
0
);
}

/*
Expand Down Expand Up @@ -851,6 +879,7 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
address _serviceProvider,
address _verifier,
address _newServiceProvider,
address _newVerifier,
uint256 _minSharesForNewProvider,
uint256 _nThawRequests
) private {
Expand Down Expand Up @@ -882,8 +911,8 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
pool.tokensThawing = tokensThawing;

if (tokensThawed != 0) {
if (_newServiceProvider != address(0)) {
_delegate(_newServiceProvider, _verifier, tokensThawed, _minSharesForNewProvider);
if (_newServiceProvider != address(0) && _newVerifier != address(0)) {
_delegate(_newServiceProvider, _newVerifier, tokensThawed, _minSharesForNewProvider);
} else {
_graphToken().pushTokens(msg.sender, tokensThawed);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1004,24 +1004,42 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
}

function _withdrawDelegated(
address serviceProvider,
address verifier,
uint256 nThawRequests
) internal {
__withdrawDelegated(
serviceProvider,
verifier,
address(0),
address(0),
0,
nThawRequests,
false
);
}

function _redelegate(
address serviceProvider,
address verifier,
address newServiceProvider,
address newVerifier,
uint256 minSharesForNewProvider,
uint256 nThawRequests
) internal {
__withdrawDelegated(
serviceProvider,
verifier,
newServiceProvider,
newVerifier,
minSharesForNewProvider,
nThawRequests,
false
);
}

function _withdrawDelegated(address serviceProvider, address newServiceProvider) internal {
__withdrawDelegated(serviceProvider, subgraphDataServiceLegacyAddress, newServiceProvider, 0, 0, true);
__withdrawDelegated(serviceProvider, subgraphDataServiceLegacyAddress, newServiceProvider, subgraphDataServiceLegacyAddress, 0, 0, true);
}

struct BeforeValues_WithdrawDelegated {
Expand All @@ -1045,19 +1063,20 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
address _serviceProvider,
address _verifier,
address _newServiceProvider,
address _newVerifier,
uint256 _minSharesForNewProvider,
uint256 _nThawRequests,
bool legacy
) private {
(, address msgSender, ) = vm.readCallers();

bool reDelegate = _newServiceProvider != address(0);
bool reDelegate = _newServiceProvider != address(0) && _newVerifier != address(0);

// before
BeforeValues_WithdrawDelegated memory beforeValues;
beforeValues.pool = _getStorage_DelegationPoolInternal(_serviceProvider, _verifier, legacy);
beforeValues.newPool = _getStorage_DelegationPoolInternal(_newServiceProvider, _verifier, legacy);
beforeValues.newDelegation = _getStorage_Delegation(_serviceProvider, _verifier, msgSender, legacy);
beforeValues.newPool = _getStorage_DelegationPoolInternal(_newServiceProvider, _newVerifier, legacy);
beforeValues.newDelegation = _getStorage_Delegation(_newServiceProvider, _newVerifier, msgSender, legacy);
beforeValues.thawRequestList = staking.getThawRequestList(_serviceProvider, _verifier, msgSender);
beforeValues.senderBalance = token.balanceOf(msgSender);
beforeValues.stakingBalance = token.balanceOf(address(staking));
Expand Down Expand Up @@ -1095,7 +1114,7 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
if (reDelegate) {
emit IHorizonStakingMain.TokensDelegated(
_newServiceProvider,
_verifier,
_newVerifier,
msgSender,
calcValues.tokensThawed
);
Expand All @@ -1104,25 +1123,33 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
}
}
vm.expectEmit();

emit IHorizonStakingMain.DelegatedTokensWithdrawn(
_serviceProvider,
_verifier,
msgSender,
calcValues.tokensThawed
);
staking.withdrawDelegated(
_serviceProvider,
_verifier,
_newServiceProvider,
_minSharesForNewProvider,
_nThawRequests
);
if (legacy) {
staking.withdrawDelegated(_serviceProvider, _newServiceProvider);
} else if (reDelegate) {
staking.redelegate(
_serviceProvider,
_verifier,
_newServiceProvider,
_newVerifier,
_minSharesForNewProvider,
_nThawRequests
);
} else {
staking.withdrawDelegated(_serviceProvider, _verifier, _nThawRequests);
}

// after
AfterValues_WithdrawDelegated memory afterValues;
afterValues.pool = _getStorage_DelegationPoolInternal(_serviceProvider, _verifier, legacy);
afterValues.newPool = _getStorage_DelegationPoolInternal(_newServiceProvider, _verifier, legacy);
afterValues.newDelegation = _getStorage_Delegation(_newServiceProvider, _verifier, msgSender, legacy);
afterValues.newPool = _getStorage_DelegationPoolInternal(_newServiceProvider, _newVerifier, legacy);
afterValues.newDelegation = _getStorage_Delegation(_newServiceProvider, _newVerifier, msgSender, legacy);
afterValues.thawRequestList = staking.getThawRequestList(_serviceProvider, _verifier, msgSender);
afterValues.senderBalance = token.balanceOf(msgSender);
afterValues.stakingBalance = token.balanceOf(address(staking));
Expand Down
Loading