New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vesting Spec #1875
Vesting Spec #1875
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #1875 +/- ##
=======================================
Coverage 63.5% 63.5%
=======================================
Files 118 118
Lines 7006 7006
=======================================
Hits 4449 4449
Misses 2301 2301
Partials 256 256 |
docs/spec/auth/vesting.md
Outdated
`{"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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol I'm not sure that a pro's con's section really belongs in a spec - but not sure where it belongs beyond a proposal github issue
docs/spec/auth/vesting.md
Outdated
|
||
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be way better if this used BFT time instead of block number
docs/spec/auth/vesting.md
Outdated
// It is upto handler to use these appropriately | ||
GetParams() map[string]interface{} | ||
SetParams(map[string]interface{}) error | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this be more like GetParams([]byte) []byte
and SetParams([]byte, []byte)
p.s. golang maps are a non-deterministic type meaning we can't use them in the state-machine
docs/spec/auth/vesting.md
Outdated
|
||
The `VestedAccount` will be an implementation of `Account` interface that wraps `BaseAccount` with | ||
`Type() => "vested` and params, `GetParams() => {"BlockLock": blockN (int64)}`. | ||
`SetParams` will be disabled as we do not want to update params after vested account initialization. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we can just eliminate Type
(are we introducing it?) and only have a block-lock param - if it exists then we know it's vested, or rather just locked - which is the important part
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I want to introduce it. If we just want to support this specific case, we can. I think we should add the Type tho, because we may want to add other types of Account. And people building on top of sdk should be able to create multiple account types as well.
For example, in the future we may want to support NLocked Accounts where each month a new amount of coins get unlocked, or maybe some other feature. I don't think we should add cruft to BaseAccount every time we want something new and specific to our application.
imo, it makes sense to create different Account types that define their own parameters and have handlers that can handle specific account types.
Will move a lot of this into a proposal issue as you suggested and rewrite.
Thanks for the review!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few comments throughout - I like the approach you've proposed of extending the account
This document is structured more like a proposal for these features, and it's placed in a funny location given that it affects bank and auth.
I think maybe this PR should actually be modifying the existing spec documents which are relevant, and also having a new document talking about vesting, but not changes to other modules. Like, let's phrase this as more definite changes and implementation requirements - not a proposal
docs/spec/auth/vesting.md
Outdated
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(btw, should be Vesting not Vested).
I'm not sure that having a top-level type is the right solution.
Would it make more sense to have an optional interface method, like
type VestingAccount interface {
Account
AssertIsVestingAccount() // existence implies that account is vesting.
}
Then you an check with vacc, ok := acc.(VestingAccount); ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That works well in our case where we are choosing from two possible account types.
I think we should leave the option open to adding more account types in the future, and for developers building on the SDK to have many supported account types.
In those cases, I think having a Type()
function that handlers can use to switch their functionality based on the account type is better than trying to type cast for multiple account types.
docs/spec/auth/vesting.md
Outdated
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vesting
docs/spec/auth/vesting.md
Outdated
} | ||
``` | ||
|
||
During `InitChain`, the GenesisAccount's are decoded. If they have `Type == "vested`, a vested account with parameters => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GenesisAccounts are
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vesting
Can someone document why we want to do this, as opposed to having one account that can vest continuously over time? The latter seems much more flexible and I am worried about having to deal with e.g. 24 accounts, or more if we want more fine-grained vesting.
I think it would require more tooling for us to deal with 24 accounts, than to deal with this more flexible vesting system. |
@jaekwon I originally considered doing vesting like a step function, it ends up being a bit more complicated than what you're suggesting. This is because transactions that we want to allow a vesting account to make (like staking) will subtract coins. Thus, vesting accounts balance are allowed to go below the equation you posted. One thing we could do is to have The downside with this approach is that all handlers for messages we want vesting accounts to be able to call (e.g staking, governance) before fully vesting must be aware of the existence of vesting accounts and update their state correctly. Whereas with the current approach, only the bank module is aware and cares about the existence of vesting accounts. Not sure how the pros and cons weigh out between the two approaches. |
Jae:
^ @zmanian |
Just realized I don't think we want any of this.. |
Yea, realized we shouldn't be changing Account interface as well. Reworking spec But we can't just implement I'll finish up what I have in mind and hopefully that clears things up. |
Awesome - yeah sounds good, excited to see your thinking |
docs/spec/auth/vesting.md
Outdated
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Old spec was not able to spend coins gained from fees/inflation. New spec will allow vesting account to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait this is kind of seperate, the ability to spend coins received through the fee-distribution mechanism is independent of the vesting account because the fee-distribution mechanism allows you with withdraw fees to a seperate account - we should keep these distinct and not make the vesting account have special logic for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the purpose of ReceivedCoins
is to makes sure that funds sent to the VestingAccount after initialization are spendable immediately.
docs/spec/auth/vesting.md
Outdated
``` | ||
|
||
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`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flagging for future reference. Since we are using the time in Genesis file as start time, we should make sure creation of Genesis file is close-ish to launch. Otherwise it messes up the slope for continuous vesting.
@jaekwon the checks described in this spec happen "upstream" from the checks that are already in bank's Keeper methods. Thus, I am really calculating the maximum amount of unlocked coins this account can spend. If the formula written says you are allowed to spend 15 steak, but you only have 10 steak in the account because you have delegated 5 of your unlocked coins, the keeper will still only allow you to spend up to 10 steak. There's no need to convert. I think it's worthwhile because Vesting accounts take up more space and gas cost for running transactions. Thus, it makes sense to me to do the one-time conversion though it isn't necessary I don't think its possible to avoid changing bank. This is because we want all the other modules to be able to set account Coins as normal, but bank module needs special logic before it can set Coins. Thus, the logic can't be implemented at the account or mapper level. I'm not sure how a different message type alleviates this though interested to hear the proposal. Ex for further clarity: Let's say I start with 50 steak in a vesting account of which 30 is locked. I get sent 10 steak by some other users. The maximum amount of unlocked coins I am allowed to spend calculated by formula in spec: 30 steak In order for a bank MsgSend to succeed, it checks: If both checks pass, the MsgSend succeeds. Else it fails. |
addressed comments, added formulas at the bottom for easier verification
@AdityaSripal I think the updates help greatly. It seems like the pseudo-code format could be improved a bit. |
@alexanderbez anything specific about the pseudocode that was hard to read? I cleaned some of it up to hopefully make it clearer. Let me know if there's still some improvements to be made |
docs/spec/auth/vesting.md
Outdated
`sendCoins` and `inputOutputCoins`. These methods must check that an account is a vesting account using the check described above. | ||
|
||
```go | ||
if Now < vacc.EndTime: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AdityaSripal I was mostly referring to this 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comments, also:
- Can we describe how
ContinuousVestingAccount
implementsBaseAccount
(in particularGetCoins
)?
```go | ||
type VestingAccount interface { | ||
Account | ||
AssertIsVestingAccount() // existence implies that account is vesting. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this function?
docs/spec/auth/vesting.md
Outdated
|
||
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be a public method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it will be used in bank's keeper methods
docs/spec/auth/vesting.md
Outdated
type VestingAccount interface { | ||
Account | ||
AssertIsVestingAccount() // existence implies that account is vesting. | ||
ConvertAccount(sdk.Context) BaseAccount |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This just accesses BaseAccount
functions, right? Convert
is a strange name, it implies that one account is being turned into another, maybe AsBase()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally the plan was to check if account has fully vested (i.e. Now > EndTime
) and then simply return the embedded BaseAccount.
This BaseAccount would then replace the completely vested VestingAccount in the Account store. The main benefit would be that BaseAccount would take up less space in store but it is certainly not necessary.
After talking to @jaekwon , removing this functionality for now.
docs/spec/auth/vesting.md
Outdated
// Continuously vests by unlocking coins linearly with respect to time | ||
type ContinuousVestingAccount struct { | ||
BaseAccount | ||
OriginalCoins sdk.Coins // Coins in account on Initialization |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e. that are vesting? VestingCoins
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No upon initialization all coins in OriginalCoins
are vesting, true. However as time goes on, some of those coins become vested but OriginalCoins
never gets updated.
OriginalCoins
denotes all the coins that are in account on initialization, which are the only coins subject to the vesting schedule.
However as these coins go from vesting
to vested
, OriginalCoins
never changes.
The way I understand it, a coin that is unspendable is "vesting" and once it becomes spendable it is "vested". It's entirely possible that this is not correct terminology.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe "OriginalVestingCoins" would make it clearer?
docs/spec/auth/vesting.md
Outdated
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these will now be time.Time
since we've merged TM 0.23
docs/spec/auth/vesting.md
Outdated
|
||
// ConvertAccount converts VestingAccount into BaseAccount | ||
// Will convert only after account has fully vested | ||
ConvertAccount(vacc ContinuousVestingAccount, ctx sdk.Context) (BaseAccount): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we planning to save the converted account or something? Don't we need to deal with any remaining OriginalCoins
or ReceivedCoins
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed above, will remove
docs/spec/auth/vesting.md
Outdated
|
||
// Uses time in context to calculate total unlocked coins | ||
TotalUnlockedCoins(vacc ContinuousVestingAccount, ctx sdk.Context) sdk.Coins: | ||
unlockedCoins := ReceivedCoins + OriginalCoins * (Now - StartTime) / (EndTime - StartTime) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is continuous, but I think we want discrete vesting (once per month)?
I could be wrong, if you've discussed this with others please disregard
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah for now it's continuous because that is what jae and I discussed last. I'm currently trying to confirm what the preferred vesting schedule is for AiB.
Changing the vesting schedule just comes down to changing implementation of this single method.
docs/spec/auth/vesting.md
Outdated
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if I've already sent some unlocked coins? I don't think this check is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, originally thought the second check would stop this but it won't.
Fixing this in refactor
docs/spec/auth/vesting.md
Outdated
|
||
// Account fully vested, convert to BaseAccount | ||
else: | ||
account = ConvertAccount(account) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above comment, also I'm not clear on why we need to convert in the first place, the space savings don't matter much since the number of vesting accounts is finite and very small
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, removing this functionality
docs/spec/auth/vesting.md
Outdated
|
||
**Maximum amount of coins spendable right now:** | ||
|
||
`min( ReceivedCoins + OriginalCoins - LockedCoins, CurrentCoins )` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to contradict the above definition under keeper changes?
Discrete vesting is not hard as far as I know, just use time |
docs/spec/auth/vesting.md
Outdated
`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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe good to add "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."
docs/spec/auth/vesting.md
Outdated
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Staking handlers SHOULD NOT update ...
Left comments, otherwise LGTM. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should merge unless @cwgoes or @alexanderbez have additional comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK
docs/
)PENDING.md
that include links to the relevant issue or PR that most accurately describes the change.cmd/gaia
andexamples/
For Admin Use: