mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-10 10:05:18 +00:00
Cdp accumulators (#751)
* Add 'InterestFactor' to CDP type (#734) * update cdp type to include interest factor * fix build * Add cdp accumulator methods (#735) * remame fees to interest * add accumulate interest method * add basic test * add note * address review comments * update tests * Add sync cdp interest method (#737) * remame fees to interest * add accumulate interest method * add basic test * add note * address review comments * update tests * remove old fee functions * add method to synchronize cdp interest * add multi-cdp tests * add test with many blocks * add test for interest getter * address review comments * calculate time difference then convert to seconds * fix: update collateral index when syncing interest * fix: differentiate between case when apy is zero and all fees are being rounded to zero * fix: round time difference properly * update cdp genesis state and migrations (#738) * remame fees to interest * add accumulate interest method * add basic test * add note * address review comments * update tests * remove old fee functions * add method to synchronize cdp interest * add multi-cdp tests * add test with many blocks * add test for interest getter * update cdp genesis state and migrations * address review comments * calculate time difference then convert to seconds * fix: update collateral index when syncing interest * fix: differentiate between case when apy is zero and all fees are being rounded to zero * fix: simplify add/remove/update collateral index * update genesis state to include total principal amounts * update migration * Delete kava-4-cdp-state-block-500000.json * Add cdp liquidations by external keeper (#750) * feat: split liquidations between external keepers and automated begin blocker * address review comments * USDX incentive accumulators (#752) * feat: split liquidations between external keepers and automated begin blocker * wip: refactor usdx minting incentives to use accumulators/hooks * wip: refactor usdx minting claim object * feat: use accumulators/hooks for usdx minting rewards * fix: get tests passing * fix: don't create claim objects unless that cdp type is eligable for rewards * add begin blocker * update client * cleanup comments/tests * update querier * address review comments * fix: check for division by zero * address review comments * run hook before interest is synced * Remove savings rate (#764) * remove savings rate * remove savings rate from debt param * update migrations * address review comments * Add usdx incentives calculation test (#765) * add usdx incentive calculation test * update reward calculation * add allowable error to test criteria * Update x/incentive/keeper/rewards_test.go Co-authored-by: Kevin Davis <karzak@users.noreply.github.com> * fix: remove old fields from test genesis state Co-authored-by: Ruaridh <rhuairahrighairidh@users.noreply.github.com> Co-authored-by: Ruaridh <rhuairahrighairidh@users.noreply.github.com>
This commit is contained in:
parent
fba6b8c4f2
commit
c63ecf908a
@ -99,7 +99,6 @@ var (
|
||||
auction.ModuleName: nil,
|
||||
cdp.ModuleName: {supply.Minter, supply.Burner},
|
||||
cdp.LiquidatorMacc: {supply.Minter, supply.Burner},
|
||||
cdp.SavingsRateMacc: {supply.Minter},
|
||||
bep3.ModuleName: {supply.Minter, supply.Burner},
|
||||
kavadist.ModuleName: {supply.Minter},
|
||||
issuance.ModuleAccountName: {supply.Minter, supply.Burner},
|
||||
@ -341,7 +340,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
|
||||
app.supplyKeeper,
|
||||
auctionSubspace,
|
||||
)
|
||||
app.cdpKeeper = cdp.NewKeeper(
|
||||
cdpKeeper := cdp.NewKeeper(
|
||||
app.cdc,
|
||||
keys[cdp.StoreKey],
|
||||
cdpSubspace,
|
||||
@ -370,7 +369,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
|
||||
keys[incentive.StoreKey],
|
||||
incentiveSubspace,
|
||||
app.supplyKeeper,
|
||||
app.cdpKeeper,
|
||||
&cdpKeeper,
|
||||
app.accountKeeper,
|
||||
)
|
||||
app.issuanceKeeper = issuance.NewKeeper(
|
||||
@ -396,6 +395,8 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
|
||||
app.stakingKeeper = *stakingKeeper.SetHooks(
|
||||
staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks()))
|
||||
|
||||
app.cdpKeeper = *cdpKeeper.SetHooks(cdp.NewMultiCDPHooks(app.incentiveKeeper.Hooks()))
|
||||
|
||||
// create the module manager (Note: Any module instantiated in the module manager that is later modified
|
||||
// must be passed by reference here.)
|
||||
app.mm = module.NewManager(
|
||||
|
@ -22,12 +22,12 @@ import (
|
||||
v38_5supply "github.com/kava-labs/kava/migrate/v0_11/legacy/cosmos-sdk/v0.38.5/supply"
|
||||
v0_11bep3 "github.com/kava-labs/kava/x/bep3"
|
||||
v0_9bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_9"
|
||||
v0_11cdp "github.com/kava-labs/kava/x/cdp"
|
||||
v0_11cdp "github.com/kava-labs/kava/x/cdp/legacy/v0_11"
|
||||
v0_9cdp "github.com/kava-labs/kava/x/cdp/legacy/v0_9"
|
||||
v0_11committee "github.com/kava-labs/kava/x/committee"
|
||||
v0_9committee "github.com/kava-labs/kava/x/committee/legacy/v0_9"
|
||||
v0_11harvest "github.com/kava-labs/kava/x/hard"
|
||||
v0_11incentive "github.com/kava-labs/kava/x/incentive"
|
||||
v0_11harvest "github.com/kava-labs/kava/x/hard/legacy/v0_11"
|
||||
v0_11incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_11"
|
||||
v0_9incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_9"
|
||||
v0_11issuance "github.com/kava-labs/kava/x/issuance"
|
||||
v0_11pricefeed "github.com/kava-labs/kava/x/pricefeed"
|
||||
@ -129,7 +129,7 @@ func MigrateAppState(v0_9AppState v39_genutil.AppMap) v39_genutil.AppMap {
|
||||
delete(v0_9AppState, v0_9pricefeed.ModuleName)
|
||||
v0_11AppState[v0_9pricefeed.ModuleName] = v0_11Codec.MustMarshalJSON(MigratePricefeed(pricefeedGenState))
|
||||
}
|
||||
// v0_11AppState[v0_11harvest.ModuleName] = v0_11Codec.MustMarshalJSON(MigrateHarvest())
|
||||
v0_11AppState[v0_11harvest.ModuleName] = v0_11Codec.MustMarshalJSON(MigrateHarvest())
|
||||
v0_11AppState[v0_11issuance.ModuleName] = v0_11Codec.MustMarshalJSON(v0_11issuance.DefaultGenesisState())
|
||||
return v0_11AppState
|
||||
}
|
||||
@ -308,7 +308,6 @@ func MigrateCommittee(oldGenState v0_9committee.GenesisState) v0_11committee.Gen
|
||||
DebtFloor: oldDebtParam.DebtFloor,
|
||||
Denom: oldDebtParam.Denom,
|
||||
ReferenceAsset: oldDebtParam.ReferenceAsset,
|
||||
SavingsRate: oldDebtParam.SavingsRate,
|
||||
}
|
||||
oldAssetParam := subPermission.AllowedAssetParams[0]
|
||||
newAssetParam := v0_11committee.AllowedAssetParam{
|
||||
@ -633,34 +632,29 @@ func MigrateGov(oldGenState v39_1gov.GenesisState) v39_1gov.GenesisState {
|
||||
return oldGenState
|
||||
}
|
||||
|
||||
// // MigrateHarvest initializes the harvest genesis state for kava-4
|
||||
// func MigrateHarvest() v0_11harvest.GenesisState {
|
||||
// // total HARD per second for lps (week one): 633761
|
||||
// // HARD per second for delegators (week one): 1267522
|
||||
// incentiveGoLiveDate := time.Date(2020, 10, 16, 14, 0, 0, 0, time.UTC)
|
||||
// incentiveEndDate := time.Date(2024, 10, 16, 14, 0, 0, 0, time.UTC)
|
||||
// claimEndDate := time.Date(2025, 10, 16, 14, 0, 0, 0, time.UTC)
|
||||
// harvestGS := v0_11harvest.NewGenesisState(v0_11harvest.NewParams(
|
||||
// true,
|
||||
// v0_11harvest.DistributionSchedules{
|
||||
// v0_11harvest.NewDistributionSchedule(true, "usdx", incentiveGoLiveDate, incentiveEndDate, sdk.NewCoin("hard", sdk.NewInt(310543)), claimEndDate, v0_11harvest.Multipliers{v0_11harvest.NewMultiplier(v0_11harvest.Small, 1, sdk.MustNewDecFromStr("0.33")), v0_11harvest.NewMultiplier(v0_11harvest.Large, 12, sdk.OneDec())}),
|
||||
// v0_11harvest.NewDistributionSchedule(true, "hard", incentiveGoLiveDate, incentiveEndDate, sdk.NewCoin("hard", sdk.NewInt(285193)), claimEndDate, v0_11harvest.Multipliers{v0_11harvest.NewMultiplier(v0_11harvest.Small, 1, sdk.MustNewDecFromStr("0.33")), v0_11harvest.NewMultiplier(v0_11harvest.Large, 12, sdk.OneDec())}),
|
||||
// v0_11harvest.NewDistributionSchedule(true, "bnb", incentiveGoLiveDate, incentiveEndDate, sdk.NewCoin("hard", sdk.NewInt(12675)), claimEndDate, v0_11harvest.Multipliers{v0_11harvest.NewMultiplier(v0_11harvest.Small, 1, sdk.MustNewDecFromStr("0.33")), v0_11harvest.NewMultiplier(v0_11harvest.Large, 12, sdk.OneDec())}),
|
||||
// v0_11harvest.NewDistributionSchedule(true, "ukava", incentiveGoLiveDate, incentiveEndDate, sdk.NewCoin("hard", sdk.NewInt(25350)), claimEndDate, v0_11harvest.Multipliers{v0_11harvest.NewMultiplier(v0_11harvest.Small, 1, sdk.MustNewDecFromStr("0.33")), v0_11harvest.NewMultiplier(v0_11harvest.Large, 12, sdk.OneDec())}),
|
||||
// },
|
||||
// v0_11harvest.DelegatorDistributionSchedules{v0_11harvest.NewDelegatorDistributionSchedule(
|
||||
// v0_11harvest.NewDistributionSchedule(true, "ukava", incentiveGoLiveDate, incentiveEndDate, sdk.NewCoin("hard", sdk.NewInt(1267522)), claimEndDate, v0_11harvest.Multipliers{v0_11harvest.NewMultiplier(v0_11harvest.Small, 1, sdk.MustNewDecFromStr("0.33")), v0_11harvest.NewMultiplier(v0_11harvest.Large, 12, sdk.OneDec())}),
|
||||
// time.Hour*24,
|
||||
// ),
|
||||
// },
|
||||
// v0_11harvest.BlockLimits{
|
||||
// v0_11harvest.NewBlockLimit("usdx", sdk.Dec(0.9)),
|
||||
// v0_11harvest.NewBlockLimit("ukava", sdk.Dec(0.6)),
|
||||
// v0_11harvest.NewBlockLimit("bnb", sdk.Dec(0.9)),
|
||||
// },
|
||||
// ), v0_11harvest.DefaultPreviousBlockTime, v0_11harvest.DefaultDistributionTimes)
|
||||
// return harvestGS
|
||||
// }
|
||||
// MigrateHarvest initializes the harvest genesis state for kava-4
|
||||
func MigrateHarvest() v0_11harvest.GenesisState {
|
||||
// total HARD per second for lps (week one): 633761
|
||||
// HARD per second for delegators (week one): 1267522
|
||||
incentiveGoLiveDate := time.Date(2020, 10, 16, 14, 0, 0, 0, time.UTC)
|
||||
incentiveEndDate := time.Date(2024, 10, 16, 14, 0, 0, 0, time.UTC)
|
||||
claimEndDate := time.Date(2025, 10, 16, 14, 0, 0, 0, time.UTC)
|
||||
harvestGS := v0_11harvest.NewGenesisState(v0_11harvest.NewParams(
|
||||
true,
|
||||
v0_11harvest.DistributionSchedules{
|
||||
v0_11harvest.NewDistributionSchedule(true, "usdx", incentiveGoLiveDate, incentiveEndDate, sdk.NewCoin("hard", sdk.NewInt(310543)), claimEndDate, v0_11harvest.Multipliers{v0_11harvest.NewMultiplier(v0_11harvest.Small, 1, sdk.MustNewDecFromStr("0.33")), v0_11harvest.NewMultiplier(v0_11harvest.Large, 12, sdk.OneDec())}),
|
||||
v0_11harvest.NewDistributionSchedule(true, "hard", incentiveGoLiveDate, incentiveEndDate, sdk.NewCoin("hard", sdk.NewInt(285193)), claimEndDate, v0_11harvest.Multipliers{v0_11harvest.NewMultiplier(v0_11harvest.Small, 1, sdk.MustNewDecFromStr("0.33")), v0_11harvest.NewMultiplier(v0_11harvest.Large, 12, sdk.OneDec())}),
|
||||
v0_11harvest.NewDistributionSchedule(true, "bnb", incentiveGoLiveDate, incentiveEndDate, sdk.NewCoin("hard", sdk.NewInt(12675)), claimEndDate, v0_11harvest.Multipliers{v0_11harvest.NewMultiplier(v0_11harvest.Small, 1, sdk.MustNewDecFromStr("0.33")), v0_11harvest.NewMultiplier(v0_11harvest.Large, 12, sdk.OneDec())}),
|
||||
v0_11harvest.NewDistributionSchedule(true, "ukava", incentiveGoLiveDate, incentiveEndDate, sdk.NewCoin("hard", sdk.NewInt(25350)), claimEndDate, v0_11harvest.Multipliers{v0_11harvest.NewMultiplier(v0_11harvest.Small, 1, sdk.MustNewDecFromStr("0.33")), v0_11harvest.NewMultiplier(v0_11harvest.Large, 12, sdk.OneDec())}),
|
||||
},
|
||||
v0_11harvest.DelegatorDistributionSchedules{v0_11harvest.NewDelegatorDistributionSchedule(
|
||||
v0_11harvest.NewDistributionSchedule(true, "ukava", incentiveGoLiveDate, incentiveEndDate, sdk.NewCoin("hard", sdk.NewInt(1267522)), claimEndDate, v0_11harvest.Multipliers{v0_11harvest.NewMultiplier(v0_11harvest.Small, 1, sdk.MustNewDecFromStr("0.33")), v0_11harvest.NewMultiplier(v0_11harvest.Large, 12, sdk.OneDec())}),
|
||||
time.Hour*24,
|
||||
),
|
||||
},
|
||||
), v0_11harvest.DefaultPreviousBlockTime, v0_11harvest.DefaultDistributionTimes)
|
||||
return harvestGS
|
||||
}
|
||||
|
||||
// MigrateCDP migrates from a v0.9 (or v0.10) cdp genesis state to a v0.11 cdp genesis state
|
||||
func MigrateCDP(oldGenState v0_9cdp.GenesisState) v0_11cdp.GenesisState {
|
||||
|
@ -12,11 +12,8 @@ import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
v39_1auth "github.com/cosmos/cosmos-sdk/x/auth"
|
||||
v39_1auth_vesting "github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||
"github.com/cosmos/cosmos-sdk/x/genutil"
|
||||
v39_1supply "github.com/cosmos/cosmos-sdk/x/supply"
|
||||
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
v38_5auth "github.com/kava-labs/kava/migrate/v0_11/legacy/cosmos-sdk/v0.38.5/auth"
|
||||
v38_5supply "github.com/kava-labs/kava/migrate/v0_11/legacy/cosmos-sdk/v0.38.5/supply"
|
||||
@ -167,25 +164,3 @@ func TestMigratePricefeed(t *testing.T) {
|
||||
err = newGenState.Validate()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestMigrateFull(t *testing.T) {
|
||||
oldGenDoc, err := tmtypes.GenesisDocFromFile(filepath.Join("testdata", "kava-3-export.json"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// 2) migrate
|
||||
newGenDoc := Migrate(*oldGenDoc)
|
||||
tApp := app.NewTestApp()
|
||||
cdc := app.MakeCodec()
|
||||
var newAppState genutil.AppMap
|
||||
require.NoError(t,
|
||||
cdc.UnmarshalJSON(newGenDoc.AppState, &newAppState),
|
||||
)
|
||||
err = app.ModuleBasics.ValidateGenesis(newAppState)
|
||||
if err != nil {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NotPanics(t, func() {
|
||||
// this runs both InitGenesis for all modules (which panic on errors) and runs all invariants
|
||||
tApp.InitializeFromGenesisStatesWithTime(newGenDoc.GenesisTime, app.GenesisState(newAppState))
|
||||
})
|
||||
}
|
||||
|
69
migrate/v0_13/migrate.go
Normal file
69
migrate/v0_13/migrate.go
Normal file
@ -0,0 +1,69 @@
|
||||
package v0_13
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
v0_11cdp "github.com/kava-labs/kava/x/cdp/legacy/v0_11"
|
||||
v0_13cdp "github.com/kava-labs/kava/x/cdp/legacy/v0_13"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// MigrateCDP migrates from a v0.9 (or v0.10) cdp genesis state to a v0.11 cdp genesis state
|
||||
func MigrateCDP(oldGenState v0_11cdp.GenesisState) v0_13cdp.GenesisState {
|
||||
var newCDPs v0_13cdp.CDPs
|
||||
var newDeposits v0_13cdp.Deposits
|
||||
var newCollateralParams v0_13cdp.CollateralParams
|
||||
var newGenesisAccumulationTimes v0_13cdp.GenesisAccumulationTimes
|
||||
var previousAccumulationTime time.Time
|
||||
var totalPrincipals v0_13cdp.GenesisTotalPrincipals
|
||||
newStartingID := oldGenState.StartingCdpID
|
||||
|
||||
totalPrincipalMap := make(map[string]sdk.Int)
|
||||
for _, cp := range oldGenState.Params.CollateralParams {
|
||||
newCollateralParam := v0_13cdp.NewCollateralParam(cp.Denom, cp.Type, cp.LiquidationRatio, cp.DebtLimit, cp.StabilityFee, cp.AuctionSize, cp.LiquidationPenalty, cp.Prefix, cp.SpotMarketID, cp.LiquidationMarketID, sdk.MustNewDecFromStr("0.01"), sdk.NewInt(10), cp.ConversionFactor)
|
||||
newCollateralParams = append(newCollateralParams, newCollateralParam)
|
||||
newGenesisAccumulationTime := v0_13cdp.NewGenesisAccumulationTime(cp.Type, previousAccumulationTime, sdk.OneDec())
|
||||
newGenesisAccumulationTimes = append(newGenesisAccumulationTimes, newGenesisAccumulationTime)
|
||||
totalPrincipalMap[cp.Type] = sdk.ZeroInt()
|
||||
}
|
||||
|
||||
for _, cdp := range oldGenState.CDPs {
|
||||
newCDP := v0_13cdp.NewCDPWithFees(cdp.ID, cdp.Owner, cdp.Collateral, cdp.Type, cdp.Principal, cdp.AccumulatedFees, cdp.FeesUpdated, sdk.OneDec())
|
||||
if previousAccumulationTime.Before(cdp.FeesUpdated) {
|
||||
previousAccumulationTime = cdp.FeesUpdated
|
||||
}
|
||||
totalPrincipalMap[cdp.Type] = totalPrincipalMap[cdp.Type].Add(newCDP.GetTotalPrincipal().Amount)
|
||||
|
||||
newCDPs = append(newCDPs, newCDP)
|
||||
}
|
||||
|
||||
for _, dep := range oldGenState.Deposits {
|
||||
newDep := v0_13cdp.NewDeposit(dep.CdpID, dep.Depositor, dep.Amount)
|
||||
newDeposits = append(newDeposits, newDep)
|
||||
}
|
||||
|
||||
for ctype, tp := range totalPrincipalMap {
|
||||
totalPrincipal := v0_13cdp.NewGenesisTotalPrincipal(ctype, tp)
|
||||
totalPrincipals = append(totalPrincipals, totalPrincipal)
|
||||
}
|
||||
|
||||
oldDebtParam := oldGenState.Params.DebtParam
|
||||
|
||||
newDebtParam := v0_13cdp.NewDebtParam(oldDebtParam.Denom, oldDebtParam.ReferenceAsset, oldDebtParam.ConversionFactor, oldDebtParam.DebtFloor)
|
||||
|
||||
newGlobalDebtLimit := oldGenState.Params.GlobalDebtLimit
|
||||
|
||||
newParams := v0_13cdp.NewParams(newGlobalDebtLimit, newCollateralParams, newDebtParam, oldGenState.Params.SurplusAuctionThreshold, oldGenState.Params.SurplusAuctionLot, oldGenState.Params.DebtAuctionThreshold, oldGenState.Params.DebtAuctionLot, false)
|
||||
|
||||
return v0_13cdp.NewGenesisState(
|
||||
newParams,
|
||||
newCDPs,
|
||||
newDeposits,
|
||||
newStartingID,
|
||||
oldGenState.DebtDenom,
|
||||
oldGenState.GovDenom,
|
||||
newGenesisAccumulationTimes,
|
||||
totalPrincipals,
|
||||
)
|
||||
}
|
43
migrate/v0_13/migrate_test.go
Normal file
43
migrate/v0_13/migrate_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
package v0_13
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
v0_11cdp "github.com/kava-labs/kava/x/cdp/legacy/v0_11"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
config := sdk.GetConfig()
|
||||
app.SetBech32AddressPrefixes(config)
|
||||
app.SetBip44CoinType(config)
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestMigrateCdp(t *testing.T) {
|
||||
bz, err := ioutil.ReadFile(filepath.Join("testdata", "kava-4-cdp-state-block-500000.json"))
|
||||
require.NoError(t, err)
|
||||
var oldGenState v0_11cdp.GenesisState
|
||||
cdc := app.MakeCodec()
|
||||
require.NotPanics(t, func() {
|
||||
cdc.MustUnmarshalJSON(bz, &oldGenState)
|
||||
})
|
||||
|
||||
newGenState := MigrateCDP(oldGenState)
|
||||
err = newGenState.Validate()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(newGenState.Params.CollateralParams), len(newGenState.PreviousAccumulationTimes))
|
||||
|
||||
cdp1 := newGenState.CDPs[0]
|
||||
require.Equal(t, sdk.OneDec(), cdp1.InterestFactor)
|
||||
|
||||
}
|
29102
migrate/v0_13/testdata/kava-4-cdp-state-block-500000.json
vendored
Normal file
29102
migrate/v0_13/testdata/kava-4-cdp-state-block-500000.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -272,10 +272,11 @@ func (a CollateralAuction) String() string {
|
||||
End Time: %s
|
||||
Max End Time: %s
|
||||
Max Bid %s
|
||||
LotReturns %s`,
|
||||
LotReturns %s
|
||||
Corresponding Debt %s`,
|
||||
a.GetID(), a.Initiator, a.Lot,
|
||||
a.Bidder, a.Bid, a.GetEndTime().String(),
|
||||
a.MaxEndTime.String(), a.MaxBid, a.LotReturns,
|
||||
a.MaxEndTime.String(), a.MaxBid, a.LotReturns, a.CorrespondingDebt,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -14,12 +14,6 @@ import (
|
||||
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
|
||||
params := k.GetParams(ctx)
|
||||
|
||||
previousDistTime, found := k.GetPreviousSavingsDistribution(ctx)
|
||||
if !found {
|
||||
previousDistTime = ctx.BlockTime()
|
||||
k.SetPreviousSavingsDistribution(ctx, previousDistTime)
|
||||
}
|
||||
|
||||
for _, cp := range params.CollateralParams {
|
||||
ok := k.UpdatePricefeedStatus(ctx, cp.SpotMarketID)
|
||||
if !ok {
|
||||
@ -31,7 +25,12 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
|
||||
continue
|
||||
}
|
||||
|
||||
err := k.UpdateFeesForAllCdps(ctx, cp.Type)
|
||||
err := k.AccumulateInterest(ctx, cp.Type)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = k.SynchronizeInterestForRiskyCDPs(ctx, cp.CheckCollateralizationIndexCount, sdk.MaxSortableDec, cp.Type)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -46,16 +45,4 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
distTimeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistTime.Unix())
|
||||
if !distTimeElapsed.GTE(sdk.NewInt(int64(params.SavingsDistributionFrequency.Seconds()))) {
|
||||
return
|
||||
}
|
||||
|
||||
err = k.DistributeSavingsRate(ctx, params.DebtParam.Denom)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
k.SetPreviousSavingsDistribution(ctx, ctx.BlockTime())
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ func (suite *ModuleTestSuite) TestSeizeSingleCdpWithFees() {
|
||||
}
|
||||
|
||||
cdpMacc = sk.GetModuleAccount(suite.ctx, cdp.ModuleName)
|
||||
suite.Equal(i(1000000900), (cdpMacc.GetCoins().AmountOf("debt")))
|
||||
suite.Equal(i(1000000891), (cdpMacc.GetCoins().AmountOf("debt")))
|
||||
cdp, _ := suite.keeper.GetCDP(suite.ctx, "xrp-a", 1)
|
||||
|
||||
err = suite.keeper.SeizeCollateral(suite.ctx, cdp)
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
BaseDigitFactor = keeper.BaseDigitFactor
|
||||
AttributeKeyCdpID = types.AttributeKeyCdpID
|
||||
AttributeKeyDeposit = types.AttributeKeyDeposit
|
||||
AttributeKeyError = types.AttributeKeyError
|
||||
@ -38,12 +37,12 @@ const (
|
||||
RestOwner = types.RestOwner
|
||||
RestRatio = types.RestRatio
|
||||
RouterKey = types.RouterKey
|
||||
SavingsRateMacc = types.SavingsRateMacc
|
||||
StoreKey = types.StoreKey
|
||||
)
|
||||
|
||||
var (
|
||||
// function aliases
|
||||
CalculateInterestFactor = keeper.CalculateInterestFactor
|
||||
FilterCDPs = keeper.FilterCDPs
|
||||
FindIntersection = keeper.FindIntersection
|
||||
NewKeeper = keeper.NewKeeper
|
||||
@ -65,12 +64,16 @@ var (
|
||||
NewCollateralParam = types.NewCollateralParam
|
||||
NewDebtParam = types.NewDebtParam
|
||||
NewDeposit = types.NewDeposit
|
||||
NewGenesisAccumulationTime = types.NewGenesisAccumulationTime
|
||||
NewGenesisState = types.NewGenesisState
|
||||
NewGenesisTotalPrincipal = types.NewGenesisTotalPrincipal
|
||||
NewMsgCreateCDP = types.NewMsgCreateCDP
|
||||
NewMsgDeposit = types.NewMsgDeposit
|
||||
NewMsgDrawDebt = types.NewMsgDrawDebt
|
||||
NewMsgLiquidate = types.NewMsgLiquidate
|
||||
NewMsgRepayDebt = types.NewMsgRepayDebt
|
||||
NewMsgWithdraw = types.NewMsgWithdraw
|
||||
NewMultiCDPHooks = types.NewMultiCDPHooks
|
||||
NewParams = types.NewParams
|
||||
NewQueryCdpDeposits = types.NewQueryCdpDeposits
|
||||
NewQueryCdpParams = types.NewQueryCdpParams
|
||||
@ -105,13 +108,12 @@ var (
|
||||
DefaultDebtThreshold = types.DefaultDebtThreshold
|
||||
DefaultGlobalDebt = types.DefaultGlobalDebt
|
||||
DefaultGovDenom = types.DefaultGovDenom
|
||||
DefaultPreviousDistributionTime = types.DefaultPreviousDistributionTime
|
||||
DefaultSavingsDistributionFrequency = types.DefaultSavingsDistributionFrequency
|
||||
DefaultSavingsRateDistributed = types.DefaultSavingsRateDistributed
|
||||
DefaultStableDenom = types.DefaultStableDenom
|
||||
DefaultSurplusLot = types.DefaultSurplusLot
|
||||
DefaultSurplusThreshold = types.DefaultSurplusThreshold
|
||||
DepositKeyPrefix = types.DepositKeyPrefix
|
||||
ErrAccountNotFound = types.ErrAccountNotFound
|
||||
ErrBelowDebtFloor = types.ErrBelowDebtFloor
|
||||
ErrCdpAlreadyExists = types.ErrCdpAlreadyExists
|
||||
ErrCdpNotAvailable = types.ErrCdpNotAvailable
|
||||
@ -122,6 +124,7 @@ var (
|
||||
ErrDepositNotAvailable = types.ErrDepositNotAvailable
|
||||
ErrDepositNotFound = types.ErrDepositNotFound
|
||||
ErrExceedsDebtLimit = types.ErrExceedsDebtLimit
|
||||
ErrInsufficientBalance = types.ErrInsufficientBalance
|
||||
ErrInvalidCollateral = types.ErrInvalidCollateral
|
||||
ErrInvalidCollateralLength = types.ErrInvalidCollateralLength
|
||||
ErrInvalidCollateralRatio = types.ErrInvalidCollateralRatio
|
||||
@ -130,24 +133,23 @@ var (
|
||||
ErrInvalidPayment = types.ErrInvalidPayment
|
||||
ErrInvalidWithdrawAmount = types.ErrInvalidWithdrawAmount
|
||||
ErrLoadingAugmentedCDP = types.ErrLoadingAugmentedCDP
|
||||
ErrNotLiquidatable = types.ErrNotLiquidatable
|
||||
ErrPricefeedDown = types.ErrPricefeedDown
|
||||
GovDenomKey = types.GovDenomKey
|
||||
InterestFactorPrefix = types.InterestFactorPrefix
|
||||
KeyCircuitBreaker = types.KeyCircuitBreaker
|
||||
KeyCollateralParams = types.KeyCollateralParams
|
||||
KeyDebtLot = types.KeyDebtLot
|
||||
KeyDebtParam = types.KeyDebtParam
|
||||
KeyDebtThreshold = types.KeyDebtThreshold
|
||||
KeyDistributionFrequency = types.KeyDistributionFrequency
|
||||
KeyGlobalDebtLimit = types.KeyGlobalDebtLimit
|
||||
KeySavingsRateDistributed = types.KeySavingsRateDistributed
|
||||
KeySurplusLot = types.KeySurplusLot
|
||||
KeySurplusThreshold = types.KeySurplusThreshold
|
||||
MaxSortableDec = types.MaxSortableDec
|
||||
ModuleCdc = types.ModuleCdc
|
||||
PreviousDistributionTimeKey = types.PreviousDistributionTimeKey
|
||||
PreviousAccrualTimePrefix = types.PreviousAccrualTimePrefix
|
||||
PricefeedStatusKeyPrefix = types.PricefeedStatusKeyPrefix
|
||||
PrincipalKeyPrefix = types.PrincipalKeyPrefix
|
||||
SavingsRateDistributedKey = types.SavingsRateDistributedKey
|
||||
)
|
||||
|
||||
type (
|
||||
@ -157,6 +159,7 @@ type (
|
||||
AugmentedCDP = types.AugmentedCDP
|
||||
AugmentedCDPs = types.AugmentedCDPs
|
||||
CDP = types.CDP
|
||||
CDPHooks = types.CDPHooks
|
||||
CDPs = types.CDPs
|
||||
CollateralParam = types.CollateralParam
|
||||
CollateralParams = types.CollateralParams
|
||||
@ -164,12 +167,18 @@ type (
|
||||
DebtParams = types.DebtParams
|
||||
Deposit = types.Deposit
|
||||
Deposits = types.Deposits
|
||||
GenesisAccumulationTime = types.GenesisAccumulationTime
|
||||
GenesisAccumulationTimes = types.GenesisAccumulationTimes
|
||||
GenesisState = types.GenesisState
|
||||
GenesisTotalPrincipal = types.GenesisTotalPrincipal
|
||||
GenesisTotalPrincipals = types.GenesisTotalPrincipals
|
||||
MsgCreateCDP = types.MsgCreateCDP
|
||||
MsgDeposit = types.MsgDeposit
|
||||
MsgDrawDebt = types.MsgDrawDebt
|
||||
MsgLiquidate = types.MsgLiquidate
|
||||
MsgRepayDebt = types.MsgRepayDebt
|
||||
MsgWithdraw = types.MsgWithdraw
|
||||
MultiCDPHooks = types.MultiCDPHooks
|
||||
Params = types.Params
|
||||
PricefeedKeeper = types.PricefeedKeeper
|
||||
QueryCdpDeposits = types.QueryCdpDeposits
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
@ -41,8 +40,6 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
QueryCdpDepositsCmd(queryRoute, cdc),
|
||||
QueryParamsCmd(queryRoute, cdc),
|
||||
QueryGetAccounts(queryRoute, cdc),
|
||||
QueryGetSavingsRateDistributed(queryRoute, cdc),
|
||||
QueryGetSavingsRateDistTime(queryRoute, cdc),
|
||||
)...)
|
||||
|
||||
return cdpQueryCmd
|
||||
@ -284,56 +281,3 @@ func QueryGetAccounts(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// QueryGetSavingsRateDistributed queries the total amount of savings rate distributed in USDX
|
||||
func QueryGetSavingsRateDistributed(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "savings-rate-dist",
|
||||
Short: "get total amount of savings rate distributed in USDX",
|
||||
Long: "get total amount of savings rate distributed",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
// Query
|
||||
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetSavingsRateDistributed), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
|
||||
// Decode and print results
|
||||
var out sdk.Int
|
||||
if err := cdc.UnmarshalJSON(res, &out); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal sdk.Int: %w", err)
|
||||
}
|
||||
return cliCtx.PrintOutput(out)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// QueryGetSavingsRateDistributed queries the total amount of savings rate distributed in USDX
|
||||
func QueryGetSavingsRateDistTime(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "savings-rate-dist-time",
|
||||
Short: "get the previous savings rate distribution time",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
// Query
|
||||
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetPreviousSavingsDistributionTime), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
|
||||
// Decode and print results
|
||||
var out time.Time
|
||||
if err := cdc.UnmarshalJSON(res, &out); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal time.Time: %w", err)
|
||||
}
|
||||
return cliCtx.PrintOutput(out)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
GetCmdWithdraw(cdc),
|
||||
GetCmdDraw(cdc),
|
||||
GetCmdRepay(cdc),
|
||||
GetCmdLiquidate(cdc),
|
||||
)...)
|
||||
|
||||
return cdpTxCmd
|
||||
@ -202,3 +203,34 @@ $ %s tx %s repay atom-a 1000usdx --from myKeyName
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetCmdLiquidate cli command for liquidating a cdp.
|
||||
func GetCmdLiquidate(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "liquidate [cdp-owner-address] [collateral-type]",
|
||||
Short: "liquidate a cdp",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Liquidate a cdp if it is below the required liquidation ratio
|
||||
|
||||
Example:
|
||||
$ %s tx %s liquidate kava1y70y90wzmnf00e63efk2lycgqwepthdmyzsfzm btcb-a --from myKeyName
|
||||
`, version.ClientName, types.ModuleName)),
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
|
||||
|
||||
addr, err := sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := types.NewMsgLiquidate(cliCtx.GetFromAddress(), addr, args[1])
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,6 @@ import (
|
||||
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
r.HandleFunc("/cdp/accounts", getAccountsHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/cdp/parameters", getParamsHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/cdp/savingsRateDist", getSavingsRateDistributedHandler(cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/cdp/savingsRateDistTime", getSavingsRateDistTimeHandler(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/cdp/cdps/cdp/{%s}/{%s}", types.RestOwner, types.RestCollateralType), queryCdpHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/cdp/cdps"), queryCdpsHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/cdp/cdps/collateralType/{%s}", types.RestCollateralType), queryCdpsByCollateralTypeHandlerFn(cliCtx)).Methods("GET") // legacy
|
||||
@ -199,42 +197,6 @@ func getAccountsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func getSavingsRateDistributedHandler(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/cdp/%s", types.QueryGetSavingsRateDistributed), nil)
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func getSavingsRateDistTimeHandler(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/cdp/%s", types.QueryGetPreviousSavingsDistributionTime), nil)
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryCdpsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
||||
|
@ -65,3 +65,10 @@ type PostRepayReq struct {
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
Payment sdk.Coin `json:"payment" yaml:"payment"`
|
||||
}
|
||||
|
||||
// PostLiquidateReq defines the properties of cdp liquidation request's body.
|
||||
type PostLiquidateReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
r.HandleFunc("/cdp/{owner}/{collateralType}/withdraw", postWithdrawHandlerFn(cliCtx)).Methods("POST")
|
||||
r.HandleFunc("/cdp/{owner}/{collateralType}/draw", postDrawHandlerFn(cliCtx)).Methods("POST")
|
||||
r.HandleFunc("/cdp/{owner}/{collateralType}/repay", postRepayHandlerFn(cliCtx)).Methods("POST")
|
||||
r.HandleFunc("/cdp/{owner}/collateralType}/liquidate", postLiquidateHandlerFn(cliCtx)).Methods("POST")
|
||||
}
|
||||
|
||||
func postCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
@ -187,3 +188,35 @@ func postRepayHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
utils.WriteGenerateStdTxResponse(w, cliCtx, requestBody.BaseReq, []sdk.Msg{msg})
|
||||
}
|
||||
}
|
||||
|
||||
func postLiquidateHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var requestBody PostLiquidateReq
|
||||
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) {
|
||||
return
|
||||
}
|
||||
|
||||
requestBody.BaseReq = requestBody.BaseReq.Sanitize()
|
||||
if !requestBody.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
fromAddr, err := sdk.AccAddressFromBech32(requestBody.BaseReq.From)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.NewMsgLiquidate(
|
||||
fromAddr,
|
||||
requestBody.Owner,
|
||||
requestBody.CollateralType,
|
||||
)
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteGenerateStdTxResponse(w, cliCtx, requestBody.BaseReq, []sdk.Msg{msg})
|
||||
}
|
||||
}
|
||||
|
@ -24,10 +24,6 @@ func InitGenesis(ctx sdk.Context, k Keeper, pk types.PricefeedKeeper, sk types.S
|
||||
if liqModuleAcc == nil {
|
||||
panic(fmt.Sprintf("%s module account has not been set", LiquidatorMacc))
|
||||
}
|
||||
savingsRateMacc := sk.GetModuleAccount(ctx, SavingsRateMacc)
|
||||
if savingsRateMacc == nil {
|
||||
panic(fmt.Sprintf("%s module account has not been set", SavingsRateMacc))
|
||||
}
|
||||
|
||||
// validate denoms - check that any collaterals in the params are in the pricefeed,
|
||||
// pricefeed MUST call InitGenesis before cdp
|
||||
@ -57,11 +53,16 @@ func InitGenesis(ctx sdk.Context, k Keeper, pk types.PricefeedKeeper, sk types.S
|
||||
|
||||
k.SetParams(ctx, gs.Params)
|
||||
|
||||
// set the per second fee rate for each collateral type
|
||||
for _, cp := range gs.Params.CollateralParams {
|
||||
k.SetTotalPrincipal(ctx, cp.Type, gs.Params.DebtParam.Denom, sdk.ZeroInt())
|
||||
for _, gat := range gs.PreviousAccumulationTimes {
|
||||
k.SetInterestFactor(ctx, gat.CollateralType, gat.InterestFactor)
|
||||
if !gat.PreviousAccumulationTime.IsZero() {
|
||||
k.SetPreviousAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
||||
}
|
||||
}
|
||||
|
||||
for _, gtp := range gs.TotalPrincipals {
|
||||
k.SetTotalPrincipal(ctx, gtp.CollateralType, types.DefaultStableDenom, gtp.TotalPrincipal)
|
||||
}
|
||||
// add cdps
|
||||
for _, cdp := range gs.CDPs {
|
||||
if cdp.ID == gs.StartingCdpID {
|
||||
@ -74,22 +75,16 @@ func InitGenesis(ctx sdk.Context, k Keeper, pk types.PricefeedKeeper, sk types.S
|
||||
k.IndexCdpByOwner(ctx, cdp)
|
||||
ratio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
k.IndexCdpByCollateralRatio(ctx, cdp.Type, cdp.ID, ratio)
|
||||
k.IncrementTotalPrincipal(ctx, cdp.Type, cdp.GetTotalPrincipal())
|
||||
}
|
||||
|
||||
k.SetNextCdpID(ctx, gs.StartingCdpID)
|
||||
k.SetDebtDenom(ctx, gs.DebtDenom)
|
||||
k.SetGovDenom(ctx, gs.GovDenom)
|
||||
// only set the previous block time if it's different than default
|
||||
if !gs.PreviousDistributionTime.Equal(types.DefaultPreviousDistributionTime) {
|
||||
k.SetPreviousSavingsDistribution(ctx, gs.PreviousDistributionTime)
|
||||
}
|
||||
|
||||
for _, d := range gs.Deposits {
|
||||
k.SetDeposit(ctx, d)
|
||||
}
|
||||
|
||||
k.SetSavingsRateDistributed(ctx, gs.SavingsRateDistributed)
|
||||
}
|
||||
|
||||
// ExportGenesis export genesis state for cdp module
|
||||
@ -110,12 +105,22 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
|
||||
cdpID := k.GetNextCdpID(ctx)
|
||||
debtDenom := k.GetDebtDenom(ctx)
|
||||
govDenom := k.GetGovDenom(ctx)
|
||||
savingsRateDist := k.GetSavingsRateDistributed(ctx)
|
||||
|
||||
previousDistributionTime, found := k.GetPreviousSavingsDistribution(ctx)
|
||||
var previousAccumTimes types.GenesisAccumulationTimes
|
||||
var totalPrincipals types.GenesisTotalPrincipals
|
||||
|
||||
for _, cp := range params.CollateralParams {
|
||||
interestFactor, found := k.GetInterestFactor(ctx, cp.Type)
|
||||
if !found {
|
||||
previousDistributionTime = DefaultPreviousDistributionTime
|
||||
interestFactor = sdk.OneDec()
|
||||
}
|
||||
previousAccumTime, _ := k.GetPreviousAccrualTime(ctx, cp.Type)
|
||||
previousAccumTimes = append(previousAccumTimes, types.NewGenesisAccumulationTime(cp.Type, previousAccumTime, interestFactor))
|
||||
|
||||
tp := k.GetTotalPrincipal(ctx, cp.Type, types.DefaultStableDenom)
|
||||
genTotalPrincipal := types.NewGenesisTotalPrincipal(cp.Type, tp)
|
||||
totalPrincipals = append(totalPrincipals, genTotalPrincipal)
|
||||
}
|
||||
|
||||
return NewGenesisState(params, cdps, deposits, cdpID, debtDenom, govDenom, previousDistributionTime, savingsRateDist)
|
||||
return NewGenesisState(params, cdps, deposits, cdpID, debtDenom, govDenom, previousAccumTimes, totalPrincipals)
|
||||
}
|
||||
|
@ -28,8 +28,9 @@ func (suite *GenesisTestSuite) TestInvalidGenState() {
|
||||
startingID uint64
|
||||
debtDenom string
|
||||
govDenom string
|
||||
prevDistTime time.Time
|
||||
savingsRateDist sdk.Int
|
||||
genAccumTimes cdp.GenesisAccumulationTimes
|
||||
genTotalPrincipals cdp.GenesisTotalPrincipals
|
||||
}
|
||||
type errArgs struct {
|
||||
expectPass bool
|
||||
@ -53,8 +54,9 @@ func (suite *GenesisTestSuite) TestInvalidGenState() {
|
||||
deposits: cdp.Deposits{},
|
||||
debtDenom: "",
|
||||
govDenom: cdp.DefaultGovDenom,
|
||||
prevDistTime: cdp.DefaultPreviousDistributionTime,
|
||||
savingsRateDist: cdp.DefaultSavingsRateDistributed,
|
||||
genAccumTimes: cdp.DefaultGenesisState().PreviousAccumulationTimes,
|
||||
genTotalPrincipals: cdp.DefaultGenesisState().TotalPrincipals,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
@ -69,8 +71,9 @@ func (suite *GenesisTestSuite) TestInvalidGenState() {
|
||||
deposits: cdp.Deposits{},
|
||||
debtDenom: cdp.DefaultDebtDenom,
|
||||
govDenom: "",
|
||||
prevDistTime: cdp.DefaultPreviousDistributionTime,
|
||||
savingsRateDist: cdp.DefaultSavingsRateDistributed,
|
||||
genAccumTimes: cdp.DefaultGenesisState().PreviousAccumulationTimes,
|
||||
genTotalPrincipals: cdp.DefaultGenesisState().TotalPrincipals,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
@ -78,48 +81,49 @@ func (suite *GenesisTestSuite) TestInvalidGenState() {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty distribution time",
|
||||
name: "interest factor below one",
|
||||
args: args{
|
||||
params: cdp.DefaultParams(),
|
||||
cdps: cdp.CDPs{},
|
||||
deposits: cdp.Deposits{},
|
||||
debtDenom: cdp.DefaultDebtDenom,
|
||||
govDenom: cdp.DefaultGovDenom,
|
||||
prevDistTime: time.Time{},
|
||||
savingsRateDist: cdp.DefaultSavingsRateDistributed,
|
||||
savingsRateDist: sdk.NewInt(100),
|
||||
genAccumTimes: cdp.GenesisAccumulationTimes{cdp.NewGenesisAccumulationTime("bnb-a", time.Time{}, sdk.OneDec().Sub(sdk.SmallestDec()))},
|
||||
genTotalPrincipals: cdp.DefaultGenesisState().TotalPrincipals,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "previous distribution time not set",
|
||||
contains: "interest factor should be ≥ 1.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative savings rate distributed",
|
||||
name: "negative total principal",
|
||||
args: args{
|
||||
params: cdp.DefaultParams(),
|
||||
cdps: cdp.CDPs{},
|
||||
deposits: cdp.Deposits{},
|
||||
debtDenom: cdp.DefaultDebtDenom,
|
||||
govDenom: cdp.DefaultGovDenom,
|
||||
prevDistTime: cdp.DefaultPreviousDistributionTime,
|
||||
savingsRateDist: sdk.NewInt(-100),
|
||||
savingsRateDist: sdk.NewInt(100),
|
||||
genAccumTimes: cdp.DefaultGenesisState().PreviousAccumulationTimes,
|
||||
genTotalPrincipals: cdp.GenesisTotalPrincipals{cdp.NewGenesisTotalPrincipal("bnb-a", sdk.NewInt(-1))},
|
||||
},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "savings rate distributed should not be negative",
|
||||
contains: "total principal should be positive",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
gs := cdp.NewGenesisState(tc.args.params, tc.args.cdps, tc.args.deposits, tc.args.startingID,
|
||||
tc.args.debtDenom, tc.args.govDenom, tc.args.prevDistTime, tc.args.savingsRateDist)
|
||||
tc.args.debtDenom, tc.args.govDenom, tc.args.genAccumTimes, tc.args.genTotalPrincipals)
|
||||
err := gs.Validate()
|
||||
if tc.errArgs.expectPass {
|
||||
suite.Require().NoError(err)
|
||||
} else {
|
||||
suite.Require().Error(err)
|
||||
suite.T().Log(err)
|
||||
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||
}
|
||||
})
|
||||
|
@ -20,6 +20,8 @@ func NewHandler(k Keeper) sdk.Handler {
|
||||
return handleMsgDrawDebt(ctx, k, msg)
|
||||
case MsgRepayDebt:
|
||||
return handleMsgRepayDebt(ctx, k, msg)
|
||||
case MsgLiquidate:
|
||||
return handleMsgLiquidate(ctx, k, msg)
|
||||
default:
|
||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
|
||||
}
|
||||
@ -110,3 +112,19 @@ func handleMsgRepayDebt(ctx sdk.Context, k Keeper, msg MsgRepayDebt) (*sdk.Resul
|
||||
)
|
||||
return &sdk.Result{Events: ctx.EventManager().Events()}, nil
|
||||
}
|
||||
|
||||
func handleMsgLiquidate(ctx sdk.Context, k Keeper, msg MsgLiquidate) (*sdk.Result, error) {
|
||||
err := k.AttemptKeeperLiquidation(ctx, msg.Keeper, msg.Borrower, msg.CollateralType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
sdk.EventTypeMessage,
|
||||
sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory),
|
||||
sdk.NewAttribute(sdk.AttributeKeySender, msg.Keeper.String()),
|
||||
),
|
||||
)
|
||||
return &sdk.Result{Events: ctx.EventManager().Events()}, nil
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
"github.com/kava-labs/kava/x/cdp/types"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
)
|
||||
|
||||
@ -45,7 +46,6 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
|
||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
|
||||
CollateralParams: cdp.CollateralParams{
|
||||
{
|
||||
Denom: asset,
|
||||
@ -59,6 +59,8 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
|
||||
ConversionFactor: i(6),
|
||||
SpotMarketID: asset + ":usd",
|
||||
LiquidationMarketID: asset + ":usd",
|
||||
KeeperRewardPercentage: d("0.01"),
|
||||
CheckCollateralizationIndexCount: i(10),
|
||||
},
|
||||
},
|
||||
DebtParam: cdp.DebtParam{
|
||||
@ -66,14 +68,18 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: i(6),
|
||||
DebtFloor: i(10000000),
|
||||
SavingsRate: d("0.95"),
|
||||
},
|
||||
},
|
||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||
DebtDenom: cdp.DefaultDebtDenom,
|
||||
GovDenom: cdp.DefaultGovDenom,
|
||||
CDPs: cdp.CDPs{},
|
||||
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
|
||||
PreviousAccumulationTimes: cdp.GenesisAccumulationTimes{
|
||||
cdp.NewGenesisAccumulationTime(asset+"-a", time.Time{}, sdk.OneDec()),
|
||||
},
|
||||
TotalPrincipals: cdp.GenesisTotalPrincipals{
|
||||
cdp.NewGenesisTotalPrincipal(asset+"-a", sdk.ZeroInt()),
|
||||
},
|
||||
}
|
||||
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
|
||||
}
|
||||
@ -111,7 +117,6 @@ func NewCDPGenStateMulti() app.GenesisState {
|
||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
|
||||
CollateralParams: cdp.CollateralParams{
|
||||
{
|
||||
Denom: "xrp",
|
||||
@ -124,6 +129,8 @@ func NewCDPGenStateMulti() app.GenesisState {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "xrp:usd",
|
||||
LiquidationMarketID: "xrp:usd",
|
||||
KeeperRewardPercentage: d("0.01"),
|
||||
CheckCollateralizationIndexCount: i(10),
|
||||
ConversionFactor: i(6),
|
||||
},
|
||||
{
|
||||
@ -137,6 +144,8 @@ func NewCDPGenStateMulti() app.GenesisState {
|
||||
Prefix: 0x21,
|
||||
SpotMarketID: "btc:usd",
|
||||
LiquidationMarketID: "btc:usd",
|
||||
KeeperRewardPercentage: d("0.01"),
|
||||
CheckCollateralizationIndexCount: i(10),
|
||||
ConversionFactor: i(8),
|
||||
},
|
||||
},
|
||||
@ -145,24 +154,30 @@ func NewCDPGenStateMulti() app.GenesisState {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: i(6),
|
||||
DebtFloor: i(10000000),
|
||||
SavingsRate: d("0.95"),
|
||||
},
|
||||
},
|
||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||
DebtDenom: cdp.DefaultDebtDenom,
|
||||
GovDenom: cdp.DefaultGovDenom,
|
||||
CDPs: cdp.CDPs{},
|
||||
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
|
||||
PreviousAccumulationTimes: cdp.GenesisAccumulationTimes{
|
||||
cdp.NewGenesisAccumulationTime("btc-a", time.Time{}, sdk.OneDec()),
|
||||
cdp.NewGenesisAccumulationTime("xrp-a", time.Time{}, sdk.OneDec()),
|
||||
},
|
||||
TotalPrincipals: types.GenesisTotalPrincipals{
|
||||
cdp.NewGenesisTotalPrincipal("btc-a", sdk.ZeroInt()),
|
||||
cdp.NewGenesisTotalPrincipal("xrp-a", sdk.ZeroInt()),
|
||||
},
|
||||
}
|
||||
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
|
||||
}
|
||||
|
||||
func cdps() (cdps cdp.CDPs) {
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(3)
|
||||
c1 := cdp.NewCDP(uint64(1), addrs[0], sdk.NewCoin("xrp", sdk.NewInt(100000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(8000000)), tmtime.Canonical(time.Now()))
|
||||
c2 := cdp.NewCDP(uint64(2), addrs[1], sdk.NewCoin("xrp", sdk.NewInt(100000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()))
|
||||
c3 := cdp.NewCDP(uint64(3), addrs[1], sdk.NewCoin("btc", sdk.NewInt(1000000000)), "btc-a", sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()))
|
||||
c4 := cdp.NewCDP(uint64(4), addrs[2], sdk.NewCoin("xrp", sdk.NewInt(1000000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(50000000)), tmtime.Canonical(time.Now()))
|
||||
c1 := cdp.NewCDP(uint64(1), addrs[0], sdk.NewCoin("xrp", sdk.NewInt(100000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(8000000)), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
c2 := cdp.NewCDP(uint64(2), addrs[1], sdk.NewCoin("xrp", sdk.NewInt(100000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
c3 := cdp.NewCDP(uint64(3), addrs[1], sdk.NewCoin("btc", sdk.NewInt(1000000000)), "btc-a", sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
c4 := cdp.NewCDP(uint64(4), addrs[2], sdk.NewCoin("xrp", sdk.NewInt(1000000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(50000000)), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
cdps = append(cdps, c1, c2, c3, c4)
|
||||
return
|
||||
}
|
||||
|
@ -11,9 +11,6 @@ import (
|
||||
"github.com/kava-labs/kava/x/cdp/types"
|
||||
)
|
||||
|
||||
// BaseDigitFactor is 10**18, used during coin calculations
|
||||
const BaseDigitFactor = 1000000000000000000
|
||||
|
||||
// AddCdp adds a cdp for a specific owner and collateral type
|
||||
func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coin, principal sdk.Coin, collateralType string) error {
|
||||
// validation
|
||||
@ -21,6 +18,10 @@ func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coi
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = k.ValidateBalance(ctx, collateral, owner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, found := k.GetCdpByOwnerAndCollateralType(ctx, owner, collateralType)
|
||||
if found {
|
||||
return sdkerrors.Wrapf(types.ErrCdpAlreadyExists, "owner %s, denom %s", owner, collateral.Denom)
|
||||
@ -41,7 +42,13 @@ func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coi
|
||||
|
||||
// send coins from the owners account to the cdp module
|
||||
id := k.GetNextCdpID(ctx)
|
||||
cdp := types.NewCDP(id, owner, collateral, collateralType, principal, ctx.BlockHeader().Time)
|
||||
interestFactor, found := k.GetInterestFactor(ctx, collateralType)
|
||||
if !found {
|
||||
interestFactor = sdk.OneDec()
|
||||
k.SetInterestFactor(ctx, collateralType, interestFactor)
|
||||
|
||||
}
|
||||
cdp := types.NewCDP(id, owner, collateral, collateralType, principal, ctx.BlockHeader().Time, interestFactor)
|
||||
deposit := types.NewDeposit(cdp.ID, owner, collateral)
|
||||
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, sdk.NewCoins(collateral))
|
||||
if err != nil {
|
||||
@ -77,6 +84,8 @@ func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coi
|
||||
k.SetDeposit(ctx, deposit)
|
||||
k.SetNextCdpID(ctx, id+1)
|
||||
|
||||
k.hooks.AfterCDPCreated(ctx, cdp)
|
||||
|
||||
// emit events for cdp creation, deposit, and draw
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
@ -102,6 +111,31 @@ func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coi
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateCdpAndCollateralRatioIndex updates the state of an existing cdp in the store by replacing the old index values and updating the store to the latest cdp object values
|
||||
func (k Keeper) UpdateCdpAndCollateralRatioIndex(ctx sdk.Context, cdp types.CDP, ratio sdk.Dec) error {
|
||||
err := k.removeOldCollateralRatioIndex(ctx, cdp.Type, cdp.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = k.SetCDP(ctx, cdp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.IndexCdpByCollateralRatio(ctx, cdp.Type, cdp.ID, ratio)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteCdpAndCollateralRatioIndex deletes an existing cdp in the store by removing the old index value and deleting the cdp object from the store
|
||||
func (k Keeper) DeleteCdpAndCollateralRatioIndex(ctx sdk.Context, cdp types.CDP) error {
|
||||
err := k.removeOldCollateralRatioIndex(ctx, cdp.Type, cdp.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return k.DeleteCDP(ctx, cdp)
|
||||
}
|
||||
|
||||
// SetCdpAndCollateralRatioIndex sets the cdp and collateral ratio index in the store
|
||||
func (k Keeper) SetCdpAndCollateralRatioIndex(ctx sdk.Context, cdp types.CDP, ratio sdk.Dec) error {
|
||||
err := k.SetCDP(ctx, cdp)
|
||||
@ -112,6 +146,16 @@ func (k Keeper) SetCdpAndCollateralRatioIndex(ctx sdk.Context, cdp types.CDP, ra
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Keeper) removeOldCollateralRatioIndex(ctx sdk.Context, ctype string, id uint64) error {
|
||||
storedCDP, found := k.GetCDP(ctx, ctype, id)
|
||||
if !found {
|
||||
return sdkerrors.Wrapf(types.ErrCdpNotFound, "%d", storedCDP.ID)
|
||||
}
|
||||
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, storedCDP.Collateral, storedCDP.Type, storedCDP.GetTotalPrincipal())
|
||||
k.RemoveCdpCollateralRatioIndex(ctx, storedCDP.Type, storedCDP.ID, oldCollateralToDebtRatio)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MintDebtCoins mints debt coins in the cdp module account
|
||||
func (k Keeper) MintDebtCoins(ctx sdk.Context, moduleAccount string, denom string, principalCoins sdk.Coin) error {
|
||||
debtCoins := sdk.NewCoins(sdk.NewCoin(denom, principalCoins.Amount))
|
||||
@ -120,7 +164,10 @@ func (k Keeper) MintDebtCoins(ctx sdk.Context, moduleAccount string, denom strin
|
||||
|
||||
// BurnDebtCoins burns debt coins from the cdp module account
|
||||
func (k Keeper) BurnDebtCoins(ctx sdk.Context, moduleAccount string, denom string, paymentCoins sdk.Coin) error {
|
||||
debtCoins := sdk.NewCoins(sdk.NewCoin(denom, paymentCoins.Amount))
|
||||
macc := k.supplyKeeper.GetModuleAccount(ctx, moduleAccount)
|
||||
maxBurnableAmount := macc.GetCoins().AmountOf(denom)
|
||||
// check that the requested burn is not greater than the mod account balance
|
||||
debtCoins := sdk.NewCoins(sdk.NewCoin(denom, sdk.MinInt(paymentCoins.Amount, maxBurnableAmount)))
|
||||
return k.supplyKeeper.BurnCoins(ctx, moduleAccount, debtCoins)
|
||||
}
|
||||
|
||||
@ -419,6 +466,20 @@ func (k Keeper) ValidateCollateralizationRatio(ctx sdk.Context, collateral sdk.C
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBalance validates that the input account has sufficient spendable funds
|
||||
func (k Keeper) ValidateBalance(ctx sdk.Context, amount sdk.Coin, sender sdk.AccAddress) error {
|
||||
acc := k.accountKeeper.GetAccount(ctx, sender)
|
||||
if acc == nil {
|
||||
return sdkerrors.Wrapf(types.ErrAccountNotFound, "address: %s", sender)
|
||||
}
|
||||
spendableBalance := acc.SpendableCoins(ctx.BlockTime()).AmountOf(amount.Denom)
|
||||
if spendableBalance.LT(amount.Amount) {
|
||||
return sdkerrors.Wrapf(types.ErrInsufficientBalance, "%s < %s", sdk.NewCoin(amount.Denom, spendableBalance), amount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalculateCollateralToDebtRatio returns the collateral to debt ratio of the input collateral and debt amounts
|
||||
func (k Keeper) CalculateCollateralToDebtRatio(ctx sdk.Context, collateral sdk.Coin, collateralType string, debt sdk.Coin) sdk.Dec {
|
||||
debtTotal := k.convertDebtToBaseUnits(ctx, debt)
|
||||
@ -433,6 +494,9 @@ func (k Keeper) CalculateCollateralToDebtRatio(ctx sdk.Context, collateral sdk.C
|
||||
|
||||
// LoadAugmentedCDP creates a new augmented CDP from an existing CDP
|
||||
func (k Keeper) LoadAugmentedCDP(ctx sdk.Context, cdp types.CDP) types.AugmentedCDP {
|
||||
// sync the latest interest of the cdp
|
||||
interestAccumulated := k.CalculateNewInterest(ctx, cdp)
|
||||
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(interestAccumulated)
|
||||
// calculate collateralization ratio
|
||||
collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral, cdp.Type, cdp.Principal, cdp.AccumulatedFees, liquidation)
|
||||
if err != nil {
|
||||
|
@ -131,7 +131,7 @@ func (suite *CdpTestSuite) TestGetNextCdpID() {
|
||||
|
||||
func (suite *CdpTestSuite) TestGetSetCdp() {
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), "xrp-a", c("usdx", 1), tmtime.Canonical(time.Now()))
|
||||
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), "xrp-a", c("usdx", 1), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
err := suite.keeper.SetCDP(suite.ctx, cdp)
|
||||
suite.NoError(err)
|
||||
|
||||
@ -147,7 +147,7 @@ func (suite *CdpTestSuite) TestGetSetCdp() {
|
||||
|
||||
func (suite *CdpTestSuite) TestGetSetCdpId() {
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), "xrp-a", c("usdx", 1), tmtime.Canonical(time.Now()))
|
||||
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), "xrp-a", c("usdx", 1), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
err := suite.keeper.SetCDP(suite.ctx, cdp)
|
||||
suite.NoError(err)
|
||||
suite.keeper.IndexCdpByOwner(suite.ctx, cdp)
|
||||
@ -162,7 +162,7 @@ func (suite *CdpTestSuite) TestGetSetCdpId() {
|
||||
|
||||
func (suite *CdpTestSuite) TestGetSetCdpByOwnerAndCollateralType() {
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), "xrp-a", c("usdx", 1), tmtime.Canonical(time.Now()))
|
||||
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), "xrp-a", c("usdx", 1), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
err := suite.keeper.SetCDP(suite.ctx, cdp)
|
||||
suite.NoError(err)
|
||||
suite.keeper.IndexCdpByOwner(suite.ctx, cdp)
|
||||
@ -178,17 +178,17 @@ func (suite *CdpTestSuite) TestGetSetCdpByOwnerAndCollateralType() {
|
||||
|
||||
func (suite *CdpTestSuite) TestCalculateCollateralToDebtRatio() {
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 3), "xrp-a", c("usdx", 1), tmtime.Canonical(time.Now()))
|
||||
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 3), "xrp-a", c("usdx", 1), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Type, cdp.Principal)
|
||||
suite.Equal(sdk.MustNewDecFromStr("3.0"), cr)
|
||||
cdp = types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), "xrp-a", c("usdx", 2), tmtime.Canonical(time.Now()))
|
||||
cdp = types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), "xrp-a", c("usdx", 2), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
cr = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Type, cdp.Principal)
|
||||
suite.Equal(sdk.MustNewDecFromStr("0.5"), cr)
|
||||
}
|
||||
|
||||
func (suite *CdpTestSuite) TestSetCdpByCollateralRatio() {
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 3), "xrp-a", c("usdx", 1), tmtime.Canonical(time.Now()))
|
||||
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 3), "xrp-a", c("usdx", 1), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Type, cdp.Principal)
|
||||
suite.NotPanics(func() { suite.keeper.IndexCdpByCollateralRatio(suite.ctx, cdp.Type, cdp.ID, cr) })
|
||||
}
|
||||
|
@ -21,6 +21,12 @@ func (k Keeper) DepositCollateral(ctx sdk.Context, owner, depositor sdk.AccAddre
|
||||
if !found {
|
||||
return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, collateral %s", owner, collateralType)
|
||||
}
|
||||
err = k.ValidateBalance(ctx, collateral, depositor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.hooks.BeforeCDPModified(ctx, cdp)
|
||||
cdp = k.SynchronizeInterest(ctx, cdp)
|
||||
|
||||
deposit, found := k.GetDeposit(ctx, cdp.ID, depositor)
|
||||
if found {
|
||||
@ -32,6 +38,12 @@ func (k Keeper) DepositCollateral(ctx sdk.Context, owner, depositor sdk.AccAddre
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k.SetDeposit(ctx, deposit)
|
||||
|
||||
cdp.Collateral = cdp.Collateral.Add(collateral)
|
||||
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeCdpDeposit,
|
||||
@ -40,14 +52,7 @@ func (k Keeper) DepositCollateral(ctx sdk.Context, owner, depositor sdk.AccAddre
|
||||
),
|
||||
)
|
||||
|
||||
k.SetDeposit(ctx, deposit)
|
||||
|
||||
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Type, cdp.ID, oldCollateralToDebtRatio)
|
||||
|
||||
cdp.Collateral = cdp.Collateral.Add(collateral)
|
||||
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
return k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
||||
return k.UpdateCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
||||
}
|
||||
|
||||
// WithdrawCollateral removes collateral from a cdp if it does not put the cdp below the liquidation ratio
|
||||
@ -67,6 +72,8 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner, depositor sdk.AccAddr
|
||||
if collateral.Amount.GT(deposit.Amount.Amount) {
|
||||
return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "collateral %s, deposit %s", collateral, deposit.Amount)
|
||||
}
|
||||
k.hooks.BeforeCDPModified(ctx, cdp)
|
||||
cdp = k.SynchronizeInterest(ctx, cdp)
|
||||
|
||||
collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral.Sub(collateral), cdp.Type, cdp.Principal, cdp.AccumulatedFees, spot)
|
||||
if err != nil {
|
||||
@ -76,24 +83,15 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner, depositor sdk.AccAddr
|
||||
if collateralizationRatio.LT(liquidationRatio) {
|
||||
return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "collateral %s, collateral ratio %s, liquidation ration %s", collateral.Denom, collateralizationRatio, liquidationRatio)
|
||||
}
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeCdpWithdrawal,
|
||||
sdk.NewAttribute(sdk.AttributeKeyAmount, collateral.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)),
|
||||
),
|
||||
)
|
||||
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, depositor, sdk.NewCoins(collateral))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Type, cdp.ID, oldCollateralToDebtRatio)
|
||||
|
||||
cdp.Collateral = cdp.Collateral.Sub(collateral)
|
||||
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
err = k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
||||
err = k.UpdateCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -105,6 +103,15 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner, depositor sdk.AccAddr
|
||||
} else {
|
||||
k.SetDeposit(ctx, deposit)
|
||||
}
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeCdpWithdrawal,
|
||||
sdk.NewAttribute(sdk.AttributeKeyAmount, collateral.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)),
|
||||
),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,8 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateralTy
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.hooks.BeforeCDPModified(ctx, cdp)
|
||||
cdp = k.SynchronizeInterest(ctx, cdp)
|
||||
|
||||
err = k.ValidateCollateralizationRatio(ctx, cdp.Collateral, cdp.Type, cdp.Principal.Add(principal), cdp.AccumulatedFees)
|
||||
if err != nil {
|
||||
@ -56,10 +58,6 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateralTy
|
||||
),
|
||||
)
|
||||
|
||||
// remove old collateral:debt index
|
||||
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Type, cdp.ID, oldCollateralToDebtRatio)
|
||||
|
||||
// update cdp state
|
||||
cdp.Principal = cdp.Principal.Add(principal)
|
||||
|
||||
@ -68,7 +66,7 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateralTy
|
||||
|
||||
// set cdp state and indexes in the store
|
||||
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
return k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
||||
return k.UpdateCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
||||
}
|
||||
|
||||
// RepayPrincipal removes debt from the cdp
|
||||
@ -85,6 +83,13 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateral
|
||||
return err
|
||||
}
|
||||
|
||||
err = k.ValidateBalance(ctx, payment, owner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.hooks.BeforeCDPModified(ctx, cdp)
|
||||
cdp = k.SynchronizeInterest(ctx, cdp)
|
||||
|
||||
// Note: assumes cdp.Principal and cdp.AccumulatedFees don't change during calculations
|
||||
totalPrincipal := cdp.GetTotalPrincipal()
|
||||
|
||||
@ -134,8 +139,6 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateral
|
||||
)
|
||||
|
||||
// remove the old collateral:debt ratio index
|
||||
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, totalPrincipal)
|
||||
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Type, cdp.ID, oldCollateralToDebtRatio)
|
||||
|
||||
// update cdp state
|
||||
if !principalPayment.IsZero() {
|
||||
@ -150,12 +153,12 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateral
|
||||
// and remove the cdp and indexes from the store
|
||||
if cdp.Principal.IsZero() && cdp.AccumulatedFees.IsZero() {
|
||||
k.ReturnCollateral(ctx, cdp)
|
||||
if err := k.DeleteCDP(ctx, cdp); err != nil {
|
||||
k.RemoveCdpOwnerIndex(ctx, cdp)
|
||||
err := k.DeleteCdpAndCollateralRatioIndex(ctx, cdp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k.RemoveCdpOwnerIndex(ctx, cdp)
|
||||
|
||||
// emit cdp close event
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
@ -168,7 +171,7 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateral
|
||||
|
||||
// set cdp state and update indexes
|
||||
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
return k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
||||
return k.UpdateCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
||||
}
|
||||
|
||||
// ValidatePaymentCoins validates that the input coins are valid for repaying debt
|
||||
|
@ -129,37 +129,6 @@ func (suite *DrawTestSuite) TestRepayPrincipalOverpay() {
|
||||
suite.False(found)
|
||||
}
|
||||
|
||||
func (suite *DrawTestSuite) TestAddRepayPrincipalFees() {
|
||||
err := suite.keeper.AddCdp(suite.ctx, suite.addrs[2], c("xrp", 1000000000000), c("usdx", 100000000000), "xrp-a")
|
||||
suite.NoError(err)
|
||||
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Minute * 10))
|
||||
err = suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp-a")
|
||||
suite.NoError(err)
|
||||
err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[2], "xrp-a", c("usdx", 10000000))
|
||||
suite.NoError(err)
|
||||
t, _ := suite.keeper.GetCDP(suite.ctx, "xrp-a", uint64(2))
|
||||
suite.Equal(c("usdx", 92827), t.AccumulatedFees)
|
||||
err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[2], "xrp-a", c("usdx", 100))
|
||||
suite.NoError(err)
|
||||
t, _ = suite.keeper.GetCDP(suite.ctx, "xrp-a", uint64(2))
|
||||
suite.Equal(c("usdx", 92727), t.AccumulatedFees)
|
||||
err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[2], "xrp-a", c("usdx", 100010092727))
|
||||
suite.NoError(err)
|
||||
_, f := suite.keeper.GetCDP(suite.ctx, "xrp-a", uint64(2))
|
||||
suite.False(f)
|
||||
|
||||
err = suite.keeper.AddCdp(suite.ctx, suite.addrs[2], c("xrp", 1000000000000), c("usdx", 100000000), "xrp-a")
|
||||
suite.NoError(err)
|
||||
|
||||
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 31536000)) // move forward one year in time
|
||||
err = suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp-a")
|
||||
suite.NoError(err)
|
||||
err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[2], "xrp-a", c("usdx", 100000000))
|
||||
suite.NoError(err)
|
||||
t, _ = suite.keeper.GetCDP(suite.ctx, "xrp-a", uint64(3))
|
||||
suite.Equal(c("usdx", 5000000), t.AccumulatedFees)
|
||||
}
|
||||
|
||||
func (suite *DrawTestSuite) TestPricefeedFailure() {
|
||||
ctx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 2))
|
||||
pfk := suite.app.GetPriceFeedKeeper()
|
||||
|
@ -1,137 +0,0 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/cdp/types"
|
||||
)
|
||||
|
||||
// CalculateFees returns the fees accumulated since fees were last calculated based on
|
||||
// the input amount of outstanding debt (principal) and the number of periods (seconds) that have passed
|
||||
func (k Keeper) CalculateFees(ctx sdk.Context, principal sdk.Coin, periods sdk.Int, collateralType string) sdk.Coin {
|
||||
// how fees are calculated:
|
||||
// feesAccumulated = (outstandingDebt * (feeRate^periods)) - outstandingDebt
|
||||
// Note that since we can't do x^y using sdk.Decimal, we are converting to int and using RelativePow
|
||||
feePerSecond := k.getFeeRate(ctx, collateralType)
|
||||
scalar := sdk.NewInt(1000000000000000000)
|
||||
feeRateInt := feePerSecond.Mul(sdk.NewDecFromInt(scalar)).TruncateInt()
|
||||
accumulator := sdk.NewDecFromInt(types.RelativePow(feeRateInt, periods, scalar)).Mul(sdk.SmallestDec())
|
||||
feesAccumulated := (sdk.NewDecFromInt(principal.Amount).Mul(accumulator)).Sub(sdk.NewDecFromInt(principal.Amount))
|
||||
newFees := sdk.NewCoin(principal.Denom, feesAccumulated.TruncateInt())
|
||||
return newFees
|
||||
}
|
||||
|
||||
// UpdateFeesForAllCdps updates the fees for each of the CDPs
|
||||
func (k Keeper) UpdateFeesForAllCdps(ctx sdk.Context, collateralType string) error {
|
||||
var iterationErr error
|
||||
k.IterateCdpsByCollateralType(ctx, collateralType, func(cdp types.CDP) bool {
|
||||
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
// periods = bblock timestamp - fees updated
|
||||
periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix()))
|
||||
|
||||
newFees := k.CalculateFees(ctx, cdp.Principal, periods, collateralType)
|
||||
|
||||
// exit without updating fees if amount has rounded down to zero
|
||||
// cdp will get updated next block when newFees, newFeesSavings, newFeesSurplus >0
|
||||
if newFees.IsZero() {
|
||||
return false
|
||||
}
|
||||
|
||||
dp, found := k.GetDebtParam(ctx, cdp.Principal.Denom)
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
savingsRate := dp.SavingsRate
|
||||
|
||||
newFeesSavings := sdk.NewDecFromInt(newFees.Amount).Mul(savingsRate).RoundInt()
|
||||
newFeesSurplus := newFees.Amount.Sub(newFeesSavings)
|
||||
|
||||
// similar to checking for rounding to zero of all fees, but in this case we
|
||||
// need to handle cases where we expect surplus or savings fees to be zero, namely
|
||||
// if newFeesSavings = 0, check if savings rate is not zero
|
||||
// if newFeesSurplus = 0, check if savings rate is not one
|
||||
if (newFeesSavings.IsZero() && !savingsRate.IsZero()) || (newFeesSurplus.IsZero() && !savingsRate.Equal(sdk.OneDec())) {
|
||||
return false
|
||||
}
|
||||
|
||||
// mint debt coins to the cdp account
|
||||
err := k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), newFees)
|
||||
if err != nil {
|
||||
iterationErr = err
|
||||
return true
|
||||
}
|
||||
|
||||
previousDebt := k.GetTotalPrincipal(ctx, cdp.Type, dp.Denom)
|
||||
newDebt := previousDebt.Add(newFees.Amount)
|
||||
k.SetTotalPrincipal(ctx, cdp.Type, dp.Denom, newDebt)
|
||||
|
||||
// mint surplus coins divided between the liquidator and savings module accounts.
|
||||
err = k.supplyKeeper.MintCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSurplus)))
|
||||
if err != nil {
|
||||
iterationErr = err
|
||||
return true
|
||||
}
|
||||
|
||||
err = k.supplyKeeper.MintCoins(ctx, types.SavingsRateMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSavings)))
|
||||
if err != nil {
|
||||
iterationErr = err
|
||||
return true
|
||||
}
|
||||
|
||||
// now add the new fees to the accumulated fees for the cdp
|
||||
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(newFees)
|
||||
|
||||
// and set the fees updated time to the current block time since we just updated it
|
||||
cdp.FeesUpdated = ctx.BlockTime()
|
||||
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Type, cdp.ID, oldCollateralToDebtRatio)
|
||||
err = k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
||||
if err != nil {
|
||||
iterationErr = err
|
||||
return true
|
||||
}
|
||||
return false // this returns true when you want to stop iterating. Since we want to iterate through all we return false
|
||||
})
|
||||
return iterationErr
|
||||
}
|
||||
|
||||
// IncrementTotalPrincipal increments the total amount of debt that has been drawn with that collateral type
|
||||
func (k Keeper) IncrementTotalPrincipal(ctx sdk.Context, collateralType string, principal sdk.Coin) {
|
||||
total := k.GetTotalPrincipal(ctx, collateralType, principal.Denom)
|
||||
total = total.Add(principal.Amount)
|
||||
k.SetTotalPrincipal(ctx, collateralType, principal.Denom, total)
|
||||
}
|
||||
|
||||
// DecrementTotalPrincipal decrements the total amount of debt that has been drawn for a particular collateral type
|
||||
func (k Keeper) DecrementTotalPrincipal(ctx sdk.Context, collateralType string, principal sdk.Coin) {
|
||||
total := k.GetTotalPrincipal(ctx, collateralType, principal.Denom)
|
||||
// NOTE: negative total principal can happen in tests due to rounding errors
|
||||
// in fee calculation
|
||||
total = sdk.MaxInt(total.Sub(principal.Amount), sdk.ZeroInt())
|
||||
k.SetTotalPrincipal(ctx, collateralType, principal.Denom, total)
|
||||
}
|
||||
|
||||
// GetTotalPrincipal returns the total amount of principal that has been drawn for a particular collateral
|
||||
func (k Keeper) GetTotalPrincipal(ctx sdk.Context, collateralType, principalDenom string) (total sdk.Int) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PrincipalKeyPrefix)
|
||||
bz := store.Get([]byte(collateralType + principalDenom))
|
||||
if bz == nil {
|
||||
k.SetTotalPrincipal(ctx, collateralType, principalDenom, sdk.ZeroInt())
|
||||
return sdk.ZeroInt()
|
||||
}
|
||||
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &total)
|
||||
return total
|
||||
}
|
||||
|
||||
// SetTotalPrincipal sets the total amount of principal that has been drawn for the input collateral
|
||||
func (k Keeper) SetTotalPrincipal(ctx sdk.Context, collateralType, principalDenom string, total sdk.Int) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PrincipalKeyPrefix)
|
||||
_, found := k.GetCollateralTypePrefix(ctx, collateralType)
|
||||
if !found {
|
||||
panic(fmt.Sprintf("collateral not found: %s", collateralType))
|
||||
}
|
||||
store.Set([]byte(collateralType+principalDenom), k.cdc.MustMarshalBinaryLengthPrefixed(total))
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/cdp/keeper"
|
||||
)
|
||||
|
||||
type FeeTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
keeper keeper.Keeper
|
||||
app app.TestApp
|
||||
ctx sdk.Context
|
||||
}
|
||||
|
||||
func (suite *FeeTestSuite) SetupTest() {
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewPricefeedGenStateMulti(),
|
||||
NewCDPGenStateMulti(),
|
||||
)
|
||||
keeper := tApp.GetCDPKeeper()
|
||||
suite.app = tApp
|
||||
suite.ctx = ctx
|
||||
suite.keeper = keeper
|
||||
}
|
||||
|
||||
// createCdps is a helper function to create two CDPs each with zero fees
|
||||
func (suite *FeeTestSuite) createCdps() {
|
||||
// create 2 accounts in the state and give them some coins
|
||||
// create two private key pair addresses
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||
ak := suite.app.GetAccountKeeper()
|
||||
// setup the first account
|
||||
acc := ak.NewAccountWithAddress(suite.ctx, addrs[0])
|
||||
acc.SetCoins(cs(c("xrp", 200000000), c("btc", 500000000)))
|
||||
|
||||
ak.SetAccount(suite.ctx, acc)
|
||||
// now setup the second account
|
||||
acc2 := ak.NewAccountWithAddress(suite.ctx, addrs[1])
|
||||
acc2.SetCoins(cs(c("xrp", 200000000), c("btc", 500000000)))
|
||||
ak.SetAccount(suite.ctx, acc2)
|
||||
|
||||
// now create two cdps with the addresses we just created
|
||||
// use the created account to create a cdp that SHOULD have fees updated
|
||||
// to get a ratio between 100 - 110% of liquidation ratio we can use 200xrp ($50) and 24 usdx (208% collateralization with liquidation ratio of 200%)
|
||||
// create CDP for the first address
|
||||
err := suite.keeper.AddCdp(suite.ctx, addrs[0], c("xrp", 200000000), c("usdx", 24000000), "xrp-a")
|
||||
suite.NoError(err) // check that no error was thrown
|
||||
|
||||
// use the other account to create a cdp that SHOULD NOT have fees updated - 500% collateralization
|
||||
// create CDP for the second address
|
||||
err = suite.keeper.AddCdp(suite.ctx, addrs[1], c("xrp", 200000000), c("usdx", 10000000), "xrp-a")
|
||||
suite.NoError(err) // check that no error was thrown
|
||||
|
||||
}
|
||||
|
||||
// TestUpdateFees tests the functionality for updating the fees for CDPs
|
||||
func (suite *FeeTestSuite) TestUpdateFees() {
|
||||
// this helper function creates two CDPs with id 1 and 2 respectively, each with zero fees
|
||||
suite.createCdps()
|
||||
|
||||
// move the context forward in time so that cdps will have fees accumulate if CalculateFees is called
|
||||
// note - time must be moved forward by a sufficient amount in order for additional
|
||||
// fees to accumulate, in this example 600 seconds
|
||||
oldtime := suite.ctx.BlockTime()
|
||||
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 600))
|
||||
err := suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp-a")
|
||||
suite.NoError(err) // check that we don't have any error
|
||||
|
||||
// cdp we expect fees to accumulate for
|
||||
cdp1, found := suite.keeper.GetCDP(suite.ctx, "xrp-a", 1)
|
||||
suite.True(found)
|
||||
// check fees are not zero
|
||||
// check that the fees have been updated
|
||||
suite.False(cdp1.AccumulatedFees.IsZero())
|
||||
// now check that we have the correct amount of fees overall (22 USDX for this scenario)
|
||||
suite.Equal(sdk.NewInt(22), cdp1.AccumulatedFees.Amount)
|
||||
suite.Equal(suite.ctx.BlockTime(), cdp1.FeesUpdated)
|
||||
// cdp we expect fees to not accumulate for because of rounding to zero
|
||||
cdp2, found := suite.keeper.GetCDP(suite.ctx, "xrp-a", 2)
|
||||
suite.True(found)
|
||||
// check fees are zero
|
||||
suite.True(cdp2.AccumulatedFees.IsZero())
|
||||
suite.Equal(oldtime, cdp2.FeesUpdated)
|
||||
}
|
||||
|
||||
func TestFeeTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(FeeTestSuite))
|
||||
}
|
23
x/cdp/keeper/hooks.go
Normal file
23
x/cdp/keeper/hooks.go
Normal file
@ -0,0 +1,23 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/kava-labs/kava/x/cdp/types"
|
||||
)
|
||||
|
||||
// Implements StakingHooks interface
|
||||
var _ types.CDPHooks = Keeper{}
|
||||
|
||||
// AfterCDPCreated - call hook if registered
|
||||
func (k Keeper) AfterCDPCreated(ctx sdk.Context, cdp types.CDP) {
|
||||
if k.hooks != nil {
|
||||
k.hooks.AfterCDPCreated(ctx, cdp)
|
||||
}
|
||||
}
|
||||
|
||||
// BeforeCDPModified - call hook if registered
|
||||
func (k Keeper) BeforeCDPModified(ctx sdk.Context, cdp types.CDP) {
|
||||
if k.hooks != nil {
|
||||
k.hooks.BeforeCDPModified(ctx, cdp)
|
||||
}
|
||||
}
|
@ -45,7 +45,6 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
|
||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
|
||||
CollateralParams: cdp.CollateralParams{
|
||||
{
|
||||
Denom: asset,
|
||||
@ -56,9 +55,11 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
|
||||
LiquidationPenalty: d("0.05"),
|
||||
AuctionSize: i(100),
|
||||
Prefix: 0x20,
|
||||
ConversionFactor: i(6),
|
||||
SpotMarketID: asset + ":usd",
|
||||
LiquidationMarketID: asset + ":usd",
|
||||
KeeperRewardPercentage: d("0.01"),
|
||||
CheckCollateralizationIndexCount: i(10),
|
||||
ConversionFactor: i(6),
|
||||
},
|
||||
},
|
||||
DebtParam: cdp.DebtParam{
|
||||
@ -66,14 +67,18 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: i(6),
|
||||
DebtFloor: i(10000000),
|
||||
SavingsRate: d("0.9"),
|
||||
},
|
||||
},
|
||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||
DebtDenom: cdp.DefaultDebtDenom,
|
||||
GovDenom: cdp.DefaultGovDenom,
|
||||
CDPs: cdp.CDPs{},
|
||||
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
|
||||
PreviousAccumulationTimes: cdp.GenesisAccumulationTimes{
|
||||
cdp.NewGenesisAccumulationTime(asset+"-a", time.Time{}, sdk.OneDec()),
|
||||
},
|
||||
TotalPrincipals: cdp.GenesisTotalPrincipals{
|
||||
cdp.NewGenesisTotalPrincipal(asset+"-a", sdk.ZeroInt()),
|
||||
},
|
||||
}
|
||||
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
|
||||
}
|
||||
@ -85,6 +90,7 @@ func NewPricefeedGenStateMulti() app.GenesisState {
|
||||
{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||
{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||
{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||
{MarketID: "busd:usd", BaseAsset: "busd", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||
},
|
||||
},
|
||||
PostedPrices: []pricefeed.PostedPrice{
|
||||
@ -106,6 +112,12 @@ func NewPricefeedGenStateMulti() app.GenesisState {
|
||||
Price: sdk.MustNewDecFromStr("17.25"),
|
||||
Expiry: time.Now().Add(1 * time.Hour),
|
||||
},
|
||||
{
|
||||
MarketID: "busd:usd",
|
||||
OracleAddress: sdk.AccAddress{},
|
||||
Price: sdk.OneDec(),
|
||||
Expiry: time.Now().Add(1 * time.Hour),
|
||||
},
|
||||
},
|
||||
}
|
||||
return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)}
|
||||
@ -113,12 +125,11 @@ func NewPricefeedGenStateMulti() app.GenesisState {
|
||||
func NewCDPGenStateMulti() app.GenesisState {
|
||||
cdpGenesis := cdp.GenesisState{
|
||||
Params: cdp.Params{
|
||||
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 1500000000000),
|
||||
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
|
||||
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
|
||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
|
||||
CollateralParams: cdp.CollateralParams{
|
||||
{
|
||||
Denom: "xrp",
|
||||
@ -131,6 +142,8 @@ func NewCDPGenStateMulti() app.GenesisState {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "xrp:usd",
|
||||
LiquidationMarketID: "xrp:usd",
|
||||
KeeperRewardPercentage: d("0.01"),
|
||||
CheckCollateralizationIndexCount: i(10),
|
||||
ConversionFactor: i(6),
|
||||
},
|
||||
{
|
||||
@ -144,6 +157,8 @@ func NewCDPGenStateMulti() app.GenesisState {
|
||||
Prefix: 0x21,
|
||||
SpotMarketID: "btc:usd",
|
||||
LiquidationMarketID: "btc:usd",
|
||||
KeeperRewardPercentage: d("0.01"),
|
||||
CheckCollateralizationIndexCount: i(10),
|
||||
ConversionFactor: i(8),
|
||||
},
|
||||
{
|
||||
@ -157,6 +172,23 @@ func NewCDPGenStateMulti() app.GenesisState {
|
||||
Prefix: 0x22,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: d("0.01"),
|
||||
CheckCollateralizationIndexCount: i(10),
|
||||
ConversionFactor: i(8),
|
||||
},
|
||||
{
|
||||
Denom: "busd",
|
||||
Type: "busd-a",
|
||||
LiquidationRatio: d("1.01"),
|
||||
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
|
||||
StabilityFee: sdk.OneDec(), // %0 apr
|
||||
LiquidationPenalty: d("0.05"),
|
||||
AuctionSize: i(10000000000),
|
||||
Prefix: 0x23,
|
||||
SpotMarketID: "busd:usd",
|
||||
LiquidationMarketID: "busd:usd",
|
||||
KeeperRewardPercentage: d("0.01"),
|
||||
CheckCollateralizationIndexCount: i(10),
|
||||
ConversionFactor: i(8),
|
||||
},
|
||||
},
|
||||
@ -165,14 +197,24 @@ func NewCDPGenStateMulti() app.GenesisState {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: i(6),
|
||||
DebtFloor: i(10000000),
|
||||
SavingsRate: d("0.95"),
|
||||
},
|
||||
},
|
||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||
DebtDenom: cdp.DefaultDebtDenom,
|
||||
GovDenom: cdp.DefaultGovDenom,
|
||||
CDPs: cdp.CDPs{},
|
||||
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
|
||||
PreviousAccumulationTimes: cdp.GenesisAccumulationTimes{
|
||||
cdp.NewGenesisAccumulationTime("btc-a", time.Time{}, sdk.OneDec()),
|
||||
cdp.NewGenesisAccumulationTime("xrp-a", time.Time{}, sdk.OneDec()),
|
||||
cdp.NewGenesisAccumulationTime("busd-a", time.Time{}, sdk.OneDec()),
|
||||
cdp.NewGenesisAccumulationTime("bnb-a", time.Time{}, sdk.OneDec()),
|
||||
},
|
||||
TotalPrincipals: cdp.GenesisTotalPrincipals{
|
||||
cdp.NewGenesisTotalPrincipal("btc-a", sdk.ZeroInt()),
|
||||
cdp.NewGenesisTotalPrincipal("xrp-a", sdk.ZeroInt()),
|
||||
cdp.NewGenesisTotalPrincipal("busd-a", sdk.ZeroInt()),
|
||||
cdp.NewGenesisTotalPrincipal("bnb-a", sdk.ZeroInt()),
|
||||
},
|
||||
}
|
||||
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
|
||||
}
|
||||
@ -185,7 +227,6 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
|
||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
|
||||
CollateralParams: cdp.CollateralParams{
|
||||
{
|
||||
Denom: "xrp",
|
||||
@ -198,6 +239,8 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "xrp:usd",
|
||||
LiquidationMarketID: "xrp:usd",
|
||||
KeeperRewardPercentage: d("0.01"),
|
||||
CheckCollateralizationIndexCount: i(10),
|
||||
ConversionFactor: i(6),
|
||||
},
|
||||
{
|
||||
@ -211,6 +254,8 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
|
||||
Prefix: 0x21,
|
||||
SpotMarketID: "btc:usd",
|
||||
LiquidationMarketID: "btc:usd",
|
||||
KeeperRewardPercentage: d("0.01"),
|
||||
CheckCollateralizationIndexCount: i(10),
|
||||
ConversionFactor: i(8),
|
||||
},
|
||||
},
|
||||
@ -219,24 +264,30 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: i(6),
|
||||
DebtFloor: i(10000000),
|
||||
SavingsRate: d("0.95"),
|
||||
},
|
||||
},
|
||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||
DebtDenom: cdp.DefaultDebtDenom,
|
||||
GovDenom: cdp.DefaultGovDenom,
|
||||
CDPs: cdp.CDPs{},
|
||||
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
|
||||
PreviousAccumulationTimes: cdp.GenesisAccumulationTimes{
|
||||
cdp.NewGenesisAccumulationTime("btc-a", time.Time{}, sdk.OneDec()),
|
||||
cdp.NewGenesisAccumulationTime("xrp-a", time.Time{}, sdk.OneDec()),
|
||||
},
|
||||
TotalPrincipals: cdp.GenesisTotalPrincipals{
|
||||
cdp.NewGenesisTotalPrincipal("btc-a", sdk.ZeroInt()),
|
||||
cdp.NewGenesisTotalPrincipal("xrp-a", sdk.ZeroInt()),
|
||||
},
|
||||
}
|
||||
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
|
||||
}
|
||||
|
||||
func cdps() (cdps cdp.CDPs) {
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(3)
|
||||
c1 := cdp.NewCDP(uint64(1), addrs[0], sdk.NewCoin("xrp", sdk.NewInt(10000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(8000000)), tmtime.Canonical(time.Now()))
|
||||
c2 := cdp.NewCDP(uint64(2), addrs[1], sdk.NewCoin("xrp", sdk.NewInt(100000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()))
|
||||
c3 := cdp.NewCDP(uint64(3), addrs[1], sdk.NewCoin("btc", sdk.NewInt(1000000000)), "btc-a", sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()))
|
||||
c4 := cdp.NewCDP(uint64(4), addrs[2], sdk.NewCoin("xrp", sdk.NewInt(1000000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(500000000)), tmtime.Canonical(time.Now()))
|
||||
c1 := cdp.NewCDP(uint64(1), addrs[0], sdk.NewCoin("xrp", sdk.NewInt(10000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(8000000)), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
c2 := cdp.NewCDP(uint64(2), addrs[1], sdk.NewCoin("xrp", sdk.NewInt(100000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
c3 := cdp.NewCDP(uint64(3), addrs[1], sdk.NewCoin("btc", sdk.NewInt(1000000000)), "btc-a", sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
c4 := cdp.NewCDP(uint64(4), addrs[2], sdk.NewCoin("xrp", sdk.NewInt(1000000000)), "xrp-a", sdk.NewCoin("usdx", sdk.NewInt(500000000)), tmtime.Canonical(time.Now()), sdk.OneDec())
|
||||
cdps = append(cdps, c1, c2, c3, c4)
|
||||
return
|
||||
}
|
||||
|
164
x/cdp/keeper/interest.go
Normal file
164
x/cdp/keeper/interest.go
Normal file
@ -0,0 +1,164 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/cdp/types"
|
||||
)
|
||||
|
||||
var (
|
||||
scalingFactor = 1e18
|
||||
secondsPerYear = 31536000
|
||||
)
|
||||
|
||||
// AccumulateInterest calculates the new interest that has accrued for the input collateral type based on the total amount of principal
|
||||
// that has been created with that collateral type and the amount of time that has passed since interest was last accumulated
|
||||
func (k Keeper) AccumulateInterest(ctx sdk.Context, ctype string) error {
|
||||
previousAccrualTime, found := k.GetPreviousAccrualTime(ctx, ctype)
|
||||
if !found {
|
||||
k.SetPreviousAccrualTime(ctx, ctype, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
|
||||
timeElapsed := int64(math.RoundToEven(
|
||||
ctx.BlockTime().Sub(previousAccrualTime).Seconds(),
|
||||
))
|
||||
if timeElapsed == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
totalPrincipalPrior := k.GetTotalPrincipal(ctx, ctype, types.DefaultStableDenom)
|
||||
if totalPrincipalPrior.IsZero() || totalPrincipalPrior.IsNegative() {
|
||||
k.SetPreviousAccrualTime(ctx, ctype, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
|
||||
interestFactorPrior, foundInterestFactorPrior := k.GetInterestFactor(ctx, ctype)
|
||||
if !foundInterestFactorPrior {
|
||||
k.SetInterestFactor(ctx, ctype, sdk.OneDec())
|
||||
// set previous accrual time exit early because interest accumulated will be zero
|
||||
k.SetPreviousAccrualTime(ctx, ctype, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
|
||||
borrowRateSpy := k.getFeeRate(ctx, ctype)
|
||||
if borrowRateSpy.Equal(sdk.OneDec()) {
|
||||
k.SetPreviousAccrualTime(ctx, ctype, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
interestFactor := CalculateInterestFactor(borrowRateSpy, sdk.NewInt(timeElapsed))
|
||||
interestAccumulated := (interestFactor.Mul(totalPrincipalPrior.ToDec())).RoundInt().Sub(totalPrincipalPrior)
|
||||
if interestAccumulated.IsZero() {
|
||||
// in the case accumulated interest rounds to zero, exit early without updating accrual time
|
||||
return nil
|
||||
}
|
||||
err := k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), sdk.NewCoin(types.DefaultStableDenom, interestAccumulated))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dp, found := k.GetDebtParam(ctx, types.DefaultStableDenom)
|
||||
if !found {
|
||||
panic(fmt.Sprintf("Debt parameters for %s not found", types.DefaultStableDenom))
|
||||
}
|
||||
|
||||
newFeesSurplus := interestAccumulated
|
||||
|
||||
// mint surplus coins to the liquidator module account.
|
||||
if newFeesSurplus.IsPositive() {
|
||||
err := k.supplyKeeper.MintCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSurplus)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
interestFactorNew := interestFactorPrior.Mul(interestFactor)
|
||||
totalPrincipalNew := totalPrincipalPrior.Add(interestAccumulated)
|
||||
|
||||
k.SetTotalPrincipal(ctx, ctype, types.DefaultStableDenom, totalPrincipalNew)
|
||||
k.SetInterestFactor(ctx, ctype, interestFactorNew)
|
||||
k.SetPreviousAccrualTime(ctx, ctype, ctx.BlockTime())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalculateInterestFactor calculates the simple interest scaling factor,
|
||||
// which is equal to: (per-second interest rate ** number of seconds elapsed)
|
||||
// Will return 1.000x, multiply by principal to get new principal with added interest
|
||||
func CalculateInterestFactor(perSecondInterestRate sdk.Dec, secondsElapsed sdk.Int) sdk.Dec {
|
||||
scalingFactorUint := sdk.NewUint(uint64(scalingFactor))
|
||||
scalingFactorInt := sdk.NewInt(int64(scalingFactor))
|
||||
|
||||
// Convert per-second interest rate to a uint scaled by 1e18
|
||||
interestMantissa := sdk.NewUint(perSecondInterestRate.MulInt(scalingFactorInt).RoundInt().Uint64())
|
||||
// Convert seconds elapsed to uint (*not scaled*)
|
||||
secondsElapsedUint := sdk.NewUint(secondsElapsed.Uint64())
|
||||
// Calculate the interest factor as a uint scaled by 1e18
|
||||
interestFactorMantissa := sdk.RelativePow(interestMantissa, secondsElapsedUint, scalingFactorUint)
|
||||
|
||||
// Convert interest factor to an unscaled sdk.Dec
|
||||
return sdk.NewDecFromBigInt(interestFactorMantissa.BigInt()).QuoInt(scalingFactorInt)
|
||||
}
|
||||
|
||||
// SynchronizeInterest updates the input cdp object to reflect the current accumulated interest, updates the cdp state in the store,
|
||||
// and returns the updated cdp object
|
||||
func (k Keeper) SynchronizeInterest(ctx sdk.Context, cdp types.CDP) types.CDP {
|
||||
globalInterestFactor, found := k.GetInterestFactor(ctx, cdp.Type)
|
||||
if !found {
|
||||
k.SetInterestFactor(ctx, cdp.Type, sdk.OneDec())
|
||||
cdp.InterestFactor = sdk.OneDec()
|
||||
cdp.FeesUpdated = ctx.BlockTime()
|
||||
k.SetCDP(ctx, cdp)
|
||||
return cdp
|
||||
}
|
||||
|
||||
accumulatedInterest := k.CalculateNewInterest(ctx, cdp)
|
||||
if accumulatedInterest.IsZero() {
|
||||
// accumulated interest is zero if apy is zero or are if the total fees for all cdps round to zero
|
||||
|
||||
prevAccrualTime, found := k.GetPreviousAccrualTime(ctx, cdp.Type)
|
||||
if !found {
|
||||
return cdp
|
||||
}
|
||||
if cdp.FeesUpdated.Equal(prevAccrualTime) {
|
||||
// if all fees are rounding to zero, don't update FeesUpdated
|
||||
return cdp
|
||||
}
|
||||
// if apy is zero, we need to update FeesUpdated
|
||||
cdp.FeesUpdated = ctx.BlockTime()
|
||||
k.SetCDP(ctx, cdp)
|
||||
}
|
||||
|
||||
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(accumulatedInterest)
|
||||
cdp.FeesUpdated = ctx.BlockTime()
|
||||
cdp.InterestFactor = globalInterestFactor
|
||||
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
k.UpdateCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
||||
return cdp
|
||||
}
|
||||
|
||||
// CalculateNewInterest returns the amount of interest that has accrued to the cdp since its interest was last synchronized
|
||||
func (k Keeper) CalculateNewInterest(ctx sdk.Context, cdp types.CDP) sdk.Coin {
|
||||
globalInterestFactor, found := k.GetInterestFactor(ctx, cdp.Type)
|
||||
if !found {
|
||||
return sdk.NewCoin(cdp.AccumulatedFees.Denom, sdk.ZeroInt())
|
||||
}
|
||||
cdpInterestFactor := globalInterestFactor.Quo(cdp.InterestFactor)
|
||||
if cdpInterestFactor.Equal(sdk.OneDec()) {
|
||||
return sdk.NewCoin(cdp.AccumulatedFees.Denom, sdk.ZeroInt())
|
||||
}
|
||||
accumulatedInterest := cdp.GetTotalPrincipal().Amount.ToDec().Mul(cdpInterestFactor).RoundInt().Sub(cdp.GetTotalPrincipal().Amount)
|
||||
return sdk.NewCoin(cdp.AccumulatedFees.Denom, accumulatedInterest)
|
||||
}
|
||||
|
||||
// SynchronizeInterestForRiskyCDPs synchronizes the interest for the slice of cdps with the lowest collateral:debt ratio
|
||||
func (k Keeper) SynchronizeInterestForRiskyCDPs(ctx sdk.Context, slice sdk.Int, targetRatio sdk.Dec, collateralType string) error {
|
||||
cdps := k.GetSliceOfCDPsByRatioAndType(ctx, slice, targetRatio, collateralType)
|
||||
for _, cdp := range cdps {
|
||||
k.SynchronizeInterest(ctx, cdp)
|
||||
}
|
||||
return nil
|
||||
}
|
763
x/cdp/keeper/interest_test.go
Normal file
763
x/cdp/keeper/interest_test.go
Normal file
@ -0,0 +1,763 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/cdp/keeper"
|
||||
"github.com/kava-labs/kava/x/cdp/types"
|
||||
)
|
||||
|
||||
type InterestTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
keeper keeper.Keeper
|
||||
app app.TestApp
|
||||
ctx sdk.Context
|
||||
}
|
||||
|
||||
func (suite *InterestTestSuite) SetupTest() {
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewPricefeedGenStateMulti(),
|
||||
NewCDPGenStateMulti(),
|
||||
)
|
||||
keeper := tApp.GetCDPKeeper()
|
||||
suite.app = tApp
|
||||
suite.ctx = ctx
|
||||
suite.keeper = keeper
|
||||
}
|
||||
|
||||
// createCdps is a helper function to create two CDPs each with zero fees
|
||||
func (suite *InterestTestSuite) createCdps() {
|
||||
// create 2 accounts in the state and give them some coins
|
||||
// create two private key pair addresses
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||
ak := suite.app.GetAccountKeeper()
|
||||
// setup the first account
|
||||
acc := ak.NewAccountWithAddress(suite.ctx, addrs[0])
|
||||
acc.SetCoins(cs(c("xrp", 200000000), c("btc", 500000000)))
|
||||
|
||||
ak.SetAccount(suite.ctx, acc)
|
||||
// now setup the second account
|
||||
acc2 := ak.NewAccountWithAddress(suite.ctx, addrs[1])
|
||||
acc2.SetCoins(cs(c("xrp", 200000000), c("btc", 500000000)))
|
||||
ak.SetAccount(suite.ctx, acc2)
|
||||
|
||||
// now create two cdps with the addresses we just created
|
||||
// use the created account to create a cdp that SHOULD have fees updated
|
||||
// to get a ratio between 100 - 110% of liquidation ratio we can use 200xrp ($50) and 24 usdx (208% collateralization with liquidation ratio of 200%)
|
||||
// create CDP for the first address
|
||||
err := suite.keeper.AddCdp(suite.ctx, addrs[0], c("xrp", 200000000), c("usdx", 24000000), "xrp-a")
|
||||
suite.NoError(err) // check that no error was thrown
|
||||
|
||||
// use the other account to create a cdp that SHOULD NOT have fees updated - 500% collateralization
|
||||
// create CDP for the second address
|
||||
err = suite.keeper.AddCdp(suite.ctx, addrs[1], c("xrp", 200000000), c("usdx", 10000000), "xrp-a")
|
||||
suite.NoError(err) // check that no error was thrown
|
||||
|
||||
}
|
||||
|
||||
func (suite *InterestTestSuite) TestCalculateInterestFactor() {
|
||||
type args struct {
|
||||
perSecondInterestRate sdk.Dec
|
||||
timeElapsed sdk.Int
|
||||
expectedValue sdk.Dec
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
}
|
||||
|
||||
oneYearInSeconds := int64(31536000)
|
||||
|
||||
testCases := []test{
|
||||
{
|
||||
"1 year",
|
||||
args{
|
||||
perSecondInterestRate: sdk.MustNewDecFromStr("1.000000005555"),
|
||||
timeElapsed: sdk.NewInt(oneYearInSeconds),
|
||||
expectedValue: sdk.MustNewDecFromStr("1.191463614477847370"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"10 year",
|
||||
args{
|
||||
perSecondInterestRate: sdk.MustNewDecFromStr("1.000000005555"),
|
||||
timeElapsed: sdk.NewInt(oneYearInSeconds * 10),
|
||||
expectedValue: sdk.MustNewDecFromStr("5.765113233897391189"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 month",
|
||||
args{
|
||||
perSecondInterestRate: sdk.MustNewDecFromStr("1.000000005555"),
|
||||
timeElapsed: sdk.NewInt(oneYearInSeconds / 12),
|
||||
expectedValue: sdk.MustNewDecFromStr("1.014705619075717373"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 day",
|
||||
args{
|
||||
perSecondInterestRate: sdk.MustNewDecFromStr("1.000000005555"),
|
||||
timeElapsed: sdk.NewInt(oneYearInSeconds / 365),
|
||||
expectedValue: sdk.MustNewDecFromStr("1.000480067194057924"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 year: low interest rate",
|
||||
args{
|
||||
perSecondInterestRate: sdk.MustNewDecFromStr("1.000000000555"),
|
||||
timeElapsed: sdk.NewInt(oneYearInSeconds),
|
||||
expectedValue: sdk.MustNewDecFromStr("1.017656545925063632"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 year, lower interest rate",
|
||||
args{
|
||||
perSecondInterestRate: sdk.MustNewDecFromStr("1.000000000055"),
|
||||
timeElapsed: sdk.NewInt(oneYearInSeconds),
|
||||
expectedValue: sdk.MustNewDecFromStr("1.001735985079841390"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 year, lowest interest rate",
|
||||
args{
|
||||
perSecondInterestRate: sdk.MustNewDecFromStr("1.000000000005"),
|
||||
timeElapsed: sdk.NewInt(oneYearInSeconds),
|
||||
expectedValue: sdk.MustNewDecFromStr("1.000157692432076670"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 year: high interest rate",
|
||||
args{
|
||||
perSecondInterestRate: sdk.MustNewDecFromStr("1.000000055555"),
|
||||
timeElapsed: sdk.NewInt(oneYearInSeconds),
|
||||
expectedValue: sdk.MustNewDecFromStr("5.766022095987868825"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 year: higher interest rate",
|
||||
args{
|
||||
perSecondInterestRate: sdk.MustNewDecFromStr("1.000000555555"),
|
||||
timeElapsed: sdk.NewInt(oneYearInSeconds),
|
||||
expectedValue: sdk.MustNewDecFromStr("40628388.864535408465693310"),
|
||||
},
|
||||
},
|
||||
// If we raise the per second interest rate too much we'll cause an integer overflow.
|
||||
// For example, perSecondInterestRate: '1.000005555555' will cause a panic.
|
||||
{
|
||||
"1 year: highest interest rate",
|
||||
args{
|
||||
perSecondInterestRate: sdk.MustNewDecFromStr("1.000001555555"),
|
||||
timeElapsed: sdk.NewInt(oneYearInSeconds),
|
||||
expectedValue: sdk.MustNewDecFromStr("2017093013158200407564.613502861572552603"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
interestFactor := keeper.CalculateInterestFactor(tc.args.perSecondInterestRate, tc.args.timeElapsed)
|
||||
suite.Require().Equal(tc.args.expectedValue, interestFactor)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *InterestTestSuite) TestAccumulateInterest() {
|
||||
|
||||
type args struct {
|
||||
ctype string
|
||||
initialTime time.Time
|
||||
totalPrincipal sdk.Int
|
||||
timeElapsed int
|
||||
expectedTotalPrincipal sdk.Int
|
||||
expectedLastAccrualTime time.Time
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
}
|
||||
oneYearInSeconds := 31536000
|
||||
|
||||
testCases := []test{
|
||||
{
|
||||
"1 year",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
totalPrincipal: sdk.NewInt(100000000000000),
|
||||
timeElapsed: oneYearInSeconds,
|
||||
expectedTotalPrincipal: sdk.NewInt(105000000000012),
|
||||
expectedLastAccrualTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * oneYearInSeconds)),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 year - zero principal",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
totalPrincipal: sdk.ZeroInt(),
|
||||
timeElapsed: oneYearInSeconds,
|
||||
expectedTotalPrincipal: sdk.ZeroInt(),
|
||||
expectedLastAccrualTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * oneYearInSeconds)),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 month",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
totalPrincipal: sdk.NewInt(100000000000000),
|
||||
timeElapsed: 86400 * 30,
|
||||
expectedTotalPrincipal: sdk.NewInt(100401820189198),
|
||||
expectedLastAccrualTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * 86400 * 30)),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 month - interest rounds to zero",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
totalPrincipal: sdk.NewInt(10),
|
||||
timeElapsed: 86400 * 30,
|
||||
expectedTotalPrincipal: sdk.NewInt(10),
|
||||
expectedLastAccrualTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
{
|
||||
"7 seconds",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
totalPrincipal: sdk.NewInt(100000000000000),
|
||||
timeElapsed: 7,
|
||||
expectedTotalPrincipal: sdk.NewInt(100000001082988),
|
||||
expectedLastAccrualTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * 7)),
|
||||
},
|
||||
},
|
||||
{
|
||||
"7 seconds - interest rounds to zero",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
totalPrincipal: sdk.NewInt(30000000),
|
||||
timeElapsed: 7,
|
||||
expectedTotalPrincipal: sdk.NewInt(30000000),
|
||||
expectedLastAccrualTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
{
|
||||
"7 seconds - zero interest",
|
||||
args{
|
||||
ctype: "busd-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
totalPrincipal: sdk.NewInt(100000000000000),
|
||||
timeElapsed: 7,
|
||||
expectedTotalPrincipal: sdk.NewInt(100000000000000),
|
||||
expectedLastAccrualTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * 7)),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
|
||||
suite.keeper.SetTotalPrincipal(suite.ctx, tc.args.ctype, types.DefaultStableDenom, tc.args.totalPrincipal)
|
||||
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, suite.ctx.BlockTime())
|
||||
suite.keeper.SetInterestFactor(suite.ctx, tc.args.ctype, sdk.OneDec())
|
||||
|
||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed))
|
||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||
err := suite.keeper.AccumulateInterest(suite.ctx, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
actualTotalPrincipal := suite.keeper.GetTotalPrincipal(suite.ctx, tc.args.ctype, types.DefaultStableDenom)
|
||||
suite.Require().Equal(tc.args.expectedTotalPrincipal, actualTotalPrincipal)
|
||||
actualAccrualTime, _ := suite.keeper.GetPreviousAccrualTime(suite.ctx, tc.args.ctype)
|
||||
suite.Require().Equal(tc.args.expectedLastAccrualTime, actualAccrualTime)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSynchronizeInterest tests the functionality of synchronizing the accumulated interest for CDPs
|
||||
func (suite *InterestTestSuite) TestSynchronizeInterest() {
|
||||
type args struct {
|
||||
ctype string
|
||||
initialTime time.Time
|
||||
initialCollateral sdk.Coin
|
||||
initialPrincipal sdk.Coin
|
||||
timeElapsed int
|
||||
expectedFees sdk.Coin
|
||||
expectedFeesUpdatedTime time.Time
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
}
|
||||
|
||||
oneYearInSeconds := 31536000
|
||||
testCases := []test{
|
||||
{
|
||||
"1 year",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000000),
|
||||
initialPrincipal: c("usdx", 100000000000),
|
||||
timeElapsed: oneYearInSeconds,
|
||||
expectedFees: c("usdx", 5000000000),
|
||||
expectedFeesUpdatedTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * oneYearInSeconds)),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 month",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000000),
|
||||
initialPrincipal: c("usdx", 100000000000),
|
||||
timeElapsed: 86400 * 30,
|
||||
expectedFees: c("usdx", 401820189),
|
||||
expectedFeesUpdatedTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * 86400 * 30)),
|
||||
},
|
||||
},
|
||||
{
|
||||
"7 seconds",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000000),
|
||||
initialPrincipal: c("usdx", 100000000000),
|
||||
timeElapsed: 7,
|
||||
expectedFees: c("usdx", 1083),
|
||||
expectedFeesUpdatedTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * 7)),
|
||||
},
|
||||
},
|
||||
{
|
||||
"7 seconds - zero apy",
|
||||
args{
|
||||
ctype: "busd-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("busd", 10000000000000),
|
||||
initialPrincipal: c("usdx", 10000000000),
|
||||
timeElapsed: 7,
|
||||
expectedFees: c("usdx", 0),
|
||||
expectedFeesUpdatedTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * 7)),
|
||||
},
|
||||
},
|
||||
{
|
||||
"7 seconds - fees round to zero",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000),
|
||||
initialPrincipal: c("usdx", 10000000),
|
||||
timeElapsed: 7,
|
||||
expectedFees: c("usdx", 0),
|
||||
expectedFeesUpdatedTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupTest()
|
||||
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
|
||||
|
||||
// setup account state
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
ak := suite.app.GetAccountKeeper()
|
||||
// setup the first account
|
||||
acc := ak.NewAccountWithAddress(suite.ctx, addrs[0])
|
||||
ak.SetAccount(suite.ctx, acc)
|
||||
sk := suite.app.GetSupplyKeeper()
|
||||
err := sk.MintCoins(suite.ctx, types.ModuleName, cs(tc.args.initialCollateral))
|
||||
suite.Require().NoError(err)
|
||||
err = sk.SendCoinsFromModuleToAccount(suite.ctx, types.ModuleName, addrs[0], cs(tc.args.initialCollateral))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// setup pricefeed
|
||||
pk := suite.app.GetPriceFeedKeeper()
|
||||
pk.SetPrice(suite.ctx, sdk.AccAddress{}, "bnb:usd", d("17.25"), tc.args.expectedFeesUpdatedTime.Add(time.Second))
|
||||
pk.SetPrice(suite.ctx, sdk.AccAddress{}, "busd:usd", d("1"), tc.args.expectedFeesUpdatedTime.Add(time.Second))
|
||||
|
||||
// setup cdp state
|
||||
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, suite.ctx.BlockTime())
|
||||
suite.keeper.SetInterestFactor(suite.ctx, tc.args.ctype, sdk.OneDec())
|
||||
err = suite.keeper.AddCdp(suite.ctx, addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed))
|
||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||
err = suite.keeper.AccumulateInterest(suite.ctx, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
cdp, found := suite.keeper.GetCDP(suite.ctx, tc.args.ctype, 1)
|
||||
suite.Require().True(found)
|
||||
|
||||
cdp = suite.keeper.SynchronizeInterest(suite.ctx, cdp)
|
||||
|
||||
suite.Require().Equal(tc.args.expectedFees, cdp.AccumulatedFees)
|
||||
suite.Require().Equal(tc.args.expectedFeesUpdatedTime, cdp.FeesUpdated)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *InterestTestSuite) TestMultipleCDPInterest() {
|
||||
type args struct {
|
||||
ctype string
|
||||
initialTime time.Time
|
||||
blockInterval int
|
||||
numberOfBlocks int
|
||||
initialCDPCollateral sdk.Coin
|
||||
initialCDPPrincipal sdk.Coin
|
||||
numberOfCdps int
|
||||
expectedFeesPerCDP sdk.Coin
|
||||
expectedTotalPrincipalPerCDP sdk.Coin
|
||||
expectedFeesUpdatedTime time.Time
|
||||
expectedTotalPrincipal sdk.Int
|
||||
expectedDebtBalance sdk.Int
|
||||
expectedStableBalance sdk.Int
|
||||
expectedSumOfCDPPrincipal sdk.Int
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
}
|
||||
|
||||
testCases := []test{
|
||||
{
|
||||
"1 block",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
blockInterval: 7,
|
||||
numberOfBlocks: 1,
|
||||
initialCDPCollateral: c("bnb", 10000000000),
|
||||
initialCDPPrincipal: c("usdx", 500000000),
|
||||
numberOfCdps: 100,
|
||||
expectedFeesPerCDP: c("usdx", 5),
|
||||
expectedTotalPrincipalPerCDP: c("usdx", 500000005),
|
||||
expectedFeesUpdatedTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * 7)),
|
||||
expectedTotalPrincipal: i(50000000541),
|
||||
expectedDebtBalance: i(50000000541),
|
||||
expectedStableBalance: i(50000000541),
|
||||
expectedSumOfCDPPrincipal: i(50000000500),
|
||||
},
|
||||
},
|
||||
{
|
||||
"100 blocks",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
blockInterval: 7,
|
||||
numberOfBlocks: 100,
|
||||
initialCDPCollateral: c("bnb", 10000000000),
|
||||
initialCDPPrincipal: c("usdx", 500000000),
|
||||
numberOfCdps: 100,
|
||||
expectedFeesPerCDP: c("usdx", 541),
|
||||
expectedTotalPrincipalPerCDP: c("usdx", 500000541),
|
||||
expectedFeesUpdatedTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * 7 * 100)),
|
||||
expectedTotalPrincipal: i(50000054100),
|
||||
expectedDebtBalance: i(50000054100),
|
||||
expectedStableBalance: i(50000054100),
|
||||
expectedSumOfCDPPrincipal: i(50000054100),
|
||||
},
|
||||
},
|
||||
{
|
||||
"10000 blocks",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
blockInterval: 7,
|
||||
numberOfBlocks: 10000,
|
||||
initialCDPCollateral: c("bnb", 10000000000),
|
||||
initialCDPPrincipal: c("usdx", 500000000),
|
||||
numberOfCdps: 100,
|
||||
expectedFeesPerCDP: c("usdx", 54152),
|
||||
expectedTotalPrincipalPerCDP: c("usdx", 500054152),
|
||||
expectedFeesUpdatedTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC).Add(time.Duration(int(time.Second) * 7 * 10000)),
|
||||
expectedTotalPrincipal: i(50005418990),
|
||||
expectedDebtBalance: i(50005418990),
|
||||
expectedStableBalance: i(50005418990),
|
||||
expectedSumOfCDPPrincipal: i(50005415200),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupTest()
|
||||
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
|
||||
|
||||
// setup pricefeed
|
||||
pk := suite.app.GetPriceFeedKeeper()
|
||||
pk.SetPrice(suite.ctx, sdk.AccAddress{}, "bnb:usd", d("17.25"), tc.args.expectedFeesUpdatedTime.Add(time.Second))
|
||||
|
||||
// setup cdp state
|
||||
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, suite.ctx.BlockTime())
|
||||
suite.keeper.SetInterestFactor(suite.ctx, tc.args.ctype, sdk.OneDec())
|
||||
|
||||
// setup account state
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(tc.args.numberOfCdps)
|
||||
for j := 0; j < tc.args.numberOfCdps; j++ {
|
||||
ak := suite.app.GetAccountKeeper()
|
||||
// setup the first account
|
||||
acc := ak.NewAccountWithAddress(suite.ctx, addrs[j])
|
||||
ak.SetAccount(suite.ctx, acc)
|
||||
sk := suite.app.GetSupplyKeeper()
|
||||
err := sk.MintCoins(suite.ctx, types.ModuleName, cs(tc.args.initialCDPCollateral))
|
||||
suite.Require().NoError(err)
|
||||
err = sk.SendCoinsFromModuleToAccount(suite.ctx, types.ModuleName, addrs[j], cs(tc.args.initialCDPCollateral))
|
||||
suite.Require().NoError(err)
|
||||
err = suite.keeper.AddCdp(suite.ctx, addrs[j], tc.args.initialCDPCollateral, tc.args.initialCDPPrincipal, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
// run a number of blocks where CDPs are not synchronized
|
||||
for j := 0; j < tc.args.numberOfBlocks; j++ {
|
||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.blockInterval))
|
||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||
err := suite.keeper.AccumulateInterest(suite.ctx, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
sk := suite.app.GetSupplyKeeper()
|
||||
supplyTotal := sk.GetSupply(suite.ctx).GetTotal()
|
||||
debtSupply := supplyTotal.AmountOf(types.DefaultDebtDenom)
|
||||
usdxSupply := supplyTotal.AmountOf(types.DefaultStableDenom)
|
||||
totalPrincipal := suite.keeper.GetTotalPrincipal(suite.ctx, tc.args.ctype, types.DefaultStableDenom)
|
||||
|
||||
suite.Require().Equal(tc.args.expectedDebtBalance, debtSupply)
|
||||
suite.Require().Equal(tc.args.expectedStableBalance, usdxSupply)
|
||||
suite.Require().Equal(tc.args.expectedTotalPrincipal, totalPrincipal)
|
||||
|
||||
sumOfCDPPrincipal := sdk.ZeroInt()
|
||||
|
||||
for j := 0; j < tc.args.numberOfCdps; j++ {
|
||||
cdp, found := suite.keeper.GetCDP(suite.ctx, tc.args.ctype, uint64(j+1))
|
||||
suite.Require().True(found)
|
||||
cdp = suite.keeper.SynchronizeInterest(suite.ctx, cdp)
|
||||
suite.Require().Equal(tc.args.expectedFeesPerCDP, cdp.AccumulatedFees)
|
||||
suite.Require().Equal(tc.args.expectedTotalPrincipalPerCDP, cdp.GetTotalPrincipal())
|
||||
suite.Require().Equal(tc.args.expectedFeesUpdatedTime, cdp.FeesUpdated)
|
||||
sumOfCDPPrincipal = sumOfCDPPrincipal.Add(cdp.GetTotalPrincipal().Amount)
|
||||
}
|
||||
|
||||
suite.Require().Equal(tc.args.expectedSumOfCDPPrincipal, sumOfCDPPrincipal)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSynchronizeInterest tests the functionality of synchronizing the accumulated interest for CDPs
|
||||
func (suite *InterestTestSuite) TestCalculateCDPInterest() {
|
||||
type args struct {
|
||||
ctype string
|
||||
initialTime time.Time
|
||||
initialCollateral sdk.Coin
|
||||
initialPrincipal sdk.Coin
|
||||
timeElapsed int
|
||||
expectedFees sdk.Coin
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
}
|
||||
|
||||
oneYearInSeconds := 31536000
|
||||
testCases := []test{
|
||||
{
|
||||
"1 year",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000000),
|
||||
initialPrincipal: c("usdx", 100000000000),
|
||||
timeElapsed: oneYearInSeconds,
|
||||
expectedFees: c("usdx", 5000000000),
|
||||
},
|
||||
},
|
||||
{
|
||||
"1 month",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000000),
|
||||
initialPrincipal: c("usdx", 100000000000),
|
||||
timeElapsed: 86400 * 30,
|
||||
expectedFees: c("usdx", 401820189),
|
||||
},
|
||||
},
|
||||
{
|
||||
"7 seconds",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000000),
|
||||
initialPrincipal: c("usdx", 100000000000),
|
||||
timeElapsed: 7,
|
||||
expectedFees: c("usdx", 1083),
|
||||
},
|
||||
},
|
||||
{
|
||||
"7 seconds - fees round to zero",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000),
|
||||
initialPrincipal: c("usdx", 10000000),
|
||||
timeElapsed: 7,
|
||||
expectedFees: c("usdx", 0),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupTest()
|
||||
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
|
||||
|
||||
// setup account state
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
ak := suite.app.GetAccountKeeper()
|
||||
// setup the first account
|
||||
acc := ak.NewAccountWithAddress(suite.ctx, addrs[0])
|
||||
ak.SetAccount(suite.ctx, acc)
|
||||
sk := suite.app.GetSupplyKeeper()
|
||||
err := sk.MintCoins(suite.ctx, types.ModuleName, cs(tc.args.initialCollateral))
|
||||
suite.Require().NoError(err)
|
||||
err = sk.SendCoinsFromModuleToAccount(suite.ctx, types.ModuleName, addrs[0], cs(tc.args.initialCollateral))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// setup pricefeed
|
||||
pk := suite.app.GetPriceFeedKeeper()
|
||||
pk.SetPrice(suite.ctx, sdk.AccAddress{}, "bnb:usd", d("17.25"), tc.args.initialTime.Add(time.Duration(int(time.Second)*tc.args.timeElapsed)))
|
||||
|
||||
// setup cdp state
|
||||
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, suite.ctx.BlockTime())
|
||||
suite.keeper.SetInterestFactor(suite.ctx, tc.args.ctype, sdk.OneDec())
|
||||
err = suite.keeper.AddCdp(suite.ctx, addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed))
|
||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||
err = suite.keeper.AccumulateInterest(suite.ctx, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
cdp, found := suite.keeper.GetCDP(suite.ctx, tc.args.ctype, 1)
|
||||
suite.Require().True(found)
|
||||
|
||||
newInterest := suite.keeper.CalculateNewInterest(suite.ctx, cdp)
|
||||
|
||||
suite.Require().Equal(tc.args.expectedFees, newInterest)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *InterestTestSuite) TestSyncInterestForRiskyCDPs() {
|
||||
type args struct {
|
||||
ctype string
|
||||
numberCdps int
|
||||
slice int
|
||||
initialCollateral sdk.Coin
|
||||
minPrincipal sdk.Coin
|
||||
principalIncrement sdk.Coin
|
||||
initialTime time.Time
|
||||
timeElapsed int
|
||||
expectedCDPs int
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
}
|
||||
|
||||
oneYearInSeconds := 31536000
|
||||
testCases := []test{
|
||||
|
||||
{
|
||||
"1 year",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
numberCdps: 20,
|
||||
slice: 10,
|
||||
initialCollateral: c("bnb", 100000000000),
|
||||
minPrincipal: c("usdx", 100000000),
|
||||
principalIncrement: c("usdx", 10000000),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
timeElapsed: oneYearInSeconds,
|
||||
expectedCDPs: 10,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupTest()
|
||||
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
|
||||
// setup account state
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(tc.args.numberCdps)
|
||||
ak := suite.app.GetAccountKeeper()
|
||||
sk := suite.app.GetSupplyKeeper()
|
||||
for _, addr := range addrs {
|
||||
acc := ak.NewAccountWithAddress(suite.ctx, addr)
|
||||
ak.SetAccount(suite.ctx, acc)
|
||||
err := sk.MintCoins(suite.ctx, types.ModuleName, cs(tc.args.initialCollateral))
|
||||
suite.Require().NoError(err)
|
||||
err = sk.SendCoinsFromModuleToAccount(suite.ctx, types.ModuleName, addr, cs(tc.args.initialCollateral))
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
// setup pricefeed
|
||||
pk := suite.app.GetPriceFeedKeeper()
|
||||
pk.SetPrice(suite.ctx, sdk.AccAddress{}, "bnb:usd", d("20.0"), tc.args.initialTime.Add(time.Duration(int(time.Second)*tc.args.timeElapsed)))
|
||||
|
||||
// setup cdp state
|
||||
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, suite.ctx.BlockTime())
|
||||
suite.keeper.SetInterestFactor(suite.ctx, tc.args.ctype, sdk.OneDec())
|
||||
for j, addr := range addrs {
|
||||
initialPrincipal := tc.args.minPrincipal.Add(c("usdx", int64(j)*tc.args.principalIncrement.Amount.Int64()))
|
||||
err := suite.keeper.AddCdp(suite.ctx, addr, tc.args.initialCollateral, initialPrincipal, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed))
|
||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||
err := suite.keeper.AccumulateInterest(suite.ctx, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
err = suite.keeper.SynchronizeInterestForRiskyCDPs(suite.ctx, i(int64(tc.args.slice)), sdk.MaxSortableDec, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
cdpsUpdatedCount := 0
|
||||
|
||||
for _, addr := range addrs {
|
||||
cdp, found := suite.keeper.GetCdpByOwnerAndCollateralType(suite.ctx, addr, tc.args.ctype)
|
||||
suite.Require().True(found)
|
||||
if cdp.FeesUpdated.Equal(suite.ctx.BlockTime()) {
|
||||
cdpsUpdatedCount += 1
|
||||
}
|
||||
}
|
||||
suite.Require().Equal(tc.args.expectedCDPs, cdpsUpdatedCount)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterestTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(InterestTestSuite))
|
||||
}
|
@ -2,6 +2,7 @@ package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||
@ -20,6 +21,7 @@ type Keeper struct {
|
||||
supplyKeeper types.SupplyKeeper
|
||||
auctionKeeper types.AuctionKeeper
|
||||
accountKeeper types.AccountKeeper
|
||||
hooks types.CDPHooks
|
||||
maccPerms map[string][]string
|
||||
}
|
||||
|
||||
@ -38,10 +40,20 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace,
|
||||
auctionKeeper: ak,
|
||||
supplyKeeper: sk,
|
||||
accountKeeper: ack,
|
||||
hooks: nil,
|
||||
maccPerms: maccs,
|
||||
}
|
||||
}
|
||||
|
||||
// SetHooks sets the cdp keeper hooks
|
||||
func (k *Keeper) SetHooks(hooks types.CDPHooks) *Keeper {
|
||||
if k.hooks != nil {
|
||||
panic("cannot set validator hooks twice")
|
||||
}
|
||||
k.hooks = hooks
|
||||
return k
|
||||
}
|
||||
|
||||
// CdpDenomIndexIterator returns an sdk.Iterator for all cdps with matching collateral denom
|
||||
func (k Keeper) CdpDenomIndexIterator(ctx sdk.Context, collateralType string) sdk.Iterator {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.CdpKeyPrefix)
|
||||
@ -111,22 +123,105 @@ func (k Keeper) IterateCdpsByCollateralRatio(ctx sdk.Context, collateralType str
|
||||
}
|
||||
}
|
||||
|
||||
// SetSavingsRateDistributed sets the SavingsRateDistributed in the store
|
||||
func (k Keeper) SetSavingsRateDistributed(ctx sdk.Context, totalDistributed sdk.Int) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.SavingsRateDistributedKey)
|
||||
bz := k.cdc.MustMarshalBinaryLengthPrefixed(totalDistributed)
|
||||
store.Set([]byte{}, bz)
|
||||
// GetSliceOfCDPsByRatioAndType returns a slice of cdps of size equal to the input cutoffCount
|
||||
// sorted by target ratio in ascending order (ie, the lowest collateral:debt ratio cdps are returned first)
|
||||
func (k Keeper) GetSliceOfCDPsByRatioAndType(ctx sdk.Context, cutoffCount sdk.Int, targetRatio sdk.Dec, collateralType string) (cdps types.CDPs) {
|
||||
count := sdk.ZeroInt()
|
||||
k.IterateCdpsByCollateralRatio(ctx, collateralType, targetRatio, func(cdp types.CDP) bool {
|
||||
cdps = append(cdps, cdp)
|
||||
count = count.Add(sdk.OneInt())
|
||||
if count.GTE(cutoffCount) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
return cdps
|
||||
}
|
||||
|
||||
// GetSavingsRateDistributed gets the SavingsRateDistributed from the store
|
||||
func (k Keeper) GetSavingsRateDistributed(ctx sdk.Context) sdk.Int {
|
||||
savingsRateDistributed := sdk.ZeroInt()
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.SavingsRateDistributedKey)
|
||||
bz := store.Get([]byte{})
|
||||
// GetPreviousAccrualTime returns the last time an individual market accrued interest
|
||||
func (k Keeper) GetPreviousAccrualTime(ctx sdk.Context, ctype string) (time.Time, bool) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousAccrualTimePrefix)
|
||||
bz := store.Get([]byte(ctype))
|
||||
if bz == nil {
|
||||
return savingsRateDistributed
|
||||
return time.Time{}, false
|
||||
}
|
||||
var previousAccrualTime time.Time
|
||||
k.cdc.MustUnmarshalBinaryBare(bz, &previousAccrualTime)
|
||||
return previousAccrualTime, true
|
||||
}
|
||||
|
||||
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &savingsRateDistributed)
|
||||
return savingsRateDistributed
|
||||
// SetPreviousAccrualTime sets the most recent accrual time for a particular market
|
||||
func (k Keeper) SetPreviousAccrualTime(ctx sdk.Context, ctype string, previousAccrualTime time.Time) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousAccrualTimePrefix)
|
||||
bz := k.cdc.MustMarshalBinaryBare(previousAccrualTime)
|
||||
store.Set([]byte(ctype), bz)
|
||||
}
|
||||
|
||||
// GetInterestFactor returns the current interest factor for an individual collateral type
|
||||
func (k Keeper) GetInterestFactor(ctx sdk.Context, ctype string) (sdk.Dec, bool) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.InterestFactorPrefix)
|
||||
bz := store.Get([]byte(ctype))
|
||||
if bz == nil {
|
||||
return sdk.ZeroDec(), false
|
||||
}
|
||||
var interestFactor sdk.Dec
|
||||
k.cdc.MustUnmarshalBinaryBare(bz, &interestFactor)
|
||||
return interestFactor, true
|
||||
}
|
||||
|
||||
// SetInterestFactor sets the current interest factor for an individual collateral type
|
||||
func (k Keeper) SetInterestFactor(ctx sdk.Context, ctype string, interestFactor sdk.Dec) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.InterestFactorPrefix)
|
||||
bz := k.cdc.MustMarshalBinaryBare(interestFactor)
|
||||
store.Set([]byte(ctype), bz)
|
||||
}
|
||||
|
||||
// IncrementTotalPrincipal increments the total amount of debt that has been drawn with that collateral type
|
||||
func (k Keeper) IncrementTotalPrincipal(ctx sdk.Context, collateralType string, principal sdk.Coin) {
|
||||
total := k.GetTotalPrincipal(ctx, collateralType, principal.Denom)
|
||||
total = total.Add(principal.Amount)
|
||||
k.SetTotalPrincipal(ctx, collateralType, principal.Denom, total)
|
||||
}
|
||||
|
||||
// DecrementTotalPrincipal decrements the total amount of debt that has been drawn for a particular collateral type
|
||||
func (k Keeper) DecrementTotalPrincipal(ctx sdk.Context, collateralType string, principal sdk.Coin) {
|
||||
total := k.GetTotalPrincipal(ctx, collateralType, principal.Denom)
|
||||
// NOTE: negative total principal can happen in tests due to rounding errors
|
||||
// in fee calculation
|
||||
total = sdk.MaxInt(total.Sub(principal.Amount), sdk.ZeroInt())
|
||||
k.SetTotalPrincipal(ctx, collateralType, principal.Denom, total)
|
||||
}
|
||||
|
||||
// GetTotalPrincipal returns the total amount of principal that has been drawn for a particular collateral
|
||||
func (k Keeper) GetTotalPrincipal(ctx sdk.Context, collateralType, principalDenom string) (total sdk.Int) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PrincipalKeyPrefix)
|
||||
bz := store.Get([]byte(collateralType + principalDenom))
|
||||
if bz == nil {
|
||||
k.SetTotalPrincipal(ctx, collateralType, principalDenom, sdk.ZeroInt())
|
||||
return sdk.ZeroInt()
|
||||
}
|
||||
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &total)
|
||||
return total
|
||||
}
|
||||
|
||||
// SetTotalPrincipal sets the total amount of principal that has been drawn for the input collateral
|
||||
func (k Keeper) SetTotalPrincipal(ctx sdk.Context, collateralType, principalDenom string, total sdk.Int) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PrincipalKeyPrefix)
|
||||
_, found := k.GetCollateralTypePrefix(ctx, collateralType)
|
||||
if !found {
|
||||
panic(fmt.Sprintf("collateral not found: %s", collateralType))
|
||||
}
|
||||
store.Set([]byte(collateralType+principalDenom), k.cdc.MustMarshalBinaryLengthPrefixed(total))
|
||||
}
|
||||
|
||||
// getModuleAccountCoins gets the total coin balance of this coin currently held by module accounts
|
||||
func (k Keeper) getModuleAccountCoins(ctx sdk.Context, denom string) sdk.Coins {
|
||||
totalModCoinBalance := sdk.NewCoins(sdk.NewCoin(denom, sdk.ZeroInt()))
|
||||
for macc := range k.maccPerms {
|
||||
modCoinBalance := k.supplyKeeper.GetModuleAccount(ctx, macc).GetCoins().AmountOf(denom)
|
||||
if modCoinBalance.IsPositive() {
|
||||
totalModCoinBalance = totalModCoinBalance.Add(sdk.NewCoin(denom, modCoinBalance))
|
||||
}
|
||||
}
|
||||
return totalModCoinBalance
|
||||
}
|
||||
|
@ -34,15 +34,3 @@ func (suite *KeeperTestSuite) ResetChain() {
|
||||
suite.ctx = ctx
|
||||
suite.keeper = keeper
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestGetSetSavingsRateDistributed() {
|
||||
suite.ResetChain()
|
||||
|
||||
// Set savings rate distributed value
|
||||
savingsRateDist := sdk.NewInt(555000555000)
|
||||
suite.keeper.SetSavingsRateDistributed(suite.ctx, savingsRateDist)
|
||||
|
||||
// Check store's savings rate distributed value
|
||||
s := suite.keeper.GetSavingsRateDistributed(suite.ctx)
|
||||
suite.Equal(savingsRateDist, s)
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
@ -32,10 +30,6 @@ func NewQuerier(keeper Keeper) sdk.Querier {
|
||||
return queryGetParams(ctx, req, keeper)
|
||||
case types.QueryGetAccounts:
|
||||
return queryGetAccounts(ctx, req, keeper)
|
||||
case types.QueryGetSavingsRateDistributed:
|
||||
return queryGetSavingsRateDistributed(ctx, req, keeper)
|
||||
case types.QueryGetPreviousSavingsDistributionTime:
|
||||
return queryGetPreviousSavingsDistributionTime(ctx, req, keeper)
|
||||
default:
|
||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint %s", types.ModuleName, path[0])
|
||||
}
|
||||
@ -172,12 +166,10 @@ func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]by
|
||||
func queryGetAccounts(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
|
||||
cdpAccAccount := keeper.supplyKeeper.GetModuleAccount(ctx, types.ModuleName)
|
||||
liquidatorAccAccount := keeper.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc)
|
||||
savingsRateAccAccount := keeper.supplyKeeper.GetModuleAccount(ctx, types.SavingsRateMacc)
|
||||
|
||||
accounts := []supply.ModuleAccount{
|
||||
*cdpAccAccount.(*supply.ModuleAccount),
|
||||
*liquidatorAccAccount.(*supply.ModuleAccount),
|
||||
*savingsRateAccAccount.(*supply.ModuleAccount),
|
||||
}
|
||||
|
||||
// Encode results
|
||||
@ -188,36 +180,6 @@ func queryGetAccounts(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]
|
||||
return bz, nil
|
||||
}
|
||||
|
||||
// query get savings rate distributed in the cdp store
|
||||
func queryGetSavingsRateDistributed(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
|
||||
// Get savings rate distributed
|
||||
savingsRateDist := keeper.GetSavingsRateDistributed(ctx)
|
||||
|
||||
// Encode results
|
||||
bz, err := codec.MarshalJSONIndent(types.ModuleCdc, savingsRateDist)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
|
||||
}
|
||||
return bz, nil
|
||||
}
|
||||
|
||||
// query get savings rate distributed in the cdp store
|
||||
func queryGetPreviousSavingsDistributionTime(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
|
||||
// Get savings rate distributed
|
||||
savingsRateDistTime, found := keeper.GetPreviousSavingsDistribution(ctx)
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("previous distribution time not found")
|
||||
}
|
||||
|
||||
// Encode results
|
||||
bz, err := codec.MarshalJSONIndent(types.ModuleCdc, savingsRateDistTime)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
|
||||
}
|
||||
return bz, nil
|
||||
}
|
||||
|
||||
// query cdps in store and filter by request params
|
||||
func queryGetCdps(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
|
||||
var params types.QueryCdpsParams
|
||||
|
@ -287,7 +287,7 @@ func (suite *QuerierTestSuite) TestQueryAccounts() {
|
||||
|
||||
var accounts []supply.ModuleAccount
|
||||
suite.Require().Nil(supply.ModuleCdc.UnmarshalJSON(bz, &accounts))
|
||||
suite.Require().Equal(3, len(accounts))
|
||||
suite.Require().Equal(2, len(accounts))
|
||||
|
||||
findByName := func(name string) bool {
|
||||
for _, account := range accounts {
|
||||
@ -300,18 +300,6 @@ func (suite *QuerierTestSuite) TestQueryAccounts() {
|
||||
|
||||
suite.Require().True(findByName("cdp"))
|
||||
suite.Require().True(findByName("liquidator"))
|
||||
suite.Require().True(findByName("savings"))
|
||||
}
|
||||
|
||||
func (suite *QuerierTestSuite) TestQuerySavingsRateDistributed() {
|
||||
ctx := suite.ctx.WithIsCheckTx(false)
|
||||
bz, err := suite.querier(ctx, []string{types.QueryGetSavingsRateDistributed}, abci.RequestQuery{})
|
||||
suite.Nil(err)
|
||||
suite.NotNil(bz)
|
||||
|
||||
var distAmount sdk.Int
|
||||
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &distAmount))
|
||||
suite.True(sdk.ZeroInt().Equal(distAmount))
|
||||
}
|
||||
|
||||
func (suite *QuerierTestSuite) TestFindIntersection() {
|
||||
|
@ -1,106 +0,0 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||
|
||||
"github.com/kava-labs/kava/x/cdp/types"
|
||||
)
|
||||
|
||||
// DistributeSavingsRate distributes surplus that has accumulated in the liquidator account to address holding stable coins according the the savings rate
|
||||
func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error {
|
||||
dp, found := k.GetDebtParam(ctx, debtDenom)
|
||||
if !found {
|
||||
return sdkerrors.Wrap(types.ErrDebtNotSupported, debtDenom)
|
||||
}
|
||||
|
||||
savingsRateMacc := k.supplyKeeper.GetModuleAccount(ctx, types.SavingsRateMacc)
|
||||
surplusToDistribute := savingsRateMacc.GetCoins().AmountOf(dp.Denom)
|
||||
if surplusToDistribute.IsZero() {
|
||||
return nil
|
||||
}
|
||||
|
||||
modAccountCoins := k.getModuleAccountCoins(ctx, dp.Denom)
|
||||
totalSupplyLessModAccounts := k.supplyKeeper.GetSupply(ctx).GetTotal().Sub(modAccountCoins)
|
||||
|
||||
// values to use in interest calculation
|
||||
totalSurplus := sdk.NewDecFromInt(surplusToDistribute)
|
||||
totalSupply := sdk.NewDecFromInt(totalSupplyLessModAccounts.AmountOf(debtDenom))
|
||||
|
||||
var iterationErr error
|
||||
// TODO: avoid iterating over all the accounts by keeping the stored stable coin
|
||||
// holders' addresses separately.
|
||||
k.accountKeeper.IterateAccounts(ctx, func(acc authexported.Account) (stop bool) {
|
||||
_, ok := acc.(supplyexported.ModuleAccountI)
|
||||
if ok {
|
||||
// don't distribute savings rate to module accounts
|
||||
return false
|
||||
}
|
||||
|
||||
debtAmount := acc.GetCoins().AmountOf(debtDenom)
|
||||
if !debtAmount.IsPositive() {
|
||||
return false
|
||||
}
|
||||
|
||||
// (balance * rewardToDisribute) / totalSupply
|
||||
// interest is the ratable fraction of savings rate owed to that account, rounded using bankers rounding
|
||||
interest := (sdk.NewDecFromInt(debtAmount).Mul(totalSurplus)).Quo(totalSupply).RoundInt()
|
||||
// sanity check, if we are going to over-distribute due to rounding, distribute only the remaining savings rate that hasn't been distributed.
|
||||
interest = sdk.MinInt(interest, surplusToDistribute)
|
||||
|
||||
// sanity check - don't send saving rate if the rounded amount is zero
|
||||
if !interest.IsPositive() {
|
||||
return false
|
||||
}
|
||||
|
||||
// update total savings rate distributed by surplus to distribute
|
||||
previousSavingsDistributed := k.GetSavingsRateDistributed(ctx)
|
||||
newTotalDistributed := previousSavingsDistributed.Add(interest)
|
||||
k.SetSavingsRateDistributed(ctx, newTotalDistributed)
|
||||
|
||||
interestCoins := sdk.NewCoins(sdk.NewCoin(debtDenom, interest))
|
||||
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.SavingsRateMacc, acc.GetAddress(), interestCoins)
|
||||
if err != nil {
|
||||
iterationErr = err
|
||||
return true
|
||||
}
|
||||
surplusToDistribute = surplusToDistribute.Sub(interest)
|
||||
return false
|
||||
})
|
||||
|
||||
return iterationErr
|
||||
}
|
||||
|
||||
// GetPreviousSavingsDistribution get the time of the previous savings rate distribution
|
||||
func (k Keeper) GetPreviousSavingsDistribution(ctx sdk.Context) (distTime time.Time, found bool) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousDistributionTimeKey)
|
||||
b := store.Get([]byte{})
|
||||
if b == nil {
|
||||
return time.Time{}, false
|
||||
}
|
||||
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &distTime)
|
||||
return distTime, true
|
||||
}
|
||||
|
||||
// SetPreviousSavingsDistribution set the time of the previous block
|
||||
func (k Keeper) SetPreviousSavingsDistribution(ctx sdk.Context, distTime time.Time) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousDistributionTimeKey)
|
||||
store.Set([]byte{}, k.cdc.MustMarshalBinaryLengthPrefixed(distTime))
|
||||
}
|
||||
|
||||
// getModuleAccountCoins gets the total coin balance of this coin currently held by module accounts
|
||||
func (k Keeper) getModuleAccountCoins(ctx sdk.Context, denom string) sdk.Coins {
|
||||
totalModCoinBalance := sdk.NewCoins(sdk.NewCoin(denom, sdk.ZeroInt()))
|
||||
for macc := range k.maccPerms {
|
||||
modCoinBalance := k.supplyKeeper.GetModuleAccount(ctx, macc).GetCoins().AmountOf(denom)
|
||||
if modCoinBalance.IsPositive() {
|
||||
totalModCoinBalance = totalModCoinBalance.Add(sdk.NewCoin(denom, modCoinBalance))
|
||||
}
|
||||
}
|
||||
return totalModCoinBalance
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/cdp/keeper"
|
||||
"github.com/kava-labs/kava/x/cdp/types"
|
||||
)
|
||||
|
||||
type SavingsTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
keeper keeper.Keeper
|
||||
app app.TestApp
|
||||
ctx sdk.Context
|
||||
addrs []sdk.AccAddress
|
||||
amountToDistribute int64
|
||||
}
|
||||
|
||||
func (suite *SavingsTestSuite) SetupTest() {
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(3)
|
||||
authGS := app.NewAuthGenState(
|
||||
addrs,
|
||||
[]sdk.Coins{
|
||||
cs(c("usdx", 100000)), cs(c("usdx", 50000)), cs(c("usdx", 50000)),
|
||||
},
|
||||
)
|
||||
|
||||
tApp.InitializeFromGenesisStates(
|
||||
authGS,
|
||||
NewPricefeedGenStateMulti(),
|
||||
NewCDPGenStateMulti(),
|
||||
)
|
||||
|
||||
sk := tApp.GetSupplyKeeper()
|
||||
macc := sk.GetModuleAccount(ctx, types.SavingsRateMacc)
|
||||
distAmount := int64(10000)
|
||||
err := sk.MintCoins(ctx, macc.GetName(), cs(c("usdx", distAmount)))
|
||||
suite.NoError(err)
|
||||
|
||||
keeper := tApp.GetCDPKeeper()
|
||||
suite.app = tApp
|
||||
suite.keeper = keeper
|
||||
suite.ctx = ctx
|
||||
suite.addrs = addrs
|
||||
suite.amountToDistribute = distAmount
|
||||
}
|
||||
|
||||
func (suite *SavingsTestSuite) TestApplySavingsRate() {
|
||||
preSavingsRateDistAmount := suite.keeper.GetSavingsRateDistributed(suite.ctx)
|
||||
|
||||
err := suite.keeper.DistributeSavingsRate(suite.ctx, "usdx")
|
||||
suite.NoError(err)
|
||||
|
||||
ak := suite.app.GetAccountKeeper()
|
||||
acc0 := ak.GetAccount(suite.ctx, suite.addrs[0])
|
||||
suite.Equal(cs(c("usdx", 105000)), acc0.GetCoins())
|
||||
acc1 := ak.GetAccount(suite.ctx, suite.addrs[1])
|
||||
suite.Equal(cs(c("usdx", 52500)), acc1.GetCoins())
|
||||
acc2 := ak.GetAccount(suite.ctx, suite.addrs[2])
|
||||
suite.Equal(cs(c("usdx", 52500)), acc2.GetCoins())
|
||||
|
||||
sk := suite.app.GetSupplyKeeper()
|
||||
macc := sk.GetModuleAccount(suite.ctx, types.SavingsRateMacc)
|
||||
suite.True(macc.GetCoins().AmountOf("usdx").IsZero())
|
||||
|
||||
expectedPostSavingsRateDistAmount := preSavingsRateDistAmount.Add(sdk.NewInt(suite.amountToDistribute))
|
||||
postSavingsRateDistAmount := suite.keeper.GetSavingsRateDistributed(suite.ctx)
|
||||
suite.True(expectedPostSavingsRateDistAmount.Equal(postSavingsRateDistAmount))
|
||||
}
|
||||
|
||||
func (suite *SavingsTestSuite) TestGetSetPreviousDistributionTime() {
|
||||
now := tmtime.Now()
|
||||
|
||||
_, f := suite.keeper.GetPreviousSavingsDistribution(suite.ctx)
|
||||
suite.Require().False(f) // distr time not set at genesis when the default genesis is used
|
||||
|
||||
suite.NotPanics(func() { suite.keeper.SetPreviousSavingsDistribution(suite.ctx, now) })
|
||||
|
||||
pdt, f := suite.keeper.GetPreviousSavingsDistribution(suite.ctx)
|
||||
suite.True(f)
|
||||
suite.Equal(now, pdt)
|
||||
}
|
||||
|
||||
func (suite *SavingsTestSuite) TestGetSetSavingsRateDistributed() {
|
||||
// Savings rate dist set to 0 when the default genesis is used
|
||||
preSavingsRateDistAmount := suite.keeper.GetSavingsRateDistributed(suite.ctx)
|
||||
suite.True(preSavingsRateDistAmount.Equal(types.DefaultSavingsRateDistributed))
|
||||
|
||||
// Adding new dist amount to existing dist so default genesis value can be updated in the future
|
||||
amountToDistribute := sdk.NewInt(9876543210)
|
||||
newTotalDistributed := preSavingsRateDistAmount.Add(amountToDistribute)
|
||||
|
||||
suite.NotPanics(func() { suite.keeper.SetSavingsRateDistributed(suite.ctx, newTotalDistributed) })
|
||||
|
||||
postSavingsRateDistAmount := suite.keeper.GetSavingsRateDistributed(suite.ctx)
|
||||
suite.Equal(newTotalDistributed, postSavingsRateDistAmount)
|
||||
}
|
||||
|
||||
func TestSavingsTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(SavingsTestSuite))
|
||||
}
|
@ -4,10 +4,33 @@ import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
"github.com/kava-labs/kava/x/cdp/types"
|
||||
)
|
||||
|
||||
// AttemptKeeperLiquidation liquidates the cdp with the input collateral type and owner if it is below the required collateralization ratio
|
||||
// if the cdp is liquidated, the keeper that sent the transaction is rewarded a percentage of the collateral according to that collateral types'
|
||||
// keeper reward percentage.
|
||||
func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper, owner sdk.AccAddress, collateralType string) error {
|
||||
cdp, found := k.GetCdpByOwnerAndCollateralType(ctx, owner, collateralType)
|
||||
if !found {
|
||||
return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", owner, collateralType)
|
||||
}
|
||||
k.hooks.BeforeCDPModified(ctx, cdp)
|
||||
cdp = k.SynchronizeInterest(ctx, cdp)
|
||||
|
||||
err := k.ValidateLiquidation(ctx, cdp.Collateral, cdp.Type, cdp.Principal, cdp.AccumulatedFees)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cdp, err = k.payoutKeeperLiquidationReward(ctx, keeper, cdp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return k.SeizeCollateral(ctx, cdp)
|
||||
}
|
||||
|
||||
// SeizeCollateral liquidates the collateral in the input cdp.
|
||||
// the following operations are performed:
|
||||
// 1. Collateral for all deposits is sent from the cdp module to the liquidator module account
|
||||
@ -79,6 +102,7 @@ func (k Keeper) LiquidateCdps(ctx sdk.Context, marketID string, collateralType s
|
||||
normalizedRatio := sdk.OneDec().Quo(priceDivLiqRatio)
|
||||
cdpsToLiquidate := k.GetAllCdpsByCollateralTypeAndRatio(ctx, collateralType, normalizedRatio)
|
||||
for _, c := range cdpsToLiquidate {
|
||||
k.hooks.BeforeCDPModified(ctx, c)
|
||||
err := k.SeizeCollateral(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -93,7 +117,53 @@ func (k Keeper) ApplyLiquidationPenalty(ctx sdk.Context, collateralType string,
|
||||
return sdk.NewDecFromInt(debt).Mul(penalty).RoundInt()
|
||||
}
|
||||
|
||||
// ValidateLiquidation validate that adding the input principal puts the cdp below the liquidation ratio
|
||||
func (k Keeper) ValidateLiquidation(ctx sdk.Context, collateral sdk.Coin, collateralType string, principal sdk.Coin, fees sdk.Coin) error {
|
||||
collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, collateral, collateralType, principal, fees, spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
liquidationRatio := k.getLiquidationRatio(ctx, collateralType)
|
||||
if collateralizationRatio.GT(liquidationRatio) {
|
||||
return sdkerrors.Wrapf(types.ErrNotLiquidatable, "collateral %s, collateral ratio %s, liquidation ratio %s", collateral.Denom, collateralizationRatio, liquidationRatio)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k Keeper) getModAccountDebt(ctx sdk.Context, accountName string) sdk.Int {
|
||||
macc := k.supplyKeeper.GetModuleAccount(ctx, accountName)
|
||||
return macc.GetCoins().AmountOf(k.GetDebtDenom(ctx))
|
||||
}
|
||||
|
||||
func (k Keeper) payoutKeeperLiquidationReward(ctx sdk.Context, keeper sdk.AccAddress, cdp types.CDP) (types.CDP, error) {
|
||||
collateralParam, found := k.GetCollateral(ctx, cdp.Type)
|
||||
if !found {
|
||||
return types.CDP{}, sdkerrors.Wrapf(types.ErrInvalidCollateral, "%s", cdp.Type)
|
||||
}
|
||||
reward := cdp.Collateral.Amount.ToDec().Mul(collateralParam.KeeperRewardPercentage).RoundInt()
|
||||
rewardCoin := sdk.NewCoin(cdp.Collateral.Denom, reward)
|
||||
paidReward := false
|
||||
deposits := k.GetDeposits(ctx, cdp.ID)
|
||||
for _, dep := range deposits {
|
||||
if dep.Amount.IsGTE(rewardCoin) {
|
||||
dep.Amount = dep.Amount.Sub(rewardCoin)
|
||||
k.SetDeposit(ctx, dep)
|
||||
paidReward = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !paidReward {
|
||||
return cdp, nil
|
||||
}
|
||||
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, keeper, sdk.NewCoins(rewardCoin))
|
||||
if err != nil {
|
||||
return types.CDP{}, err
|
||||
}
|
||||
cdp.Collateral = cdp.Collateral.Sub(rewardCoin)
|
||||
ratio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||
err = k.UpdateCdpAndCollateralRatioIndex(ctx, cdp, ratio)
|
||||
if err != nil {
|
||||
return types.CDP{}, err
|
||||
}
|
||||
return cdp, nil
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package keeper_test
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -205,6 +206,258 @@ func (suite *SeizeTestSuite) TestApplyLiquidationPenalty() {
|
||||
suite.Panics(func() { suite.keeper.ApplyLiquidationPenalty(suite.ctx, "lol-a", i(1000)) })
|
||||
}
|
||||
|
||||
func (suite *SeizeTestSuite) TestKeeperLiquidation() {
|
||||
type args struct {
|
||||
ctype string
|
||||
blockTime time.Time
|
||||
initialPrice sdk.Dec
|
||||
finalPrice sdk.Dec
|
||||
collateral sdk.Coin
|
||||
principal sdk.Coin
|
||||
expectedKeeperCoins sdk.Coins // additional coins (if any) the borrower address should have after successfully liquidating position
|
||||
expectedAuctions auction.Auctions // the auctions we should expect to find have been started
|
||||
}
|
||||
|
||||
type errArgs struct {
|
||||
expectLiquidate bool
|
||||
contains string
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
errArgs errArgs
|
||||
}
|
||||
|
||||
// Set up auction constants
|
||||
layout := "2006-01-02T15:04:05.000Z"
|
||||
endTimeStr := "9000-01-01T00:00:00.000Z"
|
||||
endTime, _ := time.Parse(layout, endTimeStr)
|
||||
addr, _ := sdk.AccAddressFromBech32("kava1ze7y9qwdddejmy7jlw4cymqqlt2wh05yhwmrv2")
|
||||
|
||||
testCases := []test{
|
||||
{
|
||||
"valid liquidation",
|
||||
args{
|
||||
"btc-a",
|
||||
time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
d("20000.00"),
|
||||
d("19000.0"),
|
||||
c("btc", 10000000),
|
||||
c("usdx", 1333330000),
|
||||
cs(c("btc", 100100000), c("xrp", 10000000000)),
|
||||
auction.Auctions{
|
||||
auction.CollateralAuction{
|
||||
BaseAuction: auction.BaseAuction{
|
||||
ID: 1,
|
||||
Initiator: "liquidator",
|
||||
Lot: c("btc", 9900000),
|
||||
Bidder: nil,
|
||||
Bid: c("usdx", 0),
|
||||
HasReceivedBids: false,
|
||||
EndTime: endTime,
|
||||
MaxEndTime: endTime,
|
||||
},
|
||||
CorrespondingDebt: c("debt", 1333330000),
|
||||
MaxBid: c("usdx", 1366663250),
|
||||
LotReturns: auction.WeightedAddresses{[]sdk.AccAddress{addr}, []sdk.Int{sdk.NewInt(9900000)}},
|
||||
},
|
||||
},
|
||||
},
|
||||
errArgs{
|
||||
true,
|
||||
"",
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid - not below collateralization ratio",
|
||||
args{
|
||||
"btc-a",
|
||||
time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
d("20000.00"),
|
||||
d("21000.0"),
|
||||
c("btc", 10000000),
|
||||
c("usdx", 1333330000),
|
||||
cs(),
|
||||
auction.Auctions{},
|
||||
},
|
||||
errArgs{
|
||||
false,
|
||||
"collateral ratio not below liquidation ratio",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupTest()
|
||||
// setup pricefeed
|
||||
pk := suite.app.GetPriceFeedKeeper()
|
||||
_, err := pk.SetPrice(suite.ctx, sdk.AccAddress{}, "btc:usd", tc.args.initialPrice, suite.ctx.BlockTime().Add(time.Hour*24))
|
||||
suite.Require().NoError(err)
|
||||
err = pk.SetCurrentPrices(suite.ctx, "btc:usd")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// setup cdp state
|
||||
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, suite.ctx.BlockTime())
|
||||
suite.keeper.SetInterestFactor(suite.ctx, tc.args.ctype, sdk.OneDec())
|
||||
err = suite.keeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.collateral, tc.args.principal, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// update pricefeed
|
||||
_, err = pk.SetPrice(suite.ctx, sdk.AccAddress{}, "btc:usd", tc.args.finalPrice, suite.ctx.BlockTime().Add(time.Hour*24))
|
||||
suite.Require().NoError(err)
|
||||
err = pk.SetCurrentPrices(suite.ctx, "btc:usd")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
_, found := suite.keeper.GetCdpByOwnerAndCollateralType(suite.ctx, suite.addrs[0], tc.args.ctype)
|
||||
suite.Require().True(found)
|
||||
|
||||
err = suite.keeper.AttemptKeeperLiquidation(suite.ctx, suite.addrs[1], suite.addrs[0], tc.args.ctype)
|
||||
|
||||
if tc.errArgs.expectLiquidate {
|
||||
suite.Require().NoError(err)
|
||||
|
||||
_, found = suite.keeper.GetCdpByOwnerAndCollateralType(suite.ctx, suite.addrs[0], tc.args.ctype)
|
||||
suite.Require().False(found)
|
||||
|
||||
ak := suite.app.GetAuctionKeeper()
|
||||
auctions := ak.GetAllAuctions(suite.ctx)
|
||||
suite.Require().Equal(tc.args.expectedAuctions, auctions)
|
||||
|
||||
ack := suite.app.GetAccountKeeper()
|
||||
keeper := ack.GetAccount(suite.ctx, suite.addrs[1])
|
||||
suite.Require().Equal(tc.args.expectedKeeperCoins, keeper.GetCoins())
|
||||
} else {
|
||||
suite.Require().Error(err)
|
||||
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *SeizeTestSuite) TestBeginBlockerLiquidation() {
|
||||
type args struct {
|
||||
ctype string
|
||||
blockTime time.Time
|
||||
initialPrice sdk.Dec
|
||||
finalPrice sdk.Dec
|
||||
collaterals sdk.Coins
|
||||
principals sdk.Coins
|
||||
expectedAuctions auction.Auctions // the auctions we should expect to find have been started
|
||||
}
|
||||
type errArgs struct {
|
||||
expectLiquidate bool
|
||||
contains string
|
||||
}
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
errArgs errArgs
|
||||
}
|
||||
// Set up auction constants
|
||||
layout := "2006-01-02T15:04:05.000Z"
|
||||
endTimeStr := "9000-01-01T00:00:00.000Z"
|
||||
endTime, _ := time.Parse(layout, endTimeStr)
|
||||
addr, _ := sdk.AccAddressFromBech32("kava1ze7y9qwdddejmy7jlw4cymqqlt2wh05yhwmrv2")
|
||||
|
||||
testCases := []test{
|
||||
{
|
||||
"1 liquidation",
|
||||
args{
|
||||
"btc-a",
|
||||
time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
d("20000.00"),
|
||||
d("10000.00"),
|
||||
sdk.Coins{c("btc", 10000000), c("btc", 10000000)},
|
||||
sdk.Coins{c("usdx", 1000000000), c("usdx", 500000000)},
|
||||
auction.Auctions{
|
||||
auction.CollateralAuction{
|
||||
BaseAuction: auction.BaseAuction{
|
||||
ID: 1,
|
||||
Initiator: "liquidator",
|
||||
Lot: c("btc", 10000000),
|
||||
Bidder: nil,
|
||||
Bid: c("usdx", 0),
|
||||
HasReceivedBids: false,
|
||||
EndTime: endTime,
|
||||
MaxEndTime: endTime,
|
||||
},
|
||||
CorrespondingDebt: c("debt", 1000000000),
|
||||
MaxBid: c("usdx", 1025000000),
|
||||
LotReturns: auction.WeightedAddresses{[]sdk.AccAddress{addr}, []sdk.Int{sdk.NewInt(10000000)}},
|
||||
},
|
||||
},
|
||||
},
|
||||
errArgs{
|
||||
true,
|
||||
"",
|
||||
},
|
||||
},
|
||||
{
|
||||
"no liquidation",
|
||||
args{
|
||||
"btc-a",
|
||||
time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
d("20000.00"),
|
||||
d("10000.00"),
|
||||
sdk.Coins{c("btc", 10000000), c("btc", 10000000)},
|
||||
sdk.Coins{c("usdx", 500000000), c("usdx", 500000000)},
|
||||
auction.Auctions{},
|
||||
},
|
||||
errArgs{
|
||||
false,
|
||||
"collateral ratio not below liquidation ratio",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupTest()
|
||||
// setup pricefeed
|
||||
pk := suite.app.GetPriceFeedKeeper()
|
||||
_, err := pk.SetPrice(suite.ctx, sdk.AccAddress{}, "btc:usd", tc.args.initialPrice, suite.ctx.BlockTime().Add(time.Hour*24))
|
||||
suite.Require().NoError(err)
|
||||
err = pk.SetCurrentPrices(suite.ctx, "btc:usd")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// setup cdp state
|
||||
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, suite.ctx.BlockTime())
|
||||
suite.keeper.SetInterestFactor(suite.ctx, tc.args.ctype, sdk.OneDec())
|
||||
|
||||
for idx, col := range tc.args.collaterals {
|
||||
err := suite.keeper.AddCdp(suite.ctx, suite.addrs[idx], col, tc.args.principals[idx], tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
// update pricefeed
|
||||
_, err = pk.SetPrice(suite.ctx, sdk.AccAddress{}, "btc:usd", tc.args.finalPrice, suite.ctx.BlockTime().Add(time.Hour*24))
|
||||
suite.Require().NoError(err)
|
||||
err = pk.SetCurrentPrices(suite.ctx, "btc:usd")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{Header: suite.ctx.BlockHeader()})
|
||||
ak := suite.app.GetAuctionKeeper()
|
||||
auctions := ak.GetAllAuctions(suite.ctx)
|
||||
if tc.errArgs.expectLiquidate {
|
||||
suite.Require().Equal(tc.args.expectedAuctions, auctions)
|
||||
for _, a := range auctions {
|
||||
ca := a.(auction.CollateralAuction)
|
||||
_, found := suite.keeper.GetCdpByOwnerAndCollateralType(suite.ctx, ca.LotReturns.Addresses[0], tc.args.ctype)
|
||||
suite.Require().False(found)
|
||||
}
|
||||
} else {
|
||||
suite.Require().Equal(0, len(auctions))
|
||||
for idx, _ := range tc.args.collaterals {
|
||||
_, found := suite.keeper.GetCdpByOwnerAndCollateralType(suite.ctx, suite.addrs[idx], tc.args.ctype)
|
||||
suite.Require().True(found)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeizeTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(SeizeTestSuite))
|
||||
}
|
||||
|
@ -8,12 +8,68 @@ import (
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
const (
|
||||
// ModuleName The name that will be used throughout the module
|
||||
ModuleName = "cdp"
|
||||
|
||||
// StoreKey Top level store key where all module items will be stored
|
||||
StoreKey = ModuleName
|
||||
|
||||
// RouterKey Top level router key
|
||||
RouterKey = ModuleName
|
||||
|
||||
// QuerierRoute Top level query string
|
||||
QuerierRoute = ModuleName
|
||||
|
||||
// DefaultParamspace default name for parameter store
|
||||
DefaultParamspace = ModuleName
|
||||
|
||||
// LiquidatorMacc module account for liquidator
|
||||
LiquidatorMacc = "liquidator"
|
||||
|
||||
// SavingsRateMacc module account for savings rate
|
||||
SavingsRateMacc = "savings"
|
||||
)
|
||||
|
||||
// Parameter keys
|
||||
var (
|
||||
stabilityFeeMax = sdk.MustNewDecFromStr("1.000000051034942716") // 500% APR
|
||||
KeyGlobalDebtLimit = []byte("GlobalDebtLimit")
|
||||
KeyCollateralParams = []byte("CollateralParams")
|
||||
KeyDebtParam = []byte("DebtParam")
|
||||
KeyDistributionFrequency = []byte("DistributionFrequency")
|
||||
KeyCircuitBreaker = []byte("CircuitBreaker")
|
||||
KeyDebtThreshold = []byte("DebtThreshold")
|
||||
KeyDebtLot = []byte("DebtLot")
|
||||
KeySurplusThreshold = []byte("SurplusThreshold")
|
||||
KeySurplusLot = []byte("SurplusLot")
|
||||
KeySavingsRateDistributed = []byte("SavingsRateDistributed")
|
||||
DefaultGlobalDebt = sdk.NewCoin(DefaultStableDenom, sdk.ZeroInt())
|
||||
DefaultCircuitBreaker = false
|
||||
DefaultCollateralParams = CollateralParams{}
|
||||
DefaultDebtParam = DebtParam{
|
||||
Denom: "usdx",
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
}
|
||||
DefaultCdpStartingID = uint64(1)
|
||||
DefaultDebtDenom = "debt"
|
||||
DefaultGovDenom = "ukava"
|
||||
DefaultStableDenom = "usdx"
|
||||
DefaultSurplusThreshold = sdk.NewInt(500000000000)
|
||||
DefaultDebtThreshold = sdk.NewInt(100000000000)
|
||||
DefaultSurplusLot = sdk.NewInt(10000000000)
|
||||
DefaultDebtLot = sdk.NewInt(10000000000)
|
||||
DefaultPreviousDistributionTime = tmtime.Canonical(time.Unix(0, 0))
|
||||
DefaultSavingsDistributionFrequency = time.Hour * 12
|
||||
DefaultSavingsRateDistributed = sdk.NewInt(0)
|
||||
minCollateralPrefix = 0
|
||||
maxCollateralPrefix = 255
|
||||
stabilityFeeMax = sdk.MustNewDecFromStr("1.000000051034942716") // 500% APR
|
||||
)
|
||||
|
||||
// CDP is the state of a single collateralized debt position.
|
||||
@ -40,6 +96,19 @@ func NewCDP(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collateralType
|
||||
}
|
||||
}
|
||||
|
||||
// NewCDPWithFees creates a new CDP object, for use during migration
|
||||
func NewCDPWithFees(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collateralType string, principal, fees sdk.Coin, time time.Time) CDP {
|
||||
return CDP{
|
||||
ID: id,
|
||||
Owner: owner,
|
||||
Type: collateralType,
|
||||
Collateral: collateral,
|
||||
Principal: principal,
|
||||
AccumulatedFees: fees,
|
||||
FeesUpdated: time,
|
||||
}
|
||||
}
|
||||
|
||||
func (cdp CDP) Validate() error {
|
||||
if cdp.ID == 0 {
|
||||
return errors.New("cdp id cannot be 0")
|
||||
|
653
x/cdp/legacy/v0_13/types.go
Normal file
653
x/cdp/legacy/v0_13/types.go
Normal file
@ -0,0 +1,653 @@
|
||||
package v0_13
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
// Parameter keys
|
||||
var (
|
||||
KeyGlobalDebtLimit = []byte("GlobalDebtLimit")
|
||||
KeyCollateralParams = []byte("CollateralParams")
|
||||
KeyDebtParam = []byte("DebtParam")
|
||||
KeyCircuitBreaker = []byte("CircuitBreaker")
|
||||
KeyDebtThreshold = []byte("DebtThreshold")
|
||||
KeyDebtLot = []byte("DebtLot")
|
||||
KeySurplusThreshold = []byte("SurplusThreshold")
|
||||
KeySurplusLot = []byte("SurplusLot")
|
||||
DefaultGlobalDebt = sdk.NewCoin(DefaultStableDenom, sdk.ZeroInt())
|
||||
DefaultCircuitBreaker = false
|
||||
DefaultCollateralParams = CollateralParams{}
|
||||
DefaultDebtParam = DebtParam{
|
||||
Denom: "usdx",
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
}
|
||||
DefaultCdpStartingID = uint64(1)
|
||||
DefaultDebtDenom = "debt"
|
||||
DefaultGovDenom = "ukava"
|
||||
DefaultStableDenom = "usdx"
|
||||
DefaultSurplusThreshold = sdk.NewInt(500000000000)
|
||||
DefaultDebtThreshold = sdk.NewInt(100000000000)
|
||||
DefaultSurplusLot = sdk.NewInt(10000000000)
|
||||
DefaultDebtLot = sdk.NewInt(10000000000)
|
||||
DefaultSavingsRateDistributed = sdk.NewInt(0)
|
||||
minCollateralPrefix = 0
|
||||
maxCollateralPrefix = 255
|
||||
stabilityFeeMax = sdk.MustNewDecFromStr("1.000000051034942716") // 500% APR
|
||||
)
|
||||
|
||||
// GenesisState is the state that must be provided at genesis.
|
||||
type GenesisState struct {
|
||||
Params Params `json:"params" yaml:"params"`
|
||||
CDPs CDPs `json:"cdps" yaml:"cdps"`
|
||||
Deposits Deposits `json:"deposits" yaml:"deposits"`
|
||||
StartingCdpID uint64 `json:"starting_cdp_id" yaml:"starting_cdp_id"`
|
||||
DebtDenom string `json:"debt_denom" yaml:"debt_denom"`
|
||||
GovDenom string `json:"gov_denom" yaml:"gov_denom"`
|
||||
PreviousAccumulationTimes GenesisAccumulationTimes `json:"previous_accumulation_times" yaml:"previous_accumulation_times"`
|
||||
TotalPrincipals GenesisTotalPrincipals `json:"total_principals" yaml:"total_principals"`
|
||||
}
|
||||
|
||||
// NewGenesisState returns a new genesis state
|
||||
func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID uint64,
|
||||
debtDenom, govDenom string, prevAccumTimes GenesisAccumulationTimes,
|
||||
totalPrincipals GenesisTotalPrincipals) GenesisState {
|
||||
return GenesisState{
|
||||
Params: params,
|
||||
CDPs: cdps,
|
||||
Deposits: deposits,
|
||||
StartingCdpID: startingCdpID,
|
||||
DebtDenom: debtDenom,
|
||||
GovDenom: govDenom,
|
||||
PreviousAccumulationTimes: prevAccumTimes,
|
||||
TotalPrincipals: totalPrincipals,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs basic validation of genesis data returning an
|
||||
// error for any failed validation criteria.
|
||||
func (gs GenesisState) Validate() error {
|
||||
|
||||
if err := gs.Params.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gs.CDPs.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gs.Deposits.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gs.PreviousAccumulationTimes.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gs.TotalPrincipals.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sdk.ValidateDenom(gs.DebtDenom); err != nil {
|
||||
return fmt.Errorf(fmt.Sprintf("debt denom invalid: %v", err))
|
||||
}
|
||||
|
||||
if err := sdk.ValidateDenom(gs.GovDenom); err != nil {
|
||||
return fmt.Errorf(fmt.Sprintf("gov denom invalid: %v", err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenesisAccumulationTime stores the previous distribution time and its corresponding denom
|
||||
type GenesisAccumulationTime struct {
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
PreviousAccumulationTime time.Time `json:"previous_accumulation_time" yaml:"previous_accumulation_time"`
|
||||
InterestFactor sdk.Dec `json:"interest_factor" yaml:"interest_factor"`
|
||||
}
|
||||
|
||||
// NewGenesisAccumulationTime returns a new GenesisAccumulationTime
|
||||
func NewGenesisAccumulationTime(ctype string, prevTime time.Time, factor sdk.Dec) GenesisAccumulationTime {
|
||||
return GenesisAccumulationTime{
|
||||
CollateralType: ctype,
|
||||
PreviousAccumulationTime: prevTime,
|
||||
InterestFactor: factor,
|
||||
}
|
||||
}
|
||||
|
||||
// GenesisAccumulationTimes slice of GenesisAccumulationTime
|
||||
type GenesisAccumulationTimes []GenesisAccumulationTime
|
||||
|
||||
// Validate performs validation of GenesisAccumulationTimes
|
||||
func (gats GenesisAccumulationTimes) Validate() error {
|
||||
for _, gat := range gats {
|
||||
if err := gat.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate performs validation of GenesisAccumulationTime
|
||||
func (gat GenesisAccumulationTime) Validate() error {
|
||||
if gat.InterestFactor.LT(sdk.OneDec()) {
|
||||
return fmt.Errorf("interest factor should be ≥ 1.0, is %s for %s", gat.InterestFactor, gat.CollateralType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenesisTotalPrincipal stores the total principal and its corresponding collateral type
|
||||
type GenesisTotalPrincipal struct {
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
TotalPrincipal sdk.Int `json:"total_principal" yaml:"total_principal"`
|
||||
}
|
||||
|
||||
// NewGenesisTotalPrincipal returns a new GenesisTotalPrincipal
|
||||
func NewGenesisTotalPrincipal(ctype string, principal sdk.Int) GenesisTotalPrincipal {
|
||||
return GenesisTotalPrincipal{
|
||||
CollateralType: ctype,
|
||||
TotalPrincipal: principal,
|
||||
}
|
||||
}
|
||||
|
||||
// GenesisTotalPrincipals slice of GenesisTotalPrincipal
|
||||
type GenesisTotalPrincipals []GenesisTotalPrincipal
|
||||
|
||||
// Validate performs validation of GenesisTotalPrincipal
|
||||
func (gtp GenesisTotalPrincipal) Validate() error {
|
||||
if gtp.TotalPrincipal.IsNegative() {
|
||||
return fmt.Errorf("total principal should be positive, is %s for %s", gtp.TotalPrincipal, gtp.CollateralType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate performs validation of GenesisTotalPrincipals
|
||||
func (gtps GenesisTotalPrincipals) Validate() error {
|
||||
for _, gtp := range gtps {
|
||||
if err := gtp.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Params governance parameters for cdp module
|
||||
type Params struct {
|
||||
CollateralParams CollateralParams `json:"collateral_params" yaml:"collateral_params"`
|
||||
DebtParam DebtParam `json:"debt_param" yaml:"debt_param"`
|
||||
GlobalDebtLimit sdk.Coin `json:"global_debt_limit" yaml:"global_debt_limit"`
|
||||
SurplusAuctionThreshold sdk.Int `json:"surplus_auction_threshold" yaml:"surplus_auction_threshold"`
|
||||
SurplusAuctionLot sdk.Int `json:"surplus_auction_lot" yaml:"surplus_auction_lot"`
|
||||
DebtAuctionThreshold sdk.Int `json:"debt_auction_threshold" yaml:"debt_auction_threshold"`
|
||||
DebtAuctionLot sdk.Int `json:"debt_auction_lot" yaml:"debt_auction_lot"`
|
||||
CircuitBreaker bool `json:"circuit_breaker" yaml:"circuit_breaker"`
|
||||
}
|
||||
|
||||
// NewParams returns a new params object
|
||||
func NewParams(
|
||||
debtLimit sdk.Coin, collateralParams CollateralParams, debtParam DebtParam, surplusThreshold,
|
||||
surplusLot, debtThreshold, debtLot sdk.Int, breaker bool,
|
||||
) Params {
|
||||
return Params{
|
||||
GlobalDebtLimit: debtLimit,
|
||||
CollateralParams: collateralParams,
|
||||
DebtParam: debtParam,
|
||||
SurplusAuctionThreshold: surplusThreshold,
|
||||
SurplusAuctionLot: surplusLot,
|
||||
DebtAuctionThreshold: debtThreshold,
|
||||
DebtAuctionLot: debtLot,
|
||||
CircuitBreaker: breaker,
|
||||
}
|
||||
}
|
||||
|
||||
// CollateralParam governance parameters for each collateral type within the cdp module
|
||||
type CollateralParam struct {
|
||||
Denom string `json:"denom" yaml:"denom"` // Coin name of collateral type
|
||||
Type string `json:"type" yaml:"type"`
|
||||
LiquidationRatio sdk.Dec `json:"liquidation_ratio" yaml:"liquidation_ratio"` // The ratio (Collateral (priced in stable coin) / Debt) under which a CDP will be liquidated
|
||||
DebtLimit sdk.Coin `json:"debt_limit" yaml:"debt_limit"` // Maximum amount of debt allowed to be drawn from this collateral type
|
||||
StabilityFee sdk.Dec `json:"stability_fee" yaml:"stability_fee"` // per second stability fee for loans opened using this collateral
|
||||
AuctionSize sdk.Int `json:"auction_size" yaml:"auction_size"` // Max amount of collateral to sell off in any one auction.
|
||||
LiquidationPenalty sdk.Dec `json:"liquidation_penalty" yaml:"liquidation_penalty"` // percentage penalty (between [0, 1]) applied to a cdp if it is liquidated
|
||||
Prefix byte `json:"prefix" yaml:"prefix"`
|
||||
SpotMarketID string `json:"spot_market_id" yaml:"spot_market_id"` // marketID of the spot price of the asset from the pricefeed - used for opening CDPs, depositing, withdrawing
|
||||
LiquidationMarketID string `json:"liquidation_market_id" yaml:"liquidation_market_id"` // marketID of the pricefeed used for liquidation
|
||||
KeeperRewardPercentage sdk.Dec `json:"keeper_reward_percentage" yaml:"keeper_reward_percentage"` // the percentage of a CDPs collateral that gets rewarded to a keeper that liquidates the position
|
||||
CheckCollateralizationIndexCount sdk.Int `json:"check_collateralization_index_count" yaml:"check_collateralization_index_count"` // the number of cdps that will be checked for liquidation in the begin blocker
|
||||
ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"` // factor for converting internal units to one base unit of collateral
|
||||
}
|
||||
|
||||
// NewCollateralParam returns a new CollateralParam
|
||||
func NewCollateralParam(
|
||||
denom, ctype string, liqRatio sdk.Dec, debtLimit sdk.Coin, stabilityFee sdk.Dec, auctionSize sdk.Int,
|
||||
liqPenalty sdk.Dec, prefix byte, spotMarketID, liquidationMarketID string, keeperReward sdk.Dec, checkIndexCount sdk.Int, conversionFactor sdk.Int) CollateralParam {
|
||||
return CollateralParam{
|
||||
Denom: denom,
|
||||
Type: ctype,
|
||||
LiquidationRatio: liqRatio,
|
||||
DebtLimit: debtLimit,
|
||||
StabilityFee: stabilityFee,
|
||||
AuctionSize: auctionSize,
|
||||
LiquidationPenalty: liqPenalty,
|
||||
Prefix: prefix,
|
||||
SpotMarketID: spotMarketID,
|
||||
LiquidationMarketID: liquidationMarketID,
|
||||
KeeperRewardPercentage: keeperReward,
|
||||
CheckCollateralizationIndexCount: checkIndexCount,
|
||||
ConversionFactor: conversionFactor,
|
||||
}
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (cp CollateralParam) String() string {
|
||||
return fmt.Sprintf(`Collateral:
|
||||
Denom: %s
|
||||
Type: %s
|
||||
Liquidation Ratio: %s
|
||||
Stability Fee: %s
|
||||
Liquidation Penalty: %s
|
||||
Debt Limit: %s
|
||||
Auction Size: %s
|
||||
Prefix: %b
|
||||
Spot Market ID: %s
|
||||
Liquidation Market ID: %s
|
||||
Keeper Reward Percentage: %s
|
||||
Check Collateralization Count: %s
|
||||
Conversion Factor: %s`,
|
||||
cp.Denom, cp.Type, cp.LiquidationRatio, cp.StabilityFee, cp.LiquidationPenalty,
|
||||
cp.DebtLimit, cp.AuctionSize, cp.Prefix, cp.SpotMarketID, cp.LiquidationMarketID,
|
||||
cp.KeeperRewardPercentage, cp.CheckCollateralizationIndexCount, cp.ConversionFactor)
|
||||
}
|
||||
|
||||
// CollateralParams array of CollateralParam
|
||||
type CollateralParams []CollateralParam
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (cps CollateralParams) String() string {
|
||||
out := "Collateral Params\n"
|
||||
for _, cp := range cps {
|
||||
out += fmt.Sprintf("%s\n", cp)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// DebtParam governance params for debt assets
|
||||
type DebtParam struct {
|
||||
Denom string `json:"denom" yaml:"denom"`
|
||||
ReferenceAsset string `json:"reference_asset" yaml:"reference_asset"`
|
||||
ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"`
|
||||
DebtFloor sdk.Int `json:"debt_floor" yaml:"debt_floor"` // minimum active loan size, used to prevent dust
|
||||
}
|
||||
|
||||
// NewDebtParam returns a new DebtParam
|
||||
func NewDebtParam(denom, refAsset string, conversionFactor, debtFloor sdk.Int) DebtParam {
|
||||
return DebtParam{
|
||||
Denom: denom,
|
||||
ReferenceAsset: refAsset,
|
||||
ConversionFactor: conversionFactor,
|
||||
DebtFloor: debtFloor,
|
||||
}
|
||||
}
|
||||
|
||||
// DebtParams array of DebtParam
|
||||
type DebtParams []DebtParam
|
||||
|
||||
// Validate checks that the parameters have valid values.
|
||||
func (p Params) Validate() error {
|
||||
if err := validateGlobalDebtLimitParam(p.GlobalDebtLimit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateCollateralParams(p.CollateralParams); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateDebtParam(p.DebtParam); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateCircuitBreakerParam(p.CircuitBreaker); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateSurplusAuctionThresholdParam(p.SurplusAuctionThreshold); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateSurplusAuctionLotParam(p.SurplusAuctionLot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateDebtAuctionThresholdParam(p.DebtAuctionThreshold); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateDebtAuctionLotParam(p.DebtAuctionLot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(p.CollateralParams) == 0 { // default value OK
|
||||
return nil
|
||||
}
|
||||
|
||||
if (DebtParam{}) != p.DebtParam {
|
||||
if p.DebtParam.Denom != p.GlobalDebtLimit.Denom {
|
||||
return fmt.Errorf("debt denom %s does not match global debt denom %s",
|
||||
p.DebtParam.Denom, p.GlobalDebtLimit.Denom)
|
||||
}
|
||||
}
|
||||
|
||||
// validate collateral params
|
||||
collateralDupMap := make(map[string]int)
|
||||
prefixDupMap := make(map[int]int)
|
||||
collateralParamsDebtLimit := sdk.ZeroInt()
|
||||
|
||||
for _, cp := range p.CollateralParams {
|
||||
|
||||
prefix := int(cp.Prefix)
|
||||
prefixDupMap[prefix] = 1
|
||||
collateralDupMap[cp.Denom] = 1
|
||||
|
||||
if cp.DebtLimit.Denom != p.GlobalDebtLimit.Denom {
|
||||
return fmt.Errorf("collateral debt limit denom %s does not match global debt limit denom %s",
|
||||
cp.DebtLimit.Denom, p.GlobalDebtLimit.Denom)
|
||||
}
|
||||
|
||||
collateralParamsDebtLimit = collateralParamsDebtLimit.Add(cp.DebtLimit.Amount)
|
||||
|
||||
if cp.DebtLimit.Amount.GT(p.GlobalDebtLimit.Amount) {
|
||||
return fmt.Errorf("collateral debt limit %s exceeds global debt limit: %s", cp.DebtLimit, p.GlobalDebtLimit)
|
||||
}
|
||||
}
|
||||
|
||||
if collateralParamsDebtLimit.GT(p.GlobalDebtLimit.Amount) {
|
||||
return fmt.Errorf("sum of collateral debt limits %s exceeds global debt limit %s",
|
||||
collateralParamsDebtLimit, p.GlobalDebtLimit)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateGlobalDebtLimitParam(i interface{}) error {
|
||||
globalDebtLimit, ok := i.(sdk.Coin)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
if !globalDebtLimit.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "global debt limit %s", globalDebtLimit.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCollateralParams(i interface{}) error {
|
||||
collateralParams, ok := i.(CollateralParams)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
prefixDupMap := make(map[int]bool)
|
||||
typeDupMap := make(map[string]bool)
|
||||
for _, cp := range collateralParams {
|
||||
if err := sdk.ValidateDenom(cp.Denom); err != nil {
|
||||
return fmt.Errorf("collateral denom invalid %s", cp.Denom)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(cp.SpotMarketID) == "" {
|
||||
return fmt.Errorf("spot market id cannot be blank %s", cp)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(cp.Type) == "" {
|
||||
return fmt.Errorf("collateral type cannot be blank %s", cp)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(cp.LiquidationMarketID) == "" {
|
||||
return fmt.Errorf("liquidation market id cannot be blank %s", cp)
|
||||
}
|
||||
|
||||
prefix := int(cp.Prefix)
|
||||
if prefix < minCollateralPrefix || prefix > maxCollateralPrefix {
|
||||
return fmt.Errorf("invalid prefix for collateral denom %s: %b", cp.Denom, cp.Prefix)
|
||||
}
|
||||
|
||||
_, found := prefixDupMap[prefix]
|
||||
if found {
|
||||
return fmt.Errorf("duplicate prefix for collateral denom %s: %v", cp.Denom, []byte{cp.Prefix})
|
||||
}
|
||||
|
||||
prefixDupMap[prefix] = true
|
||||
|
||||
_, found = typeDupMap[cp.Type]
|
||||
if found {
|
||||
return fmt.Errorf("duplicate cdp collateral type: %s", cp.Type)
|
||||
}
|
||||
typeDupMap[cp.Type] = true
|
||||
|
||||
if !cp.DebtLimit.IsValid() {
|
||||
return fmt.Errorf("debt limit for all collaterals should be positive, is %s for %s", cp.DebtLimit, cp.Denom)
|
||||
}
|
||||
|
||||
if cp.LiquidationPenalty.LT(sdk.ZeroDec()) || cp.LiquidationPenalty.GT(sdk.OneDec()) {
|
||||
return fmt.Errorf("liquidation penalty should be between 0 and 1, is %s for %s", cp.LiquidationPenalty, cp.Denom)
|
||||
}
|
||||
if !cp.AuctionSize.IsPositive() {
|
||||
return fmt.Errorf("auction size should be positive, is %s for %s", cp.AuctionSize, cp.Denom)
|
||||
}
|
||||
if cp.StabilityFee.LT(sdk.OneDec()) || cp.StabilityFee.GT(stabilityFeeMax) {
|
||||
return fmt.Errorf("stability fee must be ≥ 1.0, ≤ %s, is %s for %s", stabilityFeeMax, cp.StabilityFee, cp.Denom)
|
||||
}
|
||||
if cp.KeeperRewardPercentage.IsNegative() || cp.KeeperRewardPercentage.GT(sdk.OneDec()) {
|
||||
return fmt.Errorf("keeper reward percentage should be between 0 and 1, is %s for %s", cp.KeeperRewardPercentage, cp.Denom)
|
||||
}
|
||||
if cp.CheckCollateralizationIndexCount.IsNegative() {
|
||||
return fmt.Errorf("keeper reward percentage should be positive, is %s for %s", cp.CheckCollateralizationIndexCount, cp.Denom)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDebtParam(i interface{}) error {
|
||||
debtParam, ok := i.(DebtParam)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
if err := sdk.ValidateDenom(debtParam.Denom); err != nil {
|
||||
return fmt.Errorf("debt denom invalid %s", debtParam.Denom)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCircuitBreakerParam(i interface{}) error {
|
||||
_, ok := i.(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSurplusAuctionThresholdParam(i interface{}) error {
|
||||
sat, ok := i.(sdk.Int)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
if !sat.IsPositive() {
|
||||
return fmt.Errorf("surplus auction threshold should be positive: %s", sat)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSurplusAuctionLotParam(i interface{}) error {
|
||||
sal, ok := i.(sdk.Int)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
if !sal.IsPositive() {
|
||||
return fmt.Errorf("surplus auction lot should be positive: %s", sal)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDebtAuctionThresholdParam(i interface{}) error {
|
||||
dat, ok := i.(sdk.Int)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
if !dat.IsPositive() {
|
||||
return fmt.Errorf("debt auction threshold should be positive: %s", dat)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDebtAuctionLotParam(i interface{}) error {
|
||||
dal, ok := i.(sdk.Int)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
if !dal.IsPositive() {
|
||||
return fmt.Errorf("debt auction lot should be positive: %s", dal)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CDP is the state of a single collateralized debt position.
|
||||
type CDP struct {
|
||||
ID uint64 `json:"id" yaml:"id"` // unique id for cdp
|
||||
Owner sdk.AccAddress `json:"owner" yaml:"owner"` // Account that authorizes changes to the CDP
|
||||
Type string `json:"type" yaml:"type"` // string representing the unique collateral type of the CDP
|
||||
Collateral sdk.Coin `json:"collateral" yaml:"collateral"` // Amount of collateral stored in this CDP
|
||||
Principal sdk.Coin `json:"principal" yaml:"principal"` // Amount of debt drawn using the CDP
|
||||
AccumulatedFees sdk.Coin `json:"accumulated_fees" yaml:"accumulated_fees"` // Fees accumulated since the CDP was opened or debt was last repaid
|
||||
FeesUpdated time.Time `json:"fees_updated" yaml:"fees_updated"` // The time when fees were last updated
|
||||
InterestFactor sdk.Dec `json:"interest_factor" yaml:"interest_factor"` // the interest factor when fees were last calculated for this CDP
|
||||
}
|
||||
|
||||
// NewCDP creates a new CDP object
|
||||
func NewCDP(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collateralType string, principal sdk.Coin, time time.Time, interestFactor sdk.Dec) CDP {
|
||||
fees := sdk.NewCoin(principal.Denom, sdk.ZeroInt())
|
||||
return CDP{
|
||||
ID: id,
|
||||
Owner: owner,
|
||||
Type: collateralType,
|
||||
Collateral: collateral,
|
||||
Principal: principal,
|
||||
AccumulatedFees: fees,
|
||||
FeesUpdated: time,
|
||||
InterestFactor: interestFactor,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCDPWithFees creates a new CDP object, for use during migration
|
||||
func NewCDPWithFees(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collateralType string, principal, fees sdk.Coin, time time.Time, interestFactor sdk.Dec) CDP {
|
||||
return CDP{
|
||||
ID: id,
|
||||
Owner: owner,
|
||||
Type: collateralType,
|
||||
Collateral: collateral,
|
||||
Principal: principal,
|
||||
AccumulatedFees: fees,
|
||||
FeesUpdated: time,
|
||||
InterestFactor: interestFactor,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs a basic validation of the CDP fields.
|
||||
func (cdp CDP) Validate() error {
|
||||
if cdp.ID == 0 {
|
||||
return errors.New("cdp id cannot be 0")
|
||||
}
|
||||
if cdp.Owner.Empty() {
|
||||
return errors.New("cdp owner cannot be empty")
|
||||
}
|
||||
if !cdp.Collateral.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "collateral %s", cdp.Collateral)
|
||||
}
|
||||
if !cdp.Principal.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "principal %s", cdp.Principal)
|
||||
}
|
||||
if !cdp.AccumulatedFees.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "accumulated fees %s", cdp.AccumulatedFees)
|
||||
}
|
||||
if cdp.FeesUpdated.IsZero() {
|
||||
return errors.New("cdp updated fee time cannot be zero")
|
||||
}
|
||||
if strings.TrimSpace(cdp.Type) == "" {
|
||||
return fmt.Errorf("cdp type cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTotalPrincipal returns the total principle for the cdp
|
||||
func (cdp CDP) GetTotalPrincipal() sdk.Coin {
|
||||
return cdp.Principal.Add(cdp.AccumulatedFees)
|
||||
}
|
||||
|
||||
// CDPs a collection of CDP objects
|
||||
type CDPs []CDP
|
||||
|
||||
// Validate validates each CDP
|
||||
func (cdps CDPs) Validate() error {
|
||||
for _, cdp := range cdps {
|
||||
if err := cdp.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deposit defines an amount of coins deposited by an account to a cdp
|
||||
type Deposit struct {
|
||||
CdpID uint64 `json:"cdp_id" yaml:"cdp_id"` // cdpID of the cdp
|
||||
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` // Address of the depositor
|
||||
Amount sdk.Coin `json:"amount" yaml:"amount"` // Deposit amount
|
||||
}
|
||||
|
||||
// NewDeposit creates a new Deposit object
|
||||
func NewDeposit(cdpID uint64, depositor sdk.AccAddress, amount sdk.Coin) Deposit {
|
||||
return Deposit{cdpID, depositor, amount}
|
||||
}
|
||||
|
||||
// Validate performs a basic validation of the deposit fields.
|
||||
func (d Deposit) Validate() error {
|
||||
if d.CdpID == 0 {
|
||||
return errors.New("deposit's cdp id cannot be 0")
|
||||
}
|
||||
if d.Depositor.Empty() {
|
||||
return errors.New("depositor cannot be empty")
|
||||
}
|
||||
if !d.Amount.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "deposit %s", d.Amount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deposits a collection of Deposit objects
|
||||
type Deposits []Deposit
|
||||
|
||||
// Validate validates each deposit
|
||||
func (ds Deposits) Validate() error {
|
||||
for _, d := range ds {
|
||||
if err := d.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -4,12 +4,50 @@ import (
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
const (
|
||||
ModuleName = "cdp"
|
||||
)
|
||||
|
||||
// Parameter keys
|
||||
var (
|
||||
KeyGlobalDebtLimit = []byte("GlobalDebtLimit")
|
||||
KeyCollateralParams = []byte("CollateralParams")
|
||||
KeyDebtParam = []byte("DebtParam")
|
||||
KeyDistributionFrequency = []byte("DistributionFrequency")
|
||||
KeyCircuitBreaker = []byte("CircuitBreaker")
|
||||
KeyDebtThreshold = []byte("DebtThreshold")
|
||||
KeyDebtLot = []byte("DebtLot")
|
||||
KeySurplusThreshold = []byte("SurplusThreshold")
|
||||
KeySurplusLot = []byte("SurplusLot")
|
||||
DefaultGlobalDebt = sdk.NewCoin(DefaultStableDenom, sdk.ZeroInt())
|
||||
DefaultCircuitBreaker = false
|
||||
DefaultCollateralParams = CollateralParams{}
|
||||
DefaultDebtParam = DebtParam{
|
||||
Denom: "usdx",
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
}
|
||||
DefaultCdpStartingID = uint64(1)
|
||||
DefaultDebtDenom = "debt"
|
||||
DefaultGovDenom = "ukava"
|
||||
DefaultStableDenom = "usdx"
|
||||
DefaultSurplusThreshold = sdk.NewInt(500000000000)
|
||||
DefaultDebtThreshold = sdk.NewInt(100000000000)
|
||||
DefaultSurplusLot = sdk.NewInt(10000000000)
|
||||
DefaultDebtLot = sdk.NewInt(10000000000)
|
||||
DefaultPreviousDistributionTime = tmtime.Canonical(time.Unix(0, 0))
|
||||
DefaultSavingsDistributionFrequency = time.Hour * 12
|
||||
minCollateralPrefix = 0
|
||||
maxCollateralPrefix = 255
|
||||
stabilityFeeMax = sdk.MustNewDecFromStr("1.000000051034942716") // 500% APR
|
||||
)
|
||||
|
||||
// CDP is the state of a single collateralized debt position.
|
||||
type CDP struct {
|
||||
ID uint64 `json:"id" yaml:"id"` // unique id for cdp
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/kv"
|
||||
|
||||
@ -54,12 +53,6 @@ func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &totalB)
|
||||
return fmt.Sprintf("%s\n%s", totalA, totalB)
|
||||
|
||||
case bytes.Equal(kvA.Key[:1], types.PreviousDistributionTimeKey):
|
||||
var timeA, timeB time.Time
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &timeA)
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &timeB)
|
||||
return fmt.Sprintf("%s\n%s", timeA, timeB)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ func TestDecodeDistributionStore(t *testing.T) {
|
||||
deposit := types.Deposit{CdpID: 1, Amount: oneCoins}
|
||||
principal := sdk.OneInt()
|
||||
prevDistTime := time.Now().UTC()
|
||||
cdp := types.CDP{ID: 1, FeesUpdated: prevDistTime, Collateral: oneCoins, Principal: oneCoins, AccumulatedFees: oneCoins}
|
||||
cdp := types.CDP{ID: 1, FeesUpdated: prevDistTime, Collateral: oneCoins, Principal: oneCoins, AccumulatedFees: oneCoins, InterestFactor: sdk.OneDec()}
|
||||
|
||||
kvPairs := kv.Pairs{
|
||||
kv.Pair{Key: types.CdpIDKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(cdpIds)},
|
||||
@ -43,7 +43,6 @@ func TestDecodeDistributionStore(t *testing.T) {
|
||||
kv.Pair{Key: []byte(types.GovDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)},
|
||||
kv.Pair{Key: []byte(types.DepositKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(deposit)},
|
||||
kv.Pair{Key: []byte(types.PrincipalKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(principal)},
|
||||
kv.Pair{Key: []byte(types.PreviousDistributionTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevDistTime)},
|
||||
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
|
||||
}
|
||||
|
||||
@ -59,7 +58,6 @@ func TestDecodeDistributionStore(t *testing.T) {
|
||||
{"GovDenom", fmt.Sprintf("%s\n%s", denom, denom)},
|
||||
{"DepositKeyPrefix", fmt.Sprintf("%v\n%v", deposit, deposit)},
|
||||
{"Principal", fmt.Sprintf("%v\n%v", principal, principal)},
|
||||
{"PreviousDistributionTime", fmt.Sprintf("%s\n%s", prevDistTime, prevDistTime)},
|
||||
{"other", ""},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
|
@ -75,7 +75,6 @@ func randomCdpGenState(selection int) types.GenesisState {
|
||||
SurplusAuctionLot: types.DefaultSurplusLot,
|
||||
DebtAuctionLot: types.DefaultDebtLot,
|
||||
DebtAuctionThreshold: types.DefaultDebtThreshold,
|
||||
SavingsDistributionFrequency: types.DefaultSavingsDistributionFrequency,
|
||||
CollateralParams: types.CollateralParams{
|
||||
{
|
||||
Denom: "xrp",
|
||||
@ -122,14 +121,12 @@ func randomCdpGenState(selection int) types.GenesisState {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
},
|
||||
StartingCdpID: types.DefaultCdpStartingID,
|
||||
DebtDenom: types.DefaultDebtDenom,
|
||||
GovDenom: types.DefaultGovDenom,
|
||||
CDPs: types.CDPs{},
|
||||
PreviousDistributionTime: types.DefaultPreviousDistributionTime,
|
||||
}
|
||||
case 1:
|
||||
return types.GenesisState{
|
||||
@ -139,7 +136,6 @@ func randomCdpGenState(selection int) types.GenesisState {
|
||||
DebtAuctionThreshold: types.DefaultDebtThreshold,
|
||||
SurplusAuctionLot: types.DefaultSurplusLot,
|
||||
DebtAuctionLot: types.DefaultDebtLot,
|
||||
SavingsDistributionFrequency: types.DefaultSavingsDistributionFrequency,
|
||||
CollateralParams: types.CollateralParams{
|
||||
{
|
||||
Denom: "bnb",
|
||||
@ -160,14 +156,12 @@ func randomCdpGenState(selection int) types.GenesisState {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
},
|
||||
StartingCdpID: types.DefaultCdpStartingID,
|
||||
DebtDenom: types.DefaultDebtDenom,
|
||||
GovDenom: types.DefaultGovDenom,
|
||||
CDPs: types.CDPs{},
|
||||
PreviousDistributionTime: types.DefaultPreviousDistributionTime,
|
||||
}
|
||||
default:
|
||||
panic("invalid genesis state selector")
|
||||
|
@ -184,12 +184,12 @@ func SimulateMsgCdp(ak types.AccountKeeper, k keeper.Keeper, pfk types.Pricefeed
|
||||
if shouldDraw(r) {
|
||||
collateralShifted := ShiftDec(sdk.NewDecFromInt(existingCDP.Collateral.Amount), randCollateralParam.ConversionFactor.Neg())
|
||||
collateralValue := collateralShifted.Mul(priceShifted)
|
||||
newFeesAccumulated := k.CalculateFees(ctx, existingCDP.Principal, sdk.NewInt(ctx.BlockTime().Unix()-existingCDP.FeesUpdated.Unix()), randCollateralParam.Type).Amount
|
||||
totalFees := existingCDP.AccumulatedFees.Amount.Add(newFeesAccumulated)
|
||||
newFeesAccumulated := k.CalculateNewInterest(ctx, existingCDP)
|
||||
totalFees := existingCDP.AccumulatedFees.Add(newFeesAccumulated)
|
||||
// given the current collateral value, calculate how much debt we could add while maintaining a valid liquidation ratio
|
||||
debt := existingCDP.Principal.Amount.Add(totalFees)
|
||||
debt := existingCDP.Principal.Add(totalFees)
|
||||
maxTotalDebt := collateralValue.Quo(randCollateralParam.LiquidationRatio)
|
||||
maxDebt := (maxTotalDebt.Sub(sdk.NewDecFromInt(debt))).Mul(sdk.MustNewDecFromStr("0.95")).TruncateInt()
|
||||
maxDebt := (maxTotalDebt.Sub(sdk.NewDecFromInt(debt.Amount))).Mul(sdk.MustNewDecFromStr("0.95")).TruncateInt()
|
||||
if maxDebt.LTE(sdk.OneInt()) {
|
||||
// debt in cdp is maxed out
|
||||
return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "cdp debt maxed out, cannot draw more debt", false, nil), nil, nil
|
||||
|
@ -26,10 +26,12 @@ The CDP's collateral always equal to the total of the deposits.
|
||||
type CDP struct {
|
||||
ID uint64
|
||||
Owner sdk.AccAddress
|
||||
Type string
|
||||
Collateral sdk.Coin
|
||||
Principal sdk.Coin
|
||||
AccumulatedFees sdk.Coin
|
||||
FeesUpdated time.Time
|
||||
InterestFactor sdk.Dec
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -17,12 +17,13 @@ type CDP struct {
|
||||
Type string `json:"type" yaml:"type"` // string representing the unique collateral type of the CDP
|
||||
Collateral sdk.Coin `json:"collateral" yaml:"collateral"` // Amount of collateral stored in this CDP
|
||||
Principal sdk.Coin `json:"principal" yaml:"principal"` // Amount of debt drawn using the CDP
|
||||
AccumulatedFees sdk.Coin `json:"accumulated_fees" yaml:"accumulated_fees"` // Fees accumulated since the CDP was opened or debt was last repayed
|
||||
FeesUpdated time.Time `json:"fees_updated" yaml:"fees_updated"` // Amount of stable coin drawn from this CDP
|
||||
AccumulatedFees sdk.Coin `json:"accumulated_fees" yaml:"accumulated_fees"` // Fees accumulated since the CDP was opened or debt was last repaid
|
||||
FeesUpdated time.Time `json:"fees_updated" yaml:"fees_updated"` // The time when fees were last updated
|
||||
InterestFactor sdk.Dec `json:"interest_factor" yaml:"interest_factor"` // the interest factor when fees were last calculated for this CDP
|
||||
}
|
||||
|
||||
// NewCDP creates a new CDP object
|
||||
func NewCDP(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collateralType string, principal sdk.Coin, time time.Time) CDP {
|
||||
func NewCDP(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collateralType string, principal sdk.Coin, time time.Time, interestFactor sdk.Dec) CDP {
|
||||
fees := sdk.NewCoin(principal.Denom, sdk.ZeroInt())
|
||||
return CDP{
|
||||
ID: id,
|
||||
@ -32,11 +33,12 @@ func NewCDP(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collateralType
|
||||
Principal: principal,
|
||||
AccumulatedFees: fees,
|
||||
FeesUpdated: time,
|
||||
InterestFactor: interestFactor,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCDPWithFees creates a new CDP object, for use during migration
|
||||
func NewCDPWithFees(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collateralType string, principal, fees sdk.Coin, time time.Time) CDP {
|
||||
func NewCDPWithFees(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collateralType string, principal, fees sdk.Coin, time time.Time, interestFactor sdk.Dec) CDP {
|
||||
return CDP{
|
||||
ID: id,
|
||||
Owner: owner,
|
||||
@ -45,6 +47,7 @@ func NewCDPWithFees(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collat
|
||||
Principal: principal,
|
||||
AccumulatedFees: fees,
|
||||
FeesUpdated: time,
|
||||
InterestFactor: interestFactor,
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +60,8 @@ func (cdp CDP) String() string {
|
||||
Collateral: %s
|
||||
Principal: %s
|
||||
AccumulatedFees: %s
|
||||
Fees Last Updated: %s`,
|
||||
Fees Last Updated: %s
|
||||
Interest Factor: %s`,
|
||||
cdp.Owner,
|
||||
cdp.ID,
|
||||
cdp.Type,
|
||||
@ -65,6 +69,7 @@ func (cdp CDP) String() string {
|
||||
cdp.Principal,
|
||||
cdp.AccumulatedFees,
|
||||
cdp.FeesUpdated,
|
||||
cdp.InterestFactor,
|
||||
))
|
||||
}
|
||||
|
||||
@ -139,6 +144,7 @@ func NewAugmentedCDP(cdp CDP, collateralValue sdk.Coin, collateralizationRatio s
|
||||
Principal: cdp.Principal,
|
||||
AccumulatedFees: cdp.AccumulatedFees,
|
||||
FeesUpdated: cdp.FeesUpdated,
|
||||
InterestFactor: cdp.InterestFactor,
|
||||
},
|
||||
CollateralValue: collateralValue,
|
||||
CollateralizationRatio: collateralizationRatio,
|
||||
@ -157,6 +163,7 @@ func (augCDP AugmentedCDP) String() string {
|
||||
Principal: %s
|
||||
Fees: %s
|
||||
Fees Last Updated: %s
|
||||
Interest Factor: %s
|
||||
Collateralization ratio: %s`,
|
||||
augCDP.Owner,
|
||||
augCDP.ID,
|
||||
@ -166,6 +173,7 @@ func (augCDP AugmentedCDP) String() string {
|
||||
augCDP.Principal,
|
||||
augCDP.AccumulatedFees,
|
||||
augCDP.FeesUpdated,
|
||||
augCDP.InterestFactor,
|
||||
augCDP.CollateralizationRatio,
|
||||
))
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
||||
}{
|
||||
{
|
||||
name: "valid cdp",
|
||||
cdp: types.NewCDP(1, suite.addrs[0], sdk.NewInt64Coin("bnb", 100000), "bnb-a", sdk.NewInt64Coin("usdx", 100000), tmtime.Now()),
|
||||
cdp: types.NewCDP(1, suite.addrs[0], sdk.NewInt64Coin("bnb", 100000), "bnb-a", sdk.NewInt64Coin("usdx", 100000), tmtime.Now(), sdk.OneDec()),
|
||||
errArgs: errArgs{
|
||||
expectPass: true,
|
||||
contains: "",
|
||||
@ -50,7 +50,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
||||
},
|
||||
{
|
||||
name: "invalid cdp id",
|
||||
cdp: types.NewCDP(0, suite.addrs[0], sdk.NewInt64Coin("bnb", 100000), "bnb-a", sdk.NewInt64Coin("usdx", 100000), tmtime.Now()),
|
||||
cdp: types.NewCDP(0, suite.addrs[0], sdk.NewInt64Coin("bnb", 100000), "bnb-a", sdk.NewInt64Coin("usdx", 100000), tmtime.Now(), sdk.OneDec()),
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "cdp id cannot be 0",
|
||||
@ -58,7 +58,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
||||
},
|
||||
{
|
||||
name: "invalid collateral",
|
||||
cdp: types.CDP{1, suite.addrs[0], "bnb-a", sdk.Coin{"", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(0)}, tmtime.Now()},
|
||||
cdp: types.CDP{1, suite.addrs[0], "bnb-a", sdk.Coin{"", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(0)}, tmtime.Now(), sdk.OneDec()},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "invalid coins: collateral",
|
||||
@ -66,7 +66,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
||||
},
|
||||
{
|
||||
name: "invalid prinicpal",
|
||||
cdp: types.CDP{1, suite.addrs[0], "xrp-a", sdk.Coin{"xrp", sdk.NewInt(100)}, sdk.Coin{"", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(0)}, tmtime.Now()},
|
||||
cdp: types.CDP{1, suite.addrs[0], "xrp-a", sdk.Coin{"xrp", sdk.NewInt(100)}, sdk.Coin{"", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(0)}, tmtime.Now(), sdk.OneDec()},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "invalid coins: principal",
|
||||
@ -74,7 +74,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
||||
},
|
||||
{
|
||||
name: "invalid fees",
|
||||
cdp: types.CDP{1, suite.addrs[0], "xrp-a", sdk.Coin{"xrp", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(100)}, sdk.Coin{"", sdk.NewInt(0)}, tmtime.Now()},
|
||||
cdp: types.CDP{1, suite.addrs[0], "xrp-a", sdk.Coin{"xrp", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(100)}, sdk.Coin{"", sdk.NewInt(0)}, tmtime.Now(), sdk.OneDec()},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "invalid coins: accumulated fees",
|
||||
@ -82,7 +82,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
||||
},
|
||||
{
|
||||
name: "invalid fees updated",
|
||||
cdp: types.CDP{1, suite.addrs[0], "xrp-a", sdk.Coin{"xrp", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(0)}, time.Time{}},
|
||||
cdp: types.CDP{1, suite.addrs[0], "xrp-a", sdk.Coin{"xrp", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(0)}, time.Time{}, sdk.OneDec()},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "cdp updated fee time cannot be zero",
|
||||
@ -90,7 +90,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
||||
},
|
||||
{
|
||||
name: "invalid type",
|
||||
cdp: types.CDP{1, suite.addrs[0], "", sdk.Coin{"xrp", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(0)}, tmtime.Now()},
|
||||
cdp: types.CDP{1, suite.addrs[0], "", sdk.Coin{"xrp", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(100)}, sdk.Coin{"usdx", sdk.NewInt(0)}, tmtime.Now(), sdk.OneDec()},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "cdp type cannot be empty",
|
||||
|
@ -45,4 +45,10 @@ var (
|
||||
ErrPricefeedDown = sdkerrors.Register(ModuleName, 19, "no price found for collateral")
|
||||
// ErrInvalidCollateral error for when the input collateral denom does not match the expected collateral denom
|
||||
ErrInvalidCollateral = sdkerrors.Register(ModuleName, 20, "invalid collateral for input collateral type")
|
||||
// ErrAccountNotFound error for when no account is found for an input address
|
||||
ErrAccountNotFound = sdkerrors.Register(ModuleName, 21, "account not found")
|
||||
// ErrInsufficientBalance error for when an account does not have enough funds
|
||||
ErrInsufficientBalance = sdkerrors.Register(ModuleName, 22, "insufficient balance")
|
||||
// ErrNotLiquidatable error for when an cdp is not liquidatable
|
||||
ErrNotLiquidatable = sdkerrors.Register(ModuleName, 23, "cdp collateral ratio not below liquidation ratio")
|
||||
)
|
||||
|
@ -48,3 +48,9 @@ type AccountKeeper interface {
|
||||
IterateAccounts(ctx sdk.Context, cb func(account authexported.Account) (stop bool))
|
||||
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
|
||||
}
|
||||
|
||||
// CDPHooks event hooks for other keepers to run code in response to CDP modifications
|
||||
type CDPHooks interface {
|
||||
AfterCDPCreated(ctx sdk.Context, cdp CDP)
|
||||
BeforeCDPModified(ctx sdk.Context, cdp CDP)
|
||||
}
|
||||
|
@ -16,13 +16,14 @@ type GenesisState struct {
|
||||
StartingCdpID uint64 `json:"starting_cdp_id" yaml:"starting_cdp_id"`
|
||||
DebtDenom string `json:"debt_denom" yaml:"debt_denom"`
|
||||
GovDenom string `json:"gov_denom" yaml:"gov_denom"`
|
||||
PreviousDistributionTime time.Time `json:"previous_distribution_time" yaml:"previous_distribution_time"`
|
||||
SavingsRateDistributed sdk.Int `json:"savings_rate_distributed" yaml:"savings_rate_distributed"`
|
||||
PreviousAccumulationTimes GenesisAccumulationTimes `json:"previous_accumulation_times" yaml:"previous_accumulation_times"`
|
||||
TotalPrincipals GenesisTotalPrincipals `json:"total_principals" yaml:"total_principals"`
|
||||
}
|
||||
|
||||
// NewGenesisState returns a new genesis state
|
||||
func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID uint64,
|
||||
debtDenom, govDenom string, previousDistTime time.Time, savingsRateDist sdk.Int) GenesisState {
|
||||
debtDenom, govDenom string, prevAccumTimes GenesisAccumulationTimes,
|
||||
totalPrincipals GenesisTotalPrincipals) GenesisState {
|
||||
return GenesisState{
|
||||
Params: params,
|
||||
CDPs: cdps,
|
||||
@ -30,8 +31,8 @@ func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID
|
||||
StartingCdpID: startingCdpID,
|
||||
DebtDenom: debtDenom,
|
||||
GovDenom: govDenom,
|
||||
PreviousDistributionTime: previousDistTime,
|
||||
SavingsRateDistributed: savingsRateDist,
|
||||
PreviousAccumulationTimes: prevAccumTimes,
|
||||
TotalPrincipals: totalPrincipals,
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,8 +45,8 @@ func DefaultGenesisState() GenesisState {
|
||||
DefaultCdpStartingID,
|
||||
DefaultDebtDenom,
|
||||
DefaultGovDenom,
|
||||
DefaultPreviousDistributionTime,
|
||||
DefaultSavingsRateDistributed,
|
||||
GenesisAccumulationTimes{},
|
||||
GenesisTotalPrincipals{},
|
||||
)
|
||||
}
|
||||
|
||||
@ -65,11 +66,11 @@ func (gs GenesisState) Validate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if gs.PreviousDistributionTime.IsZero() {
|
||||
return fmt.Errorf("previous distribution time not set")
|
||||
if err := gs.PreviousAccumulationTimes.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateSavingsRateDistributed(gs.SavingsRateDistributed); err != nil {
|
||||
if err := gs.TotalPrincipals.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -108,3 +109,75 @@ func (gs GenesisState) Equal(gs2 GenesisState) bool {
|
||||
func (gs GenesisState) IsEmpty() bool {
|
||||
return gs.Equal(GenesisState{})
|
||||
}
|
||||
|
||||
// GenesisTotalPrincipal stores the total principal and its corresponding collateral type
|
||||
type GenesisTotalPrincipal struct {
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
TotalPrincipal sdk.Int `json:"total_principal" yaml:"total_principal"`
|
||||
}
|
||||
|
||||
// NewGenesisTotalPrincipal returns a new GenesisTotalPrincipal
|
||||
func NewGenesisTotalPrincipal(ctype string, principal sdk.Int) GenesisTotalPrincipal {
|
||||
return GenesisTotalPrincipal{
|
||||
CollateralType: ctype,
|
||||
TotalPrincipal: principal,
|
||||
}
|
||||
}
|
||||
|
||||
// GenesisTotalPrincipals slice of GenesisTotalPrincipal
|
||||
type GenesisTotalPrincipals []GenesisTotalPrincipal
|
||||
|
||||
// Validate performs validation of GenesisTotalPrincipal
|
||||
func (gtp GenesisTotalPrincipal) Validate() error {
|
||||
if gtp.TotalPrincipal.IsNegative() {
|
||||
return fmt.Errorf("total principal should be positive, is %s for %s", gtp.TotalPrincipal, gtp.CollateralType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate performs validation of GenesisTotalPrincipals
|
||||
func (gtps GenesisTotalPrincipals) Validate() error {
|
||||
for _, gtp := range gtps {
|
||||
if err := gtp.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenesisAccumulationTime stores the previous distribution time and its corresponding denom
|
||||
type GenesisAccumulationTime struct {
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
PreviousAccumulationTime time.Time `json:"previous_accumulation_time" yaml:"previous_accumulation_time"`
|
||||
InterestFactor sdk.Dec `json:"interest_factor" yaml:"interest_factor"`
|
||||
}
|
||||
|
||||
// NewGenesisAccumulationTime returns a new GenesisAccumulationTime
|
||||
func NewGenesisAccumulationTime(ctype string, prevTime time.Time, factor sdk.Dec) GenesisAccumulationTime {
|
||||
return GenesisAccumulationTime{
|
||||
CollateralType: ctype,
|
||||
PreviousAccumulationTime: prevTime,
|
||||
InterestFactor: factor,
|
||||
}
|
||||
}
|
||||
|
||||
// GenesisAccumulationTimes slice of GenesisAccumulationTime
|
||||
type GenesisAccumulationTimes []GenesisAccumulationTime
|
||||
|
||||
// Validate performs validation of GenesisAccumulationTimes
|
||||
func (gats GenesisAccumulationTimes) Validate() error {
|
||||
for _, gat := range gats {
|
||||
if err := gat.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate performs validation of GenesisAccumulationTime
|
||||
func (gat GenesisAccumulationTime) Validate() error {
|
||||
if gat.InterestFactor.LT(sdk.OneDec()) {
|
||||
return fmt.Errorf("interest factor should be ≥ 1.0, is %s for %s", gat.InterestFactor, gat.CollateralType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
25
x/cdp/types/hooks.go
Normal file
25
x/cdp/types/hooks.go
Normal file
@ -0,0 +1,25 @@
|
||||
package types
|
||||
|
||||
import sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
// MultiCDPHooks combine multiple cdp hooks, all hook functions are run in array sequence
|
||||
type MultiCDPHooks []CDPHooks
|
||||
|
||||
// NewMultiCDPHooks returns a new MultiCDPHooks
|
||||
func NewMultiCDPHooks(hooks ...CDPHooks) MultiCDPHooks {
|
||||
return hooks
|
||||
}
|
||||
|
||||
// BeforeCDPModified runs before a cdp is modified
|
||||
func (h MultiCDPHooks) BeforeCDPModified(ctx sdk.Context, cdp CDP) {
|
||||
for i := range h {
|
||||
h[i].BeforeCDPModified(ctx, cdp)
|
||||
}
|
||||
}
|
||||
|
||||
// AfterCDPCreated runs before a cdp is created
|
||||
func (h MultiCDPHooks) AfterCDPCreated(ctx sdk.Context, cdp CDP) {
|
||||
for i := range h {
|
||||
h[i].AfterCDPCreated(ctx, cdp)
|
||||
}
|
||||
}
|
@ -25,9 +25,6 @@ const (
|
||||
|
||||
// LiquidatorMacc module account for liquidator
|
||||
LiquidatorMacc = "liquidator"
|
||||
|
||||
// SavingsRateMacc module account for savings rate
|
||||
SavingsRateMacc = "savings"
|
||||
)
|
||||
|
||||
var sep = []byte(":")
|
||||
@ -51,17 +48,17 @@ var sep = []byte(":")
|
||||
|
||||
// KVStore key prefixes
|
||||
var (
|
||||
CdpIDKeyPrefix = []byte{0x00}
|
||||
CdpKeyPrefix = []byte{0x01}
|
||||
CollateralRatioIndexPrefix = []byte{0x02}
|
||||
CdpIDKey = []byte{0x03}
|
||||
DebtDenomKey = []byte{0x04}
|
||||
GovDenomKey = []byte{0x05}
|
||||
DepositKeyPrefix = []byte{0x06}
|
||||
PrincipalKeyPrefix = []byte{0x07}
|
||||
PreviousDistributionTimeKey = []byte{0x08}
|
||||
PricefeedStatusKeyPrefix = []byte{0x09}
|
||||
SavingsRateDistributedKey = []byte{0x10}
|
||||
CdpIDKeyPrefix = []byte{0x01}
|
||||
CdpKeyPrefix = []byte{0x02}
|
||||
CollateralRatioIndexPrefix = []byte{0x03}
|
||||
CdpIDKey = []byte{0x04}
|
||||
DebtDenomKey = []byte{0x05}
|
||||
GovDenomKey = []byte{0x06}
|
||||
DepositKeyPrefix = []byte{0x07}
|
||||
PrincipalKeyPrefix = []byte{0x08}
|
||||
PricefeedStatusKeyPrefix = []byte{0x10}
|
||||
PreviousAccrualTimePrefix = []byte{0x12}
|
||||
InterestFactorPrefix = []byte{0x13}
|
||||
)
|
||||
|
||||
// GetCdpIDBytes returns the byte representation of the cdpID
|
||||
|
@ -314,3 +314,59 @@ func (msg MsgRepayDebt) String() string {
|
||||
Payment: %s
|
||||
`, msg.Sender, msg.CollateralType, msg.Payment)
|
||||
}
|
||||
|
||||
// MsgLiquidate attempts to liquidate a borrower's cdp
|
||||
type MsgLiquidate struct {
|
||||
Keeper sdk.AccAddress `json:"keeper" yaml:"keeper"`
|
||||
Borrower sdk.AccAddress `json:"borrower" yaml:"borrower"`
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
}
|
||||
|
||||
// NewMsgLiquidate returns a new MsgLiquidate
|
||||
func NewMsgLiquidate(keeper, borrower sdk.AccAddress, ctype string) MsgLiquidate {
|
||||
return MsgLiquidate{
|
||||
Keeper: keeper,
|
||||
Borrower: borrower,
|
||||
CollateralType: ctype,
|
||||
}
|
||||
}
|
||||
|
||||
// Route return the message type used for routing the message.
|
||||
func (msg MsgLiquidate) Route() string { return RouterKey }
|
||||
|
||||
// Type returns a human-readable string for the message, intended for utilization within tags.
|
||||
func (msg MsgLiquidate) Type() string { return "liquidate" }
|
||||
|
||||
// ValidateBasic does a simple validation check that doesn't require access to any other information.
|
||||
func (msg MsgLiquidate) ValidateBasic() error {
|
||||
if msg.Keeper.Empty() {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "keeper address cannot be empty")
|
||||
}
|
||||
if msg.Borrower.Empty() {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "borrower address cannot be empty")
|
||||
}
|
||||
if strings.TrimSpace(msg.CollateralType) == "" {
|
||||
return sdkerrors.Wrap(ErrInvalidCollateral, "collateral type cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSignBytes gets the canonical byte representation of the Msg.
|
||||
func (msg MsgLiquidate) GetSignBytes() []byte {
|
||||
bz := ModuleCdc.MustMarshalJSON(msg)
|
||||
return sdk.MustSortJSON(bz)
|
||||
}
|
||||
|
||||
// GetSigners returns the addresses of signers that must sign.
|
||||
func (msg MsgLiquidate) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.Keeper}
|
||||
}
|
||||
|
||||
// String implements the Stringer interface
|
||||
func (msg MsgLiquidate) String() string {
|
||||
return fmt.Sprintf(`Liquidate Message:
|
||||
Keeper: %s
|
||||
Borrower: %s
|
||||
Collateral Type %s
|
||||
`, msg.Keeper, msg.Borrower, msg.CollateralType)
|
||||
}
|
||||
|
@ -3,13 +3,10 @@ package types
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
// Parameter keys
|
||||
@ -17,13 +14,11 @@ var (
|
||||
KeyGlobalDebtLimit = []byte("GlobalDebtLimit")
|
||||
KeyCollateralParams = []byte("CollateralParams")
|
||||
KeyDebtParam = []byte("DebtParam")
|
||||
KeyDistributionFrequency = []byte("DistributionFrequency")
|
||||
KeyCircuitBreaker = []byte("CircuitBreaker")
|
||||
KeyDebtThreshold = []byte("DebtThreshold")
|
||||
KeyDebtLot = []byte("DebtLot")
|
||||
KeySurplusThreshold = []byte("SurplusThreshold")
|
||||
KeySurplusLot = []byte("SurplusLot")
|
||||
KeySavingsRateDistributed = []byte("SavingsRateDistributed")
|
||||
DefaultGlobalDebt = sdk.NewCoin(DefaultStableDenom, sdk.ZeroInt())
|
||||
DefaultCircuitBreaker = false
|
||||
DefaultCollateralParams = CollateralParams{}
|
||||
@ -32,7 +27,6 @@ var (
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
}
|
||||
DefaultCdpStartingID = uint64(1)
|
||||
DefaultDebtDenom = "debt"
|
||||
@ -42,8 +36,6 @@ var (
|
||||
DefaultDebtThreshold = sdk.NewInt(100000000000)
|
||||
DefaultSurplusLot = sdk.NewInt(10000000000)
|
||||
DefaultDebtLot = sdk.NewInt(10000000000)
|
||||
DefaultPreviousDistributionTime = tmtime.Canonical(time.Unix(0, 0))
|
||||
DefaultSavingsDistributionFrequency = time.Hour * 12
|
||||
DefaultSavingsRateDistributed = sdk.NewInt(0)
|
||||
minCollateralPrefix = 0
|
||||
maxCollateralPrefix = 255
|
||||
@ -59,7 +51,6 @@ type Params struct {
|
||||
SurplusAuctionLot sdk.Int `json:"surplus_auction_lot" yaml:"surplus_auction_lot"`
|
||||
DebtAuctionThreshold sdk.Int `json:"debt_auction_threshold" yaml:"debt_auction_threshold"`
|
||||
DebtAuctionLot sdk.Int `json:"debt_auction_lot" yaml:"debt_auction_lot"`
|
||||
SavingsDistributionFrequency time.Duration `json:"savings_distribution_frequency" yaml:"savings_distribution_frequency"`
|
||||
CircuitBreaker bool `json:"circuit_breaker" yaml:"circuit_breaker"`
|
||||
}
|
||||
|
||||
@ -73,17 +64,16 @@ func (p Params) String() string {
|
||||
Surplus Auction Lot: %s
|
||||
Debt Auction Threshold: %s
|
||||
Debt Auction Lot: %s
|
||||
Savings Distribution Frequency: %s
|
||||
Circuit Breaker: %t`,
|
||||
p.GlobalDebtLimit, p.CollateralParams, p.DebtParam, p.SurplusAuctionThreshold, p.SurplusAuctionLot,
|
||||
p.DebtAuctionThreshold, p.DebtAuctionLot, p.SavingsDistributionFrequency, p.CircuitBreaker,
|
||||
p.DebtAuctionThreshold, p.DebtAuctionLot, p.CircuitBreaker,
|
||||
)
|
||||
}
|
||||
|
||||
// NewParams returns a new params object
|
||||
func NewParams(
|
||||
debtLimit sdk.Coin, collateralParams CollateralParams, debtParam DebtParam, surplusThreshold,
|
||||
surplusLot, debtThreshold, debtLot sdk.Int, distributionFreq time.Duration, breaker bool,
|
||||
surplusLot, debtThreshold, debtLot sdk.Int, breaker bool,
|
||||
) Params {
|
||||
return Params{
|
||||
GlobalDebtLimit: debtLimit,
|
||||
@ -93,7 +83,6 @@ func NewParams(
|
||||
SurplusAuctionLot: surplusLot,
|
||||
DebtAuctionThreshold: debtThreshold,
|
||||
DebtAuctionLot: debtLot,
|
||||
SavingsDistributionFrequency: distributionFreq,
|
||||
CircuitBreaker: breaker,
|
||||
}
|
||||
}
|
||||
@ -102,7 +91,7 @@ func NewParams(
|
||||
func DefaultParams() Params {
|
||||
return NewParams(
|
||||
DefaultGlobalDebt, DefaultCollateralParams, DefaultDebtParam, DefaultSurplusThreshold,
|
||||
DefaultSurplusLot, DefaultDebtThreshold, DefaultDebtLot, DefaultSavingsDistributionFrequency,
|
||||
DefaultSurplusLot, DefaultDebtThreshold, DefaultDebtLot,
|
||||
DefaultCircuitBreaker,
|
||||
)
|
||||
}
|
||||
@ -119,11 +108,15 @@ type CollateralParam struct {
|
||||
Prefix byte `json:"prefix" yaml:"prefix"`
|
||||
SpotMarketID string `json:"spot_market_id" yaml:"spot_market_id"` // marketID of the spot price of the asset from the pricefeed - used for opening CDPs, depositing, withdrawing
|
||||
LiquidationMarketID string `json:"liquidation_market_id" yaml:"liquidation_market_id"` // marketID of the pricefeed used for liquidation
|
||||
KeeperRewardPercentage sdk.Dec `json:"keeper_reward_percentage" yaml:"keeper_reward_percentage"` // the percentage of a CDPs collateral that gets rewarded to a keeper that liquidates the position
|
||||
CheckCollateralizationIndexCount sdk.Int `json:"check_collateralization_index_count" yaml:"check_collateralization_index_count"` // the number of cdps that will be checked for liquidation in the begin blocker
|
||||
ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"` // factor for converting internal units to one base unit of collateral
|
||||
}
|
||||
|
||||
// NewCollateralParam returns a new CollateralParam
|
||||
func NewCollateralParam(denom, ctype string, liqRatio sdk.Dec, debtLimit sdk.Coin, stabilityFee sdk.Dec, auctionSize sdk.Int, liqPenalty sdk.Dec, prefix byte, spotMarketID, liquidationMarketID string, conversionFactor sdk.Int) CollateralParam {
|
||||
func NewCollateralParam(
|
||||
denom, ctype string, liqRatio sdk.Dec, debtLimit sdk.Coin, stabilityFee sdk.Dec, auctionSize sdk.Int,
|
||||
liqPenalty sdk.Dec, prefix byte, spotMarketID, liquidationMarketID string, keeperReward sdk.Dec, checkIndexCount sdk.Int, conversionFactor sdk.Int) CollateralParam {
|
||||
return CollateralParam{
|
||||
Denom: denom,
|
||||
Type: ctype,
|
||||
@ -135,6 +128,8 @@ func NewCollateralParam(denom, ctype string, liqRatio sdk.Dec, debtLimit sdk.Coi
|
||||
Prefix: prefix,
|
||||
SpotMarketID: spotMarketID,
|
||||
LiquidationMarketID: liquidationMarketID,
|
||||
KeeperRewardPercentage: keeperReward,
|
||||
CheckCollateralizationIndexCount: checkIndexCount,
|
||||
ConversionFactor: conversionFactor,
|
||||
}
|
||||
}
|
||||
@ -152,8 +147,12 @@ func (cp CollateralParam) String() string {
|
||||
Prefix: %b
|
||||
Spot Market ID: %s
|
||||
Liquidation Market ID: %s
|
||||
Keeper Reward Percentage: %s
|
||||
Check Collateralization Count: %s
|
||||
Conversion Factor: %s`,
|
||||
cp.Denom, cp.Type, cp.LiquidationRatio, cp.StabilityFee, cp.LiquidationPenalty, cp.DebtLimit, cp.AuctionSize, cp.Prefix, cp.SpotMarketID, cp.LiquidationMarketID, cp.ConversionFactor)
|
||||
cp.Denom, cp.Type, cp.LiquidationRatio, cp.StabilityFee, cp.LiquidationPenalty,
|
||||
cp.DebtLimit, cp.AuctionSize, cp.Prefix, cp.SpotMarketID, cp.LiquidationMarketID,
|
||||
cp.KeeperRewardPercentage, cp.CheckCollateralizationIndexCount, cp.ConversionFactor)
|
||||
}
|
||||
|
||||
// CollateralParams array of CollateralParam
|
||||
@ -174,17 +173,15 @@ type DebtParam struct {
|
||||
ReferenceAsset string `json:"reference_asset" yaml:"reference_asset"`
|
||||
ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"`
|
||||
DebtFloor sdk.Int `json:"debt_floor" yaml:"debt_floor"` // minimum active loan size, used to prevent dust
|
||||
SavingsRate sdk.Dec `json:"savings_rate" yaml:"savings_rate"` // the percentage of stability fees that are redirected to savings rate
|
||||
}
|
||||
|
||||
// NewDebtParam returns a new DebtParam
|
||||
func NewDebtParam(denom, refAsset string, conversionFactor, debtFloor sdk.Int, savingsRate sdk.Dec) DebtParam {
|
||||
func NewDebtParam(denom, refAsset string, conversionFactor, debtFloor sdk.Int) DebtParam {
|
||||
return DebtParam{
|
||||
Denom: denom,
|
||||
ReferenceAsset: refAsset,
|
||||
ConversionFactor: conversionFactor,
|
||||
DebtFloor: debtFloor,
|
||||
SavingsRate: savingsRate,
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,8 +191,7 @@ func (dp DebtParam) String() string {
|
||||
Reference Asset: %s
|
||||
Conversion Factor: %s
|
||||
Debt Floor %s
|
||||
Savings Rate %s
|
||||
`, dp.Denom, dp.ReferenceAsset, dp.ConversionFactor, dp.DebtFloor, dp.SavingsRate)
|
||||
`, dp.Denom, dp.ReferenceAsset, dp.ConversionFactor, dp.DebtFloor)
|
||||
}
|
||||
|
||||
// DebtParams array of DebtParam
|
||||
@ -228,7 +224,6 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs {
|
||||
params.NewParamSetPair(KeySurplusLot, &p.SurplusAuctionLot, validateSurplusAuctionLotParam),
|
||||
params.NewParamSetPair(KeyDebtThreshold, &p.DebtAuctionThreshold, validateDebtAuctionThresholdParam),
|
||||
params.NewParamSetPair(KeyDebtLot, &p.DebtAuctionLot, validateDebtAuctionLotParam),
|
||||
params.NewParamSetPair(KeyDistributionFrequency, &p.SavingsDistributionFrequency, validateSavingsDistributionFrequencyParam),
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,10 +261,6 @@ func (p Params) Validate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validateSavingsDistributionFrequencyParam(p.SavingsDistributionFrequency); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(p.CollateralParams) == 0 { // default value OK
|
||||
return nil
|
||||
}
|
||||
@ -381,6 +372,12 @@ func validateCollateralParams(i interface{}) error {
|
||||
if cp.StabilityFee.LT(sdk.OneDec()) || cp.StabilityFee.GT(stabilityFeeMax) {
|
||||
return fmt.Errorf("stability fee must be ≥ 1.0, ≤ %s, is %s for %s", stabilityFeeMax, cp.StabilityFee, cp.Denom)
|
||||
}
|
||||
if cp.KeeperRewardPercentage.IsNegative() || cp.KeeperRewardPercentage.GT(sdk.OneDec()) {
|
||||
return fmt.Errorf("keeper reward percentage should be between 0 and 1, is %s for %s", cp.KeeperRewardPercentage, cp.Denom)
|
||||
}
|
||||
if cp.CheckCollateralizationIndexCount.IsNegative() {
|
||||
return fmt.Errorf("keeper reward percentage should be positive, is %s for %s", cp.CheckCollateralizationIndexCount, cp.Denom)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -395,9 +392,6 @@ func validateDebtParam(i interface{}) error {
|
||||
return fmt.Errorf("debt denom invalid %s", debtParam.Denom)
|
||||
}
|
||||
|
||||
if debtParam.SavingsRate.LT(sdk.ZeroDec()) || debtParam.SavingsRate.GT(sdk.OneDec()) {
|
||||
return fmt.Errorf("savings rate should be between 0 and 1, is %s for %s", debtParam.SavingsRate, debtParam.Denom)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -461,16 +455,3 @@ func validateDebtAuctionLotParam(i interface{}) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSavingsDistributionFrequencyParam(i interface{}) error {
|
||||
sdf, ok := i.(time.Duration)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
if sdf.Seconds() <= float64(0) {
|
||||
return fmt.Errorf("savings distribution frequency should be positive: %s", sdf)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package types_test
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
@ -28,7 +27,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
surplusLot sdk.Int
|
||||
debtThreshold sdk.Int
|
||||
debtLot sdk.Int
|
||||
distributionFreq time.Duration
|
||||
breaker bool
|
||||
}
|
||||
type errArgs struct {
|
||||
@ -51,7 +49,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -75,7 +72,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -83,13 +82,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -113,7 +110,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -121,13 +120,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -151,7 +148,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -159,13 +158,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -189,7 +186,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
{
|
||||
Denom: "xrp",
|
||||
@ -202,7 +201,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x21,
|
||||
SpotMarketID: "xrp:usd",
|
||||
LiquidationMarketID: "xrp:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -210,13 +211,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -240,7 +239,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
{
|
||||
Denom: "xrp",
|
||||
@ -253,7 +254,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x21,
|
||||
SpotMarketID: "xrp:usd",
|
||||
LiquidationMarketID: "xrp:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -261,13 +264,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -291,7 +292,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
{
|
||||
Denom: "xrp",
|
||||
@ -304,7 +307,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x21,
|
||||
SpotMarketID: "xrp:usd",
|
||||
LiquidationMarketID: "xrp:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -312,13 +317,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -341,7 +344,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -349,13 +354,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -379,7 +382,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "",
|
||||
LiquidationMarketID: "",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -387,13 +392,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -417,7 +420,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
{
|
||||
Denom: "bnb",
|
||||
@ -430,7 +435,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x21,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -438,13 +445,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -468,7 +473,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
{
|
||||
Denom: "bnb",
|
||||
@ -481,7 +488,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x21,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -489,13 +498,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -519,7 +526,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
{
|
||||
Denom: "xrp",
|
||||
@ -532,7 +541,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "xrp:usd",
|
||||
LiquidationMarketID: "xrp:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -540,13 +551,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -570,7 +579,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -578,13 +589,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -608,7 +617,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -616,13 +627,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -646,7 +655,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -654,13 +665,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -684,7 +693,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -692,13 +703,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -722,7 +731,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
@ -730,13 +741,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -744,44 +753,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
contains: "debt denom invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid debt param savings rate out of range",
|
||||
args: args{
|
||||
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
|
||||
collateralParams: types.CollateralParams{
|
||||
{
|
||||
Denom: "bnb",
|
||||
Type: "bnb-a",
|
||||
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
|
||||
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
|
||||
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
|
||||
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
|
||||
AuctionSize: sdk.NewInt(50000000000),
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
ConversionFactor: sdk.NewInt(8),
|
||||
},
|
||||
},
|
||||
debtParam: types.DebtParam{
|
||||
Denom: "usdx",
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: sdk.NewInt(6),
|
||||
DebtFloor: sdk.NewInt(10000000),
|
||||
SavingsRate: sdk.MustNewDecFromStr("1.05"),
|
||||
},
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "savings rate should be between 0 and 1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil debt limit",
|
||||
args: args{
|
||||
@ -792,7 +763,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -800,24 +770,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
contains: "invalid coins: global debt limit",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "zero savings distribution frequency",
|
||||
args: args{
|
||||
globalDebtLimit: types.DefaultGlobalDebt,
|
||||
collateralParams: types.DefaultCollateralParams,
|
||||
debtParam: types.DefaultDebtParam,
|
||||
surplusThreshold: types.DefaultSurplusThreshold,
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: time.Second * 0,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "savings distribution frequency should be positive",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "zero surplus auction threshold",
|
||||
args: args{
|
||||
@ -828,7 +780,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -846,7 +797,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: sdk.ZeroInt(),
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -864,7 +814,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
surplusLot: sdk.ZeroInt(),
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: types.DefaultDebtLot,
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -882,7 +831,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
surplusLot: types.DefaultSurplusLot,
|
||||
debtThreshold: types.DefaultDebtThreshold,
|
||||
debtLot: sdk.ZeroInt(),
|
||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
||||
breaker: types.DefaultCircuitBreaker,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
@ -893,7 +841,7 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
params := types.NewParams(tc.args.globalDebtLimit, tc.args.collateralParams, tc.args.debtParam, tc.args.surplusThreshold, tc.args.surplusLot, tc.args.debtThreshold, tc.args.debtLot, tc.args.distributionFreq, tc.args.breaker)
|
||||
params := types.NewParams(tc.args.globalDebtLimit, tc.args.collateralParams, tc.args.debtParam, tc.args.surplusThreshold, tc.args.surplusLot, tc.args.debtThreshold, tc.args.debtLot, tc.args.breaker)
|
||||
err := params.Validate()
|
||||
if tc.errArgs.expectPass {
|
||||
suite.Require().NoError(err)
|
||||
|
@ -68,7 +68,6 @@ func (suite *PermissionTestSuite) TestSubParamChangePermission_Allows() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: i(6),
|
||||
DebtFloor: i(10000000),
|
||||
SavingsRate: d("0.95"),
|
||||
}
|
||||
testDPUpdatedDebtFloor := testDP
|
||||
testDPUpdatedDebtFloor.DebtFloor = i(1000)
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
upgrade "github.com/cosmos/cosmos-sdk/x/upgrade"
|
||||
|
||||
bep3types "github.com/kava-labs/kava/x/bep3/types"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/legacy/v0_11"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
|
||||
)
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
upgrade "github.com/cosmos/cosmos-sdk/x/upgrade"
|
||||
|
||||
bep3types "github.com/kava-labs/kava/x/bep3/types"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/legacy/v0_9"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
|
||||
)
|
||||
|
@ -542,7 +542,6 @@ func (suite *PermissionsTestSuite) TestAllowedDebtParam_Allows() {
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: i(6),
|
||||
DebtFloor: i(10000000),
|
||||
SavingsRate: d("0.95"),
|
||||
}
|
||||
newDenomDP := testDP
|
||||
newDenomDP.Denom = "usdz"
|
||||
@ -565,7 +564,6 @@ func (suite *PermissionsTestSuite) TestAllowedDebtParam_Allows() {
|
||||
name: "allowed change",
|
||||
allowed: AllowedDebtParam{
|
||||
DebtFloor: true,
|
||||
SavingsRate: true,
|
||||
},
|
||||
current: testDP,
|
||||
incoming: newDebtFloorDP,
|
||||
@ -575,7 +573,6 @@ func (suite *PermissionsTestSuite) TestAllowedDebtParam_Allows() {
|
||||
name: "un-allowed change",
|
||||
allowed: AllowedDebtParam{
|
||||
DebtFloor: true,
|
||||
SavingsRate: true,
|
||||
},
|
||||
current: testDP,
|
||||
incoming: newDenomDP,
|
||||
@ -585,7 +582,6 @@ func (suite *PermissionsTestSuite) TestAllowedDebtParam_Allows() {
|
||||
name: "allowed no change",
|
||||
allowed: AllowedDebtParam{
|
||||
DebtFloor: true,
|
||||
SavingsRate: true,
|
||||
},
|
||||
current: testDP,
|
||||
incoming: testDP, // no change
|
||||
@ -595,7 +591,6 @@ func (suite *PermissionsTestSuite) TestAllowedDebtParam_Allows() {
|
||||
name: "un-allowed change with allowed change",
|
||||
allowed: AllowedDebtParam{
|
||||
DebtFloor: true,
|
||||
SavingsRate: true,
|
||||
},
|
||||
current: testDP,
|
||||
incoming: newDenomAndDebtFloorDP,
|
||||
|
@ -425,15 +425,13 @@ type AllowedDebtParam struct {
|
||||
ReferenceAsset bool `json:"reference_asset" yaml:"reference_asset"`
|
||||
ConversionFactor bool `json:"conversion_factor" yaml:"conversion_factor"`
|
||||
DebtFloor bool `json:"debt_floor" yaml:"debt_floor"`
|
||||
SavingsRate bool `json:"savings_rate" yaml:"savings_rate"`
|
||||
}
|
||||
|
||||
func (adp AllowedDebtParam) Allows(current, incoming cdptypes.DebtParam) bool {
|
||||
allowed := ((current.Denom == incoming.Denom) || adp.Denom) &&
|
||||
((current.ReferenceAsset == incoming.ReferenceAsset) || adp.ReferenceAsset) &&
|
||||
(current.ConversionFactor.Equal(incoming.ConversionFactor) || adp.ConversionFactor) &&
|
||||
(current.DebtFloor.Equal(incoming.DebtFloor) || adp.DebtFloor) &&
|
||||
(current.SavingsRate.Equal(incoming.SavingsRate) || adp.SavingsRate)
|
||||
(current.DebtFloor.Equal(incoming.DebtFloor) || adp.DebtFloor)
|
||||
return allowed
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package v0_12
|
||||
package v0_11
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -10,6 +10,22 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
const (
|
||||
// ModuleName name that will be used throughout the module
|
||||
ModuleName = "harvest"
|
||||
|
||||
// LPAccount LP distribution module account
|
||||
LPAccount = "harvest_lp_distribution"
|
||||
|
||||
// DelegatorAccount delegator distribution module account
|
||||
DelegatorAccount = "harvest_delegator_distribution"
|
||||
|
||||
// ModuleAccountName name of module account used to hold deposits
|
||||
ModuleAccountName = "harvest"
|
||||
)
|
||||
|
||||
// Parameter keys and default values
|
||||
@ -21,10 +37,58 @@ var (
|
||||
DefaultGovSchedules = DistributionSchedules{}
|
||||
DefaultLPSchedules = DistributionSchedules{}
|
||||
DefaultDelegatorSchedules = DelegatorDistributionSchedules{}
|
||||
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
|
||||
DefaultDistributionTimes = GenesisDistributionTimes{}
|
||||
GovDenom = cdptypes.DefaultGovDenom
|
||||
)
|
||||
|
||||
// Params governance parameters for hard module
|
||||
// GenesisState is the state that must be provided at genesis.
|
||||
type GenesisState struct {
|
||||
Params Params `json:"params" yaml:"params"`
|
||||
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
|
||||
PreviousDistributionTimes GenesisDistributionTimes `json:"previous_distribution_times" yaml:"previous_distribution_times"`
|
||||
}
|
||||
|
||||
// NewGenesisState returns a new genesis state
|
||||
func NewGenesisState(params Params, previousBlockTime time.Time, previousDistTimes GenesisDistributionTimes) GenesisState {
|
||||
return GenesisState{
|
||||
Params: params,
|
||||
PreviousBlockTime: previousBlockTime,
|
||||
PreviousDistributionTimes: previousDistTimes,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs basic validation of genesis data returning an
|
||||
// error for any failed validation criteria.
|
||||
func (gs GenesisState) Validate() error {
|
||||
|
||||
if err := gs.Params.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if gs.PreviousBlockTime.Equal(time.Time{}) {
|
||||
return fmt.Errorf("previous block time not set")
|
||||
}
|
||||
for _, gdt := range gs.PreviousDistributionTimes {
|
||||
if gdt.PreviousDistributionTime.Equal(time.Time{}) {
|
||||
return fmt.Errorf("previous distribution time not set for %s", gdt.Denom)
|
||||
}
|
||||
if err := sdk.ValidateDenom(gdt.Denom); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenesisDistributionTime stores the previous distribution time and its corresponding denom
|
||||
type GenesisDistributionTime struct {
|
||||
Denom string `json:"denom" yaml:"denom"`
|
||||
PreviousDistributionTime time.Time `json:"previous_distribution_time" yaml:"previous_distribution_time"`
|
||||
}
|
||||
|
||||
// GenesisDistributionTimes slice of GenesisDistributionTime
|
||||
type GenesisDistributionTimes []GenesisDistributionTime
|
||||
|
||||
// Params governance parameters for harvest module
|
||||
type Params struct {
|
||||
Active bool `json:"active" yaml:"active"`
|
||||
LiquidityProviderSchedules DistributionSchedules `json:"liquidity_provider_schedules" yaml:"liquidity_provider_schedules"`
|
||||
@ -228,7 +292,7 @@ func NewParams(active bool, lps DistributionSchedules, dds DelegatorDistribution
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultParams returns default params for hard module
|
||||
// DefaultParams returns default params for harvest module
|
||||
func DefaultParams() Params {
|
||||
return NewParams(DefaultActive, DefaultLPSchedules, DefaultDelegatorSchedules)
|
||||
}
|
||||
@ -342,7 +406,7 @@ func (dt DepositType) IsValid() error {
|
||||
return fmt.Errorf("invalid deposit type: %s", dt)
|
||||
}
|
||||
|
||||
// Deposit defines an amount of coins deposited into a hard module account
|
||||
// Deposit defines an amount of coins deposited into a harvest module account
|
||||
type Deposit struct {
|
||||
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"`
|
||||
Amount sdk.Coin `json:"amount" yaml:"amount"`
|
@ -8,7 +8,10 @@ import (
|
||||
|
||||
// BeginBlocker runs at the start of every block
|
||||
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
|
||||
k.DeleteExpiredClaimsAndClaimPeriods(ctx)
|
||||
k.ApplyRewardsToCdps(ctx)
|
||||
k.CreateAndDeleteRewardPeriods(ctx)
|
||||
for _, rp := range k.GetParams(ctx).RewardPeriods {
|
||||
err := k.AccumulateRewards(ctx, rp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,77 +35,76 @@ const (
|
||||
|
||||
var (
|
||||
// function aliases
|
||||
CalculateTimeElapsed = keeper.CalculateTimeElapsed
|
||||
NewKeeper = keeper.NewKeeper
|
||||
NewQuerier = keeper.NewQuerier
|
||||
BytesToUint64 = types.BytesToUint64
|
||||
DefaultGenesisState = types.DefaultGenesisState
|
||||
DefaultParams = types.DefaultParams
|
||||
GetClaimPeriodPrefix = types.GetClaimPeriodPrefix
|
||||
GetClaimPrefix = types.GetClaimPrefix
|
||||
GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength
|
||||
NewAugmentedClaim = types.NewAugmentedClaim
|
||||
NewClaim = types.NewClaim
|
||||
NewClaimPeriod = types.NewClaimPeriod
|
||||
NewGenesisAccumulationTime = types.NewGenesisAccumulationTime
|
||||
NewGenesisState = types.NewGenesisState
|
||||
NewMsgClaimReward = types.NewMsgClaimReward
|
||||
NewMsgClaimUSDXMintingReward = types.NewMsgClaimUSDXMintingReward
|
||||
NewMultiplier = types.NewMultiplier
|
||||
NewParams = types.NewParams
|
||||
NewPeriod = types.NewPeriod
|
||||
NewQueryClaimsParams = types.NewQueryClaimsParams
|
||||
NewReward = types.NewReward
|
||||
NewRewardIndex = types.NewRewardIndex
|
||||
NewRewardPeriod = types.NewRewardPeriod
|
||||
NewRewardPeriodFromReward = types.NewRewardPeriodFromReward
|
||||
NewUSDXMintingClaim = types.NewUSDXMintingClaim
|
||||
ParamKeyTable = types.ParamKeyTable
|
||||
RegisterCodec = types.RegisterCodec
|
||||
|
||||
// variable aliases
|
||||
BlockTimeKey = types.BlockTimeKey
|
||||
ClaimKeyPrefix = types.ClaimKeyPrefix
|
||||
ClaimPeriodKeyPrefix = types.ClaimPeriodKeyPrefix
|
||||
DefaultActive = types.DefaultActive
|
||||
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime
|
||||
DefaultRewards = types.DefaultRewards
|
||||
DefaultClaimEnd = types.DefaultClaimEnd
|
||||
DefaultClaims = types.DefaultClaims
|
||||
DefaultGenesisAccumulationTimes = types.DefaultGenesisAccumulationTimes
|
||||
DefaultMultipliers = types.DefaultMultipliers
|
||||
DefaultRewardPeriods = types.DefaultRewardPeriods
|
||||
ErrAccountNotFound = types.ErrAccountNotFound
|
||||
ErrClaimExpired = types.ErrClaimExpired
|
||||
ErrClaimNotFound = types.ErrClaimNotFound
|
||||
ErrClaimPeriodNotFound = types.ErrClaimPeriodNotFound
|
||||
ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance
|
||||
ErrInvalidAccountType = types.ErrInvalidAccountType
|
||||
ErrInvalidMultiplier = types.ErrInvalidMultiplier
|
||||
ErrNoClaimsFound = types.ErrNoClaimsFound
|
||||
ErrRewardPeriodNotFound = types.ErrRewardPeriodNotFound
|
||||
ErrZeroClaim = types.ErrZeroClaim
|
||||
GovDenom = types.GovDenom
|
||||
IncentiveMacc = types.IncentiveMacc
|
||||
KeyActive = types.KeyActive
|
||||
KeyClaimEnd = types.KeyClaimEnd
|
||||
KeyMultipliers = types.KeyMultipliers
|
||||
KeyRewards = types.KeyRewards
|
||||
ModuleCdc = types.ModuleCdc
|
||||
NextClaimPeriodIDPrefix = types.NextClaimPeriodIDPrefix
|
||||
PreviousBlockTimeKey = types.PreviousBlockTimeKey
|
||||
PrincipalDenom = types.PrincipalDenom
|
||||
RewardPeriodKeyPrefix = types.RewardPeriodKeyPrefix
|
||||
RewardFactorKey = types.RewardFactorKey
|
||||
USDXMintingRewardDenom = types.USDXMintingRewardDenom
|
||||
)
|
||||
|
||||
type (
|
||||
Hooks = keeper.Hooks
|
||||
Keeper = keeper.Keeper
|
||||
AccountKeeper = types.AccountKeeper
|
||||
AugmentedClaim = types.AugmentedClaim
|
||||
AugmentedClaims = types.AugmentedClaims
|
||||
CDPHooks = types.CDPHooks
|
||||
CdpKeeper = types.CdpKeeper
|
||||
Claim = types.Claim
|
||||
ClaimPeriod = types.ClaimPeriod
|
||||
ClaimPeriods = types.ClaimPeriods
|
||||
Claims = types.Claims
|
||||
GenesisClaimPeriodID = types.GenesisClaimPeriodID
|
||||
GenesisClaimPeriodIDs = types.GenesisClaimPeriodIDs
|
||||
GenesisAccumulationTime = types.GenesisAccumulationTime
|
||||
GenesisAccumulationTimes = types.GenesisAccumulationTimes
|
||||
GenesisState = types.GenesisState
|
||||
MsgClaimReward = types.MsgClaimReward
|
||||
MsgClaimUSDXMintingReward = types.MsgClaimUSDXMintingReward
|
||||
Multiplier = types.Multiplier
|
||||
MultiplierName = types.MultiplierName
|
||||
Multipliers = types.Multipliers
|
||||
Params = types.Params
|
||||
PostClaimReq = types.PostClaimReq
|
||||
QueryClaimsParams = types.QueryClaimsParams
|
||||
Reward = types.Reward
|
||||
RewardIndex = types.RewardIndex
|
||||
RewardIndexes = types.RewardIndexes
|
||||
RewardPeriod = types.RewardPeriod
|
||||
RewardPeriods = types.RewardPeriods
|
||||
Rewards = types.Rewards
|
||||
SupplyKeeper = types.SupplyKeeper
|
||||
USDXMintingClaim = types.USDXMintingClaim
|
||||
USDXMintingClaims = types.USDXMintingClaims
|
||||
)
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
@ -25,35 +26,42 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
incentiveQueryCmd.AddCommand(flags.GetCommands(
|
||||
queryParamsCmd(queryRoute, cdc),
|
||||
queryClaimsCmd(queryRoute, cdc),
|
||||
queryRewardPeriodsCmd(queryRoute, cdc),
|
||||
queryClaimPeriodsCmd(queryRoute, cdc),
|
||||
)...)
|
||||
|
||||
return incentiveQueryCmd
|
||||
|
||||
}
|
||||
|
||||
const (
|
||||
flagOwner = "owner"
|
||||
)
|
||||
|
||||
func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "claims [owner-addr] [collateral-type]",
|
||||
Short: "get claims by owner and collateral-type",
|
||||
cmd := &cobra.Command{
|
||||
Use: "claims ",
|
||||
Short: "query USDX minting claims",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Get all claims owned by the owner address for the particular collateral type.
|
||||
fmt.Sprintf(`Query USDX minting claims with optional flag for finding claims for a specifc owner
|
||||
|
||||
Example:
|
||||
$ %s query %s claims kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw bnb-a`, version.ClientName, types.ModuleName)),
|
||||
Args: cobra.ExactArgs(2),
|
||||
$ %s query %s claims
|
||||
$ %s query %s claims --owner kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw
|
||||
`,
|
||||
version.ClientName, types.ModuleName, version.ClientName, types.ModuleName)),
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
strOwner := viper.GetString(flagOwner)
|
||||
page := viper.GetInt(flags.FlagPage)
|
||||
limit := viper.GetInt(flags.FlagLimit)
|
||||
|
||||
// Prepare params for querier
|
||||
ownerAddress, err := sdk.AccAddressFromBech32(args[0])
|
||||
owner, err := sdk.AccAddressFromBech32(strOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bz, err := cdc.MarshalJSON(types.QueryClaimsParams{
|
||||
Owner: ownerAddress,
|
||||
CollateralType: args[1],
|
||||
})
|
||||
params := types.NewQueryClaimsParams(page, limit, owner)
|
||||
bz, err := cdc.MarshalJSON(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -66,7 +74,7 @@ func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
}
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
|
||||
var claims types.AugmentedClaims
|
||||
var claims types.USDXMintingClaims
|
||||
if err := cdc.UnmarshalJSON(res, &claims); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal claims: %w", err)
|
||||
}
|
||||
@ -74,6 +82,10 @@ func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
|
||||
},
|
||||
}
|
||||
cmd.Flags().String(flagOwner, "", "(optional) filter by claim owner address")
|
||||
cmd.Flags().Int(flags.FlagPage, 1, "pagination page of CDPs to to query for")
|
||||
cmd.Flags().Int(flags.FlagLimit, 100, "pagination limit of CDPs to query for")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func queryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
@ -102,57 +114,3 @@ func queryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func queryRewardPeriodsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "reward-periods",
|
||||
Short: "get active reward periods",
|
||||
Long: "Get the current set of active incentive reward periods.",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
// Query
|
||||
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetRewardPeriods)
|
||||
res, height, err := cliCtx.QueryWithData(route, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
|
||||
// Decode and print results
|
||||
var rewardPeriods types.RewardPeriods
|
||||
if err := cdc.UnmarshalJSON(res, &rewardPeriods); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal reward periods: %w", err)
|
||||
}
|
||||
return cliCtx.PrintOutput(rewardPeriods)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func queryClaimPeriodsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "claim-periods",
|
||||
Short: "get active claim periods",
|
||||
Long: "Get the current set of active incentive claim periods.",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
// Query
|
||||
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetClaimPeriods)
|
||||
res, height, err := cliCtx.QueryWithData(route, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
|
||||
// Decode and print results
|
||||
var claimPeriods types.ClaimPeriods
|
||||
if err := cdc.UnmarshalJSON(res, &claimPeriods); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal claim periods: %w", err)
|
||||
}
|
||||
return cliCtx.PrintOutput(claimPeriods)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -35,16 +35,16 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
|
||||
func getCmdClaim(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "claim [owner] [collateral-type] [multiplier]",
|
||||
Use: "claim [owner] [multiplier]",
|
||||
Short: "claim rewards for cdp owner and collateral-type",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Claim any outstanding rewards owned by owner for the input collateral-type and multiplier,
|
||||
|
||||
Example:
|
||||
$ %s tx %s claim kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw bnb-a large
|
||||
$ %s tx %s claim kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw large
|
||||
`, version.ClientName, types.ModuleName),
|
||||
),
|
||||
Args: cobra.ExactArgs(3),
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||
cliCtx := context.NewCLIContextWithInputAndFrom(inBuf, args[0]).WithCodec(cdc)
|
||||
@ -54,7 +54,7 @@ func getCmdClaim(cdc *codec.Codec) *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := types.NewMsgClaimReward(owner, args[1], args[2])
|
||||
msg := types.NewMsgClaimUSDXMintingReward(owner, args[1])
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -3,6 +3,7 @@ package rest
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
@ -14,30 +15,33 @@ import (
|
||||
)
|
||||
|
||||
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
r.HandleFunc(fmt.Sprintf("/%s/claims/{%s}/{%s}", types.ModuleName, types.RestClaimOwner, types.RestClaimCollateralType), queryClaimsHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/%s/rewardperiods", types.ModuleName), queryRewardPeriodsHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/%s/claimperiods", types.ModuleName), queryClaimPeriodsHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/%s/claims", types.ModuleName), queryClaimsHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/%s/parameters", types.ModuleName), queryParamsHandlerFn(cliCtx)).Methods("GET")
|
||||
}
|
||||
|
||||
func queryClaimsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
ownerBech32 := vars[types.RestClaimOwner]
|
||||
denom := vars[types.RestClaimCollateralType]
|
||||
|
||||
owner, err := sdk.AccAddressFromBech32(ownerBech32)
|
||||
var owner sdk.AccAddress
|
||||
if x := r.URL.Query().Get(types.RestClaimOwner); len(x) != 0 {
|
||||
ownerStr := strings.ToLower(strings.TrimSpace(x))
|
||||
owner, err = sdk.AccAddressFromBech32(ownerStr)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("cannot parse address from claim owner %s", ownerStr))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
queryParams := types.NewQueryClaimsParams(owner, denom)
|
||||
queryParams := types.NewQueryClaimsParams(page, limit, owner)
|
||||
bz, err := cliCtx.Codec.MarshalJSON(queryParams)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err))
|
||||
@ -55,46 +59,6 @@ func queryClaimsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func queryRewardPeriodsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryGetRewardPeriods)
|
||||
|
||||
res, height, err := cliCtx.QueryWithData(route, nil)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryClaimPeriodsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryGetClaimPeriods)
|
||||
|
||||
res, height, err := cliCtx.QueryWithData(route, nil)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||
|
@ -42,7 +42,7 @@ func postClaimHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
msg := types.NewMsgClaimReward(requestBody.Sender, requestBody.CollateralType, requestBody.MultiplierName)
|
||||
msg := types.NewMsgClaimUSDXMintingReward(requestBody.Sender, requestBody.MultiplierName)
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
|
@ -24,51 +24,37 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep
|
||||
|
||||
k.SetParams(ctx, gs.Params)
|
||||
|
||||
for _, r := range gs.Params.Rewards {
|
||||
k.SetNextClaimPeriodID(ctx, r.CollateralType, 1)
|
||||
for _, gat := range gs.PreviousAccumulationTimes {
|
||||
k.SetPreviousAccrualTime(ctx, gat.CollateralType, gat.PreviousAccumulationTime)
|
||||
k.SetRewardFactor(ctx, gat.CollateralType, gat.RewardFactor)
|
||||
}
|
||||
|
||||
// only set the previous block time if it's different than default
|
||||
if !gs.PreviousBlockTime.Equal(types.DefaultPreviousBlockTime) {
|
||||
k.SetPreviousBlockTime(ctx, gs.PreviousBlockTime)
|
||||
}
|
||||
|
||||
// set store objects
|
||||
for _, rp := range gs.RewardPeriods {
|
||||
k.SetRewardPeriod(ctx, rp)
|
||||
}
|
||||
|
||||
for _, cp := range gs.ClaimPeriods {
|
||||
k.SetClaimPeriod(ctx, cp)
|
||||
}
|
||||
|
||||
for _, c := range gs.Claims {
|
||||
k.SetClaim(ctx, c)
|
||||
}
|
||||
|
||||
for _, id := range gs.NextClaimPeriodIDs {
|
||||
k.SetNextClaimPeriodID(ctx, id.CollateralType, id.ID)
|
||||
for _, claim := range gs.USDXMintingClaims {
|
||||
k.SetClaim(ctx, claim)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ExportGenesis export genesis state for incentive module
|
||||
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState {
|
||||
// get all objects out of the store
|
||||
params := k.GetParams(ctx)
|
||||
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
|
||||
|
||||
// since it is not set in genesis, if somehow the chain got started and was exported
|
||||
// immediately after InitGenesis, there would be no previousBlockTime value.
|
||||
if !found {
|
||||
previousBlockTime = types.DefaultPreviousBlockTime
|
||||
}
|
||||
|
||||
// Get all objects from the store
|
||||
rewardPeriods := k.GetAllRewardPeriods(ctx)
|
||||
claimPeriods := k.GetAllClaimPeriods(ctx)
|
||||
claims := k.GetAllClaims(ctx)
|
||||
claimPeriodIDs := k.GetAllClaimPeriodIDPairs(ctx)
|
||||
|
||||
return types.NewGenesisState(params, previousBlockTime, rewardPeriods, claimPeriods, claims, claimPeriodIDs)
|
||||
var gats GenesisAccumulationTimes
|
||||
|
||||
for _, rp := range params.RewardPeriods {
|
||||
pat, found := k.GetPreviousAccrualTime(ctx, rp.CollateralType)
|
||||
if !found {
|
||||
pat = ctx.BlockTime()
|
||||
}
|
||||
factor, found := k.GetRewardFactor(ctx, rp.CollateralType)
|
||||
if !found {
|
||||
factor = sdk.ZeroDec()
|
||||
}
|
||||
gat := types.NewGenesisAccumulationTime(rp.CollateralType, pat, factor)
|
||||
gats = append(gats, gat)
|
||||
}
|
||||
|
||||
return types.NewGenesisState(params, gats, claims)
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package incentive
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
@ -15,36 +13,20 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
ctx = ctx.WithEventManager(sdk.NewEventManager())
|
||||
switch msg := msg.(type) {
|
||||
case types.MsgClaimReward:
|
||||
return handleMsgClaimReward(ctx, k, msg)
|
||||
case types.MsgClaimUSDXMintingReward:
|
||||
return handleMsgClaimUSDXMintingReward(ctx, k, msg)
|
||||
default:
|
||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgClaimReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimReward) (*sdk.Result, error) {
|
||||
func handleMsgClaimUSDXMintingReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimUSDXMintingReward) (*sdk.Result, error) {
|
||||
|
||||
claims, found := k.GetActiveClaimsByAddressAndCollateralType(ctx, msg.Sender, msg.CollateralType)
|
||||
if !found {
|
||||
return nil, sdkerrors.Wrapf(types.ErrNoClaimsFound, "address: %s, collateral type: %s", msg.Sender, msg.CollateralType)
|
||||
}
|
||||
|
||||
for _, claim := range claims {
|
||||
err := k.PayoutClaim(ctx, claim.Owner, claim.CollateralType, claim.ClaimPeriodID, types.MultiplierName(strings.ToLower(msg.MultiplierName)))
|
||||
err := k.ClaimReward(ctx, msg.Sender, types.MultiplierName(msg.MultiplierName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
sdk.EventTypeMessage,
|
||||
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
|
||||
sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()),
|
||||
),
|
||||
)
|
||||
|
||||
return &sdk.Result{
|
||||
Events: ctx.EventManager().Events(),
|
||||
}, nil
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/incentive"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
"github.com/kava-labs/kava/x/kavadist"
|
||||
)
|
||||
|
||||
@ -41,7 +42,16 @@ func (suite *HandlerTestSuite) SetupTest() {
|
||||
coins = append(coins, cs(c("bnb", 10000000000), c("ukava", 10000000000)))
|
||||
}
|
||||
authGS := app.NewAuthGenState(addrs, coins)
|
||||
tApp.InitializeFromGenesisStates(authGS)
|
||||
incentiveGS := incentive.NewGenesisState(
|
||||
incentive.NewParams(
|
||||
incentive.RewardPeriods{incentive.NewRewardPeriod(true, "bnb-a", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 12, 15, 14, 0, 0, 0, time.UTC), c("ukava", 122354))},
|
||||
incentive.Multipliers{incentive.NewMultiplier(incentive.MultiplierName("small"), 1, d("0.25")), incentive.NewMultiplier(incentive.MultiplierName("large"), 12, d("1.0"))},
|
||||
time.Date(2025, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
),
|
||||
incentive.DefaultGenesisAccumulationTimes,
|
||||
incentive.DefaultClaims,
|
||||
)
|
||||
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{incentive.ModuleName: incentive.ModuleCdc.MustMarshalJSON(incentiveGS)})
|
||||
|
||||
suite.addrs = addrs
|
||||
suite.handler = incentive.NewHandler(keeper)
|
||||
@ -51,15 +61,10 @@ func (suite *HandlerTestSuite) SetupTest() {
|
||||
}
|
||||
|
||||
func (suite *HandlerTestSuite) addClaim() {
|
||||
supplyKeeper := suite.app.GetSupplyKeeper()
|
||||
macc := supplyKeeper.GetModuleAccount(suite.ctx, kavadist.ModuleName)
|
||||
err := supplyKeeper.MintCoins(suite.ctx, macc.GetName(), cs(c("ukava", 1000000)))
|
||||
sk := suite.app.GetSupplyKeeper()
|
||||
err := sk.MintCoins(suite.ctx, kavadist.ModuleName, cs(c("ukava", 1000000000000)))
|
||||
suite.Require().NoError(err)
|
||||
cp := incentive.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), incentive.Multipliers{incentive.NewMultiplier(incentive.Small, 1, sdk.MustNewDecFromStr("0.33"))})
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.SetClaimPeriod(suite.ctx, cp)
|
||||
})
|
||||
c1 := incentive.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1)
|
||||
c1 := incentive.NewUSDXMintingClaim(suite.addrs[0], c("ukava", 1000000), types.RewardIndexes{types.NewRewardIndex("bnb-s", sdk.ZeroDec())})
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.SetClaim(suite.ctx, c1)
|
||||
})
|
||||
@ -67,7 +72,7 @@ func (suite *HandlerTestSuite) addClaim() {
|
||||
|
||||
func (suite *HandlerTestSuite) TestMsgClaimReward() {
|
||||
suite.addClaim()
|
||||
msg := incentive.NewMsgClaimReward(suite.addrs[0], "bnb", "small")
|
||||
msg := incentive.NewMsgClaimUSDXMintingReward(suite.addrs[0], "small")
|
||||
res, err := suite.handler(suite.ctx, msg)
|
||||
suite.NoError(err)
|
||||
suite.Require().NotNil(res)
|
||||
@ -75,3 +80,7 @@ func (suite *HandlerTestSuite) TestMsgClaimReward() {
|
||||
func TestHandlerTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(HandlerTestSuite))
|
||||
}
|
||||
|
||||
// Avoid cluttering test cases with long function names
|
||||
func i(in int64) sdk.Int { return sdk.NewInt(in) }
|
||||
func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) }
|
||||
|
28
x/incentive/keeper/hooks.go
Normal file
28
x/incentive/keeper/hooks.go
Normal file
@ -0,0 +1,28 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
)
|
||||
|
||||
// Hooks wrapper struct for hooks
|
||||
type Hooks struct {
|
||||
k Keeper
|
||||
}
|
||||
|
||||
var _ cdptypes.CDPHooks = Hooks{}
|
||||
|
||||
// Hooks create new incentive hooks
|
||||
func (k Keeper) Hooks() Hooks { return Hooks{k} }
|
||||
|
||||
// AfterCDPCreated function that runs after a cdp is created
|
||||
func (h Hooks) AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP) {
|
||||
h.k.InitializeClaim(ctx, cdp)
|
||||
}
|
||||
|
||||
// BeforeCDPModified function that runs before a cdp is modified
|
||||
// note that this is called immediately after interest is synchronized, and so could potentially
|
||||
// be called AfterCDPInterestUpdated or something like that, if we we're to expand the scope of cdp hooks
|
||||
func (h Hooks) BeforeCDPModified(ctx sdk.Context, cdp cdptypes.CDP) {
|
||||
h.k.SynchronizeReward(ctx, cdp)
|
||||
}
|
212
x/incentive/keeper/integration_test.go
Normal file
212
x/incentive/keeper/integration_test.go
Normal file
@ -0,0 +1,212 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
"github.com/kava-labs/kava/x/incentive"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
)
|
||||
|
||||
func NewCDPGenStateMulti() app.GenesisState {
|
||||
cdpGenesis := cdp.GenesisState{
|
||||
Params: cdp.Params{
|
||||
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
|
||||
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
|
||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||
CollateralParams: cdp.CollateralParams{
|
||||
{
|
||||
Denom: "xrp",
|
||||
Type: "xrp-a",
|
||||
LiquidationRatio: sdk.MustNewDecFromStr("2.0"),
|
||||
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
|
||||
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
|
||||
LiquidationPenalty: d("0.05"),
|
||||
AuctionSize: i(7000000000),
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "xrp:usd",
|
||||
LiquidationMarketID: "xrp:usd",
|
||||
ConversionFactor: i(6),
|
||||
},
|
||||
{
|
||||
Denom: "btc",
|
||||
Type: "btc-a",
|
||||
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
|
||||
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
|
||||
StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr
|
||||
LiquidationPenalty: d("0.025"),
|
||||
AuctionSize: i(10000000),
|
||||
Prefix: 0x21,
|
||||
SpotMarketID: "btc:usd",
|
||||
LiquidationMarketID: "btc:usd",
|
||||
ConversionFactor: i(8),
|
||||
},
|
||||
{
|
||||
Denom: "bnb",
|
||||
Type: "bnb-a",
|
||||
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
|
||||
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
|
||||
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
|
||||
LiquidationPenalty: d("0.05"),
|
||||
AuctionSize: i(50000000000),
|
||||
Prefix: 0x22,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
ConversionFactor: i(8),
|
||||
},
|
||||
{
|
||||
Denom: "busd",
|
||||
Type: "busd-a",
|
||||
LiquidationRatio: d("1.01"),
|
||||
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
|
||||
StabilityFee: sdk.OneDec(), // %0 apr
|
||||
LiquidationPenalty: d("0.05"),
|
||||
AuctionSize: i(10000000000),
|
||||
Prefix: 0x23,
|
||||
SpotMarketID: "busd:usd",
|
||||
LiquidationMarketID: "busd:usd",
|
||||
ConversionFactor: i(8),
|
||||
},
|
||||
},
|
||||
DebtParam: cdp.DebtParam{
|
||||
Denom: "usdx",
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: i(6),
|
||||
DebtFloor: i(10000000),
|
||||
},
|
||||
},
|
||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||
DebtDenom: cdp.DefaultDebtDenom,
|
||||
GovDenom: cdp.DefaultGovDenom,
|
||||
CDPs: cdp.CDPs{},
|
||||
PreviousAccumulationTimes: cdp.GenesisAccumulationTimes{
|
||||
cdp.NewGenesisAccumulationTime("btc-a", time.Time{}, sdk.OneDec()),
|
||||
cdp.NewGenesisAccumulationTime("xrp-a", time.Time{}, sdk.OneDec()),
|
||||
cdp.NewGenesisAccumulationTime("busd-a", time.Time{}, sdk.OneDec()),
|
||||
cdp.NewGenesisAccumulationTime("bnb-a", time.Time{}, sdk.OneDec()),
|
||||
},
|
||||
TotalPrincipals: cdp.GenesisTotalPrincipals{
|
||||
cdp.NewGenesisTotalPrincipal("btc-a", sdk.ZeroInt()),
|
||||
cdp.NewGenesisTotalPrincipal("xrp-a", sdk.ZeroInt()),
|
||||
cdp.NewGenesisTotalPrincipal("busd-a", sdk.ZeroInt()),
|
||||
cdp.NewGenesisTotalPrincipal("bnb-a", sdk.ZeroInt()),
|
||||
},
|
||||
}
|
||||
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
|
||||
}
|
||||
|
||||
func NewPricefeedGenStateMulti() app.GenesisState {
|
||||
pfGenesis := pricefeed.GenesisState{
|
||||
Params: pricefeed.Params{
|
||||
Markets: []pricefeed.Market{
|
||||
{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||
{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||
{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||
{MarketID: "busd:usd", BaseAsset: "busd", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||
},
|
||||
},
|
||||
PostedPrices: []pricefeed.PostedPrice{
|
||||
{
|
||||
MarketID: "btc:usd",
|
||||
OracleAddress: sdk.AccAddress{},
|
||||
Price: sdk.MustNewDecFromStr("8000.00"),
|
||||
Expiry: time.Now().Add(1 * time.Hour),
|
||||
},
|
||||
{
|
||||
MarketID: "xrp:usd",
|
||||
OracleAddress: sdk.AccAddress{},
|
||||
Price: sdk.MustNewDecFromStr("0.25"),
|
||||
Expiry: time.Now().Add(1 * time.Hour),
|
||||
},
|
||||
{
|
||||
MarketID: "bnb:usd",
|
||||
OracleAddress: sdk.AccAddress{},
|
||||
Price: sdk.MustNewDecFromStr("17.25"),
|
||||
Expiry: time.Now().Add(1 * time.Hour),
|
||||
},
|
||||
{
|
||||
MarketID: "busd:usd",
|
||||
OracleAddress: sdk.AccAddress{},
|
||||
Price: sdk.OneDec(),
|
||||
Expiry: time.Now().Add(1 * time.Hour),
|
||||
},
|
||||
},
|
||||
}
|
||||
return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)}
|
||||
}
|
||||
|
||||
func NewIncentiveGenState(previousAccumTime, endTime time.Time, rewardPeriods ...incentive.RewardPeriod) app.GenesisState {
|
||||
var accumulationTimes incentive.GenesisAccumulationTimes
|
||||
for _, rp := range rewardPeriods {
|
||||
accumulationTimes = append(
|
||||
accumulationTimes,
|
||||
incentive.NewGenesisAccumulationTime(
|
||||
rp.CollateralType,
|
||||
previousAccumTime,
|
||||
sdk.ZeroDec(),
|
||||
),
|
||||
)
|
||||
}
|
||||
genesis := incentive.NewGenesisState(
|
||||
incentive.NewParams(
|
||||
rewardPeriods,
|
||||
incentive.Multipliers{
|
||||
incentive.NewMultiplier(incentive.Small, 1, d("0.25")),
|
||||
incentive.NewMultiplier(incentive.Large, 12, d("1.0")),
|
||||
},
|
||||
endTime,
|
||||
),
|
||||
accumulationTimes,
|
||||
incentive.USDXMintingClaims{},
|
||||
)
|
||||
return app.GenesisState{incentive.ModuleName: incentive.ModuleCdc.MustMarshalJSON(genesis)}
|
||||
}
|
||||
|
||||
func NewCDPGenStateHighInterest() app.GenesisState {
|
||||
cdpGenesis := cdp.GenesisState{
|
||||
Params: cdp.Params{
|
||||
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
|
||||
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
|
||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||
CollateralParams: cdp.CollateralParams{
|
||||
{
|
||||
Denom: "bnb",
|
||||
Type: "bnb-a",
|
||||
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
|
||||
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
|
||||
StabilityFee: sdk.MustNewDecFromStr("1.000000051034942716"), // 500% APR
|
||||
LiquidationPenalty: d("0.05"),
|
||||
AuctionSize: i(50000000000),
|
||||
Prefix: 0x22,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
ConversionFactor: i(8),
|
||||
},
|
||||
},
|
||||
DebtParam: cdp.DebtParam{
|
||||
Denom: "usdx",
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: i(6),
|
||||
DebtFloor: i(10000000),
|
||||
},
|
||||
},
|
||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||
DebtDenom: cdp.DefaultDebtDenom,
|
||||
GovDenom: cdp.DefaultGovDenom,
|
||||
CDPs: cdp.CDPs{},
|
||||
PreviousAccumulationTimes: cdp.GenesisAccumulationTimes{
|
||||
cdp.NewGenesisAccumulationTime("bnb-a", time.Time{}, sdk.OneDec()),
|
||||
},
|
||||
TotalPrincipals: cdp.GenesisTotalPrincipals{
|
||||
cdp.NewGenesisTotalPrincipal("bnb-a", sdk.ZeroInt()),
|
||||
},
|
||||
}
|
||||
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
|
||||
}
|
@ -37,182 +37,39 @@ func NewKeeper(
|
||||
}
|
||||
}
|
||||
|
||||
// GetRewardPeriod returns the reward period from the store for the input collateral type and a boolean for if it was found
|
||||
func (k Keeper) GetRewardPeriod(ctx sdk.Context, collateralType string) (types.RewardPeriod, bool) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
|
||||
bz := store.Get([]byte(collateralType))
|
||||
if bz == nil {
|
||||
return types.RewardPeriod{}, false
|
||||
}
|
||||
var rp types.RewardPeriod
|
||||
k.cdc.MustUnmarshalBinaryBare(bz, &rp)
|
||||
return rp, true
|
||||
}
|
||||
|
||||
// SetRewardPeriod sets the reward period in the store for the input deno,
|
||||
func (k Keeper) SetRewardPeriod(ctx sdk.Context, rp types.RewardPeriod) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
|
||||
bz := k.cdc.MustMarshalBinaryBare(rp)
|
||||
store.Set([]byte(rp.CollateralType), bz)
|
||||
}
|
||||
|
||||
// DeleteRewardPeriod deletes the reward period in the store for the input collateral type,
|
||||
func (k Keeper) DeleteRewardPeriod(ctx sdk.Context, collateralType string) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
|
||||
store.Delete([]byte(collateralType))
|
||||
}
|
||||
|
||||
// IterateRewardPeriods iterates over all reward period objects in the store and preforms a callback function
|
||||
func (k Keeper) IterateRewardPeriods(ctx sdk.Context, cb func(rp types.RewardPeriod) (stop bool)) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
|
||||
iterator := sdk.KVStorePrefixIterator(store, []byte{})
|
||||
defer iterator.Close()
|
||||
for ; iterator.Valid(); iterator.Next() {
|
||||
var rp types.RewardPeriod
|
||||
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &rp)
|
||||
if cb(rp) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllRewardPeriods returns all reward periods in the store
|
||||
func (k Keeper) GetAllRewardPeriods(ctx sdk.Context) types.RewardPeriods {
|
||||
rps := types.RewardPeriods{}
|
||||
k.IterateRewardPeriods(ctx, func(rp types.RewardPeriod) (stop bool) {
|
||||
rps = append(rps, rp)
|
||||
return false
|
||||
})
|
||||
return rps
|
||||
}
|
||||
|
||||
// GetNextClaimPeriodID returns the highest claim period id in the store for the input collateral type
|
||||
func (k Keeper) GetNextClaimPeriodID(ctx sdk.Context, collateralType string) uint64 {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.NextClaimPeriodIDPrefix)
|
||||
bz := store.Get([]byte(collateralType))
|
||||
if bz == nil {
|
||||
k.SetNextClaimPeriodID(ctx, collateralType, 1)
|
||||
return uint64(1)
|
||||
}
|
||||
return types.BytesToUint64(bz)
|
||||
}
|
||||
|
||||
// SetNextClaimPeriodID sets the highest claim period id in the store for the input collateral type
|
||||
func (k Keeper) SetNextClaimPeriodID(ctx sdk.Context, collateralType string, id uint64) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.NextClaimPeriodIDPrefix)
|
||||
store.Set([]byte(collateralType), sdk.Uint64ToBigEndian(id))
|
||||
}
|
||||
|
||||
// IterateClaimPeriodIDKeysAndValues iterates over the claim period id (value) and collateral type (key) of each claim period id in the store and performs a callback function
|
||||
func (k Keeper) IterateClaimPeriodIDKeysAndValues(ctx sdk.Context, cb func(collateralType string, id uint64) (stop bool)) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.NextClaimPeriodIDPrefix)
|
||||
iterator := sdk.KVStorePrefixIterator(store, []byte{})
|
||||
defer iterator.Close()
|
||||
for ; iterator.Valid(); iterator.Next() {
|
||||
id := types.BytesToUint64(iterator.Value())
|
||||
collateralType := string(iterator.Key())
|
||||
if cb(collateralType, id) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllClaimPeriodIDPairs returns all collateralType:nextClaimPeriodID pairs in the store
|
||||
func (k Keeper) GetAllClaimPeriodIDPairs(ctx sdk.Context) types.GenesisClaimPeriodIDs {
|
||||
ids := types.GenesisClaimPeriodIDs{}
|
||||
k.IterateClaimPeriodIDKeysAndValues(ctx, func(collateralType string, id uint64) (stop bool) {
|
||||
genID := types.GenesisClaimPeriodID{
|
||||
CollateralType: collateralType,
|
||||
ID: id,
|
||||
}
|
||||
ids = append(ids, genID)
|
||||
return false
|
||||
})
|
||||
return ids
|
||||
}
|
||||
|
||||
// GetClaimPeriod returns claim period in the store for the input ID and collateral type and a boolean for if it was found
|
||||
func (k Keeper) GetClaimPeriod(ctx sdk.Context, id uint64, collateralType string) (types.ClaimPeriod, bool) {
|
||||
var cp types.ClaimPeriod
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix)
|
||||
bz := store.Get(types.GetClaimPeriodPrefix(collateralType, id))
|
||||
if bz == nil {
|
||||
return types.ClaimPeriod{}, false
|
||||
}
|
||||
k.cdc.MustUnmarshalBinaryBare(bz, &cp)
|
||||
return cp, true
|
||||
}
|
||||
|
||||
// SetClaimPeriod sets the claim period in the store for the input ID and collateral type
|
||||
func (k Keeper) SetClaimPeriod(ctx sdk.Context, cp types.ClaimPeriod) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix)
|
||||
bz := k.cdc.MustMarshalBinaryBare(cp)
|
||||
store.Set(types.GetClaimPeriodPrefix(cp.CollateralType, cp.ID), bz)
|
||||
}
|
||||
|
||||
// DeleteClaimPeriod deletes the claim period in the store for the input ID and collateral type
|
||||
func (k Keeper) DeleteClaimPeriod(ctx sdk.Context, id uint64, collateralType string) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix)
|
||||
store.Delete(types.GetClaimPeriodPrefix(collateralType, id))
|
||||
}
|
||||
|
||||
// IterateClaimPeriods iterates over all claim period objects in the store and preforms a callback function
|
||||
func (k Keeper) IterateClaimPeriods(ctx sdk.Context, cb func(cp types.ClaimPeriod) (stop bool)) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix)
|
||||
iterator := sdk.KVStorePrefixIterator(store, []byte{})
|
||||
defer iterator.Close()
|
||||
for ; iterator.Valid(); iterator.Next() {
|
||||
var cp types.ClaimPeriod
|
||||
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &cp)
|
||||
if cb(cp) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllClaimPeriods returns all ClaimPeriod objects in the store
|
||||
func (k Keeper) GetAllClaimPeriods(ctx sdk.Context) types.ClaimPeriods {
|
||||
cps := types.ClaimPeriods{}
|
||||
k.IterateClaimPeriods(ctx, func(cp types.ClaimPeriod) (stop bool) {
|
||||
cps = append(cps, cp)
|
||||
return false
|
||||
})
|
||||
return cps
|
||||
}
|
||||
|
||||
// GetClaim returns the claim in the store corresponding the the input address collateral type and id and a boolean for if the claim was found
|
||||
func (k Keeper) GetClaim(ctx sdk.Context, addr sdk.AccAddress, collateralType string, id uint64) (types.Claim, bool) {
|
||||
func (k Keeper) GetClaim(ctx sdk.Context, addr sdk.AccAddress) (types.USDXMintingClaim, bool) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix)
|
||||
bz := store.Get(types.GetClaimPrefix(addr, collateralType, id))
|
||||
bz := store.Get(addr)
|
||||
if bz == nil {
|
||||
return types.Claim{}, false
|
||||
return types.USDXMintingClaim{}, false
|
||||
}
|
||||
var c types.Claim
|
||||
var c types.USDXMintingClaim
|
||||
k.cdc.MustUnmarshalBinaryBare(bz, &c)
|
||||
return c, true
|
||||
}
|
||||
|
||||
// SetClaim sets the claim in the store corresponding to the input address, collateral type, and id
|
||||
func (k Keeper) SetClaim(ctx sdk.Context, c types.Claim) {
|
||||
func (k Keeper) SetClaim(ctx sdk.Context, c types.USDXMintingClaim) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix)
|
||||
bz := k.cdc.MustMarshalBinaryBare(c)
|
||||
store.Set(types.GetClaimPrefix(c.Owner, c.CollateralType, c.ClaimPeriodID), bz)
|
||||
store.Set(c.Owner, bz)
|
||||
|
||||
}
|
||||
|
||||
// DeleteClaim deletes the claim in the store corresponding to the input address, collateral type, and id
|
||||
func (k Keeper) DeleteClaim(ctx sdk.Context, owner sdk.AccAddress, collateralType string, id uint64) {
|
||||
func (k Keeper) DeleteClaim(ctx sdk.Context, owner sdk.AccAddress) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix)
|
||||
store.Delete(types.GetClaimPrefix(owner, collateralType, id))
|
||||
store.Delete(owner)
|
||||
}
|
||||
|
||||
// IterateClaims iterates over all claim objects in the store and preforms a callback function
|
||||
func (k Keeper) IterateClaims(ctx sdk.Context, cb func(c types.Claim) (stop bool)) {
|
||||
func (k Keeper) IterateClaims(ctx sdk.Context, cb func(c types.USDXMintingClaim) (stop bool)) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix)
|
||||
iterator := sdk.KVStorePrefixIterator(store, []byte{})
|
||||
defer iterator.Close()
|
||||
for ; iterator.Valid(); iterator.Next() {
|
||||
var c types.Claim
|
||||
var c types.USDXMintingClaim
|
||||
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &c)
|
||||
if cb(c) {
|
||||
break
|
||||
@ -221,28 +78,61 @@ func (k Keeper) IterateClaims(ctx sdk.Context, cb func(c types.Claim) (stop bool
|
||||
}
|
||||
|
||||
// GetAllClaims returns all Claim objects in the store
|
||||
func (k Keeper) GetAllClaims(ctx sdk.Context) types.Claims {
|
||||
cs := types.Claims{}
|
||||
k.IterateClaims(ctx, func(c types.Claim) (stop bool) {
|
||||
func (k Keeper) GetAllClaims(ctx sdk.Context) types.USDXMintingClaims {
|
||||
cs := types.USDXMintingClaims{}
|
||||
k.IterateClaims(ctx, func(c types.USDXMintingClaim) (stop bool) {
|
||||
cs = append(cs, c)
|
||||
return false
|
||||
})
|
||||
return cs
|
||||
}
|
||||
|
||||
// GetPreviousBlockTime get the blocktime for the previous block
|
||||
func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time, found bool) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
|
||||
b := store.Get([]byte{})
|
||||
if b == nil {
|
||||
// GetPreviousAccrualTime returns the last time a collateral type accrued rewards
|
||||
func (k Keeper) GetPreviousAccrualTime(ctx sdk.Context, ctype string) (blockTime time.Time, found bool) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.BlockTimeKey)
|
||||
bz := store.Get([]byte(ctype))
|
||||
if bz == nil {
|
||||
return time.Time{}, false
|
||||
}
|
||||
k.cdc.MustUnmarshalBinaryBare(b, &blockTime)
|
||||
k.cdc.MustUnmarshalBinaryBare(bz, &blockTime)
|
||||
return blockTime, true
|
||||
}
|
||||
|
||||
// SetPreviousBlockTime set the time of the previous block
|
||||
func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
|
||||
store.Set([]byte{}, k.cdc.MustMarshalBinaryBare(blockTime))
|
||||
// SetPreviousAccrualTime sets the last time a collateral type accrued rewards
|
||||
func (k Keeper) SetPreviousAccrualTime(ctx sdk.Context, ctype string, blockTime time.Time) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.BlockTimeKey)
|
||||
store.Set([]byte(ctype), k.cdc.MustMarshalBinaryBare(blockTime))
|
||||
}
|
||||
|
||||
// IterateAccrualTimes iterates over all previous accrual times and preforms a callback function
|
||||
func (k Keeper) IterateAccrualTimes(ctx sdk.Context, cb func(string, time.Time) (stop bool)) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.BlockTimeKey)
|
||||
iterator := sdk.KVStorePrefixIterator(store, []byte{})
|
||||
defer iterator.Close()
|
||||
for ; iterator.Valid(); iterator.Next() {
|
||||
var accrualTime time.Time
|
||||
var collateralType string
|
||||
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &collateralType)
|
||||
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &accrualTime)
|
||||
if cb(collateralType, accrualTime) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetRewardFactor returns the current reward factor for an individual collateral type
|
||||
func (k Keeper) GetRewardFactor(ctx sdk.Context, ctype string) (factor sdk.Dec, found bool) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardFactorKey)
|
||||
bz := store.Get([]byte(ctype))
|
||||
if bz == nil {
|
||||
return sdk.ZeroDec(), false
|
||||
}
|
||||
k.cdc.MustUnmarshalBinaryBare(bz, &factor)
|
||||
return factor, true
|
||||
}
|
||||
|
||||
// SetRewardFactor sets the current reward factor for an individual collateral type
|
||||
func (k Keeper) SetRewardFactor(ctx sdk.Context, ctype string, factor sdk.Dec) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardFactorKey)
|
||||
store.Set([]byte(ctype), k.cdc.MustMarshalBinaryBare(factor))
|
||||
}
|
||||
|
@ -2,12 +2,13 @@ package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
@ -33,7 +34,7 @@ func (suite *KeeperTestSuite) SetupTest() {
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
tApp.InitializeFromGenesisStates()
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||
keeper := tApp.GetIncentiveKeeper()
|
||||
suite.app = tApp
|
||||
suite.ctx = ctx
|
||||
@ -51,129 +52,63 @@ func (suite *KeeperTestSuite) getModuleAccount(name string) supplyexported.Modul
|
||||
return sk.GetModuleAccount(suite.ctx, name)
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestGetSetDeleteRewardPeriod() {
|
||||
rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
|
||||
_, found := suite.keeper.GetRewardPeriod(suite.ctx, "bnb")
|
||||
suite.False(found)
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.SetRewardPeriod(suite.ctx, rp)
|
||||
})
|
||||
testRP, found := suite.keeper.GetRewardPeriod(suite.ctx, "bnb")
|
||||
suite.True(found)
|
||||
suite.Equal(rp, testRP)
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.DeleteRewardPeriod(suite.ctx, "bnb")
|
||||
})
|
||||
_, found = suite.keeper.GetRewardPeriod(suite.ctx, "bnb")
|
||||
suite.False(found)
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestGetSetDeleteClaimPeriod() {
|
||||
cp := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
|
||||
_, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb")
|
||||
suite.False(found)
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.SetClaimPeriod(suite.ctx, cp)
|
||||
})
|
||||
testCP, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb")
|
||||
suite.True(found)
|
||||
suite.Equal(cp, testCP)
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.DeleteClaimPeriod(suite.ctx, 1, "bnb")
|
||||
})
|
||||
_, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb")
|
||||
suite.False(found)
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestGetSetClaimPeriodID() {
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.GetNextClaimPeriodID(suite.ctx, "yolo")
|
||||
})
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1)
|
||||
})
|
||||
testID := suite.keeper.GetNextClaimPeriodID(suite.ctx, "bnb")
|
||||
suite.Equal(uint64(1), testID)
|
||||
testID = suite.keeper.GetNextClaimPeriodID(suite.ctx, "yolo")
|
||||
suite.Equal(uint64(1), testID)
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestGetSetDeleteClaim() {
|
||||
c := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1)
|
||||
_, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1)
|
||||
suite.False(found)
|
||||
suite.NotPanics(func() {
|
||||
c := types.NewUSDXMintingClaim(suite.addrs[0], c("ukava", 1000000), types.RewardIndexes{types.NewRewardIndex("bnb-a", sdk.ZeroDec())})
|
||||
_, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||
suite.Require().False(found)
|
||||
suite.Require().NotPanics(func() {
|
||||
suite.keeper.SetClaim(suite.ctx, c)
|
||||
})
|
||||
testC, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1)
|
||||
suite.True(found)
|
||||
suite.Equal(c, testC)
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.DeleteClaim(suite.ctx, suite.addrs[0], "bnb", 1)
|
||||
testC, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||
suite.Require().True(found)
|
||||
suite.Require().Equal(c, testC)
|
||||
suite.Require().NotPanics(func() {
|
||||
suite.keeper.DeleteClaim(suite.ctx, suite.addrs[0])
|
||||
})
|
||||
_, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1)
|
||||
suite.False(found)
|
||||
_, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||
suite.Require().False(found)
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestIterateMethods() {
|
||||
suite.addObjectsToStore() // adds 2 objects of each type to the store
|
||||
|
||||
var rewardPeriods types.RewardPeriods
|
||||
suite.keeper.IterateRewardPeriods(suite.ctx, func(rp types.RewardPeriod) (stop bool) {
|
||||
rewardPeriods = append(rewardPeriods, rp)
|
||||
return false
|
||||
func (suite *KeeperTestSuite) TestIterateClaims() {
|
||||
for i := 0; i < len(suite.addrs); i++ {
|
||||
c := types.NewUSDXMintingClaim(suite.addrs[i], c("ukava", 100000), types.RewardIndexes{types.NewRewardIndex("bnb-a", sdk.ZeroDec())})
|
||||
suite.Require().NotPanics(func() {
|
||||
suite.keeper.SetClaim(suite.ctx, c)
|
||||
})
|
||||
suite.Equal(2, len(rewardPeriods))
|
||||
|
||||
var claimPeriods types.ClaimPeriods
|
||||
suite.keeper.IterateClaimPeriods(suite.ctx, func(cp types.ClaimPeriod) (stop bool) {
|
||||
claimPeriods = append(claimPeriods, cp)
|
||||
return false
|
||||
})
|
||||
suite.Equal(2, len(claimPeriods))
|
||||
|
||||
var claims types.Claims
|
||||
suite.keeper.IterateClaims(suite.ctx, func(c types.Claim) (stop bool) {
|
||||
}
|
||||
claims := types.USDXMintingClaims{}
|
||||
suite.keeper.IterateClaims(suite.ctx, func(c types.USDXMintingClaim) bool {
|
||||
claims = append(claims, c)
|
||||
return false
|
||||
})
|
||||
suite.Equal(2, len(claims))
|
||||
|
||||
var genIDs types.GenesisClaimPeriodIDs
|
||||
suite.keeper.IterateClaimPeriodIDKeysAndValues(suite.ctx, func(collateralType string, id uint64) (stop bool) {
|
||||
genID := types.GenesisClaimPeriodID{CollateralType: collateralType, ID: id}
|
||||
genIDs = append(genIDs, genID)
|
||||
return false
|
||||
})
|
||||
suite.Equal(2, len(genIDs))
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) addObjectsToStore() {
|
||||
rp1 := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
|
||||
rp2 := types.NewRewardPeriod("xrp", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
|
||||
suite.keeper.SetRewardPeriod(suite.ctx, rp1)
|
||||
suite.keeper.SetRewardPeriod(suite.ctx, rp2)
|
||||
|
||||
cp1 := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
|
||||
cp2 := types.NewClaimPeriod("xrp", 1, suite.ctx.BlockTime().Add(time.Hour*168), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
|
||||
suite.keeper.SetClaimPeriod(suite.ctx, cp1)
|
||||
suite.keeper.SetClaimPeriod(suite.ctx, cp2)
|
||||
|
||||
suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 2)
|
||||
suite.keeper.SetNextClaimPeriodID(suite.ctx, "xrp", 2)
|
||||
|
||||
c1 := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1)
|
||||
c2 := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "xrp", 1)
|
||||
suite.keeper.SetClaim(suite.ctx, c1)
|
||||
suite.keeper.SetClaim(suite.ctx, c2)
|
||||
|
||||
params := types.NewParams(
|
||||
true, types.Rewards{types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))}, time.Hour*7*24)},
|
||||
)
|
||||
suite.keeper.SetParams(suite.ctx, params)
|
||||
suite.Require().Equal(len(suite.addrs), len(claims))
|
||||
|
||||
claims = suite.keeper.GetAllClaims(suite.ctx)
|
||||
suite.Require().Equal(len(suite.addrs), len(claims))
|
||||
}
|
||||
|
||||
func TestKeeperTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(KeeperTestSuite))
|
||||
}
|
||||
|
||||
func createPeriodicVestingAccount(origVesting sdk.Coins, periods vesting.Periods, startTime, endTime int64) (*vesting.PeriodicVestingAccount, error) {
|
||||
_, addr := app.GeneratePrivKeyAddressPairs(1)
|
||||
bacc := auth.NewBaseAccountWithAddress(addr[0])
|
||||
bacc.Coins = origVesting
|
||||
bva, err := vesting.NewBaseVestingAccount(&bacc, origVesting, endTime)
|
||||
if err != nil {
|
||||
return &vesting.PeriodicVestingAccount{}, err
|
||||
}
|
||||
pva := vesting.NewPeriodicVestingAccountRaw(bva, startTime, periods)
|
||||
err = pva.Validate()
|
||||
if err != nil {
|
||||
return &vesting.PeriodicVestingAccount{}, err
|
||||
}
|
||||
return pva, nil
|
||||
}
|
||||
|
||||
// Avoid cluttering test cases with long function names
|
||||
func i(in int64) sdk.Int { return sdk.NewInt(in) }
|
||||
func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) }
|
||||
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
|
||||
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
|
||||
|
@ -1,6 +1,8 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
@ -17,3 +19,31 @@ func (k Keeper) GetParams(ctx sdk.Context) types.Params {
|
||||
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
|
||||
k.paramSubspace.SetParamSet(ctx, ¶ms)
|
||||
}
|
||||
|
||||
// GetRewardPeriod returns the reward period with the specified collateral type if it's found in the params
|
||||
func (k Keeper) GetRewardPeriod(ctx sdk.Context, collateralType string) (types.RewardPeriod, bool) {
|
||||
params := k.GetParams(ctx)
|
||||
for _, rp := range params.RewardPeriods {
|
||||
if rp.CollateralType == collateralType {
|
||||
return rp, true
|
||||
}
|
||||
}
|
||||
return types.RewardPeriod{}, false
|
||||
}
|
||||
|
||||
// GetMultiplier returns the multiplier with the specified name if it's found in the params
|
||||
func (k Keeper) GetMultiplier(ctx sdk.Context, name types.MultiplierName) (types.Multiplier, bool) {
|
||||
params := k.GetParams(ctx)
|
||||
for _, m := range params.ClaimMultipliers {
|
||||
if m.Name == name {
|
||||
return m, true
|
||||
}
|
||||
}
|
||||
return types.Multiplier{}, false
|
||||
}
|
||||
|
||||
// GetClaimEnd returns the claim end time for the params
|
||||
func (k Keeper) GetClaimEnd(ctx sdk.Context) time.Time {
|
||||
params := k.GetParams(ctx)
|
||||
return params.ClaimEnd
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
@ -14,42 +12,48 @@ import (
|
||||
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||
)
|
||||
|
||||
// PayoutClaim sends the timelocked claim coins to the input address
|
||||
func (k Keeper) PayoutClaim(ctx sdk.Context, addr sdk.AccAddress, collateralType string, id uint64, multiplierName types.MultiplierName) error {
|
||||
claim, found := k.GetClaim(ctx, addr, collateralType, id)
|
||||
// ClaimReward sends the reward amount to the input address and zero's out the claim in the store
|
||||
func (k Keeper) ClaimReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName types.MultiplierName) error {
|
||||
claim, found := k.GetClaim(ctx, addr)
|
||||
if !found {
|
||||
return sdkerrors.Wrapf(types.ErrClaimNotFound, "id: %d, collateral type %s, address: %s", id, collateralType, addr)
|
||||
}
|
||||
claimPeriod, found := k.GetClaimPeriod(ctx, id, collateralType)
|
||||
if !found {
|
||||
return sdkerrors.Wrapf(types.ErrClaimPeriodNotFound, "id: %d, collateral type: %s", id, collateralType)
|
||||
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", addr)
|
||||
}
|
||||
|
||||
multiplier, found := claimPeriod.GetMultiplier(multiplierName)
|
||||
multiplier, found := k.GetMultiplier(ctx, multiplierName)
|
||||
if !found {
|
||||
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
|
||||
}
|
||||
|
||||
rewardAmount := sdk.NewDecFromInt(claim.Reward.Amount).Mul(multiplier.Factor).RoundInt()
|
||||
claimEnd := k.GetClaimEnd(ctx)
|
||||
|
||||
if ctx.BlockTime().After(claimEnd) {
|
||||
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
|
||||
}
|
||||
|
||||
claim, err := k.SynchronizeClaim(ctx, claim)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rewardAmount := claim.Reward.Amount.ToDec().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)
|
||||
err = k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, addr, sdk.NewCoins(rewardCoin), length)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k.DeleteClaim(ctx, addr, collateralType, id)
|
||||
k.ZeroClaim(ctx, claim)
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeClaim,
|
||||
sdk.NewAttribute(types.AttributeKeyClaimedBy, addr.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.Reward.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyClaimPeriod, fmt.Sprintf("%d", claim.ClaimPeriodID)),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
@ -112,75 +116,6 @@ func (k Keeper) SendTimeLockedCoinsToBaseAccount(ctx sdk.Context, senderModule s
|
||||
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) {
|
||||
if !(c.CollateralType == cp.CollateralType && c.ClaimPeriodID == cp.ID) {
|
||||
return false
|
||||
}
|
||||
k.DeleteClaim(ctx, c.Owner, c.CollateralType, c.ClaimPeriodID)
|
||||
return false
|
||||
})
|
||||
k.DeleteClaimPeriod(ctx, cp.ID, cp.CollateralType)
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeClaimPeriodExpiry,
|
||||
sdk.NewAttribute(types.AttributeKeyClaimPeriod, cp.String()),
|
||||
),
|
||||
)
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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
|
||||
}
|
||||
found = true
|
||||
claims = append(claims, c)
|
||||
return false
|
||||
})
|
||||
return claims, found
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
@ -2,119 +2,154 @@ package keeper_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
"github.com/kava-labs/kava/x/kavadist"
|
||||
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
func (suite *KeeperTestSuite) setupChain() {
|
||||
// creates a new app state with 4 funded addresses and 1 module account
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: time.Unix(100, 0)})
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(4)
|
||||
authGS := app.NewAuthGenState(
|
||||
addrs,
|
||||
[]sdk.Coins{
|
||||
cs(c("ukava", 400)),
|
||||
cs(c("ukava", 400)),
|
||||
cs(c("ukava", 400)),
|
||||
cs(c("ukava", 400)),
|
||||
})
|
||||
tApp.InitializeFromGenesisStates(
|
||||
authGS,
|
||||
func (suite *KeeperTestSuite) TestPayoutClaim() {
|
||||
type args struct {
|
||||
ctype string
|
||||
rewardsPerSecond sdk.Coin
|
||||
initialTime time.Time
|
||||
initialCollateral sdk.Coin
|
||||
initialPrincipal sdk.Coin
|
||||
multipliers types.Multipliers
|
||||
multiplier types.MultiplierName
|
||||
timeElapsed int
|
||||
expectedBalance sdk.Coins
|
||||
expectedPeriods vesting.Periods
|
||||
isPeriodicVestingAccount bool
|
||||
}
|
||||
type errArgs struct {
|
||||
expectPass bool
|
||||
contains string
|
||||
}
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
errArgs errArgs
|
||||
}
|
||||
testCases := []test{
|
||||
{
|
||||
"valid 1 day",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
rewardsPerSecond: c("ukava", 122354),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000000),
|
||||
initialPrincipal: c("usdx", 10000000000),
|
||||
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
|
||||
multiplier: types.MultiplierName("large"),
|
||||
timeElapsed: 86400,
|
||||
expectedBalance: cs(c("usdx", 10000000000), c("ukava", 10571385600)),
|
||||
expectedPeriods: vesting.Periods{vesting.Period{Length: 31536000, Amount: cs(c("ukava", 10571385600))}},
|
||||
isPeriodicVestingAccount: true,
|
||||
},
|
||||
errArgs{
|
||||
expectPass: true,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid zero rewards",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
rewardsPerSecond: c("ukava", 0),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000000),
|
||||
initialPrincipal: c("usdx", 10000000000),
|
||||
multipliers: types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
|
||||
multiplier: types.MultiplierName("large"),
|
||||
timeElapsed: 86400,
|
||||
expectedBalance: cs(c("usdx", 10000000000)),
|
||||
expectedPeriods: vesting.Periods{},
|
||||
isPeriodicVestingAccount: false,
|
||||
},
|
||||
errArgs{
|
||||
expectPass: false,
|
||||
contains: "claim amount rounds to zero",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupWithCDPGenState()
|
||||
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
|
||||
|
||||
// setup incentive state
|
||||
params := types.NewParams(
|
||||
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
|
||||
tc.args.multipliers,
|
||||
tc.args.initialTime.Add(time.Hour*24*365*5),
|
||||
)
|
||||
supplyKeeper := tApp.GetSupplyKeeper()
|
||||
macc := supplyKeeper.GetModuleAccount(ctx, kavadist.ModuleName)
|
||||
err := supplyKeeper.MintCoins(ctx, macc.GetName(), cs(c("ukava", 600)))
|
||||
suite.keeper.SetParams(suite.ctx, params)
|
||||
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime)
|
||||
suite.keeper.SetRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec())
|
||||
|
||||
// setup account state
|
||||
sk := suite.app.GetSupplyKeeper()
|
||||
err := sk.MintCoins(suite.ctx, cdptypes.ModuleName, sdk.NewCoins(tc.args.initialCollateral))
|
||||
suite.Require().NoError(err)
|
||||
err = sk.SendCoinsFromModuleToAccount(suite.ctx, cdptypes.ModuleName, suite.addrs[0], sdk.NewCoins(tc.args.initialCollateral))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// sets addrs[0] to be a periodic vesting account
|
||||
ak := tApp.GetAccountKeeper()
|
||||
acc := ak.GetAccount(ctx, addrs[0])
|
||||
bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
|
||||
periods := vesting.Periods{
|
||||
vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))},
|
||||
vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))},
|
||||
vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))},
|
||||
vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))},
|
||||
}
|
||||
bva, err2 := vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), ctx.BlockTime().Unix()+16)
|
||||
suite.Require().NoError(err2)
|
||||
pva := vesting.NewPeriodicVestingAccountRaw(bva, ctx.BlockTime().Unix(), periods)
|
||||
ak.SetAccount(ctx, pva)
|
||||
// setup kavadist state
|
||||
err = sk.MintCoins(suite.ctx, kavadist.ModuleName, cs(c("ukava", 1000000000000)))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// sets addrs[2] to be a validator vesting account
|
||||
acc = ak.GetAccount(ctx, addrs[2])
|
||||
bacc = auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
|
||||
bva, err2 = vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), ctx.BlockTime().Unix()+16)
|
||||
suite.Require().NoError(err2)
|
||||
vva := validatorvesting.NewValidatorVestingAccountRaw(bva, ctx.BlockTime().Unix(), periods, sdk.ConsAddress{}, nil, 90)
|
||||
ak.SetAccount(ctx, vva)
|
||||
suite.app = tApp
|
||||
suite.keeper = tApp.GetIncentiveKeeper()
|
||||
suite.ctx = ctx
|
||||
suite.addrs = addrs
|
||||
// setup cdp state
|
||||
cdpKeeper := suite.app.GetCDPKeeper()
|
||||
err = cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
claim, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||
suite.Require().True(found)
|
||||
suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor)
|
||||
|
||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed))
|
||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||
rewardPeriod, found := suite.keeper.GetRewardPeriod(suite.ctx, tc.args.ctype)
|
||||
suite.Require().True(found)
|
||||
err = suite.keeper.AccumulateRewards(suite.ctx, rewardPeriod)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
err = suite.keeper.ClaimReward(suite.ctx, suite.addrs[0], tc.args.multiplier)
|
||||
|
||||
if tc.errArgs.expectPass {
|
||||
suite.Require().NoError(err)
|
||||
ak := suite.app.GetAccountKeeper()
|
||||
acc := ak.GetAccount(suite.ctx, suite.addrs[0])
|
||||
suite.Require().Equal(tc.args.expectedBalance, acc.GetCoins())
|
||||
|
||||
if tc.args.isPeriodicVestingAccount {
|
||||
vacc, ok := acc.(*vesting.PeriodicVestingAccount)
|
||||
suite.Require().True(ok)
|
||||
suite.Require().Equal(tc.args.expectedPeriods, vacc.VestingPeriods)
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) setupExpiredClaims() {
|
||||
// creates a new app state with 4 funded addresses
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: time.Unix(100, 0)})
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(4)
|
||||
authGS := app.NewAuthGenState(
|
||||
addrs,
|
||||
[]sdk.Coins{
|
||||
cs(c("ukava", 400)),
|
||||
cs(c("ukava", 400)),
|
||||
cs(c("ukava", 400)),
|
||||
cs(c("ukava", 400)),
|
||||
claim, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||
fmt.Println(claim)
|
||||
suite.Require().True(found)
|
||||
suite.Require().Equal(c("ukava", 0), claim.Reward)
|
||||
} else {
|
||||
suite.Require().Error(err)
|
||||
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||
}
|
||||
})
|
||||
tApp.InitializeFromGenesisStates(
|
||||
authGS,
|
||||
)
|
||||
|
||||
// creates two claim periods, one expired, and one that expires in the future
|
||||
cp1 := types.NewClaimPeriod("bnb", 1, time.Unix(90, 0), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})
|
||||
cp2 := types.NewClaimPeriod("xrp", 1, time.Unix(110, 0), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})
|
||||
suite.keeper = tApp.GetIncentiveKeeper()
|
||||
suite.keeper.SetClaimPeriod(ctx, cp1)
|
||||
suite.keeper.SetClaimPeriod(ctx, cp2)
|
||||
// creates one claim for the non-expired claim period and one claim for the expired claim period
|
||||
c1 := types.NewClaim(addrs[0], c("ukava", 1000000), "bnb", 1)
|
||||
c2 := types.NewClaim(addrs[0], c("ukava", 1000000), "xrp", 1)
|
||||
suite.keeper.SetClaim(ctx, c1)
|
||||
suite.keeper.SetClaim(ctx, c2)
|
||||
suite.app = tApp
|
||||
suite.ctx = ctx
|
||||
suite.addrs = addrs
|
||||
}
|
||||
|
||||
func createPeriodicVestingAccount(origVesting sdk.Coins, periods vesting.Periods, startTime, endTime int64) (*vesting.PeriodicVestingAccount, error) {
|
||||
_, addr := app.GeneratePrivKeyAddressPairs(1)
|
||||
bacc := auth.NewBaseAccountWithAddress(addr[0])
|
||||
bacc.Coins = origVesting
|
||||
bva, err := vesting.NewBaseVestingAccount(&bacc, origVesting, endTime)
|
||||
if err != nil {
|
||||
return &vesting.PeriodicVestingAccount{}, err
|
||||
}
|
||||
pva := vesting.NewPeriodicVestingAccountRaw(bva, startTime, periods)
|
||||
err = pva.Validate()
|
||||
if err != nil {
|
||||
return &vesting.PeriodicVestingAccount{}, err
|
||||
}
|
||||
return pva, nil
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestSendCoinsToPeriodicVestingAccount() {
|
||||
@ -391,7 +426,7 @@ func (suite *KeeperTestSuite) TestSendCoinsToPeriodicVestingAccount() {
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestSendCoinsToBaseAccount() {
|
||||
suite.setupChain()
|
||||
suite.SetupWithAccountState()
|
||||
// send coins to base account
|
||||
err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[1], cs(c("ukava", 100)), 5)
|
||||
suite.Require().NoError(err)
|
||||
@ -410,267 +445,59 @@ func (suite *KeeperTestSuite) TestSendCoinsToBaseAccount() {
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestSendCoinsToInvalidAccount() {
|
||||
suite.setupChain()
|
||||
suite.SetupWithAccountState()
|
||||
err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[2], cs(c("ukava", 100)), 5)
|
||||
suite.Require().True(errors.Is(err, types.ErrInvalidAccountType))
|
||||
macc := suite.getModuleAccount(cdp.ModuleName)
|
||||
macc := suite.getModuleAccount(cdptypes.ModuleName)
|
||||
err = suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, macc.GetAddress(), cs(c("ukava", 100)), 5)
|
||||
suite.Require().True(errors.Is(err, types.ErrInvalidAccountType))
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestDeleteExpiredClaimPeriods() {
|
||||
suite.setupExpiredClaims() // creates new app state with one non-expired claim period (xrp) and one expired claim period (bnb) as well as a claim that corresponds to each claim period
|
||||
|
||||
// both claim periods are present
|
||||
_, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb")
|
||||
suite.True(found)
|
||||
_, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "xrp")
|
||||
suite.True(found)
|
||||
// both claims are present
|
||||
_, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1)
|
||||
suite.True(found)
|
||||
_, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "xrp", 1)
|
||||
suite.True(found)
|
||||
|
||||
// expired claim period and associated claims should get deleted
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.DeleteExpiredClaimsAndClaimPeriods(suite.ctx)
|
||||
})
|
||||
// expired claim period and claim are not found
|
||||
_, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb")
|
||||
suite.False(found)
|
||||
_, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1)
|
||||
suite.False(found)
|
||||
// non-expired claim period and claim are found
|
||||
_, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "xrp")
|
||||
suite.True(found)
|
||||
_, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "xrp", 1)
|
||||
suite.True(found)
|
||||
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestPayoutClaim() {
|
||||
type args struct {
|
||||
claimOwner sdk.AccAddress
|
||||
collateralType string
|
||||
id uint64
|
||||
multiplier types.MultiplierName
|
||||
blockTime time.Time
|
||||
rewards types.Rewards
|
||||
rewardperiods types.RewardPeriods
|
||||
claimPeriods types.ClaimPeriods
|
||||
claims types.Claims
|
||||
genIDs types.GenesisClaimPeriodIDs
|
||||
active bool
|
||||
validatorVesting bool
|
||||
expectedAccountBalance sdk.Coins
|
||||
expectedModAccountBalance sdk.Coins
|
||||
expectedVestingAccount bool
|
||||
expectedVestingLength int64
|
||||
}
|
||||
type errArgs struct {
|
||||
expectPass bool
|
||||
contains string
|
||||
}
|
||||
type claimTest struct {
|
||||
name string
|
||||
args args
|
||||
errArgs errArgs
|
||||
}
|
||||
testCases := []claimTest{
|
||||
{
|
||||
"valid small claim",
|
||||
args{
|
||||
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
|
||||
collateralType: "bnb-a",
|
||||
id: 1,
|
||||
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
|
||||
rewards: types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
|
||||
rewardperiods: types.RewardPeriods{types.NewRewardPeriod("bnb-a", time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), c("ukava", 1000), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
|
||||
claimPeriods: types.ClaimPeriods{types.NewClaimPeriod("bnb-a", 1, time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
|
||||
claims: types.Claims{types.NewClaim(sdk.AccAddress(crypto.AddressHash([]byte("test"))), sdk.NewCoin("ukava", sdk.NewInt(1000)), "bnb-a", 1)},
|
||||
genIDs: types.GenesisClaimPeriodIDs{types.GenesisClaimPeriodID{CollateralType: "bnb-a", ID: 2}},
|
||||
active: true,
|
||||
validatorVesting: false,
|
||||
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
|
||||
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500))),
|
||||
expectedVestingAccount: true,
|
||||
expectedVestingLength: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).AddDate(0, 1, 0).Unix() - time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Unix(),
|
||||
multiplier: types.Small,
|
||||
},
|
||||
errArgs{
|
||||
expectPass: true,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"valid large claim",
|
||||
args{
|
||||
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
|
||||
collateralType: "bnb-a",
|
||||
id: 1,
|
||||
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
|
||||
rewards: types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
|
||||
rewardperiods: types.RewardPeriods{types.NewRewardPeriod("bnb-a", time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), c("ukava", 1000), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
|
||||
claimPeriods: types.ClaimPeriods{types.NewClaimPeriod("bnb-a", 1, time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
|
||||
claims: types.Claims{types.NewClaim(sdk.AccAddress(crypto.AddressHash([]byte("test"))), sdk.NewCoin("ukava", sdk.NewInt(1000)), "bnb-a", 1)},
|
||||
genIDs: types.GenesisClaimPeriodIDs{types.GenesisClaimPeriodID{CollateralType: "bnb-a", ID: 2}},
|
||||
active: true,
|
||||
validatorVesting: false,
|
||||
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
|
||||
expectedModAccountBalance: sdk.Coins(nil),
|
||||
expectedVestingAccount: true,
|
||||
expectedVestingLength: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).AddDate(0, 12, 0).Unix() - time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Unix(),
|
||||
multiplier: types.Large,
|
||||
},
|
||||
errArgs{
|
||||
expectPass: true,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"valid liquid claim",
|
||||
args{
|
||||
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
|
||||
collateralType: "bnb-a",
|
||||
id: 1,
|
||||
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
|
||||
rewards: types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
|
||||
rewardperiods: types.RewardPeriods{types.NewRewardPeriod("bnb-a", time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), c("ukava", 1000), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
|
||||
claimPeriods: types.ClaimPeriods{types.NewClaimPeriod("bnb-a", 1, time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
|
||||
claims: types.Claims{types.NewClaim(sdk.AccAddress(crypto.AddressHash([]byte("test"))), sdk.NewCoin("ukava", sdk.NewInt(1000)), "bnb-a", 1)},
|
||||
genIDs: types.GenesisClaimPeriodIDs{types.GenesisClaimPeriodID{CollateralType: "bnb-a", ID: 2}},
|
||||
active: true,
|
||||
validatorVesting: false,
|
||||
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
|
||||
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500))),
|
||||
expectedVestingAccount: false,
|
||||
expectedVestingLength: 0,
|
||||
multiplier: types.Small,
|
||||
},
|
||||
errArgs{
|
||||
expectPass: true,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"no matching claim",
|
||||
args{
|
||||
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
|
||||
collateralType: "btcb-a",
|
||||
id: 1,
|
||||
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
|
||||
rewards: types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
|
||||
rewardperiods: types.RewardPeriods{types.NewRewardPeriod("bnb-a", time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), c("ukava", 1000), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
|
||||
claimPeriods: types.ClaimPeriods{types.NewClaimPeriod("bnb-a", 1, time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
|
||||
claims: types.Claims{types.NewClaim(sdk.AccAddress(crypto.AddressHash([]byte("test"))), sdk.NewCoin("ukava", sdk.NewInt(1000)), "bnb-a", 1)},
|
||||
genIDs: types.GenesisClaimPeriodIDs{types.GenesisClaimPeriodID{CollateralType: "bnb-a", ID: 2}},
|
||||
active: true,
|
||||
validatorVesting: false,
|
||||
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
|
||||
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500))),
|
||||
expectedVestingAccount: false,
|
||||
expectedVestingLength: 0,
|
||||
multiplier: types.Small,
|
||||
},
|
||||
errArgs{
|
||||
expectPass: false,
|
||||
contains: "no claim with input id found for owner and collateral type",
|
||||
},
|
||||
},
|
||||
{
|
||||
"validator vesting claim",
|
||||
args{
|
||||
claimOwner: sdk.AccAddress(crypto.AddressHash([]byte("test"))),
|
||||
collateralType: "bnb-a",
|
||||
id: 1,
|
||||
blockTime: time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC),
|
||||
rewards: types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
|
||||
rewardperiods: types.RewardPeriods{types.NewRewardPeriod("bnb-a", time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), c("ukava", 1000), time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
|
||||
claimPeriods: types.ClaimPeriods{types.NewClaimPeriod("bnb-a", 1, time.Date(2020, 11, 1, 14, 0, 0, 0, time.UTC).Add(time.Hour*7*24), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
|
||||
claims: types.Claims{types.NewClaim(sdk.AccAddress(crypto.AddressHash([]byte("test"))), sdk.NewCoin("ukava", sdk.NewInt(1000)), "bnb-a", 1)},
|
||||
genIDs: types.GenesisClaimPeriodIDs{types.GenesisClaimPeriodID{CollateralType: "bnb-a", ID: 2}},
|
||||
active: true,
|
||||
validatorVesting: true,
|
||||
expectedAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500)), sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
|
||||
expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(500))),
|
||||
expectedVestingAccount: false,
|
||||
expectedVestingLength: 0,
|
||||
multiplier: types.Small,
|
||||
},
|
||||
errArgs{
|
||||
expectPass: false,
|
||||
contains: "account type not supported",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
// create new app with one funded account
|
||||
config := sdk.GetConfig()
|
||||
app.SetBech32AddressPrefixes(config)
|
||||
// Initialize test app and set context
|
||||
func (suite *KeeperTestSuite) SetupWithAccountState() {
|
||||
// creates a new app state with 4 funded addresses and 1 module account
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tc.args.blockTime})
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: time.Unix(100, 0)})
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(4)
|
||||
authGS := app.NewAuthGenState(
|
||||
[]sdk.AccAddress{tc.args.claimOwner},
|
||||
addrs,
|
||||
[]sdk.Coins{
|
||||
sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
|
||||
cs(c("ukava", 400)),
|
||||
cs(c("ukava", 400)),
|
||||
cs(c("ukava", 400)),
|
||||
cs(c("ukava", 400)),
|
||||
})
|
||||
incentiveGS := types.NewGenesisState(types.NewParams(tc.args.active, tc.args.rewards), types.DefaultPreviousBlockTime, tc.args.rewardperiods, tc.args.claimPeriods, tc.args.claims, tc.args.genIDs)
|
||||
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(incentiveGS)})
|
||||
if tc.args.validatorVesting {
|
||||
ak := tApp.GetAccountKeeper()
|
||||
acc := ak.GetAccount(ctx, tc.args.claimOwner)
|
||||
bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
|
||||
bva, err := vesting.NewBaseVestingAccount(
|
||||
bacc,
|
||||
sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(20))), time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC).Unix()+100)
|
||||
suite.Require().NoError(err)
|
||||
vva := validatorvesting.NewValidatorVestingAccountRaw(
|
||||
bva,
|
||||
time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC).Unix(),
|
||||
vesting.Periods{
|
||||
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))},
|
||||
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))},
|
||||
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))},
|
||||
vesting.Period{Length: 25, Amount: cs(c("bnb", 5))}},
|
||||
sdk.ConsAddress(crypto.AddressHash([]byte("test"))),
|
||||
sdk.AccAddress{},
|
||||
95,
|
||||
tApp.InitializeFromGenesisStates(
|
||||
authGS,
|
||||
)
|
||||
err = vva.Validate()
|
||||
suite.Require().NoError(err)
|
||||
ak.SetAccount(ctx, vva)
|
||||
}
|
||||
supplyKeeper := tApp.GetSupplyKeeper()
|
||||
supplyKeeper.MintCoins(ctx, types.IncentiveMacc, sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000))))
|
||||
keeper := tApp.GetIncentiveKeeper()
|
||||
suite.app = tApp
|
||||
suite.ctx = ctx
|
||||
suite.keeper = keeper
|
||||
|
||||
err := suite.keeper.PayoutClaim(suite.ctx, tc.args.claimOwner, tc.args.collateralType, tc.args.id, tc.args.multiplier)
|
||||
|
||||
if tc.errArgs.expectPass {
|
||||
macc := supplyKeeper.GetModuleAccount(ctx, kavadist.ModuleName)
|
||||
err := supplyKeeper.MintCoins(ctx, macc.GetName(), cs(c("ukava", 600)))
|
||||
suite.Require().NoError(err)
|
||||
acc := suite.getAccount(tc.args.claimOwner)
|
||||
suite.Require().Equal(tc.args.expectedAccountBalance, acc.GetCoins())
|
||||
mAcc := suite.getModuleAccount(types.IncentiveMacc)
|
||||
suite.Require().Equal(tc.args.expectedModAccountBalance, mAcc.GetCoins())
|
||||
vacc, ok := acc.(*vesting.PeriodicVestingAccount)
|
||||
if tc.args.expectedVestingAccount {
|
||||
suite.Require().True(ok)
|
||||
suite.Require().Equal(tc.args.expectedVestingLength, vacc.VestingPeriods[0].Length)
|
||||
} else {
|
||||
suite.Require().False(ok)
|
||||
}
|
||||
_, f := suite.keeper.GetClaim(ctx, tc.args.claimOwner, tc.args.collateralType, tc.args.id)
|
||||
suite.Require().False(f)
|
||||
} else {
|
||||
suite.Require().Error(err)
|
||||
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||
}
|
||||
})
|
||||
|
||||
// sets addrs[0] to be a periodic vesting account
|
||||
ak := tApp.GetAccountKeeper()
|
||||
acc := ak.GetAccount(ctx, addrs[0])
|
||||
bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
|
||||
periods := vesting.Periods{
|
||||
vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))},
|
||||
vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))},
|
||||
vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))},
|
||||
vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))},
|
||||
}
|
||||
bva, err2 := vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), ctx.BlockTime().Unix()+16)
|
||||
suite.Require().NoError(err2)
|
||||
pva := vesting.NewPeriodicVestingAccountRaw(bva, ctx.BlockTime().Unix(), periods)
|
||||
ak.SetAccount(ctx, pva)
|
||||
|
||||
// sets addrs[2] to be a validator vesting account
|
||||
acc = ak.GetAccount(ctx, addrs[2])
|
||||
bacc = auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
|
||||
bva, err2 = vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), ctx.BlockTime().Unix()+16)
|
||||
suite.Require().NoError(err2)
|
||||
vva := validatorvesting.NewValidatorVestingAccountRaw(bva, ctx.BlockTime().Unix(), periods, sdk.ConsAddress{}, nil, 90)
|
||||
ak.SetAccount(ctx, vva)
|
||||
suite.app = tApp
|
||||
suite.keeper = tApp.GetIncentiveKeeper()
|
||||
suite.ctx = ctx
|
||||
suite.addrs = addrs
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
@ -18,10 +19,6 @@ func NewQuerier(k Keeper) sdk.Querier {
|
||||
return queryGetParams(ctx, req, k)
|
||||
case types.QueryGetClaims:
|
||||
return queryGetClaims(ctx, req, k)
|
||||
case types.QueryGetRewardPeriods:
|
||||
return queryGetRewardPeriods(ctx, req, k)
|
||||
case types.QueryGetClaimPeriods:
|
||||
return queryGetClaimPeriods(ctx, req, k)
|
||||
default:
|
||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName)
|
||||
}
|
||||
@ -41,41 +38,30 @@ func queryGetParams(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, e
|
||||
return bz, nil
|
||||
}
|
||||
|
||||
// query reward periods in the store
|
||||
func queryGetRewardPeriods(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
|
||||
// Get params
|
||||
rewardPeriods := k.GetAllRewardPeriods(ctx)
|
||||
|
||||
// Encode results
|
||||
bz, err := codec.MarshalJSONIndent(k.cdc, rewardPeriods)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
|
||||
}
|
||||
return bz, nil
|
||||
}
|
||||
|
||||
// query claim periods in the store
|
||||
func queryGetClaimPeriods(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
|
||||
// Get params
|
||||
claimPeriods := k.GetAllClaimPeriods(ctx)
|
||||
|
||||
// Encode results
|
||||
bz, err := codec.MarshalJSONIndent(k.cdc, claimPeriods)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
|
||||
}
|
||||
return bz, nil
|
||||
}
|
||||
|
||||
func queryGetClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
|
||||
var requestParams types.QueryClaimsParams
|
||||
err := k.cdc.UnmarshalJSON(req.Data, &requestParams)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||
}
|
||||
claims, _ := k.GetAllClaimsByAddressAndCollateralType(ctx, requestParams.Owner, requestParams.CollateralType)
|
||||
var claims types.USDXMintingClaims
|
||||
if len(requestParams.Owner) > 0 {
|
||||
claim, _ := k.GetClaim(ctx, requestParams.Owner)
|
||||
claims = append(claims, claim)
|
||||
} else {
|
||||
claims = k.GetAllClaims(ctx)
|
||||
}
|
||||
|
||||
bz, err := codec.MarshalJSONIndent(k.cdc, claims)
|
||||
var paginatedClaims types.USDXMintingClaims
|
||||
|
||||
start, end := client.Paginate(len(claims), requestParams.Page, requestParams.Limit, 100)
|
||||
if start < 0 || end < 0 {
|
||||
paginatedClaims = types.USDXMintingClaims{}
|
||||
} else {
|
||||
paginatedClaims = claims[start:end]
|
||||
}
|
||||
|
||||
bz, err := codec.MarshalJSONIndent(k.cdc, paginatedClaims)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
func (suite *KeeperTestSuite) TestQuerier() {
|
||||
suite.addObjectsToStore()
|
||||
querier := keeper.NewQuerier(suite.keeper)
|
||||
bz, err := querier(suite.ctx, []string{types.QueryGetParams}, abci.RequestQuery{})
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(bz)
|
||||
|
||||
var p types.Params
|
||||
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &p))
|
||||
|
||||
claimQueryParams := types.NewQueryClaimsParams(suite.addrs[0], "bnb")
|
||||
query := abci.RequestQuery{
|
||||
Path: strings.Join([]string{"custom", types.QuerierRoute, types.QueryGetClaims}, "/"),
|
||||
Data: types.ModuleCdc.MustMarshalJSON(claimQueryParams),
|
||||
}
|
||||
bz, err = querier(suite.ctx, []string{types.QueryGetClaims}, query)
|
||||
|
||||
var claims types.AugmentedClaims
|
||||
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &claims))
|
||||
suite.Equal(1, len(claims))
|
||||
suite.Equal(types.AugmentedClaims{
|
||||
types.NewAugmentedClaim(types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1), true),
|
||||
}, claims)
|
||||
|
||||
var rp types.RewardPeriods
|
||||
bz, err = querier(suite.ctx, []string{types.QueryGetRewardPeriods}, abci.RequestQuery{})
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(bz)
|
||||
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &rp))
|
||||
|
||||
var cp types.ClaimPeriods
|
||||
bz, err = querier(suite.ctx, []string{types.QueryGetClaimPeriods}, abci.RequestQuery{})
|
||||
suite.Require().NoError(err)
|
||||
suite.NotNil(bz)
|
||||
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &cp))
|
||||
|
||||
}
|
@ -1,118 +1,172 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
// HandleRewardPeriodExpiry deletes expired RewardPeriods from the store and creates a ClaimPeriod in the store for each expired RewardPeriod
|
||||
func (k Keeper) HandleRewardPeriodExpiry(ctx sdk.Context, rp types.RewardPeriod) {
|
||||
k.CreateUniqueClaimPeriod(ctx, rp.CollateralType, rp.ClaimEnd, rp.ClaimMultipliers)
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
|
||||
store.Delete([]byte(rp.CollateralType))
|
||||
return
|
||||
// AccumulateRewards updates the rewards accumulated for the input reward period
|
||||
func (k Keeper) AccumulateRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error {
|
||||
if !rewardPeriod.Active {
|
||||
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateNewRewardPeriod creates a new reward period from the input reward
|
||||
func (k Keeper) CreateNewRewardPeriod(ctx sdk.Context, reward types.Reward) {
|
||||
rp := types.NewRewardPeriodFromReward(reward, ctx.BlockTime())
|
||||
k.SetRewardPeriod(ctx, rp)
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeRewardPeriod,
|
||||
sdk.NewAttribute(types.AttributeKeyRewardPeriod, rp.String()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// CreateAndDeleteRewardPeriods creates reward periods for active rewards that don't already have a reward period and deletes reward periods for inactive rewards that currently have a reward period
|
||||
func (k Keeper) CreateAndDeleteRewardPeriods(ctx sdk.Context) {
|
||||
params := k.GetParams(ctx)
|
||||
|
||||
for _, r := range params.Rewards {
|
||||
_, found := k.GetRewardPeriod(ctx, r.CollateralType)
|
||||
// if governance has made a reward inactive, delete the current period
|
||||
if found && !r.Active {
|
||||
k.DeleteRewardPeriod(ctx, r.CollateralType)
|
||||
}
|
||||
// if a reward period for an active reward is not found, create one
|
||||
if !found && r.Active {
|
||||
k.CreateNewRewardPeriod(ctx, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyRewardsToCdps iterates over the reward periods and creates a claim for each
|
||||
// cdp owner that created usdx with the collateral specified in the reward period.
|
||||
func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) {
|
||||
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
|
||||
previousAccrualTime, found := k.GetPreviousAccrualTime(ctx, rewardPeriod.CollateralType)
|
||||
if !found {
|
||||
previousBlockTime = ctx.BlockTime()
|
||||
k.SetPreviousBlockTime(ctx, previousBlockTime)
|
||||
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
timeElapsed := CalculateTimeElapsed(rewardPeriod, ctx.BlockTime(), previousAccrualTime)
|
||||
if timeElapsed.IsZero() {
|
||||
return nil
|
||||
}
|
||||
if rewardPeriod.RewardsPerSecond.Amount.IsZero() {
|
||||
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, rewardPeriod.CollateralType, types.PrincipalDenom).ToDec()
|
||||
if totalPrincipal.IsZero() {
|
||||
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount)
|
||||
cdpFactor, found := k.cdpKeeper.GetInterestFactor(ctx, rewardPeriod.CollateralType)
|
||||
if !found {
|
||||
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
rewardFactor := newRewards.ToDec().Mul(cdpFactor).Quo(totalPrincipal)
|
||||
|
||||
previousRewardFactor, found := k.GetRewardFactor(ctx, rewardPeriod.CollateralType)
|
||||
if !found {
|
||||
previousRewardFactor = sdk.ZeroDec()
|
||||
}
|
||||
newRewardFactor := previousRewardFactor.Add(rewardFactor)
|
||||
k.SetRewardFactor(ctx, rewardPeriod.CollateralType, newRewardFactor)
|
||||
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitializeClaim creates or updates a claim such that no new rewards are accrued, but any existing rewards are not lost.
|
||||
// this function should be called after a cdp is created. If a user previously had a cdp, then closed it, they shouldn't
|
||||
// accrue rewards during the period the cdp was closed. By setting the reward factor to the current global reward factor,
|
||||
// any unclaimed rewards are preserved, but no new rewards are added.
|
||||
func (k Keeper) InitializeClaim(ctx sdk.Context, cdp cdptypes.CDP) {
|
||||
_, found := k.GetRewardPeriod(ctx, cdp.Type)
|
||||
if !found {
|
||||
// this collateral type is not incentivized, do nothing
|
||||
return
|
||||
}
|
||||
|
||||
k.IterateRewardPeriods(ctx, func(rp types.RewardPeriod) bool {
|
||||
expired := false
|
||||
// the total amount of usdx created with the collateral type being incentivized
|
||||
totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, rp.CollateralType, types.PrincipalDenom)
|
||||
// the number of seconds since last payout
|
||||
timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix())
|
||||
if rp.End.Before(ctx.BlockTime()) {
|
||||
timeElapsed = sdk.NewInt(rp.End.Unix() - previousBlockTime.Unix())
|
||||
expired = true
|
||||
rewardFactor, found := k.GetRewardFactor(ctx, cdp.Type)
|
||||
if !found {
|
||||
rewardFactor = sdk.ZeroDec()
|
||||
}
|
||||
|
||||
// the amount of rewards to pay (rewardAmount * timeElapsed)
|
||||
rewardsThisPeriod := rp.Reward.Amount.Mul(timeElapsed)
|
||||
id := k.GetNextClaimPeriodID(ctx, rp.CollateralType)
|
||||
k.cdpKeeper.IterateCdpsByCollateralType(ctx, rp.CollateralType, func(cdp cdptypes.CDP) bool {
|
||||
rewardsShare := sdk.NewDecFromInt(cdp.GetTotalPrincipal().Amount).Quo(sdk.NewDecFromInt(totalPrincipal))
|
||||
// sanity check - don't create zero claims
|
||||
if rewardsShare.IsZero() {
|
||||
return false
|
||||
claim, found := k.GetClaim(ctx, cdp.Owner)
|
||||
if !found { // this is the owner's first usdx minting reward claim
|
||||
claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, rewardFactor)})
|
||||
k.SetClaim(ctx, claim)
|
||||
return
|
||||
}
|
||||
rewardsEarned := rewardsShare.Mul(sdk.NewDecFromInt(rewardsThisPeriod)).RoundInt()
|
||||
k.AddToClaim(ctx, cdp.Owner, rp.CollateralType, id, sdk.NewCoin(types.GovDenom, rewardsEarned))
|
||||
return false
|
||||
})
|
||||
if !expired {
|
||||
return false
|
||||
}
|
||||
k.HandleRewardPeriodExpiry(ctx, rp)
|
||||
return false
|
||||
})
|
||||
|
||||
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
||||
}
|
||||
|
||||
// CreateUniqueClaimPeriod creates a new claim period in the store and updates the highest claim period id
|
||||
func (k Keeper) CreateUniqueClaimPeriod(ctx sdk.Context, collateralType string, end time.Time, multipliers types.Multipliers) {
|
||||
id := k.GetNextClaimPeriodID(ctx, collateralType)
|
||||
claimPeriod := types.NewClaimPeriod(collateralType, id, end, multipliers)
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeClaimPeriod,
|
||||
sdk.NewAttribute(types.AttributeKeyClaimPeriod, claimPeriod.String()),
|
||||
),
|
||||
)
|
||||
k.SetClaimPeriod(ctx, claimPeriod)
|
||||
k.SetNextClaimPeriodID(ctx, collateralType, id+1)
|
||||
}
|
||||
|
||||
// AddToClaim adds the amount to an existing claim or creates a new one for that amount
|
||||
func (k Keeper) AddToClaim(ctx sdk.Context, addr sdk.AccAddress, collateralType string, id uint64, amount sdk.Coin) {
|
||||
claim, found := k.GetClaim(ctx, addr, collateralType, id)
|
||||
if found {
|
||||
claim.Reward = claim.Reward.Add(amount)
|
||||
} else {
|
||||
claim = types.NewClaim(addr, amount, collateralType, id)
|
||||
// the owner has an existing usdx minting reward claim
|
||||
index, hasRewardIndex := claim.HasRewardIndex(cdp.Type)
|
||||
if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type
|
||||
claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(cdp.Type, rewardFactor))
|
||||
} else { // the owner has a previous usdx minting reward for this collateral type
|
||||
claim.RewardIndexes[index] = types.NewRewardIndex(cdp.Type, rewardFactor)
|
||||
}
|
||||
k.SetClaim(ctx, claim)
|
||||
}
|
||||
|
||||
// SynchronizeReward updates the claim object by adding any accumulated rewards and updating the reward index value.
|
||||
// this should be called before a cdp is modified, immediately after the 'SynchronizeInterest' method is called in the cdp module
|
||||
func (k Keeper) SynchronizeReward(ctx sdk.Context, cdp cdptypes.CDP) {
|
||||
_, found := k.GetRewardPeriod(ctx, cdp.Type)
|
||||
if !found {
|
||||
// this collateral type is not incentivized, do nothing
|
||||
return
|
||||
}
|
||||
|
||||
globalRewardFactor, found := k.GetRewardFactor(ctx, cdp.Type)
|
||||
if !found {
|
||||
globalRewardFactor = sdk.ZeroDec()
|
||||
}
|
||||
claim, found := k.GetClaim(ctx, cdp.Owner)
|
||||
if !found {
|
||||
claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, globalRewardFactor)})
|
||||
k.SetClaim(ctx, claim)
|
||||
return
|
||||
}
|
||||
|
||||
// the owner has an existing usdx minting reward claim
|
||||
index, hasRewardIndex := claim.HasRewardIndex(cdp.Type)
|
||||
if !hasRewardIndex { // this is the owner's first usdx minting reward for this collateral type
|
||||
claim.RewardIndexes = append(claim.RewardIndexes, types.NewRewardIndex(cdp.Type, globalRewardFactor))
|
||||
k.SetClaim(ctx, claim)
|
||||
return
|
||||
}
|
||||
userRewardFactor := claim.RewardIndexes[index].RewardFactor
|
||||
rewardsAccumulatedFactor := globalRewardFactor.Sub(userRewardFactor)
|
||||
if rewardsAccumulatedFactor.IsZero() {
|
||||
return
|
||||
}
|
||||
claim.RewardIndexes[index].RewardFactor = globalRewardFactor
|
||||
newRewardsAmount := cdp.GetTotalPrincipal().Amount.ToDec().Quo(cdp.InterestFactor).Mul(rewardsAccumulatedFactor).RoundInt()
|
||||
if newRewardsAmount.IsZero() {
|
||||
k.SetClaim(ctx, claim)
|
||||
return
|
||||
}
|
||||
newRewardsCoin := sdk.NewCoin(types.USDXMintingRewardDenom, newRewardsAmount)
|
||||
claim.Reward = claim.Reward.Add(newRewardsCoin)
|
||||
k.SetClaim(ctx, claim)
|
||||
return
|
||||
}
|
||||
|
||||
// ZeroClaim zeroes out the claim object's rewards and returns the updated claim object
|
||||
func (k Keeper) ZeroClaim(ctx sdk.Context, claim types.USDXMintingClaim) types.USDXMintingClaim {
|
||||
claim.Reward = sdk.NewCoin(claim.Reward.Denom, sdk.ZeroInt())
|
||||
k.SetClaim(ctx, claim)
|
||||
return claim
|
||||
}
|
||||
|
||||
// SynchronizeClaim updates the claim object by adding any rewards that have accumulated.
|
||||
// Returns the updated claim object
|
||||
func (k Keeper) SynchronizeClaim(ctx sdk.Context, claim types.USDXMintingClaim) (types.USDXMintingClaim, error) {
|
||||
for _, ri := range claim.RewardIndexes {
|
||||
cdp, found := k.cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, claim.Owner, ri.CollateralType)
|
||||
if !found {
|
||||
// if the cdp for this collateral type has been closed, no updates are needed
|
||||
continue
|
||||
}
|
||||
claim = k.synchronizeRewardAndReturnClaim(ctx, cdp)
|
||||
}
|
||||
return claim, nil
|
||||
}
|
||||
|
||||
// this function assumes a claim already exists, so don't call it if that's not the case
|
||||
func (k Keeper) synchronizeRewardAndReturnClaim(ctx sdk.Context, cdp cdptypes.CDP) types.USDXMintingClaim {
|
||||
k.SynchronizeReward(ctx, cdp)
|
||||
claim, _ := k.GetClaim(ctx, cdp.Owner)
|
||||
return claim
|
||||
}
|
||||
|
||||
// CalculateTimeElapsed calculates the number of reward-eligible seconds that have passed since the previous
|
||||
// time rewards were accrued, taking into account the end time of the reward period
|
||||
func CalculateTimeElapsed(rewardPeriod types.RewardPeriod, blockTime time.Time, previousAccrualTime time.Time) sdk.Int {
|
||||
if rewardPeriod.End.Before(blockTime) &&
|
||||
(rewardPeriod.End.Before(previousAccrualTime) || rewardPeriod.End.Equal(previousAccrualTime)) {
|
||||
return sdk.ZeroInt()
|
||||
}
|
||||
if rewardPeriod.End.Before(blockTime) {
|
||||
return sdk.NewInt(int64(math.RoundToEven(
|
||||
rewardPeriod.End.Sub(previousAccrualTime).Seconds(),
|
||||
)))
|
||||
}
|
||||
return sdk.NewInt(int64(math.RoundToEven(
|
||||
blockTime.Sub(previousAccrualTime).Seconds(),
|
||||
)))
|
||||
}
|
||||
|
@ -1,262 +1,323 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
)
|
||||
|
||||
func (suite *KeeperTestSuite) TestExpireRewardPeriod() {
|
||||
rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})
|
||||
suite.keeper.SetRewardPeriod(suite.ctx, rp)
|
||||
suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1)
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.HandleRewardPeriodExpiry(suite.ctx, rp)
|
||||
})
|
||||
_, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb")
|
||||
suite.True(found)
|
||||
func (suite *KeeperTestSuite) TestAccumulateRewards() {
|
||||
type args struct {
|
||||
ctype string
|
||||
rewardsPerSecond sdk.Coin
|
||||
initialTime time.Time
|
||||
initialTotalPrincipal sdk.Coin
|
||||
timeElapsed int
|
||||
expectedRewardFactor sdk.Dec
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestAddToClaim() {
|
||||
rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})
|
||||
suite.keeper.SetRewardPeriod(suite.ctx, rp)
|
||||
suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1)
|
||||
suite.keeper.HandleRewardPeriodExpiry(suite.ctx, rp)
|
||||
c1 := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1)
|
||||
suite.keeper.SetClaim(suite.ctx, c1)
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.AddToClaim(suite.ctx, suite.addrs[0], "bnb", 1, c("ukava", 1000000))
|
||||
})
|
||||
testC, _ := suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1)
|
||||
suite.Equal(c("ukava", 2000000), testC.Reward)
|
||||
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.AddToClaim(suite.ctx, suite.addrs[0], "xpr", 1, c("ukava", 1000000))
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestCreateRewardPeriod() {
|
||||
reward := types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.CreateNewRewardPeriod(suite.ctx, reward)
|
||||
})
|
||||
_, found := suite.keeper.GetRewardPeriod(suite.ctx, "bnb")
|
||||
suite.True(found)
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestCreateAndDeleteRewardsPeriods() {
|
||||
reward1 := types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)
|
||||
reward2 := types.NewReward(false, "xrp", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)
|
||||
reward3 := types.NewReward(false, "btc", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)
|
||||
// add a reward period to the store for a non-active reward
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.CreateNewRewardPeriod(suite.ctx, reward3)
|
||||
})
|
||||
params := types.NewParams(true, types.Rewards{reward1, reward2, reward3})
|
||||
suite.keeper.SetParams(suite.ctx, params)
|
||||
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.CreateAndDeleteRewardPeriods(suite.ctx)
|
||||
})
|
||||
testCases := []struct {
|
||||
type test struct {
|
||||
name string
|
||||
arg string
|
||||
expectFound bool
|
||||
}{
|
||||
args args
|
||||
}
|
||||
testCases := []test{
|
||||
{
|
||||
"active reward period",
|
||||
"bnb",
|
||||
true,
|
||||
"7 seconds",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
rewardsPerSecond: c("ukava", 122354),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialTotalPrincipal: c("usdx", 1000000000000),
|
||||
timeElapsed: 7,
|
||||
expectedRewardFactor: d("0.000000856478000000"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"attempt to add inactive reward period",
|
||||
"xrp",
|
||||
false,
|
||||
"1 day",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
rewardsPerSecond: c("ukava", 122354),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialTotalPrincipal: c("usdx", 1000000000000),
|
||||
timeElapsed: 86400,
|
||||
expectedRewardFactor: d("0.0105713856"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"remove inactive reward period",
|
||||
"btc",
|
||||
false,
|
||||
"0 seconds",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
rewardsPerSecond: c("ukava", 122354),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialTotalPrincipal: c("usdx", 1000000000000),
|
||||
timeElapsed: 0,
|
||||
expectedRewardFactor: d("0.0"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
_, found := suite.keeper.GetRewardPeriod(suite.ctx, tc.arg)
|
||||
if tc.expectFound {
|
||||
suite.True(found)
|
||||
} else {
|
||||
suite.False(found)
|
||||
}
|
||||
suite.SetupWithCDPGenState()
|
||||
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
|
||||
|
||||
// setup cdp state
|
||||
cdpKeeper := suite.app.GetCDPKeeper()
|
||||
cdpKeeper.SetTotalPrincipal(suite.ctx, tc.args.ctype, cdptypes.DefaultStableDenom, tc.args.initialTotalPrincipal.Amount)
|
||||
|
||||
// setup incentive state
|
||||
params := types.NewParams(
|
||||
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
|
||||
types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
|
||||
tc.args.initialTime.Add(time.Hour*24*365*5),
|
||||
)
|
||||
suite.keeper.SetParams(suite.ctx, params)
|
||||
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime)
|
||||
suite.keeper.SetRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec())
|
||||
|
||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed))
|
||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||
rewardPeriod, found := suite.keeper.GetRewardPeriod(suite.ctx, tc.args.ctype)
|
||||
suite.Require().True(found)
|
||||
err := suite.keeper.AccumulateRewards(suite.ctx, rewardPeriod)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
rewardFactor, found := suite.keeper.GetRewardFactor(suite.ctx, tc.args.ctype)
|
||||
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestApplyRewardsToCdps() {
|
||||
suite.setupCdpChain() // creates a test app with 3 BNB cdps and usdx incentives for bnb - each reward period is one week
|
||||
|
||||
// move the context forward by 100 periods
|
||||
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 100))
|
||||
// apply rewards to BNB cdps
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.ApplyRewardsToCdps(suite.ctx)
|
||||
})
|
||||
// each cdp should have a claim
|
||||
claims := types.Claims{}
|
||||
suite.keeper.IterateClaims(suite.ctx, func(c types.Claim) (stop bool) {
|
||||
claims = append(claims, c)
|
||||
return false
|
||||
})
|
||||
suite.Equal(3, len(claims))
|
||||
// there should be no associated claim period, because the reward period has not ended yet
|
||||
_, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb-a")
|
||||
suite.False(found)
|
||||
|
||||
// move ctx to the reward period expiry and check that the claim period has been created and the next claim period id has increased
|
||||
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 24 * 7))
|
||||
|
||||
suite.NotPanics(func() {
|
||||
// apply rewards to cdps
|
||||
suite.keeper.ApplyRewardsToCdps(suite.ctx)
|
||||
// delete the old reward period amd create a new one
|
||||
suite.keeper.CreateAndDeleteRewardPeriods(suite.ctx)
|
||||
})
|
||||
_, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb-a")
|
||||
suite.True(found)
|
||||
testID := suite.keeper.GetNextClaimPeriodID(suite.ctx, "bnb-a")
|
||||
suite.Equal(uint64(2), testID)
|
||||
|
||||
// move the context forward by 100 periods
|
||||
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 100))
|
||||
// run the begin blocker functions
|
||||
suite.NotPanics(func() {
|
||||
suite.keeper.DeleteExpiredClaimsAndClaimPeriods(suite.ctx)
|
||||
suite.keeper.ApplyRewardsToCdps(suite.ctx)
|
||||
suite.keeper.CreateAndDeleteRewardPeriods(suite.ctx)
|
||||
})
|
||||
// each cdp should now have two claims
|
||||
claims = types.Claims{}
|
||||
suite.keeper.IterateClaims(suite.ctx, func(c types.Claim) (stop bool) {
|
||||
claims = append(claims, c)
|
||||
return false
|
||||
})
|
||||
suite.Equal(6, len(claims))
|
||||
func (suite *KeeperTestSuite) TestSyncRewards() {
|
||||
type args struct {
|
||||
ctype string
|
||||
rewardsPerSecond sdk.Coin
|
||||
initialTime time.Time
|
||||
initialCollateral sdk.Coin
|
||||
initialPrincipal sdk.Coin
|
||||
blockTimes []int
|
||||
expectedRewardFactor sdk.Dec
|
||||
expectedRewards sdk.Coin
|
||||
}
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) setupCdpChain() {
|
||||
// creates a new test app with bnb as the only asset the pricefeed and cdp modules
|
||||
// funds three addresses and creates 3 cdps, funded with 100 BNB, 1000 BNB, and 10000 BNB
|
||||
// each CDP draws 10, 100, and 1000 USDX respectively
|
||||
// adds usdx incentives for bnb - 1000 KAVA per week with a 1 year time lock
|
||||
testCases := []test{
|
||||
{
|
||||
"10 blocks",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
rewardsPerSecond: c("ukava", 122354),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000000),
|
||||
initialPrincipal: c("usdx", 10000000000),
|
||||
blockTimes: []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10},
|
||||
expectedRewardFactor: d("0.001223540000000000"),
|
||||
expectedRewards: c("ukava", 12235400),
|
||||
},
|
||||
},
|
||||
{
|
||||
"10 blocks - long block time",
|
||||
args{
|
||||
ctype: "bnb-a",
|
||||
rewardsPerSecond: c("ukava", 122354),
|
||||
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||
initialCollateral: c("bnb", 1000000000000),
|
||||
initialPrincipal: c("usdx", 10000000000),
|
||||
blockTimes: []int{86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400},
|
||||
expectedRewardFactor: d("10.57138560000000000"),
|
||||
expectedRewards: c("ukava", 105713856000),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupWithCDPGenState()
|
||||
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
|
||||
|
||||
// setup incentive state
|
||||
params := types.NewParams(
|
||||
types.RewardPeriods{types.NewRewardPeriod(true, tc.args.ctype, tc.args.initialTime, tc.args.initialTime.Add(time.Hour*24*365*4), tc.args.rewardsPerSecond)},
|
||||
types.Multipliers{types.NewMultiplier(types.MultiplierName("small"), 1, d("0.25")), types.NewMultiplier(types.MultiplierName("large"), 12, d("1.0"))},
|
||||
tc.args.initialTime.Add(time.Hour*24*365*5),
|
||||
)
|
||||
suite.keeper.SetParams(suite.ctx, params)
|
||||
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime)
|
||||
suite.keeper.SetRewardFactor(suite.ctx, tc.args.ctype, sdk.ZeroDec())
|
||||
|
||||
// setup account state
|
||||
sk := suite.app.GetSupplyKeeper()
|
||||
sk.MintCoins(suite.ctx, cdptypes.ModuleName, sdk.NewCoins(tc.args.initialCollateral))
|
||||
sk.SendCoinsFromModuleToAccount(suite.ctx, cdptypes.ModuleName, suite.addrs[0], sdk.NewCoins(tc.args.initialCollateral))
|
||||
|
||||
// setup cdp state
|
||||
cdpKeeper := suite.app.GetCDPKeeper()
|
||||
err := cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
claim, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||
suite.Require().True(found)
|
||||
suite.Require().Equal(sdk.ZeroDec(), claim.RewardIndexes[0].RewardFactor)
|
||||
|
||||
var timeElapsed int
|
||||
previousBlockTime := suite.ctx.BlockTime()
|
||||
for _, t := range tc.args.blockTimes {
|
||||
timeElapsed += t
|
||||
updatedBlockTime := previousBlockTime.Add(time.Duration(int(time.Second) * t))
|
||||
previousBlockTime = updatedBlockTime
|
||||
blockCtx := suite.ctx.WithBlockTime(updatedBlockTime)
|
||||
rewardPeriod, found := suite.keeper.GetRewardPeriod(blockCtx, tc.args.ctype)
|
||||
suite.Require().True(found)
|
||||
err := suite.keeper.AccumulateRewards(blockCtx, rewardPeriod)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
updatedBlockTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * timeElapsed))
|
||||
suite.ctx = suite.ctx.WithBlockTime(updatedBlockTime)
|
||||
cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(suite.ctx, suite.addrs[0], tc.args.ctype)
|
||||
suite.Require().True(found)
|
||||
suite.Require().NotPanics(func() {
|
||||
suite.keeper.SynchronizeReward(suite.ctx, cdp)
|
||||
})
|
||||
|
||||
rewardFactor, found := suite.keeper.GetRewardFactor(suite.ctx, tc.args.ctype)
|
||||
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
|
||||
|
||||
claim, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||
fmt.Println(claim)
|
||||
suite.Require().True(found)
|
||||
suite.Require().Equal(tc.args.expectedRewardFactor, claim.RewardIndexes[0].RewardFactor)
|
||||
suite.Require().Equal(tc.args.expectedRewards, claim.Reward)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRewardCalculation(t *testing.T) {
|
||||
|
||||
// Test Params
|
||||
ctype := "bnb-a"
|
||||
initialTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
rewardsPerSecond := c("ukava", 122_354)
|
||||
initialCollateral := c("bnb", 10_000_000_000)
|
||||
initialPrincipal := c("usdx", 100_000_000)
|
||||
oneYear := time.Hour * 24 * 365
|
||||
rewardPeriod := types.NewRewardPeriod(
|
||||
true,
|
||||
ctype,
|
||||
initialTime,
|
||||
initialTime.Add(4*oneYear),
|
||||
rewardsPerSecond,
|
||||
)
|
||||
|
||||
// Setup app and module params
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: initialTime})
|
||||
tApp.InitializeFromGenesisStates(
|
||||
app.NewAuthGenState(addrs[:1], []sdk.Coins{cs(initialCollateral)}),
|
||||
NewPricefeedGenStateMulti(),
|
||||
NewCDPGenStateHighInterest(),
|
||||
NewIncentiveGenState(initialTime, initialTime.Add(oneYear), rewardPeriod),
|
||||
)
|
||||
|
||||
// Create a CDP
|
||||
cdpKeeper := tApp.GetCDPKeeper()
|
||||
err := cdpKeeper.AddCdp(
|
||||
ctx,
|
||||
addrs[0],
|
||||
initialCollateral,
|
||||
initialPrincipal,
|
||||
ctype,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Calculate expected cdp reward using iteration
|
||||
|
||||
// Use 10 blocks, each a very long 630720s, to total 6307200s or 1/5th of a year
|
||||
// The cdp stability fee is set to the max value 500%, so this time ensures the debt increases a significant amount (doubles)
|
||||
// High stability fees increase the chance of catching calculation bugs.
|
||||
blockTimes := newRepeatingSliceInt(630720, 10)
|
||||
expectedCDPReward := sdk.ZeroDec() //c(rewardPeriod.RewardsPerSecond.Denom, 0)
|
||||
for _, bt := range blockTimes {
|
||||
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Duration(int(time.Second) * bt)))
|
||||
|
||||
// run cdp and incentive begin blockers to update factors
|
||||
tApp.BeginBlocker(ctx, abci.RequestBeginBlock{})
|
||||
|
||||
// calculate expected cdp reward
|
||||
cdpBlockReward, err := calculateCDPBlockReward(ctx, cdpKeeper, addrs[0], ctype, sdk.NewInt(int64(bt)), rewardPeriod)
|
||||
require.NoError(t, err)
|
||||
expectedCDPReward = expectedCDPReward.Add(cdpBlockReward)
|
||||
}
|
||||
|
||||
// calculate cdp reward using factor
|
||||
cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, addrs[0], ctype)
|
||||
require.True(t, found)
|
||||
incentiveKeeper := tApp.GetIncentiveKeeper()
|
||||
require.NotPanics(t, func() {
|
||||
incentiveKeeper.SynchronizeReward(ctx, cdp)
|
||||
})
|
||||
claim, found := incentiveKeeper.GetClaim(ctx, addrs[0])
|
||||
require.True(t, found)
|
||||
|
||||
// Compare two methods of calculation
|
||||
relativeError := expectedCDPReward.Sub(claim.Reward.Amount.ToDec()).Quo(expectedCDPReward).Abs()
|
||||
maxError := d("0.0001")
|
||||
require.Truef(t, relativeError.LT(maxError),
|
||||
"percent diff %s > %s , expected: %s, actual %s,", relativeError, maxError, expectedCDPReward, claim.Reward.Amount,
|
||||
)
|
||||
}
|
||||
|
||||
// calculateCDPBlockReward computes the reward that should be distributed to a cdp for the current block.
|
||||
func calculateCDPBlockReward(ctx sdk.Context, cdpKeeper cdpkeeper.Keeper, owner sdk.AccAddress, ctype string, timeElapsed sdk.Int, rewardPeriod types.RewardPeriod) (sdk.Dec, error) {
|
||||
// Calculate total rewards to distribute this block
|
||||
newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount)
|
||||
|
||||
// Calculate cdp's share of total debt
|
||||
totalPrincipal := cdpKeeper.GetTotalPrincipal(ctx, ctype, types.PrincipalDenom).ToDec()
|
||||
// cdpDebt
|
||||
cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, owner, ctype)
|
||||
if !found {
|
||||
return sdk.Dec{}, fmt.Errorf("couldn't find cdp for owner '%s' and collateral type '%s'", owner, ctype)
|
||||
}
|
||||
accumulatedInterest := cdpKeeper.CalculateNewInterest(ctx, cdp)
|
||||
cdpDebt := cdp.Principal.Add(cdp.AccumulatedFees).Add(accumulatedInterest).Amount
|
||||
|
||||
// Calculate cdp's reward
|
||||
return newRewards.Mul(cdpDebt).ToDec().Quo(totalPrincipal), nil
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) SetupWithCDPGenState() {
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
// need pricefeed and cdp gen state with one collateral
|
||||
pricefeedGS := pricefeed.GenesisState{
|
||||
Params: pricefeed.Params{
|
||||
Markets: []pricefeed.Market{
|
||||
{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||
},
|
||||
},
|
||||
PostedPrices: []pricefeed.PostedPrice{
|
||||
{
|
||||
MarketID: "bnb:usd",
|
||||
OracleAddress: sdk.AccAddress{},
|
||||
Price: d("12.29"),
|
||||
Expiry: time.Now().Add(100000 * time.Hour),
|
||||
},
|
||||
},
|
||||
}
|
||||
// need incentive params for one collateral
|
||||
cdpGS := cdp.GenesisState{
|
||||
Params: cdp.Params{
|
||||
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
|
||||
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
|
||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
|
||||
CollateralParams: cdp.CollateralParams{
|
||||
{
|
||||
Denom: "bnb",
|
||||
Type: "bnb-a",
|
||||
LiquidationRatio: sdk.MustNewDecFromStr("2.0"),
|
||||
DebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
|
||||
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
|
||||
LiquidationPenalty: d("0.05"),
|
||||
AuctionSize: i(10000000000),
|
||||
Prefix: 0x20,
|
||||
SpotMarketID: "bnb:usd",
|
||||
LiquidationMarketID: "bnb:usd",
|
||||
ConversionFactor: i(8),
|
||||
},
|
||||
},
|
||||
DebtParam: cdp.DebtParam{
|
||||
Denom: "usdx",
|
||||
ReferenceAsset: "usd",
|
||||
ConversionFactor: i(6),
|
||||
DebtFloor: i(10000000),
|
||||
SavingsRate: d("0.95"),
|
||||
},
|
||||
},
|
||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||
DebtDenom: cdp.DefaultDebtDenom,
|
||||
GovDenom: cdp.DefaultGovDenom,
|
||||
CDPs: cdp.CDPs{},
|
||||
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
|
||||
}
|
||||
incentiveGS := types.NewGenesisState(
|
||||
types.NewParams(
|
||||
true, types.Rewards{types.NewReward(true, "bnb-a", c("ukava", 1000000000), time.Hour*7*24, types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))}, time.Hour*7*24)},
|
||||
),
|
||||
types.DefaultPreviousBlockTime,
|
||||
types.RewardPeriods{types.NewRewardPeriod("bnb-a", ctx.BlockTime(), ctx.BlockTime().Add(time.Hour*7*24), c("ukava", 1000), ctx.BlockTime().Add(time.Hour*7*24*2), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))})},
|
||||
types.ClaimPeriods{},
|
||||
types.Claims{},
|
||||
types.GenesisClaimPeriodIDs{})
|
||||
pricefeedAppGs := app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pricefeedGS)}
|
||||
cdpAppGs := app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGS)}
|
||||
incentiveAppGs := app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(incentiveGS)}
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(3)
|
||||
authGS := app.NewAuthGenState(
|
||||
addrs[0:3],
|
||||
[]sdk.Coins{
|
||||
cs(c("bnb", 10000000000)),
|
||||
cs(c("bnb", 100000000000)),
|
||||
cs(c("bnb", 1000000000000)),
|
||||
})
|
||||
tApp.InitializeFromGenesisStates(
|
||||
authGS,
|
||||
pricefeedAppGs,
|
||||
incentiveAppGs,
|
||||
cdpAppGs,
|
||||
NewPricefeedGenStateMulti(),
|
||||
NewCDPGenStateMulti(),
|
||||
)
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||
keeper := tApp.GetIncentiveKeeper()
|
||||
suite.app = tApp
|
||||
suite.keeper = tApp.GetIncentiveKeeper()
|
||||
suite.ctx = ctx
|
||||
// create 3 cdps
|
||||
cdpKeeper := tApp.GetCDPKeeper()
|
||||
err := cdpKeeper.AddCdp(suite.ctx, addrs[0], c("bnb", 10000000000), c("usdx", 10000000), "bnb-a")
|
||||
suite.Require().NoError(err)
|
||||
err = cdpKeeper.AddCdp(suite.ctx, addrs[1], c("bnb", 100000000000), c("usdx", 100000000), "bnb-a")
|
||||
suite.Require().NoError(err)
|
||||
err = cdpKeeper.AddCdp(suite.ctx, addrs[2], c("bnb", 1000000000000), c("usdx", 1000000000), "bnb-a")
|
||||
suite.Require().NoError(err)
|
||||
// total usd is 1110
|
||||
|
||||
// set the previous block time
|
||||
suite.keeper.SetPreviousBlockTime(suite.ctx, suite.ctx.BlockTime())
|
||||
suite.keeper = keeper
|
||||
suite.addrs = addrs
|
||||
}
|
||||
|
||||
// Avoid cluttering test cases with long function names
|
||||
func i(in int64) sdk.Int { return sdk.NewInt(in) }
|
||||
func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) }
|
||||
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
|
||||
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
|
||||
// newRepeatingSliceInt creates a slice of the specified length containing a single repeating element.
|
||||
func newRepeatingSliceInt(element int, length int) []int {
|
||||
slice := make([]int, length)
|
||||
for i := 0; i < length; i++ {
|
||||
slice[i] = element
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ package simulation
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/kv"
|
||||
|
||||
@ -16,35 +16,25 @@ import (
|
||||
// DecodeStore unmarshals the KVPair's Value to the module's corresponding type
|
||||
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
|
||||
switch {
|
||||
case bytes.Equal(kvA.Key[:1], types.RewardPeriodKeyPrefix):
|
||||
var rewardPeriodA, rewardPeriodB types.RewardPeriod
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &rewardPeriodA)
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &rewardPeriodB)
|
||||
return fmt.Sprintf("%v\n%v", rewardPeriodA, rewardPeriodB)
|
||||
|
||||
case bytes.Equal(kvA.Key[:1], types.ClaimPeriodKeyPrefix):
|
||||
var claimPeriodA, claimPeriodB types.ClaimPeriod
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &claimPeriodA)
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &claimPeriodB)
|
||||
return fmt.Sprintf("%v\n%v", claimPeriodA, claimPeriodB)
|
||||
|
||||
case bytes.Equal(kvA.Key[:1], types.ClaimKeyPrefix):
|
||||
var claimA, claimB types.Claim
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &claimA)
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &claimB)
|
||||
var claimA, claimB types.USDXMintingClaim
|
||||
cdc.MustUnmarshalBinaryBare(kvA.Value, &claimA)
|
||||
cdc.MustUnmarshalBinaryBare(kvB.Value, &claimB)
|
||||
return fmt.Sprintf("%v\n%v", claimA, claimB)
|
||||
|
||||
case bytes.Equal(kvA.Key[:1], types.NextClaimPeriodIDPrefix):
|
||||
claimPeriodIDA := binary.BigEndian.Uint64(kvA.Value)
|
||||
claimPeriodIDB := binary.BigEndian.Uint64(kvB.Value)
|
||||
return fmt.Sprintf("%d\n%d", claimPeriodIDA, claimPeriodIDB)
|
||||
|
||||
case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey):
|
||||
case bytes.Equal(kvA.Key[:1], types.BlockTimeKey):
|
||||
var timeA, timeB time.Time
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &timeA)
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &timeB)
|
||||
cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA)
|
||||
cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB)
|
||||
return fmt.Sprintf("%s\n%s", timeA, timeB)
|
||||
|
||||
case bytes.Equal(kvA.Key[:1], types.RewardFactorKey):
|
||||
var factorA, factorB sdk.Dec
|
||||
cdc.MustUnmarshalBinaryBare(kvA.Value, &factorA)
|
||||
cdc.MustUnmarshalBinaryBare(kvB.Value, &factorB)
|
||||
return fmt.Sprintf("%s\n%s", factorA, factorB)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
|
||||
}
|
||||
|
@ -24,21 +24,15 @@ func makeTestCodec() (cdc *codec.Codec) {
|
||||
|
||||
func TestDecodeDistributionStore(t *testing.T) {
|
||||
cdc := makeTestCodec()
|
||||
|
||||
// Set up RewardPeriod, ClaimPeriod, Claim, and previous block time
|
||||
rewardPeriod := types.NewRewardPeriod("btc", time.Now().UTC(), time.Now().Add(time.Hour*1).UTC(),
|
||||
sdk.NewCoin("ukava", sdk.NewInt(10000000000)), time.Now().Add(time.Hour*2).UTC(), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
|
||||
claimPeriod := types.NewClaimPeriod("btc", 1, time.Now().Add(time.Hour*24).UTC(), types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33"))})
|
||||
addr, _ := sdk.AccAddressFromBech32("kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw")
|
||||
claim := types.NewClaim(addr, sdk.NewCoin("ukava", sdk.NewInt(1000000)), "bnb", 1)
|
||||
claim := types.NewUSDXMintingClaim(addr, sdk.NewCoin("ukava", sdk.NewInt(1000000)), types.RewardIndexes{types.NewRewardIndex("bnb-a", sdk.ZeroDec())})
|
||||
prevBlockTime := time.Now().Add(time.Hour * -1).UTC()
|
||||
factor := sdk.ZeroDec()
|
||||
|
||||
kvPairs := kv.Pairs{
|
||||
kv.Pair{Key: types.RewardPeriodKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&rewardPeriod)},
|
||||
kv.Pair{Key: types.ClaimPeriodKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&claimPeriod)},
|
||||
kv.Pair{Key: types.ClaimKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&claim)},
|
||||
kv.Pair{Key: types.NextClaimPeriodIDPrefix, Value: sdk.Uint64ToBigEndian(10)},
|
||||
kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevBlockTime)},
|
||||
kv.Pair{Key: types.ClaimKeyPrefix, Value: cdc.MustMarshalBinaryBare(claim)},
|
||||
kv.Pair{Key: []byte(types.BlockTimeKey), Value: cdc.MustMarshalBinaryBare(prevBlockTime)},
|
||||
kv.Pair{Key: []byte(types.RewardFactorKey), Value: cdc.MustMarshalBinaryBare(factor)},
|
||||
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
|
||||
}
|
||||
|
||||
@ -46,11 +40,9 @@ func TestDecodeDistributionStore(t *testing.T) {
|
||||
name string
|
||||
expectedLog string
|
||||
}{
|
||||
{"RewardPeriod", fmt.Sprintf("%v\n%v", rewardPeriod, rewardPeriod)},
|
||||
{"ClaimPeriod", fmt.Sprintf("%v\n%v", claimPeriod, claimPeriod)},
|
||||
{"Claim", fmt.Sprintf("%v\n%v", claim, claim)},
|
||||
{"NextClaimPeriodID", "10\n10"},
|
||||
{"PreviousBlockTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)},
|
||||
{"RewardFactor", fmt.Sprintf("%v\n%v", factor, factor)},
|
||||
{"other", ""},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
|
@ -2,15 +2,11 @@ package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/module"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
@ -23,115 +19,9 @@ var (
|
||||
// RandomizedGenState generates a random GenesisState for incentive module
|
||||
func RandomizedGenState(simState *module.SimulationState) {
|
||||
|
||||
// Get collateral asset denoms from existing CDP genesis state and pass to incentive params
|
||||
var cdpGenesis cdp.GenesisState
|
||||
simState.Cdc.MustUnmarshalJSON(simState.GenState[cdp.ModuleName], &cdpGenesis)
|
||||
if len(CollateralDenoms) == 0 {
|
||||
for _, collateral := range cdpGenesis.Params.CollateralParams {
|
||||
CollateralDenoms = append(CollateralDenoms, collateral.Type)
|
||||
}
|
||||
}
|
||||
|
||||
params := genParams(simState.Rand)
|
||||
|
||||
// Generate random reward and claim periods
|
||||
rewardPeriods := genRewardPeriods(simState.Rand, simState.GenTimestamp, params.Rewards)
|
||||
claimPeriods := genClaimPeriods(rewardPeriods)
|
||||
claimPeriodIDs := genNextClaimPeriodIds(claimPeriods)
|
||||
|
||||
// New genesis state holds valid, linked reward periods, claim periods, and claim period IDs
|
||||
incentiveGenesis := types.NewGenesisState(params, types.DefaultPreviousBlockTime,
|
||||
rewardPeriods, claimPeriods, types.Claims{}, claimPeriodIDs)
|
||||
if err := incentiveGenesis.Validate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
incentiveGenesis := types.DefaultGenesisState()
|
||||
|
||||
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, incentiveGenesis))
|
||||
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(incentiveGenesis)
|
||||
}
|
||||
|
||||
// genParams generates random rewards and is active by default
|
||||
func genParams(r *rand.Rand) types.Params {
|
||||
params := types.NewParams(true, genRewards(r))
|
||||
if err := params.Validate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
// genRewards generates rewards for each specified collateral type
|
||||
func genRewards(r *rand.Rand) types.Rewards {
|
||||
rewards := make(types.Rewards, len(CollateralDenoms))
|
||||
for i, denom := range CollateralDenoms {
|
||||
active := true
|
||||
// total reward is in range (half max total reward, max total reward)
|
||||
amount := simulation.RandIntBetween(r, int(MaxTotalAssetReward.Int64()/2), int(MaxTotalAssetReward.Int64()))
|
||||
totalRewards := sdk.NewInt64Coin(RewardDenom, int64(amount))
|
||||
// generate a random number of months for lockups
|
||||
numMonthsSmall := simulation.RandIntBetween(r, 0, 6)
|
||||
numMonthsLarge := simulation.RandIntBetween(r, 7, 12)
|
||||
multiplierSmall := types.NewMultiplier(types.Small, int64(numMonthsSmall), sdk.MustNewDecFromStr("0.33"))
|
||||
multiplierLarge := types.NewMultiplier(types.Large, int64(numMonthsLarge), sdk.MustNewDecFromStr("1.0"))
|
||||
|
||||
duration := time.Duration(time.Hour * time.Duration(simulation.RandIntBetween(r, 1, 48)))
|
||||
claimDuration := time.Hour * time.Duration(simulation.RandIntBetween(r, 1, 48)) // twice as long as duration
|
||||
rewards[i] = types.NewReward(active, denom, totalRewards, duration, types.Multipliers{multiplierSmall, multiplierLarge}, claimDuration)
|
||||
}
|
||||
return rewards
|
||||
}
|
||||
|
||||
// genRewardPeriods generates chronological reward periods for each given reward type
|
||||
func genRewardPeriods(r *rand.Rand, timestamp time.Time, rewards types.Rewards) types.RewardPeriods {
|
||||
rewardPeriods := make(types.RewardPeriods, len(rewards))
|
||||
rewardPeriodStart := timestamp
|
||||
|
||||
for i, reward := range rewards {
|
||||
// Set up reward period parameters
|
||||
start := rewardPeriodStart
|
||||
end := start.Add(reward.Duration).UTC()
|
||||
baseRewardAmount := reward.AvailableRewards.Amount.Quo(sdk.NewInt(100)) // base period reward is 1/100 total reward
|
||||
// Earlier periods have larger rewards
|
||||
amount := sdk.NewCoin("ukava", baseRewardAmount.Mul(sdk.NewInt(int64(i))))
|
||||
claimEnd := end.Add(reward.ClaimDuration)
|
||||
claimMultipliers := reward.ClaimMultipliers
|
||||
// Create reward period and append to array
|
||||
rewardPeriods[i] = types.NewRewardPeriod(reward.CollateralType, start, end, amount, claimEnd, claimMultipliers)
|
||||
// Update start time of next reward period
|
||||
rewardPeriodStart = end
|
||||
}
|
||||
return rewardPeriods
|
||||
}
|
||||
|
||||
// genClaimPeriods loads valid claim periods for an array of reward periods
|
||||
func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods {
|
||||
denomRewardPeriodsCount := make(map[string]uint64)
|
||||
claimPeriods := make(types.ClaimPeriods, len(rewardPeriods))
|
||||
for i, rewardPeriod := range rewardPeriods {
|
||||
// Increment reward period count for this denom (this is our claim period's ID)
|
||||
denom := rewardPeriod.CollateralType
|
||||
numbRewardPeriods := denomRewardPeriodsCount[denom] + 1
|
||||
denomRewardPeriodsCount[denom] = numbRewardPeriods
|
||||
// Set end and timelock from the associated reward period
|
||||
end := rewardPeriod.ClaimEnd
|
||||
claimMultipliers := rewardPeriod.ClaimMultipliers
|
||||
// Create the new claim period for this reward period
|
||||
claimPeriods[i] = types.NewClaimPeriod(denom, numbRewardPeriods, end, claimMultipliers)
|
||||
}
|
||||
return claimPeriods
|
||||
}
|
||||
|
||||
// genNextClaimPeriodIds returns an array of the most recent claim period IDs for each denom
|
||||
func genNextClaimPeriodIds(cps types.ClaimPeriods) types.GenesisClaimPeriodIDs {
|
||||
// Build a map of the most recent claim periods by denom
|
||||
mostRecentClaimPeriodByDenom := make(map[string]uint64)
|
||||
var claimPeriodIDs types.GenesisClaimPeriodIDs
|
||||
for _, cp := range cps {
|
||||
if cp.ID <= mostRecentClaimPeriodByDenom[cp.CollateralType] {
|
||||
continue
|
||||
}
|
||||
claimPeriodIDs = append(claimPeriodIDs, types.GenesisClaimPeriodID{CollateralType: cp.CollateralType, ID: cp.ID})
|
||||
mostRecentClaimPeriodByDenom[cp.CollateralType] = cp.ID
|
||||
|
||||
}
|
||||
return claimPeriodIDs
|
||||
}
|
||||
|
@ -1,21 +1,17 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/simapp/helpers"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
|
||||
appparams "github.com/kava-labs/kava/app/params"
|
||||
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
"github.com/kava-labs/kava/x/kavadist"
|
||||
)
|
||||
|
||||
// Simulation operation weights constants
|
||||
@ -49,100 +45,7 @@ func SimulateMsgClaimReward(ak auth.AccountKeeper, sk types.SupplyKeeper, k keep
|
||||
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
|
||||
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
|
||||
|
||||
// Load only account types that can claim rewards
|
||||
validAccounts := make(map[string]bool)
|
||||
for _, acc := range accs {
|
||||
account := ak.GetAccount(ctx, acc.Address)
|
||||
switch account.(type) {
|
||||
case *vesting.PeriodicVestingAccount, *auth.BaseAccount: // Valid: BaseAccount, PeriodicVestingAccount
|
||||
validAccounts[account.GetAddress().String()] = true
|
||||
default: // Invalid: ValidatorVestingAccount, DelayedVestingAccount, ContinuousVestingAccount
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Load open claims and shuffle them to randomize
|
||||
openClaims := types.Claims{}
|
||||
k.IterateClaims(ctx, func(claim types.Claim) bool {
|
||||
openClaims = append(openClaims, claim)
|
||||
return false
|
||||
})
|
||||
r.Shuffle(len(openClaims), func(i, j int) {
|
||||
openClaims[i], openClaims[j] = openClaims[j], openClaims[i]
|
||||
})
|
||||
|
||||
kavadistMacc := sk.GetModuleAccount(ctx, kavadist.KavaDistMacc)
|
||||
kavadistBalance := kavadistMacc.SpendableCoins(ctx.BlockTime())
|
||||
|
||||
// Find address that has a claim of the same reward denom, then confirm it's distributable
|
||||
claimer, claim, found := findValidAccountClaimPair(accs, openClaims, func(acc simulation.Account, claim types.Claim) bool {
|
||||
if validAccounts[acc.Address.String()] { // Address must be valid type
|
||||
if claim.Owner.Equals(acc.Address) { // Account must be claim owner
|
||||
allClaims, found := k.GetActiveClaimsByAddressAndCollateralType(ctx, claim.Owner, claim.CollateralType)
|
||||
if found { // found should always be true
|
||||
var rewards sdk.Coins
|
||||
for _, individualClaim := range allClaims {
|
||||
rewards = rewards.Add(individualClaim.Reward)
|
||||
}
|
||||
if rewards.AmountOf(claim.Reward.Denom).IsPositive() { // Can't distribute 0 coins
|
||||
// Validate that kavadist module has enough coins to distribute rewards
|
||||
if kavadistBalance.AmountOf(claim.Reward.Denom).GTE(rewards.AmountOf(claim.Reward.Denom)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if !found {
|
||||
return simulation.NewOperationMsgBasic(types.ModuleName,
|
||||
"no-operation (no accounts currently have fulfillable claims)", "", false, nil), nil, nil
|
||||
}
|
||||
|
||||
claimerAcc := ak.GetAccount(ctx, claimer.Address)
|
||||
if claimerAcc == nil {
|
||||
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("couldn't find account %s", claimer.Address)
|
||||
}
|
||||
|
||||
multiplierName := types.Small
|
||||
|
||||
if simulation.RandIntBetween(r, 0, 1) == 0 {
|
||||
multiplierName = types.Large
|
||||
}
|
||||
|
||||
msg := types.NewMsgClaimReward(claimer.Address, claim.CollateralType, string(multiplierName))
|
||||
|
||||
tx := helpers.GenTx(
|
||||
[]sdk.Msg{msg},
|
||||
sdk.NewCoins(),
|
||||
helpers.DefaultGenTxGas,
|
||||
chainID,
|
||||
[]uint64{claimerAcc.GetAccountNumber()},
|
||||
[]uint64{claimerAcc.GetSequence()},
|
||||
claimer.PrivKey,
|
||||
)
|
||||
|
||||
_, result, err := app.Deliver(tx)
|
||||
if err != nil {
|
||||
// to aid debugging, add the stack trace to the comment field of the returned opMsg
|
||||
return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err
|
||||
}
|
||||
|
||||
// to aid debugging, add the result log to the comment field
|
||||
return simulation.NewOperationMsg(msg, true, result.Log), nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// findValidAccountClaimPair finds an account and reward claim for which the callback func returns true
|
||||
func findValidAccountClaimPair(accounts []simulation.Account, claims types.Claims,
|
||||
cb func(simulation.Account, types.Claim) bool) (simulation.Account, types.Claim, bool) {
|
||||
for _, claim := range claims {
|
||||
for _, acc := range accounts {
|
||||
if isValid := cb(acc, claim); isValid {
|
||||
return acc, claim, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return simulation.Account{}, types.Claim{}, false
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -23,16 +20,5 @@ func genActive(r *rand.Rand) bool {
|
||||
|
||||
// ParamChanges defines the parameters that can be modified by param change proposals
|
||||
func ParamChanges(r *rand.Rand) []simulation.ParamChange {
|
||||
return []simulation.ParamChange{
|
||||
simulation.NewSimParamChange(types.ModuleName, keyActive,
|
||||
func(r *rand.Rand) string {
|
||||
return fmt.Sprintf("\"%t\"", genActive(r))
|
||||
},
|
||||
),
|
||||
simulation.NewSimParamChange(types.ModuleName, keyRewards,
|
||||
func(r *rand.Rand) string {
|
||||
return fmt.Sprintf("\"%v\"", genRewards(r))
|
||||
},
|
||||
),
|
||||
}
|
||||
return []simulation.ParamChange{}
|
||||
}
|
||||
|
112
x/incentive/types/claims.go
Normal file
112
x/incentive/types/claims.go
Normal file
@ -0,0 +1,112 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// USDXMintingClaim stores the usdx mintng rewards that can be claimed by owner
|
||||
type USDXMintingClaim struct {
|
||||
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
|
||||
Reward sdk.Coin `json:"reward" yaml:"reward"`
|
||||
RewardIndexes RewardIndexes `json:"reward_indexes" yaml:"reward_indexes"`
|
||||
}
|
||||
|
||||
// NewUSDXMintingClaim returns a new USDXMintingClaim
|
||||
func NewUSDXMintingClaim(owner sdk.AccAddress, reward sdk.Coin, rewardIndexes RewardIndexes) USDXMintingClaim {
|
||||
return USDXMintingClaim{
|
||||
Owner: owner,
|
||||
Reward: reward,
|
||||
RewardIndexes: rewardIndexes,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs a basic check of a Claim fields.
|
||||
func (c USDXMintingClaim) Validate() error {
|
||||
if c.Owner.Empty() {
|
||||
return errors.New("claim owner cannot be empty")
|
||||
}
|
||||
if !c.Reward.IsValid() {
|
||||
return fmt.Errorf("invalid reward amount: %s", c.Reward)
|
||||
}
|
||||
return c.RewardIndexes.Validate()
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (c USDXMintingClaim) String() string {
|
||||
return fmt.Sprintf(`Claim:
|
||||
Owner: %s,
|
||||
Reward: %s,
|
||||
Reward Indexes: %s,
|
||||
`, c.Owner, c.Reward, c.RewardIndexes)
|
||||
}
|
||||
|
||||
// HasRewardIndex check if a claim has a reward index for the input collateral type
|
||||
func (c USDXMintingClaim) HasRewardIndex(collateralType string) (int64, bool) {
|
||||
for index, ri := range c.RewardIndexes {
|
||||
if ri.CollateralType == collateralType {
|
||||
return int64(index), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// USDXMintingClaims array of USDXMintingClaim
|
||||
type USDXMintingClaims []USDXMintingClaim
|
||||
|
||||
// Validate checks if all the claims are valid and there are no duplicated
|
||||
// entries.
|
||||
func (cs USDXMintingClaims) Validate() error {
|
||||
for _, c := range cs {
|
||||
if err := c.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RewardIndex stores reward accumulation information
|
||||
type RewardIndex struct {
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
RewardFactor sdk.Dec `json:"reward_factor" yaml:"reward_factor"`
|
||||
}
|
||||
|
||||
// NewRewardIndex returns a new RewardIndex
|
||||
func NewRewardIndex(collateralType string, factor sdk.Dec) RewardIndex {
|
||||
return RewardIndex{
|
||||
CollateralType: collateralType,
|
||||
RewardFactor: factor,
|
||||
}
|
||||
}
|
||||
|
||||
func (ri RewardIndex) String() string {
|
||||
return fmt.Sprintf(`Collateral Type: %s, RewardFactor: %s`, ri.CollateralType, ri.RewardFactor)
|
||||
}
|
||||
|
||||
// Validate validates reward index
|
||||
func (ri RewardIndex) Validate() error {
|
||||
if ri.RewardFactor.IsNegative() {
|
||||
return fmt.Errorf("reward factor value should be positive, is %s for %s", ri.RewardFactor, ri.CollateralType)
|
||||
}
|
||||
if strings.TrimSpace(ri.CollateralType) == "" {
|
||||
return fmt.Errorf("collateral type should not be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RewardIndexes slice of RewardIndex
|
||||
type RewardIndexes []RewardIndex
|
||||
|
||||
// Validate validation for reward indexes
|
||||
func (ris RewardIndexes) Validate() error {
|
||||
for _, ri := range ris {
|
||||
if err := ri.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
66
x/incentive/types/claims_test.go
Normal file
66
x/incentive/types/claims_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func TestClaimsValidate(t *testing.T) {
|
||||
owner := sdk.AccAddress(crypto.AddressHash([]byte("KavaTestUser1")))
|
||||
|
||||
testCases := []struct {
|
||||
msg string
|
||||
claims USDXMintingClaims
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
"valid",
|
||||
USDXMintingClaims{
|
||||
NewUSDXMintingClaim(owner, sdk.NewCoin("bnb", sdk.OneInt()), RewardIndexes{NewRewardIndex("bnb-a", sdk.ZeroDec())}),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid owner",
|
||||
USDXMintingClaims{
|
||||
{Owner: nil},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid reward",
|
||||
USDXMintingClaims{
|
||||
{
|
||||
Owner: owner,
|
||||
Reward: sdk.Coin{Denom: "", Amount: sdk.ZeroInt()},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid collateral type",
|
||||
USDXMintingClaims{
|
||||
{
|
||||
Owner: owner,
|
||||
Reward: sdk.NewCoin("bnb", sdk.OneInt()),
|
||||
RewardIndexes: []RewardIndex{{"", sdk.ZeroDec()}},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := tc.claims.Validate()
|
||||
if tc.expPass {
|
||||
require.NoError(t, err, tc.msg)
|
||||
} else {
|
||||
require.Error(t, err, tc.msg)
|
||||
}
|
||||
}
|
||||
}
|
@ -14,9 +14,5 @@ func init() {
|
||||
|
||||
// RegisterCodec registers the necessary types for incentive module
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
cdc.RegisterConcrete(MsgClaimReward{}, "incentive/MsgClaimReward", nil)
|
||||
cdc.RegisterConcrete(GenesisClaimPeriodID{}, "incentive/GenesisClaimPeriodID", nil)
|
||||
cdc.RegisterConcrete(RewardPeriod{}, "incentive/RewardPeriod", nil)
|
||||
cdc.RegisterConcrete(ClaimPeriod{}, "incentive/ClaimPeriod", nil)
|
||||
cdc.RegisterConcrete(Claim{}, "incentive/Claim", nil)
|
||||
cdc.RegisterConcrete(MsgClaimUSDXMintingReward{}, "incentive/MsgClaimUSDXMintingReward", nil)
|
||||
}
|
||||
|
@ -9,11 +9,12 @@ import (
|
||||
// Incentive module errors
|
||||
var (
|
||||
ErrClaimNotFound = sdkerrors.Register(ModuleName, 2, "no claim with input id found for owner and collateral type")
|
||||
ErrClaimPeriodNotFound = sdkerrors.Register(ModuleName, 3, "no claim period found for id and collateral type")
|
||||
ErrRewardPeriodNotFound = sdkerrors.Register(ModuleName, 3, "no reward period found for collateral type")
|
||||
ErrInvalidAccountType = sdkerrors.Register(ModuleName, 4, "account type not supported")
|
||||
ErrNoClaimsFound = sdkerrors.Register(ModuleName, 5, "no claims with collateral type found for address")
|
||||
ErrInsufficientModAccountBalance = sdkerrors.Register(ModuleName, 6, "module account has insufficient balance to pay claim")
|
||||
ErrAccountNotFound = sdkerrors.Register(ModuleName, 7, "account not found")
|
||||
ErrInvalidMultiplier = sdkerrors.Register(ModuleName, 8, "invalid rewards multiplier")
|
||||
ErrZeroClaim = sdkerrors.Register(ModuleName, 9, "cannot claim - claim amount rounds to zero")
|
||||
ErrClaimExpired = sdkerrors.Register(ModuleName, 10, "claim has expired")
|
||||
)
|
||||
|
@ -17,8 +17,9 @@ type SupplyKeeper interface {
|
||||
|
||||
// CdpKeeper defines the expected cdp keeper for interacting with cdps
|
||||
type CdpKeeper interface {
|
||||
IterateCdpsByCollateralType(ctx sdk.Context, collateralType string, cb func(cdp cdptypes.CDP) (stop bool))
|
||||
GetTotalPrincipal(ctx sdk.Context, collateralType string, principalDenom string) (total sdk.Int)
|
||||
GetCdpByOwnerAndCollateralType(ctx sdk.Context, owner sdk.AccAddress, collateralType string) (cdptypes.CDP, bool)
|
||||
GetInterestFactor(ctx sdk.Context, collateralType string) (sdk.Dec, bool)
|
||||
}
|
||||
|
||||
// AccountKeeper defines the expected keeper interface for interacting with account
|
||||
@ -26,3 +27,9 @@ type AccountKeeper interface {
|
||||
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
|
||||
SetAccount(ctx sdk.Context, acc authexported.Account)
|
||||
}
|
||||
|
||||
// CDPHooks event hooks for other keepers to run code in response to CDP modifications
|
||||
type CDPHooks interface {
|
||||
AfterCDPCreated(ctx sdk.Context, cdp cdptypes.CDP)
|
||||
BeforeCDPModified(ctx sdk.Context, cdp cdptypes.CDP)
|
||||
}
|
||||
|
@ -2,71 +2,25 @@ package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// GenesisClaimPeriodID stores the next claim id and its corresponding collateral type
|
||||
type GenesisClaimPeriodID struct {
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
ID uint64 `json:"id" yaml:"id"`
|
||||
}
|
||||
|
||||
// Validate performs a basic check of a GenesisClaimPeriodID fields.
|
||||
func (gcp GenesisClaimPeriodID) Validate() error {
|
||||
if gcp.ID == 0 {
|
||||
return errors.New("genesis claim period id cannot be 0")
|
||||
}
|
||||
if strings.TrimSpace(gcp.CollateralType) == "" {
|
||||
return fmt.Errorf("collateral type cannot be blank: %v", gcp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenesisClaimPeriodIDs array of GenesisClaimPeriodID
|
||||
type GenesisClaimPeriodIDs []GenesisClaimPeriodID
|
||||
|
||||
// Validate checks if all the GenesisClaimPeriodIDs are valid and there are no duplicated
|
||||
// entries.
|
||||
func (gcps GenesisClaimPeriodIDs) Validate() error {
|
||||
seenIDS := make(map[string]bool)
|
||||
var key string
|
||||
for _, gcp := range gcps {
|
||||
key = gcp.CollateralType + fmt.Sprint(gcp.ID)
|
||||
if seenIDS[key] {
|
||||
return fmt.Errorf("duplicated genesis claim period with id %d and collateral type %s", gcp.ID, gcp.CollateralType)
|
||||
}
|
||||
|
||||
if err := gcp.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
seenIDS[key] = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenesisState is the state that must be provided at genesis.
|
||||
type GenesisState struct {
|
||||
Params Params `json:"params" yaml:"params"`
|
||||
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
|
||||
RewardPeriods RewardPeriods `json:"reward_periods" yaml:"reward_periods"`
|
||||
ClaimPeriods ClaimPeriods `json:"claim_periods" yaml:"claim_periods"`
|
||||
Claims Claims `json:"claims" yaml:"claims"`
|
||||
NextClaimPeriodIDs GenesisClaimPeriodIDs `json:"next_claim_period_ids" yaml:"next_claim_period_ids"`
|
||||
PreviousAccumulationTimes GenesisAccumulationTimes `json:"previous_accumulation_times" yaml:"previous_accumulation_times"`
|
||||
USDXMintingClaims USDXMintingClaims `json:"usdx_minting_claims" yaml:"usdx_minting_claims"`
|
||||
}
|
||||
|
||||
// NewGenesisState returns a new genesis state
|
||||
func NewGenesisState(params Params, previousBlockTime time.Time, rp RewardPeriods, cp ClaimPeriods, c Claims, ids GenesisClaimPeriodIDs) GenesisState {
|
||||
func NewGenesisState(params Params, prevAccumTimes GenesisAccumulationTimes, c USDXMintingClaims) GenesisState {
|
||||
return GenesisState{
|
||||
Params: params,
|
||||
PreviousBlockTime: previousBlockTime,
|
||||
RewardPeriods: rp,
|
||||
ClaimPeriods: cp,
|
||||
Claims: c,
|
||||
NextClaimPeriodIDs: ids,
|
||||
PreviousAccumulationTimes: prevAccumTimes,
|
||||
USDXMintingClaims: c,
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,11 +28,8 @@ func NewGenesisState(params Params, previousBlockTime time.Time, rp RewardPeriod
|
||||
func DefaultGenesisState() GenesisState {
|
||||
return GenesisState{
|
||||
Params: DefaultParams(),
|
||||
PreviousBlockTime: DefaultPreviousBlockTime,
|
||||
RewardPeriods: RewardPeriods{},
|
||||
ClaimPeriods: ClaimPeriods{},
|
||||
Claims: Claims{},
|
||||
NextClaimPeriodIDs: GenesisClaimPeriodIDs{},
|
||||
PreviousAccumulationTimes: GenesisAccumulationTimes{},
|
||||
USDXMintingClaims: DefaultClaims,
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,19 +39,11 @@ func (gs GenesisState) Validate() error {
|
||||
if err := gs.Params.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if gs.PreviousBlockTime.IsZero() {
|
||||
return errors.New("previous block time cannot be 0")
|
||||
}
|
||||
if err := gs.RewardPeriods.Validate(); err != nil {
|
||||
if err := gs.PreviousAccumulationTimes.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gs.ClaimPeriods.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gs.Claims.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return gs.NextClaimPeriodIDs.Validate()
|
||||
|
||||
return gs.USDXMintingClaims.Validate()
|
||||
}
|
||||
|
||||
// Equal checks whether two gov GenesisState structs are equivalent
|
||||
@ -114,3 +57,43 @@ func (gs GenesisState) Equal(gs2 GenesisState) bool {
|
||||
func (gs GenesisState) IsEmpty() bool {
|
||||
return gs.Equal(GenesisState{})
|
||||
}
|
||||
|
||||
// GenesisAccumulationTime stores the previous reward distribution time and its corresponding collateral type
|
||||
type GenesisAccumulationTime struct {
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
PreviousAccumulationTime time.Time `json:"previous_accumulation_time" yaml:"previous_accumulation_time"`
|
||||
RewardFactor sdk.Dec `json:"reward_factor" yaml:"reward_factor"`
|
||||
}
|
||||
|
||||
// NewGenesisAccumulationTime returns a new GenesisAccumulationTime
|
||||
func NewGenesisAccumulationTime(ctype string, prevTime time.Time, factor sdk.Dec) GenesisAccumulationTime {
|
||||
return GenesisAccumulationTime{
|
||||
CollateralType: ctype,
|
||||
PreviousAccumulationTime: prevTime,
|
||||
RewardFactor: factor,
|
||||
}
|
||||
}
|
||||
|
||||
// GenesisAccumulationTimes slice of GenesisAccumulationTime
|
||||
type GenesisAccumulationTimes []GenesisAccumulationTime
|
||||
|
||||
// Validate performs validation of GenesisAccumulationTimes
|
||||
func (gats GenesisAccumulationTimes) Validate() error {
|
||||
for _, gat := range gats {
|
||||
if err := gat.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate performs validation of GenesisAccumulationTime
|
||||
func (gat GenesisAccumulationTime) Validate() error {
|
||||
if gat.RewardFactor == (sdk.Dec{}) {
|
||||
return fmt.Errorf("reward factor not initialized for %s", gat.CollateralType)
|
||||
}
|
||||
if gat.RewardFactor.LT(sdk.ZeroDec()) {
|
||||
return fmt.Errorf("reward factor should be ≥ 0.0, is %s for %s", gat.RewardFactor, gat.CollateralType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,166 +1,145 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func TestGenesisClaimPeriodIDsValidate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
msg string
|
||||
genesisClaimPeriodIDs GenesisClaimPeriodIDs
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
"valid",
|
||||
GenesisClaimPeriodIDs{
|
||||
{CollateralType: "bnb", ID: 1},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid collateral type",
|
||||
GenesisClaimPeriodIDs{
|
||||
{CollateralType: "", ID: 1},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid ID",
|
||||
GenesisClaimPeriodIDs{
|
||||
{CollateralType: "bnb", ID: 0},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"duplicate",
|
||||
GenesisClaimPeriodIDs{
|
||||
{CollateralType: "bnb", ID: 1},
|
||||
{CollateralType: "bnb", ID: 1},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := tc.genesisClaimPeriodIDs.Validate()
|
||||
if tc.expPass {
|
||||
require.NoError(t, err, tc.msg)
|
||||
} else {
|
||||
require.Error(t, err, tc.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenesisStateValidate(t *testing.T) {
|
||||
now := time.Now()
|
||||
mockPrivKey := tmtypes.NewMockPV()
|
||||
pubkey, err := mockPrivKey.GetPubKey()
|
||||
require.NoError(t, err)
|
||||
owner := sdk.AccAddress(pubkey.Address())
|
||||
|
||||
rewards := Rewards{
|
||||
NewReward(
|
||||
true, "bnb", sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||
time.Hour*24*7, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))}, time.Hour*24*14,
|
||||
),
|
||||
type args struct {
|
||||
params Params
|
||||
genAccTimes GenesisAccumulationTimes
|
||||
claims USDXMintingClaims
|
||||
}
|
||||
|
||||
type errArgs struct {
|
||||
expectPass bool
|
||||
contains string
|
||||
}
|
||||
rewardPeriods := RewardPeriods{NewRewardPeriod("bnb", now, now.Add(time.Hour), sdk.NewCoin("bnb", sdk.OneInt()), now, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))})}
|
||||
claimPeriods := ClaimPeriods{NewClaimPeriod("bnb", 10, now, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))})}
|
||||
claims := Claims{NewClaim(owner, sdk.NewCoin("bnb", sdk.OneInt()), "bnb", 10)}
|
||||
gcps := GenesisClaimPeriodIDs{{CollateralType: "bnb", ID: 1}}
|
||||
|
||||
testCases := []struct {
|
||||
msg string
|
||||
genesisState GenesisState
|
||||
expPass bool
|
||||
name string
|
||||
args args
|
||||
errArgs errArgs
|
||||
}{
|
||||
{
|
||||
msg: "default",
|
||||
genesisState: DefaultGenesisState(),
|
||||
expPass: true,
|
||||
name: "default",
|
||||
args: args{
|
||||
params: DefaultParams(),
|
||||
genAccTimes: DefaultGenesisAccumulationTimes,
|
||||
claims: DefaultClaims,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
expectPass: true,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "valid genesis",
|
||||
genesisState: NewGenesisState(
|
||||
NewParams(true, rewards),
|
||||
now, rewardPeriods, claimPeriods, claims, gcps,
|
||||
),
|
||||
expPass: true,
|
||||
name: "valid",
|
||||
args: args{
|
||||
params: NewParams(RewardPeriods{NewRewardPeriod(true, "bnb-a", time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC), sdk.NewCoin("ukava", sdk.NewInt(25000)))}, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))}, time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC)),
|
||||
genAccTimes: GenesisAccumulationTimes{GenesisAccumulationTime{
|
||||
CollateralType: "bnb-a",
|
||||
PreviousAccumulationTime: time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||
RewardFactor: sdk.ZeroDec(),
|
||||
}},
|
||||
claims: USDXMintingClaims{
|
||||
{
|
||||
Owner: sdk.AccAddress(crypto.AddressHash([]byte("KavaTestUser1"))),
|
||||
Reward: sdk.NewCoin("ukava", sdk.NewInt(100000000)),
|
||||
|
||||
RewardIndexes: []RewardIndex{
|
||||
{
|
||||
CollateralType: "bnb-a",
|
||||
RewardFactor: sdk.ZeroDec(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errArgs: errArgs{
|
||||
expectPass: true,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
msg: "invalid Params",
|
||||
genesisState: GenesisState{
|
||||
Params: Params{
|
||||
Active: true,
|
||||
Rewards: Rewards{
|
||||
Reward{},
|
||||
name: "invalid genesis accumulation time",
|
||||
args: args{
|
||||
params: DefaultParams(),
|
||||
genAccTimes: GenesisAccumulationTimes{
|
||||
{
|
||||
CollateralType: "btcb-a",
|
||||
RewardFactor: sdk.MustNewDecFromStr("-0.1"),
|
||||
},
|
||||
},
|
||||
claims: DefaultClaims,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "reward factor should be ≥ 0.0",
|
||||
},
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
msg: "zero PreviousBlockTime",
|
||||
genesisState: GenesisState{
|
||||
PreviousBlockTime: time.Time{},
|
||||
name: "invalid genesis accumulation time",
|
||||
args: args{
|
||||
params: DefaultParams(),
|
||||
genAccTimes: GenesisAccumulationTimes{
|
||||
{
|
||||
CollateralType: "btcb-a",
|
||||
PreviousAccumulationTime: time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||
RewardFactor: sdk.Dec{},
|
||||
},
|
||||
},
|
||||
claims: DefaultClaims,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "reward factor not initialized",
|
||||
},
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
msg: "invalid RewardsPeriod",
|
||||
genesisState: GenesisState{
|
||||
PreviousBlockTime: now,
|
||||
RewardPeriods: RewardPeriods{
|
||||
{Start: time.Time{}},
|
||||
},
|
||||
},
|
||||
expPass: false,
|
||||
},
|
||||
name: "invalid claim",
|
||||
args: args{
|
||||
params: DefaultParams(),
|
||||
genAccTimes: DefaultGenesisAccumulationTimes,
|
||||
claims: USDXMintingClaims{
|
||||
{
|
||||
msg: "invalid ClaimPeriods",
|
||||
genesisState: GenesisState{
|
||||
PreviousBlockTime: now,
|
||||
ClaimPeriods: ClaimPeriods{
|
||||
{ID: 0},
|
||||
},
|
||||
},
|
||||
expPass: false,
|
||||
},
|
||||
Owner: sdk.AccAddress{},
|
||||
Reward: sdk.NewCoin("ukava", sdk.NewInt(100000000)),
|
||||
|
||||
RewardIndexes: []RewardIndex{
|
||||
{
|
||||
msg: "invalid Claims",
|
||||
genesisState: GenesisState{
|
||||
PreviousBlockTime: now,
|
||||
Claims: Claims{
|
||||
{ClaimPeriodID: 0},
|
||||
CollateralType: "bnb-a",
|
||||
RewardFactor: sdk.ZeroDec(),
|
||||
},
|
||||
},
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
msg: "invalid NextClaimPeriodIds",
|
||||
genesisState: GenesisState{
|
||||
PreviousBlockTime: now,
|
||||
NextClaimPeriodIDs: GenesisClaimPeriodIDs{
|
||||
{ID: 0},
|
||||
},
|
||||
},
|
||||
expPass: false,
|
||||
},
|
||||
errArgs: errArgs{
|
||||
expectPass: false,
|
||||
contains: "claim owner cannot be empty",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := tc.genesisState.Validate()
|
||||
if tc.expPass {
|
||||
require.NoError(t, err, tc.msg)
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gs := NewGenesisState(tc.args.params, tc.args.genAccTimes, tc.args.claims)
|
||||
err := gs.Validate()
|
||||
if tc.errArgs.expectPass {
|
||||
require.NoError(t, err, tc.name)
|
||||
} else {
|
||||
require.Error(t, err, tc.msg)
|
||||
}
|
||||
require.Error(t, err, tc.name)
|
||||
require.True(t, strings.Contains(err.Error(), tc.errArgs.contains))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,5 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// ModuleName The name that will be used throughout the module
|
||||
ModuleName = "incentive"
|
||||
@ -25,37 +19,9 @@ const (
|
||||
|
||||
// Key Prefixes
|
||||
var (
|
||||
RewardPeriodKeyPrefix = []byte{0x01} // prefix for keys that store reward periods
|
||||
ClaimPeriodKeyPrefix = []byte{0x02} // prefix for keys that store claim periods
|
||||
ClaimKeyPrefix = []byte{0x03} // prefix for keys that store claims
|
||||
NextClaimPeriodIDPrefix = []byte{0x04} // prefix for keys that store the next ID for claims periods
|
||||
PreviousBlockTimeKey = []byte{0x05} // prefix for key that stores the previous blocktime
|
||||
ClaimKeyPrefix = []byte{0x01} // prefix for keys that store claims
|
||||
BlockTimeKey = []byte{0x02} // prefix for key that stores the blocktime
|
||||
RewardFactorKey = []byte{0x03} // prefix for key that stores reward factors
|
||||
|
||||
USDXMintingRewardDenom = "ukava"
|
||||
)
|
||||
|
||||
// Keys
|
||||
// 0x00:CollateralType <> RewardPeriod the current active reward period (max 1 reward period per collateral type)
|
||||
// 0x01:CollateralType:ID <> ClaimPeriod object for that ID, indexed by collateral type and ID
|
||||
// 0x02:CollateralType:ID:Owner <> Claim object, indexed by collateral type, ID and owner
|
||||
// 0x03:CollateralType <> NextClaimPeriodIDPrefix the ID of the next claim period, indexed by collateral type
|
||||
|
||||
// BytesToUint64 returns uint64 format from a byte array
|
||||
func BytesToUint64(bz []byte) uint64 {
|
||||
return binary.BigEndian.Uint64(bz)
|
||||
}
|
||||
|
||||
// GetClaimPeriodPrefix returns the key (collateral type + id) for a claim prefix
|
||||
func GetClaimPeriodPrefix(collateralType string, id uint64) []byte {
|
||||
return createKey([]byte(collateralType), sdk.Uint64ToBigEndian(id))
|
||||
}
|
||||
|
||||
// GetClaimPrefix returns the key (collateral type + id + address) for a claim
|
||||
func GetClaimPrefix(addr sdk.AccAddress, collateralType string, id uint64) []byte {
|
||||
return createKey([]byte(collateralType), sdk.Uint64ToBigEndian(id), addr)
|
||||
}
|
||||
|
||||
func createKey(bytes ...[]byte) (r []byte) {
|
||||
for _, b := range bytes {
|
||||
r = append(r, b...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
@ -9,48 +8,43 @@ import (
|
||||
)
|
||||
|
||||
// ensure Msg interface compliance at compile time
|
||||
var _ sdk.Msg = &MsgClaimReward{}
|
||||
var _ sdk.Msg = &MsgClaimUSDXMintingReward{}
|
||||
|
||||
// MsgClaimReward message type used to claim rewards
|
||||
type MsgClaimReward struct {
|
||||
// MsgClaimUSDXMintingReward message type used to claim rewards
|
||||
type MsgClaimUSDXMintingReward struct {
|
||||
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
|
||||
}
|
||||
|
||||
// NewMsgClaimReward returns a new MsgClaimReward.
|
||||
func NewMsgClaimReward(sender sdk.AccAddress, collateralType, multiplierName string) MsgClaimReward {
|
||||
return MsgClaimReward{
|
||||
// NewMsgClaimUSDXMintingReward returns a new MsgClaimUSDXMintingReward.
|
||||
func NewMsgClaimUSDXMintingReward(sender sdk.AccAddress, multiplierName string) MsgClaimUSDXMintingReward {
|
||||
return MsgClaimUSDXMintingReward{
|
||||
Sender: sender,
|
||||
CollateralType: collateralType,
|
||||
MultiplierName: multiplierName,
|
||||
}
|
||||
}
|
||||
|
||||
// Route return the message type used for routing the message.
|
||||
func (msg MsgClaimReward) Route() string { return RouterKey }
|
||||
func (msg MsgClaimUSDXMintingReward) Route() string { return RouterKey }
|
||||
|
||||
// Type returns a human-readable string for the message, intended for utilization within tags.
|
||||
func (msg MsgClaimReward) Type() string { return "claim_reward" }
|
||||
func (msg MsgClaimUSDXMintingReward) Type() string { return "claim_reward" }
|
||||
|
||||
// ValidateBasic does a simple validation check that doesn't require access to state.
|
||||
func (msg MsgClaimReward) ValidateBasic() error {
|
||||
func (msg MsgClaimUSDXMintingReward) ValidateBasic() error {
|
||||
if msg.Sender.Empty() {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
|
||||
}
|
||||
if strings.TrimSpace(msg.CollateralType) == "" {
|
||||
return fmt.Errorf("collateral type cannot be blank")
|
||||
}
|
||||
return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid()
|
||||
}
|
||||
|
||||
// GetSignBytes gets the canonical byte representation of the Msg.
|
||||
func (msg MsgClaimReward) GetSignBytes() []byte {
|
||||
func (msg MsgClaimUSDXMintingReward) GetSignBytes() []byte {
|
||||
bz := ModuleCdc.MustMarshalJSON(msg)
|
||||
return sdk.MustSortJSON(bz)
|
||||
}
|
||||
|
||||
// GetSigners returns the addresses of signers that must sign.
|
||||
func (msg MsgClaimReward) GetSigners() []sdk.AccAddress {
|
||||
func (msg MsgClaimUSDXMintingReward) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.Sender}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
|
||||
type msgTest struct {
|
||||
from sdk.AccAddress
|
||||
collateralType string
|
||||
multiplierName string
|
||||
expectPass bool
|
||||
}
|
||||
@ -29,25 +28,26 @@ func (suite *MsgTestSuite) SetupTest() {
|
||||
tests := []msgTest{
|
||||
{
|
||||
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
||||
collateralType: "bnb",
|
||||
multiplierName: "large",
|
||||
expectPass: true,
|
||||
},
|
||||
{
|
||||
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
||||
collateralType: "",
|
||||
multiplierName: "medium",
|
||||
expectPass: true,
|
||||
},
|
||||
{
|
||||
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
||||
multiplierName: "small",
|
||||
expectPass: false,
|
||||
expectPass: true,
|
||||
},
|
||||
{
|
||||
from: sdk.AccAddress{},
|
||||
collateralType: "bnb",
|
||||
multiplierName: "medium",
|
||||
expectPass: false,
|
||||
},
|
||||
{
|
||||
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
||||
collateralType: "bnb",
|
||||
multiplierName: "huge",
|
||||
expectPass: false,
|
||||
},
|
||||
@ -57,7 +57,7 @@ func (suite *MsgTestSuite) SetupTest() {
|
||||
|
||||
func (suite *MsgTestSuite) TestMsgValidation() {
|
||||
for _, t := range suite.tests {
|
||||
msg := types.NewMsgClaimReward(t.from, t.collateralType, t.multiplierName)
|
||||
msg := types.NewMsgClaimUSDXMintingReward(t.from, t.multiplierName)
|
||||
err := msg.ValidateBasic()
|
||||
if t.expectPass {
|
||||
suite.Require().NoError(err)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@ -24,10 +25,15 @@ const (
|
||||
// Parameter keys and default values
|
||||
var (
|
||||
KeyActive = []byte("Active")
|
||||
KeyRewards = []byte("Rewards")
|
||||
KeyRewards = []byte("RewardPeriods")
|
||||
KeyClaimEnd = []byte("ClaimEnd")
|
||||
KeyMultipliers = []byte("ClaimMultipliers")
|
||||
DefaultActive = false
|
||||
DefaultRewards = Rewards{}
|
||||
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
|
||||
DefaultRewardPeriods = RewardPeriods{}
|
||||
DefaultMultipliers = Multipliers{}
|
||||
DefaultClaims = USDXMintingClaims{}
|
||||
DefaultGenesisAccumulationTimes = GenesisAccumulationTimes{}
|
||||
DefaultClaimEnd = tmtime.Canonical(time.Unix(0, 0))
|
||||
GovDenom = cdptypes.DefaultGovDenom
|
||||
PrincipalDenom = "usdx"
|
||||
IncentiveMacc = kavadistTypes.ModuleName
|
||||
@ -35,28 +41,32 @@ var (
|
||||
|
||||
// Params governance parameters for the incentive module
|
||||
type Params struct {
|
||||
Active bool `json:"active" yaml:"active"` // top level governance switch to disable all rewards
|
||||
Rewards Rewards `json:"rewards" yaml:"rewards"`
|
||||
RewardPeriods RewardPeriods `json:"reward_periods" yaml:"reward_periods"`
|
||||
ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"`
|
||||
ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"`
|
||||
}
|
||||
|
||||
// NewParams returns a new params object
|
||||
func NewParams(active bool, rewards Rewards) Params {
|
||||
func NewParams(rewards RewardPeriods, multipliers Multipliers, claimEnd time.Time) Params {
|
||||
return Params{
|
||||
Active: active,
|
||||
Rewards: rewards,
|
||||
RewardPeriods: rewards,
|
||||
ClaimMultipliers: multipliers,
|
||||
ClaimEnd: claimEnd,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultParams returns default params for incentive module
|
||||
func DefaultParams() Params {
|
||||
return NewParams(DefaultActive, DefaultRewards)
|
||||
return NewParams(DefaultRewardPeriods, DefaultMultipliers, DefaultClaimEnd)
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (p Params) String() string {
|
||||
return fmt.Sprintf(`Params:
|
||||
Active: %t
|
||||
Rewards: %s`, p.Active, p.Rewards)
|
||||
Rewards: %s
|
||||
Claim Multipliers :%s
|
||||
Claim End Time: %s
|
||||
`, p.RewardPeriods, p.ClaimMultipliers, p.ClaimEnd)
|
||||
}
|
||||
|
||||
// ParamKeyTable Key declaration for parameters
|
||||
@ -67,18 +77,19 @@ func ParamKeyTable() params.KeyTable {
|
||||
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
|
||||
func (p *Params) ParamSetPairs() params.ParamSetPairs {
|
||||
return params.ParamSetPairs{
|
||||
params.NewParamSetPair(KeyActive, &p.Active, validateActiveParam),
|
||||
params.NewParamSetPair(KeyRewards, &p.Rewards, validateRewardsParam),
|
||||
params.NewParamSetPair(KeyRewards, &p.RewardPeriods, validateRewardsParam),
|
||||
params.NewParamSetPair(KeyClaimEnd, &p.ClaimEnd, validateClaimEndParam),
|
||||
params.NewParamSetPair(KeyMultipliers, &p.ClaimMultipliers, validateMultipliersParam),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks that the parameters have valid values.
|
||||
func (p Params) Validate() error {
|
||||
if err := validateActiveParam(p.Active); err != nil {
|
||||
if err := validateMultipliersParam(p.ClaimMultipliers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return validateRewardsParam(p.Rewards)
|
||||
return validateRewardsParam(p.RewardPeriods)
|
||||
}
|
||||
|
||||
func validateActiveParam(i interface{}) error {
|
||||
@ -90,7 +101,7 @@ func validateActiveParam(i interface{}) error {
|
||||
}
|
||||
|
||||
func validateRewardsParam(i interface{}) error {
|
||||
rewards, ok := i.(Rewards)
|
||||
rewards, ok := i.(RewardPeriods)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
@ -98,93 +109,97 @@ func validateRewardsParam(i interface{}) error {
|
||||
return rewards.Validate()
|
||||
}
|
||||
|
||||
// Reward stores the specified state for a single reward period.
|
||||
type Reward struct {
|
||||
Active bool `json:"active" yaml:"active"` // governance switch to disable a period
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"` // the collateral type rewards apply to, must be found in the cdp collaterals
|
||||
AvailableRewards sdk.Coin `json:"available_rewards" yaml:"available_rewards"` // the total amount of coins distributed per period
|
||||
Duration time.Duration `json:"duration" yaml:"duration"` // the duration of the period
|
||||
ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"` // the reward multiplier and timelock schedule - applied at the time users claim rewards
|
||||
ClaimDuration time.Duration `json:"claim_duration" yaml:"claim_duration"` // how long users have after the period ends to claim their rewards
|
||||
func validateMultipliersParam(i interface{}) error {
|
||||
multipliers, ok := i.(Multipliers)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
return multipliers.Validate()
|
||||
}
|
||||
|
||||
// NewReward returns a new Reward
|
||||
func NewReward(active bool, collateralType string, reward sdk.Coin, duration time.Duration, multiplier Multipliers, claimDuration time.Duration) Reward {
|
||||
return Reward{
|
||||
func validateClaimEndParam(i interface{}) error {
|
||||
endTime, ok := i.(time.Time)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
if endTime.IsZero() {
|
||||
return fmt.Errorf("end time should not be zero")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RewardPeriod stores the state of an ongoing reward
|
||||
type RewardPeriod struct {
|
||||
Active bool `json:"active" yaml:"active"`
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
Start time.Time `json:"start" yaml:"start"`
|
||||
End time.Time `json:"end" yaml:"end"`
|
||||
RewardsPerSecond sdk.Coin `json:"rewards_per_second" yaml:"rewards_per_second"` // per second reward payouts
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (rp RewardPeriod) String() string {
|
||||
return fmt.Sprintf(`Reward Period:
|
||||
Collateral Type: %s,
|
||||
Start: %s,
|
||||
End: %s,
|
||||
Rewards Per Second: %s,
|
||||
Active %t,
|
||||
`, rp.CollateralType, rp.Start, rp.End, rp.RewardsPerSecond, rp.Active)
|
||||
}
|
||||
|
||||
// NewRewardPeriod returns a new RewardPeriod
|
||||
func NewRewardPeriod(active bool, collateralType string, start time.Time, end time.Time, reward sdk.Coin) RewardPeriod {
|
||||
return RewardPeriod{
|
||||
Active: active,
|
||||
CollateralType: collateralType,
|
||||
AvailableRewards: reward,
|
||||
Duration: duration,
|
||||
ClaimMultipliers: multiplier,
|
||||
ClaimDuration: claimDuration,
|
||||
Start: start,
|
||||
End: end,
|
||||
RewardsPerSecond: reward,
|
||||
}
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (r Reward) String() string {
|
||||
return fmt.Sprintf(`Reward:
|
||||
Active: %t,
|
||||
CollateralType: %s,
|
||||
Available Rewards: %s,
|
||||
Duration: %s,
|
||||
%s,
|
||||
Claim Duration: %s`,
|
||||
r.Active, r.CollateralType, r.AvailableRewards, r.Duration, r.ClaimMultipliers, r.ClaimDuration)
|
||||
// Validate performs a basic check of a RewardPeriod fields.
|
||||
func (rp RewardPeriod) Validate() error {
|
||||
if rp.Start.IsZero() {
|
||||
return errors.New("reward period start time cannot be 0")
|
||||
}
|
||||
|
||||
// Validate performs a basic check of a reward fields.
|
||||
func (r Reward) Validate() error {
|
||||
if !r.AvailableRewards.IsValid() {
|
||||
return fmt.Errorf("invalid reward coins %s for %s", r.AvailableRewards, r.CollateralType)
|
||||
if rp.End.IsZero() {
|
||||
return errors.New("reward period end time cannot be 0")
|
||||
}
|
||||
if !r.AvailableRewards.IsPositive() {
|
||||
return fmt.Errorf("reward amount must be positive, is %s for %s", r.AvailableRewards, r.CollateralType)
|
||||
if rp.Start.After(rp.End) {
|
||||
return fmt.Errorf("end period time %s cannot be before start time %s", rp.End, rp.Start)
|
||||
}
|
||||
if r.Duration <= 0 {
|
||||
return fmt.Errorf("reward duration must be positive, is %s for %s", r.Duration, r.CollateralType)
|
||||
if !rp.RewardsPerSecond.IsValid() {
|
||||
return fmt.Errorf("invalid reward amount: %s", rp.RewardsPerSecond)
|
||||
}
|
||||
if err := r.ClaimMultipliers.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if r.ClaimDuration <= 0 {
|
||||
return fmt.Errorf("claim duration must be positive, is %s for %s", r.ClaimDuration, r.CollateralType)
|
||||
}
|
||||
if strings.TrimSpace(r.CollateralType) == "" {
|
||||
return fmt.Errorf("collateral type cannot be blank: %s", r)
|
||||
if strings.TrimSpace(rp.CollateralType) == "" {
|
||||
return fmt.Errorf("reward period collateral type cannot be blank: %s", rp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rewards array of Reward
|
||||
type Rewards []Reward
|
||||
// RewardPeriods array of RewardPeriod
|
||||
type RewardPeriods []RewardPeriod
|
||||
|
||||
// Validate checks if all the rewards are valid and there are no duplicated
|
||||
// Validate checks if all the RewardPeriods are valid and there are no duplicated
|
||||
// entries.
|
||||
func (rs Rewards) Validate() error {
|
||||
rewardCollateralTypes := make(map[string]bool)
|
||||
for _, r := range rs {
|
||||
if rewardCollateralTypes[r.CollateralType] {
|
||||
return fmt.Errorf("cannot have duplicate reward collateral types: %s", r.CollateralType)
|
||||
func (rps RewardPeriods) Validate() error {
|
||||
seenPeriods := make(map[string]bool)
|
||||
for _, rp := range rps {
|
||||
if seenPeriods[rp.CollateralType] {
|
||||
return fmt.Errorf("duplicated reward period with collateral type %s", rp.CollateralType)
|
||||
}
|
||||
|
||||
if err := r.Validate(); err != nil {
|
||||
if err := rp.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rewardCollateralTypes[r.CollateralType] = true
|
||||
seenPeriods[rp.CollateralType] = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (rs Rewards) String() string {
|
||||
out := "Rewards\n"
|
||||
for _, r := range rs {
|
||||
out += fmt.Sprintf("%s\n", r)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Multiplier amount the claim rewards get increased by, along with how long the claim rewards are locked
|
||||
type Multiplier struct {
|
||||
Name MultiplierName `json:"name" yaml:"name"`
|
||||
@ -209,6 +224,9 @@ func (m Multiplier) Validate() error {
|
||||
if m.MonthsLockup < 0 {
|
||||
return fmt.Errorf("expected non-negative lockup, got %d", m.MonthsLockup)
|
||||
}
|
||||
if m.Factor == (sdk.Dec{}) {
|
||||
return fmt.Errorf("claim multiplier factor not initialized for %s", m.Name)
|
||||
}
|
||||
if m.Factor.IsNegative() {
|
||||
return fmt.Errorf("expected non-negative factor, got %s", m.Factor.String())
|
||||
}
|
||||
|
@ -5,212 +5,104 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/kava-labs/kava/x/incentive/types"
|
||||
)
|
||||
|
||||
type paramTest struct {
|
||||
name string
|
||||
params types.Params
|
||||
errResult errResult
|
||||
type ParamTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
type errResult struct {
|
||||
func (suite *ParamTestSuite) SetupTest() {}
|
||||
|
||||
func (suite *ParamTestSuite) TestParamValidation() {
|
||||
type args struct {
|
||||
rewardPeriods types.RewardPeriods
|
||||
multipliers types.Multipliers
|
||||
end time.Time
|
||||
}
|
||||
|
||||
type errArgs struct {
|
||||
expectPass bool
|
||||
contains string
|
||||
}
|
||||
|
||||
type ParamTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
tests []paramTest
|
||||
type test struct {
|
||||
name string
|
||||
args args
|
||||
errArgs errArgs
|
||||
}
|
||||
|
||||
func (suite *ParamTestSuite) SetupTest() {
|
||||
suite.tests = []paramTest{
|
||||
testCases := []test{
|
||||
{
|
||||
name: "valid - active",
|
||||
params: types.Params{
|
||||
Active: true,
|
||||
Rewards: types.Rewards{
|
||||
types.Reward{
|
||||
Active: true,
|
||||
CollateralType: "bnb-a",
|
||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||
Duration: time.Hour * 24 * 7,
|
||||
ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
|
||||
ClaimDuration: time.Hour * 24 * 14,
|
||||
"default",
|
||||
args{
|
||||
rewardPeriods: types.DefaultRewardPeriods,
|
||||
multipliers: types.DefaultMultipliers,
|
||||
end: types.DefaultClaimEnd,
|
||||
},
|
||||
},
|
||||
},
|
||||
errResult: errResult{
|
||||
errArgs{
|
||||
expectPass: true,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid - inactive",
|
||||
params: types.Params{
|
||||
Active: false,
|
||||
Rewards: types.Rewards{
|
||||
types.Reward{
|
||||
Active: true,
|
||||
CollateralType: "bnb-a",
|
||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||
Duration: time.Hour * 24 * 7,
|
||||
ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
|
||||
ClaimDuration: time.Hour * 24 * 14,
|
||||
"valid",
|
||||
args{
|
||||
rewardPeriods: types.RewardPeriods{types.NewRewardPeriod(
|
||||
true, "bnb-a", time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||
sdk.NewCoin(types.USDXMintingRewardDenom, sdk.NewInt(122354)))},
|
||||
multipliers: types.Multipliers{
|
||||
types.NewMultiplier(
|
||||
types.Small, 1, sdk.MustNewDecFromStr("0.25"),
|
||||
),
|
||||
types.NewMultiplier(
|
||||
types.Large, 1, sdk.MustNewDecFromStr("1.0"),
|
||||
),
|
||||
},
|
||||
end: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
errResult: errResult{
|
||||
errArgs{
|
||||
expectPass: true,
|
||||
contains: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "duplicate reward",
|
||||
params: types.Params{
|
||||
Active: true,
|
||||
Rewards: types.Rewards{
|
||||
types.Reward{
|
||||
Active: true,
|
||||
CollateralType: "bnb-a",
|
||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||
Duration: time.Hour * 24 * 7,
|
||||
ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
|
||||
ClaimDuration: time.Hour * 24 * 14,
|
||||
"invalid: empty reward factor",
|
||||
args{
|
||||
rewardPeriods: types.RewardPeriods{types.NewRewardPeriod(
|
||||
true, "bnb-a", time.Date(2020, 10, 15, 14, 0, 0, 0, time.UTC), time.Date(2024, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||
sdk.NewCoin(types.USDXMintingRewardDenom, sdk.NewInt(122354)))},
|
||||
multipliers: types.Multipliers{
|
||||
types.NewMultiplier(
|
||||
types.Small, 1, sdk.MustNewDecFromStr("0.25"),
|
||||
),
|
||||
types.NewMultiplier(
|
||||
types.Large, 1, sdk.Dec{},
|
||||
),
|
||||
},
|
||||
types.Reward{
|
||||
Active: true,
|
||||
CollateralType: "bnb-a",
|
||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||
Duration: time.Hour * 24 * 7,
|
||||
ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
|
||||
ClaimDuration: time.Hour * 24 * 14,
|
||||
end: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
},
|
||||
errResult: errResult{
|
||||
errArgs{
|
||||
expectPass: false,
|
||||
contains: "cannot have duplicate reward collateral type",
|
||||
contains: "claim multiplier factor not initialized",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative reward duration",
|
||||
params: types.Params{
|
||||
Active: true,
|
||||
Rewards: types.Rewards{
|
||||
types.Reward{
|
||||
Active: true,
|
||||
CollateralType: "bnb-a",
|
||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||
Duration: time.Hour * -24 * 7,
|
||||
ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
|
||||
ClaimDuration: time.Hour * 24 * 14,
|
||||
},
|
||||
},
|
||||
},
|
||||
errResult: errResult{
|
||||
expectPass: false,
|
||||
contains: "reward duration must be positive",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative time lock",
|
||||
params: types.Params{
|
||||
Active: true,
|
||||
Rewards: types.Rewards{
|
||||
types.Reward{
|
||||
Active: true,
|
||||
CollateralType: "bnb-a",
|
||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||
Duration: time.Hour * 24 * 7,
|
||||
ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, -1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
|
||||
ClaimDuration: time.Hour * 24 * 14,
|
||||
},
|
||||
},
|
||||
},
|
||||
errResult: errResult{
|
||||
expectPass: false,
|
||||
contains: "expected non-negative lockup",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "zero claim duration",
|
||||
params: types.Params{
|
||||
Active: true,
|
||||
Rewards: types.Rewards{
|
||||
types.Reward{
|
||||
Active: true,
|
||||
CollateralType: "bnb-a",
|
||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
||||
Duration: time.Hour * 24 * 7,
|
||||
ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
|
||||
ClaimDuration: time.Hour * 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
errResult: errResult{
|
||||
expectPass: false,
|
||||
contains: "claim duration must be positive",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "zero reward",
|
||||
params: types.Params{
|
||||
Active: true,
|
||||
Rewards: types.Rewards{
|
||||
types.Reward{
|
||||
Active: true,
|
||||
CollateralType: "bnb-a",
|
||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)),
|
||||
Duration: time.Hour * 24 * 7,
|
||||
ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
|
||||
ClaimDuration: time.Hour * 24 * 14,
|
||||
},
|
||||
},
|
||||
},
|
||||
errResult: errResult{
|
||||
expectPass: false,
|
||||
contains: "reward amount must be positive",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty reward collateral type",
|
||||
params: types.Params{
|
||||
Active: true,
|
||||
Rewards: types.Rewards{
|
||||
types.Reward{
|
||||
Active: true,
|
||||
CollateralType: "",
|
||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(1)),
|
||||
Duration: time.Hour * 24 * 7,
|
||||
ClaimMultipliers: types.Multipliers{types.NewMultiplier(types.Small, 1, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Large, 12, sdk.MustNewDecFromStr("1.0"))},
|
||||
ClaimDuration: time.Hour * 24 * 14,
|
||||
},
|
||||
},
|
||||
},
|
||||
errResult: errResult{
|
||||
expectPass: false,
|
||||
contains: "collateral type cannot be blank",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ParamTestSuite) TestParamValidation() {
|
||||
for _, t := range suite.tests {
|
||||
suite.Run(t.name, func() {
|
||||
err := t.params.Validate()
|
||||
if t.errResult.expectPass {
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
params := types.NewParams(
|
||||
tc.args.rewardPeriods, tc.args.multipliers, tc.args.end,
|
||||
)
|
||||
err := params.Validate()
|
||||
if tc.errArgs.expectPass {
|
||||
suite.Require().NoError(err)
|
||||
} else {
|
||||
suite.Require().Error(err)
|
||||
suite.Require().True(strings.Contains(err.Error(), t.errResult.contains))
|
||||
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -17,15 +17,17 @@ const (
|
||||
|
||||
// QueryClaimsParams params for query /incentive/claims
|
||||
type QueryClaimsParams struct {
|
||||
Page int `json:"page" yaml:"page"`
|
||||
Limit int `json:"limit" yaml:"limit"`
|
||||
Owner sdk.AccAddress
|
||||
CollateralType string
|
||||
}
|
||||
|
||||
// NewQueryClaimsParams returns QueryClaimsParams
|
||||
func NewQueryClaimsParams(owner sdk.AccAddress, collateralType string) QueryClaimsParams {
|
||||
func NewQueryClaimsParams(page, limit int, owner sdk.AccAddress) QueryClaimsParams {
|
||||
return QueryClaimsParams{
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
Owner: owner,
|
||||
CollateralType: collateralType,
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +35,5 @@ func NewQueryClaimsParams(owner sdk.AccAddress, collateralType string) QueryClai
|
||||
type PostClaimReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user