508 lines
15 KiB
Go
508 lines
15 KiB
Go
package auth
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/tendermint/tendermint/crypto"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
)
|
|
|
|
// Account is an interface used to store coins at a given address within state.
|
|
// It presumes a notion of sequence numbers for replay protection,
|
|
// a notion of account numbers for replay protection for previously pruned accounts,
|
|
// and a pubkey for authentication purposes.
|
|
//
|
|
// Many complex conditions can be used in the concrete struct which implements Account.
|
|
type Account interface {
|
|
GetAddress() sdk.AccAddress
|
|
SetAddress(sdk.AccAddress) error // errors if already set.
|
|
|
|
GetPubKey() crypto.PubKey // can return nil.
|
|
SetPubKey(crypto.PubKey) error
|
|
|
|
GetAccountNumber() uint64
|
|
SetAccountNumber(uint64) error
|
|
|
|
GetSequence() uint64
|
|
SetSequence(uint64) error
|
|
|
|
GetCoins() sdk.Coins
|
|
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
|
|
|
|
// Ensure that account implements stringer
|
|
String() string
|
|
}
|
|
|
|
// 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
|
|
|
|
GetStartTime() int64
|
|
GetEndTime() int64
|
|
|
|
GetOriginalVesting() sdk.Coins
|
|
GetDelegatedFree() sdk.Coins
|
|
GetDelegatedVesting() sdk.Coins
|
|
}
|
|
|
|
// AccountDecoder unmarshals account bytes
|
|
// TODO: Think about removing
|
|
type AccountDecoder func(accountBytes []byte) (Account, error)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// BaseAccount
|
|
|
|
var _ Account = (*BaseAccount)(nil)
|
|
|
|
// BaseAccount - a base account structure.
|
|
// This can be extended by embedding within in your AppAccount.
|
|
// However one doesn't have to use BaseAccount as long as your struct
|
|
// implements Account.
|
|
type BaseAccount struct {
|
|
Address sdk.AccAddress `json:"address"`
|
|
Coins sdk.Coins `json:"coins"`
|
|
PubKey crypto.PubKey `json:"public_key"`
|
|
AccountNumber uint64 `json:"account_number"`
|
|
Sequence uint64 `json:"sequence"`
|
|
}
|
|
|
|
// String implements fmt.Stringer
|
|
func (acc BaseAccount) String() string {
|
|
var pubkey string
|
|
|
|
if acc.PubKey != nil {
|
|
pubkey = sdk.MustBech32ifyAccPub(acc.PubKey)
|
|
}
|
|
|
|
return fmt.Sprintf(`Account:
|
|
Address: %s
|
|
Pubkey: %s
|
|
Coins: %s
|
|
AccountNumber: %d
|
|
Sequence: %d`,
|
|
acc.Address, pubkey, acc.Coins, acc.AccountNumber, acc.Sequence,
|
|
)
|
|
}
|
|
|
|
// ProtoBaseAccount - a prototype function for BaseAccount
|
|
func ProtoBaseAccount() Account {
|
|
return &BaseAccount{}
|
|
}
|
|
|
|
// NewBaseAccountWithAddress - returns a new base account with a given address
|
|
func NewBaseAccountWithAddress(addr sdk.AccAddress) BaseAccount {
|
|
return BaseAccount{
|
|
Address: addr,
|
|
}
|
|
}
|
|
|
|
// GetAddress - Implements sdk.Account.
|
|
func (acc BaseAccount) GetAddress() sdk.AccAddress {
|
|
return acc.Address
|
|
}
|
|
|
|
// SetAddress - Implements sdk.Account.
|
|
func (acc *BaseAccount) SetAddress(addr sdk.AccAddress) error {
|
|
if len(acc.Address) != 0 {
|
|
return errors.New("cannot override BaseAccount address")
|
|
}
|
|
acc.Address = addr
|
|
return nil
|
|
}
|
|
|
|
// GetPubKey - Implements sdk.Account.
|
|
func (acc BaseAccount) GetPubKey() crypto.PubKey {
|
|
return acc.PubKey
|
|
}
|
|
|
|
// SetPubKey - Implements sdk.Account.
|
|
func (acc *BaseAccount) SetPubKey(pubKey crypto.PubKey) error {
|
|
acc.PubKey = pubKey
|
|
return nil
|
|
}
|
|
|
|
// GetCoins - Implements sdk.Account.
|
|
func (acc *BaseAccount) GetCoins() sdk.Coins {
|
|
return acc.Coins
|
|
}
|
|
|
|
// SetCoins - Implements sdk.Account.
|
|
func (acc *BaseAccount) SetCoins(coins sdk.Coins) error {
|
|
acc.Coins = coins
|
|
return nil
|
|
}
|
|
|
|
// GetAccountNumber - Implements Account
|
|
func (acc *BaseAccount) GetAccountNumber() uint64 {
|
|
return acc.AccountNumber
|
|
}
|
|
|
|
// SetAccountNumber - Implements Account
|
|
func (acc *BaseAccount) SetAccountNumber(accNumber uint64) error {
|
|
acc.AccountNumber = accNumber
|
|
return nil
|
|
}
|
|
|
|
// GetSequence - Implements sdk.Account.
|
|
func (acc *BaseAccount) GetSequence() uint64 {
|
|
return acc.Sequence
|
|
}
|
|
|
|
// SetSequence - Implements sdk.Account.
|
|
func (acc *BaseAccount) SetSequence(seq uint64) error {
|
|
acc.Sequence = seq
|
|
return nil
|
|
}
|
|
|
|
// SpendableCoins returns the total set of spendable coins. For a base account,
|
|
// 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 `json:"original_vesting"` // coins in account upon initialization
|
|
DelegatedFree sdk.Coins `json:"delegated_free"` // coins that are vested and delegated
|
|
DelegatedVesting sdk.Coins `json:"delegated_vesting"` // coins that vesting and delegated
|
|
|
|
EndTime int64 `json:"end_time"` // when the coins become unlocked
|
|
}
|
|
|
|
// String implements fmt.Stringer
|
|
func (bva BaseVestingAccount) String() string {
|
|
var pubkey string
|
|
|
|
if bva.PubKey != nil {
|
|
pubkey = sdk.MustBech32ifyAccPub(bva.PubKey)
|
|
}
|
|
|
|
return fmt.Sprintf(`Vesting Account:
|
|
Address: %s
|
|
Pubkey: %s
|
|
Coins: %s
|
|
AccountNumber: %d
|
|
Sequence: %d
|
|
OriginalVesting: %s
|
|
DelegatedFree: %s
|
|
DelegatedVesting: %s
|
|
EndTime: %d `,
|
|
bva.Address, pubkey, bva.Coins, bva.AccountNumber, bva.Sequence,
|
|
bva.OriginalVesting, bva.DelegatedFree, bva.DelegatedVesting, bva.EndTime,
|
|
)
|
|
}
|
|
|
|
// 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()
|
|
|
|
for _, coin := range bc {
|
|
// zip/lineup all coins by their denomination to provide O(n) time
|
|
baseAmt := coin.Amount
|
|
vestingAmt := vestingCoins.AmountOf(coin.Denom)
|
|
delVestingAmt := bva.DelegatedVesting.AmountOf(coin.Denom)
|
|
|
|
// 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()
|
|
|
|
for _, coin := range amount {
|
|
// zip/lineup all coins by their denomination to provide O(n) time
|
|
|
|
baseAmt := bc.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.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) {
|
|
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)
|
|
|
|
// 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})
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 int64 `json:"start_time"` // when the coins start to vest
|
|
}
|
|
|
|
// NewContinuousVestingAccount returns a new ContinuousVestingAccount
|
|
func NewContinuousVestingAccount(
|
|
baseAcc *BaseAccount, StartTime, EndTime int64,
|
|
) *ContinuousVestingAccount {
|
|
|
|
baseVestingAcc := &BaseVestingAccount{
|
|
BaseAccount: baseAcc,
|
|
OriginalVesting: baseAcc.Coins,
|
|
EndTime: EndTime,
|
|
}
|
|
|
|
return &ContinuousVestingAccount{
|
|
StartTime: StartTime,
|
|
BaseVestingAccount: baseVestingAcc,
|
|
}
|
|
}
|
|
|
|
func (cva ContinuousVestingAccount) String() string {
|
|
var pubkey string
|
|
|
|
if cva.PubKey != nil {
|
|
pubkey = sdk.MustBech32ifyAccPub(cva.PubKey)
|
|
}
|
|
|
|
return fmt.Sprintf(`Continuous Vesting Account:
|
|
Address: %s
|
|
Pubkey: %s
|
|
Coins: %s
|
|
AccountNumber: %d
|
|
Sequence: %d
|
|
OriginalVesting: %s
|
|
DelegatedFree: %s
|
|
DelegatedVesting: %s
|
|
StartTime: %d
|
|
EndTime: %d `,
|
|
cva.Address, pubkey, cva.Coins, cva.AccountNumber, cva.Sequence,
|
|
cva.OriginalVesting, cva.DelegatedFree, cva.DelegatedVesting,
|
|
cva.StartTime, cva.EndTime,
|
|
)
|
|
}
|
|
|
|
// 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 := 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)
|
|
}
|
|
|
|
// GetStartTime returns the time when vesting starts for a continuous vesting
|
|
// account.
|
|
func (cva *ContinuousVestingAccount) GetStartTime() int64 {
|
|
return cva.StartTime
|
|
}
|
|
|
|
// GetEndTime returns the time when vesting ends for a continuous vesting account.
|
|
func (cva *ContinuousVestingAccount) GetEndTime() int64 {
|
|
return cva.EndTime
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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
|
|
}
|
|
|
|
// NewDelayedVestingAccount returns a DelayedVestingAccount
|
|
func NewDelayedVestingAccount(baseAcc *BaseAccount, EndTime int64) *DelayedVestingAccount {
|
|
baseVestingAcc := &BaseVestingAccount{
|
|
BaseAccount: baseAcc,
|
|
OriginalVesting: baseAcc.Coins,
|
|
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.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)
|
|
}
|
|
|
|
// GetStartTime returns zero since a delayed vesting account has no start time.
|
|
func (dva *DelayedVestingAccount) GetStartTime() int64 {
|
|
return 0
|
|
}
|
|
|
|
// GetEndTime returns the time when vesting ends for a delayed vesting account.
|
|
func (dva *DelayedVestingAccount) GetEndTime() int64 {
|
|
return dva.EndTime
|
|
}
|