Merge PR #2694: Vesting Account(s) Implementation
This commit is contained in:
parent
e4efb8da8a
commit
a984a22373
|
@ -49,6 +49,7 @@ FEATURES
|
||||||
* [\#2182] [x/staking] Added querier for querying a single redelegation
|
* [\#2182] [x/staking] Added querier for querying a single redelegation
|
||||||
|
|
||||||
* SDK
|
* SDK
|
||||||
|
* \#2694 Vesting account implementation.
|
||||||
* \#2996 Update the `AccountKeeper` to contain params used in the context of
|
* \#2996 Update the `AccountKeeper` to contain params used in the context of
|
||||||
the ante handler.
|
the ante handler.
|
||||||
* [\#3179](https://github.com/cosmos/cosmos-sdk/pull/3179) New CodeNoSignatures error code.
|
* [\#3179](https://github.com/cosmos/cosmos-sdk/pull/3179) New CodeNoSignatures error code.
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
# Vesting
|
# Vesting
|
||||||
|
|
||||||
<!-- TOC -->
|
|
||||||
|
|
||||||
- [Vesting](#vesting)
|
- [Vesting](#vesting)
|
||||||
- [Intro and Requirements](#intro-and-requirements)
|
- [Intro and Requirements](#intro-and-requirements)
|
||||||
- [Vesting Account Types](#vesting-account-types)
|
- [Vesting Account Types](#vesting-account-types)
|
||||||
|
@ -10,17 +8,11 @@
|
||||||
- [Continuously Vesting Accounts](#continuously-vesting-accounts)
|
- [Continuously Vesting Accounts](#continuously-vesting-accounts)
|
||||||
- [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts)
|
- [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts)
|
||||||
- [Transferring/Sending](#transferringsending)
|
- [Transferring/Sending](#transferringsending)
|
||||||
- [Continuously Vesting Accounts](#continuously-vesting-accounts-1)
|
- [Keepers/Handlers](#keepershandlers)
|
||||||
- [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts-1)
|
|
||||||
- [Keepers/Handlers](#keepershandlers)
|
|
||||||
- [Delegating](#delegating)
|
- [Delegating](#delegating)
|
||||||
- [Continuously Vesting Accounts](#continuously-vesting-accounts-2)
|
- [Keepers/Handlers](#keepershandlers-1)
|
||||||
- [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts-2)
|
|
||||||
- [Keepers/Handlers](#keepershandlers-1)
|
|
||||||
- [Undelegating](#undelegating)
|
- [Undelegating](#undelegating)
|
||||||
- [Continuously Vesting Accounts](#continuously-vesting-accounts-3)
|
- [Keepers/Handlers](#keepershandlers-2)
|
||||||
- [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts-3)
|
|
||||||
- [Keepers/Handlers](#keepershandlers-2)
|
|
||||||
- [Keepers & Handlers](#keepers--handlers)
|
- [Keepers & Handlers](#keepers--handlers)
|
||||||
- [Initializing at Genesis](#initializing-at-genesis)
|
- [Initializing at Genesis](#initializing-at-genesis)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
|
@ -28,19 +20,15 @@
|
||||||
- [Slashing](#slashing)
|
- [Slashing](#slashing)
|
||||||
- [Glossary](#glossary)
|
- [Glossary](#glossary)
|
||||||
|
|
||||||
<!-- /TOC -->
|
|
||||||
|
|
||||||
## Intro and Requirements
|
## Intro and Requirements
|
||||||
|
|
||||||
This paper specifies vesting account implementation for the Cosmos Hub.
|
This specification describes the vesting account implementation for the Cosmos Hub.
|
||||||
The requirements for this vesting account is that it should be initialized
|
The requirements for this vesting account is that it should be initialized
|
||||||
during genesis with a starting balance `X` coins and a vesting end time `T`.
|
during genesis with a starting balance `X` and a vesting end time `T`.
|
||||||
|
|
||||||
The owner of this account should be able to delegate to validators
|
The owner of this account should be able to delegate to and undelegate from
|
||||||
and vote with locked coins, however they cannot send locked coins to other
|
validators, however they cannot send locked coins to other accounts until those
|
||||||
accounts until those coins have been unlocked. When it comes to governance, it
|
coins have been fully vested.
|
||||||
is yet undefined if we want to allow a vesting account to be able to deposit
|
|
||||||
vesting coins into proposals.
|
|
||||||
|
|
||||||
In addition, a vesting account vests all of its coin denominations at the same
|
In addition, a vesting account vests all of its coin denominations at the same
|
||||||
rate. This may be subject to change.
|
rate. This may be subject to change.
|
||||||
|
@ -56,15 +44,14 @@ order to make such a distinction.
|
||||||
// implement.
|
// implement.
|
||||||
type VestingAccount interface {
|
type VestingAccount interface {
|
||||||
Account
|
Account
|
||||||
AssertIsVestingAccount() // existence implies that account is vesting
|
|
||||||
|
|
||||||
// Calculates the amount of coins that can be sent to other accounts given
|
GetVestedCoins(Time) Coins
|
||||||
// the current time.
|
GetVestingCoins(Time) Coins
|
||||||
SpendableCoins(Context) Coins
|
|
||||||
// Performs delegation accounting.
|
// Delegation and undelegation accounting that returns the resulting base
|
||||||
TrackDelegation(amount)
|
// coins amount.
|
||||||
// Performs undelegation accounting.
|
TrackDelegation(Time, Coins)
|
||||||
TrackUndelegation(amount)
|
TrackUndelegation(Coins)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseVestingAccount implements the VestingAccount interface. It contains all
|
// BaseVestingAccount implements the VestingAccount interface. It contains all
|
||||||
|
@ -74,28 +61,41 @@ type BaseVestingAccount struct {
|
||||||
|
|
||||||
OriginalVesting Coins // coins in account upon initialization
|
OriginalVesting Coins // coins in account upon initialization
|
||||||
DelegatedFree Coins // coins that are vested and delegated
|
DelegatedFree Coins // coins that are vested and delegated
|
||||||
EndTime Time // when the coins become unlocked
|
DelegatedVesting Coins // coins that vesting and delegated
|
||||||
|
|
||||||
|
EndTime Time // when the coins become unlocked
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContinuousVestingAccount implements the VestingAccount interface. It
|
// ContinuousVestingAccount implements the VestingAccount interface. It
|
||||||
// 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
|
|
||||||
BaseVestingAccount
|
BaseVestingAccount
|
||||||
|
|
||||||
DelegatedVesting Coins // coins that vesting and delegated
|
StartTime Time // when the coins start to vest
|
||||||
StartTime Time // when the coins start to vest
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DelayedVestingAccount implements the VestingAccount interface. It vests all
|
// DelayedVestingAccount implements the VestingAccount interface. It vests all
|
||||||
// coins after a specific time, but non prior. In other words, it keeps them
|
// coins after a specific time, but non prior. In other words, it keeps them
|
||||||
// locked until a specified time.
|
// locked until a specified time.
|
||||||
type DelayedVestingAccount struct {
|
type DelayedVestingAccount struct {
|
||||||
BaseAccount
|
|
||||||
BaseVestingAccount
|
BaseVestingAccount
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In order to facilitate less ad-hoc type checking and assertions and to support
|
||||||
|
flexibility in account usage, the existing `Account` interface is updated to contain
|
||||||
|
the following:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Account interface {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Calculates the amount of coins that can be sent to other accounts given
|
||||||
|
// the current time.
|
||||||
|
SpendableCoins(Time) Coins
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Vesting Account Specification
|
## Vesting Account Specification
|
||||||
|
|
||||||
Given a vesting account, we define the following in the proceeding operations:
|
Given a vesting account, we define the following in the proceeding operations:
|
||||||
|
@ -105,19 +105,19 @@ Given a vesting account, we define the following in the proceeding operations:
|
||||||
- `V'`: The number of `OV` coins that are _vested_ (unlocked). This value is computed on demand and not a per-block basis.
|
- `V'`: The number of `OV` coins that are _vested_ (unlocked). This value is computed on demand and not a per-block basis.
|
||||||
- `DV`: The number of delegated _vesting_ coins. It is a variable value. It is stored and modified directly in the vesting account.
|
- `DV`: The number of delegated _vesting_ coins. It is a variable value. It is stored and modified directly in the vesting account.
|
||||||
- `DF`: The number of delegated _vested_ (unlocked) coins. It is a variable value. It is stored and modified directly in the vesting account.
|
- `DF`: The number of delegated _vested_ (unlocked) coins. It is a variable value. It is stored and modified directly in the vesting account.
|
||||||
- `BC`: The number of `OV` coins less any coins that are transferred, which can be negative, or delegated (`DV + DF`). It is considered to be balance of the embedded base account. It is stored and modified directly in the vesting account.
|
- `BC`: The number of `OV` coins less any coins that are transferred (which can be negative or delegated). It is considered to be balance of the embedded base account. It is stored and modified directly in the vesting account.
|
||||||
|
|
||||||
### Determining Vesting & Vested Amounts
|
### Determining Vesting & Vested Amounts
|
||||||
|
|
||||||
It is important to note that these values are computed on demand and not on a
|
It is important to note that these values are computed on demand and not on a
|
||||||
mandatory per-block basis.
|
mandatory per-block basis (e.g. `BeginBlocker` or `EndBlocker`).
|
||||||
|
|
||||||
#### Continuously Vesting Accounts
|
#### Continuously Vesting Accounts
|
||||||
|
|
||||||
To determine the amount of coins that are vested for a given block `B`, the
|
To determine the amount of coins that are vested for a given block time `T`, the
|
||||||
following is performed:
|
following is performed:
|
||||||
|
|
||||||
1. Compute `X := B.Time - StartTime`
|
1. Compute `X := T - StartTime`
|
||||||
2. Compute `Y := EndTime - StartTime`
|
2. Compute `Y := EndTime - StartTime`
|
||||||
3. Compute `V' := OV * (X / Y)`
|
3. Compute `V' := OV * (X / Y)`
|
||||||
4. Compute `V := OV - V'`
|
4. Compute `V := OV - V'`
|
||||||
|
@ -126,100 +126,87 @@ Thus, the total amount of _vested_ coins is `V'` and the remaining amount, `V`,
|
||||||
is _vesting_.
|
is _vesting_.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (cva ContinuousVestingAccount) GetVestedCoins(b Block) Coins {
|
func (cva ContinuousVestingAccount) GetVestedCoins(t Time) Coins {
|
||||||
// We must handle the case where the start time for a vesting account has
|
// We must handle the case where the start time for a vesting account has
|
||||||
// been set into the future or when the start of the chain is not exactly
|
// been set into the future or when the start of the chain is not exactly
|
||||||
// known.
|
// known.
|
||||||
if b.Time < va.StartTime {
|
if t <= va.StartTime {
|
||||||
return ZeroCoins
|
return ZeroCoins
|
||||||
}
|
}
|
||||||
|
|
||||||
x := b.Time - cva.StartTime
|
x := t - cva.StartTime
|
||||||
y := cva.EndTime - cva.StartTime
|
y := cva.EndTime - cva.StartTime
|
||||||
|
|
||||||
return cva.OriginalVesting * (x / y)
|
return cva.OriginalVesting * (x / y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cva ContinuousVestingAccount) GetVestingCoins(b Block) Coins {
|
func (cva ContinuousVestingAccount) GetVestingCoins(t Time) Coins {
|
||||||
return cva.OriginalVesting - cva.GetVestedCoins(b)
|
return cva.OriginalVesting - cva.GetVestedCoins(t)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Delayed/Discrete Vesting Accounts
|
#### Delayed/Discrete Vesting Accounts
|
||||||
|
|
||||||
Delayed vesting accounts are easier to reason about as they only have the full
|
Delayed vesting accounts are easier to reason about as they only have the full
|
||||||
amount vesting up until a certain time, then they all become vested (unlocked).
|
amount vesting up until a certain time, then all the coins become vested (unlocked).
|
||||||
|
This does not include any unlocked coins the account may have initially.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (dva DelayedVestingAccount) GetVestedCoins(b Block) Coins {
|
func (dva DelayedVestingAccount) GetVestedCoins(t Time) Coins {
|
||||||
if b.Time >= dva.EndTime {
|
if t >= dva.EndTime {
|
||||||
return dva.OriginalVesting
|
return dva.OriginalVesting
|
||||||
}
|
}
|
||||||
|
|
||||||
return ZeroCoins
|
return ZeroCoins
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dva DelayedVestingAccount) GetVestingCoins(b Block) Coins {
|
func (dva DelayedVestingAccount) GetVestingCoins(t Time) Coins {
|
||||||
return cva.OriginalVesting - cva.GetVestedCoins(b)
|
return dva.OriginalVesting - dva.GetVestedCoins(t)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Transferring/Sending
|
### Transferring/Sending
|
||||||
|
|
||||||
#### Continuously Vesting Accounts
|
At any given time, a vesting account may transfer: `min((BC + DV) - V, BC)`.
|
||||||
|
|
||||||
At any given time, a continuous vesting account may transfer: `min((BC + DV) - V, BC)`.
|
|
||||||
|
|
||||||
In other words, a vesting account may transfer the minimum of the base account
|
In other words, a vesting account may transfer the minimum of the base account
|
||||||
balance and the base account balance plus the number of currently delegated
|
balance and the base account balance plus the number of currently delegated
|
||||||
vesting coins less the number of coins vested so far.
|
vesting coins less the number of coins vested so far.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (cva ContinuousVestingAccount) SpendableCoins() Coins {
|
func (va VestingAccount) SpendableCoins(t Time) Coins {
|
||||||
bc := cva.GetCoins()
|
bc := va.GetCoins()
|
||||||
return min((bc + cva.DelegatedVesting) - cva.GetVestingCoins(), bc)
|
return min((bc + va.DelegatedVesting) - va.GetVestingCoins(t), bc)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Delayed/Discrete Vesting Accounts
|
#### Keepers/Handlers
|
||||||
|
|
||||||
A delayed vesting account may send any coins it has received. In addition, if it
|
|
||||||
has fully vested, it can send any of it's vested coins.
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (dva DelayedVestingAccount) SpendableCoins() Coins {
|
|
||||||
bc := dva.GetCoins()
|
|
||||||
return bc - dva.GetVestingCoins()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Keepers/Handlers
|
|
||||||
|
|
||||||
The corresponding `x/bank` keeper should appropriately handle sending coins
|
The corresponding `x/bank` keeper should appropriately handle sending coins
|
||||||
based on if the account is a vesting account or not.
|
based on if the account is a vesting account or not.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func SendCoins(from Account, to Account amount Coins) {
|
func SendCoins(t Time, from Account, to Account, amount Coins) {
|
||||||
|
bc := from.GetCoins()
|
||||||
|
|
||||||
if isVesting(from) {
|
if isVesting(from) {
|
||||||
sc := from.SpendableCoins()
|
sc := from.SpendableCoins(t)
|
||||||
} else {
|
assert(amount <= sc)
|
||||||
sc := from.GetCoins()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if amount <= sc {
|
newCoins := bc - amount
|
||||||
from.SetCoins(sc - amount)
|
assert(newCoins >= 0)
|
||||||
to.SetCoins(amount)
|
|
||||||
// save accounts...
|
from.SetCoins(bc - amount)
|
||||||
}
|
to.SetCoins(amount)
|
||||||
|
|
||||||
|
// save accounts...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Delegating
|
### Delegating
|
||||||
|
|
||||||
#### Continuously Vesting Accounts
|
For a vesting account attempting to delegate `D` coins, the following is performed:
|
||||||
|
|
||||||
For a continuous vesting account attempting to delegate `D` coins, the following
|
|
||||||
is performed:
|
|
||||||
|
|
||||||
1. Verify `BC >= D > 0`
|
1. Verify `BC >= D > 0`
|
||||||
2. Compute `X := min(max(V - DV, 0), D)` (portion of `D` that is vesting)
|
2. Compute `X := min(max(V - DV, 0), D)` (portion of `D` that is vesting)
|
||||||
|
@ -229,98 +216,66 @@ is performed:
|
||||||
6. Set `BC -= D`
|
6. Set `BC -= D`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (cva ContinuousVestingAccount) TrackDelegation(amount Coins) {
|
func (va VestingAccount) TrackDelegation(t Time, amount Coins) {
|
||||||
x := min(max(cva.GetVestingCoins() - cva.DelegatedVesting, 0), amount)
|
x := min(max(va.GetVestingCoins(t) - va.DelegatedVesting, 0), amount)
|
||||||
y := amount - x
|
y := amount - x
|
||||||
|
|
||||||
cva.DelegatedVesting += x
|
va.DelegatedVesting += x
|
||||||
cva.DelegatedFree += y
|
va.DelegatedFree += y
|
||||||
|
va.SetCoins(va.GetCoins() - amount)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Delayed/Discrete Vesting Accounts
|
#### Keepers/Handlers
|
||||||
|
|
||||||
For a delayed vesting account, it can only delegate with received coins and
|
|
||||||
coins that are fully vested so we only need to update `DF`.
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (dva DelayedVestingAccount) TrackDelegation(amount Coins) {
|
func DelegateCoins(t Time, from Account, amount Coins) {
|
||||||
dva.DelegatedFree += amount
|
bc := from.GetCoins()
|
||||||
}
|
assert(amount <= bc)
|
||||||
```
|
|
||||||
|
|
||||||
##### Keepers/Handlers
|
if isVesting(from) {
|
||||||
|
from.TrackDelegation(t, amount)
|
||||||
```go
|
|
||||||
func DelegateCoins(from Account, amount Coins) {
|
|
||||||
// canDelegate checks different semantics for continuous and delayed vesting
|
|
||||||
// accounts
|
|
||||||
if isVesting(from) && canDelegate(from) {
|
|
||||||
sc := from.GetCoins()
|
|
||||||
|
|
||||||
if amount <= sc {
|
|
||||||
from.TrackDelegation(amount)
|
|
||||||
from.SetCoins(sc - amount)
|
|
||||||
// save account...
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
sc := from.GetCoins()
|
from.SetCoins(sc - amount)
|
||||||
|
|
||||||
if amount <= sc {
|
|
||||||
from.SetCoins(sc - amount)
|
|
||||||
// save account...
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save account...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Undelegating
|
### Undelegating
|
||||||
|
|
||||||
#### Continuously Vesting Accounts
|
For a vesting account attempting to undelegate `D` coins, the following is performed:
|
||||||
|
|
||||||
For a continuous vesting account attempting to undelegate `D` coins, the
|
|
||||||
following is performed:
|
|
||||||
|
|
||||||
1. Verify `(DV + DF) >= D > 0` (this is simply a sanity check)
|
1. Verify `(DV + DF) >= D > 0` (this is simply a sanity check)
|
||||||
2. Compute `Y := min(DF, D)` (portion of `D` that should become free, prioritizing free coins)
|
2. Compute `X := min(DF, D)` (portion of `D` that should become free, prioritizing free coins)
|
||||||
3. Compute `X := D - Y` (portion of `D` that should remain vesting)
|
3. Compute `Y := D - X` (portion of `D` that should remain vesting)
|
||||||
4. Set `DV -= X`
|
4. Set `DF -= X`
|
||||||
5. Set `DF -= Y`
|
5. Set `DV -= Y`
|
||||||
6. Set `BC += D`
|
6. Set `BC += D`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (cva ContinuousVestingAccount) TrackUndelegation(amount Coins) {
|
func (cva ContinuousVestingAccount) TrackUndelegation(amount Coins) {
|
||||||
y := min(cva.DelegatedFree, amount)
|
x := min(cva.DelegatedFree, amount)
|
||||||
x := amount - y
|
y := amount - x
|
||||||
|
|
||||||
cva.DelegatedVesting -= x
|
cva.DelegatedFree -= x
|
||||||
cva.DelegatedFree -= y
|
cva.DelegatedVesting -= y
|
||||||
|
cva.SetCoins(cva.GetCoins() + amount)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: If a delegation is slashed, the continuous vesting account will end up
|
**Note**: If a delegation is slashed, the continuous vesting account will end up
|
||||||
with excess an `DV` amount, even after all its coins have vested. This is because
|
with an excess `DV` amount, even after all its coins have vested. This is because
|
||||||
undelegating free coins are prioritized.
|
undelegating free coins are prioritized.
|
||||||
|
|
||||||
##### Delayed/Discrete Vesting Accounts
|
#### Keepers/Handlers
|
||||||
|
|
||||||
For a delayed vesting account, it only needs to add back the `DF` amount since
|
|
||||||
the account is fully vested.
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (dva DelayedVestingAccount) TrackUndelegation(amount Coins) {
|
|
||||||
dva.DelegatedFree -= amount
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Keepers/Handlers
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func UndelegateCoins(to Account, amount Coins) {
|
func UndelegateCoins(to Account, amount Coins) {
|
||||||
if isVesting(to) {
|
if isVesting(to) {
|
||||||
if to.DelegatedFree + to.DelegatedVesting >= amount {
|
if to.DelegatedFree + to.DelegatedVesting >= amount {
|
||||||
to.TrackUndelegation(amount)
|
to.TrackUndelegation(amount)
|
||||||
AddCoins(to, amount)
|
|
||||||
// save account ...
|
// save account ...
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -346,16 +301,17 @@ See the above specification for full implementation details.
|
||||||
|
|
||||||
## Initializing at Genesis
|
## Initializing at Genesis
|
||||||
|
|
||||||
To initialize both vesting accounts and base accounts, the `GenesisAccount`
|
To initialize both vesting and base accounts, the `GenesisAccount` struct will
|
||||||
struct will include an `EndTime`. Accounts meant to be of type `BaseAccount` will
|
include an `EndTime`. Accounts meant to be of type `BaseAccount` will
|
||||||
have `EndTime = 0`. The `initChainer` method will parse the GenesisAccount into
|
have `EndTime = 0`. The `initChainer` method will parse the GenesisAccount into
|
||||||
BaseAccounts and VestingAccounts as appropriate.
|
BaseAccounts and VestingAccounts as appropriate.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type GenesisAccount struct {
|
type GenesisAccount struct {
|
||||||
Address sdk.AccAddress
|
Address sdk.AccAddress
|
||||||
GenesisCoins sdk.Coins
|
GenesisCoins sdk.Coins
|
||||||
EndTime int64
|
EndTime int64
|
||||||
|
StartTime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func initChainer() {
|
func initChainer() {
|
||||||
|
@ -365,11 +321,20 @@ func initChainer() {
|
||||||
Coins: genAcc.GenesisCoins,
|
Coins: genAcc.GenesisCoins,
|
||||||
}
|
}
|
||||||
|
|
||||||
if genAcc.EndTime != 0 {
|
if genAcc.StartTime != 0 && genAcc.EndTime != 0 {
|
||||||
vestingAccount := ContinuousVestingAccount{
|
vestingAccount := ContinuousVestingAccount{
|
||||||
BaseAccount: baseAccount,
|
BaseAccount: baseAccount,
|
||||||
OriginalVesting: genAcc.GenesisCoins,
|
OriginalVesting: genAcc.GenesisCoins,
|
||||||
StartTime: RequestInitChain.Time,
|
StartTime: RequestInitChain.Time,
|
||||||
|
StartTime: genAcc.StartTime,
|
||||||
|
EndTime: genAcc.EndTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
AddAccountToState(vestingAccount)
|
||||||
|
} else if genAcc.EndTime != 0 {
|
||||||
|
vestingAccount := DelayedVestingAccount{
|
||||||
|
BaseAccount: baseAccount,
|
||||||
|
OriginalVesting: genAcc.GenesisCoins,
|
||||||
EndTime: genAcc.EndTime,
|
EndTime: genAcc.EndTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,30 +362,30 @@ V' = 0
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Immediately receives 1 coin
|
1. Immediately receives 1 coin
|
||||||
```
|
```text
|
||||||
BC = 11
|
BC = 11
|
||||||
```
|
```
|
||||||
2. Time passes, 2 coins vest
|
2. Time passes, 2 coins vest
|
||||||
```
|
```text
|
||||||
V = 8
|
V = 8
|
||||||
V' = 2
|
V' = 2
|
||||||
```
|
```
|
||||||
3. Delegates 4 coins to validator A
|
3. Delegates 4 coins to validator A
|
||||||
```
|
```text
|
||||||
DV = 4
|
DV = 4
|
||||||
BC = 7
|
BC = 7
|
||||||
```
|
```
|
||||||
4. Sends 3 coins
|
4. Sends 3 coins
|
||||||
```
|
```text
|
||||||
BC = 4
|
BC = 4
|
||||||
```
|
```
|
||||||
5. More time passes, 2 more coins vest
|
5. More time passes, 2 more coins vest
|
||||||
```
|
```text
|
||||||
V = 6
|
V = 6
|
||||||
V' = 4
|
V' = 4
|
||||||
```
|
```
|
||||||
6. Sends 2 coins. At this point the account cannot send anymore until further coins vest or it receives additional coins. It can still however, delegate.
|
6. Sends 2 coins. At this point the account cannot send anymore until further coins vest or it receives additional coins. It can still however, delegate.
|
||||||
```
|
```text
|
||||||
BC = 2
|
BC = 2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -429,34 +394,34 @@ V' = 0
|
||||||
Same initial starting conditions as the simple example.
|
Same initial starting conditions as the simple example.
|
||||||
|
|
||||||
1. Time passes, 5 coins vest
|
1. Time passes, 5 coins vest
|
||||||
```
|
```text
|
||||||
V = 5
|
V = 5
|
||||||
V' = 5
|
V' = 5
|
||||||
```
|
```
|
||||||
2. Delegate 5 coins to validator A
|
2. Delegate 5 coins to validator A
|
||||||
```
|
```text
|
||||||
DV = 5
|
DV = 5
|
||||||
BC = 5
|
BC = 5
|
||||||
```
|
```
|
||||||
3. Delegate 5 coins to validator B
|
3. Delegate 5 coins to validator B
|
||||||
```
|
```text
|
||||||
DF = 5
|
DF = 5
|
||||||
BC = 0
|
BC = 0
|
||||||
```
|
```
|
||||||
4. Validator A gets slashed by 50%, making the delegation to A now worth 2.5 coins
|
4. Validator A gets slashed by 50%, making the delegation to A now worth 2.5 coins
|
||||||
5. Undelegate from validator A (2.5 coins)
|
5. Undelegate from validator A (2.5 coins)
|
||||||
```
|
```text
|
||||||
DF = 5 - 2.5 = 2.5
|
DF = 5 - 2.5 = 2.5
|
||||||
BC = 0 + 2.5 = 2.5
|
BC = 0 + 2.5 = 2.5
|
||||||
```
|
```
|
||||||
6. Undelegate from validator B (5 coins). The account at this point can only send 2.5 coins unless it receives more coins or until more coins vest. It can still however, delegate.
|
6. Undelegate from validator B (5 coins). The account at this point can only send 2.5 coins unless it receives more coins or until more coins vest. It can still however, delegate.
|
||||||
```
|
```text
|
||||||
DV = 5 - 2.5 = 2.5
|
DV = 5 - 2.5 = 2.5
|
||||||
DF = 2.5 - 2.5 = 0
|
DF = 2.5 - 2.5 = 0
|
||||||
BC = 2.5 + 5 = 7.5
|
BC = 2.5 + 5 = 7.5
|
||||||
```
|
```
|
||||||
|
|
||||||
Notice how we have an excess amount of `DV`.
|
Notice how we have an excess amount of `DV`.
|
||||||
|
|
||||||
## Glossary
|
## Glossary
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ func (coin Coin) Minus(coinB Coin) Coin {
|
||||||
}
|
}
|
||||||
|
|
||||||
res := Coin{coin.Denom, coin.Amount.Sub(coinB.Amount)}
|
res := Coin{coin.Denom, coin.Amount.Sub(coinB.Amount)}
|
||||||
if !res.IsNotNegative() {
|
if res.IsNegative() {
|
||||||
panic("negative count amount")
|
panic("negative count amount")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,14 +107,14 @@ func (coin Coin) Minus(coinB Coin) Coin {
|
||||||
//
|
//
|
||||||
// TODO: Remove once unsigned integers are used.
|
// TODO: Remove once unsigned integers are used.
|
||||||
func (coin Coin) IsPositive() bool {
|
func (coin Coin) IsPositive() bool {
|
||||||
return (coin.Amount.Sign() == 1)
|
return coin.Amount.Sign() == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNotNegative returns true if coin amount is not negative and false otherwise.
|
// IsNegative returns true if the coin amount is negative and false otherwise.
|
||||||
//
|
//
|
||||||
// TODO: Remove once unsigned integers are used.
|
// TODO: Remove once unsigned integers are used.
|
||||||
func (coin Coin) IsNotNegative() bool {
|
func (coin Coin) IsNegative() bool {
|
||||||
return (coin.Amount.Sign() != -1)
|
return coin.Amount.Sign() == -1
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -425,7 +425,7 @@ func (coins Coins) IsNotNegative() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, coin := range coins {
|
for _, coin := range coins {
|
||||||
if !coin.IsNotNegative() {
|
if coin.IsNegative() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -465,6 +465,12 @@ func removeZeroCoins(coins Coins) Coins {
|
||||||
return coins[:i]
|
return coins[:i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copyCoins(coins Coins) Coins {
|
||||||
|
copyCoins := make(Coins, len(coins))
|
||||||
|
copy(copyCoins, coins)
|
||||||
|
return copyCoins
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Sort interface
|
// Sort interface
|
||||||
|
|
||||||
|
|
19
types/int.go
19
types/int.go
|
@ -37,14 +37,16 @@ func min(i *big.Int, i2 *big.Int) *big.Int {
|
||||||
if i.Cmp(i2) == 1 {
|
if i.Cmp(i2) == 1 {
|
||||||
return new(big.Int).Set(i2)
|
return new(big.Int).Set(i2)
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(big.Int).Set(i)
|
return new(big.Int).Set(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func max(i *big.Int, i2 *big.Int) *big.Int {
|
func max(i *big.Int, i2 *big.Int) *big.Int {
|
||||||
if i.Cmp(i2) == 1 {
|
if i.Cmp(i2) == -1 {
|
||||||
return new(big.Int).Set(i)
|
return new(big.Int).Set(i2)
|
||||||
}
|
}
|
||||||
return new(big.Int).Set(i2)
|
|
||||||
|
return new(big.Int).Set(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalAmino for custom encoding scheme
|
// MarshalAmino for custom encoding scheme
|
||||||
|
@ -276,9 +278,9 @@ func MinInt(i1, i2 Int) Int {
|
||||||
return Int{min(i1.BigInt(), i2.BigInt())}
|
return Int{min(i1.BigInt(), i2.BigInt())}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the maximum of the ints
|
// MaxInt returns the maximum between two integers.
|
||||||
func MaxInt(i1, i2 Int) Int {
|
func MaxInt(i, i2 Int) Int {
|
||||||
return Int{max(i1.BigInt(), i2.BigInt())}
|
return Int{max(i.BigInt(), i2.BigInt())}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Human readable string
|
// Human readable string
|
||||||
|
@ -518,6 +520,11 @@ func MinUint(i1, i2 Uint) Uint {
|
||||||
return Uint{min(i1.BigInt(), i2.BigInt())}
|
return Uint{min(i1.BigInt(), i2.BigInt())}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MaxUint returns the maximum between two unsigned integers.
|
||||||
|
func MaxUint(i, i2 Uint) Uint {
|
||||||
|
return Uint{max(i.BigInt(), i2.BigInt())}
|
||||||
|
}
|
||||||
|
|
||||||
// Human readable string
|
// Human readable string
|
||||||
func (i Uint) String() string {
|
func (i Uint) String() string {
|
||||||
return i.i.String()
|
return i.i.String()
|
||||||
|
|
|
@ -145,6 +145,13 @@ func minint(i1, i2 int64) int64 {
|
||||||
return i2
|
return i2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func maxint(i1, i2 int64) int64 {
|
||||||
|
if i1 > i2 {
|
||||||
|
return i1
|
||||||
|
}
|
||||||
|
return i2
|
||||||
|
}
|
||||||
|
|
||||||
func TestArithInt(t *testing.T) {
|
func TestArithInt(t *testing.T) {
|
||||||
for d := 0; d < 1000; d++ {
|
for d := 0; d < 1000; d++ {
|
||||||
n1 := int64(rand.Int31())
|
n1 := int64(rand.Int31())
|
||||||
|
@ -165,6 +172,7 @@ func TestArithInt(t *testing.T) {
|
||||||
{i1.MulRaw(n2), n1 * n2},
|
{i1.MulRaw(n2), n1 * n2},
|
||||||
{i1.DivRaw(n2), n1 / n2},
|
{i1.DivRaw(n2), n1 / n2},
|
||||||
{MinInt(i1, i2), minint(n1, n2)},
|
{MinInt(i1, i2), minint(n1, n2)},
|
||||||
|
{MaxInt(i1, i2), maxint(n1, n2)},
|
||||||
{i1.Neg(), -n1},
|
{i1.Neg(), -n1},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,6 +234,13 @@ func minuint(i1, i2 uint64) uint64 {
|
||||||
return i2
|
return i2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func maxuint(i1, i2 uint64) uint64 {
|
||||||
|
if i1 > i2 {
|
||||||
|
return i1
|
||||||
|
}
|
||||||
|
return i2
|
||||||
|
}
|
||||||
|
|
||||||
func TestArithUint(t *testing.T) {
|
func TestArithUint(t *testing.T) {
|
||||||
for d := 0; d < 1000; d++ {
|
for d := 0; d < 1000; d++ {
|
||||||
n1 := uint64(rand.Uint32())
|
n1 := uint64(rand.Uint32())
|
||||||
|
@ -244,6 +259,7 @@ func TestArithUint(t *testing.T) {
|
||||||
{i1.MulRaw(n2), n1 * n2},
|
{i1.MulRaw(n2), n1 * n2},
|
||||||
{i1.DivRaw(n2), n1 / n2},
|
{i1.DivRaw(n2), n1 / n2},
|
||||||
{MinUint(i1, i2), minuint(n1, n2)},
|
{MinUint(i1, i2), minuint(n1, n2)},
|
||||||
|
{MaxUint(i1, i2), maxuint(n1, n2)},
|
||||||
}
|
}
|
||||||
|
|
||||||
for tcnum, tc := range cases {
|
for tcnum, tc := range cases {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
|
|
||||||
|
@ -30,12 +31,29 @@ type Account interface {
|
||||||
|
|
||||||
GetCoins() sdk.Coins
|
GetCoins() sdk.Coins
|
||||||
SetCoins(sdk.Coins) error
|
SetCoins(sdk.Coins) error
|
||||||
|
|
||||||
|
// Calculates the amount of coins that can be sent to other accounts given
|
||||||
|
// the current time.
|
||||||
|
SpendableCoins(blockTime time.Time) sdk.Coins
|
||||||
|
}
|
||||||
|
|
||||||
|
// VestingAccount defines an account type that vests coins via a vesting schedule.
|
||||||
|
type VestingAccount interface {
|
||||||
|
Account
|
||||||
|
|
||||||
|
// Delegation and undelegation accounting that returns the resulting base
|
||||||
|
// coins amount.
|
||||||
|
TrackDelegation(blockTime time.Time, amount sdk.Coins)
|
||||||
|
TrackUndelegation(amount sdk.Coins)
|
||||||
|
|
||||||
|
GetVestedCoins(blockTime time.Time) sdk.Coins
|
||||||
|
GetVestingCoins(blockTime time.Time) sdk.Coins
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountDecoder unmarshals account bytes
|
// AccountDecoder unmarshals account bytes
|
||||||
type AccountDecoder func(accountBytes []byte) (Account, error)
|
type AccountDecoder func(accountBytes []byte) (Account, error)
|
||||||
|
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// BaseAccount
|
// BaseAccount
|
||||||
|
|
||||||
var _ Account = (*BaseAccount)(nil)
|
var _ Account = (*BaseAccount)(nil)
|
||||||
|
@ -122,12 +140,323 @@ func (acc *BaseAccount) SetSequence(seq uint64) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------
|
// SpendableCoins returns the total set of spendable coins. For a base account,
|
||||||
// Wire
|
// this is simply the base coins.
|
||||||
|
func (acc *BaseAccount) SpendableCoins(_ time.Time) sdk.Coins {
|
||||||
|
return acc.GetCoins()
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Base Vesting Account
|
||||||
|
|
||||||
|
// BaseVestingAccount implements the VestingAccount interface. It contains all
|
||||||
|
// the necessary fields needed for any vesting account implementation.
|
||||||
|
type BaseVestingAccount struct {
|
||||||
|
*BaseAccount
|
||||||
|
|
||||||
|
OriginalVesting sdk.Coins // coins in account upon initialization
|
||||||
|
DelegatedFree sdk.Coins // coins that are vested and delegated
|
||||||
|
DelegatedVesting sdk.Coins // coins that vesting and delegated
|
||||||
|
|
||||||
|
EndTime time.Time // when the coins become unlocked
|
||||||
|
}
|
||||||
|
|
||||||
|
// spendableCoins returns all the spendable coins for a vesting account given a
|
||||||
|
// set of vesting coins.
|
||||||
|
//
|
||||||
|
// CONTRACT: The account's coins, delegated vesting coins, vestingCoins must be
|
||||||
|
// sorted.
|
||||||
|
func (bva BaseVestingAccount) spendableCoins(vestingCoins sdk.Coins) sdk.Coins {
|
||||||
|
var spendableCoins sdk.Coins
|
||||||
|
bc := bva.GetCoins()
|
||||||
|
|
||||||
|
j, k := 0, 0
|
||||||
|
for _, coin := range bc {
|
||||||
|
// zip/lineup all coins by their denomination to provide O(n) time
|
||||||
|
for j < len(vestingCoins) && vestingCoins[j].Denom != coin.Denom {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
for k < len(bva.DelegatedVesting) && bva.DelegatedVesting[k].Denom != coin.Denom {
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
|
||||||
|
baseAmt := coin.Amount
|
||||||
|
|
||||||
|
vestingAmt := sdk.ZeroInt()
|
||||||
|
if len(vestingCoins) > 0 {
|
||||||
|
vestingAmt = vestingCoins[j].Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
delVestingAmt := sdk.ZeroInt()
|
||||||
|
if len(bva.DelegatedVesting) > 0 {
|
||||||
|
delVestingAmt = bva.DelegatedVesting[k].Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute min((BC + DV) - V, BC) per the specification
|
||||||
|
min := sdk.MinInt(baseAmt.Add(delVestingAmt).Sub(vestingAmt), baseAmt)
|
||||||
|
spendableCoin := sdk.NewCoin(coin.Denom, min)
|
||||||
|
|
||||||
|
if !spendableCoin.IsZero() {
|
||||||
|
spendableCoins = spendableCoins.Plus(sdk.Coins{spendableCoin})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return spendableCoins
|
||||||
|
}
|
||||||
|
|
||||||
|
// trackDelegation tracks a delegation amount for any given vesting account type
|
||||||
|
// given the amount of coins currently vesting. It returns the resulting base
|
||||||
|
// coins.
|
||||||
|
//
|
||||||
|
// CONTRACT: The account's coins, delegation coins, vesting coins, and delegated
|
||||||
|
// vesting coins must be sorted.
|
||||||
|
func (bva *BaseVestingAccount) trackDelegation(vestingCoins, amount sdk.Coins) {
|
||||||
|
bc := bva.GetCoins()
|
||||||
|
|
||||||
|
i, j, k := 0, 0, 0
|
||||||
|
for _, coin := range amount {
|
||||||
|
// zip/lineup all coins by their denomination to provide O(n) time
|
||||||
|
for i < len(bc) && bc[i].Denom != coin.Denom {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for j < len(vestingCoins) && vestingCoins[j].Denom != coin.Denom {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
for k < len(bva.DelegatedVesting) && bva.DelegatedVesting[k].Denom != coin.Denom {
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
|
||||||
|
baseAmt := sdk.ZeroInt()
|
||||||
|
if len(bc) > 0 {
|
||||||
|
baseAmt = bc[i].Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
vestingAmt := sdk.ZeroInt()
|
||||||
|
if len(vestingCoins) > 0 {
|
||||||
|
vestingAmt = vestingCoins[j].Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
delVestingAmt := sdk.ZeroInt()
|
||||||
|
if len(bva.DelegatedVesting) > 0 {
|
||||||
|
delVestingAmt = bva.DelegatedVesting[k].Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic if the delegation amount is zero or if the base coins does not
|
||||||
|
// exceed the desired delegation amount.
|
||||||
|
if coin.Amount.IsZero() || baseAmt.LT(coin.Amount) {
|
||||||
|
panic("delegation attempt with zero coins or insufficient funds")
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute x and y per the specification, where:
|
||||||
|
// X := min(max(V - DV, 0), D)
|
||||||
|
// Y := D - X
|
||||||
|
x := sdk.MinInt(sdk.MaxInt(vestingAmt.Sub(delVestingAmt), sdk.ZeroInt()), coin.Amount)
|
||||||
|
y := coin.Amount.Sub(x)
|
||||||
|
|
||||||
|
if !x.IsZero() {
|
||||||
|
xCoin := sdk.NewCoin(coin.Denom, x)
|
||||||
|
bva.DelegatedVesting = bva.DelegatedVesting.Plus(sdk.Coins{xCoin})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !y.IsZero() {
|
||||||
|
yCoin := sdk.NewCoin(coin.Denom, y)
|
||||||
|
bva.DelegatedFree = bva.DelegatedFree.Plus(sdk.Coins{yCoin})
|
||||||
|
}
|
||||||
|
|
||||||
|
bva.Coins = bva.Coins.Minus(sdk.Coins{coin})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackUndelegation tracks an undelegation amount by setting the necessary
|
||||||
|
// values by which delegated vesting and delegated vesting need to decrease and
|
||||||
|
// by which amount the base coins need to increase. The resulting base coins are
|
||||||
|
// returned.
|
||||||
|
//
|
||||||
|
// CONTRACT: The account's coins and undelegation coins must be sorted.
|
||||||
|
func (bva *BaseVestingAccount) TrackUndelegation(amount sdk.Coins) {
|
||||||
|
i := 0
|
||||||
|
for _, coin := range amount {
|
||||||
|
// panic if the undelegation amount is zero
|
||||||
|
if coin.Amount.IsZero() {
|
||||||
|
panic("undelegation attempt with zero coins")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i < len(bva.DelegatedFree) && bva.DelegatedFree[i].Denom != coin.Denom {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
delegatedFree := sdk.ZeroInt()
|
||||||
|
if len(bva.DelegatedFree) > 0 {
|
||||||
|
delegatedFree = bva.DelegatedFree[i].Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute x and y per the specification, where:
|
||||||
|
// X := min(DF, D)
|
||||||
|
// Y := D - X
|
||||||
|
x := sdk.MinInt(delegatedFree, coin.Amount)
|
||||||
|
y := coin.Amount.Sub(x)
|
||||||
|
|
||||||
|
if !x.IsZero() {
|
||||||
|
xCoin := sdk.NewCoin(coin.Denom, x)
|
||||||
|
bva.DelegatedFree = bva.DelegatedFree.Minus(sdk.Coins{xCoin})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !y.IsZero() {
|
||||||
|
yCoin := sdk.NewCoin(coin.Denom, y)
|
||||||
|
bva.DelegatedVesting = bva.DelegatedVesting.Minus(sdk.Coins{yCoin})
|
||||||
|
}
|
||||||
|
|
||||||
|
bva.Coins = bva.Coins.Plus(sdk.Coins{coin})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Continuous Vesting Account
|
||||||
|
|
||||||
|
var _ VestingAccount = (*ContinuousVestingAccount)(nil)
|
||||||
|
|
||||||
|
// ContinuousVestingAccount implements the VestingAccount interface. It
|
||||||
|
// continuously vests by unlocking coins linearly with respect to time.
|
||||||
|
type ContinuousVestingAccount struct {
|
||||||
|
*BaseVestingAccount
|
||||||
|
|
||||||
|
StartTime time.Time // when the coins start to vest
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContinuousVestingAccount(
|
||||||
|
addr sdk.AccAddress, origCoins sdk.Coins, StartTime, EndTime time.Time,
|
||||||
|
) *ContinuousVestingAccount {
|
||||||
|
|
||||||
|
baseAcc := &BaseAccount{
|
||||||
|
Address: addr,
|
||||||
|
Coins: origCoins,
|
||||||
|
}
|
||||||
|
|
||||||
|
baseVestingAcc := &BaseVestingAccount{
|
||||||
|
BaseAccount: baseAcc,
|
||||||
|
OriginalVesting: origCoins,
|
||||||
|
EndTime: EndTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ContinuousVestingAccount{
|
||||||
|
StartTime: StartTime,
|
||||||
|
BaseVestingAccount: baseVestingAcc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVestedCoins returns the total number of vested coins. If no coins are vested,
|
||||||
|
// nil is returned.
|
||||||
|
func (cva ContinuousVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins {
|
||||||
|
var vestedCoins sdk.Coins
|
||||||
|
|
||||||
|
// We must handle the case where the start time for a vesting account has
|
||||||
|
// been set into the future or when the start of the chain is not exactly
|
||||||
|
// known.
|
||||||
|
if blockTime.Unix() <= cva.StartTime.Unix() {
|
||||||
|
return vestedCoins
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the vesting scalar
|
||||||
|
x := int64(blockTime.Sub(cva.StartTime).Seconds())
|
||||||
|
y := int64(cva.EndTime.Sub(cva.StartTime).Seconds())
|
||||||
|
s := sdk.NewDec(x).Quo(sdk.NewDec(y))
|
||||||
|
|
||||||
|
for _, ovc := range cva.OriginalVesting {
|
||||||
|
vestedAmt := sdk.NewDecFromInt(ovc.Amount).Mul(s).RoundInt()
|
||||||
|
vestedCoins = append(vestedCoins, sdk.NewCoin(ovc.Denom, vestedAmt))
|
||||||
|
}
|
||||||
|
|
||||||
|
return vestedCoins
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVestingCoins returns the total number of vesting coins. If no coins are
|
||||||
|
// vesting, nil is returned.
|
||||||
|
func (cva ContinuousVestingAccount) GetVestingCoins(blockTime time.Time) sdk.Coins {
|
||||||
|
return cva.OriginalVesting.Minus(cva.GetVestedCoins(blockTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpendableCoins returns the total number of spendable coins per denom for a
|
||||||
|
// continuous vesting account.
|
||||||
|
func (cva ContinuousVestingAccount) SpendableCoins(blockTime time.Time) sdk.Coins {
|
||||||
|
return cva.spendableCoins(cva.GetVestingCoins(blockTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackDelegation tracks a desired delegation amount by setting the appropriate
|
||||||
|
// values for the amount of delegated vesting, delegated free, and reducing the
|
||||||
|
// overall amount of base coins.
|
||||||
|
func (cva *ContinuousVestingAccount) TrackDelegation(blockTime time.Time, amount sdk.Coins) {
|
||||||
|
cva.trackDelegation(cva.GetVestingCoins(blockTime), amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Delayed Vesting Account
|
||||||
|
|
||||||
|
var _ VestingAccount = (*DelayedVestingAccount)(nil)
|
||||||
|
|
||||||
|
// DelayedVestingAccount implements the VestingAccount interface. It vests all
|
||||||
|
// coins after a specific time, but non prior. In other words, it keeps them
|
||||||
|
// locked until a specified time.
|
||||||
|
type DelayedVestingAccount struct {
|
||||||
|
*BaseVestingAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDelayedVestingAccount(
|
||||||
|
addr sdk.AccAddress, origCoins sdk.Coins, EndTime time.Time,
|
||||||
|
) *DelayedVestingAccount {
|
||||||
|
|
||||||
|
baseAcc := &BaseAccount{
|
||||||
|
Address: addr,
|
||||||
|
Coins: origCoins,
|
||||||
|
}
|
||||||
|
|
||||||
|
baseVestingAcc := &BaseVestingAccount{
|
||||||
|
BaseAccount: baseAcc,
|
||||||
|
OriginalVesting: origCoins,
|
||||||
|
EndTime: EndTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DelayedVestingAccount{baseVestingAcc}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVestedCoins returns the total amount of vested coins for a delayed vesting
|
||||||
|
// account. All coins are only vested once the schedule has elapsed.
|
||||||
|
func (dva DelayedVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins {
|
||||||
|
if blockTime.Unix() >= dva.EndTime.Unix() {
|
||||||
|
return dva.OriginalVesting
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVestingCoins returns the total number of vesting coins for a delayed
|
||||||
|
// vesting account.
|
||||||
|
func (dva DelayedVestingAccount) GetVestingCoins(blockTime time.Time) sdk.Coins {
|
||||||
|
return dva.OriginalVesting.Minus(dva.GetVestedCoins(blockTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpendableCoins returns the total number of spendable coins for a delayed
|
||||||
|
// vesting account.
|
||||||
|
func (dva DelayedVestingAccount) SpendableCoins(blockTime time.Time) sdk.Coins {
|
||||||
|
return dva.spendableCoins(dva.GetVestingCoins(blockTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackDelegation tracks a desired delegation amount by setting the appropriate
|
||||||
|
// values for the amount of delegated vesting, delegated free, and reducing the
|
||||||
|
// overall amount of base coins.
|
||||||
|
func (dva *DelayedVestingAccount) TrackDelegation(blockTime time.Time, amount sdk.Coins) {
|
||||||
|
dva.trackDelegation(dva.GetVestingCoins(blockTime), amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Codec
|
||||||
|
|
||||||
// Most users shouldn't use this, but this comes in handy for tests.
|
// Most users shouldn't use this, but this comes in handy for tests.
|
||||||
func RegisterBaseAccount(cdc *codec.Codec) {
|
func RegisterBaseAccount(cdc *codec.Codec) {
|
||||||
cdc.RegisterInterface((*Account)(nil), nil)
|
cdc.RegisterInterface((*Account)(nil), nil)
|
||||||
|
cdc.RegisterInterface((*VestingAccount)(nil), nil)
|
||||||
cdc.RegisterConcrete(&BaseAccount{}, "cosmos-sdk/BaseAccount", nil)
|
cdc.RegisterConcrete(&BaseAccount{}, "cosmos-sdk/BaseAccount", nil)
|
||||||
|
cdc.RegisterConcrete(&BaseVestingAccount{}, "cosmos-sdk/BaseVestingAccount", nil)
|
||||||
|
cdc.RegisterConcrete(&ContinuousVestingAccount{}, "cosmos-sdk/ContinuousVestingAccount", nil)
|
||||||
|
cdc.RegisterConcrete(&DelayedVestingAccount{}, "cosmos-sdk/DelayedVestingAccount", nil)
|
||||||
codec.RegisterCrypto(cdc)
|
codec.RegisterCrypto(cdc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,18 @@ package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var testDenom = "testdenom"
|
||||||
|
|
||||||
func TestBaseAddressPubKey(t *testing.T) {
|
func TestBaseAddressPubKey(t *testing.T) {
|
||||||
_, pub1, addr1 := keyPubAddr()
|
_, pub1, addr1 := keyPubAddr()
|
||||||
_, pub2, addr2 := keyPubAddr()
|
_, pub2, addr2 := keyPubAddr()
|
||||||
|
@ -96,3 +101,341 @@ func TestBaseAccountMarshal(t *testing.T) {
|
||||||
err = cdc.UnmarshalBinaryLengthPrefixed(b[:len(b)/2], &acc2)
|
err = cdc.UnmarshalBinaryLengthPrefixed(b[:len(b)/2], &acc2)
|
||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetVestedCoinsContVestingAcc(t *testing.T) {
|
||||||
|
now := tmtime.Now()
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
_, _, addr := keyPubAddr()
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)}
|
||||||
|
cva := NewContinuousVestingAccount(addr, origCoins, now, endTime)
|
||||||
|
|
||||||
|
// require no coins vested in the very beginning of the vesting schedule
|
||||||
|
vestedCoins := cva.GetVestedCoins(now)
|
||||||
|
require.Nil(t, vestedCoins)
|
||||||
|
|
||||||
|
// require all coins vested at the end of the vesting schedule
|
||||||
|
vestedCoins = cva.GetVestedCoins(endTime)
|
||||||
|
require.Equal(t, origCoins, vestedCoins)
|
||||||
|
|
||||||
|
// require 50% of coins vested
|
||||||
|
vestedCoins = cva.GetVestedCoins(now.Add(12 * time.Hour))
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, vestedCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVestingCoinsContVestingAcc(t *testing.T) {
|
||||||
|
now := tmtime.Now()
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
_, _, addr := keyPubAddr()
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)}
|
||||||
|
cva := NewContinuousVestingAccount(addr, origCoins, now, endTime)
|
||||||
|
|
||||||
|
// require all coins vesting in the beginning of the vesting schedule
|
||||||
|
vestingCoins := cva.GetVestingCoins(now)
|
||||||
|
require.Equal(t, origCoins, vestingCoins)
|
||||||
|
|
||||||
|
// require no coins vesting at the end of the vesting schedule
|
||||||
|
vestingCoins = cva.GetVestingCoins(endTime)
|
||||||
|
require.Nil(t, vestingCoins)
|
||||||
|
|
||||||
|
// require 50% of coins vesting
|
||||||
|
vestingCoins = cva.GetVestingCoins(now.Add(12 * time.Hour))
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, vestingCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpendableCoinsContVestingAcc(t *testing.T) {
|
||||||
|
now := tmtime.Now()
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
_, _, addr := keyPubAddr()
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)}
|
||||||
|
cva := NewContinuousVestingAccount(addr, origCoins, now, endTime)
|
||||||
|
|
||||||
|
// require that there exist no spendable coins in the beginning of the
|
||||||
|
// vesting schedule
|
||||||
|
spendableCoins := cva.SpendableCoins(now)
|
||||||
|
require.Nil(t, spendableCoins)
|
||||||
|
|
||||||
|
// require that all original coins are spendable at the end of the vesting
|
||||||
|
// schedule
|
||||||
|
spendableCoins = cva.SpendableCoins(endTime)
|
||||||
|
require.Equal(t, origCoins, spendableCoins)
|
||||||
|
|
||||||
|
// require that all vested coins (50%) are spendable
|
||||||
|
spendableCoins = cva.SpendableCoins(now.Add(12 * time.Hour))
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, spendableCoins)
|
||||||
|
|
||||||
|
// receive some coins
|
||||||
|
recvAmt := sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}
|
||||||
|
cva.SetCoins(cva.GetCoins().Plus(recvAmt))
|
||||||
|
|
||||||
|
// require that all vested coins (50%) are spendable plus any received
|
||||||
|
spendableCoins = cva.SpendableCoins(now.Add(12 * time.Hour))
|
||||||
|
require.Equal(t, origCoins, spendableCoins)
|
||||||
|
|
||||||
|
// spend all spendable coins
|
||||||
|
cva.SetCoins(cva.GetCoins().Minus(spendableCoins))
|
||||||
|
|
||||||
|
// require that no more coins are spendable
|
||||||
|
spendableCoins = cva.SpendableCoins(now.Add(12 * time.Hour))
|
||||||
|
require.Nil(t, spendableCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrackDelegationContVestingAcc(t *testing.T) {
|
||||||
|
now := tmtime.Now()
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
_, _, addr := keyPubAddr()
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)}
|
||||||
|
|
||||||
|
// require the ability to delegate all vesting coins
|
||||||
|
cva := NewContinuousVestingAccount(addr, origCoins, now, endTime)
|
||||||
|
cva.TrackDelegation(now, origCoins)
|
||||||
|
require.Equal(t, origCoins, cva.DelegatedVesting)
|
||||||
|
require.Nil(t, cva.DelegatedFree)
|
||||||
|
require.Nil(t, cva.GetCoins())
|
||||||
|
|
||||||
|
// require the ability to delegate all vested coins
|
||||||
|
cva = NewContinuousVestingAccount(addr, origCoins, now, endTime)
|
||||||
|
cva.TrackDelegation(endTime, origCoins)
|
||||||
|
require.Nil(t, cva.DelegatedVesting)
|
||||||
|
require.Equal(t, origCoins, cva.DelegatedFree)
|
||||||
|
require.Nil(t, cva.GetCoins())
|
||||||
|
|
||||||
|
// require the ability to delegate all vesting coins (50%) and all vested coins (50%)
|
||||||
|
cva = NewContinuousVestingAccount(addr, origCoins, now, endTime)
|
||||||
|
cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)})
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedVesting)
|
||||||
|
require.Nil(t, cva.DelegatedFree)
|
||||||
|
|
||||||
|
cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)})
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedVesting)
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedFree)
|
||||||
|
require.Nil(t, cva.GetCoins())
|
||||||
|
|
||||||
|
// require no modifications when delegation amount is zero or not enough funds
|
||||||
|
cva = NewContinuousVestingAccount(addr, origCoins, now, endTime)
|
||||||
|
require.Panics(t, func() {
|
||||||
|
cva.TrackDelegation(endTime, sdk.Coins{sdk.NewInt64Coin(testDenom, 1000000)})
|
||||||
|
})
|
||||||
|
require.Nil(t, cva.DelegatedVesting)
|
||||||
|
require.Nil(t, cva.DelegatedFree)
|
||||||
|
require.Equal(t, origCoins, cva.GetCoins())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrackUndelegationContVestingAcc(t *testing.T) {
|
||||||
|
now := tmtime.Now()
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
_, _, addr := keyPubAddr()
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)}
|
||||||
|
|
||||||
|
// require the ability to undelegate all vesting coins
|
||||||
|
cva := NewContinuousVestingAccount(addr, origCoins, now, endTime)
|
||||||
|
cva.TrackDelegation(now, origCoins)
|
||||||
|
cva.TrackUndelegation(origCoins)
|
||||||
|
require.Nil(t, cva.DelegatedFree)
|
||||||
|
require.Nil(t, cva.DelegatedVesting)
|
||||||
|
require.Equal(t, origCoins, cva.GetCoins())
|
||||||
|
|
||||||
|
// require the ability to undelegate all vested coins
|
||||||
|
cva = NewContinuousVestingAccount(addr, origCoins, now, endTime)
|
||||||
|
cva.TrackDelegation(endTime, origCoins)
|
||||||
|
cva.TrackUndelegation(origCoins)
|
||||||
|
require.Nil(t, cva.DelegatedFree)
|
||||||
|
require.Nil(t, cva.DelegatedVesting)
|
||||||
|
require.Equal(t, origCoins, cva.GetCoins())
|
||||||
|
|
||||||
|
// require no modifications when the undelegation amount is zero
|
||||||
|
cva = NewContinuousVestingAccount(addr, origCoins, now, endTime)
|
||||||
|
|
||||||
|
require.Panics(t, func() {
|
||||||
|
cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 0)})
|
||||||
|
})
|
||||||
|
require.Nil(t, cva.DelegatedFree)
|
||||||
|
require.Nil(t, cva.DelegatedVesting)
|
||||||
|
require.Equal(t, origCoins, cva.GetCoins())
|
||||||
|
|
||||||
|
// vest 50% and delegate to two validators
|
||||||
|
cva = NewContinuousVestingAccount(addr, origCoins, now, endTime)
|
||||||
|
cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)})
|
||||||
|
cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)})
|
||||||
|
|
||||||
|
// undelegate from one validator that got slashed 50%
|
||||||
|
cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 25)})
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, cva.DelegatedFree)
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedVesting)
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, cva.GetCoins())
|
||||||
|
|
||||||
|
// undelegate from the other validator that did not get slashed
|
||||||
|
cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 50)})
|
||||||
|
require.Nil(t, cva.DelegatedFree)
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, cva.DelegatedVesting)
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 75)}, cva.GetCoins())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVestedCoinsDelVestingAcc(t *testing.T) {
|
||||||
|
now := tmtime.Now()
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
_, _, addr := keyPubAddr()
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)}
|
||||||
|
|
||||||
|
// require no coins are vested until schedule maturation
|
||||||
|
dva := NewDelayedVestingAccount(addr, origCoins, endTime)
|
||||||
|
vestedCoins := dva.GetVestedCoins(now)
|
||||||
|
require.Nil(t, vestedCoins)
|
||||||
|
|
||||||
|
// require all coins be vested at schedule maturation
|
||||||
|
vestedCoins = dva.GetVestedCoins(endTime)
|
||||||
|
require.Equal(t, origCoins, vestedCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVestingCoinsDelVestingAcc(t *testing.T) {
|
||||||
|
now := tmtime.Now()
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
_, _, addr := keyPubAddr()
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)}
|
||||||
|
|
||||||
|
// require all coins vesting at the beginning of the schedule
|
||||||
|
dva := NewDelayedVestingAccount(addr, origCoins, endTime)
|
||||||
|
vestingCoins := dva.GetVestingCoins(now)
|
||||||
|
require.Equal(t, origCoins, vestingCoins)
|
||||||
|
|
||||||
|
// require no coins vesting at schedule maturation
|
||||||
|
vestingCoins = dva.GetVestingCoins(endTime)
|
||||||
|
require.Nil(t, vestingCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpendableCoinsDelVestingAcc(t *testing.T) {
|
||||||
|
now := tmtime.Now()
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
_, _, addr := keyPubAddr()
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)}
|
||||||
|
|
||||||
|
// require that no coins are spendable in the beginning of the vesting
|
||||||
|
// schedule
|
||||||
|
dva := NewDelayedVestingAccount(addr, origCoins, endTime)
|
||||||
|
spendableCoins := dva.SpendableCoins(now)
|
||||||
|
require.Nil(t, spendableCoins)
|
||||||
|
|
||||||
|
// require that all coins are spendable after the maturation of the vesting
|
||||||
|
// schedule
|
||||||
|
spendableCoins = dva.SpendableCoins(endTime)
|
||||||
|
require.Equal(t, origCoins, spendableCoins)
|
||||||
|
|
||||||
|
// require that all coins are still vesting after some time
|
||||||
|
spendableCoins = dva.SpendableCoins(now.Add(12 * time.Hour))
|
||||||
|
require.Nil(t, spendableCoins)
|
||||||
|
|
||||||
|
// receive some coins
|
||||||
|
recvAmt := sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}
|
||||||
|
dva.SetCoins(dva.GetCoins().Plus(recvAmt))
|
||||||
|
|
||||||
|
// require that only received coins are spendable since the account is still
|
||||||
|
// vesting
|
||||||
|
spendableCoins = dva.SpendableCoins(now.Add(12 * time.Hour))
|
||||||
|
require.Equal(t, recvAmt, spendableCoins)
|
||||||
|
|
||||||
|
// spend all spendable coins
|
||||||
|
dva.SetCoins(dva.GetCoins().Minus(spendableCoins))
|
||||||
|
|
||||||
|
// require that no more coins are spendable
|
||||||
|
spendableCoins = dva.SpendableCoins(now.Add(12 * time.Hour))
|
||||||
|
require.Nil(t, spendableCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrackDelegationDelVestingAcc(t *testing.T) {
|
||||||
|
now := tmtime.Now()
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
_, _, addr := keyPubAddr()
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)}
|
||||||
|
|
||||||
|
// require the ability to delegate all vesting coins
|
||||||
|
dva := NewDelayedVestingAccount(addr, origCoins, endTime)
|
||||||
|
dva.TrackDelegation(now, origCoins)
|
||||||
|
require.Equal(t, origCoins, dva.DelegatedVesting)
|
||||||
|
require.Nil(t, dva.DelegatedFree)
|
||||||
|
require.Nil(t, dva.GetCoins())
|
||||||
|
|
||||||
|
// require the ability to delegate all vested coins
|
||||||
|
dva = NewDelayedVestingAccount(addr, origCoins, endTime)
|
||||||
|
dva.TrackDelegation(endTime, origCoins)
|
||||||
|
require.Nil(t, dva.DelegatedVesting)
|
||||||
|
require.Equal(t, origCoins, dva.DelegatedFree)
|
||||||
|
require.Nil(t, dva.GetCoins())
|
||||||
|
|
||||||
|
// require the ability to delegate all coins half way through the vesting
|
||||||
|
// schedule
|
||||||
|
dva = NewDelayedVestingAccount(addr, origCoins, endTime)
|
||||||
|
dva.TrackDelegation(now.Add(12*time.Hour), origCoins)
|
||||||
|
require.Equal(t, origCoins, dva.DelegatedVesting)
|
||||||
|
require.Nil(t, dva.DelegatedFree)
|
||||||
|
require.Nil(t, dva.GetCoins())
|
||||||
|
|
||||||
|
// require no modifications when delegation amount is zero or not enough funds
|
||||||
|
dva = NewDelayedVestingAccount(addr, origCoins, endTime)
|
||||||
|
|
||||||
|
require.Panics(t, func() {
|
||||||
|
dva.TrackDelegation(endTime, sdk.Coins{sdk.NewInt64Coin(testDenom, 1000000)})
|
||||||
|
})
|
||||||
|
require.Nil(t, dva.DelegatedVesting)
|
||||||
|
require.Nil(t, dva.DelegatedFree)
|
||||||
|
require.Equal(t, origCoins, dva.GetCoins())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrackUndelegationDelVestingAcc(t *testing.T) {
|
||||||
|
now := tmtime.Now()
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
_, _, addr := keyPubAddr()
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)}
|
||||||
|
|
||||||
|
// require the ability to undelegate all vesting coins
|
||||||
|
dva := NewDelayedVestingAccount(addr, origCoins, endTime)
|
||||||
|
dva.TrackDelegation(now, origCoins)
|
||||||
|
dva.TrackUndelegation(origCoins)
|
||||||
|
require.Nil(t, dva.DelegatedFree)
|
||||||
|
require.Nil(t, dva.DelegatedVesting)
|
||||||
|
require.Equal(t, origCoins, dva.GetCoins())
|
||||||
|
|
||||||
|
// require the ability to undelegate all vested coins
|
||||||
|
dva = NewDelayedVestingAccount(addr, origCoins, endTime)
|
||||||
|
dva.TrackDelegation(endTime, origCoins)
|
||||||
|
dva.TrackUndelegation(origCoins)
|
||||||
|
require.Nil(t, dva.DelegatedFree)
|
||||||
|
require.Nil(t, dva.DelegatedVesting)
|
||||||
|
require.Equal(t, origCoins, dva.GetCoins())
|
||||||
|
|
||||||
|
// require no modifications when the undelegation amount is zero
|
||||||
|
dva = NewDelayedVestingAccount(addr, origCoins, endTime)
|
||||||
|
|
||||||
|
require.Panics(t, func() {
|
||||||
|
dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 0)})
|
||||||
|
})
|
||||||
|
require.Nil(t, dva.DelegatedFree)
|
||||||
|
require.Nil(t, dva.DelegatedVesting)
|
||||||
|
require.Equal(t, origCoins, dva.GetCoins())
|
||||||
|
|
||||||
|
// vest 50% and delegate to two validators
|
||||||
|
dva = NewDelayedVestingAccount(addr, origCoins, endTime)
|
||||||
|
dva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)})
|
||||||
|
dva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)})
|
||||||
|
|
||||||
|
// undelegate from one validator that got slashed 50%
|
||||||
|
dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 25)})
|
||||||
|
|
||||||
|
require.Nil(t, dva.DelegatedFree)
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 75)}, dva.DelegatedVesting)
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, dva.GetCoins())
|
||||||
|
|
||||||
|
// undelegate from the other validator that did not get slashed
|
||||||
|
dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 50)})
|
||||||
|
require.Nil(t, dva.DelegatedFree)
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 25)}, dva.DelegatedVesting)
|
||||||
|
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 75)}, dva.GetCoins())
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/crypto"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
|
@ -87,8 +88,9 @@ func NewAnteHandler(ak AccountKeeper, fck FeeCollectionKeeper) sdk.AnteHandler {
|
||||||
if !res.IsOK() {
|
if !res.IsOK() {
|
||||||
return newCtx, res, true
|
return newCtx, res, true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !stdTx.Fee.Amount.IsZero() {
|
if !stdTx.Fee.Amount.IsZero() {
|
||||||
signerAccs[0], res = DeductFees(signerAccs[0], stdTx.Fee)
|
signerAccs[0], res = DeductFees(ctx.BlockHeader().Time, signerAccs[0], stdTx.Fee)
|
||||||
if !res.IsOK() {
|
if !res.IsOK() {
|
||||||
return newCtx, res, true
|
return newCtx, res, true
|
||||||
}
|
}
|
||||||
|
@ -254,7 +256,7 @@ func adjustFeesByGas(fees sdk.Coins, gas uint64) sdk.Coins {
|
||||||
//
|
//
|
||||||
// NOTE: We could use the CoinKeeper (in addition to the AccountKeeper, because
|
// NOTE: We could use the CoinKeeper (in addition to the AccountKeeper, because
|
||||||
// the CoinKeeper doesn't give us accounts), but it seems easier to do this.
|
// the CoinKeeper doesn't give us accounts), but it seems easier to do this.
|
||||||
func DeductFees(acc Account, fee StdFee) (Account, sdk.Result) {
|
func DeductFees(blockTime time.Time, acc Account, fee StdFee) (Account, sdk.Result) {
|
||||||
coins := acc.GetCoins()
|
coins := acc.GetCoins()
|
||||||
feeAmount := fee.Amount
|
feeAmount := fee.Amount
|
||||||
|
|
||||||
|
@ -262,14 +264,21 @@ func DeductFees(acc Account, fee StdFee) (Account, sdk.Result) {
|
||||||
return nil, sdk.ErrInsufficientFee(fmt.Sprintf("invalid fee amount: %s", feeAmount)).Result()
|
return nil, sdk.ErrInsufficientFee(fmt.Sprintf("invalid fee amount: %s", feeAmount)).Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the resulting coins deducting the fees
|
||||||
newCoins, ok := coins.SafeMinus(feeAmount)
|
newCoins, ok := coins.SafeMinus(feeAmount)
|
||||||
if ok {
|
if ok {
|
||||||
errMsg := fmt.Sprintf("%s < %s", coins, feeAmount)
|
errMsg := fmt.Sprintf("%s < %s", coins, feeAmount)
|
||||||
return nil, sdk.ErrInsufficientFunds(errMsg).Result()
|
return nil, sdk.ErrInsufficientFunds(errMsg).Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := acc.SetCoins(newCoins)
|
// Validate the account has enough "spendable" coins as this will cover cases
|
||||||
if err != nil {
|
// such as vesting accounts.
|
||||||
|
spendableCoins := acc.SpendableCoins(blockTime)
|
||||||
|
if _, hasNeg := spendableCoins.SafeMinus(feeAmount); hasNeg {
|
||||||
|
return nil, sdk.ErrInsufficientFunds(fmt.Sprintf("%s < %s", spendableCoins, feeAmount)).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := acc.SetCoins(newCoins); err != nil {
|
||||||
// Handle w/ #870
|
// Handle w/ #870
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
130
x/bank/keeper.go
130
x/bank/keeper.go
|
@ -2,6 +2,7 @@ package bank
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
@ -21,6 +22,9 @@ type Keeper interface {
|
||||||
SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error)
|
SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error)
|
||||||
AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error)
|
AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error)
|
||||||
InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error)
|
InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error)
|
||||||
|
|
||||||
|
DelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error)
|
||||||
|
UndelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseKeeper manages transfers between accounts. It implements the Keeper
|
// BaseKeeper manages transfers between accounts. It implements the Keeper
|
||||||
|
@ -68,6 +72,20 @@ func (keeper BaseKeeper) InputOutputCoins(
|
||||||
return inputOutputCoins(ctx, keeper.ak, inputs, outputs)
|
return inputOutputCoins(ctx, keeper.ak, inputs, outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DelegateCoins performs delegation by deducting amt coins from an account with
|
||||||
|
// address addr. For vesting accounts, delegations amounts are tracked for both
|
||||||
|
// vesting and vested coins.
|
||||||
|
func (keeper BaseKeeper) DelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) {
|
||||||
|
return delegateCoins(ctx, keeper.ak, addr, amt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UndelegateCoins performs undelegation by crediting amt coins to an account with
|
||||||
|
// address addr. For vesting accounts, undelegation amounts are tracked for both
|
||||||
|
// vesting and vested coins.
|
||||||
|
func (keeper BaseKeeper) UndelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) {
|
||||||
|
return undelegateCoins(ctx, keeper.ak, addr, amt)
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Send Keeper
|
// Send Keeper
|
||||||
|
|
||||||
|
@ -140,6 +158,7 @@ func (keeper BaseViewKeeper) HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
// Auxiliary
|
||||||
|
|
||||||
func getCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress) sdk.Coins {
|
func getCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress) sdk.Coins {
|
||||||
acc := am.GetAccount(ctx, addr)
|
acc := am.GetAccount(ctx, addr)
|
||||||
|
@ -168,16 +187,37 @@ func hasCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt s
|
||||||
return getCoins(ctx, am, addr).IsAllGTE(amt)
|
return getCoins(ctx, am, addr).IsAllGTE(amt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubtractCoins subtracts amt from the coins at the addr.
|
func getAccount(ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress) auth.Account {
|
||||||
func subtractCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
|
return ak.GetAccount(ctx, addr)
|
||||||
oldCoins := getCoins(ctx, am, addr)
|
}
|
||||||
newCoins, hasNeg := oldCoins.SafeMinus(amt)
|
|
||||||
if hasNeg {
|
func setAccount(ctx sdk.Context, ak auth.AccountKeeper, acc auth.Account) {
|
||||||
return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt))
|
ak.SetAccount(ctx, acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// subtractCoins subtracts amt coins from an account with the given address addr.
|
||||||
|
//
|
||||||
|
// CONTRACT: If the account is a vesting account, the amount has to be spendable.
|
||||||
|
func subtractCoins(ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
|
||||||
|
oldCoins, spendableCoins := sdk.Coins{}, sdk.Coins{}
|
||||||
|
|
||||||
|
acc := getAccount(ctx, ak, addr)
|
||||||
|
if acc != nil {
|
||||||
|
oldCoins = acc.GetCoins()
|
||||||
|
spendableCoins = acc.SpendableCoins(ctx.BlockHeader().Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := setCoins(ctx, am, addr, newCoins)
|
// For non-vesting accounts, spendable coins will simply be the original coins.
|
||||||
tags := sdk.NewTags("sender", []byte(addr.String()))
|
// So the check here is sufficient instead of subtracting from oldCoins.
|
||||||
|
_, hasNeg := spendableCoins.SafeMinus(amt)
|
||||||
|
if hasNeg {
|
||||||
|
return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", spendableCoins, amt))
|
||||||
|
}
|
||||||
|
|
||||||
|
newCoins := oldCoins.Minus(amt) // should not panic as spendable coins was already checked
|
||||||
|
err := setCoins(ctx, ak, addr, newCoins)
|
||||||
|
tags := sdk.NewTags(TagKeySender, []byte(addr.String()))
|
||||||
|
|
||||||
return newCoins, tags, err
|
return newCoins, tags, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,11 +225,14 @@ func subtractCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress,
|
||||||
func addCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
|
func addCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
|
||||||
oldCoins := getCoins(ctx, am, addr)
|
oldCoins := getCoins(ctx, am, addr)
|
||||||
newCoins := oldCoins.Plus(amt)
|
newCoins := oldCoins.Plus(amt)
|
||||||
|
|
||||||
if !newCoins.IsNotNegative() {
|
if !newCoins.IsNotNegative() {
|
||||||
return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt))
|
return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt))
|
||||||
}
|
}
|
||||||
|
|
||||||
err := setCoins(ctx, am, addr, newCoins)
|
err := setCoins(ctx, am, addr, newCoins)
|
||||||
tags := sdk.NewTags("recipient", []byte(addr.String()))
|
tags := sdk.NewTags(TagKeyRecipient, []byte(addr.String()))
|
||||||
|
|
||||||
return newCoins, tags, err
|
return newCoins, tags, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,3 +286,72 @@ func inputOutputCoins(ctx sdk.Context, am auth.AccountKeeper, inputs []Input, ou
|
||||||
|
|
||||||
return allTags, nil
|
return allTags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func delegateCoins(
|
||||||
|
ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins,
|
||||||
|
) (sdk.Tags, sdk.Error) {
|
||||||
|
|
||||||
|
acc := getAccount(ctx, ak, addr)
|
||||||
|
if acc == nil {
|
||||||
|
return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
oldCoins := acc.GetCoins()
|
||||||
|
|
||||||
|
_, hasNeg := oldCoins.SafeMinus(amt)
|
||||||
|
if hasNeg {
|
||||||
|
return nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := trackDelegation(acc, ctx.BlockHeader().Time, amt); err != nil {
|
||||||
|
return nil, sdk.ErrInternal(fmt.Sprintf("failed to track delegation: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
setAccount(ctx, ak, acc)
|
||||||
|
|
||||||
|
return sdk.NewTags(
|
||||||
|
sdk.TagAction, TagActionDelegateCoins,
|
||||||
|
sdk.TagDelegator, []byte(addr.String()),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func undelegateCoins(
|
||||||
|
ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins,
|
||||||
|
) (sdk.Tags, sdk.Error) {
|
||||||
|
|
||||||
|
acc := getAccount(ctx, ak, addr)
|
||||||
|
if acc == nil {
|
||||||
|
return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := trackUndelegation(acc, amt); err != nil {
|
||||||
|
return nil, sdk.ErrInternal(fmt.Sprintf("failed to track undelegation: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
setAccount(ctx, ak, acc)
|
||||||
|
|
||||||
|
return sdk.NewTags(
|
||||||
|
sdk.TagAction, TagActionUndelegateCoins,
|
||||||
|
sdk.TagDelegator, []byte(addr.String()),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trackDelegation(acc auth.Account, blockTime time.Time, amount sdk.Coins) error {
|
||||||
|
vacc, ok := acc.(auth.VestingAccount)
|
||||||
|
if ok {
|
||||||
|
vacc.TrackDelegation(blockTime, amount)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc.SetCoins(acc.GetCoins().Minus(amount))
|
||||||
|
}
|
||||||
|
|
||||||
|
func trackUndelegation(acc auth.Account, amount sdk.Coins) error {
|
||||||
|
vacc, ok := acc.(auth.VestingAccount)
|
||||||
|
if ok {
|
||||||
|
vacc.TrackUndelegation(amount)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc.SetCoins(acc.GetCoins().Plus(amount))
|
||||||
|
}
|
||||||
|
|
|
@ -2,11 +2,13 @@ package bank
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
|
|
||||||
codec "github.com/cosmos/cosmos-sdk/codec"
|
codec "github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/store"
|
"github.com/cosmos/cosmos-sdk/store"
|
||||||
|
@ -198,3 +200,143 @@ func TestViewKeeper(t *testing.T) {
|
||||||
require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 15)}))
|
require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 15)}))
|
||||||
require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 5)}))
|
require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 5)}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVestingAccountSend(t *testing.T) {
|
||||||
|
input := setupTestInput()
|
||||||
|
now := tmtime.Now()
|
||||||
|
ctx := input.ctx.WithBlockHeader(abci.Header{Time: now})
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)}
|
||||||
|
sendCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)}
|
||||||
|
bankKeeper := NewBaseKeeper(input.ak)
|
||||||
|
|
||||||
|
addr1 := sdk.AccAddress([]byte("addr1"))
|
||||||
|
addr2 := sdk.AccAddress([]byte("addr2"))
|
||||||
|
vacc := auth.NewContinuousVestingAccount(addr1, origCoins, ctx.BlockHeader().Time, endTime)
|
||||||
|
input.ak.SetAccount(ctx, vacc)
|
||||||
|
|
||||||
|
// require that no coins be sendable at the beginning of the vesting schedule
|
||||||
|
_, err := bankKeeper.SendCoins(ctx, addr1, addr2, sendCoins)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// receive some coins
|
||||||
|
vacc.SetCoins(origCoins.Plus(sendCoins))
|
||||||
|
input.ak.SetAccount(ctx, vacc)
|
||||||
|
|
||||||
|
// require that all vested coins are spendable plus any received
|
||||||
|
ctx = ctx.WithBlockTime(now.Add(12 * time.Hour))
|
||||||
|
_, err = bankKeeper.SendCoins(ctx, addr1, addr2, sendCoins)
|
||||||
|
vacc = input.ak.GetAccount(ctx, addr1).(*auth.ContinuousVestingAccount)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, origCoins, vacc.GetCoins())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVestingAccountReceive(t *testing.T) {
|
||||||
|
input := setupTestInput()
|
||||||
|
now := tmtime.Now()
|
||||||
|
ctx := input.ctx.WithBlockHeader(abci.Header{Time: now})
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)}
|
||||||
|
sendCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)}
|
||||||
|
bankKeeper := NewBaseKeeper(input.ak)
|
||||||
|
|
||||||
|
addr1 := sdk.AccAddress([]byte("addr1"))
|
||||||
|
addr2 := sdk.AccAddress([]byte("addr2"))
|
||||||
|
|
||||||
|
vacc := auth.NewContinuousVestingAccount(addr1, origCoins, ctx.BlockHeader().Time, endTime)
|
||||||
|
acc := input.ak.NewAccountWithAddress(ctx, addr2)
|
||||||
|
input.ak.SetAccount(ctx, vacc)
|
||||||
|
input.ak.SetAccount(ctx, acc)
|
||||||
|
bankKeeper.SetCoins(ctx, addr2, origCoins)
|
||||||
|
|
||||||
|
// send some coins to the vesting account
|
||||||
|
bankKeeper.SendCoins(ctx, addr2, addr1, sendCoins)
|
||||||
|
|
||||||
|
// require the coins are spendable
|
||||||
|
vacc = input.ak.GetAccount(ctx, addr1).(*auth.ContinuousVestingAccount)
|
||||||
|
require.Equal(t, origCoins.Plus(sendCoins), vacc.GetCoins())
|
||||||
|
require.Equal(t, vacc.SpendableCoins(now), sendCoins)
|
||||||
|
|
||||||
|
// require coins are spendable plus any that have vested
|
||||||
|
require.Equal(t, vacc.SpendableCoins(now.Add(12*time.Hour)), origCoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelegateCoins(t *testing.T) {
|
||||||
|
input := setupTestInput()
|
||||||
|
now := tmtime.Now()
|
||||||
|
ctx := input.ctx.WithBlockHeader(abci.Header{Time: now})
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)}
|
||||||
|
delCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)}
|
||||||
|
bankKeeper := NewBaseKeeper(input.ak)
|
||||||
|
|
||||||
|
addr1 := sdk.AccAddress([]byte("addr1"))
|
||||||
|
addr2 := sdk.AccAddress([]byte("addr2"))
|
||||||
|
|
||||||
|
vacc := auth.NewContinuousVestingAccount(addr1, origCoins, ctx.BlockHeader().Time, endTime)
|
||||||
|
acc := input.ak.NewAccountWithAddress(ctx, addr2)
|
||||||
|
input.ak.SetAccount(ctx, vacc)
|
||||||
|
input.ak.SetAccount(ctx, acc)
|
||||||
|
bankKeeper.SetCoins(ctx, addr2, origCoins)
|
||||||
|
|
||||||
|
ctx = ctx.WithBlockTime(now.Add(12 * time.Hour))
|
||||||
|
|
||||||
|
// require the ability for a non-vesting account to delegate
|
||||||
|
_, err := bankKeeper.DelegateCoins(ctx, addr2, delCoins)
|
||||||
|
acc = input.ak.GetAccount(ctx, addr2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, delCoins, acc.GetCoins())
|
||||||
|
|
||||||
|
// require the ability for a vesting account to delegate
|
||||||
|
_, err = bankKeeper.DelegateCoins(ctx, addr1, delCoins)
|
||||||
|
vacc = input.ak.GetAccount(ctx, addr1).(*auth.ContinuousVestingAccount)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, delCoins, vacc.GetCoins())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUndelegateCoins(t *testing.T) {
|
||||||
|
input := setupTestInput()
|
||||||
|
now := tmtime.Now()
|
||||||
|
ctx := input.ctx.WithBlockHeader(abci.Header{Time: now})
|
||||||
|
endTime := now.Add(24 * time.Hour)
|
||||||
|
|
||||||
|
origCoins := sdk.Coins{sdk.NewInt64Coin("steak", 100)}
|
||||||
|
delCoins := sdk.Coins{sdk.NewInt64Coin("steak", 50)}
|
||||||
|
bankKeeper := NewBaseKeeper(input.ak)
|
||||||
|
|
||||||
|
addr1 := sdk.AccAddress([]byte("addr1"))
|
||||||
|
addr2 := sdk.AccAddress([]byte("addr2"))
|
||||||
|
|
||||||
|
vacc := auth.NewContinuousVestingAccount(addr1, origCoins, ctx.BlockHeader().Time, endTime)
|
||||||
|
acc := input.ak.NewAccountWithAddress(ctx, addr2)
|
||||||
|
input.ak.SetAccount(ctx, vacc)
|
||||||
|
input.ak.SetAccount(ctx, acc)
|
||||||
|
bankKeeper.SetCoins(ctx, addr2, origCoins)
|
||||||
|
|
||||||
|
ctx = ctx.WithBlockTime(now.Add(12 * time.Hour))
|
||||||
|
|
||||||
|
// require the ability for a non-vesting account to delegate
|
||||||
|
_, err := bankKeeper.DelegateCoins(ctx, addr2, delCoins)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// require the ability for a non-vesting account to undelegate
|
||||||
|
_, err = bankKeeper.UndelegateCoins(ctx, addr2, delCoins)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
acc = input.ak.GetAccount(ctx, addr2)
|
||||||
|
require.Equal(t, origCoins, acc.GetCoins())
|
||||||
|
|
||||||
|
// require the ability for a vesting account to delegate
|
||||||
|
_, err = bankKeeper.DelegateCoins(ctx, addr1, delCoins)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// require the ability for a vesting account to undelegate
|
||||||
|
_, err = bankKeeper.UndelegateCoins(ctx, addr1, delCoins)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
vacc = input.ak.GetAccount(ctx, addr1).(*auth.ContinuousVestingAccount)
|
||||||
|
require.Equal(t, origCoins, vacc.GetCoins())
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package bank
|
||||||
|
|
||||||
|
// Tag keys and values
|
||||||
|
var (
|
||||||
|
TagActionUndelegateCoins = []byte("undelegateCoins")
|
||||||
|
TagActionDelegateCoins = []byte("delegateCoins")
|
||||||
|
|
||||||
|
TagKeyRecipient = "recipient"
|
||||||
|
TagKeySender = "sender"
|
||||||
|
)
|
|
@ -404,10 +404,9 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co
|
||||||
}
|
}
|
||||||
|
|
||||||
if subtractAccount {
|
if subtractAccount {
|
||||||
// Account new shares, save
|
_, err := k.bankKeeper.DelegateCoins(ctx, delegation.DelegatorAddr, sdk.Coins{bondAmt})
|
||||||
_, _, err = k.bankKeeper.SubtractCoins(ctx, delegation.DelegatorAddr, sdk.Coins{bondAmt})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return sdk.Dec{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,10 +522,11 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context,
|
||||||
|
|
||||||
// no need to create the ubd object just complete now
|
// no need to create the ubd object just complete now
|
||||||
if completeNow {
|
if completeNow {
|
||||||
_, _, err := k.bankKeeper.AddCoins(ctx, delAddr, sdk.Coins{balance})
|
_, err := k.bankKeeper.UndelegateCoins(ctx, delAddr, sdk.Coins{balance})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.UnbondingDelegation{}, err
|
return types.UnbondingDelegation{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.UnbondingDelegation{MinTime: minTime}, nil
|
return types.UnbondingDelegation{MinTime: minTime}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue