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

Vesting accounts with clawback #4085

Closed
JimLarson opened this issue Nov 16, 2021 · 10 comments · Fixed by agoric-labs/cosmos-sdk#155
Closed

Vesting accounts with clawback #4085

JimLarson opened this issue Nov 16, 2021 · 10 comments · Fixed by agoric-labs/cosmos-sdk#155
Assignees
Labels
agoric-cosmos enhancement New feature or request
Milestone

Comments

@JimLarson
Copy link
Contributor

What is the Problem Being Solved?

It is too error-prone to perform vesting events manually for vesting token awards. There will be dozens of events on different days per month going to different accounts.

The current "vesting" accounts implement an automatic unlocking schedule which prevent tokens from being transferred out, but there is no provision for clawback. The locked-up tokens are available for staking (and subject to slashing).

We'd like to have:

  • an automated implementation of vesting awards that require no manual action
  • allow a command for clawback of unvested tokens
  • play nicely with existing encumbrances (lockup, lien)
  • allow the staking operations decided below.

Description of the Design

To allow maximum use of unvested tokens, we could simply modify current periodic vesting accounts to have a "clawback" command which transfers the unvested amount back to the funding account, subject to signature from the funding account.

For minimum use of the unvested tokens, the vesting schedule could control the incremental transfer of funds from the funding account (or an intermediate escrow account) to the target account.

In either case, it would make sense to use the same format for a vesting schedule as is used for the current periodic "vesting" accounts.

Note that the vesting dimension is orthogonal to the locking dimension. A given amount of tokens may be subject to both vesting and lockup with different schedules, and either dimension should be able to prevent transfer.

Unknowns

  • What happens to the rewards from staked unvested tokens - is that subject to lockup or clawback?
  • What happens if unvested staked tokens get slashed?
  • Do the above answers square with all agreements about such vesting awards?
  • Does clawback transfer tokens back only to the original funding account, or possibly somewhere else?
  • Can staked, unvested tokens be voted?
  • Can a line of credit be taken on unvested tokens?
  • Do we need to be able to add new vesting awards to existing accounts?

Security Considerations

Test Plan

@JimLarson JimLarson added the enhancement New feature or request label Nov 16, 2021
@JimLarson JimLarson self-assigned this Nov 16, 2021
@JimLarson
Copy link
Contributor Author

Implementation

  • Need to immediately unbond staked tokens for clawback. This might require a new public method in the staking module.
  • Need to transfer locked-up tokens for clawback. This might require a new public method in the bank module.

Voting

If allowed, voting unvested tokens is not problematic, as voting power is determined by a snapshot at vote-counting time. Nothing special needs to be done for clawback.

Line of Credit

Since there's no way to guarantee repayment of the loan, and clawed-back tokens revert to being unbonded and have no ongoing revenue stream, I recommend that we disallow a line of credit using unvested tokens. This means that clawback will not have to contend with a lien encumbrance. (What if we encumber the loan so that it can't be transferred out of the account? Then there's little point in having the loan.)

Slashing

We can have slashing preferentially affect vested tokens. But otherwise, being able to stake unvested tokens means that they might be lost to slashing and not available for clawback. However, one could consider slashing a form of early clawback - they're still confiscated from the recipient (although not returned to the issuer). The recipient is still motivated to stake wisely as long as they do not plan on a clawback.

Rewards

The simplest approach is to not encumber the rewards, so I recommend that if it's allowable. We might also want to split the policy based on the type of reward, i.e. BLD vs RUN.

If we do need to encumber the rewards, the simplest formula is to vest the reward when the originating token vests.

@dtribble
Copy link
Member

dtribble commented Dec 2, 2021

Preferred mechanics:

  • the mechanism MUST support transferring all unvested tokens at once
  • clawback MUST support retrieving of all unvested tokens
  • clawback SHOULD support specifying a destination account for the reclaimed tokens
  • clawback MUST NOT apply to vested tokens
  • clawback MAY support partial clawback; how that interacts with the schedule might make this to complicated
  • unvested tokens MUST be votable by the recipient
  • if slashed, vested and unvested SHOULD be slashed proportionately
  • rewards for unvested tokens vest SHOULD go along with their tokens (whereas rewards for vested-but-locked tokens SHOULD be unlocked)
  • while vesting and locking are independent, it's probably worth seeing if we can support needed use cases with a single schedule

@JimLarson
Copy link
Contributor Author

I think I can do all the MUST (NOT) requirements, most of the SHOULDs, and perhaps the MAY. I think we can allow both vesting and locking in the same account. We can even add new awards with their own locking, vesting, or combined schedule.

Slashing: It's difficult to make slashing proportional because we don't have a good way of actually knowing how much was slashed after the fact. Adding such instrumentation would require changes to the staking module, which I'd like to avoid. This is what led us to the rule for liens: slashing first affects the unliened tokens. I'd like to have the analogous behavior where slashing first affects the vested tokens.

