addressed comments, added formulas for easy verification
This commit is contained in:
parent
d4d7658166
commit
10b2e830a2
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
### Intro and Requirements
|
### Intro and Requirements
|
||||||
|
|
||||||
This paper specifies changes to the auth and bank modules to implement vesting accounts for the Cosmos Hub.
|
This paper specifies vesting account implementation for the Cosmos Hub.
|
||||||
The requirements for this vesting account is that it should be capable of being initialized during genesis with
|
The requirements for this vesting account is that it should be initialized during genesis with
|
||||||
a starting balance X coins and a vesting blocktime T. The owner of this account should be able to delegate to validators
|
a starting balance X coins and a vesting endtime T. The owner of this account should be able to delegate to validators
|
||||||
and vote with locked coins, however they cannot send locked coins to other accounts until those coins have been unlocked.
|
and vote with locked coins, however they cannot send locked coins to other accounts until those coins have been unlocked.
|
||||||
The vesting account should also be able to spend any coins it receives from other users or from fees/inflation rewards.
|
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
|
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.
|
unlocked coin amount.
|
||||||
|
|
||||||
|
@ -14,35 +14,40 @@ unlocked coin amount.
|
||||||
|
|
||||||
##### Vesting Account implementation
|
##### Vesting Account implementation
|
||||||
|
|
||||||
|
NOTE: `Now = ctx.BlockHeader().Time`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type VestingAccount interface {
|
type VestingAccount interface {
|
||||||
Account
|
Account
|
||||||
AssertIsVestingAccount() // existence implies that account is vesting.
|
AssertIsVestingAccount() // existence implies that account is vesting.
|
||||||
|
ConvertAccount(sdk.Context) BaseAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements Vesting Account
|
// Implements Vesting Account
|
||||||
// Continuously vests by unlocking coins linearly with respect to time
|
// Continuously vests by unlocking coins linearly with respect to time
|
||||||
type ContinuousVestingAccount struct {
|
type ContinuousVestingAccount struct {
|
||||||
BaseAccount
|
BaseAccount
|
||||||
OriginalCoins sdk.Coins
|
OriginalCoins sdk.Coins // Coins in account on Initialization
|
||||||
ReceivedCoins sdk.Coins
|
ReceivedCoins sdk.Coins // Coins received from other accounts
|
||||||
|
|
||||||
|
// StartTime and EndTime used to calculate how much of OriginalCoins is unlocked at any given point
|
||||||
StartTime int64
|
StartTime int64
|
||||||
EndTime int64
|
EndTime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vacc ContinuousVestingAccount) ConvertAccount() BaseAccount {
|
ConvertAccount(vacc ContinuousVestingAccount) (BaseAccount):
|
||||||
if T > vacc.EndTime {
|
if Now > vacc.EndTime then // Convert to BaseAccount
|
||||||
// Convert to BaseAccount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `VestingAccount` interface is used purely to assert that an account is a vesting account like so:
|
The `VestingAccount` interface is used to assert that an account is a vesting account like so:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
vacc, ok := acc.(VestingAccount); ok
|
vacc, ok := acc.(VestingAccount); ok
|
||||||
```
|
```
|
||||||
|
|
||||||
|
as well as to convert to BaseAccount again once the account has fully vested.
|
||||||
|
|
||||||
The `ContinuousVestingAccount` struct implements the Vesting account interface. It uses `OriginalCoins`, `ReceivedCoins`,
|
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,
|
`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.
|
the next `bank.MsgSend` will convert the account into a `BaseAccount` and store it in state as such from that point on.
|
||||||
|
@ -54,35 +59,78 @@ the `Account` interface exactly like `BaseAccount`.
|
||||||
Since a vesting account should be capable of doing everything but sending with its locked coins, the restriction should be
|
Since a vesting account should be capable of doing everything but sending with its locked coins, the restriction should be
|
||||||
handled at the `bank.Keeper` level. Specifically in methods that are explicitly used for sending like
|
handled at the `bank.Keeper` level. Specifically in methods that are explicitly used for sending like
|
||||||
`sendCoins` and `inputOutputCoins`. These methods must check that an account is a vesting account using the check described above.
|
`sendCoins` and `inputOutputCoins`. These methods must check that an account is a vesting account using the check described above.
|
||||||
NOTE: `Now = ctx.BlockHeader().Time`
|
|
||||||
|
|
||||||
1. If `Now < vacc.EndTime`
|
```go
|
||||||
1. Calculate `SendableCoins := ReceivedCoins + OriginalCoins * (Now - StartTime)/(EndTime - StartTime))`
|
if Now < vacc.EndTime:
|
||||||
- NOTE: `SendableCoins` may be greater than total coins in account. This is because coins can be subtracted by staking module.
|
// 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 right now.
|
// SendableCoins denotes maximum coins allowed to be spent.
|
||||||
2. If `msg.Amount > SendableCoins`, return sdk.Error. Else, allow transaction to process normally.
|
SendableCoins := ReceivedCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime)
|
||||||
2. Else:
|
if msg.Amount > SendableCoins then fail
|
||||||
1. Convert account to `BaseAccount` and process normally.
|
|
||||||
|
|
||||||
Coins that are sent to a vesting account after initialization either through users sending them coins or through fees/inflation rewards
|
else: account = ConvertAccount(account) // Account fully vested, convert to BaseAccount
|
||||||
should be spendable immediately after receiving them. Thus, handlers (like staking or bank) that send coins that a vesting account did not
|
|
||||||
|
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
|
||||||
originally own should increment `ReceivedCoins` by the amount sent.
|
originally own should increment `ReceivedCoins` by the amount sent.
|
||||||
|
|
||||||
WARNING: Handlers SHOULD NOT update `ReceivedCoins` if they were originally sent from the vesting account. For example, if a vesting account
|
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.
|
||||||
unbonds from a validator, their tokens should be added back to account but `ReceivedCoins` SHOULD NOT be incremented.
|
|
||||||
However when the staking handler is handing out fees or inflation rewards, then `ReceivedCoins` SHOULD be incremented.
|
However when the staking handler is handing out fees or inflation rewards, then `ReceivedCoins` SHOULD be incremented.
|
||||||
|
|
||||||
### Initializing at Genesis
|
### Initializing at Genesis
|
||||||
|
|
||||||
To initialize both vesting accounts and base accounts, the `GenesisAccount` struct will be:
|
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.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type GenesisAccount struct {
|
type GenesisAccount struct {
|
||||||
Address sdk.AccAddress `json:"address"`
|
Address sdk.AccAddress `json:"address"`
|
||||||
Coins sdk.Coins `json:"coins"`
|
GenesisCoins sdk.Coins `json:"coins"`
|
||||||
EndTime int64 `json:"lock"`
|
EndTime int64 `json:"lock"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
During `InitChain`, the GenesisAccounts are decoded. If `EndTime == 0`, a BaseAccount gets created and put in Genesis state.
|
### Formulas
|
||||||
Otherwise a vesting account is created with `StartTime = RequestInitChain.Time`, `EndTime = gacc.EndTime`, and `OriginalCoins = Coins`.
|
|
||||||
|
`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 )`
|
||||||
|
|
Loading…
Reference in New Issue