2020-04-24 15:20:34 +00:00
package keeper
import (
2020-08-27 00:34:55 +00:00
"fmt"
2020-04-24 15:20:34 +00:00
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
supplyExported "github.com/cosmos/cosmos-sdk/x/supply/exported"
2020-04-30 14:13:31 +00:00
2020-04-24 15:20:34 +00:00
"github.com/kava-labs/kava/x/incentive/types"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
)
// PayoutClaim sends the timelocked claim coins to the input address
2020-09-21 20:20:11 +00:00
func ( k Keeper ) PayoutClaim ( ctx sdk . Context , addr sdk . AccAddress , collateralType string , id uint64 , multiplierName types . MultiplierName ) error {
2020-08-21 19:42:46 +00:00
claim , found := k . GetClaim ( ctx , addr , collateralType , id )
2020-04-24 15:20:34 +00:00
if ! found {
2020-08-21 19:42:46 +00:00
return sdkerrors . Wrapf ( types . ErrClaimNotFound , "id: %d, collateral type %s, address: %s" , id , collateralType , addr )
2020-04-24 15:20:34 +00:00
}
2020-08-21 19:42:46 +00:00
claimPeriod , found := k . GetClaimPeriod ( ctx , id , collateralType )
2020-04-24 15:20:34 +00:00
if ! found {
2020-08-21 19:42:46 +00:00
return sdkerrors . Wrapf ( types . ErrClaimPeriodNotFound , "id: %d, collateral type: %s" , id , collateralType )
2020-04-24 15:20:34 +00:00
}
2020-09-21 20:20:11 +00:00
multiplier , found := claimPeriod . GetMultiplier ( multiplierName )
if ! found {
return sdkerrors . Wrapf ( types . ErrInvalidMultiplier , string ( multiplierName ) )
}
rewardAmount := sdk . NewDecFromInt ( claim . Reward . Amount ) . Mul ( multiplier . Factor ) . RoundInt ( )
if rewardAmount . IsZero ( ) {
return types . ErrZeroClaim
}
rewardCoin := sdk . NewCoin ( claim . Reward . Denom , rewardAmount )
length := ctx . BlockTime ( ) . AddDate ( 0 , int ( multiplier . MonthsLockup ) , 0 ) . Unix ( ) - ctx . BlockTime ( ) . Unix ( )
err := k . SendTimeLockedCoinsToAccount ( ctx , types . IncentiveMacc , addr , sdk . NewCoins ( rewardCoin ) , length )
2020-04-24 15:20:34 +00:00
if err != nil {
return err
}
2020-08-21 19:42:46 +00:00
k . DeleteClaim ( ctx , addr , collateralType , id )
2020-05-07 17:46:40 +00:00
2020-04-24 15:20:34 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeClaim ,
2020-05-07 17:46:40 +00:00
sdk . NewAttribute ( types . AttributeKeyClaimedBy , addr . String ( ) ) ,
sdk . NewAttribute ( types . AttributeKeyClaimAmount , claim . Reward . String ( ) ) ,
2020-08-27 00:34:55 +00:00
sdk . NewAttribute ( types . AttributeKeyClaimPeriod , fmt . Sprintf ( "%d" , claim . ClaimPeriodID ) ) ,
2020-04-24 15:20:34 +00:00
) ,
)
return nil
}
2020-09-14 20:18:21 +00:00
// SendTimeLockedCoinsToAccount sends time-locked coins from the input module account to the recipient. If the recipients account is not a vesting account and the input length is greater than zero, the recipient account is converted to a periodic vesting account and the coins are added to the vesting balance as a vesting period with the input length.
2020-04-24 15:20:34 +00:00
func ( k Keeper ) SendTimeLockedCoinsToAccount ( ctx sdk . Context , senderModule string , recipientAddr sdk . AccAddress , amt sdk . Coins , length int64 ) error {
macc := k . supplyKeeper . GetModuleAccount ( ctx , senderModule )
if ! macc . GetCoins ( ) . IsAllGTE ( amt ) {
return sdkerrors . Wrapf ( types . ErrInsufficientModAccountBalance , "%s" , senderModule )
}
// 0. Get the account from the account keeper and do a type switch, error if it's a validator vesting account or module account (can make this work for validator vesting later if necessary)
acc := k . accountKeeper . GetAccount ( ctx , recipientAddr )
2020-09-14 20:18:21 +00:00
if acc == nil {
return sdkerrors . Wrapf ( types . ErrAccountNotFound , recipientAddr . String ( ) )
}
if length == 0 {
return k . supplyKeeper . SendCoinsFromModuleToAccount ( ctx , senderModule , recipientAddr , amt )
}
2020-04-24 15:20:34 +00:00
switch acc . ( type ) {
case * validatorvesting . ValidatorVestingAccount , supplyExported . ModuleAccountI :
return sdkerrors . Wrapf ( types . ErrInvalidAccountType , "%T" , acc )
case * vesting . PeriodicVestingAccount :
return k . SendTimeLockedCoinsToPeriodicVestingAccount ( ctx , senderModule , recipientAddr , amt , length )
case * auth . BaseAccount :
return k . SendTimeLockedCoinsToBaseAccount ( ctx , senderModule , recipientAddr , amt , length )
default :
return sdkerrors . Wrapf ( types . ErrInvalidAccountType , "%T" , acc )
}
}
// SendTimeLockedCoinsToPeriodicVestingAccount sends time-locked coins from the input module account to the recipient
func ( k Keeper ) SendTimeLockedCoinsToPeriodicVestingAccount ( ctx sdk . Context , senderModule string , recipientAddr sdk . AccAddress , amt sdk . Coins , length int64 ) error {
err := k . supplyKeeper . SendCoinsFromModuleToAccount ( ctx , senderModule , recipientAddr , amt )
if err != nil {
return err
}
k . addCoinsToVestingSchedule ( ctx , recipientAddr , amt , length )
return nil
}
// SendTimeLockedCoinsToBaseAccount sends time-locked coins from the input module account to the recipient, converting the recipient account to a vesting account
func ( k Keeper ) SendTimeLockedCoinsToBaseAccount ( ctx sdk . Context , senderModule string , recipientAddr sdk . AccAddress , amt sdk . Coins , length int64 ) error {
err := k . supplyKeeper . SendCoinsFromModuleToAccount ( ctx , senderModule , recipientAddr , amt )
if err != nil {
return err
}
acc := k . accountKeeper . GetAccount ( ctx , recipientAddr )
// transition the account to a periodic vesting account:
bacc := authtypes . NewBaseAccount ( acc . GetAddress ( ) , acc . GetCoins ( ) , acc . GetPubKey ( ) , acc . GetAccountNumber ( ) , acc . GetSequence ( ) )
newPeriods := vesting . Periods { types . NewPeriod ( amt , length ) }
bva , err := vesting . NewBaseVestingAccount ( bacc , amt , ctx . BlockTime ( ) . Unix ( ) + length )
if err != nil {
return err
}
pva := vesting . NewPeriodicVestingAccountRaw ( bva , ctx . BlockTime ( ) . Unix ( ) , newPeriods )
k . accountKeeper . SetAccount ( ctx , pva )
return nil
}
// DeleteExpiredClaimsAndClaimPeriods deletes expired claim periods and their associated claims
func ( k Keeper ) DeleteExpiredClaimsAndClaimPeriods ( ctx sdk . Context ) {
k . IterateClaimPeriods ( ctx , func ( cp types . ClaimPeriod ) ( stop bool ) {
if ! cp . End . Before ( ctx . BlockTime ( ) ) {
return false
}
k . IterateClaims ( ctx , func ( c types . Claim ) ( stop bool ) {
2020-08-21 19:42:46 +00:00
if ! ( c . CollateralType == cp . CollateralType && c . ClaimPeriodID == cp . ID ) {
2020-04-24 15:20:34 +00:00
return false
}
2020-08-21 19:42:46 +00:00
k . DeleteClaim ( ctx , c . Owner , c . CollateralType , c . ClaimPeriodID )
2020-04-24 15:20:34 +00:00
return false
} )
2020-08-21 19:42:46 +00:00
k . DeleteClaimPeriod ( ctx , cp . ID , cp . CollateralType )
2020-05-07 17:46:40 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeClaimPeriodExpiry ,
sdk . NewAttribute ( types . AttributeKeyClaimPeriod , cp . String ( ) ) ,
) ,
)
2020-04-24 15:20:34 +00:00
return false
} )
}
2020-09-01 12:34:11 +00:00
// GetActiveClaimsByAddressAndCollateralType returns all claims for a specific user and address and a bool for if any were found
func ( k Keeper ) GetActiveClaimsByAddressAndCollateralType ( ctx sdk . Context , addr sdk . AccAddress , collateralType string ) ( claims types . Claims , found bool ) {
2020-04-24 15:20:34 +00:00
found = false
k . IterateClaimPeriods ( ctx , func ( cp types . ClaimPeriod ) ( stop bool ) {
2020-08-21 19:42:46 +00:00
if cp . CollateralType != collateralType {
2020-04-24 15:20:34 +00:00
return false
}
2020-08-21 19:42:46 +00:00
c , hasClaim := k . GetClaim ( ctx , addr , cp . CollateralType , cp . ID )
2020-04-24 15:20:34 +00:00
if ! hasClaim {
return false
}
found = true
claims = append ( claims , c )
return false
} )
return claims , found
}
2020-09-01 12:34:11 +00:00
// GetAllClaimsByAddressAndCollateralType returns all claims for a specific user and address and a bool for if any were found
func ( k Keeper ) GetAllClaimsByAddressAndCollateralType ( ctx sdk . Context , addr sdk . AccAddress , collateralType string ) ( claims types . AugmentedClaims , found bool ) {
found = false
k . IterateClaimPeriods ( ctx , func ( cp types . ClaimPeriod ) ( stop bool ) {
if cp . CollateralType != collateralType {
return false
}
c , hasClaim := k . GetClaim ( ctx , addr , cp . CollateralType , cp . ID )
if ! hasClaim {
return false
}
ac := types . NewAugmentedClaim ( c , true )
found = true
claims = append ( claims , ac )
return false
} )
nextClaimID := k . GetNextClaimPeriodID ( ctx , collateralType )
c , hasClaim := k . GetClaim ( ctx , addr , collateralType , nextClaimID )
if ! hasClaim {
return claims , found
}
ac := types . NewAugmentedClaim ( c , false )
claims = append ( claims , ac )
return claims , true
}
2020-04-24 15:20:34 +00:00
// addCoinsToVestingSchedule adds coins to the input account's vesting schedule where length is the amount of time (from the current block time), in seconds, that the coins will be vesting for
// the input address must be a periodic vesting account
func ( k Keeper ) addCoinsToVestingSchedule ( ctx sdk . Context , addr sdk . AccAddress , amt sdk . Coins , length int64 ) {
acc := k . accountKeeper . GetAccount ( ctx , addr )
vacc := acc . ( * vesting . PeriodicVestingAccount )
// Add the new vesting coins to OriginalVesting
vacc . OriginalVesting = vacc . OriginalVesting . Add ( amt ... )
// update vesting periods
2020-08-27 00:34:55 +00:00
// EndTime = 100
// BlockTime = 110
// length == 6
2020-04-24 15:20:34 +00:00
if vacc . EndTime < ctx . BlockTime ( ) . Unix ( ) {
// edge case one - the vesting account's end time is in the past (ie, all previous vesting periods have completed)
// append a new period to the vesting account, update the end time, update the account in the store and return
2020-08-27 00:34:55 +00:00
newPeriodLength := ( ctx . BlockTime ( ) . Unix ( ) - vacc . EndTime ) + length // 110 - 100 + 6 = 16
2020-04-24 15:20:34 +00:00
newPeriod := types . NewPeriod ( amt , newPeriodLength )
vacc . VestingPeriods = append ( vacc . VestingPeriods , newPeriod )
vacc . EndTime = ctx . BlockTime ( ) . Unix ( ) + length
k . accountKeeper . SetAccount ( ctx , vacc )
return
}
2020-08-27 00:34:55 +00:00
// StartTime = 110
// BlockTime = 100
// length = 6
2020-04-24 15:20:34 +00:00
if vacc . StartTime > ctx . BlockTime ( ) . Unix ( ) {
// edge case two - the vesting account's start time is in the future (all periods have not started)
// update the start time to now and adjust the period lengths in place - a new period will be inserted in the next code block
updatedPeriods := vesting . Periods { }
for i , period := range vacc . VestingPeriods {
updatedPeriod := period
if i == 0 {
2020-08-27 00:34:55 +00:00
updatedPeriod = types . NewPeriod ( period . Amount , ( vacc . StartTime - ctx . BlockTime ( ) . Unix ( ) ) + period . Length ) // 110 - 100 + 6 = 16
2020-04-24 15:20:34 +00:00
}
updatedPeriods = append ( updatedPeriods , updatedPeriod )
}
vacc . VestingPeriods = updatedPeriods
vacc . StartTime = ctx . BlockTime ( ) . Unix ( )
}
// logic for inserting a new vesting period into the existing vesting schedule
2020-08-27 00:34:55 +00:00
remainingLength := vacc . EndTime - ctx . BlockTime ( ) . Unix ( )
elapsedTime := ctx . BlockTime ( ) . Unix ( ) - vacc . StartTime
2020-04-24 15:20:34 +00:00
proposedEndTime := ctx . BlockTime ( ) . Unix ( ) + length
2020-08-27 00:34:55 +00:00
if remainingLength < length {
// in the case that the proposed length is longer than the remaining length of all vesting periods, create a new period with length equal to the difference between the proposed length and the previous total length
newPeriodLength := length - remainingLength
2020-04-24 15:20:34 +00:00
newPeriod := types . NewPeriod ( amt , newPeriodLength )
vacc . VestingPeriods = append ( vacc . VestingPeriods , newPeriod )
// update the end time so that the sum of all period lengths equals endTime - startTime
vacc . EndTime = proposedEndTime
} else {
// In the case that the proposed length is less than or equal to the sum of all previous period lengths, insert the period and update other periods as necessary.
// EXAMPLE (l is length, a is amount)
// Original Periods: {[l: 1 a: 1], [l: 2, a: 1], [l:8, a:3], [l: 5, a: 3]}
// Period we want to insert [l: 5, a: x]
// Expected result:
// {[l: 1, a: 1], [l:2, a: 1], [l:2, a:x], [l:6, a:3], [l:5, a:3]}
2020-08-27 00:34:55 +00:00
// StartTime = 100
// Periods = [5,5,5,5]
// EndTime = 120
// BlockTime = 101
// length = 2
// for period in Periods:
// iteration 1:
// lengthCounter = 5
// if 5 < 101 - 100 + 2 - no
// if 5 = 3 - no
// else
// newperiod = 2 - 0
2020-04-24 15:20:34 +00:00
newPeriods := vesting . Periods { }
lengthCounter := int64 ( 0 )
appendRemaining := false
for _ , period := range vacc . VestingPeriods {
if appendRemaining {
newPeriods = append ( newPeriods , period )
continue
}
lengthCounter += period . Length
2020-08-27 00:34:55 +00:00
if lengthCounter < elapsedTime + length { // 1
2020-04-24 15:20:34 +00:00
newPeriods = append ( newPeriods , period )
2020-08-27 00:34:55 +00:00
} else if lengthCounter == elapsedTime + length {
2020-04-24 15:20:34 +00:00
newPeriod := types . NewPeriod ( period . Amount . Add ( amt ... ) , period . Length )
newPeriods = append ( newPeriods , newPeriod )
appendRemaining = true
} else {
2020-08-27 00:34:55 +00:00
newPeriod := types . NewPeriod ( amt , elapsedTime + length - types . GetTotalVestingPeriodLength ( newPeriods ) )
2020-04-24 15:20:34 +00:00
previousPeriod := types . NewPeriod ( period . Amount , period . Length - newPeriod . Length )
newPeriods = append ( newPeriods , newPeriod , previousPeriod )
appendRemaining = true
}
}
vacc . VestingPeriods = newPeriods
}
k . accountKeeper . SetAccount ( ctx , vacc )
return
}