Partial clawback: We can make this work, but which sort of behavior do you want? We can cancel the soonest upcoming vesting events, cancel starting with the last vesting events, or we can proportionately reduce the amount of all upcoming vesting events.

Question on signature: I envision the clawback being signed by the account that donated the original amount, even if a different account is specified as the beneficiary. If we allow multiple vesting awards to be given to the same account, are we okay with the restriction that all awards must come from the same source? If so, then we need only keep track of the combined schedule of all vesting events. Otherwise we need to store each schedule individually, plus the combined schedule - not too hard, but I don't want to commit all that storage unless we need to.

@JimLarson
Copy link
Contributor Author

Another option for partial clawback for @dtribble : clawback only has one setting - take all unvested tokens. If you wanted to take less than all, you can restore some by giving a fresh vesting grant with whatever amount and schedule is appropriate. At the very least, this method could delay the urgency for a partial clawback feature.

@JimLarson
Copy link
Contributor Author

JimLarson commented Dec 20, 2021

A note on reward distribution. For slashing, we assume that staked tokens prefer to be vested tokens. For clawback, we prefer to transfer unbonded tokens first, which reflects the same policy. Therefore for reward distribution, we'll prefer to consider the bonded tokens to be the vested tokens first, then assign unvested tokens as necessary, and divide rewards between vested and unvested according to this ratio.

For instance, suppose we have a vesting grant of 400 tokens vesting monthly over 4 years and we are 18 months into the schedule, so 150 have vested. There are 370 tokens total in the account: 210 in the account balance plus 160 staked. The 30 missing tokens could have been vested tokens that were transferred out, or vested tokens that got slashed. We now get a reward of 12 tokens. To distribute the reward, we see that the account has 250 unvested tokens, so 120 of the tokens are vested (= total - unvested). So for the 160 staked tokens, we'll first consider 120 of them (75%) to be vested, then 40 (25%) to be unvested. Therefore we distribute the reward as 9 (75%) tokens vested, 3 (25%) unvested. The unvested tokens will follow a vesting schedule proportional to the remaining vesting schedule for staking tokens: in even amounts over the next 30 months, i.e. 0.1 per month.

@JimLarson
Copy link
Contributor Author

JimLarson commented Dec 20, 2021

Summary of the design.

Vesting Accounts With Clawback

Note on nomenclature: in this spec we'll refer to the encumbrance implemented by other vesting accounts a "lockup". "Vesting" will refer to the new form of encumbrance here that's subject to clawback.

Requirements

  • Grant: A funding account will be able to create a new account with a vesting grant and schedule. Initially, the grant will all be unvested, but over time it will all become vested, according to the schedule. The schedule is specified similar to that of a Periodic Vesting Account (PVA).

  • Lockup: Unvested tokens will not be able to be transferred out of the account to another account, similar to the encumbrance of PVAs.

  • Combined Locking and Vesting: A grant will be able to specify both a vesting and a lockup schedule, to indicate periods of time where coins might be vested (not subject to clawback), but still locked up (not available for transfer).

  • Staking: The account will be able to stake unvested tokens. Note that the delegation command or storage formats do not allow a specification of which particular tokens are staked: vested vs unvested. Therefore we'll need to institute a policy for the disposition of rewards, slashing, and clawback. We will claw back tokens in their staked or unbonding condition rather than forcing immediate unbonding, so that vesting grants don't have to be a privileged operation (otherwise you can grant to yourself and clawback to skip the unbonding period).

  • Clawback: The funder of a true vesting grant will be able to command "clawback" - the transfer of all unvested tokens out of an account. Vested tokens will not be affected. The clawback takes effect at the time of its command - vesting events are irrevocable once they occur. Partial clawback, if desired, can be effected by a total clawback followed by a new grant of the desired net amount.

  • Rewards: Rewards from staked but unvested tokens must also be unvested, and will vest at a similar schedule to the staking tokens that the reward is based on.

  • Incremental grants: The funder may make additional vesting grants to the account which will be merged in with the existing grants. Future grants must come from the same funder - for a single authority for clawback.

  • Destination account: The funder will specify a destination account for clawed-back tokens and staking state to be transferred to. The account will be created as an ordinary account if it does not exist already. It should be a best practice to use a separate destination account for each clawback event.

  • Slashing: Slashing events must be apportioned between vested and unvested amounts.

Design

Implement as a variation of PeriodicVestingAccount (PVE). We've previously extended PVEs to allow incremental awards. Some of the mechanisms needed for that can be reused for true vesting accounts.

Distribution of Rewards, Slashing, and Clawback

We'll prefer to consider staked tokens using the vested tokens first, followed by the unvested tokens. This is preferred for several reasons. As a practical matter, in an account with both vested and unvested tokens, some of which are staked, it is difficult to distinguish the post-slashing state from a state where some vested tokens have been transferred out.

Users who feel disadvantaged by this policy are able to transfer their staked tokens to a different account that's not subject to clawback.

