2018-07-27 18:35:21 -07:00
## Vesting
### Intro and Requirements
2018-08-03 13:12:34 -07:00
This paper specifies vesting account implementation for the Cosmos Hub.
The requirements for this vesting account is that it should be initialized during genesis with
a starting balance X coins and a vesting endtime T. The owner of this account should be able to delegate to validators
2018-07-31 18:11:19 -07:00
and vote with locked coins, however they cannot send locked coins to other accounts until those coins have been unlocked.
2018-08-03 13:12:34 -07:00
The vesting account should also be able to spend any coins it receives from other users.
2018-07-31 18:11:19 -07:00
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-07-27 18:35:21 -07:00
### Implementation
2018-07-31 18:11:19 -07:00
##### Vesting Account implementation
2018-07-27 18:35:21 -07:00
2018-08-03 13:12:34 -07:00
NOTE: `Now = ctx.BlockHeader().Time`
2018-07-27 18:35:21 -07:00
```go
2018-07-31 18:11:19 -07:00
type VestingAccount interface {
Account
AssertIsVestingAccount() // existence implies that account is vesting.
2018-08-03 13:12:34 -07:00
ConvertAccount(sdk.Context) BaseAccount
2018-07-27 18:35:21 -07:00
}
2018-07-31 18:11:19 -07:00
// Implements Vesting Account
// Continuously vests by unlocking coins linearly with respect to time
type ContinuousVestingAccount struct {
BaseAccount
2018-08-03 13:12:34 -07:00
OriginalCoins sdk.Coins // Coins in account on Initialization
ReceivedCoins sdk.Coins // Coins received from other accounts
// StartTime and EndTime used to calculate how much of OriginalCoins is unlocked at any given point
2018-07-31 18:11:19 -07:00
StartTime int64
EndTime int64
}
2018-07-27 18:35:21 -07:00
2018-08-03 13:12:34 -07:00
ConvertAccount(vacc ContinuousVestingAccount) (BaseAccount):
if Now > vacc.EndTime then // Convert to BaseAccount
2018-07-31 18:11:19 -07:00
```
2018-07-27 18:35:21 -07:00
2018-08-03 13:12:34 -07:00
The `VestingAccount` interface is used to assert that an account is a vesting account like so:
2018-07-27 18:35:21 -07:00
2018-07-31 18:11:19 -07:00
```go
vacc, ok := acc.(VestingAccount); ok
```
2018-07-27 18:35:21 -07:00
2018-08-03 13:12:34 -07:00
as well as to convert to BaseAccount again once the account has fully vested.
2018-07-31 18:11:19 -07:00
The `ContinuousVestingAccount` struct implements the Vesting account interface. It uses `OriginalCoins` , `ReceivedCoins` ,
`StartTime` , and `EndTime` to calculate how many coins are sendable at any given point. Once the account has fully vested,
the next `bank.MsgSend` will convert the account into a `BaseAccount` and store it in state as such from that point on.
Since the vesting restrictions need to be implemented on a per-module basis, the `ContinuouosVestingAccount` implements
the `Account` interface exactly like `BaseAccount` .
2018-07-27 18:35:21 -07:00
2018-07-31 18:11:19 -07:00
##### Changes to Keepers/Handler
2018-07-27 18:35:21 -07:00
2018-07-31 18:11:19 -07:00
Since a vesting account should be capable of doing everything but sending with its locked coins, the restriction should be
2018-07-27 18:35:21 -07:00
handled at the `bank.Keeper` level. Specifically in methods that are explicitly used for sending like
2018-07-31 18:11:19 -07:00
`sendCoins` and `inputOutputCoins` . These methods must check that an account is a vesting account using the check described above.
2018-08-03 13:12:34 -07:00
```go
if Now < vacc.EndTime:
// NOTE: SendableCoins may be greater than total coins in account because coins can be subtracted by staking module
// SendableCoins denotes maximum coins allowed to be spent.
SendableCoins := ReceivedCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime)
if msg.Amount > SendableCoins then fail
else: account = ConvertAccount(account) // Account fully vested, convert to BaseAccount
if msg.Amount > account.GetCoins() then fail // Must still check if account has enough coins, since SendableCoins does not check this.
// All checks passed, send the coins
SendCoins(inputs, outputs)
```
Coins that are sent to a vesting account after initialization by users sending them coins should be spendable
immediately after receiving them. Thus, handlers (like staking or bank) that send coins that a vesting account did not
2018-07-31 18:11:19 -07:00
originally own should increment `ReceivedCoins` by the amount sent.
2018-08-03 13:12:34 -07:00
CONTRACT: Handlers SHOULD NOT update `ReceivedCoins` if they were originally sent from the vesting account. For example, if a vesting account unbonds from a validator, their tokens should be added back to account but `ReceivedCoins` SHOULD NOT be incremented.
2018-07-31 18:11:19 -07:00
However when the staking handler is handing out fees or inflation rewards, then `ReceivedCoins` SHOULD be incremented.
2018-07-27 18:35:21 -07:00
### Initializing at Genesis
2018-08-03 13:12:34 -07:00
To initialize both vesting accounts and base accounts, the `GenesisAccount` struct will include an EndTime. Accounts meant to be
BaseAccounts will have `EndTime = 0` . The `initChainer` method will parse the GenesisAccount into BaseAccounts and VestingAccounts
as appropriate.
2018-07-29 19:29:54 -07:00
```go
type GenesisAccount struct {
2018-08-03 13:12:34 -07:00
Address sdk.AccAddress `json:"address"`
GenesisCoins sdk.Coins `json:"coins"`
EndTime int64 `json:"lock"`
2018-07-29 19:29:54 -07:00
}
2018-08-03 13:12:34 -07:00
initChainer:
for genesis_acc in GenesisAccounts:
if EndTime == 0 then // Create BaseAccount
else:
vesting_account = ContinuouslyVestingAccount{
OriginalCoins: GenesisCoins,
StartTime: RequestInitChain.Time,
EndTime: EndTime,
}
// Add account to initial state
2018-07-29 19:29:54 -07:00
```
2018-08-03 13:12:34 -07:00
### Formulas
`OriginalCoins` : Amount of coins in account at Genesis
`CurrentCoins` : Coins currently in the baseaccount (both locked and unlocked)
`ReceivedCoins` : Coins received from other accounts (always unlocked)
`LockedCoins` : Coins that are currently locked
`Delegated` : Coins that have been delegated (no longer in account; may be locked or unlocked)
`Sent` : Coins sent to other accounts (MUST be unlocked)
Maximum amount of coins vesting schedule allows to be sent:
`ReceivedCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime)`
`ReceivedCoins + OriginalCoins - LockedCoins`
Coins currently in Account:
`CurrentCoins = OriginalCoins + ReceivedCoins - Delegated - Sent`
**Maximum amount of coins spendable right now:**
`min( ReceivedCoins + OriginalCoins - LockedCoins, CurrentCoins )`