From 3445a85aad5c595a7e2d666950fdba53d19255b3 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Fri, 27 Jul 2018 18:35:21 -0700 Subject: [PATCH 01/14] initial progress on vesting spec --- docs/spec/auth/vesting.md | 88 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/spec/auth/vesting.md diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md new file mode 100644 index 0000000000..0515953573 --- /dev/null +++ b/docs/spec/auth/vesting.md @@ -0,0 +1,88 @@ +## Vesting + +### Intro and Requirements + +This paper specifies changes to the auth and bank modules to implement vested accounts for the Cosmos Hub. +The requirements for this vested account is that it should be capable of being initialized during genesis with +a starting balance X and a vesting blocknumber N. The owner of this account should be able to delegate to validators, +but they cannot send their initial coins to other accounts. However; funds sent to this account, or fees and +inflation rewards from delegation should be spendable. Thus, the bank module's MsgSend handler should error if +a vested account is trying to send an amount `x > currentBalance - initialBalance` before block N. + +### Implementation + +##### Changes to x/auth Module + +The first change is to the Account interface to specify both the Account type and any parameters it needs. + +```go +// Account is a standard account using a sequence number for replay protection +// and a pubkey for authentication. +type Account interface { + Type() string // returns the type of the account + + GetAddress() sdk.AccAddress + SetAddress(sdk.AccAddress) error // errors if already set. + + GetPubKey() crypto.PubKey // can return nil. + SetPubKey(crypto.PubKey) error + + GetAccountNumber() int64 + SetAccountNumber(int64) error + + GetSequence() int64 + SetSequence(int64) error + + GetCoins() sdk.Coins + SetCoins(sdk.Coins) error + + // Getter and setter methods for account params + // It is upto handler to use these appropriately + GetParams() map[string]interface{} + SetParams(map[string]interface{}) error +} +``` + +The `Type` method will allow handlers to determine what type of account is sending the message, and the +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 +`Type() => "vested` and params, `GetParams() => {"Funds": initialBalance (sdk.Coins), "BlockLock": blockN (int64)}`. +`SetParams` will be disabled as we do not want to update params after vested account initialization. +The `VestedAccount` will also maintain an attribute called `FreeCoins` + + +`auth.AccountMapper` to handle vested accounts as well. Specific changes +are omitted in this doc for succinctness. + + +##### 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 +`sendCoins` and `inputOutputCoins`. These methods must check an account's `Type` method; if it is a vested +account (i.e. `acc.Type() == "vested"`): + +1. Check if `ctx.BlockHeight() < acc.GetParams()["BlockLock"]` + * If `true`, the account is still vesting +2. If account is still vesting, check that `(acc.GetCoins() - acc.GetParams()["Funds"] - amount).IsValid()`. + * This will check that amount trying to be spent will not come from initial balance. +3. If above checks pass, allow transaction to go through. Else, return sdk.Error. + +### Initializing at Genesis + +### 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 From 39d1cf69b5240ea84692261411de02f40b8d1b5b Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Sun, 29 Jul 2018 19:29:54 -0700 Subject: [PATCH 02/14] simplify spec --- docs/spec/auth/vesting.md | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index 0515953573..3a3ef07e85 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -4,16 +4,15 @@ This paper specifies changes to the auth and bank modules to implement vested accounts for the Cosmos Hub. The requirements for this vested account is that it should be capable of being initialized during genesis with -a starting balance X and a vesting blocknumber N. The owner of this account should be able to delegate to validators, -but they cannot send their initial coins to other accounts. However; funds sent to this account, or fees and -inflation rewards from delegation should be spendable. Thus, the bank module's MsgSend handler should error if -a vested account is trying to send an amount `x > currentBalance - initialBalance` before block N. +a starting balance X coins and a vesting blocknumber N. The owner of this account should be able to delegate to validators, +but they cannot send their coins to other accounts. Thus, the bank module's MsgSend handler should error if +a vested account is trying to send an amount before block N. ### Implementation ##### Changes to x/auth Module -The first change is to the Account interface to specify both the Account type and any parameters it needs. +The Account interface will specify both the Account type and any parameters it needs. ```go // Account is a standard account using a sequence number for replay protection @@ -48,12 +47,11 @@ handler can then call `GetParams` to handle the specific account type using the exist in the parameter map. The `VestedAccount` will be an implementation of `Account` interface that wraps `BaseAccount` with -`Type() => "vested` and params, `GetParams() => {"Funds": initialBalance (sdk.Coins), "BlockLock": blockN (int64)}`. -`SetParams` will be disabled as we do not want to update params after vested account initialization. -The `VestedAccount` will also maintain an attribute called `FreeCoins` +`Type() => "vested` and params, `GetParams() => {"BlockLock": blockN (int64)}`. +`SetParams` will be disabled as we do not want to update params after vested account initialization. -`auth.AccountMapper` to handle vested accounts as well. Specific changes +`auth.AccountMapper` will be modified handle vested accounts as well. Specific changes are omitted in this doc for succinctness. @@ -65,13 +63,25 @@ handled at the `bank.Keeper` level. Specifically in methods that are explicitly account (i.e. `acc.Type() == "vested"`): 1. Check if `ctx.BlockHeight() < acc.GetParams()["BlockLock"]` - * If `true`, the account is still vesting -2. If account is still vesting, check that `(acc.GetCoins() - acc.GetParams()["Funds"] - amount).IsValid()`. - * This will check that amount trying to be spent will not come from initial balance. -3. If above checks pass, allow transaction to go through. Else, return sdk.Error. +2. If `true`, the account is still vesting, return sdk.Error. Else, allow transaction to be processed as normal. ### Initializing at Genesis +To initialize both vested accounts and base accounts, the `GenesisAccount` struct will be: + +```go +type GenesisAccount struct { + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` + Type string `json:"type"` + BlockLock int64 `json:"lock"` +} +``` + +During `InitChain`, the GenesisAccount's are decoded. If they have `Type == "vested`, a vested account with parameters => +`{"BlockLock": BlockLock}` gets created and put in initial state. Otherwise if `Type == "base"` a base account is created +and the `BlockLock` attribute of corresponding `GenesisAccount` is ignored. `InitChain` will panic on any other account types. + ### Pros and Cons ##### Pros From 69d1fe2fb18c0e6181a6ead92f3f9e031a6f688d Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Sun, 29 Jul 2018 19:33:17 -0700 Subject: [PATCH 03/14] clarify requirements --- docs/spec/auth/vesting.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index 3a3ef07e85..c188f3f644 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -4,9 +4,9 @@ This paper specifies changes to the auth and bank modules to implement vested accounts for the Cosmos Hub. The requirements for this vested account is that it should be capable of being initialized during genesis with -a starting balance X coins and a vesting blocknumber N. The owner of this account should be able to delegate to validators, -but they cannot send their coins to other accounts. Thus, the bank module's MsgSend handler should error if -a vested account is trying to send an amount before block N. +a starting balance X coins and a vesting blocknumber N. The owner of this account should be able to delegate to validators and vote, +however they cannot send their coins to other accounts until the account has fully vested. Thus, the bank module's MsgSend handler +should error if a vested account is trying to send an amount before block N. ### Implementation From 89494ef73eedb5e1ca0527eca161bf21030d3867 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Sun, 29 Jul 2018 19:35:23 -0700 Subject: [PATCH 04/14] improve readability --- docs/spec/auth/vesting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index c188f3f644..dcb07a18bc 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -5,14 +5,14 @@ This paper specifies changes to the auth and bank modules to implement vested accounts for the Cosmos Hub. The requirements for this vested account is that it should be capable of being initialized during genesis with a starting balance X coins and a vesting blocknumber N. The owner of this account should be able to delegate to validators and vote, -however they cannot send their coins to other accounts until the account has fully vested. Thus, the bank module's MsgSend handler +however they cannot send their coins to other accounts until the account has fully vested. Thus, the bank module's `MsgSend` handler should error if a vested account is trying to send an amount before block N. ### Implementation ##### Changes to x/auth Module -The Account interface will specify both the Account type and any parameters it needs. +The `Account` interface will specify both the Account type and any parameters it needs. ```go // Account is a standard account using a sequence number for replay protection From 41130f87422dcb401b2a18a8d730724c22cd8510 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 30 Jul 2018 13:25:44 -0700 Subject: [PATCH 05/14] Addressed basic comments --- docs/spec/auth/vesting.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index dcb07a18bc..1f72b48e51 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -4,9 +4,9 @@ This paper specifies changes to the auth and bank modules to implement vested accounts for the Cosmos Hub. The requirements for this vested account is that it should be capable of being initialized during genesis with -a starting balance X coins and a vesting blocknumber N. 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 and vote, however they cannot send their coins to other accounts until the account has fully vested. Thus, the bank module's `MsgSend` handler -should error if a vested account is trying to send an amount before block N. +should error if a vested account is trying to send an amount before time T. ### Implementation @@ -36,9 +36,10 @@ type Account interface { 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() map[string]interface{} - SetParams(map[string]interface{}) error + GetParams([]byte) []byte + SetParams([]byte, []byte) error } ``` @@ -47,7 +48,7 @@ handler can then call `GetParams` to handle the specific account type using the exist in the parameter map. The `VestedAccount` will be an implementation of `Account` interface that wraps `BaseAccount` with -`Type() => "vested` and params, `GetParams() => {"BlockLock": blockN (int64)}`. +`Type() => "vested` and params, `GetParams() => {"TimeLock": N (int64)}`. `SetParams` will be disabled as we do not want to update params after vested account initialization. @@ -62,7 +63,7 @@ handled at the `bank.Keeper` level. Specifically in methods that are explicitly `sendCoins` and `inputOutputCoins`. These methods must check an account's `Type` method; if it is a vested account (i.e. `acc.Type() == "vested"`): -1. Check if `ctx.BlockHeight() < acc.GetParams()["BlockLock"]` +1. Check if `ctx.BlockHeader().Time < acc.GetParams()["BlockLock"]` 2. If `true`, the account is still vesting, return sdk.Error. Else, allow transaction to be processed as normal. ### Initializing at Genesis @@ -71,16 +72,16 @@ To initialize both vested accounts and base accounts, the `GenesisAccount` struc ```go type GenesisAccount struct { - Address sdk.AccAddress `json:"address"` - Coins sdk.Coins `json:"coins"` - Type string `json:"type"` - BlockLock int64 `json:"lock"` + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` + Type string `json:"type"` + TimeLock int64 `json:"lock"` } ``` During `InitChain`, the GenesisAccount's are decoded. If they have `Type == "vested`, a vested account with parameters => -`{"BlockLock": BlockLock}` gets created and put in initial state. Otherwise if `Type == "base"` a base account is created -and the `BlockLock` attribute of corresponding `GenesisAccount` is ignored. `InitChain` will panic on any other account types. +`{"TimeLock": N}` gets created and put in initial state. Otherwise if `Type == "base"` a base account is created +and the `TimeLock` attribute of corresponding `GenesisAccount` is ignored. `InitChain` will panic on any other account types. ### Pros and Cons From d4d7658166100a156a76f8b29c6a1d1f3bc5ea99 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Tue, 31 Jul 2018 18:11:19 -0700 Subject: [PATCH 06/14] New idea for spec --- docs/spec/auth/vesting.md | 129 +++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 70 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index 1f72b48e51..87194ce8e9 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -2,98 +2,87 @@ ### Intro and Requirements -This paper specifies changes to the auth and bank modules to implement vested accounts for the Cosmos Hub. -The requirements for this vested 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, -however they cannot send their coins to other accounts until the account has fully vested. Thus, the bank module's `MsgSend` handler -should error if a vested account is trying to send an amount before time T. +This paper specifies changes to the auth and bank modules to implement vesting accounts for the Cosmos Hub. +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 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. +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 -##### Changes to x/auth Module - -The `Account` interface will specify both the Account type and any parameters it needs. +##### Vesting Account implementation ```go -// Account is a standard account using a sequence number for replay protection -// and a pubkey for authentication. -type Account interface { - Type() string // returns the type of the account +type VestingAccount interface { + Account + AssertIsVestingAccount() // existence implies that account is vesting. +} - GetAddress() sdk.AccAddress - SetAddress(sdk.AccAddress) error // errors if already set. +// Implements Vesting Account +// 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. - SetPubKey(crypto.PubKey) error - - 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 +func (vacc ContinuousVestingAccount) ConvertAccount() BaseAccount { + if T > vacc.EndTime { + // Convert to BaseAccount + } } ``` -The `Type` method will allow handlers to determine what type of account is sending the message, and the -handler can then call `GetParams` to handle the specific account type using the parameters it expects to -exist in the parameter map. +The `VestingAccount` interface is used purely to assert that an account is a vesting account like so: -The `VestedAccount` will be an implementation of `Account` interface that wraps `BaseAccount` with -`Type() => "vested` and params, `GetParams() => {"TimeLock": N (int64)}`. -`SetParams` will be disabled as we do not want to update params after vested account initialization. +```go +vacc, ok := acc.(VestingAccount); ok +``` +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 -are omitted in this doc for succinctness. +##### Changes to Keepers/Handler - -##### Changes to bank MsgSend Handler - -Since a vested account should be capable of doing everything but sending, 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 -`sendCoins` and `inputOutputCoins`. These methods must check an account's `Type` method; if it is a vested -account (i.e. `acc.Type() == "vested"`): +`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. Check if `ctx.BlockHeader().Time < acc.GetParams()["BlockLock"]` -2. If `true`, the account is still vesting, return sdk.Error. Else, allow transaction to be processed as normal. +1. If `Now < vacc.EndTime` + 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 -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 type GenesisAccount struct { - Address sdk.AccAddress `json:"address"` - Coins sdk.Coins `json:"coins"` - Type string `json:"type"` - TimeLock int64 `json:"lock"` + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` + EndTime int64 `json:"lock"` } ``` -During `InitChain`, the GenesisAccount's are decoded. If they have `Type == "vested`, a vested account with parameters => -`{"TimeLock": N}` gets created and put in initial state. Otherwise if `Type == "base"` a base account is created -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 +During `InitChain`, the GenesisAccounts are decoded. If `EndTime == 0`, a BaseAccount gets created and put in Genesis state. +Otherwise a vesting account is created with `StartTime = RequestInitChain.Time`, `EndTime = gacc.EndTime`, and `OriginalCoins = Coins`. From 10b2e830a224b00f5096c486ffdaec74b0ae29bf Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Fri, 3 Aug 2018 13:12:34 -0700 Subject: [PATCH 07/14] addressed comments, added formulas for easy verification --- docs/spec/auth/vesting.md | 108 +++++++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 30 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index 87194ce8e9..99a051284a 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -2,11 +2,11 @@ ### Intro and Requirements -This paper specifies changes to the auth and bank modules to implement vesting accounts for the Cosmos Hub. -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 +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 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 unlocked coin amount. @@ -14,35 +14,40 @@ unlocked coin amount. ##### Vesting Account implementation +NOTE: `Now = ctx.BlockHeader().Time` + ```go type VestingAccount interface { Account AssertIsVestingAccount() // existence implies that account is vesting. + ConvertAccount(sdk.Context) BaseAccount } // Implements Vesting Account // Continuously vests by unlocking coins linearly with respect to time type ContinuousVestingAccount struct { BaseAccount - OriginalCoins sdk.Coins - ReceivedCoins sdk.Coins + 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 StartTime int64 EndTime int64 } -func (vacc ContinuousVestingAccount) ConvertAccount() BaseAccount { - if T > vacc.EndTime { - // Convert to BaseAccount - } -} +ConvertAccount(vacc ContinuousVestingAccount) (BaseAccount): + if Now > vacc.EndTime then // 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 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`, `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. @@ -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 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. -NOTE: `Now = ctx.BlockHeader().Time` -1. If `Now < vacc.EndTime` - 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. +```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 -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 +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 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. +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. However when the staking handler is handing out fees or inflation rewards, then `ReceivedCoins` SHOULD be incremented. ### 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 type GenesisAccount struct { - Address sdk.AccAddress `json:"address"` - Coins sdk.Coins `json:"coins"` - EndTime int64 `json:"lock"` + Address sdk.AccAddress `json:"address"` + GenesisCoins sdk.Coins `json:"coins"` + 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. -Otherwise a vesting account is created with `StartTime = RequestInitChain.Time`, `EndTime = gacc.EndTime`, and `OriginalCoins = Coins`. +### 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 )` From 9c1918efdc04545297ff5b0c3f07da076fdc7e97 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Fri, 3 Aug 2018 13:16:41 -0700 Subject: [PATCH 08/14] improve readability --- docs/spec/auth/vesting.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index 99a051284a..d70c94b1f9 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -62,14 +62,18 @@ handled at the `bank.Keeper` level. Specifically in methods that are explicitly ```go if Now < vacc.EndTime: - // NOTE: SendableCoins may be greater than total coins in account 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. SendableCoins := ReceivedCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime) if msg.Amount > SendableCoins then fail -else: account = ConvertAccount(account) // Account fully vested, convert to BaseAccount +// Account fully vested, convert to BaseAccount +else: account = ConvertAccount(account) -if msg.Amount > account.GetCoins() then fail // Must still check if account has enough coins, since SendableCoins does not check this. +// Must still check if account has enough coins, +// since SendableCoins does not check this. +if msg.Amount > account.GetCoins() then fail // All checks passed, send the coins SendCoins(inputs, outputs) @@ -125,6 +129,7 @@ initChainer: Maximum amount of coins vesting schedule allows to be sent: `ReceivedCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime)` + `ReceivedCoins + OriginalCoins - LockedCoins` Coins currently in Account: From d56e3a7ef919f1fc3b64bc028a08bf8cc2ab9652 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 6 Aug 2018 13:03:58 -0700 Subject: [PATCH 09/14] better pseudocode --- docs/spec/auth/vesting.md | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index d70c94b1f9..3d2be53c32 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -35,8 +35,12 @@ type ContinuousVestingAccount struct { EndTime int64 } +// ConvertAccount converts VestingAccount into BaseAccount +// Will convert only after account has fully vested ConvertAccount(vacc ContinuousVestingAccount) (BaseAccount): - if Now > vacc.EndTime then // Convert to BaseAccount + if Now > vacc.EndTime: + account = NewBaseAccount(vacc.Address, vacc.OriginalCoins + vacc.ReceivedCoins) + return account ``` @@ -101,15 +105,22 @@ type GenesisAccount struct { } initChainer: - for genesis_acc in GenesisAccounts: - if EndTime == 0 then // Create BaseAccount - else: - vesting_account = ContinuouslyVestingAccount{ - OriginalCoins: GenesisCoins, + for gacc in GenesisAccounts: + baseAccount := BaseAccount{ + Address: gacc.Address, + Coins: gacc.GenesisCoins, + } + if gacc.EndTime != 0: + vestingAccount := ContinuouslyVestingAccount{ + BaseAccount: baseAccount, + OriginalCoins: gacc.GenesisCoins, StartTime: RequestInitChain.Time, - EndTime: EndTime, + EndTime: gacc.EndTime, } - // Add account to initial state + AddAccountToState(vestingAccount) + else: + AddAccountToState(baseAccount) + ``` ### Formulas From 2ac55ebb1dac3b53fe5eaf06daf757eecbb233e8 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 6 Aug 2018 15:23:47 -0700 Subject: [PATCH 10/14] even better pseudocode --- docs/spec/auth/vesting.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index 3d2be53c32..3123e1da6b 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -65,7 +65,7 @@ handled at the `bank.Keeper` level. Specifically in methods that are explicitly `sendCoins` and `inputOutputCoins`. These methods must check that an account is a vesting account using the check described above. ```go -if Now < vacc.EndTime: +if Now < vestingAccount.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. @@ -73,11 +73,12 @@ if Now < vacc.EndTime: if msg.Amount > SendableCoins then fail // Account fully vested, convert to BaseAccount -else: account = ConvertAccount(account) +else: + account = ConvertAccount(account) // Must still check if account has enough coins, // since SendableCoins does not check this. -if msg.Amount > account.GetCoins() then fail +if msg.Amount > account.GetCoins() then fail // All checks passed, send the coins SendCoins(inputs, outputs) From feb9a22663546fc55bd627ae2a2b2d471697de72 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Tue, 7 Aug 2018 17:02:28 -0700 Subject: [PATCH 11/14] Cleaned up spec further --- docs/spec/auth/vesting.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index 3123e1da6b..b9535af05a 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -21,6 +21,10 @@ type VestingAccount interface { Account AssertIsVestingAccount() // existence implies that account is vesting. ConvertAccount(sdk.Context) BaseAccount + + // Calculates total amount of unlocked coins released by vesting schedule + // May be larger than total coins in account right now + TotalUnlockedCoins(sdk.Context) sdk.Coins } // Implements Vesting Account @@ -37,10 +41,13 @@ type ContinuousVestingAccount struct { // ConvertAccount converts VestingAccount into BaseAccount // Will convert only after account has fully vested -ConvertAccount(vacc ContinuousVestingAccount) (BaseAccount): +ConvertAccount(vacc ContinuousVestingAccount, ctx sdk.Context) (BaseAccount): if Now > vacc.EndTime: - account = NewBaseAccount(vacc.Address, vacc.OriginalCoins + vacc.ReceivedCoins) - return account + return vacc.BaseAccount + +// Uses time in context to calculate total unlocked coins +TotalUnlockedCoins(vacc ContinuousVestingAccount, ctx sdk.Context) sdk.Coins: + return ReceivedCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime) ``` @@ -69,8 +76,7 @@ if Now < vestingAccount.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 + if msg.Amount > vestingAccount.TotalUnlockedCoins() then fail // Account fully vested, convert to BaseAccount else: @@ -148,6 +154,8 @@ Coins currently in Account: `CurrentCoins = OriginalCoins + ReceivedCoins - Delegated - Sent` +`CurrentCoins = vestingAccount.BaseAccount.GetCoins()` + **Maximum amount of coins spendable right now:** `min( ReceivedCoins + OriginalCoins - LockedCoins, CurrentCoins )` From 7539e212ee5f4dc7dfed4a746430a9e5ae1278b4 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Tue, 7 Aug 2018 17:04:22 -0700 Subject: [PATCH 12/14] more readable pseudo --- docs/spec/auth/vesting.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index b9535af05a..a69a342e4d 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -47,7 +47,8 @@ ConvertAccount(vacc ContinuousVestingAccount, ctx sdk.Context) (BaseAccount): // Uses time in context to calculate total unlocked coins TotalUnlockedCoins(vacc ContinuousVestingAccount, ctx sdk.Context) sdk.Coins: - return ReceivedCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime) + unlockedCoins := ReceivedCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime) + return unlockedCoins ``` From 599b8ba4cf26d139b827aac504092ffb77e66080 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 15 Aug 2018 13:54:03 -0700 Subject: [PATCH 13/14] Fix bug, clearer logic --- docs/spec/auth/vesting.md | 71 +++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index a69a342e4d..344e10049d 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -20,11 +20,9 @@ NOTE: `Now = ctx.BlockHeader().Time` type VestingAccount interface { Account AssertIsVestingAccount() // existence implies that account is vesting. - ConvertAccount(sdk.Context) BaseAccount - // Calculates total amount of unlocked coins released by vesting schedule - // May be larger than total coins in account right now - TotalUnlockedCoins(sdk.Context) sdk.Coins + // Calculates amount of coins that can be sent to other accounts given the current time + SendableCoins(sdk.Context) sdk.Coins } // Implements Vesting Account @@ -33,22 +31,24 @@ type ContinuousVestingAccount struct { BaseAccount OriginalCoins sdk.Coins // Coins in account on Initialization ReceivedCoins sdk.Coins // Coins received from other accounts + SentCoins sdk.Coins // Coins sent to other accounts // StartTime and EndTime used to calculate how much of OriginalCoins is unlocked at any given point - StartTime int64 - EndTime int64 + StartTime time.Time + EndTime time.Time } -// ConvertAccount converts VestingAccount into BaseAccount -// Will convert only after account has fully vested -ConvertAccount(vacc ContinuousVestingAccount, ctx sdk.Context) (BaseAccount): - if Now > vacc.EndTime: - return vacc.BaseAccount - // Uses time in context to calculate total unlocked coins -TotalUnlockedCoins(vacc ContinuousVestingAccount, ctx sdk.Context) sdk.Coins: - unlockedCoins := ReceivedCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime) - return unlockedCoins +SendableCoins(vacc ContinuousVestingAccount, ctx sdk.Context) sdk.Coins: + + // Coins unlocked by vesting schedule + unlockedCoins := ReceivedCoins - SentCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime) + + // Must still check for currentCoins constraint since some unlocked coins may have been delegated. + currentCoins := vacc.BaseAccount.GetCoins() + + // min will return sdk.Coins with each denom having the minimum amount from unlockedCoins and currentCoins + return min(unlockedCoins, currentCoins) ``` @@ -58,13 +58,13 @@ The `VestingAccount` interface is used to assert that an account is a vesting ac vacc, ok := acc.(VestingAccount); ok ``` -as well as to convert to BaseAccount again once the account has fully vested. +as well as to calculate the SendableCoins at any given moment. 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`. +`SentCoins`, `StartTime`, and `EndTime` to calculate how many coins are sendable at any given point. +Since the vesting restrictions need to be implemented on a per-module basis, the `ContinuousVestingAccount` implements +the `Account` interface exactly like `BaseAccount`. Thus, `ContinuousVestingAccount.GetCoins()` will return the total of +both locked coins and unlocked coins currently in the account. ##### Changes to Keepers/Handler @@ -73,19 +73,15 @@ handled at the `bank.Keeper` level. Specifically in methods that are explicitly `sendCoins` and `inputOutputCoins`. These methods must check that an account is a vesting account using the check described above. ```go -if Now < vestingAccount.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. - if msg.Amount > vestingAccount.TotalUnlockedCoins() then fail +if acc is VestingAccount and Now < vestingAccount.EndTime: + // Check if amount is less than currently allowed sendable coins + if msg.Amount > vestingAccount.SendableCoins(ctx) then fail + else: + vestingAccount.SentCoins += msg.Amount -// Account fully vested, convert to BaseAccount else: - account = ConvertAccount(account) - -// Must still check if account has enough coins, -// since SendableCoins does not check this. -if msg.Amount > account.GetCoins() then fail + // Account has fully vested, treat like regular account + if msg.Amount > account.GetCoins() then fail // All checks passed, send the coins SendCoins(inputs, outputs) @@ -95,9 +91,10 @@ 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. +Unlocked coins that are sent to other accounts will increment the vesting account's `SentCoins` attribute. 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. -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/inflation rewards or a user sends coins to vesting account, then `ReceivedCoins` SHOULD be incremented. ### Initializing at Genesis @@ -135,7 +132,7 @@ initChainer: `OriginalCoins`: Amount of coins in account at Genesis -`CurrentCoins`: Coins currently in the baseaccount (both locked and unlocked) +`CurrentCoins`: Coins currently in the baseaccount (both locked and unlocked: `vestingAccount.GetCoins`) `ReceivedCoins`: Coins received from other accounts (always unlocked) @@ -147,16 +144,16 @@ initChainer: Maximum amount of coins vesting schedule allows to be sent: -`ReceivedCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime)` +`ReceivedCoins - SentCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime)` -`ReceivedCoins + OriginalCoins - LockedCoins` +`ReceivedCoins - SentCoins + OriginalCoins - LockedCoins` Coins currently in Account: `CurrentCoins = OriginalCoins + ReceivedCoins - Delegated - Sent` -`CurrentCoins = vestingAccount.BaseAccount.GetCoins()` +`CurrentCoins = vestingAccount.GetCoins()` **Maximum amount of coins spendable right now:** -`min( ReceivedCoins + OriginalCoins - LockedCoins, CurrentCoins )` +`min( ReceivedCoins - SentCoins + OriginalCoins - LockedCoins, CurrentCoins )` From c79e130d0fbabea6dae0fca5d4c47dabb66f79f9 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Thu, 16 Aug 2018 12:07:09 -0700 Subject: [PATCH 14/14] jae comments --- docs/spec/auth/vesting.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index 344e10049d..c5c25ecaed 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -29,20 +29,20 @@ type VestingAccount interface { // Continuously vests by unlocking coins linearly with respect to time type ContinuousVestingAccount struct { BaseAccount - OriginalCoins sdk.Coins // Coins in account on Initialization - ReceivedCoins sdk.Coins // Coins received from other accounts - SentCoins sdk.Coins // Coins sent to other accounts + OriginalVestingCoins sdk.Coins // Coins in account on Initialization + ReceivedCoins sdk.Coins // Coins received from other accounts + SentCoins sdk.Coins // Coins sent to other accounts // StartTime and EndTime used to calculate how much of OriginalCoins is unlocked at any given point - StartTime time.Time - EndTime time.Time + StartTime time.Time + EndTime time.Time } // Uses time in context to calculate total unlocked coins SendableCoins(vacc ContinuousVestingAccount, ctx sdk.Context) sdk.Coins: // Coins unlocked by vesting schedule - unlockedCoins := ReceivedCoins - SentCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime) + unlockedCoins := ReceivedCoins - SentCoins + OriginalVestingCoins * (Now - StartTime) / (EndTime - StartTime) // Must still check for currentCoins constraint since some unlocked coins may have been delegated. currentCoins := vacc.BaseAccount.GetCoins() @@ -60,11 +60,11 @@ vacc, ok := acc.(VestingAccount); ok as well as to calculate the SendableCoins at any given moment. -The `ContinuousVestingAccount` struct implements the Vesting account interface. It uses `OriginalCoins`, `ReceivedCoins`, +The `ContinuousVestingAccount` struct implements the Vesting account interface. It uses `OriginalVestingCoins`, `ReceivedCoins`, `SentCoins`, `StartTime`, and `EndTime` to calculate how many coins are sendable at any given point. Since the vesting restrictions need to be implemented on a per-module basis, the `ContinuousVestingAccount` implements the `Account` interface exactly like `BaseAccount`. Thus, `ContinuousVestingAccount.GetCoins()` will return the total of -both locked coins and unlocked coins currently in the account. +both locked coins and unlocked coins currently in the account. Delegated coins are deducted from `Account.GetCoins()`, but do not count against unlocked coins because they are still at stake and will be reinstated (partially if slashed) after waiting the full unbonding period. ##### Changes to Keepers/Handler @@ -93,8 +93,8 @@ immediately after receiving them. Thus, handlers (like staking or bank) that sen originally own should increment `ReceivedCoins` by the amount sent. Unlocked coins that are sent to other accounts will increment the vesting account's `SentCoins` attribute. -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. -However when the staking handler is handing out fees/inflation rewards or a user sends coins to vesting account, then `ReceivedCoins` SHOULD be incremented. +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 staking handlers SHOULD NOT update `ReceivedCoins`. +However when a user sends coins to vesting account, then `ReceivedCoins` SHOULD be incremented. ### Initializing at Genesis @@ -117,10 +117,10 @@ initChainer: } if gacc.EndTime != 0: vestingAccount := ContinuouslyVestingAccount{ - BaseAccount: baseAccount, - OriginalCoins: gacc.GenesisCoins, - StartTime: RequestInitChain.Time, - EndTime: gacc.EndTime, + BaseAccount: baseAccount, + OriginalVestingCoins: gacc.GenesisCoins, + StartTime: RequestInitChain.Time, + EndTime: gacc.EndTime, } AddAccountToState(vestingAccount) else: @@ -130,7 +130,7 @@ initChainer: ### Formulas -`OriginalCoins`: Amount of coins in account at Genesis +`OriginalVestingCoins`: Amount of coins in account at Genesis `CurrentCoins`: Coins currently in the baseaccount (both locked and unlocked: `vestingAccount.GetCoins`) @@ -144,16 +144,16 @@ initChainer: Maximum amount of coins vesting schedule allows to be sent: -`ReceivedCoins - SentCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime)` +`ReceivedCoins - SentCoins + OriginalVestingCoins * (Now - StartTime) / (EndTime - StartTime)` -`ReceivedCoins - SentCoins + OriginalCoins - LockedCoins` +`ReceivedCoins - SentCoins + OriginalVestingCoins - LockedCoins` Coins currently in Account: -`CurrentCoins = OriginalCoins + ReceivedCoins - Delegated - Sent` +`CurrentCoins = OriginalVestingCoins + ReceivedCoins - Delegated - Sent` `CurrentCoins = vestingAccount.GetCoins()` **Maximum amount of coins spendable right now:** -`min( ReceivedCoins - SentCoins + OriginalCoins - LockedCoins, CurrentCoins )` +`min( ReceivedCoins - SentCoins + OriginalVestingCoins - LockedCoins, CurrentCoins )`