Data structure

Like a PVA, but instead of a single schedule, we store a vesting_periods schedule for the true vesting (subject to clawback), a lockup_periods schedule for pure lockup (not subject to clawback), and a combined_periods schedule for the merge of the two.

The combined_periods is derived from the other schedules. It can be omitted in the genesis data and reconstructed at genesis time.

We'll be merging unvested rewards into these same data structures, which is okay since a Period handles amounts of arbitrary sdk.Coins.

Merging schedules

We've previously implemented a "disjunctive" merge of two schedules to implement incremental grants. The combined schedule vests tokens when they are vested in either of the inputs to the merge. For true vesting plus an independent lockup dimension, we'll implement "conjunctive" merging, where coins are vested in the combined schedule when they're vested in both of the input schedules.

To add a new grant or reward to a true vesting account, with vesting schedule V and lockup schedule L (both for the same total amount of tokens), we modify the account as:

vesting = disjunctMerge(vesting, V)
lockup = disjunctMerge(lockup, L)
combined = conjunctMerge(vesting, lockup)

Rewards

See the above comment in the issue history to see a detailed description of how rewards are apportioned.

Note that we have not yet taken a close look for where to insert this special method into the reward distribution flow.

Clawback

The amount of clawback is the total unvested amount in the account. But some of the account may be staked, and the staking commands do not specify which sort of tokens (vested, unlocked, etc) should be used. To retrieve the clawback amount, we'll prefer to confiscate the least encumbered tokens possible:

  • First, the balance remaining in the account will be transferred, regardless of its lockup state.

  • Second, we'll transfer tokens in the "unbonding"/"undelegating" state by moving whole or partial UnbondingDelegation records. These tokens will show up in the clawback destination account after the unbonding period has passed.

  • Lastly, we'll transfer bonded tokens by moving whole or partial Delegation records. For any transferred Delegation, we may have to transfer some Redelegation records along with it to make sure that each Redelegation entry is associated with a particular set of bonded tokens. Ideally we'd scan for the least encumbered delegations to transfer, but we might not do this in the first implementation.

This means that after the clawback command succeeds, the transferred funds may still be encumbered, may still be subject to future or retroactive slashing, and may require manual action to initiate unbonding. It should be a best practice to use a new destination account for each clawback event for simpler management of the post-clawback state.

If there are not enough tokens available from all sources to fulfill the amount desired for clawback, then the account must have been slashed and the amount clawed back will simply be less than desired. This is an unavoidable outcome of being able to stake unvested tokens.

The vesting schedule will be truncated at the time of clawback so that there are no future vesting events. The unlocking schedule will be capped to the number of vested tokens at time of clawback, so that vested but locked tokens will still unlock in the future.

Interaction with Agoric features

We don't want to allow a line of credit based on unvested tokens. Therefore the lien logic will need to export the number of unvested tokens in the AccountState, and disallow creating a lien against unvested tokens.

The current state of the implementation can be seen at https://github.com/agoric-labs/cosmos-sdk/tree/4085-true-vesting

@JimLarson
Copy link
Contributor Author

Rewards

When you withdraw rewards via the x/distribution module, the reward is the sum of all rewards given since the last withdrawal. In theory, one must compute the vested/unvested split of a reward by looking at the vesting state at the time each reward was issued, and then track what part of the unvested reward amount has become vested at the withdrawal time.

Fortunately, the computation simplifies greatly. Imagine that each end-of-block reward gets attached to every staked token, and the reward vests at the time its staked token vests. Therefore to compute the vested split of an integral of rewards over a time interval, you need only look at the vesting state of staked tokens at the end of the interval. If a token is vested at the end of the interval, it does not matter if it was unvested at the time of the reward.

One complication with this algorithm is our preference to count slashing against vested tokens. Slashing effectively pulls previously unstaked unvested tokens into staking, thus the slashed vested tokens do not get credit for producing vested rewards. However, this effect of undercounting vested rewards is trivial compared to the effect of slashing, and can be arbitrarily minimized by frequent reward withdrawal.

@JimLarson
Copy link
Contributor Author

Note that there's a bug if we try to use the standard vesting account delegation bookkeeping with accounts which allow new grants to be added. (Specifically, when the unvested amount can increase.) See #4300. The bug can lead to premature unlocking of amounts which should be vested. The solution is to track delegated amounts exactly.

@Tartuffo
Copy link
Contributor

@JimLarson This is in the "Up Next" pipeline, but does not have a MN-1 label. If it is needed for MN-1, please label, otherwise move from "Up Next" to "Product Backlog". I'm asking because I want "Up Next" to really reflect the things we plan to work on for MN-1.

@JimLarson JimLarson added the MN-1 label Jan 21, 2022
@JimLarson
Copy link
Contributor Author

@Tartuffo Done. Added an "MN-1" label to github.com/agoric-labs/cosmos-sdk too, and labeled my PRs there as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
agoric-cosmos enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants