simd: '0148' title: MoveStake and MoveLamports Instructions authors:
We propose introducing two new instructions to the stake program for moving
value between stake accounts with identical Authorized and Lockup:
MoveStake: Move a given amount of active stake from one active account to
another active account, or from an active account to an inactive one, turning it
into an active account. If the entire source account delegation is moved, the
source account becomes inactive. In all cases, rent-exempt balance is unaffected
and minimum delegations are respected for accounts that end in an active state.MoveLamports: Move a given amount of excess lamports from one active or
inactive account to another active or inactive account, where "excess lamports"
refers to lamports that are neither delegated stake nor required for
rent-exemption.For simplicity of implementation, we choose not to support accounts that are activating, deactivating, or partially active. A future SIMD may choose to extend this functionality should it be desirable.
Recently, a feature was activated which mandates that Split destinations be
prefunded with the rent-exempt reserve, because before that, Split could be
used to deactivate stake immediately, bypassing the cooldown period.
However, this has introduced issues for protocols that manage stake on behalf
of users without taking Withdrawer authority. Particularly, for one that
splits user stake across many validators and periodically redelegates between
them, every time they want to split part of a user stake to deactivate, the
protocol must fund the rent-exemption themselves. And then when that split
account is merged, those lamports cannot be reclaimed by the protocol, instead
accumulating (undelegated) in the destination merge accounts.
The purpose of the MoveStake instruction is to enable a flow whereby moving
stake from a user's stake accounts U1 -> U2 from validator V1 to validator V2
may proceed:
MoveStake the amount of stake from the user stake account U1 to an
inactive account T holding sufficient lamports for rent exemption. T instantly
becomes a second active stake account delegated to V1 with amount stake.Deactivate T and wait an epoch.DelegateStake T to V2 and wait an epoch. T becomes an active stake account
delegated to V2 with amount stake.MoveStake the amount stake from T to U2. T returns to its initial inactive
state. Stake has moved from U1 to U2 with no outside lamports required and no
new undelegated lamports becoming trapped in delegated stake accounts.The motivation for MoveLamports is to enable housekeeping tasks such as
reclaiming lamports from Merge destinations.
DelegateStake to accept an active stake account to begin
activating an account's excess (non-rent non-stake) lamports, or allowing a
Split source to Deactivate enough stake to cover rent-exemption for the new
account. However, Multistake is a much larger design/engineering project, and
we have to solve the existing proble sooner than Multistake would be ready.Merge to leave behind the
source account or Split to split into any mergeable destination. However this
confuses the presently clear distinction between these two operations and
entails additional implementation risk as they are already rather complex. A
new instruction that does one specific thing seems highly preferable.Move that did not take an
amount, but this would require changes to Split to enable the first leg of
the proposed flow.Split destinations.
This is undesirable because that restriction was added for very good reason: an
effectively unbounded amount of stake could be instantly deactivated through
repeated splitting.MoveStake and MoveLamports, two new stake program instructions.
For clarity of terminology inside this specification:
Stake state with nonzero delegation, 100% of which
is effective stake. There is no activating or deactivating stake.Initialized or Stake state. There is no
effective, activating, or deactivating stake.MoveStakeMoveStake requires 3 accounts:
MoveStake instruction data is 12 bytes, containing:
0x10 0x00 0x00 0x00, a fixed-value four-byte little-endian unsigned integer
acting as the instruction discriminatoramount, an unaligned eight-byte little-endian unsigned integer indicating
the quantity of lamports to moveMoveStake aborts the transaction when:
amount is 0AuthorizedLockup is in force, source and destination do not have identical LockupStaker on both accountsStakeStateStakeStateamount stake would leave source with a nonzero amount of stake less
than the minimum delegationamount stake would fail to bring destination up to minimum delegationIf all of these conditions hold, then:
amountamountamount constitutes the full delegation on the source, source is reset to
an Initialized stateStake
as source, aside from delegation amountcredits_observed must be updated on the destination according to the same
rules as MergeMoveLamportsMoveLamports requires 3 accounts:
MoveLamports instruction data is 12 bytes, containing:
0x11 0x00 0x00 0x00, a fixed-value four-byte little-endian unsigned integer
acting as the instruction discriminatoramount, an unaligned eight-byte little-endian unsigned integer indicating
the quantity of lamports to moveMoveLamports aborts the transaction when:
amount is 0AuthorizedLockup is in force, source and destination do not have identical LockupStaker on both accountsamount exceeds source lamports - effective_stake - rent_exempt_reserveIf all of these conditions hold, then:
amountamountThe primary utility of the proposed instructions is to support protocol
developers in moving value between stake accounts with the same authorities
without controlling the Withdrawer. There is no loss of existing
functionality.
Care must be taken to ensure stakes are active, as moving delegations
between accounts in any kind of intermediate state is fraught. Otherwise this
change should be fairly low impact, as it does not require changing any existing
logic, in particular avoiding making Split or Merge more permissive.