cosmos-sdk/docs/spec/auth/vesting.md

435 lines
13 KiB
Markdown
Raw Normal View History

2018-11-01 15:13:40 -07:00
# Vesting
- [Vesting](#vesting)
- [Intro and Requirements](#intro-and-requirements)
- [Vesting Account Types](#vesting-account-types)
- [Vesting Account Specification](#vesting-account-specification)
- [Determining Vesting & Vested Amounts](#determining-vesting--vested-amounts)
- [Continuously Vesting Accounts](#continuously-vesting-accounts)
- [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts)
- [Transferring/Sending](#transferringsending)
- [Keepers/Handlers](#keepershandlers)
2018-11-01 15:13:40 -07:00
- [Delegating](#delegating)
- [Keepers/Handlers](#keepershandlers-1)
2018-11-01 15:13:40 -07:00
- [Undelegating](#undelegating)
- [Keepers/Handlers](#keepershandlers-2)
2018-11-01 15:13:40 -07:00
- [Keepers & Handlers](#keepers--handlers)
- [Initializing at Genesis](#initializing-at-genesis)
- [Examples](#examples)
- [Simple](#simple)
- [Slashing](#slashing)
- [Glossary](#glossary)
## Intro and Requirements
This specification describes the vesting account implementation for the Cosmos Hub.
2018-11-01 15:13:40 -07:00
The requirements for this vesting account is that it should be initialized
during genesis with a starting balance `X` and a vesting end time `T`.
2018-11-01 15:13:40 -07:00
The owner of this account should be able to delegate to and undelegate from
validators, however they cannot send locked coins to other accounts until those
coins have been fully vested.
2018-11-01 15:13:40 -07:00
In addition, a vesting account vests all of its coin denominations at the same
rate. This may be subject to change.
**Note**: A vesting account could have some vesting and non-vesting coins. To
support such a feature, the `GenesisAccount` type will need to be updated in
order to make such a distinction.
## Vesting Account Types
2018-07-27 18:35:21 -07:00
```go
2018-11-01 15:13:40 -07:00
// VestingAccount defines an interface that any vesting account type must
// implement.
2018-07-31 18:11:19 -07:00
type VestingAccount interface {
Account
2018-11-01 15:13:40 -07:00
GetVestedCoins(Time) Coins
GetVestingCoins(Time) Coins
// Delegation and undelegation accounting that returns the resulting base
// coins amount.
TrackDelegation(Time, Coins)
TrackUndelegation(Coins)
2018-11-01 15:13:40 -07:00
}
// BaseVestingAccount implements the VestingAccount interface. It contains all
// the necessary fields needed for any vesting account implementation.
type BaseVestingAccount struct {
BaseAccount
2018-08-07 17:02:28 -07:00
2018-11-01 15:13:40 -07:00
OriginalVesting Coins // coins in account upon initialization
DelegatedFree Coins // coins that are vested and delegated
DelegatedVesting Coins // coins that vesting and delegated
EndTime Time // when the coins become unlocked
2018-07-27 18:35:21 -07:00
}
2018-11-01 15:13:40 -07:00
// ContinuousVestingAccount implements the VestingAccount interface. It
// continuously vests by unlocking coins linearly with respect to time.
2018-07-31 18:11:19 -07:00
type ContinuousVestingAccount struct {
2018-11-01 15:13:40 -07:00
BaseVestingAccount
StartTime Time // when the coins start to vest
2018-07-31 18:11:19 -07:00
}
2018-07-27 18:35:21 -07:00
2018-11-01 15:13:40 -07:00
// DelayedVestingAccount implements the VestingAccount interface. It vests all
// coins after a specific time, but non prior. In other words, it keeps them
// locked until a specified time.
type DelayedVestingAccount struct {
BaseVestingAccount
}
```
2018-08-15 13:54:03 -07:00
In order to facilitate less ad-hoc type checking and assertions and to support
flexibility in account usage, the existing `Account` interface is updated to contain
the following:
```go
type Account interface {
// ...
// Calculates the amount of coins that can be sent to other accounts given
// the current time.
SpendableCoins(Time) Coins
}
```
2018-11-01 15:13:40 -07:00
## Vesting Account Specification
2018-08-15 13:54:03 -07:00
2018-11-01 15:13:40 -07:00
Given a vesting account, we define the following in the proceeding operations:
2018-11-01 15:13:40 -07:00
- `OV`: The original vesting coin amount. It is a constant value.
- `V`: The number of `OV` coins that are still _vesting_. It is derived by `OV`, `StartTime` and `EndTime`. This value is computed on demand and not on a per-block basis.
- `V'`: The number of `OV` coins that are _vested_ (unlocked). This value is computed on demand and not a per-block basis.
- `DV`: The number of delegated _vesting_ coins. It is a variable value. It is stored and modified directly in the vesting account.
- `DF`: The number of delegated _vested_ (unlocked) coins. It is a variable value. It is stored and modified directly in the vesting account.
- `BC`: The number of `OV` coins less any coins that are transferred (which can be negative or delegated). It is considered to be balance of the embedded base account. It is stored and modified directly in the vesting account.
2018-11-01 15:13:40 -07:00
### Determining Vesting & Vested Amounts
It is important to note that these values are computed on demand and not on a
mandatory per-block basis (e.g. `BeginBlocker` or `EndBlocker`).
2018-11-01 15:13:40 -07:00
#### Continuously Vesting Accounts
To determine the amount of coins that are vested for a given block time `T`, the
2018-11-01 15:13:40 -07:00
following is performed:
1. Compute `X := T - StartTime`
2018-11-01 15:13:40 -07:00
2. Compute `Y := EndTime - StartTime`
3. Compute `V' := OV * (X / Y)`
4. Compute `V := OV - V'`
Thus, the total amount of _vested_ coins is `V'` and the remaining amount, `V`,
is _vesting_.
```go
func (cva ContinuousVestingAccount) GetVestedCoins(t Time) Coins {
2018-11-01 15:13:40 -07:00
// We must handle the case where the start time for a vesting account has
// been set into the future or when the start of the chain is not exactly
// known.
if t <= va.StartTime {
2018-11-01 15:13:40 -07:00
return ZeroCoins
}
x := t - cva.StartTime
2018-11-01 15:13:40 -07:00
y := cva.EndTime - cva.StartTime
return cva.OriginalVesting * (x / y)
}
func (cva ContinuousVestingAccount) GetVestingCoins(t Time) Coins {
return cva.OriginalVesting - cva.GetVestedCoins(t)
2018-11-01 15:13:40 -07:00
}
2018-07-31 18:11:19 -07:00
```
2018-07-27 18:35:21 -07:00
2018-11-01 15:13:40 -07:00
#### Delayed/Discrete Vesting Accounts
Delayed vesting accounts are easier to reason about as they only have the full
amount vesting up until a certain time, then all the coins become vested (unlocked).
This does not include any unlocked coins the account may have initially.
2018-07-27 18:35:21 -07:00
2018-07-31 18:11:19 -07:00
```go
func (dva DelayedVestingAccount) GetVestedCoins(t Time) Coins {
if t >= dva.EndTime {
2018-11-01 15:13:40 -07:00
return dva.OriginalVesting
}
return ZeroCoins
}
func (dva DelayedVestingAccount) GetVestingCoins(t Time) Coins {
return dva.OriginalVesting - dva.GetVestedCoins(t)
2018-11-01 15:13:40 -07:00
}
2018-07-31 18:11:19 -07:00
```
2018-07-27 18:35:21 -07:00
2018-11-01 15:13:40 -07:00
### Transferring/Sending
At any given time, a vesting account may transfer: `min((BC + DV) - V, BC)`.
2018-07-27 18:35:21 -07:00
2018-11-01 15:13:40 -07:00
In other words, a vesting account may transfer the minimum of the base account
balance and the base account balance plus the number of currently delegated
vesting coins less the number of coins vested so far.
```go
func (va VestingAccount) SpendableCoins(t Time) Coins {
bc := va.GetCoins()
return min((bc + va.DelegatedVesting) - va.GetVestingCoins(t), bc)
2018-11-01 15:13:40 -07:00
}
```
#### Keepers/Handlers
2018-07-31 18:11:19 -07:00
2018-11-01 15:13:40 -07:00
The corresponding `x/bank` keeper should appropriately handle sending coins
based on if the account is a vesting account or not.
2018-07-27 18:35:21 -07:00
2018-11-01 15:13:40 -07:00
```go
func SendCoins(t Time, from Account, to Account, amount Coins) {
bc := from.GetCoins()
2018-11-01 15:13:40 -07:00
if isVesting(from) {
sc := from.SpendableCoins(t)
assert(amount <= sc)
2018-11-01 15:13:40 -07:00
}
newCoins := bc - amount
assert(newCoins >= 0)
from.SetCoins(bc - amount)
to.SetCoins(amount)
// save accounts...
2018-11-01 15:13:40 -07:00
}
```
2018-07-27 18:35:21 -07:00
2018-11-01 15:13:40 -07:00
### Delegating
For a vesting account attempting to delegate `D` coins, the following is performed:
2018-11-01 15:13:40 -07:00
1. Verify `BC >= D > 0`
2. Compute `X := min(max(V - DV, 0), D)` (portion of `D` that is vesting)
3. Compute `Y := D - X` (portion of `D` that is free)
4. Set `DV += X`
5. Set `DF += Y`
6. Set `BC -= D`
2018-07-29 19:29:54 -07:00
```go
func (va VestingAccount) TrackDelegation(t Time, amount Coins) {
x := min(max(va.GetVestingCoins(t) - va.DelegatedVesting, 0), amount)
2018-11-01 15:13:40 -07:00
y := amount - x
va.DelegatedVesting += x
va.DelegatedFree += y
va.SetCoins(va.GetCoins() - amount)
2018-07-29 19:29:54 -07:00
}
2018-11-01 15:13:40 -07:00
```
#### Keepers/Handlers
2018-11-01 15:13:40 -07:00
```go
func DelegateCoins(t Time, from Account, amount Coins) {
bc := from.GetCoins()
assert(amount <= bc)
2018-11-01 15:13:40 -07:00
if isVesting(from) {
from.TrackDelegation(t, amount)
2018-11-01 15:13:40 -07:00
} else {
from.SetCoins(sc - amount)
2018-11-01 15:13:40 -07:00
}
// save account...
2018-11-01 15:13:40 -07:00
}
2018-07-29 19:29:54 -07:00
```
2018-11-01 15:13:40 -07:00
### Undelegating
For a vesting account attempting to undelegate `D` coins, the following is performed:
2018-11-01 15:13:40 -07:00
1. Verify `(DV + DF) >= D > 0` (this is simply a sanity check)
2. Compute `X := min(DF, D)` (portion of `D` that should become free, prioritizing free coins)
3. Compute `Y := D - X` (portion of `D` that should remain vesting)
4. Set `DF -= X`
5. Set `DV -= Y`
2018-11-01 15:13:40 -07:00
6. Set `BC += D`
2018-11-01 15:13:40 -07:00
```go
func (cva ContinuousVestingAccount) TrackUndelegation(amount Coins) {
x := min(cva.DelegatedFree, amount)
y := amount - x
cva.DelegatedFree -= x
cva.DelegatedVesting -= y
cva.SetCoins(cva.GetCoins() + amount)
2018-11-01 15:13:40 -07:00
}
```
2018-11-01 15:13:40 -07:00
**Note**: If a delegation is slashed, the continuous vesting account will end up
with an excess `DV` amount, even after all its coins have vested. This is because
2018-11-01 15:13:40 -07:00
undelegating free coins are prioritized.
#### Keepers/Handlers
2018-11-01 15:13:40 -07:00
```go
func UndelegateCoins(to Account, amount Coins) {
if isVesting(to) {
if to.DelegatedFree + to.DelegatedVesting >= amount {
to.TrackUndelegation(amount)
// save account ...
}
} else {
AddCoins(to, amount)
// save account...
}
}
```
## Keepers & Handlers
The `VestingAccount` implementations reside in `x/auth`. However, any keeper in
2019-01-11 12:08:01 -08:00
a module (e.g. staking in `x/staking`) wishing to potentially utilize any vesting
2018-11-01 15:13:40 -07:00
coins, must call explicit methods on the `x/bank` keeper (e.g. `DelegateCoins`)
opposed to `SendCoins` and `SubtractCoins`.
In addition, the vesting account should also be able to spend any coins it
receives from other users. Thus, the bank module's `MsgSend` handler should
error if a vesting account is trying to send an amount that exceeds their
unlocked coin amount.
2018-11-01 15:13:40 -07:00
See the above specification for full implementation details.
2018-08-07 17:02:28 -07:00
2018-11-01 15:13:40 -07:00
## Initializing at Genesis
To initialize both vesting and base accounts, the `GenesisAccount` struct will
include an `EndTime`. Accounts meant to be of type `BaseAccount` will
2018-11-01 15:13:40 -07:00
have `EndTime = 0`. The `initChainer` method will parse the GenesisAccount into
BaseAccounts and VestingAccounts as appropriate.
```go
type GenesisAccount struct {
Address sdk.AccAddress
GenesisCoins sdk.Coins
EndTime int64
StartTime int64
2018-11-01 15:13:40 -07:00
}
func initChainer() {
for genAcc in GenesisAccounts {
baseAccount := BaseAccount{
Address: genAcc.Address,
Coins: genAcc.GenesisCoins,
}
if genAcc.StartTime != 0 && genAcc.EndTime != 0 {
2018-11-01 15:13:40 -07:00
vestingAccount := ContinuousVestingAccount{
BaseAccount: baseAccount,
OriginalVesting: genAcc.GenesisCoins,
StartTime: RequestInitChain.Time,
StartTime: genAcc.StartTime,
EndTime: genAcc.EndTime,
}
AddAccountToState(vestingAccount)
} else if genAcc.EndTime != 0 {
vestingAccount := DelayedVestingAccount{
BaseAccount: baseAccount,
OriginalVesting: genAcc.GenesisCoins,
2018-11-01 15:13:40 -07:00
EndTime: genAcc.EndTime,
}
AddAccountToState(vestingAccount)
} else {
AddAccountToState(baseAccount)
}
}
}
```
## Examples
### Simple
Given a continuous vesting account with 10 vesting coins.
```
OV = 10
DF = 0
DV = 0
BC = 10
V = 10
V' = 0
```
2018-11-01 15:13:40 -07:00
1. Immediately receives 1 coin
```text
2018-11-01 15:13:40 -07:00
BC = 11
```
2. Time passes, 2 coins vest
```text
2018-11-01 15:13:40 -07:00
V = 8
V' = 2
```
3. Delegates 4 coins to validator A
```text
2018-11-01 15:13:40 -07:00
DV = 4
BC = 7
```
4. Sends 3 coins
```text
2018-11-01 15:13:40 -07:00
BC = 4
```
5. More time passes, 2 more coins vest
```text
2018-11-01 15:13:40 -07:00
V = 6
V' = 4
```
6. Sends 2 coins. At this point the account cannot send anymore until further coins vest or it receives additional coins. It can still however, delegate.
```text
2018-11-01 15:13:40 -07:00
BC = 2
```
### Slashing
Same initial starting conditions as the simple example.
1. Time passes, 5 coins vest
```text
2018-11-01 15:13:40 -07:00
V = 5
V' = 5
```
2. Delegate 5 coins to validator A
```text
2018-11-01 15:13:40 -07:00
DV = 5
BC = 5
```
3. Delegate 5 coins to validator B
```text
2018-11-01 15:13:40 -07:00
DF = 5
BC = 0
```
4. Validator A gets slashed by 50%, making the delegation to A now worth 2.5 coins
5. Undelegate from validator A (2.5 coins)
```text
2018-11-01 15:13:40 -07:00
DF = 5 - 2.5 = 2.5
BC = 0 + 2.5 = 2.5
```
6. Undelegate from validator B (5 coins). The account at this point can only send 2.5 coins unless it receives more coins or until more coins vest. It can still however, delegate.
```text
2018-11-01 15:13:40 -07:00
DV = 5 - 2.5 = 2.5
DF = 2.5 - 2.5 = 0
BC = 2.5 + 5 = 7.5
```
Notice how we have an excess amount of `DV`.
2018-11-01 15:13:40 -07:00
## Glossary
- OriginalVesting: The amount of coins (per denomination) that are initially part of a vesting account. These coins are set at genesis.
- StartTime: The BFT time at which a vesting account starts to vest.
- EndTime: The BFT time at which a vesting account is fully vested.
- DelegatedFree: The tracked amount of coins (per denomination) that are delegated from a vesting account that have been fully vested at time of delegation.
- DelegatedVesting: The tracked amount of coins (per denomination) that are delegated from a vesting account that were vesting at time of delegation.
- ContinuousVestingAccount: A vesting account implementation that vests coins linearly over time.
- DelayedVestingAccount: A vesting account implementation that only fully vests all coins at a given time.