package keeper
import (
"fmt"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
"math"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/tendermint/tendermint/libs/log"
ctypes "github.com/joinresistance/space-pussy/types"
bandwithkeeper "github.com/joinresistance/space-pussy/x/bandwidth/keeper"
"github.com/joinresistance/space-pussy/x/resources/types"
)
type Keeper struct {
cdc codec.BinaryCodec
accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
bandwidthMeter *bandwithkeeper.BandwidthMeter
paramSpace paramstypes.Subspace
}
func NewKeeper(
cdc codec.BinaryCodec,
ak types.AccountKeeper,
bk types.BankKeeper,
bm *bandwithkeeper.BandwidthMeter,
paramSpace paramstypes.Subspace,
) Keeper {
if addr := ak.GetModuleAddress(types.ResourcesName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", types.ResourcesName))
}
if !paramSpace.HasKeyTable() {
paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable())
}
keeper := Keeper{
cdc: cdc,
accountKeeper: ak,
bankKeeper: bk,
bandwidthMeter: bm,
paramSpace: paramSpace,
}
return keeper
}
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
k.paramSpace.GetParamSet(ctx, ¶ms)
return params
}
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramSpace.SetParamSet(ctx, ¶ms)
}
func (k Keeper) ConvertResource(
ctx sdk.Context,
neuron sdk.AccAddress,
amount sdk.Coin,
resource string,
length uint64,
) (error, sdk.Coin) {
periodAvailableFlag := k.CheckAvailablePeriod(ctx, length, resource)
if periodAvailableFlag == false {
return types.ErrNotAvailablePeriod, sdk.Coin{}
}
if k.bankKeeper.SpendableCoins(ctx, neuron).AmountOf(ctypes.SCYB).LT(amount.Amount) {
return sdkerrors.ErrInsufficientFunds, sdk.Coin{}
}
if uint32(length) < k.MinInvestmintPeriodSec(ctx) {
return types.ErrNotAvailablePeriod, sdk.Coin{}
}
err := k.AddTimeLockedCoinsToAccount(ctx, neuron, sdk.NewCoins(amount), int64(length))
if err != nil {
return sdkerrors.Wrapf(types.ErrTimeLockCoins, err.Error()), sdk.Coin{}
}
err, minted := k.Mint(ctx, neuron, amount, resource, length)
if err != nil {
return sdkerrors.Wrapf(types.ErrIssueCoins, err.Error()), sdk.Coin{}
}
return err, minted
}
func (k Keeper) AddTimeLockedCoinsToAccount(ctx sdk.Context, recipientAddr sdk.AccAddress, amt sdk.Coins, length int64) error {
acc := k.accountKeeper.GetAccount(ctx, recipientAddr)
if acc == nil {
return sdkerrors.Wrapf(types.ErrAccountNotFound, recipientAddr.String())
}
switch acc.(type) {
case *vestingtypes.PeriodicVestingAccount:
return k.AddTimeLockedCoinsToPeriodicVestingAccount(ctx, recipientAddr, amt, length, false)
case *authtypes.BaseAccount:
return k.AddTimeLockedCoinsToBaseAccount(ctx, recipientAddr, amt, length)
default:
return sdkerrors.Wrapf(types.ErrInvalidAccountType, "%T", acc)
}
}
func (k Keeper) AddTimeLockedCoinsToPeriodicVestingAccount(ctx sdk.Context, recipientAddr sdk.AccAddress, amt sdk.Coins, length int64, mergeSlot bool) error {
err := k.addCoinsToVestingSchedule(ctx, recipientAddr, amt, length, mergeSlot)
if err != nil {
return err
}
return nil
}
func (k Keeper) AddTimeLockedCoinsToBaseAccount(ctx sdk.Context, recipientAddr sdk.AccAddress, amt sdk.Coins, length int64) error {
acc := k.accountKeeper.GetAccount(ctx, recipientAddr)
bacc := authtypes.NewBaseAccount(acc.GetAddress(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
newPeriods := vestingtypes.Periods{types.NewPeriod(amt, length)}
bva := vestingtypes.NewBaseVestingAccount(bacc, amt, ctx.BlockTime().Unix()+length)
pva := vestingtypes.NewPeriodicVestingAccountRaw(bva, ctx.BlockTime().Unix(), newPeriods)
k.accountKeeper.SetAccount(ctx, pva)
return nil
}
func (k Keeper) addCoinsToVestingSchedule(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, length int64, mergeSlot bool) error {
acc := k.accountKeeper.GetAccount(ctx, addr)
vacc := acc.(*vestingtypes.PeriodicVestingAccount)
vacc.OriginalVesting = vacc.OriginalVesting.Add(amt...)
if vacc.EndTime < ctx.BlockTime().Unix() {
newPeriod := types.NewPeriod(amt, length)
vacc.VestingPeriods = append(vestingtypes.Periods{}, newPeriod)
vacc.StartTime = ctx.BlockTime().Unix()
vacc.EndTime = ctx.BlockTime().Unix() + length
vacc.OriginalVesting = newPeriod.Amount
k.accountKeeper.SetAccount(ctx, vacc)
return nil
}
if vacc.StartTime > ctx.BlockTime().Unix() {
updatedPeriods := vestingtypes.Periods{}
for i, period := range vacc.VestingPeriods {
updatedPeriod := period
if i == 0 {
updatedPeriod = types.NewPeriod(period.Amount, (vacc.StartTime-ctx.BlockTime().Unix())+period.Length) }
updatedPeriods = append(updatedPeriods, updatedPeriod)
}
vacc.VestingPeriods = updatedPeriods
vacc.StartTime = ctx.BlockTime().Unix()
}
if len(vacc.VestingPeriods) == int(k.MaxSlots(ctx)) && !mergeSlot {
if vacc.StartTime+vacc.VestingPeriods[0].Length > ctx.BlockTime().Unix() {
return types.ErrFullSlots
} else {
activePeriods := vestingtypes.Periods{}
accumulatedLength := int64(0)
shiftStartTime := int64(0)
for _, period := range vacc.VestingPeriods {
if vacc.StartTime+period.Length+accumulatedLength > ctx.BlockTime().Unix() {
activePeriods = append(activePeriods, period)
} else {
shiftStartTime += period.Length
}
accumulatedLength += period.Length
}
updatedPeriods := vestingtypes.Periods{}
updatedOriginalVesting := sdk.Coins{}
for _, period := range activePeriods {
updatedOriginalVesting = updatedOriginalVesting.Add(period.Amount...)
updatedPeriods = append(updatedPeriods, period)
}
vacc.OriginalVesting = updatedOriginalVesting.Add(amt...)
vacc.VestingPeriods = updatedPeriods
vacc.StartTime = vacc.StartTime + shiftStartTime
}
}
remainingLength := vacc.EndTime - ctx.BlockTime().Unix()
elapsedTime := ctx.BlockTime().Unix() - vacc.StartTime
proposedEndTime := ctx.BlockTime().Unix() + length
if remainingLength < length {
newPeriodLength := length - remainingLength
newPeriod := types.NewPeriod(amt, newPeriodLength)
vacc.VestingPeriods = append(vacc.VestingPeriods, newPeriod)
vacc.EndTime = proposedEndTime
} else {
newPeriods := vestingtypes.Periods{}
lengthCounter := int64(0)
appendRemaining := false
for _, period := range vacc.VestingPeriods {
if appendRemaining {
newPeriods = append(newPeriods, period)
continue
}
lengthCounter += period.Length
if lengthCounter < elapsedTime+length { newPeriods = append(newPeriods, period)
} else if lengthCounter == elapsedTime+length {
newPeriod := types.NewPeriod(period.Amount.Add(amt...), period.Length)
newPeriods = append(newPeriods, newPeriod)
appendRemaining = true
} else {
newPeriod := types.NewPeriod(amt, elapsedTime+length-types.GetTotalVestingPeriodLength(newPeriods))
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 nil
}
func (k Keeper) Mint(ctx sdk.Context, recipientAddr sdk.AccAddress, amt sdk.Coin, resource string, length uint64) (error, sdk.Coin) {
acc := k.accountKeeper.GetAccount(ctx, recipientAddr)
if acc == nil {
return sdkerrors.Wrapf(types.ErrAccountNotFound, recipientAddr.String()), sdk.Coin{}
}
toMint := k.CalculateInvestmint(ctx, amt, resource, length)
if toMint.Amount.LT(sdk.NewInt(1000)) {
return sdkerrors.Wrapf(types.ErrSmallReturn, recipientAddr.String()), sdk.Coin{}
}
err := k.bankKeeper.MintCoins(ctx, types.ResourcesName, sdk.NewCoins(toMint))
if err != nil {
return sdkerrors.Wrapf(types.ErrMintCoins, recipientAddr.String()), sdk.Coin{}
}
err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ResourcesName, recipientAddr, sdk.NewCoins(toMint))
if err != nil {
return sdkerrors.Wrapf(types.ErrSendMintedCoins, recipientAddr.String()), sdk.Coin{}
}
err = k.AddTimeLockedCoinsToPeriodicVestingAccount(ctx, recipientAddr, sdk.NewCoins(toMint), int64(length), true)
if err != nil {
return sdkerrors.Wrapf(types.ErrTimeLockCoins, err.Error()), sdk.Coin{}
}
if resource == ctypes.VOLT {
k.bandwidthMeter.AddToDesirableBandwidth(ctx, toMint.Amount.Uint64())
neuronBandwidth := k.bandwidthMeter.GetAccountBandwidth(ctx, recipientAddr)
k.bandwidthMeter.ChargeAccountBandwidth(ctx, neuronBandwidth, 1000)
}
return nil, toMint
}
func (k Keeper) CalculateInvestmint(ctx sdk.Context, amt sdk.Coin, resource string, length uint64) sdk.Coin {
var toMint sdk.Coin
var halving sdk.Dec
switch resource {
case ctypes.VOLT:
cycles := sdk.NewDec(int64(length)).QuoInt64(int64(k.BaseInvestmintPeriodVolt(ctx)))
base := sdk.NewDec(amt.Amount.Int64()).QuoInt64(k.BaseInvestmintAmountVolt(ctx).Amount.Int64())
if ctx.BlockHeight() > 15000000 {
halving = sdk.NewDecWithPrec(int64(math.Pow(0.5, float64((ctx.BlockHeight()-600000)/int64(k.BaseHalvingPeriodVolt(ctx))))*10000), 4)
} else {
halving = sdk.OneDec()
}
if halving.LT(sdk.NewDecWithPrec(1, 2)) {
halving = sdk.NewDecWithPrec(1, 2)
}
toMint = ctypes.NewVoltCoin(base.Mul(cycles).Mul(halving).Mul(sdk.NewDec(1000)).TruncateInt64())
k.Logger(ctx).Info("Investmint", "cycles", cycles.String(), "base", base.String(), "halving", halving.String(), "mint", toMint.String())
case ctypes.AMPERE:
cycles := sdk.NewDec(int64(length)).QuoInt64(int64(k.BaseInvestmintPeriodAmpere(ctx)))
base := sdk.NewDec(amt.Amount.Int64()).QuoInt64(k.BaseInvestmintAmountAmpere(ctx).Amount.Int64())
if ctx.BlockHeight() > 15000000 {
halving = sdk.NewDecWithPrec(int64(math.Pow(0.5, float64((ctx.BlockHeight()-600000)/int64(k.BaseHalvingPeriodAmpere(ctx))))*10000), 4)
} else {
halving = sdk.OneDec()
}
if halving.LT(sdk.NewDecWithPrec(1, 2)) {
halving = sdk.NewDecWithPrec(1, 2)
}
toMint = ctypes.NewAmpereCoin(base.Mul(cycles).Mul(halving).Mul(sdk.NewDec(1000)).TruncateInt64())
k.Logger(ctx).Info("Investmint", "cycles", cycles.String(), "base", base.String(), "halving", halving.String(), "mint", toMint.String())
}
return toMint
}
func (k Keeper) CheckAvailablePeriod(ctx sdk.Context, length uint64, resource string) bool {
var availableLength uint64
passed := ctx.BlockHeight()
switch resource {
case ctypes.VOLT:
halvingVolt := k.BaseHalvingPeriodVolt(ctx)
doubling := uint32(math.Pow(2, float64(passed/int64(halvingVolt))))
availableLength = uint64(doubling * halvingVolt * 5)
case ctypes.AMPERE:
halvingAmpere := k.BaseHalvingPeriodAmpere(ctx)
doubling := uint32(math.Pow(2, float64(passed/int64(halvingAmpere))))
availableLength = uint64(doubling * halvingAmpere * 5)
}
return length <= availableLength
}
func (k Keeper) MaxSlots(ctx sdk.Context) (res uint32) {
k.paramSpace.Get(ctx, types.KeyMaxSlots, &res)
return
}
func (k Keeper) BaseHalvingPeriodVolt(ctx sdk.Context) (res uint32) {
k.paramSpace.Get(ctx, types.KeyHalvingPeriodVoltBlocks, &res)
return
}
func (k Keeper) BaseHalvingPeriodAmpere(ctx sdk.Context) (res uint32) {
k.paramSpace.Get(ctx, types.KeyHalvingPeriodAmpereBlocks, &res)
return
}
func (k Keeper) BaseInvestmintPeriodVolt(ctx sdk.Context) (res uint32) {
k.paramSpace.Get(ctx, types.KeyBaseInvestmintPeriodVolt, &res)
return
}
func (k Keeper) BaseInvestmintPeriodAmpere(ctx sdk.Context) (res uint32) {
k.paramSpace.Get(ctx, types.KeyBaseInvestmintPeriodAmpere, &res)
return
}
func (k Keeper) BaseInvestmintAmountVolt(ctx sdk.Context) (res sdk.Coin) {
k.paramSpace.Get(ctx, types.KeyBaseInvestmintAmountVolt, &res)
return
}
func (k Keeper) BaseInvestmintAmountAmpere(ctx sdk.Context) (res sdk.Coin) {
k.paramSpace.Get(ctx, types.KeyBaseInvestmintAmountAmpere, &res)
return
}
func (k Keeper) MinInvestmintPeriodSec(ctx sdk.Context) (res uint32) {
k.paramSpace.Get(ctx, types.KeyMinInvestmintPeriod, &res)
return
}