New idea for spec

This commit is contained in:
Aditya Sripal 2018-07-31 18:11:19 -07:00
parent 41130f8742
commit d4d7658166
1 changed files with 59 additions and 70 deletions

View File

@ -2,98 +2,87 @@
### Intro and Requirements ### Intro and Requirements
This paper specifies changes to the auth and bank modules to implement vested accounts for the Cosmos Hub. This paper specifies changes to the auth and bank modules to implement vesting accounts for the Cosmos Hub.
The requirements for this vested account is that it should be capable of being initialized during genesis with The requirements for this vesting account is that it should be capable of being 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 and vote, a starting balance X coins and a vesting blocktime T. The owner of this account should be able to delegate to validators
however they cannot send their coins to other accounts until the account has fully vested. Thus, the bank module's `MsgSend` handler and vote with locked coins, however they cannot send locked coins to other accounts until those coins have been unlocked.
should error if a vested account is trying to send an amount before time T. The vesting account should also be able to spend any coins it receives from other users or from fees/inflation rewards.
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.
### Implementation ### Implementation
##### Changes to x/auth Module ##### Vesting Account implementation
The `Account` interface will specify both the Account type and any parameters it needs.
```go ```go
// Account is a standard account using a sequence number for replay protection type VestingAccount interface {
// and a pubkey for authentication. Account
type Account interface { AssertIsVestingAccount() // existence implies that account is vesting.
Type() string // returns the type of the account }
GetAddress() sdk.AccAddress // Implements Vesting Account
SetAddress(sdk.AccAddress) error // errors if already set. // Continuously vests by unlocking coins linearly with respect to time
type ContinuousVestingAccount struct {
BaseAccount
OriginalCoins sdk.Coins
ReceivedCoins sdk.Coins
StartTime int64
EndTime int64
}
GetPubKey() crypto.PubKey // can return nil. func (vacc ContinuousVestingAccount) ConvertAccount() BaseAccount {
SetPubKey(crypto.PubKey) error if T > vacc.EndTime {
// Convert to BaseAccount
GetAccountNumber() int64 }
SetAccountNumber(int64) error
GetSequence() int64
SetSequence(int64) error
GetCoins() sdk.Coins
SetCoins(sdk.Coins) error
// Getter and setter methods for account params
// Parameters can be understood to be a map[string]interface{} with encoded keys and vals in store
// It is upto handler to use these appropriately
GetParams([]byte) []byte
SetParams([]byte, []byte) error
} }
``` ```
The `Type` method will allow handlers to determine what type of account is sending the message, and the The `VestingAccount` interface is used purely to assert that an account is a vesting account like so:
handler can then call `GetParams` to handle the specific account type using the parameters it expects to
exist in the parameter map.
The `VestedAccount` will be an implementation of `Account` interface that wraps `BaseAccount` with ```go
`Type() => "vested` and params, `GetParams() => {"TimeLock": N (int64)}`. vacc, ok := acc.(VestingAccount); ok
`SetParams` will be disabled as we do not want to update params after vested account initialization. ```
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`.
`auth.AccountMapper` will be modified handle vested accounts as well. Specific changes ##### Changes to Keepers/Handler
are omitted in this doc for succinctness.
Since a vesting account should be capable of doing everything but sending with its locked coins, the restriction should be
##### Changes to bank MsgSend Handler
Since a vested account should be capable of doing everything but sending, 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 an account's `Type` method; if it is a vested `sendCoins` and `inputOutputCoins`. These methods must check that an account is a vesting account using the check described above.
account (i.e. `acc.Type() == "vested"`): NOTE: `Now = ctx.BlockHeader().Time`
1. Check if `ctx.BlockHeader().Time < acc.GetParams()["BlockLock"]` 1. If `Now < vacc.EndTime`
2. If `true`, the account is still vesting, return sdk.Error. Else, allow transaction to be processed as normal. 1. Calculate `SendableCoins := ReceivedCoins + OriginalCoins * (Now - StartTime)/(EndTime - StartTime))`
- NOTE: `SendableCoins` may be greater than total coins in account. This is because coins can be subtracted by staking module.
`SendableCoins` denotes maximum coins allowed to be spent right now.
2. If `msg.Amount > SendableCoins`, return sdk.Error. Else, allow transaction to process normally.
2. Else:
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
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.
WARNING: 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.
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 vested accounts and base accounts, the `GenesisAccount` struct will be: To initialize both vesting accounts and base accounts, the `GenesisAccount` struct will be:
```go ```go
type GenesisAccount struct { type GenesisAccount struct {
Address sdk.AccAddress `json:"address"` Address sdk.AccAddress `json:"address"`
Coins sdk.Coins `json:"coins"` Coins sdk.Coins `json:"coins"`
Type string `json:"type"` EndTime int64 `json:"lock"`
TimeLock int64 `json:"lock"`
} }
``` ```
During `InitChain`, the GenesisAccount's are decoded. If they have `Type == "vested`, a vested account with parameters => During `InitChain`, the GenesisAccounts are decoded. If `EndTime == 0`, a BaseAccount gets created and put in Genesis state.
`{"TimeLock": N}` gets created and put in initial state. Otherwise if `Type == "base"` a base account is created Otherwise a vesting account is created with `StartTime = RequestInitChain.Time`, `EndTime = gacc.EndTime`, and `OriginalCoins = Coins`.
and the `TimeLock` attribute of corresponding `GenesisAccount` is ignored. `InitChain` will panic on any other account types.
### Pros and Cons
##### Pros
- Easily Extensible. If more account types need to get added in the future or if developers building on top of SDK
want to handle multiple custom account types, they simply have to implement the `Account` interface with unique `Type`
and their custom parameters.
- Handlers (and their associated keepers) get to determine what types of accounts they will handle and can use the parameters
in Account interface to handle different accounts appropriately.
##### Cons
- Changes to `Account` interface
- Slightly more complex code in `bank.Keeper` functions