cosmos-sdk/x/auth/vesting/types/vesting_account.go

604 lines
20 KiB
Go

package types
import (
"errors"
"time"
"sigs.k8s.io/yaml"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
)
// Compile-time type assertions
var (
_ authtypes.AccountI = (*BaseVestingAccount)(nil)
_ vestexported.VestingAccount = (*ContinuousVestingAccount)(nil)
_ vestexported.VestingAccount = (*PeriodicVestingAccount)(nil)
_ vestexported.VestingAccount = (*DelayedVestingAccount)(nil)
)
// Base Vesting Account
// NewBaseVestingAccount creates a new BaseVestingAccount object. It is the
// callers responsibility to ensure the base account has sufficient funds with
// regards to the original vesting amount.
func NewBaseVestingAccount(baseAccount *authtypes.BaseAccount, originalVesting sdk.Coins, endTime int64) *BaseVestingAccount {
return &BaseVestingAccount{
BaseAccount: baseAccount,
OriginalVesting: originalVesting,
DelegatedFree: sdk.NewCoins(),
DelegatedVesting: sdk.NewCoins(),
EndTime: endTime,
}
}
// LockedCoinsFromVesting returns all the coins that are not spendable (i.e. locked)
// for a vesting account given the current vesting coins. If no coins are locked,
// an empty slice of Coins is returned.
//
// CONTRACT: Delegated vesting coins and vestingCoins must be sorted.
func (bva BaseVestingAccount) LockedCoinsFromVesting(vestingCoins sdk.Coins) sdk.Coins {
lockedCoins := vestingCoins.Sub(vestingCoins.Min(bva.DelegatedVesting))
if lockedCoins == nil {
return sdk.Coins{}
}
return lockedCoins
}
// TrackDelegation tracks a delegation amount for any given vesting account type
// given the amount of coins currently vesting and the current account balance
// of the delegation denominations.
//
// CONTRACT: The account's coins, delegation coins, vesting coins, and delegated
// vesting coins must be sorted.
func (bva *BaseVestingAccount) TrackDelegation(balance, vestingCoins, amount sdk.Coins) {
for _, coin := range amount {
baseAmt := balance.AmountOf(coin.Denom)
vestingAmt := vestingCoins.AmountOf(coin.Denom)
delVestingAmt := bva.DelegatedVesting.AmountOf(coin.Denom)
// 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.Add(xCoin)
}
if !y.IsZero() {
yCoin := sdk.NewCoin(coin.Denom, y)
bva.DelegatedFree = bva.DelegatedFree.Add(yCoin)
}
}
}
// 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.
//
// NOTE: The undelegation (bond refund) amount may exceed the delegated
// vesting (bond) amount due to the way undelegation truncates the bond refund,
// which can increase the validator's exchange rate (tokens/shares) slightly if
// the undelegated tokens are non-integral.
//
// CONTRACT: The account's coins and undelegation coins must be sorted.
func (bva *BaseVestingAccount) TrackUndelegation(amount sdk.Coins) {
for _, coin := range amount {
// panic if the undelegation amount is zero
if coin.Amount.IsZero() {
panic("undelegation attempt with zero coins")
}
delegatedFree := bva.DelegatedFree.AmountOf(coin.Denom)
delegatedVesting := bva.DelegatedVesting.AmountOf(coin.Denom)
// compute x and y per the specification, where:
// X := min(DF, D)
// Y := min(DV, D - X)
x := sdk.MinInt(delegatedFree, coin.Amount)
y := sdk.MinInt(delegatedVesting, coin.Amount.Sub(x))
if !x.IsZero() {
xCoin := sdk.NewCoin(coin.Denom, x)
bva.DelegatedFree = bva.DelegatedFree.Sub(sdk.Coins{xCoin})
}
if !y.IsZero() {
yCoin := sdk.NewCoin(coin.Denom, y)
bva.DelegatedVesting = bva.DelegatedVesting.Sub(sdk.Coins{yCoin})
}
}
}
// GetOriginalVesting returns a vesting account's original vesting amount
func (bva BaseVestingAccount) GetOriginalVesting() sdk.Coins {
return bva.OriginalVesting
}
// GetDelegatedFree returns a vesting account's delegation amount that is not
// vesting.
func (bva BaseVestingAccount) GetDelegatedFree() sdk.Coins {
return bva.DelegatedFree
}
// GetDelegatedVesting returns a vesting account's delegation amount that is
// still vesting.
func (bva BaseVestingAccount) GetDelegatedVesting() sdk.Coins {
return bva.DelegatedVesting
}
// GetEndTime returns a vesting account's end time
func (bva BaseVestingAccount) GetEndTime() int64 {
return bva.EndTime
}
// Validate checks for errors on the account fields
func (bva BaseVestingAccount) Validate() error {
if !(bva.DelegatedVesting.IsAllLTE(bva.OriginalVesting)) {
return errors.New("delegated vesting amount cannot be greater than original vesting amount")
}
return bva.BaseAccount.Validate()
}
type vestingAccountYAML struct {
Address sdk.AccAddress `json:"address"`
PubKey string `json:"public_key"`
AccountNumber uint64 `json:"account_number"`
Sequence uint64 `json:"sequence"`
OriginalVesting sdk.Coins `json:"original_vesting"`
DelegatedFree sdk.Coins `json:"delegated_free"`
DelegatedVesting sdk.Coins `json:"delegated_vesting"`
EndTime int64 `json:"end_time"`
// custom fields based on concrete vesting type which can be omitted
StartTime int64 `json:"start_time,omitempty"`
VestingPeriods Periods `json:"vesting_periods,omitempty"`
}
func (bva BaseVestingAccount) String() string {
out, _ := bva.MarshalYAML()
return out.(string)
}
// MarshalYAML returns the YAML representation of a BaseVestingAccount.
func (bva BaseVestingAccount) MarshalYAML() (interface{}, error) {
accAddr, err := sdk.AccAddressFromBech32(bva.Address)
if err != nil {
return nil, err
}
out := vestingAccountYAML{
Address: accAddr,
AccountNumber: bva.AccountNumber,
PubKey: getPKString(bva),
Sequence: bva.Sequence,
OriginalVesting: bva.OriginalVesting,
DelegatedFree: bva.DelegatedFree,
DelegatedVesting: bva.DelegatedVesting,
EndTime: bva.EndTime,
}
return marshalYaml(out)
}
// Continuous Vesting Account
var _ vestexported.VestingAccount = (*ContinuousVestingAccount)(nil)
var _ authtypes.GenesisAccount = (*ContinuousVestingAccount)(nil)
// NewContinuousVestingAccountRaw creates a new ContinuousVestingAccount object from BaseVestingAccount
func NewContinuousVestingAccountRaw(bva *BaseVestingAccount, startTime int64) *ContinuousVestingAccount {
return &ContinuousVestingAccount{
BaseVestingAccount: bva,
StartTime: startTime,
}
}
// NewContinuousVestingAccount returns a new ContinuousVestingAccount
func NewContinuousVestingAccount(baseAcc *authtypes.BaseAccount, originalVesting sdk.Coins, startTime, endTime int64) *ContinuousVestingAccount {
baseVestingAcc := &BaseVestingAccount{
BaseAccount: baseAcc,
OriginalVesting: originalVesting,
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 {
return vestedCoins
} else if blockTime.Unix() >= cva.EndTime {
return cva.OriginalVesting
}
// calculate the vesting scalar
x := blockTime.Unix() - cva.StartTime
y := cva.EndTime - cva.StartTime
s := sdk.NewDec(x).Quo(sdk.NewDec(y))
for _, ovc := range cva.OriginalVesting {
vestedAmt := ovc.Amount.ToDec().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.Sub(cva.GetVestedCoins(blockTime))
}
// LockedCoins returns the set of coins that are not spendable (i.e. locked),
// defined as the vesting coins that are not delegated.
func (cva ContinuousVestingAccount) LockedCoins(blockTime time.Time) sdk.Coins {
return cva.BaseVestingAccount.LockedCoinsFromVesting(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, balance, amount sdk.Coins) {
cva.BaseVestingAccount.TrackDelegation(balance, cva.GetVestingCoins(blockTime), amount)
}
// GetStartTime returns the time when vesting starts for a continuous vesting
// account.
func (cva ContinuousVestingAccount) GetStartTime() int64 {
return cva.StartTime
}
// Validate checks for errors on the account fields
func (cva ContinuousVestingAccount) Validate() error {
if cva.GetStartTime() >= cva.GetEndTime() {
return errors.New("vesting start-time cannot be before end-time")
}
return cva.BaseVestingAccount.Validate()
}
func (cva ContinuousVestingAccount) String() string {
out, _ := cva.MarshalYAML()
return out.(string)
}
// MarshalYAML returns the YAML representation of a ContinuousVestingAccount.
func (cva ContinuousVestingAccount) MarshalYAML() (interface{}, error) {
accAddr, err := sdk.AccAddressFromBech32(cva.Address)
if err != nil {
return nil, err
}
out := vestingAccountYAML{
Address: accAddr,
AccountNumber: cva.AccountNumber,
PubKey: getPKString(cva),
Sequence: cva.Sequence,
OriginalVesting: cva.OriginalVesting,
DelegatedFree: cva.DelegatedFree,
DelegatedVesting: cva.DelegatedVesting,
EndTime: cva.EndTime,
StartTime: cva.StartTime,
}
return marshalYaml(out)
}
// Periodic Vesting Account
var _ vestexported.VestingAccount = (*PeriodicVestingAccount)(nil)
var _ authtypes.GenesisAccount = (*PeriodicVestingAccount)(nil)
// NewPeriodicVestingAccountRaw creates a new PeriodicVestingAccount object from BaseVestingAccount
func NewPeriodicVestingAccountRaw(bva *BaseVestingAccount, startTime int64, periods Periods) *PeriodicVestingAccount {
return &PeriodicVestingAccount{
BaseVestingAccount: bva,
StartTime: startTime,
VestingPeriods: periods,
}
}
// NewPeriodicVestingAccount returns a new PeriodicVestingAccount
func NewPeriodicVestingAccount(baseAcc *authtypes.BaseAccount, originalVesting sdk.Coins, startTime int64, periods Periods) *PeriodicVestingAccount {
endTime := startTime
for _, p := range periods {
endTime += p.Length
}
baseVestingAcc := &BaseVestingAccount{
BaseAccount: baseAcc,
OriginalVesting: originalVesting,
EndTime: endTime,
}
return &PeriodicVestingAccount{
BaseVestingAccount: baseVestingAcc,
StartTime: startTime,
VestingPeriods: periods,
}
}
// GetVestedCoins returns the total number of vested coins. If no coins are vested,
// nil is returned.
func (pva PeriodicVestingAccount) 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() <= pva.StartTime {
return vestedCoins
} else if blockTime.Unix() >= pva.EndTime {
return pva.OriginalVesting
}
// track the start time of the next period
currentPeriodStartTime := pva.StartTime
// for each period, if the period is over, add those coins as vested and check the next period.
for _, period := range pva.VestingPeriods {
x := blockTime.Unix() - currentPeriodStartTime
if x < period.Length {
break
}
vestedCoins = vestedCoins.Add(period.Amount...)
// update the start time of the next period
currentPeriodStartTime += period.Length
}
return vestedCoins
}
// GetVestingCoins returns the total number of vesting coins. If no coins are
// vesting, nil is returned.
func (pva PeriodicVestingAccount) GetVestingCoins(blockTime time.Time) sdk.Coins {
return pva.OriginalVesting.Sub(pva.GetVestedCoins(blockTime))
}
// LockedCoins returns the set of coins that are not spendable (i.e. locked),
// defined as the vesting coins that are not delegated.
func (pva PeriodicVestingAccount) LockedCoins(blockTime time.Time) sdk.Coins {
return pva.BaseVestingAccount.LockedCoinsFromVesting(pva.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 (pva *PeriodicVestingAccount) TrackDelegation(blockTime time.Time, balance, amount sdk.Coins) {
pva.BaseVestingAccount.TrackDelegation(balance, pva.GetVestingCoins(blockTime), amount)
}
// GetStartTime returns the time when vesting starts for a periodic vesting
// account.
func (pva PeriodicVestingAccount) GetStartTime() int64 {
return pva.StartTime
}
// GetVestingPeriods returns vesting periods associated with periodic vesting account.
func (pva PeriodicVestingAccount) GetVestingPeriods() Periods {
return pva.VestingPeriods
}
// Validate checks for errors on the account fields
func (pva PeriodicVestingAccount) Validate() error {
if pva.GetStartTime() >= pva.GetEndTime() {
return errors.New("vesting start-time cannot be before end-time")
}
endTime := pva.StartTime
originalVesting := sdk.NewCoins()
for _, p := range pva.VestingPeriods {
endTime += p.Length
originalVesting = originalVesting.Add(p.Amount...)
}
if endTime != pva.EndTime {
return errors.New("vesting end time does not match length of all vesting periods")
}
if !originalVesting.IsEqual(pva.OriginalVesting) {
return errors.New("original vesting coins does not match the sum of all coins in vesting periods")
}
return pva.BaseVestingAccount.Validate()
}
func (pva PeriodicVestingAccount) String() string {
out, _ := pva.MarshalYAML()
return out.(string)
}
// MarshalYAML returns the YAML representation of a PeriodicVestingAccount.
func (pva PeriodicVestingAccount) MarshalYAML() (interface{}, error) {
accAddr, err := sdk.AccAddressFromBech32(pva.Address)
if err != nil {
return nil, err
}
out := vestingAccountYAML{
Address: accAddr,
AccountNumber: pva.AccountNumber,
PubKey: getPKString(pva),
Sequence: pva.Sequence,
OriginalVesting: pva.OriginalVesting,
DelegatedFree: pva.DelegatedFree,
DelegatedVesting: pva.DelegatedVesting,
EndTime: pva.EndTime,
StartTime: pva.StartTime,
VestingPeriods: pva.VestingPeriods,
}
return marshalYaml(out)
}
// Delayed Vesting Account
var _ vestexported.VestingAccount = (*DelayedVestingAccount)(nil)
var _ authtypes.GenesisAccount = (*DelayedVestingAccount)(nil)
// NewDelayedVestingAccountRaw creates a new DelayedVestingAccount object from BaseVestingAccount
func NewDelayedVestingAccountRaw(bva *BaseVestingAccount) *DelayedVestingAccount {
return &DelayedVestingAccount{
BaseVestingAccount: bva,
}
}
// NewDelayedVestingAccount returns a DelayedVestingAccount
func NewDelayedVestingAccount(baseAcc *authtypes.BaseAccount, originalVesting sdk.Coins, endTime int64) *DelayedVestingAccount {
baseVestingAcc := &BaseVestingAccount{
BaseAccount: baseAcc,
OriginalVesting: originalVesting,
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 {
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.Sub(dva.GetVestedCoins(blockTime))
}
// LockedCoins returns the set of coins that are not spendable (i.e. locked),
// defined as the vesting coins that are not delegated.
func (dva DelayedVestingAccount) LockedCoins(blockTime time.Time) sdk.Coins {
return dva.BaseVestingAccount.LockedCoinsFromVesting(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, balance, amount sdk.Coins) {
dva.BaseVestingAccount.TrackDelegation(balance, dva.GetVestingCoins(blockTime), amount)
}
// GetStartTime returns zero since a delayed vesting account has no start time.
func (dva DelayedVestingAccount) GetStartTime() int64 {
return 0
}
// Validate checks for errors on the account fields
func (dva DelayedVestingAccount) Validate() error {
return dva.BaseVestingAccount.Validate()
}
func (dva DelayedVestingAccount) String() string {
out, _ := dva.MarshalYAML()
return out.(string)
}
//-----------------------------------------------------------------------------
// Permanent Locked Vesting Account
var _ vestexported.VestingAccount = (*PermanentLockedAccount)(nil)
var _ authtypes.GenesisAccount = (*PermanentLockedAccount)(nil)
// NewPermanentLockedAccount returns a PermanentLockedAccount
func NewPermanentLockedAccount(baseAcc *authtypes.BaseAccount, coins sdk.Coins) *PermanentLockedAccount {
baseVestingAcc := &BaseVestingAccount{
BaseAccount: baseAcc,
OriginalVesting: coins,
EndTime: 0, // ensure EndTime is set to 0, as PermanentLockedAccount's do not have an EndTime
}
return &PermanentLockedAccount{baseVestingAcc}
}
// GetVestedCoins returns the total amount of vested coins for a permanent locked vesting
// account. All coins are only vested once the schedule has elapsed.
func (plva PermanentLockedAccount) GetVestedCoins(_ time.Time) sdk.Coins {
return nil
}
// GetVestingCoins returns the total number of vesting coins for a permanent locked
// vesting account.
func (plva PermanentLockedAccount) GetVestingCoins(_ time.Time) sdk.Coins {
return plva.OriginalVesting
}
// LockedCoins returns the set of coins that are not spendable (i.e. locked),
// defined as the vesting coins that are not delegated.
func (plva PermanentLockedAccount) LockedCoins(_ time.Time) sdk.Coins {
return plva.BaseVestingAccount.LockedCoinsFromVesting(plva.OriginalVesting)
}
// 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 (plva *PermanentLockedAccount) TrackDelegation(blockTime time.Time, balance, amount sdk.Coins) {
plva.BaseVestingAccount.TrackDelegation(balance, plva.OriginalVesting, amount)
}
// GetStartTime returns zero since a permanent locked vesting account has no start time.
func (plva PermanentLockedAccount) GetStartTime() int64 {
return 0
}
// GetEndTime returns a vesting account's end time, we return 0 to denote that
// a permanently locked vesting account has no end time.
func (plva PermanentLockedAccount) GetEndTime() int64 {
return 0
}
// Validate checks for errors on the account fields
func (plva PermanentLockedAccount) Validate() error {
if plva.EndTime > 0 {
return errors.New("permanently vested accounts cannot have an end-time")
}
return plva.BaseVestingAccount.Validate()
}
func (plva PermanentLockedAccount) String() string {
out, _ := plva.MarshalYAML()
return out.(string)
}
type getPK interface {
GetPubKey() cryptotypes.PubKey
}
func getPKString(g getPK) string {
if pk := g.GetPubKey(); pk != nil {
return pk.String()
}
return ""
}
func marshalYaml(i interface{}) (interface{}, error) {
bz, err := yaml.Marshal(i)
if err != nil {
return nil, err
}
return string(bz), nil
}