Closed JimLarson closed 2 years ago
@dtribble here's some options, let's discuss. @Tartuffo too.
Summary of Friday's (2022-02-11) discussion.
The goal is to create an account with rules that automate as much of the contract as practicable, though full automation is impossible
Proposal based on Friday's discussion.
If we're allowed to stake vested tokens, we're allowed to lose vested tokens. There needs to be an out-of-band mechanism for exercising the repurchase right on tokens not available in the account.
Unvested tokens are "encumbered", meaning that they can't be transferred out of the account. Staked, locked, and liened tokens are also encumbered, and it's possible to be encumbered in multiple ways. As noted above, vesting and unlocking are automatic time-based processes, but staking and liening are user operations that do not specify the character of the tokens (un)staked or (un)liened. The minimum number of tokens that need to be encumbered is the maximum of staked, locked, or unvested + liened.
We'd like to avoid giving users the ability to "game" the system by giving them a better outcome when juggling tokens among multiple accounts, vs keeping everything in one account.
Lastly, we'd prefer not to have to check for slashing, a relatively expensive operation, on ever transfer out of the account. It's reasonable to check for slashing when we're traversing the staking data structures anyhow, but we'd like to avoid having idiosyncratic performance for common operations compared to other cosmos-sdk chains.
Therefore, I propose the consistent approach where encumbered tokens are overlapped as much as possible, and that we apply the same preference to rewards and slashing: staked tokens are locked-first and unvested-first, followed by liened.
But even if nominally-unvested tokens are slashed, it does not reduce the liability at clawback time, where one of the following happens:
Option 1 has the disadvantage that it removes tokens which are nominally vested. For instance, if the user had transferred some tokens in from an external source, those could be taken during clawback. However, one could argue that the repurchase right covers all tokens in the account.
Option 2 has the disadvantage of needed to resort to out-of-band repurchase in more cases, though "more" could be negligible if slashing and clawback are both rare.
Option 2 will be the easiest to implement, and Option 1a easier than 1b.
Dean agrees with the proposal. I'll detail work items, make an estimate, and see what's necessary to do before MN-1.
Work to do:
Closing this task in favor of specific subtasks.
What is the Problem Being Solved?
Vesting with clawback adds another dimension of state and activity to an already complicated picture. There are concerns that the currently-implemented behavior isn't as consistent as it could be. This issue explores the options.
Description of the Design
For the purpose of this discussion, we'll only consider staking tokens. Other tokens work similarly, except they can't be staked.
We'll also describe the operations and behavior of a
ClawbackVestingAccount
, as every other account type is a subset of its capabilities.Primary Facts of Account Management in Cosmis-SDK
Dimensions of State
Quantities noted as "encumbered" may not be transferred out of the account.
Staking Dimension
For the purposes here we'll lump "Unbonding" tokens in with "Bonded".
x/staking
.x/bank
, not staked, could still be encumbered by other dimensions.Lockup Dimension
Lien/Vesting Dimension
Vesting bookkeeping
Vesting accounts keep additional bookkeeping for the interaction with staking. Here "vested" means Unlocked and Vested in the clawback sense.
To determine how much of your
x/bank
balance is actually available to spend:x/bank
account.Operations
Each has a single argument - the number of tokens.
Current Implementation
The Liened amount is never reduced by slashing, only by an unlien operation (from repayment of a loan or reduction of required lien ratio). This can lead to the Liened amount exceeding the total account balance.
Option A: Bonded prefers encumbered always
For greater consistency, we could remove the exceptions noted above and always prefer that slashed/bonded tokens be the Unvested/Locked ones. This would mean:
This would be a relatively small change (3 story points?) and would actually clean up a small slashing-rewards inconsistency noted at the end of my Jan 10 comment on #4085.
Option B1: Proportional
We could make the different dimensions uncorrelated and simply say that each category of one dimension is proportionally divided among the other dimensions. E.g. if we have 100 tokens total, 40 bonded, 25 unvested, 50 liened, and 60 locked, then we'd have 8 bonded-liened-unlocked tokens (= 100 0.4 0.5 * 0.4), and so on for all other combinations.
Unfortunately, this gives some strange results, as each operation changes the proportions. For instance, if we are 100% vested but 50% locked and 50% bonded with 100 tokens, and we want to withdraw as many tokens as we can, we can immediately withdraw 25 unlocked-unbonded tokens, leaving 75 tokens with 67% bonded and 67% locked - which means we now have 8.3 unlocked-unbonded tokens, which we can withdraw leaving 67 tokens with 75% bonded and 75 locked, which means we can withdraw 4 more ... and so on. There is similar behavior for liens. It seems we can iteratively remove unencumbered tokens and have the account as if we were working with Option A.
Option B2: Inconsistent-Proportional
If we drop the requirement to use the same policy consistently everywhere, we could use a proportional strategy in some situations while using another policy (e.g. Option A) elsewhere. For instance, we can do reward division and clawback proportionally. This would avoid the strange behavior documented in Option B1. The cost of this, as with the current implementation, is that the impact of slashing would vary based on the operation.
Option B3: Proportional with memory
Doing my best to give viable options for proportional: there might be a way to avoid the silly behavior of plain Option B1, but without the user-facing complexity and development time impact of Option C below. The operations still don't specify the character of the tokens that are staked, vested, unlocked, liened, etc., but we keep track of the explicit amounts on each side and select tokens proportionally. For instance, if we're totally unstaked with 70 unvested tokens and 30 vested, and we stake 20, then the staked tokens will be 70% unvested, and we remember the amounts on each side of the staking barrier: 20 staked (of which 14 are unvested and 6 vested) and 80 unstaked (of which 56 are unvested and 24 vested). Now we transfer in 80 tokens (considered vested) from outside. So we have 160 unstaked (of which 56 are unvested and 104 vested - so 35% are unvested). The amount of vested/unvested staked tokens doesn't change. If we stake additional tokens, 35% will be unvested (reflecting the proportion from the unstaked side) but if we unstake tokens, 70% will be unvested (reflecting the proportion from the staked side).
But this is just one operation pair (staking/unstaking) and one orthogonal dimension (vesting). We have another operation pair (lien/unlien) and another orthogonal dimension (lockup). I'm not yet sure if this all plays well together, or if there are other paradoxes of silliness awaiting. Even if there's a consistent design, I don't know if it can be implemented without wholesale changes to cosmos-sdk.
Option C: Explicit Cartesian product of states
Lastly, we could make the user make choices instead of forcing a policy. When staking and unstaking, liening and unleining, the user could provide the exact character of the tokens involved, e.g. "stake 12 vested-locked, 37 vested-unlocked, and 53 lieneed-locked.
We would still need to come up with an automatic policy for determining what gets slashed, perhaps proportional.
This would require extensive rework to
x/auth/vesting
,x/staking
, as well as the staking and getRUN UI.Security Considerations
If correctly implemented, all are secure.
Test Plan
Existing unit tests provide reasonable coverage.