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

stake: Allow initialized stakes to be below the min delegation #24670

Merged
merged 7 commits into from
May 4, 2022

Conversation

joncinque
Copy link
Contributor

@joncinque joncinque commented Apr 25, 2022

Problem

The economics of stake pools will change as a result of a higher minimum delegation. First, some background on pools:

  • Stake pools consist of an initialized stake account for the reserve, and one delegated stake account per validator included.
  • A pool manager commits their funds to start up the pool, in the form of rent-exemption for accounts, and pre-funding the stake pool's reserve, which is an initialized stake account with rent-exemption + 1 lamport
  • A pool manager also commits their funds when adding a validator by creating a stake account with rent-exemption + 0.001 SOL. This amount can be recuperated when removing the validator.
  • Users can either deposit SOL directly into the reserve, or deposit a delegated stake by merging it into the corresponding stake account.
  • Users can either withdraw SOL from the reserve, or from one of the pool's delegated stake accounts via a split.
  • To calculate the value of pool tokens, the required minimums (0.001 SOL + rent exemption for delegated stakes, 1 lamport + rent exemption for the reserve) are not included, since the stake pool program does not allow accounts to go below these minimums. They are not considered usable, since users cannot withdraw them, and the manager cannot decrease stake past the minimum.

When the minimum stake delegation is raised to a larger number, the minimums when calculating the usable amount of SOL in the pool will change from 0.001 SOL to 1 SOL for delegated stakes, and from 1 lamport to 1 SOL for the reserve. In this way, existing stake pools essentially co-opt the new minimums from depositors. 1 SOL in each stake account (including the reserve) will be completely untouchable by both the pool manager and by depositors.

With a minimum delegation of 1 SOL, users with less than 1 SOL must withdraw from the reserve -- for stakes, they need to provide enough stake pool tokens to equal 1 SOL + rent-exemption.

Summary of Changes

It makes sense for delegated stake accounts to adhere to these rules, but initialized stake accounts can live apart, since they aren't present in the runtime's stakes cache.

Allow initialized stakes to be below the minimum delegation amount, while still forcing that all other stake types adhere to the minimum stake delegation. A lot of the changes were cribbed from #24603 (thanks @brooksprumo !)

With this, the whole reserve (minus rent exemption) will be usable for user withdrawals and manager delegations.

Also, for whenever stake v2 is created, with lower or no minimum delegation, their initialized state can still interoperate with initialized stake v1 accounts.

Feature Gate Issue: #24669

@joncinque joncinque added the feature-gate Pull Request adds or modifies a runtime feature gate label Apr 25, 2022
@joncinque joncinque requested review from ryoqun and brooksprumo April 25, 2022 23:56
Copy link
Contributor

@t-nelson t-nelson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused as to why there are changes to initialize and split here. Both would allow new sub-limit delegations to be created. Aren't we just providing an escape hatch for existing sub-limit delegations here? That is, deactivate and withdraw.

@joncinque
Copy link
Contributor Author

Deactivate and withdraw were required to not lock user funds in existing stake accounts, but the idea is to only require the minimum delegation amount for activating and active stakes, which will make usability much better for small stakes and stake pools.

@t-nelson
Copy link
Contributor

Deactivate and withdraw were required to not lock user funds in existing stake accounts, but the idea is to only require the minimum delegation amount for activating and active stakes, which will make usability much better for small stakes and stake pools.

I believe this behavior (esp for split) will retain the accounts in the stakes cache and add work to the rewards calculation. Both of which are the motivation for instating a minimum stake limit.

cc/ @jstarry @behzadnouri

@joncinque
Copy link
Contributor Author

joncinque commented Apr 26, 2022

The stakes cache doesn't do anything with initialized stakes since they don't have a delegation -- when trying to insert them in the stakes cache, it'll fail on deserializing:

return Err(Error::InvalidDelegation(stake_account.stake_state));
, which is the scope of this change.

Your point is well-taken for the future work on split + deactivate on small stakes, since those definitely need to exist in the cache to get their rewards for the deactivation epoch. For those, it might be time to finally add a new stake account type that doesn't get its rewards paid out at the epoch boundary, and add a new instruction to harvest it from the vote account! Unless it's acceptable for small deactivating stakes to exist in the cache.

Copy link
Contributor

@brooksprumo brooksprumo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good. I need to finish going through the test cases in stake_instruction.rs; here's a few thoughts/nits in the meantime.

programs/stake/src/stake_instruction.rs Outdated Show resolved Hide resolved
programs/stake/src/stake_instruction.rs Outdated Show resolved Hide resolved
programs/stake/src/stake_instruction.rs Show resolved Hide resolved
Copy link
Contributor

@brooksprumo brooksprumo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, finished a first pass. Looks good to me. Comments below.

programs/stake/src/stake_instruction.rs Outdated Show resolved Hide resolved
@joncinque
Copy link
Contributor Author

Thanks for the review! This should be ready for another look

@joncinque
Copy link
Contributor Author

I also had to update the frozen abi digest on BankSlotDelta, since it contains transaction::Result, and TransactionError wraps InstructionError, which has a new variant.

brooksprumo
brooksprumo previously approved these changes Apr 28, 2022
Copy link
Contributor

@brooksprumo brooksprumo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! I think I'm also a little biased so it's probably valuable to have another person sign off on this as well.

@codecov
Copy link

codecov bot commented Apr 28, 2022

Codecov Report

Merging #24670 (cc80628) into master (8ba003a) will increase coverage by 0.1%.
The diff coverage is 89.3%.

❗ Current head cc80628 differs from pull request most recent head da3efde. Consider uploading reports for the commit da3efde to get more accurate results

@@            Coverage Diff            @@
##           master   #24670     +/-   ##
=========================================
+ Coverage    81.8%    82.0%   +0.1%     
=========================================
  Files         632      598     -34     
  Lines      167499   165628   -1871     
  Branches      322        0    -322     
=========================================
- Hits       137169   135904   -1265     
+ Misses      30217    29724    -493     
+ Partials      113        0    -113     

Copy link
Contributor

@CriesofCarrots CriesofCarrots left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic looks correct to me, just the one question about error type

if feature_set.is_active(&stake_allow_zero_undelegated_amount::id())
&& stake_amount < crate::get_minimum_delegation(feature_set)
{
return Err(InstructionError::InsufficientStakeDelegation);
Copy link
Contributor

@CriesofCarrots CriesofCarrots May 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this needs to be a new InstructionError variant instead of a new StakeError variant?
We did similar for ActiveVoteAccountClose, but that was because VoteError is really specific to Vote instructions, whereas StakeError covers a variety of Stake instructions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good call, I'll make this a new StakeError

programs/stake/src/stake_instruction.rs Outdated Show resolved Hide resolved
) -> Result<ValidatedDelegatedInfo, InstructionError> {
let stake_amount = account
.get_lamports()
.saturating_sub(meta.rent_exempt_reserve); // can't stake the rent
if feature_set.is_active(&stake_allow_zero_undelegated_amount::id())
Copy link
Contributor

@CriesofCarrots CriesofCarrots May 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a comment about the behavior change here?
Maybe something like this (but moar better): Previously, all StakeState::Initialize accounts were required to have a balance greater than the minimum delegation amount. After feature stake_allow_zero_undelegated_amount, StakeState::Initialize accounts may have a lower balance, so the minimum must be checked on delegation via this function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this feature gate actually necessary? It's not currently possible to initialize a stake account which doesn't already hit the minimum delegation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, I see it was possible to withdraw and then delegate again. Will make sure this behavior gets documented here: #24603

@joncinque
Copy link
Contributor Author

This should be ready for another look, thanks for the careful eyes!

Copy link
Contributor

@CriesofCarrots CriesofCarrots left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@joncinque joncinque merged commit 326e53b into solana-labs:master May 4, 2022
@joncinque joncinque deleted the stk-min branch May 4, 2022 10:17
jeffwashington pushed a commit to jeffwashington/solana that referenced this pull request Jun 29, 2022
…a-labs#24670)

* stake: Allow initialized stakes to be below the min delegation

* Add PR number in feature

* Fixup RPC subscription test

* Address feedback pt 1

* Address feedback pt 2

* Update FrozenAbi Digest

* Address feedback: no new error type, more comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-gate Pull Request adds or modifies a runtime feature gate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants