mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00: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,
|
auction.ModuleName: nil,
|
||||||
cdp.ModuleName: {supply.Minter, supply.Burner},
|
cdp.ModuleName: {supply.Minter, supply.Burner},
|
||||||
cdp.LiquidatorMacc: {supply.Minter, supply.Burner},
|
cdp.LiquidatorMacc: {supply.Minter, supply.Burner},
|
||||||
cdp.SavingsRateMacc: {supply.Minter},
|
|
||||||
bep3.ModuleName: {supply.Minter, supply.Burner},
|
bep3.ModuleName: {supply.Minter, supply.Burner},
|
||||||
kavadist.ModuleName: {supply.Minter},
|
kavadist.ModuleName: {supply.Minter},
|
||||||
issuance.ModuleAccountName: {supply.Minter, supply.Burner},
|
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,
|
app.supplyKeeper,
|
||||||
auctionSubspace,
|
auctionSubspace,
|
||||||
)
|
)
|
||||||
app.cdpKeeper = cdp.NewKeeper(
|
cdpKeeper := cdp.NewKeeper(
|
||||||
app.cdc,
|
app.cdc,
|
||||||
keys[cdp.StoreKey],
|
keys[cdp.StoreKey],
|
||||||
cdpSubspace,
|
cdpSubspace,
|
||||||
@ -370,7 +369,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
|
|||||||
keys[incentive.StoreKey],
|
keys[incentive.StoreKey],
|
||||||
incentiveSubspace,
|
incentiveSubspace,
|
||||||
app.supplyKeeper,
|
app.supplyKeeper,
|
||||||
app.cdpKeeper,
|
&cdpKeeper,
|
||||||
app.accountKeeper,
|
app.accountKeeper,
|
||||||
)
|
)
|
||||||
app.issuanceKeeper = issuance.NewKeeper(
|
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(
|
app.stakingKeeper = *stakingKeeper.SetHooks(
|
||||||
staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks()))
|
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
|
// create the module manager (Note: Any module instantiated in the module manager that is later modified
|
||||||
// must be passed by reference here.)
|
// must be passed by reference here.)
|
||||||
app.mm = module.NewManager(
|
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"
|
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_11bep3 "github.com/kava-labs/kava/x/bep3"
|
||||||
v0_9bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_9"
|
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_9cdp "github.com/kava-labs/kava/x/cdp/legacy/v0_9"
|
||||||
v0_11committee "github.com/kava-labs/kava/x/committee"
|
v0_11committee "github.com/kava-labs/kava/x/committee"
|
||||||
v0_9committee "github.com/kava-labs/kava/x/committee/legacy/v0_9"
|
v0_9committee "github.com/kava-labs/kava/x/committee/legacy/v0_9"
|
||||||
v0_11harvest "github.com/kava-labs/kava/x/hard"
|
v0_11harvest "github.com/kava-labs/kava/x/hard/legacy/v0_11"
|
||||||
v0_11incentive "github.com/kava-labs/kava/x/incentive"
|
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_9incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_9"
|
||||||
v0_11issuance "github.com/kava-labs/kava/x/issuance"
|
v0_11issuance "github.com/kava-labs/kava/x/issuance"
|
||||||
v0_11pricefeed "github.com/kava-labs/kava/x/pricefeed"
|
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)
|
delete(v0_9AppState, v0_9pricefeed.ModuleName)
|
||||||
v0_11AppState[v0_9pricefeed.ModuleName] = v0_11Codec.MustMarshalJSON(MigratePricefeed(pricefeedGenState))
|
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())
|
v0_11AppState[v0_11issuance.ModuleName] = v0_11Codec.MustMarshalJSON(v0_11issuance.DefaultGenesisState())
|
||||||
return v0_11AppState
|
return v0_11AppState
|
||||||
}
|
}
|
||||||
@ -308,7 +308,6 @@ func MigrateCommittee(oldGenState v0_9committee.GenesisState) v0_11committee.Gen
|
|||||||
DebtFloor: oldDebtParam.DebtFloor,
|
DebtFloor: oldDebtParam.DebtFloor,
|
||||||
Denom: oldDebtParam.Denom,
|
Denom: oldDebtParam.Denom,
|
||||||
ReferenceAsset: oldDebtParam.ReferenceAsset,
|
ReferenceAsset: oldDebtParam.ReferenceAsset,
|
||||||
SavingsRate: oldDebtParam.SavingsRate,
|
|
||||||
}
|
}
|
||||||
oldAssetParam := subPermission.AllowedAssetParams[0]
|
oldAssetParam := subPermission.AllowedAssetParams[0]
|
||||||
newAssetParam := v0_11committee.AllowedAssetParam{
|
newAssetParam := v0_11committee.AllowedAssetParam{
|
||||||
@ -633,34 +632,29 @@ func MigrateGov(oldGenState v39_1gov.GenesisState) v39_1gov.GenesisState {
|
|||||||
return oldGenState
|
return oldGenState
|
||||||
}
|
}
|
||||||
|
|
||||||
// // MigrateHarvest initializes the harvest genesis state for kava-4
|
// MigrateHarvest initializes the harvest genesis state for kava-4
|
||||||
// func MigrateHarvest() v0_11harvest.GenesisState {
|
func MigrateHarvest() v0_11harvest.GenesisState {
|
||||||
// // total HARD per second for lps (week one): 633761
|
// total HARD per second for lps (week one): 633761
|
||||||
// // HARD per second for delegators (week one): 1267522
|
// HARD per second for delegators (week one): 1267522
|
||||||
// incentiveGoLiveDate := time.Date(2020, 10, 16, 14, 0, 0, 0, time.UTC)
|
incentiveGoLiveDate := time.Date(2020, 10, 16, 14, 0, 0, 0, time.UTC)
|
||||||
// incentiveEndDate := time.Date(2024, 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)
|
claimEndDate := time.Date(2025, 10, 16, 14, 0, 0, 0, time.UTC)
|
||||||
// harvestGS := v0_11harvest.NewGenesisState(v0_11harvest.NewParams(
|
harvestGS := v0_11harvest.NewGenesisState(v0_11harvest.NewParams(
|
||||||
// true,
|
true,
|
||||||
// v0_11harvest.DistributionSchedules{
|
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, "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, "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, "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.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.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())}),
|
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,
|
time.Hour*24,
|
||||||
// ),
|
),
|
||||||
// },
|
},
|
||||||
// v0_11harvest.BlockLimits{
|
), v0_11harvest.DefaultPreviousBlockTime, v0_11harvest.DefaultDistributionTimes)
|
||||||
// v0_11harvest.NewBlockLimit("usdx", sdk.Dec(0.9)),
|
return harvestGS
|
||||||
// v0_11harvest.NewBlockLimit("ukava", sdk.Dec(0.6)),
|
}
|
||||||
// v0_11harvest.NewBlockLimit("bnb", sdk.Dec(0.9)),
|
|
||||||
// },
|
|
||||||
// ), 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
|
// 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 {
|
func MigrateCDP(oldGenState v0_9cdp.GenesisState) v0_11cdp.GenesisState {
|
||||||
|
@ -12,11 +12,8 @@ import (
|
|||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
v39_1auth "github.com/cosmos/cosmos-sdk/x/auth"
|
v39_1auth "github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
v39_1auth_vesting "github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
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"
|
v39_1supply "github.com/cosmos/cosmos-sdk/x/supply"
|
||||||
|
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
|
||||||
|
|
||||||
"github.com/kava-labs/kava/app"
|
"github.com/kava-labs/kava/app"
|
||||||
v38_5auth "github.com/kava-labs/kava/migrate/v0_11/legacy/cosmos-sdk/v0.38.5/auth"
|
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"
|
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()
|
err = newGenState.Validate()
|
||||||
require.NoError(t, err)
|
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
|
End Time: %s
|
||||||
Max End Time: %s
|
Max End Time: %s
|
||||||
Max Bid %s
|
Max Bid %s
|
||||||
LotReturns %s`,
|
LotReturns %s
|
||||||
|
Corresponding Debt %s`,
|
||||||
a.GetID(), a.Initiator, a.Lot,
|
a.GetID(), a.Initiator, a.Lot,
|
||||||
a.Bidder, a.Bid, a.GetEndTime().String(),
|
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) {
|
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
|
||||||
params := k.GetParams(ctx)
|
params := k.GetParams(ctx)
|
||||||
|
|
||||||
previousDistTime, found := k.GetPreviousSavingsDistribution(ctx)
|
|
||||||
if !found {
|
|
||||||
previousDistTime = ctx.BlockTime()
|
|
||||||
k.SetPreviousSavingsDistribution(ctx, previousDistTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, cp := range params.CollateralParams {
|
for _, cp := range params.CollateralParams {
|
||||||
ok := k.UpdatePricefeedStatus(ctx, cp.SpotMarketID)
|
ok := k.UpdatePricefeedStatus(ctx, cp.SpotMarketID)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -31,7 +25,12 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
|
|||||||
continue
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -46,16 +45,4 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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)
|
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)
|
cdp, _ := suite.keeper.GetCDP(suite.ctx, "xrp-a", 1)
|
||||||
|
|
||||||
err = suite.keeper.SeizeCollateral(suite.ctx, cdp)
|
err = suite.keeper.SeizeCollateral(suite.ctx, cdp)
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BaseDigitFactor = keeper.BaseDigitFactor
|
|
||||||
AttributeKeyCdpID = types.AttributeKeyCdpID
|
AttributeKeyCdpID = types.AttributeKeyCdpID
|
||||||
AttributeKeyDeposit = types.AttributeKeyDeposit
|
AttributeKeyDeposit = types.AttributeKeyDeposit
|
||||||
AttributeKeyError = types.AttributeKeyError
|
AttributeKeyError = types.AttributeKeyError
|
||||||
@ -38,12 +37,12 @@ const (
|
|||||||
RestOwner = types.RestOwner
|
RestOwner = types.RestOwner
|
||||||
RestRatio = types.RestRatio
|
RestRatio = types.RestRatio
|
||||||
RouterKey = types.RouterKey
|
RouterKey = types.RouterKey
|
||||||
SavingsRateMacc = types.SavingsRateMacc
|
|
||||||
StoreKey = types.StoreKey
|
StoreKey = types.StoreKey
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// function aliases
|
// function aliases
|
||||||
|
CalculateInterestFactor = keeper.CalculateInterestFactor
|
||||||
FilterCDPs = keeper.FilterCDPs
|
FilterCDPs = keeper.FilterCDPs
|
||||||
FindIntersection = keeper.FindIntersection
|
FindIntersection = keeper.FindIntersection
|
||||||
NewKeeper = keeper.NewKeeper
|
NewKeeper = keeper.NewKeeper
|
||||||
@ -65,12 +64,16 @@ var (
|
|||||||
NewCollateralParam = types.NewCollateralParam
|
NewCollateralParam = types.NewCollateralParam
|
||||||
NewDebtParam = types.NewDebtParam
|
NewDebtParam = types.NewDebtParam
|
||||||
NewDeposit = types.NewDeposit
|
NewDeposit = types.NewDeposit
|
||||||
|
NewGenesisAccumulationTime = types.NewGenesisAccumulationTime
|
||||||
NewGenesisState = types.NewGenesisState
|
NewGenesisState = types.NewGenesisState
|
||||||
|
NewGenesisTotalPrincipal = types.NewGenesisTotalPrincipal
|
||||||
NewMsgCreateCDP = types.NewMsgCreateCDP
|
NewMsgCreateCDP = types.NewMsgCreateCDP
|
||||||
NewMsgDeposit = types.NewMsgDeposit
|
NewMsgDeposit = types.NewMsgDeposit
|
||||||
NewMsgDrawDebt = types.NewMsgDrawDebt
|
NewMsgDrawDebt = types.NewMsgDrawDebt
|
||||||
|
NewMsgLiquidate = types.NewMsgLiquidate
|
||||||
NewMsgRepayDebt = types.NewMsgRepayDebt
|
NewMsgRepayDebt = types.NewMsgRepayDebt
|
||||||
NewMsgWithdraw = types.NewMsgWithdraw
|
NewMsgWithdraw = types.NewMsgWithdraw
|
||||||
|
NewMultiCDPHooks = types.NewMultiCDPHooks
|
||||||
NewParams = types.NewParams
|
NewParams = types.NewParams
|
||||||
NewQueryCdpDeposits = types.NewQueryCdpDeposits
|
NewQueryCdpDeposits = types.NewQueryCdpDeposits
|
||||||
NewQueryCdpParams = types.NewQueryCdpParams
|
NewQueryCdpParams = types.NewQueryCdpParams
|
||||||
@ -105,13 +108,12 @@ var (
|
|||||||
DefaultDebtThreshold = types.DefaultDebtThreshold
|
DefaultDebtThreshold = types.DefaultDebtThreshold
|
||||||
DefaultGlobalDebt = types.DefaultGlobalDebt
|
DefaultGlobalDebt = types.DefaultGlobalDebt
|
||||||
DefaultGovDenom = types.DefaultGovDenom
|
DefaultGovDenom = types.DefaultGovDenom
|
||||||
DefaultPreviousDistributionTime = types.DefaultPreviousDistributionTime
|
|
||||||
DefaultSavingsDistributionFrequency = types.DefaultSavingsDistributionFrequency
|
|
||||||
DefaultSavingsRateDistributed = types.DefaultSavingsRateDistributed
|
DefaultSavingsRateDistributed = types.DefaultSavingsRateDistributed
|
||||||
DefaultStableDenom = types.DefaultStableDenom
|
DefaultStableDenom = types.DefaultStableDenom
|
||||||
DefaultSurplusLot = types.DefaultSurplusLot
|
DefaultSurplusLot = types.DefaultSurplusLot
|
||||||
DefaultSurplusThreshold = types.DefaultSurplusThreshold
|
DefaultSurplusThreshold = types.DefaultSurplusThreshold
|
||||||
DepositKeyPrefix = types.DepositKeyPrefix
|
DepositKeyPrefix = types.DepositKeyPrefix
|
||||||
|
ErrAccountNotFound = types.ErrAccountNotFound
|
||||||
ErrBelowDebtFloor = types.ErrBelowDebtFloor
|
ErrBelowDebtFloor = types.ErrBelowDebtFloor
|
||||||
ErrCdpAlreadyExists = types.ErrCdpAlreadyExists
|
ErrCdpAlreadyExists = types.ErrCdpAlreadyExists
|
||||||
ErrCdpNotAvailable = types.ErrCdpNotAvailable
|
ErrCdpNotAvailable = types.ErrCdpNotAvailable
|
||||||
@ -122,6 +124,7 @@ var (
|
|||||||
ErrDepositNotAvailable = types.ErrDepositNotAvailable
|
ErrDepositNotAvailable = types.ErrDepositNotAvailable
|
||||||
ErrDepositNotFound = types.ErrDepositNotFound
|
ErrDepositNotFound = types.ErrDepositNotFound
|
||||||
ErrExceedsDebtLimit = types.ErrExceedsDebtLimit
|
ErrExceedsDebtLimit = types.ErrExceedsDebtLimit
|
||||||
|
ErrInsufficientBalance = types.ErrInsufficientBalance
|
||||||
ErrInvalidCollateral = types.ErrInvalidCollateral
|
ErrInvalidCollateral = types.ErrInvalidCollateral
|
||||||
ErrInvalidCollateralLength = types.ErrInvalidCollateralLength
|
ErrInvalidCollateralLength = types.ErrInvalidCollateralLength
|
||||||
ErrInvalidCollateralRatio = types.ErrInvalidCollateralRatio
|
ErrInvalidCollateralRatio = types.ErrInvalidCollateralRatio
|
||||||
@ -130,24 +133,23 @@ var (
|
|||||||
ErrInvalidPayment = types.ErrInvalidPayment
|
ErrInvalidPayment = types.ErrInvalidPayment
|
||||||
ErrInvalidWithdrawAmount = types.ErrInvalidWithdrawAmount
|
ErrInvalidWithdrawAmount = types.ErrInvalidWithdrawAmount
|
||||||
ErrLoadingAugmentedCDP = types.ErrLoadingAugmentedCDP
|
ErrLoadingAugmentedCDP = types.ErrLoadingAugmentedCDP
|
||||||
|
ErrNotLiquidatable = types.ErrNotLiquidatable
|
||||||
ErrPricefeedDown = types.ErrPricefeedDown
|
ErrPricefeedDown = types.ErrPricefeedDown
|
||||||
GovDenomKey = types.GovDenomKey
|
GovDenomKey = types.GovDenomKey
|
||||||
|
InterestFactorPrefix = types.InterestFactorPrefix
|
||||||
KeyCircuitBreaker = types.KeyCircuitBreaker
|
KeyCircuitBreaker = types.KeyCircuitBreaker
|
||||||
KeyCollateralParams = types.KeyCollateralParams
|
KeyCollateralParams = types.KeyCollateralParams
|
||||||
KeyDebtLot = types.KeyDebtLot
|
KeyDebtLot = types.KeyDebtLot
|
||||||
KeyDebtParam = types.KeyDebtParam
|
KeyDebtParam = types.KeyDebtParam
|
||||||
KeyDebtThreshold = types.KeyDebtThreshold
|
KeyDebtThreshold = types.KeyDebtThreshold
|
||||||
KeyDistributionFrequency = types.KeyDistributionFrequency
|
|
||||||
KeyGlobalDebtLimit = types.KeyGlobalDebtLimit
|
KeyGlobalDebtLimit = types.KeyGlobalDebtLimit
|
||||||
KeySavingsRateDistributed = types.KeySavingsRateDistributed
|
|
||||||
KeySurplusLot = types.KeySurplusLot
|
KeySurplusLot = types.KeySurplusLot
|
||||||
KeySurplusThreshold = types.KeySurplusThreshold
|
KeySurplusThreshold = types.KeySurplusThreshold
|
||||||
MaxSortableDec = types.MaxSortableDec
|
MaxSortableDec = types.MaxSortableDec
|
||||||
ModuleCdc = types.ModuleCdc
|
ModuleCdc = types.ModuleCdc
|
||||||
PreviousDistributionTimeKey = types.PreviousDistributionTimeKey
|
PreviousAccrualTimePrefix = types.PreviousAccrualTimePrefix
|
||||||
PricefeedStatusKeyPrefix = types.PricefeedStatusKeyPrefix
|
PricefeedStatusKeyPrefix = types.PricefeedStatusKeyPrefix
|
||||||
PrincipalKeyPrefix = types.PrincipalKeyPrefix
|
PrincipalKeyPrefix = types.PrincipalKeyPrefix
|
||||||
SavingsRateDistributedKey = types.SavingsRateDistributedKey
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -157,6 +159,7 @@ type (
|
|||||||
AugmentedCDP = types.AugmentedCDP
|
AugmentedCDP = types.AugmentedCDP
|
||||||
AugmentedCDPs = types.AugmentedCDPs
|
AugmentedCDPs = types.AugmentedCDPs
|
||||||
CDP = types.CDP
|
CDP = types.CDP
|
||||||
|
CDPHooks = types.CDPHooks
|
||||||
CDPs = types.CDPs
|
CDPs = types.CDPs
|
||||||
CollateralParam = types.CollateralParam
|
CollateralParam = types.CollateralParam
|
||||||
CollateralParams = types.CollateralParams
|
CollateralParams = types.CollateralParams
|
||||||
@ -164,12 +167,18 @@ type (
|
|||||||
DebtParams = types.DebtParams
|
DebtParams = types.DebtParams
|
||||||
Deposit = types.Deposit
|
Deposit = types.Deposit
|
||||||
Deposits = types.Deposits
|
Deposits = types.Deposits
|
||||||
|
GenesisAccumulationTime = types.GenesisAccumulationTime
|
||||||
|
GenesisAccumulationTimes = types.GenesisAccumulationTimes
|
||||||
GenesisState = types.GenesisState
|
GenesisState = types.GenesisState
|
||||||
|
GenesisTotalPrincipal = types.GenesisTotalPrincipal
|
||||||
|
GenesisTotalPrincipals = types.GenesisTotalPrincipals
|
||||||
MsgCreateCDP = types.MsgCreateCDP
|
MsgCreateCDP = types.MsgCreateCDP
|
||||||
MsgDeposit = types.MsgDeposit
|
MsgDeposit = types.MsgDeposit
|
||||||
MsgDrawDebt = types.MsgDrawDebt
|
MsgDrawDebt = types.MsgDrawDebt
|
||||||
|
MsgLiquidate = types.MsgLiquidate
|
||||||
MsgRepayDebt = types.MsgRepayDebt
|
MsgRepayDebt = types.MsgRepayDebt
|
||||||
MsgWithdraw = types.MsgWithdraw
|
MsgWithdraw = types.MsgWithdraw
|
||||||
|
MultiCDPHooks = types.MultiCDPHooks
|
||||||
Params = types.Params
|
Params = types.Params
|
||||||
PricefeedKeeper = types.PricefeedKeeper
|
PricefeedKeeper = types.PricefeedKeeper
|
||||||
QueryCdpDeposits = types.QueryCdpDeposits
|
QueryCdpDeposits = types.QueryCdpDeposits
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -41,8 +40,6 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|||||||
QueryCdpDepositsCmd(queryRoute, cdc),
|
QueryCdpDepositsCmd(queryRoute, cdc),
|
||||||
QueryParamsCmd(queryRoute, cdc),
|
QueryParamsCmd(queryRoute, cdc),
|
||||||
QueryGetAccounts(queryRoute, cdc),
|
QueryGetAccounts(queryRoute, cdc),
|
||||||
QueryGetSavingsRateDistributed(queryRoute, cdc),
|
|
||||||
QueryGetSavingsRateDistTime(queryRoute, cdc),
|
|
||||||
)...)
|
)...)
|
||||||
|
|
||||||
return cdpQueryCmd
|
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),
|
GetCmdWithdraw(cdc),
|
||||||
GetCmdDraw(cdc),
|
GetCmdDraw(cdc),
|
||||||
GetCmdRepay(cdc),
|
GetCmdRepay(cdc),
|
||||||
|
GetCmdLiquidate(cdc),
|
||||||
)...)
|
)...)
|
||||||
|
|
||||||
return cdpTxCmd
|
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) {
|
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||||
r.HandleFunc("/cdp/accounts", getAccountsHandlerFn(cliCtx)).Methods("GET")
|
r.HandleFunc("/cdp/accounts", getAccountsHandlerFn(cliCtx)).Methods("GET")
|
||||||
r.HandleFunc("/cdp/parameters", getParamsHandlerFn(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/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"), queryCdpsHandlerFn(cliCtx)).Methods("GET")
|
||||||
r.HandleFunc(fmt.Sprintf("/cdp/cdps/collateralType/{%s}", types.RestCollateralType), queryCdpsByCollateralTypeHandlerFn(cliCtx)).Methods("GET") // legacy
|
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 {
|
func queryCdpsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
|
||||||
|
@ -65,3 +65,10 @@ type PostRepayReq struct {
|
|||||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
||||||
Payment sdk.Coin `json:"payment" yaml:"payment"`
|
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}/withdraw", postWithdrawHandlerFn(cliCtx)).Methods("POST")
|
||||||
r.HandleFunc("/cdp/{owner}/{collateralType}/draw", postDrawHandlerFn(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}/repay", postRepayHandlerFn(cliCtx)).Methods("POST")
|
||||||
|
r.HandleFunc("/cdp/{owner}/collateralType}/liquidate", postLiquidateHandlerFn(cliCtx)).Methods("POST")
|
||||||
}
|
}
|
||||||
|
|
||||||
func postCdpHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
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})
|
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 {
|
if liqModuleAcc == nil {
|
||||||
panic(fmt.Sprintf("%s module account has not been set", LiquidatorMacc))
|
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,
|
// validate denoms - check that any collaterals in the params are in the pricefeed,
|
||||||
// pricefeed MUST call InitGenesis before cdp
|
// 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)
|
k.SetParams(ctx, gs.Params)
|
||||||
|
|
||||||
// set the per second fee rate for each collateral type
|
for _, gat := range gs.PreviousAccumulationTimes {
|
||||||
for _, cp := range gs.Params.CollateralParams {
|
k.SetInterestFactor(ctx, gat.CollateralType, gat.InterestFactor)
|
||||||
k.SetTotalPrincipal(ctx, cp.Type, gs.Params.DebtParam.Denom, sdk.ZeroInt())
|
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
|
// add cdps
|
||||||
for _, cdp := range gs.CDPs {
|
for _, cdp := range gs.CDPs {
|
||||||
if cdp.ID == gs.StartingCdpID {
|
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)
|
k.IndexCdpByOwner(ctx, cdp)
|
||||||
ratio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
ratio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
||||||
k.IndexCdpByCollateralRatio(ctx, cdp.Type, cdp.ID, ratio)
|
k.IndexCdpByCollateralRatio(ctx, cdp.Type, cdp.ID, ratio)
|
||||||
k.IncrementTotalPrincipal(ctx, cdp.Type, cdp.GetTotalPrincipal())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
k.SetNextCdpID(ctx, gs.StartingCdpID)
|
k.SetNextCdpID(ctx, gs.StartingCdpID)
|
||||||
k.SetDebtDenom(ctx, gs.DebtDenom)
|
k.SetDebtDenom(ctx, gs.DebtDenom)
|
||||||
k.SetGovDenom(ctx, gs.GovDenom)
|
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 {
|
for _, d := range gs.Deposits {
|
||||||
k.SetDeposit(ctx, d)
|
k.SetDeposit(ctx, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
k.SetSavingsRateDistributed(ctx, gs.SavingsRateDistributed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportGenesis export genesis state for cdp module
|
// ExportGenesis export genesis state for cdp module
|
||||||
@ -110,12 +105,22 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
|
|||||||
cdpID := k.GetNextCdpID(ctx)
|
cdpID := k.GetNextCdpID(ctx)
|
||||||
debtDenom := k.GetDebtDenom(ctx)
|
debtDenom := k.GetDebtDenom(ctx)
|
||||||
govDenom := k.GetGovDenom(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 {
|
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
|
startingID uint64
|
||||||
debtDenom string
|
debtDenom string
|
||||||
govDenom string
|
govDenom string
|
||||||
prevDistTime time.Time
|
|
||||||
savingsRateDist sdk.Int
|
savingsRateDist sdk.Int
|
||||||
|
genAccumTimes cdp.GenesisAccumulationTimes
|
||||||
|
genTotalPrincipals cdp.GenesisTotalPrincipals
|
||||||
}
|
}
|
||||||
type errArgs struct {
|
type errArgs struct {
|
||||||
expectPass bool
|
expectPass bool
|
||||||
@ -53,8 +54,9 @@ func (suite *GenesisTestSuite) TestInvalidGenState() {
|
|||||||
deposits: cdp.Deposits{},
|
deposits: cdp.Deposits{},
|
||||||
debtDenom: "",
|
debtDenom: "",
|
||||||
govDenom: cdp.DefaultGovDenom,
|
govDenom: cdp.DefaultGovDenom,
|
||||||
prevDistTime: cdp.DefaultPreviousDistributionTime,
|
|
||||||
savingsRateDist: cdp.DefaultSavingsRateDistributed,
|
savingsRateDist: cdp.DefaultSavingsRateDistributed,
|
||||||
|
genAccumTimes: cdp.DefaultGenesisState().PreviousAccumulationTimes,
|
||||||
|
genTotalPrincipals: cdp.DefaultGenesisState().TotalPrincipals,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
@ -69,8 +71,9 @@ func (suite *GenesisTestSuite) TestInvalidGenState() {
|
|||||||
deposits: cdp.Deposits{},
|
deposits: cdp.Deposits{},
|
||||||
debtDenom: cdp.DefaultDebtDenom,
|
debtDenom: cdp.DefaultDebtDenom,
|
||||||
govDenom: "",
|
govDenom: "",
|
||||||
prevDistTime: cdp.DefaultPreviousDistributionTime,
|
|
||||||
savingsRateDist: cdp.DefaultSavingsRateDistributed,
|
savingsRateDist: cdp.DefaultSavingsRateDistributed,
|
||||||
|
genAccumTimes: cdp.DefaultGenesisState().PreviousAccumulationTimes,
|
||||||
|
genTotalPrincipals: cdp.DefaultGenesisState().TotalPrincipals,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
@ -78,48 +81,49 @@ func (suite *GenesisTestSuite) TestInvalidGenState() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty distribution time",
|
name: "interest factor below one",
|
||||||
args: args{
|
args: args{
|
||||||
params: cdp.DefaultParams(),
|
params: cdp.DefaultParams(),
|
||||||
cdps: cdp.CDPs{},
|
cdps: cdp.CDPs{},
|
||||||
deposits: cdp.Deposits{},
|
deposits: cdp.Deposits{},
|
||||||
debtDenom: cdp.DefaultDebtDenom,
|
debtDenom: cdp.DefaultDebtDenom,
|
||||||
govDenom: cdp.DefaultGovDenom,
|
govDenom: cdp.DefaultGovDenom,
|
||||||
prevDistTime: time.Time{},
|
savingsRateDist: sdk.NewInt(100),
|
||||||
savingsRateDist: cdp.DefaultSavingsRateDistributed,
|
genAccumTimes: cdp.GenesisAccumulationTimes{cdp.NewGenesisAccumulationTime("bnb-a", time.Time{}, sdk.OneDec().Sub(sdk.SmallestDec()))},
|
||||||
|
genTotalPrincipals: cdp.DefaultGenesisState().TotalPrincipals,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
expectPass: false,
|
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{
|
args: args{
|
||||||
params: cdp.DefaultParams(),
|
params: cdp.DefaultParams(),
|
||||||
cdps: cdp.CDPs{},
|
cdps: cdp.CDPs{},
|
||||||
deposits: cdp.Deposits{},
|
deposits: cdp.Deposits{},
|
||||||
debtDenom: cdp.DefaultDebtDenom,
|
debtDenom: cdp.DefaultDebtDenom,
|
||||||
govDenom: cdp.DefaultGovDenom,
|
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{
|
errArgs: errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
contains: "savings rate distributed should not be negative",
|
contains: "total principal should be positive",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
suite.Run(tc.name, func() {
|
suite.Run(tc.name, func() {
|
||||||
gs := cdp.NewGenesisState(tc.args.params, tc.args.cdps, tc.args.deposits, tc.args.startingID,
|
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()
|
err := gs.Validate()
|
||||||
if tc.errArgs.expectPass {
|
if tc.errArgs.expectPass {
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
} else {
|
} else {
|
||||||
suite.Require().Error(err)
|
suite.Require().Error(err)
|
||||||
suite.T().Log(err)
|
|
||||||
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
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)
|
return handleMsgDrawDebt(ctx, k, msg)
|
||||||
case MsgRepayDebt:
|
case MsgRepayDebt:
|
||||||
return handleMsgRepayDebt(ctx, k, msg)
|
return handleMsgRepayDebt(ctx, k, msg)
|
||||||
|
case MsgLiquidate:
|
||||||
|
return handleMsgLiquidate(ctx, k, msg)
|
||||||
default:
|
default:
|
||||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
|
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
|
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/app"
|
||||||
"github.com/kava-labs/kava/x/cdp"
|
"github.com/kava-labs/kava/x/cdp"
|
||||||
|
"github.com/kava-labs/kava/x/cdp/types"
|
||||||
"github.com/kava-labs/kava/x/pricefeed"
|
"github.com/kava-labs/kava/x/pricefeed"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,7 +46,6 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
|
|||||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||||
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
|
|
||||||
CollateralParams: cdp.CollateralParams{
|
CollateralParams: cdp.CollateralParams{
|
||||||
{
|
{
|
||||||
Denom: asset,
|
Denom: asset,
|
||||||
@ -59,6 +59,8 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
|
|||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
SpotMarketID: asset + ":usd",
|
SpotMarketID: asset + ":usd",
|
||||||
LiquidationMarketID: asset + ":usd",
|
LiquidationMarketID: asset + ":usd",
|
||||||
|
KeeperRewardPercentage: d("0.01"),
|
||||||
|
CheckCollateralizationIndexCount: i(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DebtParam: cdp.DebtParam{
|
DebtParam: cdp.DebtParam{
|
||||||
@ -66,14 +68,18 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
DebtFloor: i(10000000),
|
DebtFloor: i(10000000),
|
||||||
SavingsRate: d("0.95"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||||
DebtDenom: cdp.DefaultDebtDenom,
|
DebtDenom: cdp.DefaultDebtDenom,
|
||||||
GovDenom: cdp.DefaultGovDenom,
|
GovDenom: cdp.DefaultGovDenom,
|
||||||
CDPs: cdp.CDPs{},
|
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)}
|
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
|
||||||
}
|
}
|
||||||
@ -111,7 +117,6 @@ func NewCDPGenStateMulti() app.GenesisState {
|
|||||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||||
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
|
|
||||||
CollateralParams: cdp.CollateralParams{
|
CollateralParams: cdp.CollateralParams{
|
||||||
{
|
{
|
||||||
Denom: "xrp",
|
Denom: "xrp",
|
||||||
@ -124,6 +129,8 @@ func NewCDPGenStateMulti() app.GenesisState {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "xrp:usd",
|
SpotMarketID: "xrp:usd",
|
||||||
LiquidationMarketID: "xrp:usd",
|
LiquidationMarketID: "xrp:usd",
|
||||||
|
KeeperRewardPercentage: d("0.01"),
|
||||||
|
CheckCollateralizationIndexCount: i(10),
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -137,6 +144,8 @@ func NewCDPGenStateMulti() app.GenesisState {
|
|||||||
Prefix: 0x21,
|
Prefix: 0x21,
|
||||||
SpotMarketID: "btc:usd",
|
SpotMarketID: "btc:usd",
|
||||||
LiquidationMarketID: "btc:usd",
|
LiquidationMarketID: "btc:usd",
|
||||||
|
KeeperRewardPercentage: d("0.01"),
|
||||||
|
CheckCollateralizationIndexCount: i(10),
|
||||||
ConversionFactor: i(8),
|
ConversionFactor: i(8),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -145,24 +154,30 @@ func NewCDPGenStateMulti() app.GenesisState {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
DebtFloor: i(10000000),
|
DebtFloor: i(10000000),
|
||||||
SavingsRate: d("0.95"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||||
DebtDenom: cdp.DefaultDebtDenom,
|
DebtDenom: cdp.DefaultDebtDenom,
|
||||||
GovDenom: cdp.DefaultGovDenom,
|
GovDenom: cdp.DefaultGovDenom,
|
||||||
CDPs: cdp.CDPs{},
|
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)}
|
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cdps() (cdps cdp.CDPs) {
|
func cdps() (cdps cdp.CDPs) {
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(3)
|
_, 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()))
|
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()))
|
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()))
|
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()))
|
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)
|
cdps = append(cdps, c1, c2, c3, c4)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,6 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/cdp/types"
|
"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
|
// 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 {
|
func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coin, principal sdk.Coin, collateralType string) error {
|
||||||
// validation
|
// validation
|
||||||
@ -21,6 +18,10 @@ func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = k.ValidateBalance(ctx, collateral, owner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
_, found := k.GetCdpByOwnerAndCollateralType(ctx, owner, collateralType)
|
_, found := k.GetCdpByOwnerAndCollateralType(ctx, owner, collateralType)
|
||||||
if found {
|
if found {
|
||||||
return sdkerrors.Wrapf(types.ErrCdpAlreadyExists, "owner %s, denom %s", owner, collateral.Denom)
|
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
|
// send coins from the owners account to the cdp module
|
||||||
id := k.GetNextCdpID(ctx)
|
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)
|
deposit := types.NewDeposit(cdp.ID, owner, collateral)
|
||||||
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, sdk.NewCoins(collateral))
|
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, sdk.NewCoins(collateral))
|
||||||
if err != nil {
|
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.SetDeposit(ctx, deposit)
|
||||||
k.SetNextCdpID(ctx, id+1)
|
k.SetNextCdpID(ctx, id+1)
|
||||||
|
|
||||||
|
k.hooks.AfterCDPCreated(ctx, cdp)
|
||||||
|
|
||||||
// emit events for cdp creation, deposit, and draw
|
// emit events for cdp creation, deposit, and draw
|
||||||
ctx.EventManager().EmitEvent(
|
ctx.EventManager().EmitEvent(
|
||||||
sdk.NewEvent(
|
sdk.NewEvent(
|
||||||
@ -102,6 +111,31 @@ func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coi
|
|||||||
return nil
|
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
|
// 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 {
|
func (k Keeper) SetCdpAndCollateralRatioIndex(ctx sdk.Context, cdp types.CDP, ratio sdk.Dec) error {
|
||||||
err := k.SetCDP(ctx, cdp)
|
err := k.SetCDP(ctx, cdp)
|
||||||
@ -112,6 +146,16 @@ func (k Keeper) SetCdpAndCollateralRatioIndex(ctx sdk.Context, cdp types.CDP, ra
|
|||||||
return nil
|
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
|
// MintDebtCoins mints debt coins in the cdp module account
|
||||||
func (k Keeper) MintDebtCoins(ctx sdk.Context, moduleAccount string, denom string, principalCoins sdk.Coin) error {
|
func (k Keeper) MintDebtCoins(ctx sdk.Context, moduleAccount string, denom string, principalCoins sdk.Coin) error {
|
||||||
debtCoins := sdk.NewCoins(sdk.NewCoin(denom, principalCoins.Amount))
|
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
|
// BurnDebtCoins burns debt coins from the cdp module account
|
||||||
func (k Keeper) BurnDebtCoins(ctx sdk.Context, moduleAccount string, denom string, paymentCoins sdk.Coin) error {
|
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)
|
return k.supplyKeeper.BurnCoins(ctx, moduleAccount, debtCoins)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,6 +466,20 @@ func (k Keeper) ValidateCollateralizationRatio(ctx sdk.Context, collateral sdk.C
|
|||||||
return nil
|
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
|
// 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 {
|
func (k Keeper) CalculateCollateralToDebtRatio(ctx sdk.Context, collateral sdk.Coin, collateralType string, debt sdk.Coin) sdk.Dec {
|
||||||
debtTotal := k.convertDebtToBaseUnits(ctx, debt)
|
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
|
// LoadAugmentedCDP creates a new augmented CDP from an existing CDP
|
||||||
func (k Keeper) LoadAugmentedCDP(ctx sdk.Context, cdp types.CDP) types.AugmentedCDP {
|
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
|
// calculate collateralization ratio
|
||||||
collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral, cdp.Type, cdp.Principal, cdp.AccumulatedFees, liquidation)
|
collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral, cdp.Type, cdp.Principal, cdp.AccumulatedFees, liquidation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -131,7 +131,7 @@ func (suite *CdpTestSuite) TestGetNextCdpID() {
|
|||||||
|
|
||||||
func (suite *CdpTestSuite) TestGetSetCdp() {
|
func (suite *CdpTestSuite) TestGetSetCdp() {
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
_, 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)
|
err := suite.keeper.SetCDP(suite.ctx, cdp)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
@ -147,7 +147,7 @@ func (suite *CdpTestSuite) TestGetSetCdp() {
|
|||||||
|
|
||||||
func (suite *CdpTestSuite) TestGetSetCdpId() {
|
func (suite *CdpTestSuite) TestGetSetCdpId() {
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
_, 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)
|
err := suite.keeper.SetCDP(suite.ctx, cdp)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.keeper.IndexCdpByOwner(suite.ctx, cdp)
|
suite.keeper.IndexCdpByOwner(suite.ctx, cdp)
|
||||||
@ -162,7 +162,7 @@ func (suite *CdpTestSuite) TestGetSetCdpId() {
|
|||||||
|
|
||||||
func (suite *CdpTestSuite) TestGetSetCdpByOwnerAndCollateralType() {
|
func (suite *CdpTestSuite) TestGetSetCdpByOwnerAndCollateralType() {
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
_, 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)
|
err := suite.keeper.SetCDP(suite.ctx, cdp)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.keeper.IndexCdpByOwner(suite.ctx, cdp)
|
suite.keeper.IndexCdpByOwner(suite.ctx, cdp)
|
||||||
@ -178,17 +178,17 @@ func (suite *CdpTestSuite) TestGetSetCdpByOwnerAndCollateralType() {
|
|||||||
|
|
||||||
func (suite *CdpTestSuite) TestCalculateCollateralToDebtRatio() {
|
func (suite *CdpTestSuite) TestCalculateCollateralToDebtRatio() {
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
_, 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)
|
cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Type, cdp.Principal)
|
||||||
suite.Equal(sdk.MustNewDecFromStr("3.0"), cr)
|
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)
|
cr = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Type, cdp.Principal)
|
||||||
suite.Equal(sdk.MustNewDecFromStr("0.5"), cr)
|
suite.Equal(sdk.MustNewDecFromStr("0.5"), cr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *CdpTestSuite) TestSetCdpByCollateralRatio() {
|
func (suite *CdpTestSuite) TestSetCdpByCollateralRatio() {
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
_, 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)
|
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) })
|
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 {
|
if !found {
|
||||||
return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, collateral %s", owner, collateralType)
|
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)
|
deposit, found := k.GetDeposit(ctx, cdp.ID, depositor)
|
||||||
if found {
|
if found {
|
||||||
@ -32,6 +38,12 @@ func (k Keeper) DepositCollateral(ctx sdk.Context, owner, depositor sdk.AccAddre
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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(
|
ctx.EventManager().EmitEvent(
|
||||||
sdk.NewEvent(
|
sdk.NewEvent(
|
||||||
types.EventTypeCdpDeposit,
|
types.EventTypeCdpDeposit,
|
||||||
@ -40,14 +52,7 @@ func (k Keeper) DepositCollateral(ctx sdk.Context, owner, depositor sdk.AccAddre
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
k.SetDeposit(ctx, deposit)
|
return k.UpdateCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithdrawCollateral removes collateral from a cdp if it does not put the cdp below the liquidation ratio
|
// 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) {
|
if collateral.Amount.GT(deposit.Amount.Amount) {
|
||||||
return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "collateral %s, deposit %s", collateral, deposit.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)
|
collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral.Sub(collateral), cdp.Type, cdp.Principal, cdp.AccumulatedFees, spot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,24 +83,15 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner, depositor sdk.AccAddr
|
|||||||
if collateralizationRatio.LT(liquidationRatio) {
|
if collateralizationRatio.LT(liquidationRatio) {
|
||||||
return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "collateral %s, collateral ratio %s, liquidation ration %s", collateral.Denom, collateralizationRatio, 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))
|
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, depositor, sdk.NewCoins(collateral))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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)
|
cdp.Collateral = cdp.Collateral.Sub(collateral)
|
||||||
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -105,6 +103,15 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner, depositor sdk.AccAddr
|
|||||||
} else {
|
} else {
|
||||||
k.SetDeposit(ctx, deposit)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateralTy
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
err = k.ValidateCollateralizationRatio(ctx, cdp.Collateral, cdp.Type, cdp.Principal.Add(principal), cdp.AccumulatedFees)
|
||||||
if err != nil {
|
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
|
// update cdp state
|
||||||
cdp.Principal = cdp.Principal.Add(principal)
|
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
|
// set cdp state and indexes in the store
|
||||||
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
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
|
// RepayPrincipal removes debt from the cdp
|
||||||
@ -85,6 +83,13 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateral
|
|||||||
return err
|
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
|
// Note: assumes cdp.Principal and cdp.AccumulatedFees don't change during calculations
|
||||||
totalPrincipal := cdp.GetTotalPrincipal()
|
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
|
// 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
|
// update cdp state
|
||||||
if !principalPayment.IsZero() {
|
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
|
// and remove the cdp and indexes from the store
|
||||||
if cdp.Principal.IsZero() && cdp.AccumulatedFees.IsZero() {
|
if cdp.Principal.IsZero() && cdp.AccumulatedFees.IsZero() {
|
||||||
k.ReturnCollateral(ctx, cdp)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
k.RemoveCdpOwnerIndex(ctx, cdp)
|
|
||||||
|
|
||||||
// emit cdp close event
|
// emit cdp close event
|
||||||
ctx.EventManager().EmitEvent(
|
ctx.EventManager().EmitEvent(
|
||||||
sdk.NewEvent(
|
sdk.NewEvent(
|
||||||
@ -168,7 +171,7 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateral
|
|||||||
|
|
||||||
// set cdp state and update indexes
|
// set cdp state and update indexes
|
||||||
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
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
|
// ValidatePaymentCoins validates that the input coins are valid for repaying debt
|
||||||
|
@ -129,37 +129,6 @@ func (suite *DrawTestSuite) TestRepayPrincipalOverpay() {
|
|||||||
suite.False(found)
|
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() {
|
func (suite *DrawTestSuite) TestPricefeedFailure() {
|
||||||
ctx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 2))
|
ctx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 2))
|
||||||
pfk := suite.app.GetPriceFeedKeeper()
|
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,
|
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||||
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
|
|
||||||
CollateralParams: cdp.CollateralParams{
|
CollateralParams: cdp.CollateralParams{
|
||||||
{
|
{
|
||||||
Denom: asset,
|
Denom: asset,
|
||||||
@ -56,9 +55,11 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
|
|||||||
LiquidationPenalty: d("0.05"),
|
LiquidationPenalty: d("0.05"),
|
||||||
AuctionSize: i(100),
|
AuctionSize: i(100),
|
||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
ConversionFactor: i(6),
|
|
||||||
SpotMarketID: asset + ":usd",
|
SpotMarketID: asset + ":usd",
|
||||||
LiquidationMarketID: asset + ":usd",
|
LiquidationMarketID: asset + ":usd",
|
||||||
|
KeeperRewardPercentage: d("0.01"),
|
||||||
|
CheckCollateralizationIndexCount: i(10),
|
||||||
|
ConversionFactor: i(6),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DebtParam: cdp.DebtParam{
|
DebtParam: cdp.DebtParam{
|
||||||
@ -66,14 +67,18 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
DebtFloor: i(10000000),
|
DebtFloor: i(10000000),
|
||||||
SavingsRate: d("0.9"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||||
DebtDenom: cdp.DefaultDebtDenom,
|
DebtDenom: cdp.DefaultDebtDenom,
|
||||||
GovDenom: cdp.DefaultGovDenom,
|
GovDenom: cdp.DefaultGovDenom,
|
||||||
CDPs: cdp.CDPs{},
|
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)}
|
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: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||||
{MarketID: "xrp:usd", BaseAsset: "xrp", 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: "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{
|
PostedPrices: []pricefeed.PostedPrice{
|
||||||
@ -106,6 +112,12 @@ func NewPricefeedGenStateMulti() app.GenesisState {
|
|||||||
Price: sdk.MustNewDecFromStr("17.25"),
|
Price: sdk.MustNewDecFromStr("17.25"),
|
||||||
Expiry: time.Now().Add(1 * time.Hour),
|
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)}
|
return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)}
|
||||||
@ -113,12 +125,11 @@ func NewPricefeedGenStateMulti() app.GenesisState {
|
|||||||
func NewCDPGenStateMulti() app.GenesisState {
|
func NewCDPGenStateMulti() app.GenesisState {
|
||||||
cdpGenesis := cdp.GenesisState{
|
cdpGenesis := cdp.GenesisState{
|
||||||
Params: cdp.Params{
|
Params: cdp.Params{
|
||||||
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 1500000000000),
|
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
|
||||||
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
|
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
|
||||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||||
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
|
|
||||||
CollateralParams: cdp.CollateralParams{
|
CollateralParams: cdp.CollateralParams{
|
||||||
{
|
{
|
||||||
Denom: "xrp",
|
Denom: "xrp",
|
||||||
@ -131,6 +142,8 @@ func NewCDPGenStateMulti() app.GenesisState {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "xrp:usd",
|
SpotMarketID: "xrp:usd",
|
||||||
LiquidationMarketID: "xrp:usd",
|
LiquidationMarketID: "xrp:usd",
|
||||||
|
KeeperRewardPercentage: d("0.01"),
|
||||||
|
CheckCollateralizationIndexCount: i(10),
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -144,6 +157,8 @@ func NewCDPGenStateMulti() app.GenesisState {
|
|||||||
Prefix: 0x21,
|
Prefix: 0x21,
|
||||||
SpotMarketID: "btc:usd",
|
SpotMarketID: "btc:usd",
|
||||||
LiquidationMarketID: "btc:usd",
|
LiquidationMarketID: "btc:usd",
|
||||||
|
KeeperRewardPercentage: d("0.01"),
|
||||||
|
CheckCollateralizationIndexCount: i(10),
|
||||||
ConversionFactor: i(8),
|
ConversionFactor: i(8),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -157,6 +172,23 @@ func NewCDPGenStateMulti() app.GenesisState {
|
|||||||
Prefix: 0x22,
|
Prefix: 0x22,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "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),
|
ConversionFactor: i(8),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -165,14 +197,24 @@ func NewCDPGenStateMulti() app.GenesisState {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
DebtFloor: i(10000000),
|
DebtFloor: i(10000000),
|
||||||
SavingsRate: d("0.95"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||||
DebtDenom: cdp.DefaultDebtDenom,
|
DebtDenom: cdp.DefaultDebtDenom,
|
||||||
GovDenom: cdp.DefaultGovDenom,
|
GovDenom: cdp.DefaultGovDenom,
|
||||||
CDPs: cdp.CDPs{},
|
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)}
|
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
|
||||||
}
|
}
|
||||||
@ -185,7 +227,6 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
|
|||||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||||
DebtAuctionLot: cdp.DefaultDebtLot,
|
DebtAuctionLot: cdp.DefaultDebtLot,
|
||||||
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
|
|
||||||
CollateralParams: cdp.CollateralParams{
|
CollateralParams: cdp.CollateralParams{
|
||||||
{
|
{
|
||||||
Denom: "xrp",
|
Denom: "xrp",
|
||||||
@ -198,6 +239,8 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "xrp:usd",
|
SpotMarketID: "xrp:usd",
|
||||||
LiquidationMarketID: "xrp:usd",
|
LiquidationMarketID: "xrp:usd",
|
||||||
|
KeeperRewardPercentage: d("0.01"),
|
||||||
|
CheckCollateralizationIndexCount: i(10),
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -211,6 +254,8 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
|
|||||||
Prefix: 0x21,
|
Prefix: 0x21,
|
||||||
SpotMarketID: "btc:usd",
|
SpotMarketID: "btc:usd",
|
||||||
LiquidationMarketID: "btc:usd",
|
LiquidationMarketID: "btc:usd",
|
||||||
|
KeeperRewardPercentage: d("0.01"),
|
||||||
|
CheckCollateralizationIndexCount: i(10),
|
||||||
ConversionFactor: i(8),
|
ConversionFactor: i(8),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -219,24 +264,30 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
DebtFloor: i(10000000),
|
DebtFloor: i(10000000),
|
||||||
SavingsRate: d("0.95"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
StartingCdpID: cdp.DefaultCdpStartingID,
|
StartingCdpID: cdp.DefaultCdpStartingID,
|
||||||
DebtDenom: cdp.DefaultDebtDenom,
|
DebtDenom: cdp.DefaultDebtDenom,
|
||||||
GovDenom: cdp.DefaultGovDenom,
|
GovDenom: cdp.DefaultGovDenom,
|
||||||
CDPs: cdp.CDPs{},
|
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)}
|
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cdps() (cdps cdp.CDPs) {
|
func cdps() (cdps cdp.CDPs) {
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(3)
|
_, 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()))
|
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()))
|
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()))
|
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()))
|
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)
|
cdps = append(cdps, c1, c2, c3, c4)
|
||||||
return
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
@ -20,6 +21,7 @@ type Keeper struct {
|
|||||||
supplyKeeper types.SupplyKeeper
|
supplyKeeper types.SupplyKeeper
|
||||||
auctionKeeper types.AuctionKeeper
|
auctionKeeper types.AuctionKeeper
|
||||||
accountKeeper types.AccountKeeper
|
accountKeeper types.AccountKeeper
|
||||||
|
hooks types.CDPHooks
|
||||||
maccPerms map[string][]string
|
maccPerms map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,10 +40,20 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace,
|
|||||||
auctionKeeper: ak,
|
auctionKeeper: ak,
|
||||||
supplyKeeper: sk,
|
supplyKeeper: sk,
|
||||||
accountKeeper: ack,
|
accountKeeper: ack,
|
||||||
|
hooks: nil,
|
||||||
maccPerms: maccs,
|
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
|
// CdpDenomIndexIterator returns an sdk.Iterator for all cdps with matching collateral denom
|
||||||
func (k Keeper) CdpDenomIndexIterator(ctx sdk.Context, collateralType string) sdk.Iterator {
|
func (k Keeper) CdpDenomIndexIterator(ctx sdk.Context, collateralType string) sdk.Iterator {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.CdpKeyPrefix)
|
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
|
// GetSliceOfCDPsByRatioAndType returns a slice of cdps of size equal to the input cutoffCount
|
||||||
func (k Keeper) SetSavingsRateDistributed(ctx sdk.Context, totalDistributed sdk.Int) {
|
// sorted by target ratio in ascending order (ie, the lowest collateral:debt ratio cdps are returned first)
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.SavingsRateDistributedKey)
|
func (k Keeper) GetSliceOfCDPsByRatioAndType(ctx sdk.Context, cutoffCount sdk.Int, targetRatio sdk.Dec, collateralType string) (cdps types.CDPs) {
|
||||||
bz := k.cdc.MustMarshalBinaryLengthPrefixed(totalDistributed)
|
count := sdk.ZeroInt()
|
||||||
store.Set([]byte{}, bz)
|
k.IterateCdpsByCollateralRatio(ctx, collateralType, targetRatio, func(cdp types.CDP) bool {
|
||||||
}
|
cdps = append(cdps, cdp)
|
||||||
|
count = count.Add(sdk.OneInt())
|
||||||
// GetSavingsRateDistributed gets the SavingsRateDistributed from the store
|
if count.GTE(cutoffCount) {
|
||||||
func (k Keeper) GetSavingsRateDistributed(ctx sdk.Context) sdk.Int {
|
return true
|
||||||
savingsRateDistributed := sdk.ZeroInt()
|
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.SavingsRateDistributedKey)
|
|
||||||
bz := store.Get([]byte{})
|
|
||||||
if bz == nil {
|
|
||||||
return savingsRateDistributed
|
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &savingsRateDistributed)
|
})
|
||||||
return savingsRateDistributed
|
return cdps
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 time.Time{}, false
|
||||||
|
}
|
||||||
|
var previousAccrualTime time.Time
|
||||||
|
k.cdc.MustUnmarshalBinaryBare(bz, &previousAccrualTime)
|
||||||
|
return previousAccrualTime, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.ctx = ctx
|
||||||
suite.keeper = keeper
|
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
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
@ -32,10 +30,6 @@ func NewQuerier(keeper Keeper) sdk.Querier {
|
|||||||
return queryGetParams(ctx, req, keeper)
|
return queryGetParams(ctx, req, keeper)
|
||||||
case types.QueryGetAccounts:
|
case types.QueryGetAccounts:
|
||||||
return queryGetAccounts(ctx, req, keeper)
|
return queryGetAccounts(ctx, req, keeper)
|
||||||
case types.QueryGetSavingsRateDistributed:
|
|
||||||
return queryGetSavingsRateDistributed(ctx, req, keeper)
|
|
||||||
case types.QueryGetPreviousSavingsDistributionTime:
|
|
||||||
return queryGetPreviousSavingsDistributionTime(ctx, req, keeper)
|
|
||||||
default:
|
default:
|
||||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint %s", types.ModuleName, path[0])
|
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) {
|
func queryGetAccounts(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
|
||||||
cdpAccAccount := keeper.supplyKeeper.GetModuleAccount(ctx, types.ModuleName)
|
cdpAccAccount := keeper.supplyKeeper.GetModuleAccount(ctx, types.ModuleName)
|
||||||
liquidatorAccAccount := keeper.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc)
|
liquidatorAccAccount := keeper.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc)
|
||||||
savingsRateAccAccount := keeper.supplyKeeper.GetModuleAccount(ctx, types.SavingsRateMacc)
|
|
||||||
|
|
||||||
accounts := []supply.ModuleAccount{
|
accounts := []supply.ModuleAccount{
|
||||||
*cdpAccAccount.(*supply.ModuleAccount),
|
*cdpAccAccount.(*supply.ModuleAccount),
|
||||||
*liquidatorAccAccount.(*supply.ModuleAccount),
|
*liquidatorAccAccount.(*supply.ModuleAccount),
|
||||||
*savingsRateAccAccount.(*supply.ModuleAccount),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode results
|
// Encode results
|
||||||
@ -188,36 +180,6 @@ func queryGetAccounts(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]
|
|||||||
return bz, nil
|
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
|
// query cdps in store and filter by request params
|
||||||
func queryGetCdps(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
|
func queryGetCdps(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
|
||||||
var params types.QueryCdpsParams
|
var params types.QueryCdpsParams
|
||||||
|
@ -287,7 +287,7 @@ func (suite *QuerierTestSuite) TestQueryAccounts() {
|
|||||||
|
|
||||||
var accounts []supply.ModuleAccount
|
var accounts []supply.ModuleAccount
|
||||||
suite.Require().Nil(supply.ModuleCdc.UnmarshalJSON(bz, &accounts))
|
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 {
|
findByName := func(name string) bool {
|
||||||
for _, account := range accounts {
|
for _, account := range accounts {
|
||||||
@ -300,18 +300,6 @@ func (suite *QuerierTestSuite) TestQueryAccounts() {
|
|||||||
|
|
||||||
suite.Require().True(findByName("cdp"))
|
suite.Require().True(findByName("cdp"))
|
||||||
suite.Require().True(findByName("liquidator"))
|
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() {
|
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"
|
"fmt"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/cdp/types"
|
"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.
|
// SeizeCollateral liquidates the collateral in the input cdp.
|
||||||
// the following operations are performed:
|
// the following operations are performed:
|
||||||
// 1. Collateral for all deposits is sent from the cdp module to the liquidator module account
|
// 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)
|
normalizedRatio := sdk.OneDec().Quo(priceDivLiqRatio)
|
||||||
cdpsToLiquidate := k.GetAllCdpsByCollateralTypeAndRatio(ctx, collateralType, normalizedRatio)
|
cdpsToLiquidate := k.GetAllCdpsByCollateralTypeAndRatio(ctx, collateralType, normalizedRatio)
|
||||||
for _, c := range cdpsToLiquidate {
|
for _, c := range cdpsToLiquidate {
|
||||||
|
k.hooks.BeforeCDPModified(ctx, c)
|
||||||
err := k.SeizeCollateral(ctx, c)
|
err := k.SeizeCollateral(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -93,7 +117,53 @@ func (k Keeper) ApplyLiquidationPenalty(ctx sdk.Context, collateralType string,
|
|||||||
return sdk.NewDecFromInt(debt).Mul(penalty).RoundInt()
|
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 {
|
func (k Keeper) getModAccountDebt(ctx sdk.Context, accountName string) sdk.Int {
|
||||||
macc := k.supplyKeeper.GetModuleAccount(ctx, accountName)
|
macc := k.supplyKeeper.GetModuleAccount(ctx, accountName)
|
||||||
return macc.GetCoins().AmountOf(k.GetDebtDenom(ctx))
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -205,6 +206,258 @@ func (suite *SeizeTestSuite) TestApplyLiquidationPenalty() {
|
|||||||
suite.Panics(func() { suite.keeper.ApplyLiquidationPenalty(suite.ctx, "lol-a", i(1000)) })
|
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) {
|
func TestSeizeTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(SeizeTestSuite))
|
suite.Run(t, new(SeizeTestSuite))
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,68 @@ import (
|
|||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
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 (
|
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
|
minCollateralPrefix = 0
|
||||||
maxCollateralPrefix = 255
|
maxCollateralPrefix = 255
|
||||||
|
stabilityFeeMax = sdk.MustNewDecFromStr("1.000000051034942716") // 500% APR
|
||||||
)
|
)
|
||||||
|
|
||||||
// CDP is the state of a single collateralized debt position.
|
// 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 {
|
func (cdp CDP) Validate() error {
|
||||||
if cdp.ID == 0 {
|
if cdp.ID == 0 {
|
||||||
return errors.New("cdp id cannot be 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"
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ModuleName = "cdp"
|
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.
|
// CDP is the state of a single collateralized debt position.
|
||||||
type CDP struct {
|
type CDP struct {
|
||||||
ID uint64 `json:"id" yaml:"id"` // unique id for cdp
|
ID uint64 `json:"id" yaml:"id"` // unique id for cdp
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/libs/kv"
|
"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)
|
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &totalB)
|
||||||
return fmt.Sprintf("%s\n%s", totalA, 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:
|
default:
|
||||||
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
|
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}
|
deposit := types.Deposit{CdpID: 1, Amount: oneCoins}
|
||||||
principal := sdk.OneInt()
|
principal := sdk.OneInt()
|
||||||
prevDistTime := time.Now().UTC()
|
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{
|
kvPairs := kv.Pairs{
|
||||||
kv.Pair{Key: types.CdpIDKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(cdpIds)},
|
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.GovDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)},
|
||||||
kv.Pair{Key: []byte(types.DepositKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(deposit)},
|
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.PrincipalKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(principal)},
|
||||||
kv.Pair{Key: []byte(types.PreviousDistributionTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevDistTime)},
|
|
||||||
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
|
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)},
|
{"GovDenom", fmt.Sprintf("%s\n%s", denom, denom)},
|
||||||
{"DepositKeyPrefix", fmt.Sprintf("%v\n%v", deposit, deposit)},
|
{"DepositKeyPrefix", fmt.Sprintf("%v\n%v", deposit, deposit)},
|
||||||
{"Principal", fmt.Sprintf("%v\n%v", principal, principal)},
|
{"Principal", fmt.Sprintf("%v\n%v", principal, principal)},
|
||||||
{"PreviousDistributionTime", fmt.Sprintf("%s\n%s", prevDistTime, prevDistTime)},
|
|
||||||
{"other", ""},
|
{"other", ""},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
|
@ -75,7 +75,6 @@ func randomCdpGenState(selection int) types.GenesisState {
|
|||||||
SurplusAuctionLot: types.DefaultSurplusLot,
|
SurplusAuctionLot: types.DefaultSurplusLot,
|
||||||
DebtAuctionLot: types.DefaultDebtLot,
|
DebtAuctionLot: types.DefaultDebtLot,
|
||||||
DebtAuctionThreshold: types.DefaultDebtThreshold,
|
DebtAuctionThreshold: types.DefaultDebtThreshold,
|
||||||
SavingsDistributionFrequency: types.DefaultSavingsDistributionFrequency,
|
|
||||||
CollateralParams: types.CollateralParams{
|
CollateralParams: types.CollateralParams{
|
||||||
{
|
{
|
||||||
Denom: "xrp",
|
Denom: "xrp",
|
||||||
@ -122,14 +121,12 @@ func randomCdpGenState(selection int) types.GenesisState {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
StartingCdpID: types.DefaultCdpStartingID,
|
StartingCdpID: types.DefaultCdpStartingID,
|
||||||
DebtDenom: types.DefaultDebtDenom,
|
DebtDenom: types.DefaultDebtDenom,
|
||||||
GovDenom: types.DefaultGovDenom,
|
GovDenom: types.DefaultGovDenom,
|
||||||
CDPs: types.CDPs{},
|
CDPs: types.CDPs{},
|
||||||
PreviousDistributionTime: types.DefaultPreviousDistributionTime,
|
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
return types.GenesisState{
|
return types.GenesisState{
|
||||||
@ -139,7 +136,6 @@ func randomCdpGenState(selection int) types.GenesisState {
|
|||||||
DebtAuctionThreshold: types.DefaultDebtThreshold,
|
DebtAuctionThreshold: types.DefaultDebtThreshold,
|
||||||
SurplusAuctionLot: types.DefaultSurplusLot,
|
SurplusAuctionLot: types.DefaultSurplusLot,
|
||||||
DebtAuctionLot: types.DefaultDebtLot,
|
DebtAuctionLot: types.DefaultDebtLot,
|
||||||
SavingsDistributionFrequency: types.DefaultSavingsDistributionFrequency,
|
|
||||||
CollateralParams: types.CollateralParams{
|
CollateralParams: types.CollateralParams{
|
||||||
{
|
{
|
||||||
Denom: "bnb",
|
Denom: "bnb",
|
||||||
@ -160,14 +156,12 @@ func randomCdpGenState(selection int) types.GenesisState {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
StartingCdpID: types.DefaultCdpStartingID,
|
StartingCdpID: types.DefaultCdpStartingID,
|
||||||
DebtDenom: types.DefaultDebtDenom,
|
DebtDenom: types.DefaultDebtDenom,
|
||||||
GovDenom: types.DefaultGovDenom,
|
GovDenom: types.DefaultGovDenom,
|
||||||
CDPs: types.CDPs{},
|
CDPs: types.CDPs{},
|
||||||
PreviousDistributionTime: types.DefaultPreviousDistributionTime,
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
panic("invalid genesis state selector")
|
panic("invalid genesis state selector")
|
||||||
|
@ -184,12 +184,12 @@ func SimulateMsgCdp(ak types.AccountKeeper, k keeper.Keeper, pfk types.Pricefeed
|
|||||||
if shouldDraw(r) {
|
if shouldDraw(r) {
|
||||||
collateralShifted := ShiftDec(sdk.NewDecFromInt(existingCDP.Collateral.Amount), randCollateralParam.ConversionFactor.Neg())
|
collateralShifted := ShiftDec(sdk.NewDecFromInt(existingCDP.Collateral.Amount), randCollateralParam.ConversionFactor.Neg())
|
||||||
collateralValue := collateralShifted.Mul(priceShifted)
|
collateralValue := collateralShifted.Mul(priceShifted)
|
||||||
newFeesAccumulated := k.CalculateFees(ctx, existingCDP.Principal, sdk.NewInt(ctx.BlockTime().Unix()-existingCDP.FeesUpdated.Unix()), randCollateralParam.Type).Amount
|
newFeesAccumulated := k.CalculateNewInterest(ctx, existingCDP)
|
||||||
totalFees := existingCDP.AccumulatedFees.Amount.Add(newFeesAccumulated)
|
totalFees := existingCDP.AccumulatedFees.Add(newFeesAccumulated)
|
||||||
// given the current collateral value, calculate how much debt we could add while maintaining a valid liquidation ratio
|
// 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)
|
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()) {
|
if maxDebt.LTE(sdk.OneInt()) {
|
||||||
// debt in cdp is maxed out
|
// 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
|
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 {
|
type CDP struct {
|
||||||
ID uint64
|
ID uint64
|
||||||
Owner sdk.AccAddress
|
Owner sdk.AccAddress
|
||||||
|
Type string
|
||||||
Collateral sdk.Coin
|
Collateral sdk.Coin
|
||||||
Principal sdk.Coin
|
Principal sdk.Coin
|
||||||
AccumulatedFees sdk.Coin
|
AccumulatedFees sdk.Coin
|
||||||
FeesUpdated time.Time
|
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
|
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
|
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
|
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
|
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"` // Amount of stable coin drawn from this CDP
|
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
|
// 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())
|
fees := sdk.NewCoin(principal.Denom, sdk.ZeroInt())
|
||||||
return CDP{
|
return CDP{
|
||||||
ID: id,
|
ID: id,
|
||||||
@ -32,11 +33,12 @@ func NewCDP(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collateralType
|
|||||||
Principal: principal,
|
Principal: principal,
|
||||||
AccumulatedFees: fees,
|
AccumulatedFees: fees,
|
||||||
FeesUpdated: time,
|
FeesUpdated: time,
|
||||||
|
InterestFactor: interestFactor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCDPWithFees creates a new CDP object, for use during migration
|
// 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{
|
return CDP{
|
||||||
ID: id,
|
ID: id,
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
@ -45,6 +47,7 @@ func NewCDPWithFees(id uint64, owner sdk.AccAddress, collateral sdk.Coin, collat
|
|||||||
Principal: principal,
|
Principal: principal,
|
||||||
AccumulatedFees: fees,
|
AccumulatedFees: fees,
|
||||||
FeesUpdated: time,
|
FeesUpdated: time,
|
||||||
|
InterestFactor: interestFactor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +60,8 @@ func (cdp CDP) String() string {
|
|||||||
Collateral: %s
|
Collateral: %s
|
||||||
Principal: %s
|
Principal: %s
|
||||||
AccumulatedFees: %s
|
AccumulatedFees: %s
|
||||||
Fees Last Updated: %s`,
|
Fees Last Updated: %s
|
||||||
|
Interest Factor: %s`,
|
||||||
cdp.Owner,
|
cdp.Owner,
|
||||||
cdp.ID,
|
cdp.ID,
|
||||||
cdp.Type,
|
cdp.Type,
|
||||||
@ -65,6 +69,7 @@ func (cdp CDP) String() string {
|
|||||||
cdp.Principal,
|
cdp.Principal,
|
||||||
cdp.AccumulatedFees,
|
cdp.AccumulatedFees,
|
||||||
cdp.FeesUpdated,
|
cdp.FeesUpdated,
|
||||||
|
cdp.InterestFactor,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +144,7 @@ func NewAugmentedCDP(cdp CDP, collateralValue sdk.Coin, collateralizationRatio s
|
|||||||
Principal: cdp.Principal,
|
Principal: cdp.Principal,
|
||||||
AccumulatedFees: cdp.AccumulatedFees,
|
AccumulatedFees: cdp.AccumulatedFees,
|
||||||
FeesUpdated: cdp.FeesUpdated,
|
FeesUpdated: cdp.FeesUpdated,
|
||||||
|
InterestFactor: cdp.InterestFactor,
|
||||||
},
|
},
|
||||||
CollateralValue: collateralValue,
|
CollateralValue: collateralValue,
|
||||||
CollateralizationRatio: collateralizationRatio,
|
CollateralizationRatio: collateralizationRatio,
|
||||||
@ -157,6 +163,7 @@ func (augCDP AugmentedCDP) String() string {
|
|||||||
Principal: %s
|
Principal: %s
|
||||||
Fees: %s
|
Fees: %s
|
||||||
Fees Last Updated: %s
|
Fees Last Updated: %s
|
||||||
|
Interest Factor: %s
|
||||||
Collateralization ratio: %s`,
|
Collateralization ratio: %s`,
|
||||||
augCDP.Owner,
|
augCDP.Owner,
|
||||||
augCDP.ID,
|
augCDP.ID,
|
||||||
@ -166,6 +173,7 @@ func (augCDP AugmentedCDP) String() string {
|
|||||||
augCDP.Principal,
|
augCDP.Principal,
|
||||||
augCDP.AccumulatedFees,
|
augCDP.AccumulatedFees,
|
||||||
augCDP.FeesUpdated,
|
augCDP.FeesUpdated,
|
||||||
|
augCDP.InterestFactor,
|
||||||
augCDP.CollateralizationRatio,
|
augCDP.CollateralizationRatio,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid cdp",
|
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{
|
errArgs: errArgs{
|
||||||
expectPass: true,
|
expectPass: true,
|
||||||
contains: "",
|
contains: "",
|
||||||
@ -50,7 +50,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid cdp id",
|
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{
|
errArgs: errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
contains: "cdp id cannot be 0",
|
contains: "cdp id cannot be 0",
|
||||||
@ -58,7 +58,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid collateral",
|
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{
|
errArgs: errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
contains: "invalid coins: collateral",
|
contains: "invalid coins: collateral",
|
||||||
@ -66,7 +66,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid prinicpal",
|
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{
|
errArgs: errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
contains: "invalid coins: principal",
|
contains: "invalid coins: principal",
|
||||||
@ -74,7 +74,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid fees",
|
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{
|
errArgs: errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
contains: "invalid coins: accumulated fees",
|
contains: "invalid coins: accumulated fees",
|
||||||
@ -82,7 +82,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid fees updated",
|
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{
|
errArgs: errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
contains: "cdp updated fee time cannot be zero",
|
contains: "cdp updated fee time cannot be zero",
|
||||||
@ -90,7 +90,7 @@ func (suite *CdpValidationSuite) TestCdpValidation() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid type",
|
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{
|
errArgs: errArgs{
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
contains: "cdp type cannot be empty",
|
contains: "cdp type cannot be empty",
|
||||||
|
@ -45,4 +45,10 @@ var (
|
|||||||
ErrPricefeedDown = sdkerrors.Register(ModuleName, 19, "no price found for collateral")
|
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 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")
|
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))
|
IterateAccounts(ctx sdk.Context, cb func(account authexported.Account) (stop bool))
|
||||||
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
|
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"`
|
StartingCdpID uint64 `json:"starting_cdp_id" yaml:"starting_cdp_id"`
|
||||||
DebtDenom string `json:"debt_denom" yaml:"debt_denom"`
|
DebtDenom string `json:"debt_denom" yaml:"debt_denom"`
|
||||||
GovDenom string `json:"gov_denom" yaml:"gov_denom"`
|
GovDenom string `json:"gov_denom" yaml:"gov_denom"`
|
||||||
PreviousDistributionTime time.Time `json:"previous_distribution_time" yaml:"previous_distribution_time"`
|
PreviousAccumulationTimes GenesisAccumulationTimes `json:"previous_accumulation_times" yaml:"previous_accumulation_times"`
|
||||||
SavingsRateDistributed sdk.Int `json:"savings_rate_distributed" yaml:"savings_rate_distributed"`
|
TotalPrincipals GenesisTotalPrincipals `json:"total_principals" yaml:"total_principals"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGenesisState returns a new genesis state
|
// NewGenesisState returns a new genesis state
|
||||||
func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID uint64,
|
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{
|
return GenesisState{
|
||||||
Params: params,
|
Params: params,
|
||||||
CDPs: cdps,
|
CDPs: cdps,
|
||||||
@ -30,8 +31,8 @@ func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID
|
|||||||
StartingCdpID: startingCdpID,
|
StartingCdpID: startingCdpID,
|
||||||
DebtDenom: debtDenom,
|
DebtDenom: debtDenom,
|
||||||
GovDenom: govDenom,
|
GovDenom: govDenom,
|
||||||
PreviousDistributionTime: previousDistTime,
|
PreviousAccumulationTimes: prevAccumTimes,
|
||||||
SavingsRateDistributed: savingsRateDist,
|
TotalPrincipals: totalPrincipals,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,8 +45,8 @@ func DefaultGenesisState() GenesisState {
|
|||||||
DefaultCdpStartingID,
|
DefaultCdpStartingID,
|
||||||
DefaultDebtDenom,
|
DefaultDebtDenom,
|
||||||
DefaultGovDenom,
|
DefaultGovDenom,
|
||||||
DefaultPreviousDistributionTime,
|
GenesisAccumulationTimes{},
|
||||||
DefaultSavingsRateDistributed,
|
GenesisTotalPrincipals{},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,11 +66,11 @@ func (gs GenesisState) Validate() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if gs.PreviousDistributionTime.IsZero() {
|
if err := gs.PreviousAccumulationTimes.Validate(); err != nil {
|
||||||
return fmt.Errorf("previous distribution time not set")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateSavingsRateDistributed(gs.SavingsRateDistributed); err != nil {
|
if err := gs.TotalPrincipals.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,3 +109,75 @@ func (gs GenesisState) Equal(gs2 GenesisState) bool {
|
|||||||
func (gs GenesisState) IsEmpty() bool {
|
func (gs GenesisState) IsEmpty() bool {
|
||||||
return gs.Equal(GenesisState{})
|
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 module account for liquidator
|
||||||
LiquidatorMacc = "liquidator"
|
LiquidatorMacc = "liquidator"
|
||||||
|
|
||||||
// SavingsRateMacc module account for savings rate
|
|
||||||
SavingsRateMacc = "savings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var sep = []byte(":")
|
var sep = []byte(":")
|
||||||
@ -51,17 +48,17 @@ var sep = []byte(":")
|
|||||||
|
|
||||||
// KVStore key prefixes
|
// KVStore key prefixes
|
||||||
var (
|
var (
|
||||||
CdpIDKeyPrefix = []byte{0x00}
|
CdpIDKeyPrefix = []byte{0x01}
|
||||||
CdpKeyPrefix = []byte{0x01}
|
CdpKeyPrefix = []byte{0x02}
|
||||||
CollateralRatioIndexPrefix = []byte{0x02}
|
CollateralRatioIndexPrefix = []byte{0x03}
|
||||||
CdpIDKey = []byte{0x03}
|
CdpIDKey = []byte{0x04}
|
||||||
DebtDenomKey = []byte{0x04}
|
DebtDenomKey = []byte{0x05}
|
||||||
GovDenomKey = []byte{0x05}
|
GovDenomKey = []byte{0x06}
|
||||||
DepositKeyPrefix = []byte{0x06}
|
DepositKeyPrefix = []byte{0x07}
|
||||||
PrincipalKeyPrefix = []byte{0x07}
|
PrincipalKeyPrefix = []byte{0x08}
|
||||||
PreviousDistributionTimeKey = []byte{0x08}
|
PricefeedStatusKeyPrefix = []byte{0x10}
|
||||||
PricefeedStatusKeyPrefix = []byte{0x09}
|
PreviousAccrualTimePrefix = []byte{0x12}
|
||||||
SavingsRateDistributedKey = []byte{0x10}
|
InterestFactorPrefix = []byte{0x13}
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetCdpIDBytes returns the byte representation of the cdpID
|
// GetCdpIDBytes returns the byte representation of the cdpID
|
||||||
|
@ -314,3 +314,59 @@ func (msg MsgRepayDebt) String() string {
|
|||||||
Payment: %s
|
Payment: %s
|
||||||
`, msg.Sender, msg.CollateralType, msg.Payment)
|
`, 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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
"github.com/cosmos/cosmos-sdk/x/params"
|
"github.com/cosmos/cosmos-sdk/x/params"
|
||||||
|
|
||||||
tmtime "github.com/tendermint/tendermint/types/time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parameter keys
|
// Parameter keys
|
||||||
@ -17,13 +14,11 @@ var (
|
|||||||
KeyGlobalDebtLimit = []byte("GlobalDebtLimit")
|
KeyGlobalDebtLimit = []byte("GlobalDebtLimit")
|
||||||
KeyCollateralParams = []byte("CollateralParams")
|
KeyCollateralParams = []byte("CollateralParams")
|
||||||
KeyDebtParam = []byte("DebtParam")
|
KeyDebtParam = []byte("DebtParam")
|
||||||
KeyDistributionFrequency = []byte("DistributionFrequency")
|
|
||||||
KeyCircuitBreaker = []byte("CircuitBreaker")
|
KeyCircuitBreaker = []byte("CircuitBreaker")
|
||||||
KeyDebtThreshold = []byte("DebtThreshold")
|
KeyDebtThreshold = []byte("DebtThreshold")
|
||||||
KeyDebtLot = []byte("DebtLot")
|
KeyDebtLot = []byte("DebtLot")
|
||||||
KeySurplusThreshold = []byte("SurplusThreshold")
|
KeySurplusThreshold = []byte("SurplusThreshold")
|
||||||
KeySurplusLot = []byte("SurplusLot")
|
KeySurplusLot = []byte("SurplusLot")
|
||||||
KeySavingsRateDistributed = []byte("SavingsRateDistributed")
|
|
||||||
DefaultGlobalDebt = sdk.NewCoin(DefaultStableDenom, sdk.ZeroInt())
|
DefaultGlobalDebt = sdk.NewCoin(DefaultStableDenom, sdk.ZeroInt())
|
||||||
DefaultCircuitBreaker = false
|
DefaultCircuitBreaker = false
|
||||||
DefaultCollateralParams = CollateralParams{}
|
DefaultCollateralParams = CollateralParams{}
|
||||||
@ -32,7 +27,6 @@ var (
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
}
|
}
|
||||||
DefaultCdpStartingID = uint64(1)
|
DefaultCdpStartingID = uint64(1)
|
||||||
DefaultDebtDenom = "debt"
|
DefaultDebtDenom = "debt"
|
||||||
@ -42,8 +36,6 @@ var (
|
|||||||
DefaultDebtThreshold = sdk.NewInt(100000000000)
|
DefaultDebtThreshold = sdk.NewInt(100000000000)
|
||||||
DefaultSurplusLot = sdk.NewInt(10000000000)
|
DefaultSurplusLot = sdk.NewInt(10000000000)
|
||||||
DefaultDebtLot = sdk.NewInt(10000000000)
|
DefaultDebtLot = sdk.NewInt(10000000000)
|
||||||
DefaultPreviousDistributionTime = tmtime.Canonical(time.Unix(0, 0))
|
|
||||||
DefaultSavingsDistributionFrequency = time.Hour * 12
|
|
||||||
DefaultSavingsRateDistributed = sdk.NewInt(0)
|
DefaultSavingsRateDistributed = sdk.NewInt(0)
|
||||||
minCollateralPrefix = 0
|
minCollateralPrefix = 0
|
||||||
maxCollateralPrefix = 255
|
maxCollateralPrefix = 255
|
||||||
@ -59,7 +51,6 @@ type Params struct {
|
|||||||
SurplusAuctionLot sdk.Int `json:"surplus_auction_lot" yaml:"surplus_auction_lot"`
|
SurplusAuctionLot sdk.Int `json:"surplus_auction_lot" yaml:"surplus_auction_lot"`
|
||||||
DebtAuctionThreshold sdk.Int `json:"debt_auction_threshold" yaml:"debt_auction_threshold"`
|
DebtAuctionThreshold sdk.Int `json:"debt_auction_threshold" yaml:"debt_auction_threshold"`
|
||||||
DebtAuctionLot sdk.Int `json:"debt_auction_lot" yaml:"debt_auction_lot"`
|
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"`
|
CircuitBreaker bool `json:"circuit_breaker" yaml:"circuit_breaker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,17 +64,16 @@ func (p Params) String() string {
|
|||||||
Surplus Auction Lot: %s
|
Surplus Auction Lot: %s
|
||||||
Debt Auction Threshold: %s
|
Debt Auction Threshold: %s
|
||||||
Debt Auction Lot: %s
|
Debt Auction Lot: %s
|
||||||
Savings Distribution Frequency: %s
|
|
||||||
Circuit Breaker: %t`,
|
Circuit Breaker: %t`,
|
||||||
p.GlobalDebtLimit, p.CollateralParams, p.DebtParam, p.SurplusAuctionThreshold, p.SurplusAuctionLot,
|
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
|
// NewParams returns a new params object
|
||||||
func NewParams(
|
func NewParams(
|
||||||
debtLimit sdk.Coin, collateralParams CollateralParams, debtParam DebtParam, surplusThreshold,
|
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 {
|
) Params {
|
||||||
return Params{
|
return Params{
|
||||||
GlobalDebtLimit: debtLimit,
|
GlobalDebtLimit: debtLimit,
|
||||||
@ -93,7 +83,6 @@ func NewParams(
|
|||||||
SurplusAuctionLot: surplusLot,
|
SurplusAuctionLot: surplusLot,
|
||||||
DebtAuctionThreshold: debtThreshold,
|
DebtAuctionThreshold: debtThreshold,
|
||||||
DebtAuctionLot: debtLot,
|
DebtAuctionLot: debtLot,
|
||||||
SavingsDistributionFrequency: distributionFreq,
|
|
||||||
CircuitBreaker: breaker,
|
CircuitBreaker: breaker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,7 +91,7 @@ func NewParams(
|
|||||||
func DefaultParams() Params {
|
func DefaultParams() Params {
|
||||||
return NewParams(
|
return NewParams(
|
||||||
DefaultGlobalDebt, DefaultCollateralParams, DefaultDebtParam, DefaultSurplusThreshold,
|
DefaultGlobalDebt, DefaultCollateralParams, DefaultDebtParam, DefaultSurplusThreshold,
|
||||||
DefaultSurplusLot, DefaultDebtThreshold, DefaultDebtLot, DefaultSavingsDistributionFrequency,
|
DefaultSurplusLot, DefaultDebtThreshold, DefaultDebtLot,
|
||||||
DefaultCircuitBreaker,
|
DefaultCircuitBreaker,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -119,11 +108,15 @@ type CollateralParam struct {
|
|||||||
Prefix byte `json:"prefix" yaml:"prefix"`
|
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
|
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
|
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
|
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
|
// 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{
|
return CollateralParam{
|
||||||
Denom: denom,
|
Denom: denom,
|
||||||
Type: ctype,
|
Type: ctype,
|
||||||
@ -135,6 +128,8 @@ func NewCollateralParam(denom, ctype string, liqRatio sdk.Dec, debtLimit sdk.Coi
|
|||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
SpotMarketID: spotMarketID,
|
SpotMarketID: spotMarketID,
|
||||||
LiquidationMarketID: liquidationMarketID,
|
LiquidationMarketID: liquidationMarketID,
|
||||||
|
KeeperRewardPercentage: keeperReward,
|
||||||
|
CheckCollateralizationIndexCount: checkIndexCount,
|
||||||
ConversionFactor: conversionFactor,
|
ConversionFactor: conversionFactor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,8 +147,12 @@ func (cp CollateralParam) String() string {
|
|||||||
Prefix: %b
|
Prefix: %b
|
||||||
Spot Market ID: %s
|
Spot Market ID: %s
|
||||||
Liquidation Market ID: %s
|
Liquidation Market ID: %s
|
||||||
|
Keeper Reward Percentage: %s
|
||||||
|
Check Collateralization Count: %s
|
||||||
Conversion Factor: %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
|
// CollateralParams array of CollateralParam
|
||||||
@ -174,17 +173,15 @@ type DebtParam struct {
|
|||||||
ReferenceAsset string `json:"reference_asset" yaml:"reference_asset"`
|
ReferenceAsset string `json:"reference_asset" yaml:"reference_asset"`
|
||||||
ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"`
|
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
|
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
|
// 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{
|
return DebtParam{
|
||||||
Denom: denom,
|
Denom: denom,
|
||||||
ReferenceAsset: refAsset,
|
ReferenceAsset: refAsset,
|
||||||
ConversionFactor: conversionFactor,
|
ConversionFactor: conversionFactor,
|
||||||
DebtFloor: debtFloor,
|
DebtFloor: debtFloor,
|
||||||
SavingsRate: savingsRate,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,8 +191,7 @@ func (dp DebtParam) String() string {
|
|||||||
Reference Asset: %s
|
Reference Asset: %s
|
||||||
Conversion Factor: %s
|
Conversion Factor: %s
|
||||||
Debt Floor %s
|
Debt Floor %s
|
||||||
Savings Rate %s
|
`, dp.Denom, dp.ReferenceAsset, dp.ConversionFactor, dp.DebtFloor)
|
||||||
`, dp.Denom, dp.ReferenceAsset, dp.ConversionFactor, dp.DebtFloor, dp.SavingsRate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebtParams array of DebtParam
|
// DebtParams array of DebtParam
|
||||||
@ -228,7 +224,6 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs {
|
|||||||
params.NewParamSetPair(KeySurplusLot, &p.SurplusAuctionLot, validateSurplusAuctionLotParam),
|
params.NewParamSetPair(KeySurplusLot, &p.SurplusAuctionLot, validateSurplusAuctionLotParam),
|
||||||
params.NewParamSetPair(KeyDebtThreshold, &p.DebtAuctionThreshold, validateDebtAuctionThresholdParam),
|
params.NewParamSetPair(KeyDebtThreshold, &p.DebtAuctionThreshold, validateDebtAuctionThresholdParam),
|
||||||
params.NewParamSetPair(KeyDebtLot, &p.DebtAuctionLot, validateDebtAuctionLotParam),
|
params.NewParamSetPair(KeyDebtLot, &p.DebtAuctionLot, validateDebtAuctionLotParam),
|
||||||
params.NewParamSetPair(KeyDistributionFrequency, &p.SavingsDistributionFrequency, validateSavingsDistributionFrequencyParam),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,10 +261,6 @@ func (p Params) Validate() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateSavingsDistributionFrequencyParam(p.SavingsDistributionFrequency); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.CollateralParams) == 0 { // default value OK
|
if len(p.CollateralParams) == 0 { // default value OK
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -381,6 +372,12 @@ func validateCollateralParams(i interface{}) error {
|
|||||||
if cp.StabilityFee.LT(sdk.OneDec()) || cp.StabilityFee.GT(stabilityFeeMax) {
|
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)
|
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
|
return nil
|
||||||
@ -395,9 +392,6 @@ func validateDebtParam(i interface{}) error {
|
|||||||
return fmt.Errorf("debt denom invalid %s", debtParam.Denom)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,16 +455,3 @@ func validateDebtAuctionLotParam(i interface{}) error {
|
|||||||
|
|
||||||
return nil
|
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 (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
@ -28,7 +27,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
surplusLot sdk.Int
|
surplusLot sdk.Int
|
||||||
debtThreshold sdk.Int
|
debtThreshold sdk.Int
|
||||||
debtLot sdk.Int
|
debtLot sdk.Int
|
||||||
distributionFreq time.Duration
|
|
||||||
breaker bool
|
breaker bool
|
||||||
}
|
}
|
||||||
type errArgs struct {
|
type errArgs struct {
|
||||||
@ -51,7 +49,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -75,7 +72,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -83,13 +82,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -113,7 +110,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -121,13 +120,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -151,7 +148,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -159,13 +158,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -189,7 +186,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Denom: "xrp",
|
Denom: "xrp",
|
||||||
@ -202,7 +201,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x21,
|
Prefix: 0x21,
|
||||||
SpotMarketID: "xrp:usd",
|
SpotMarketID: "xrp:usd",
|
||||||
LiquidationMarketID: "xrp:usd",
|
LiquidationMarketID: "xrp:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -210,13 +211,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -240,7 +239,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Denom: "xrp",
|
Denom: "xrp",
|
||||||
@ -253,7 +254,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x21,
|
Prefix: 0x21,
|
||||||
SpotMarketID: "xrp:usd",
|
SpotMarketID: "xrp:usd",
|
||||||
LiquidationMarketID: "xrp:usd",
|
LiquidationMarketID: "xrp:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -261,13 +264,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -291,7 +292,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Denom: "xrp",
|
Denom: "xrp",
|
||||||
@ -304,7 +307,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x21,
|
Prefix: 0x21,
|
||||||
SpotMarketID: "xrp:usd",
|
SpotMarketID: "xrp:usd",
|
||||||
LiquidationMarketID: "xrp:usd",
|
LiquidationMarketID: "xrp:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -312,13 +317,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -341,7 +344,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -349,13 +354,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -379,7 +382,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "",
|
SpotMarketID: "",
|
||||||
LiquidationMarketID: "",
|
LiquidationMarketID: "",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -387,13 +392,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -417,7 +420,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Denom: "bnb",
|
Denom: "bnb",
|
||||||
@ -430,7 +435,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x21,
|
Prefix: 0x21,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -438,13 +445,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -468,7 +473,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Denom: "bnb",
|
Denom: "bnb",
|
||||||
@ -481,7 +488,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x21,
|
Prefix: 0x21,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -489,13 +498,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -519,7 +526,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Denom: "xrp",
|
Denom: "xrp",
|
||||||
@ -532,7 +541,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "xrp:usd",
|
SpotMarketID: "xrp:usd",
|
||||||
LiquidationMarketID: "xrp:usd",
|
LiquidationMarketID: "xrp:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -540,13 +551,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -570,7 +579,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -578,13 +589,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -608,7 +617,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -616,13 +627,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -646,7 +655,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -654,13 +665,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -684,7 +693,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -692,13 +703,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -722,7 +731,9 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
SpotMarketID: "bnb:usd",
|
SpotMarketID: "bnb:usd",
|
||||||
LiquidationMarketID: "bnb:usd",
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
ConversionFactor: sdk.NewInt(8),
|
ConversionFactor: sdk.NewInt(8),
|
||||||
|
CheckCollateralizationIndexCount: sdk.NewInt(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
debtParam: types.DebtParam{
|
debtParam: types.DebtParam{
|
||||||
@ -730,13 +741,11 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: sdk.NewInt(6),
|
ConversionFactor: sdk.NewInt(6),
|
||||||
DebtFloor: sdk.NewInt(10000000),
|
DebtFloor: sdk.NewInt(10000000),
|
||||||
SavingsRate: sdk.MustNewDecFromStr("0.95"),
|
|
||||||
},
|
},
|
||||||
surplusThreshold: types.DefaultSurplusThreshold,
|
surplusThreshold: types.DefaultSurplusThreshold,
|
||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -744,44 +753,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
contains: "debt denom invalid",
|
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",
|
name: "nil debt limit",
|
||||||
args: args{
|
args: args{
|
||||||
@ -792,7 +763,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -800,24 +770,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
contains: "invalid coins: global debt limit",
|
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",
|
name: "zero surplus auction threshold",
|
||||||
args: args{
|
args: args{
|
||||||
@ -828,7 +780,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -846,7 +797,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: sdk.ZeroInt(),
|
debtThreshold: sdk.ZeroInt(),
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -864,7 +814,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
surplusLot: sdk.ZeroInt(),
|
surplusLot: sdk.ZeroInt(),
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: types.DefaultDebtLot,
|
debtLot: types.DefaultDebtLot,
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -882,7 +831,6 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
surplusLot: types.DefaultSurplusLot,
|
surplusLot: types.DefaultSurplusLot,
|
||||||
debtThreshold: types.DefaultDebtThreshold,
|
debtThreshold: types.DefaultDebtThreshold,
|
||||||
debtLot: sdk.ZeroInt(),
|
debtLot: sdk.ZeroInt(),
|
||||||
distributionFreq: types.DefaultSavingsDistributionFrequency,
|
|
||||||
breaker: types.DefaultCircuitBreaker,
|
breaker: types.DefaultCircuitBreaker,
|
||||||
},
|
},
|
||||||
errArgs: errArgs{
|
errArgs: errArgs{
|
||||||
@ -893,7 +841,7 @@ func (suite *ParamsTestSuite) TestParamValidation() {
|
|||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
suite.Run(tc.name, func() {
|
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()
|
err := params.Validate()
|
||||||
if tc.errArgs.expectPass {
|
if tc.errArgs.expectPass {
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
@ -68,7 +68,6 @@ func (suite *PermissionTestSuite) TestSubParamChangePermission_Allows() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
DebtFloor: i(10000000),
|
DebtFloor: i(10000000),
|
||||||
SavingsRate: d("0.95"),
|
|
||||||
}
|
}
|
||||||
testDPUpdatedDebtFloor := testDP
|
testDPUpdatedDebtFloor := testDP
|
||||||
testDPUpdatedDebtFloor.DebtFloor = i(1000)
|
testDPUpdatedDebtFloor.DebtFloor = i(1000)
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
upgrade "github.com/cosmos/cosmos-sdk/x/upgrade"
|
upgrade "github.com/cosmos/cosmos-sdk/x/upgrade"
|
||||||
|
|
||||||
bep3types "github.com/kava-labs/kava/x/bep3/types"
|
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"
|
"github.com/kava-labs/kava/x/pricefeed"
|
||||||
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
|
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
upgrade "github.com/cosmos/cosmos-sdk/x/upgrade"
|
upgrade "github.com/cosmos/cosmos-sdk/x/upgrade"
|
||||||
|
|
||||||
bep3types "github.com/kava-labs/kava/x/bep3/types"
|
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"
|
"github.com/kava-labs/kava/x/pricefeed"
|
||||||
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
|
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
|
||||||
)
|
)
|
||||||
|
@ -542,7 +542,6 @@ func (suite *PermissionsTestSuite) TestAllowedDebtParam_Allows() {
|
|||||||
ReferenceAsset: "usd",
|
ReferenceAsset: "usd",
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
DebtFloor: i(10000000),
|
DebtFloor: i(10000000),
|
||||||
SavingsRate: d("0.95"),
|
|
||||||
}
|
}
|
||||||
newDenomDP := testDP
|
newDenomDP := testDP
|
||||||
newDenomDP.Denom = "usdz"
|
newDenomDP.Denom = "usdz"
|
||||||
@ -565,7 +564,6 @@ func (suite *PermissionsTestSuite) TestAllowedDebtParam_Allows() {
|
|||||||
name: "allowed change",
|
name: "allowed change",
|
||||||
allowed: AllowedDebtParam{
|
allowed: AllowedDebtParam{
|
||||||
DebtFloor: true,
|
DebtFloor: true,
|
||||||
SavingsRate: true,
|
|
||||||
},
|
},
|
||||||
current: testDP,
|
current: testDP,
|
||||||
incoming: newDebtFloorDP,
|
incoming: newDebtFloorDP,
|
||||||
@ -575,7 +573,6 @@ func (suite *PermissionsTestSuite) TestAllowedDebtParam_Allows() {
|
|||||||
name: "un-allowed change",
|
name: "un-allowed change",
|
||||||
allowed: AllowedDebtParam{
|
allowed: AllowedDebtParam{
|
||||||
DebtFloor: true,
|
DebtFloor: true,
|
||||||
SavingsRate: true,
|
|
||||||
},
|
},
|
||||||
current: testDP,
|
current: testDP,
|
||||||
incoming: newDenomDP,
|
incoming: newDenomDP,
|
||||||
@ -585,7 +582,6 @@ func (suite *PermissionsTestSuite) TestAllowedDebtParam_Allows() {
|
|||||||
name: "allowed no change",
|
name: "allowed no change",
|
||||||
allowed: AllowedDebtParam{
|
allowed: AllowedDebtParam{
|
||||||
DebtFloor: true,
|
DebtFloor: true,
|
||||||
SavingsRate: true,
|
|
||||||
},
|
},
|
||||||
current: testDP,
|
current: testDP,
|
||||||
incoming: testDP, // no change
|
incoming: testDP, // no change
|
||||||
@ -595,7 +591,6 @@ func (suite *PermissionsTestSuite) TestAllowedDebtParam_Allows() {
|
|||||||
name: "un-allowed change with allowed change",
|
name: "un-allowed change with allowed change",
|
||||||
allowed: AllowedDebtParam{
|
allowed: AllowedDebtParam{
|
||||||
DebtFloor: true,
|
DebtFloor: true,
|
||||||
SavingsRate: true,
|
|
||||||
},
|
},
|
||||||
current: testDP,
|
current: testDP,
|
||||||
incoming: newDenomAndDebtFloorDP,
|
incoming: newDenomAndDebtFloorDP,
|
||||||
|
@ -425,15 +425,13 @@ type AllowedDebtParam struct {
|
|||||||
ReferenceAsset bool `json:"reference_asset" yaml:"reference_asset"`
|
ReferenceAsset bool `json:"reference_asset" yaml:"reference_asset"`
|
||||||
ConversionFactor bool `json:"conversion_factor" yaml:"conversion_factor"`
|
ConversionFactor bool `json:"conversion_factor" yaml:"conversion_factor"`
|
||||||
DebtFloor bool `json:"debt_floor" yaml:"debt_floor"`
|
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 {
|
func (adp AllowedDebtParam) Allows(current, incoming cdptypes.DebtParam) bool {
|
||||||
allowed := ((current.Denom == incoming.Denom) || adp.Denom) &&
|
allowed := ((current.Denom == incoming.Denom) || adp.Denom) &&
|
||||||
((current.ReferenceAsset == incoming.ReferenceAsset) || adp.ReferenceAsset) &&
|
((current.ReferenceAsset == incoming.ReferenceAsset) || adp.ReferenceAsset) &&
|
||||||
(current.ConversionFactor.Equal(incoming.ConversionFactor) || adp.ConversionFactor) &&
|
(current.ConversionFactor.Equal(incoming.ConversionFactor) || adp.ConversionFactor) &&
|
||||||
(current.DebtFloor.Equal(incoming.DebtFloor) || adp.DebtFloor) &&
|
(current.DebtFloor.Equal(incoming.DebtFloor) || adp.DebtFloor)
|
||||||
(current.SavingsRate.Equal(incoming.SavingsRate) || adp.SavingsRate)
|
|
||||||
return allowed
|
return allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package v0_12
|
package v0_11
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -10,6 +10,22 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/x/params"
|
"github.com/cosmos/cosmos-sdk/x/params"
|
||||||
|
|
||||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
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
|
// Parameter keys and default values
|
||||||
@ -21,10 +37,58 @@ var (
|
|||||||
DefaultGovSchedules = DistributionSchedules{}
|
DefaultGovSchedules = DistributionSchedules{}
|
||||||
DefaultLPSchedules = DistributionSchedules{}
|
DefaultLPSchedules = DistributionSchedules{}
|
||||||
DefaultDelegatorSchedules = DelegatorDistributionSchedules{}
|
DefaultDelegatorSchedules = DelegatorDistributionSchedules{}
|
||||||
|
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
|
||||||
|
DefaultDistributionTimes = GenesisDistributionTimes{}
|
||||||
GovDenom = cdptypes.DefaultGovDenom
|
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 {
|
type Params struct {
|
||||||
Active bool `json:"active" yaml:"active"`
|
Active bool `json:"active" yaml:"active"`
|
||||||
LiquidityProviderSchedules DistributionSchedules `json:"liquidity_provider_schedules" yaml:"liquidity_provider_schedules"`
|
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 {
|
func DefaultParams() Params {
|
||||||
return NewParams(DefaultActive, DefaultLPSchedules, DefaultDelegatorSchedules)
|
return NewParams(DefaultActive, DefaultLPSchedules, DefaultDelegatorSchedules)
|
||||||
}
|
}
|
||||||
@ -342,7 +406,7 @@ func (dt DepositType) IsValid() error {
|
|||||||
return fmt.Errorf("invalid deposit type: %s", dt)
|
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 {
|
type Deposit struct {
|
||||||
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"`
|
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"`
|
||||||
Amount sdk.Coin `json:"amount" yaml:"amount"`
|
Amount sdk.Coin `json:"amount" yaml:"amount"`
|
@ -8,7 +8,10 @@ import (
|
|||||||
|
|
||||||
// BeginBlocker runs at the start of every block
|
// BeginBlocker runs at the start of every block
|
||||||
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
|
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
|
||||||
k.DeleteExpiredClaimsAndClaimPeriods(ctx)
|
for _, rp := range k.GetParams(ctx).RewardPeriods {
|
||||||
k.ApplyRewardsToCdps(ctx)
|
err := k.AccumulateRewards(ctx, rp)
|
||||||
k.CreateAndDeleteRewardPeriods(ctx)
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,77 +35,76 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// function aliases
|
// function aliases
|
||||||
|
CalculateTimeElapsed = keeper.CalculateTimeElapsed
|
||||||
NewKeeper = keeper.NewKeeper
|
NewKeeper = keeper.NewKeeper
|
||||||
NewQuerier = keeper.NewQuerier
|
NewQuerier = keeper.NewQuerier
|
||||||
BytesToUint64 = types.BytesToUint64
|
|
||||||
DefaultGenesisState = types.DefaultGenesisState
|
DefaultGenesisState = types.DefaultGenesisState
|
||||||
DefaultParams = types.DefaultParams
|
DefaultParams = types.DefaultParams
|
||||||
GetClaimPeriodPrefix = types.GetClaimPeriodPrefix
|
|
||||||
GetClaimPrefix = types.GetClaimPrefix
|
|
||||||
GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength
|
GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength
|
||||||
NewAugmentedClaim = types.NewAugmentedClaim
|
NewGenesisAccumulationTime = types.NewGenesisAccumulationTime
|
||||||
NewClaim = types.NewClaim
|
|
||||||
NewClaimPeriod = types.NewClaimPeriod
|
|
||||||
NewGenesisState = types.NewGenesisState
|
NewGenesisState = types.NewGenesisState
|
||||||
NewMsgClaimReward = types.NewMsgClaimReward
|
NewMsgClaimUSDXMintingReward = types.NewMsgClaimUSDXMintingReward
|
||||||
NewMultiplier = types.NewMultiplier
|
NewMultiplier = types.NewMultiplier
|
||||||
NewParams = types.NewParams
|
NewParams = types.NewParams
|
||||||
NewPeriod = types.NewPeriod
|
NewPeriod = types.NewPeriod
|
||||||
NewQueryClaimsParams = types.NewQueryClaimsParams
|
NewQueryClaimsParams = types.NewQueryClaimsParams
|
||||||
NewReward = types.NewReward
|
NewRewardIndex = types.NewRewardIndex
|
||||||
NewRewardPeriod = types.NewRewardPeriod
|
NewRewardPeriod = types.NewRewardPeriod
|
||||||
NewRewardPeriodFromReward = types.NewRewardPeriodFromReward
|
NewUSDXMintingClaim = types.NewUSDXMintingClaim
|
||||||
ParamKeyTable = types.ParamKeyTable
|
ParamKeyTable = types.ParamKeyTable
|
||||||
RegisterCodec = types.RegisterCodec
|
RegisterCodec = types.RegisterCodec
|
||||||
|
|
||||||
// variable aliases
|
// variable aliases
|
||||||
|
BlockTimeKey = types.BlockTimeKey
|
||||||
ClaimKeyPrefix = types.ClaimKeyPrefix
|
ClaimKeyPrefix = types.ClaimKeyPrefix
|
||||||
ClaimPeriodKeyPrefix = types.ClaimPeriodKeyPrefix
|
|
||||||
DefaultActive = types.DefaultActive
|
DefaultActive = types.DefaultActive
|
||||||
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime
|
DefaultClaimEnd = types.DefaultClaimEnd
|
||||||
DefaultRewards = types.DefaultRewards
|
DefaultClaims = types.DefaultClaims
|
||||||
|
DefaultGenesisAccumulationTimes = types.DefaultGenesisAccumulationTimes
|
||||||
|
DefaultMultipliers = types.DefaultMultipliers
|
||||||
|
DefaultRewardPeriods = types.DefaultRewardPeriods
|
||||||
ErrAccountNotFound = types.ErrAccountNotFound
|
ErrAccountNotFound = types.ErrAccountNotFound
|
||||||
|
ErrClaimExpired = types.ErrClaimExpired
|
||||||
ErrClaimNotFound = types.ErrClaimNotFound
|
ErrClaimNotFound = types.ErrClaimNotFound
|
||||||
ErrClaimPeriodNotFound = types.ErrClaimPeriodNotFound
|
|
||||||
ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance
|
ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance
|
||||||
ErrInvalidAccountType = types.ErrInvalidAccountType
|
ErrInvalidAccountType = types.ErrInvalidAccountType
|
||||||
ErrInvalidMultiplier = types.ErrInvalidMultiplier
|
ErrInvalidMultiplier = types.ErrInvalidMultiplier
|
||||||
ErrNoClaimsFound = types.ErrNoClaimsFound
|
ErrNoClaimsFound = types.ErrNoClaimsFound
|
||||||
|
ErrRewardPeriodNotFound = types.ErrRewardPeriodNotFound
|
||||||
ErrZeroClaim = types.ErrZeroClaim
|
ErrZeroClaim = types.ErrZeroClaim
|
||||||
GovDenom = types.GovDenom
|
GovDenom = types.GovDenom
|
||||||
IncentiveMacc = types.IncentiveMacc
|
IncentiveMacc = types.IncentiveMacc
|
||||||
KeyActive = types.KeyActive
|
KeyActive = types.KeyActive
|
||||||
|
KeyClaimEnd = types.KeyClaimEnd
|
||||||
|
KeyMultipliers = types.KeyMultipliers
|
||||||
KeyRewards = types.KeyRewards
|
KeyRewards = types.KeyRewards
|
||||||
ModuleCdc = types.ModuleCdc
|
ModuleCdc = types.ModuleCdc
|
||||||
NextClaimPeriodIDPrefix = types.NextClaimPeriodIDPrefix
|
|
||||||
PreviousBlockTimeKey = types.PreviousBlockTimeKey
|
|
||||||
PrincipalDenom = types.PrincipalDenom
|
PrincipalDenom = types.PrincipalDenom
|
||||||
RewardPeriodKeyPrefix = types.RewardPeriodKeyPrefix
|
RewardFactorKey = types.RewardFactorKey
|
||||||
|
USDXMintingRewardDenom = types.USDXMintingRewardDenom
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
Hooks = keeper.Hooks
|
||||||
Keeper = keeper.Keeper
|
Keeper = keeper.Keeper
|
||||||
AccountKeeper = types.AccountKeeper
|
AccountKeeper = types.AccountKeeper
|
||||||
AugmentedClaim = types.AugmentedClaim
|
CDPHooks = types.CDPHooks
|
||||||
AugmentedClaims = types.AugmentedClaims
|
|
||||||
CdpKeeper = types.CdpKeeper
|
CdpKeeper = types.CdpKeeper
|
||||||
Claim = types.Claim
|
GenesisAccumulationTime = types.GenesisAccumulationTime
|
||||||
ClaimPeriod = types.ClaimPeriod
|
GenesisAccumulationTimes = types.GenesisAccumulationTimes
|
||||||
ClaimPeriods = types.ClaimPeriods
|
|
||||||
Claims = types.Claims
|
|
||||||
GenesisClaimPeriodID = types.GenesisClaimPeriodID
|
|
||||||
GenesisClaimPeriodIDs = types.GenesisClaimPeriodIDs
|
|
||||||
GenesisState = types.GenesisState
|
GenesisState = types.GenesisState
|
||||||
MsgClaimReward = types.MsgClaimReward
|
MsgClaimUSDXMintingReward = types.MsgClaimUSDXMintingReward
|
||||||
Multiplier = types.Multiplier
|
Multiplier = types.Multiplier
|
||||||
MultiplierName = types.MultiplierName
|
MultiplierName = types.MultiplierName
|
||||||
Multipliers = types.Multipliers
|
Multipliers = types.Multipliers
|
||||||
Params = types.Params
|
Params = types.Params
|
||||||
PostClaimReq = types.PostClaimReq
|
PostClaimReq = types.PostClaimReq
|
||||||
QueryClaimsParams = types.QueryClaimsParams
|
QueryClaimsParams = types.QueryClaimsParams
|
||||||
Reward = types.Reward
|
RewardIndex = types.RewardIndex
|
||||||
|
RewardIndexes = types.RewardIndexes
|
||||||
RewardPeriod = types.RewardPeriod
|
RewardPeriod = types.RewardPeriod
|
||||||
RewardPeriods = types.RewardPeriods
|
RewardPeriods = types.RewardPeriods
|
||||||
Rewards = types.Rewards
|
|
||||||
SupplyKeeper = types.SupplyKeeper
|
SupplyKeeper = types.SupplyKeeper
|
||||||
|
USDXMintingClaim = types.USDXMintingClaim
|
||||||
|
USDXMintingClaims = types.USDXMintingClaims
|
||||||
)
|
)
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
@ -25,35 +26,42 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|||||||
incentiveQueryCmd.AddCommand(flags.GetCommands(
|
incentiveQueryCmd.AddCommand(flags.GetCommands(
|
||||||
queryParamsCmd(queryRoute, cdc),
|
queryParamsCmd(queryRoute, cdc),
|
||||||
queryClaimsCmd(queryRoute, cdc),
|
queryClaimsCmd(queryRoute, cdc),
|
||||||
queryRewardPeriodsCmd(queryRoute, cdc),
|
|
||||||
queryClaimPeriodsCmd(queryRoute, cdc),
|
|
||||||
)...)
|
)...)
|
||||||
|
|
||||||
return incentiveQueryCmd
|
return incentiveQueryCmd
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagOwner = "owner"
|
||||||
|
)
|
||||||
|
|
||||||
func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||||
return &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "claims [owner-addr] [collateral-type]",
|
Use: "claims ",
|
||||||
Short: "get claims by owner and collateral-type",
|
Short: "query USDX minting claims",
|
||||||
Long: strings.TrimSpace(
|
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:
|
Example:
|
||||||
$ %s query %s claims kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw bnb-a`, version.ClientName, types.ModuleName)),
|
$ %s query %s claims
|
||||||
Args: cobra.ExactArgs(2),
|
$ %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 {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||||
|
|
||||||
|
strOwner := viper.GetString(flagOwner)
|
||||||
|
page := viper.GetInt(flags.FlagPage)
|
||||||
|
limit := viper.GetInt(flags.FlagLimit)
|
||||||
|
|
||||||
// Prepare params for querier
|
// Prepare params for querier
|
||||||
ownerAddress, err := sdk.AccAddressFromBech32(args[0])
|
owner, err := sdk.AccAddressFromBech32(strOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bz, err := cdc.MarshalJSON(types.QueryClaimsParams{
|
params := types.NewQueryClaimsParams(page, limit, owner)
|
||||||
Owner: ownerAddress,
|
bz, err := cdc.MarshalJSON(params)
|
||||||
CollateralType: args[1],
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -66,7 +74,7 @@ func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|||||||
}
|
}
|
||||||
cliCtx = cliCtx.WithHeight(height)
|
cliCtx = cliCtx.WithHeight(height)
|
||||||
|
|
||||||
var claims types.AugmentedClaims
|
var claims types.USDXMintingClaims
|
||||||
if err := cdc.UnmarshalJSON(res, &claims); err != nil {
|
if err := cdc.UnmarshalJSON(res, &claims); err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal claims: %w", err)
|
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 {
|
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 {
|
func getCmdClaim(cdc *codec.Codec) *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "claim [owner] [collateral-type] [multiplier]",
|
Use: "claim [owner] [multiplier]",
|
||||||
Short: "claim rewards for cdp owner and collateral-type",
|
Short: "claim rewards for cdp owner and collateral-type",
|
||||||
Long: strings.TrimSpace(
|
Long: strings.TrimSpace(
|
||||||
fmt.Sprintf(`Claim any outstanding rewards owned by owner for the input collateral-type and multiplier,
|
fmt.Sprintf(`Claim any outstanding rewards owned by owner for the input collateral-type and multiplier,
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
$ %s tx %s claim kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw bnb-a large
|
$ %s tx %s claim kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw large
|
||||||
`, version.ClientName, types.ModuleName),
|
`, version.ClientName, types.ModuleName),
|
||||||
),
|
),
|
||||||
Args: cobra.ExactArgs(3),
|
Args: cobra.ExactArgs(2),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||||
cliCtx := context.NewCLIContextWithInputAndFrom(inBuf, args[0]).WithCodec(cdc)
|
cliCtx := context.NewCLIContextWithInputAndFrom(inBuf, args[0]).WithCodec(cdc)
|
||||||
@ -54,7 +54,7 @@ func getCmdClaim(cdc *codec.Codec) *cobra.Command {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := types.NewMsgClaimReward(owner, args[1], args[2])
|
msg := types.NewMsgClaimUSDXMintingReward(owner, args[1])
|
||||||
err = msg.ValidateBasic()
|
err = msg.ValidateBasic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -3,6 +3,7 @@ package rest
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
@ -14,30 +15,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
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/claims", types.ModuleName), 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/parameters", types.ModuleName), queryParamsHandlerFn(cliCtx)).Methods("GET")
|
r.HandleFunc(fmt.Sprintf("/%s/parameters", types.ModuleName), queryParamsHandlerFn(cliCtx)).Methods("GET")
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryClaimsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
func queryClaimsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
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)
|
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
var owner sdk.AccAddress
|
||||||
ownerBech32 := vars[types.RestClaimOwner]
|
if x := r.URL.Query().Get(types.RestClaimOwner); len(x) != 0 {
|
||||||
denom := vars[types.RestClaimCollateralType]
|
ownerStr := strings.ToLower(strings.TrimSpace(x))
|
||||||
|
owner, err = sdk.AccAddressFromBech32(ownerStr)
|
||||||
owner, err := sdk.AccAddressFromBech32(ownerBech32)
|
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
queryParams := types.NewQueryClaimsParams(owner, denom)
|
queryParams := types.NewQueryClaimsParams(page, limit, owner)
|
||||||
bz, err := cliCtx.Codec.MarshalJSON(queryParams)
|
bz, err := cliCtx.Codec.MarshalJSON(queryParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err))
|
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 {
|
func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||||
|
@ -42,7 +42,7 @@ func postClaimHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := types.NewMsgClaimReward(requestBody.Sender, requestBody.CollateralType, requestBody.MultiplierName)
|
msg := types.NewMsgClaimUSDXMintingReward(requestBody.Sender, requestBody.MultiplierName)
|
||||||
if err := msg.ValidateBasic(); err != nil {
|
if err := msg.ValidateBasic(); err != nil {
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
|
@ -24,51 +24,37 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep
|
|||||||
|
|
||||||
k.SetParams(ctx, gs.Params)
|
k.SetParams(ctx, gs.Params)
|
||||||
|
|
||||||
for _, r := range gs.Params.Rewards {
|
for _, gat := range gs.PreviousAccumulationTimes {
|
||||||
k.SetNextClaimPeriodID(ctx, r.CollateralType, 1)
|
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
|
for _, claim := range gs.USDXMintingClaims {
|
||||||
if !gs.PreviousBlockTime.Equal(types.DefaultPreviousBlockTime) {
|
k.SetClaim(ctx, claim)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportGenesis export genesis state for incentive module
|
// ExportGenesis export genesis state for incentive module
|
||||||
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState {
|
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState {
|
||||||
// get all objects out of the store
|
|
||||||
params := k.GetParams(ctx)
|
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
|
claims := k.GetAllClaims(ctx)
|
||||||
// immediately after InitGenesis, there would be no previousBlockTime value.
|
|
||||||
|
var gats GenesisAccumulationTimes
|
||||||
|
|
||||||
|
for _, rp := range params.RewardPeriods {
|
||||||
|
pat, found := k.GetPreviousAccrualTime(ctx, rp.CollateralType)
|
||||||
if !found {
|
if !found {
|
||||||
previousBlockTime = types.DefaultPreviousBlockTime
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all objects from the store
|
return types.NewGenesisState(params, gats, claims)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package incentive
|
package incentive
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
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) {
|
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||||
ctx = ctx.WithEventManager(sdk.NewEventManager())
|
ctx = ctx.WithEventManager(sdk.NewEventManager())
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case types.MsgClaimReward:
|
case types.MsgClaimUSDXMintingReward:
|
||||||
return handleMsgClaimReward(ctx, k, msg)
|
return handleMsgClaimUSDXMintingReward(ctx, k, msg)
|
||||||
default:
|
default:
|
||||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
|
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)
|
err := k.ClaimReward(ctx, msg.Sender, types.MultiplierName(msg.MultiplierName))
|
||||||
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)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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{
|
return &sdk.Result{
|
||||||
Events: ctx.EventManager().Events(),
|
Events: ctx.EventManager().Events(),
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/kava-labs/kava/app"
|
"github.com/kava-labs/kava/app"
|
||||||
"github.com/kava-labs/kava/x/incentive"
|
"github.com/kava-labs/kava/x/incentive"
|
||||||
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
"github.com/kava-labs/kava/x/kavadist"
|
"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)))
|
coins = append(coins, cs(c("bnb", 10000000000), c("ukava", 10000000000)))
|
||||||
}
|
}
|
||||||
authGS := app.NewAuthGenState(addrs, coins)
|
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.addrs = addrs
|
||||||
suite.handler = incentive.NewHandler(keeper)
|
suite.handler = incentive.NewHandler(keeper)
|
||||||
@ -51,15 +61,10 @@ func (suite *HandlerTestSuite) SetupTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *HandlerTestSuite) addClaim() {
|
func (suite *HandlerTestSuite) addClaim() {
|
||||||
supplyKeeper := suite.app.GetSupplyKeeper()
|
sk := suite.app.GetSupplyKeeper()
|
||||||
macc := supplyKeeper.GetModuleAccount(suite.ctx, kavadist.ModuleName)
|
err := sk.MintCoins(suite.ctx, kavadist.ModuleName, cs(c("ukava", 1000000000000)))
|
||||||
err := supplyKeeper.MintCoins(suite.ctx, macc.GetName(), cs(c("ukava", 1000000)))
|
|
||||||
suite.Require().NoError(err)
|
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"))})
|
c1 := incentive.NewUSDXMintingClaim(suite.addrs[0], c("ukava", 1000000), types.RewardIndexes{types.NewRewardIndex("bnb-s", sdk.ZeroDec())})
|
||||||
suite.NotPanics(func() {
|
|
||||||
suite.keeper.SetClaimPeriod(suite.ctx, cp)
|
|
||||||
})
|
|
||||||
c1 := incentive.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1)
|
|
||||||
suite.NotPanics(func() {
|
suite.NotPanics(func() {
|
||||||
suite.keeper.SetClaim(suite.ctx, c1)
|
suite.keeper.SetClaim(suite.ctx, c1)
|
||||||
})
|
})
|
||||||
@ -67,7 +72,7 @@ func (suite *HandlerTestSuite) addClaim() {
|
|||||||
|
|
||||||
func (suite *HandlerTestSuite) TestMsgClaimReward() {
|
func (suite *HandlerTestSuite) TestMsgClaimReward() {
|
||||||
suite.addClaim()
|
suite.addClaim()
|
||||||
msg := incentive.NewMsgClaimReward(suite.addrs[0], "bnb", "small")
|
msg := incentive.NewMsgClaimUSDXMintingReward(suite.addrs[0], "small")
|
||||||
res, err := suite.handler(suite.ctx, msg)
|
res, err := suite.handler(suite.ctx, msg)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Require().NotNil(res)
|
suite.Require().NotNil(res)
|
||||||
@ -75,3 +80,7 @@ func (suite *HandlerTestSuite) TestMsgClaimReward() {
|
|||||||
func TestHandlerTestSuite(t *testing.T) {
|
func TestHandlerTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(HandlerTestSuite))
|
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
|
// 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)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix)
|
||||||
bz := store.Get(types.GetClaimPrefix(addr, collateralType, id))
|
bz := store.Get(addr)
|
||||||
if bz == nil {
|
if bz == nil {
|
||||||
return types.Claim{}, false
|
return types.USDXMintingClaim{}, false
|
||||||
}
|
}
|
||||||
var c types.Claim
|
var c types.USDXMintingClaim
|
||||||
k.cdc.MustUnmarshalBinaryBare(bz, &c)
|
k.cdc.MustUnmarshalBinaryBare(bz, &c)
|
||||||
return c, true
|
return c, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetClaim sets the claim in the store corresponding to the input address, collateral type, and id
|
// 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)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix)
|
||||||
bz := k.cdc.MustMarshalBinaryBare(c)
|
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
|
// 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 := 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
|
// 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)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix)
|
||||||
iterator := sdk.KVStorePrefixIterator(store, []byte{})
|
iterator := sdk.KVStorePrefixIterator(store, []byte{})
|
||||||
defer iterator.Close()
|
defer iterator.Close()
|
||||||
for ; iterator.Valid(); iterator.Next() {
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
var c types.Claim
|
var c types.USDXMintingClaim
|
||||||
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &c)
|
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &c)
|
||||||
if cb(c) {
|
if cb(c) {
|
||||||
break
|
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
|
// GetAllClaims returns all Claim objects in the store
|
||||||
func (k Keeper) GetAllClaims(ctx sdk.Context) types.Claims {
|
func (k Keeper) GetAllClaims(ctx sdk.Context) types.USDXMintingClaims {
|
||||||
cs := types.Claims{}
|
cs := types.USDXMintingClaims{}
|
||||||
k.IterateClaims(ctx, func(c types.Claim) (stop bool) {
|
k.IterateClaims(ctx, func(c types.USDXMintingClaim) (stop bool) {
|
||||||
cs = append(cs, c)
|
cs = append(cs, c)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
return cs
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPreviousBlockTime get the blocktime for the previous block
|
// GetPreviousAccrualTime returns the last time a collateral type accrued rewards
|
||||||
func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time, found bool) {
|
func (k Keeper) GetPreviousAccrualTime(ctx sdk.Context, ctype string) (blockTime time.Time, found bool) {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.BlockTimeKey)
|
||||||
b := store.Get([]byte{})
|
bz := store.Get([]byte(ctype))
|
||||||
if b == nil {
|
if bz == nil {
|
||||||
return time.Time{}, false
|
return time.Time{}, false
|
||||||
}
|
}
|
||||||
k.cdc.MustUnmarshalBinaryBare(b, &blockTime)
|
k.cdc.MustUnmarshalBinaryBare(bz, &blockTime)
|
||||||
return blockTime, true
|
return blockTime, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPreviousBlockTime set the time of the previous block
|
// SetPreviousAccrualTime sets the last time a collateral type accrued rewards
|
||||||
func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) {
|
func (k Keeper) SetPreviousAccrualTime(ctx sdk.Context, ctype string, blockTime time.Time) {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.BlockTimeKey)
|
||||||
store.Set([]byte{}, k.cdc.MustMarshalBinaryBare(blockTime))
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
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"
|
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
@ -33,7 +34,7 @@ func (suite *KeeperTestSuite) SetupTest() {
|
|||||||
tApp := app.NewTestApp()
|
tApp := app.NewTestApp()
|
||||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||||
tApp.InitializeFromGenesisStates()
|
tApp.InitializeFromGenesisStates()
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||||
keeper := tApp.GetIncentiveKeeper()
|
keeper := tApp.GetIncentiveKeeper()
|
||||||
suite.app = tApp
|
suite.app = tApp
|
||||||
suite.ctx = ctx
|
suite.ctx = ctx
|
||||||
@ -51,129 +52,63 @@ func (suite *KeeperTestSuite) getModuleAccount(name string) supplyexported.Modul
|
|||||||
return sk.GetModuleAccount(suite.ctx, name)
|
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() {
|
func (suite *KeeperTestSuite) TestGetSetDeleteClaim() {
|
||||||
c := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1)
|
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], "bnb", 1)
|
_, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||||
suite.False(found)
|
suite.Require().False(found)
|
||||||
suite.NotPanics(func() {
|
suite.Require().NotPanics(func() {
|
||||||
suite.keeper.SetClaim(suite.ctx, c)
|
suite.keeper.SetClaim(suite.ctx, c)
|
||||||
})
|
})
|
||||||
testC, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1)
|
testC, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||||
suite.True(found)
|
suite.Require().True(found)
|
||||||
suite.Equal(c, testC)
|
suite.Require().Equal(c, testC)
|
||||||
suite.NotPanics(func() {
|
suite.Require().NotPanics(func() {
|
||||||
suite.keeper.DeleteClaim(suite.ctx, suite.addrs[0], "bnb", 1)
|
suite.keeper.DeleteClaim(suite.ctx, suite.addrs[0])
|
||||||
})
|
})
|
||||||
_, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1)
|
_, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||||
suite.False(found)
|
suite.Require().False(found)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *KeeperTestSuite) TestIterateMethods() {
|
func (suite *KeeperTestSuite) TestIterateClaims() {
|
||||||
suite.addObjectsToStore() // adds 2 objects of each type to the store
|
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())})
|
||||||
var rewardPeriods types.RewardPeriods
|
suite.Require().NotPanics(func() {
|
||||||
suite.keeper.IterateRewardPeriods(suite.ctx, func(rp types.RewardPeriod) (stop bool) {
|
suite.keeper.SetClaim(suite.ctx, c)
|
||||||
rewardPeriods = append(rewardPeriods, rp)
|
|
||||||
return false
|
|
||||||
})
|
})
|
||||||
suite.Equal(2, len(rewardPeriods))
|
}
|
||||||
|
claims := types.USDXMintingClaims{}
|
||||||
var claimPeriods types.ClaimPeriods
|
suite.keeper.IterateClaims(suite.ctx, func(c types.USDXMintingClaim) bool {
|
||||||
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 = append(claims, c)
|
claims = append(claims, c)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
suite.Equal(2, len(claims))
|
suite.Require().Equal(len(suite.addrs), 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)
|
|
||||||
|
|
||||||
|
claims = suite.keeper.GetAllClaims(suite.ctx)
|
||||||
|
suite.Require().Equal(len(suite.addrs), len(claims))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeeperTestSuite(t *testing.T) {
|
func TestKeeperTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(KeeperTestSuite))
|
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
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/incentive/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) {
|
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
|
||||||
k.paramSubspace.SetParamSet(ctx, ¶ms)
|
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
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
@ -14,42 +12,48 @@ import (
|
|||||||
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PayoutClaim sends the timelocked claim coins to the input address
|
// ClaimReward sends the reward amount to the input address and zero's out the claim in the store
|
||||||
func (k Keeper) PayoutClaim(ctx sdk.Context, addr sdk.AccAddress, collateralType string, id uint64, multiplierName types.MultiplierName) error {
|
func (k Keeper) ClaimReward(ctx sdk.Context, addr sdk.AccAddress, multiplierName types.MultiplierName) error {
|
||||||
claim, found := k.GetClaim(ctx, addr, collateralType, id)
|
claim, found := k.GetClaim(ctx, addr)
|
||||||
if !found {
|
if !found {
|
||||||
return sdkerrors.Wrapf(types.ErrClaimNotFound, "id: %d, collateral type %s, address: %s", id, collateralType, addr)
|
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", addr)
|
||||||
}
|
|
||||||
claimPeriod, found := k.GetClaimPeriod(ctx, id, collateralType)
|
|
||||||
if !found {
|
|
||||||
return sdkerrors.Wrapf(types.ErrClaimPeriodNotFound, "id: %d, collateral type: %s", id, collateralType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
multiplier, found := claimPeriod.GetMultiplier(multiplierName)
|
multiplier, found := k.GetMultiplier(ctx, multiplierName)
|
||||||
if !found {
|
if !found {
|
||||||
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, string(multiplierName))
|
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() {
|
if rewardAmount.IsZero() {
|
||||||
return types.ErrZeroClaim
|
return types.ErrZeroClaim
|
||||||
}
|
}
|
||||||
rewardCoin := sdk.NewCoin(claim.Reward.Denom, rewardAmount)
|
rewardCoin := sdk.NewCoin(claim.Reward.Denom, rewardAmount)
|
||||||
length := ctx.BlockTime().AddDate(0, int(multiplier.MonthsLockup), 0).Unix() - ctx.BlockTime().Unix()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
k.DeleteClaim(ctx, addr, collateralType, id)
|
k.ZeroClaim(ctx, claim)
|
||||||
|
|
||||||
ctx.EventManager().EmitEvent(
|
ctx.EventManager().EmitEvent(
|
||||||
sdk.NewEvent(
|
sdk.NewEvent(
|
||||||
types.EventTypeClaim,
|
types.EventTypeClaim,
|
||||||
sdk.NewAttribute(types.AttributeKeyClaimedBy, addr.String()),
|
sdk.NewAttribute(types.AttributeKeyClaimedBy, addr.String()),
|
||||||
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.Reward.String()),
|
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.Reward.String()),
|
||||||
sdk.NewAttribute(types.AttributeKeyClaimPeriod, fmt.Sprintf("%d", claim.ClaimPeriodID)),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
@ -112,75 +116,6 @@ func (k Keeper) SendTimeLockedCoinsToBaseAccount(ctx sdk.Context, senderModule s
|
|||||||
return nil
|
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
|
// 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
|
// the input address must be a periodic vesting account
|
||||||
func (k Keeper) addCoinsToVestingSchedule(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, length int64) {
|
func (k Keeper) addCoinsToVestingSchedule(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, length int64) {
|
||||||
|
@ -2,119 +2,154 @@ package keeper_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
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/auth/vesting"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
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/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/incentive/types"
|
||||||
"github.com/kava-labs/kava/x/kavadist"
|
"github.com/kava-labs/kava/x/kavadist"
|
||||||
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||||
"github.com/tendermint/tendermint/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (suite *KeeperTestSuite) setupChain() {
|
func (suite *KeeperTestSuite) TestPayoutClaim() {
|
||||||
// creates a new app state with 4 funded addresses and 1 module account
|
type args struct {
|
||||||
tApp := app.NewTestApp()
|
ctype string
|
||||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: time.Unix(100, 0)})
|
rewardsPerSecond sdk.Coin
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(4)
|
initialTime time.Time
|
||||||
authGS := app.NewAuthGenState(
|
initialCollateral sdk.Coin
|
||||||
addrs,
|
initialPrincipal sdk.Coin
|
||||||
[]sdk.Coins{
|
multipliers types.Multipliers
|
||||||
cs(c("ukava", 400)),
|
multiplier types.MultiplierName
|
||||||
cs(c("ukava", 400)),
|
timeElapsed int
|
||||||
cs(c("ukava", 400)),
|
expectedBalance sdk.Coins
|
||||||
cs(c("ukava", 400)),
|
expectedPeriods vesting.Periods
|
||||||
})
|
isPeriodicVestingAccount bool
|
||||||
tApp.InitializeFromGenesisStates(
|
}
|
||||||
authGS,
|
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()
|
suite.keeper.SetParams(suite.ctx, params)
|
||||||
macc := supplyKeeper.GetModuleAccount(ctx, kavadist.ModuleName)
|
suite.keeper.SetPreviousAccrualTime(suite.ctx, tc.args.ctype, tc.args.initialTime)
|
||||||
err := supplyKeeper.MintCoins(ctx, macc.GetName(), cs(c("ukava", 600)))
|
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)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
// sets addrs[0] to be a periodic vesting account
|
// setup kavadist state
|
||||||
ak := tApp.GetAccountKeeper()
|
err = sk.MintCoins(suite.ctx, kavadist.ModuleName, cs(c("ukava", 1000000000000)))
|
||||||
acc := ak.GetAccount(ctx, addrs[0])
|
suite.Require().NoError(err)
|
||||||
bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
|
|
||||||
periods := vesting.Periods{
|
// setup cdp state
|
||||||
vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))},
|
cdpKeeper := suite.app.GetCDPKeeper()
|
||||||
vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))},
|
err = cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype)
|
||||||
vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))},
|
suite.Require().NoError(err)
|
||||||
vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))},
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
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
|
claim, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||||
acc = ak.GetAccount(ctx, addrs[2])
|
fmt.Println(claim)
|
||||||
bacc = auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
|
suite.Require().True(found)
|
||||||
bva, err2 = vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), ctx.BlockTime().Unix()+16)
|
suite.Require().Equal(c("ukava", 0), claim.Reward)
|
||||||
suite.Require().NoError(err2)
|
} else {
|
||||||
vva := validatorvesting.NewValidatorVestingAccountRaw(bva, ctx.BlockTime().Unix(), periods, sdk.ConsAddress{}, nil, 90)
|
suite.Require().Error(err)
|
||||||
ak.SetAccount(ctx, vva)
|
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||||
suite.app = tApp
|
}
|
||||||
suite.keeper = tApp.GetIncentiveKeeper()
|
|
||||||
suite.ctx = ctx
|
|
||||||
suite.addrs = addrs
|
|
||||||
}
|
|
||||||
|
|
||||||
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)),
|
|
||||||
})
|
})
|
||||||
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() {
|
func (suite *KeeperTestSuite) TestSendCoinsToPeriodicVestingAccount() {
|
||||||
@ -391,7 +426,7 @@ func (suite *KeeperTestSuite) TestSendCoinsToPeriodicVestingAccount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *KeeperTestSuite) TestSendCoinsToBaseAccount() {
|
func (suite *KeeperTestSuite) TestSendCoinsToBaseAccount() {
|
||||||
suite.setupChain()
|
suite.SetupWithAccountState()
|
||||||
// send coins to base account
|
// send coins to base account
|
||||||
err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[1], cs(c("ukava", 100)), 5)
|
err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[1], cs(c("ukava", 100)), 5)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
@ -410,267 +445,59 @@ func (suite *KeeperTestSuite) TestSendCoinsToBaseAccount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *KeeperTestSuite) TestSendCoinsToInvalidAccount() {
|
func (suite *KeeperTestSuite) TestSendCoinsToInvalidAccount() {
|
||||||
suite.setupChain()
|
suite.SetupWithAccountState()
|
||||||
err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[2], cs(c("ukava", 100)), 5)
|
err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[2], cs(c("ukava", 100)), 5)
|
||||||
suite.Require().True(errors.Is(err, types.ErrInvalidAccountType))
|
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)
|
err = suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, macc.GetAddress(), cs(c("ukava", 100)), 5)
|
||||||
suite.Require().True(errors.Is(err, types.ErrInvalidAccountType))
|
suite.Require().True(errors.Is(err, types.ErrInvalidAccountType))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *KeeperTestSuite) TestDeleteExpiredClaimPeriods() {
|
func (suite *KeeperTestSuite) SetupWithAccountState() {
|
||||||
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
|
// creates a new app state with 4 funded addresses and 1 module account
|
||||||
|
|
||||||
// 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
|
|
||||||
tApp := app.NewTestApp()
|
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(
|
authGS := app.NewAuthGenState(
|
||||||
[]sdk.AccAddress{tc.args.claimOwner},
|
addrs,
|
||||||
[]sdk.Coins{
|
[]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(
|
||||||
tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(incentiveGS)})
|
authGS,
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
err = vva.Validate()
|
|
||||||
suite.Require().NoError(err)
|
|
||||||
ak.SetAccount(ctx, vva)
|
|
||||||
}
|
|
||||||
supplyKeeper := tApp.GetSupplyKeeper()
|
supplyKeeper := tApp.GetSupplyKeeper()
|
||||||
supplyKeeper.MintCoins(ctx, types.IncentiveMacc, sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000))))
|
macc := supplyKeeper.GetModuleAccount(ctx, kavadist.ModuleName)
|
||||||
keeper := tApp.GetIncentiveKeeper()
|
err := supplyKeeper.MintCoins(ctx, macc.GetName(), cs(c("ukava", 600)))
|
||||||
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 {
|
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
acc := suite.getAccount(tc.args.claimOwner)
|
|
||||||
suite.Require().Equal(tc.args.expectedAccountBalance, acc.GetCoins())
|
// sets addrs[0] to be a periodic vesting account
|
||||||
mAcc := suite.getModuleAccount(types.IncentiveMacc)
|
ak := tApp.GetAccountKeeper()
|
||||||
suite.Require().Equal(tc.args.expectedModAccountBalance, mAcc.GetCoins())
|
acc := ak.GetAccount(ctx, addrs[0])
|
||||||
vacc, ok := acc.(*vesting.PeriodicVestingAccount)
|
bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence())
|
||||||
if tc.args.expectedVestingAccount {
|
periods := vesting.Periods{
|
||||||
suite.Require().True(ok)
|
vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))},
|
||||||
suite.Require().Equal(tc.args.expectedVestingLength, vacc.VestingPeriods[0].Length)
|
vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))},
|
||||||
} else {
|
vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))},
|
||||||
suite.Require().False(ok)
|
vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))},
|
||||||
}
|
|
||||||
_, 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))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
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
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
@ -18,10 +19,6 @@ func NewQuerier(k Keeper) sdk.Querier {
|
|||||||
return queryGetParams(ctx, req, k)
|
return queryGetParams(ctx, req, k)
|
||||||
case types.QueryGetClaims:
|
case types.QueryGetClaims:
|
||||||
return queryGetClaims(ctx, req, k)
|
return queryGetClaims(ctx, req, k)
|
||||||
case types.QueryGetRewardPeriods:
|
|
||||||
return queryGetRewardPeriods(ctx, req, k)
|
|
||||||
case types.QueryGetClaimPeriods:
|
|
||||||
return queryGetClaimPeriods(ctx, req, k)
|
|
||||||
default:
|
default:
|
||||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName)
|
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
|
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) {
|
func queryGetClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) {
|
||||||
var requestParams types.QueryClaimsParams
|
var requestParams types.QueryClaimsParams
|
||||||
err := k.cdc.UnmarshalJSON(req.Data, &requestParams)
|
err := k.cdc.UnmarshalJSON(req.Data, &requestParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
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 {
|
if err != nil {
|
||||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
|
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
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||||
"github.com/kava-labs/kava/x/incentive/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
|
// AccumulateRewards updates the rewards accumulated for the input reward period
|
||||||
func (k Keeper) HandleRewardPeriodExpiry(ctx sdk.Context, rp types.RewardPeriod) {
|
func (k Keeper) AccumulateRewards(ctx sdk.Context, rewardPeriod types.RewardPeriod) error {
|
||||||
k.CreateUniqueClaimPeriod(ctx, rp.CollateralType, rp.ClaimEnd, rp.ClaimMultipliers)
|
if !rewardPeriod.Active {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix)
|
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||||
store.Delete([]byte(rp.CollateralType))
|
return nil
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
previousAccrualTime, found := k.GetPreviousAccrualTime(ctx, rewardPeriod.CollateralType)
|
||||||
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)
|
|
||||||
if !found {
|
if !found {
|
||||||
previousBlockTime = ctx.BlockTime()
|
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
|
||||||
k.SetPreviousBlockTime(ctx, previousBlockTime)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
rewardFactor, found := k.GetRewardFactor(ctx, cdp.Type)
|
||||||
k.IterateRewardPeriods(ctx, func(rp types.RewardPeriod) bool {
|
if !found {
|
||||||
expired := false
|
rewardFactor = sdk.ZeroDec()
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
claim, found := k.GetClaim(ctx, cdp.Owner)
|
||||||
// the amount of rewards to pay (rewardAmount * timeElapsed)
|
if !found { // this is the owner's first usdx minting reward claim
|
||||||
rewardsThisPeriod := rp.Reward.Amount.Mul(timeElapsed)
|
claim = types.NewUSDXMintingClaim(cdp.Owner, sdk.NewCoin(types.USDXMintingRewardDenom, sdk.ZeroInt()), types.RewardIndexes{types.NewRewardIndex(cdp.Type, rewardFactor)})
|
||||||
id := k.GetNextClaimPeriodID(ctx, rp.CollateralType)
|
k.SetClaim(ctx, claim)
|
||||||
k.cdpKeeper.IterateCdpsByCollateralType(ctx, rp.CollateralType, func(cdp cdptypes.CDP) bool {
|
return
|
||||||
rewardsShare := sdk.NewDecFromInt(cdp.GetTotalPrincipal().Amount).Quo(sdk.NewDecFromInt(totalPrincipal))
|
|
||||||
// sanity check - don't create zero claims
|
|
||||||
if rewardsShare.IsZero() {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
rewardsEarned := rewardsShare.Mul(sdk.NewDecFromInt(rewardsThisPeriod)).RoundInt()
|
// the owner has an existing usdx minting reward claim
|
||||||
k.AddToClaim(ctx, cdp.Owner, rp.CollateralType, id, sdk.NewCoin(types.GovDenom, rewardsEarned))
|
index, hasRewardIndex := claim.HasRewardIndex(cdp.Type)
|
||||||
return false
|
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))
|
||||||
if !expired {
|
} else { // the owner has a previous usdx minting reward for this collateral type
|
||||||
return false
|
claim.RewardIndexes[index] = types.NewRewardIndex(cdp.Type, rewardFactor)
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
k.SetClaim(ctx, claim)
|
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
|
package keeper_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
tmtime "github.com/tendermint/tendermint/types/time"
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/app"
|
"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/incentive/types"
|
||||||
"github.com/kava-labs/kava/x/pricefeed"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (suite *KeeperTestSuite) TestExpireRewardPeriod() {
|
func (suite *KeeperTestSuite) TestAccumulateRewards() {
|
||||||
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"))})
|
type args struct {
|
||||||
suite.keeper.SetRewardPeriod(suite.ctx, rp)
|
ctype string
|
||||||
suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1)
|
rewardsPerSecond sdk.Coin
|
||||||
suite.NotPanics(func() {
|
initialTime time.Time
|
||||||
suite.keeper.HandleRewardPeriodExpiry(suite.ctx, rp)
|
initialTotalPrincipal sdk.Coin
|
||||||
})
|
timeElapsed int
|
||||||
_, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb")
|
expectedRewardFactor sdk.Dec
|
||||||
suite.True(found)
|
}
|
||||||
}
|
type test struct {
|
||||||
|
|
||||||
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 {
|
|
||||||
name string
|
name string
|
||||||
arg string
|
args args
|
||||||
expectFound bool
|
}
|
||||||
}{
|
testCases := []test{
|
||||||
{
|
{
|
||||||
"active reward period",
|
"7 seconds",
|
||||||
"bnb",
|
args{
|
||||||
true,
|
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",
|
"1 day",
|
||||||
"xrp",
|
args{
|
||||||
false,
|
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",
|
"0 seconds",
|
||||||
"btc",
|
args{
|
||||||
false,
|
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 {
|
for _, tc := range testCases {
|
||||||
suite.Run(tc.name, func() {
|
suite.Run(tc.name, func() {
|
||||||
_, found := suite.keeper.GetRewardPeriod(suite.ctx, tc.arg)
|
suite.SetupWithCDPGenState()
|
||||||
if tc.expectFound {
|
suite.ctx = suite.ctx.WithBlockTime(tc.args.initialTime)
|
||||||
suite.True(found)
|
|
||||||
} else {
|
// setup cdp state
|
||||||
suite.False(found)
|
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() {
|
func (suite *KeeperTestSuite) TestSyncRewards() {
|
||||||
suite.setupCdpChain() // creates a test app with 3 BNB cdps and usdx incentives for bnb - each reward period is one week
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// move the context forward by 100 periods
|
testCases := []test{
|
||||||
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 100))
|
{
|
||||||
// apply rewards to BNB cdps
|
"10 blocks",
|
||||||
suite.NotPanics(func() {
|
args{
|
||||||
suite.keeper.ApplyRewardsToCdps(suite.ctx)
|
ctype: "bnb-a",
|
||||||
})
|
rewardsPerSecond: c("ukava", 122354),
|
||||||
// each cdp should have a claim
|
initialTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC),
|
||||||
claims := types.Claims{}
|
initialCollateral: c("bnb", 1000000000000),
|
||||||
suite.keeper.IterateClaims(suite.ctx, func(c types.Claim) (stop bool) {
|
initialPrincipal: c("usdx", 10000000000),
|
||||||
claims = append(claims, c)
|
blockTimes: []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10},
|
||||||
return false
|
expectedRewardFactor: d("0.001223540000000000"),
|
||||||
})
|
expectedRewards: c("ukava", 12235400),
|
||||||
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)
|
"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)
|
||||||
|
|
||||||
// move ctx to the reward period expiry and check that the claim period has been created and the next claim period id has increased
|
// setup incentive state
|
||||||
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 24 * 7))
|
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())
|
||||||
|
|
||||||
suite.NotPanics(func() {
|
// setup account state
|
||||||
// apply rewards to cdps
|
sk := suite.app.GetSupplyKeeper()
|
||||||
suite.keeper.ApplyRewardsToCdps(suite.ctx)
|
sk.MintCoins(suite.ctx, cdptypes.ModuleName, sdk.NewCoins(tc.args.initialCollateral))
|
||||||
// delete the old reward period amd create a new one
|
sk.SendCoinsFromModuleToAccount(suite.ctx, cdptypes.ModuleName, suite.addrs[0], sdk.NewCoins(tc.args.initialCollateral))
|
||||||
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
|
// setup cdp state
|
||||||
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 100))
|
cdpKeeper := suite.app.GetCDPKeeper()
|
||||||
// run the begin blocker functions
|
err := cdpKeeper.AddCdp(suite.ctx, suite.addrs[0], tc.args.initialCollateral, tc.args.initialPrincipal, tc.args.ctype)
|
||||||
suite.NotPanics(func() {
|
suite.Require().NoError(err)
|
||||||
suite.keeper.DeleteExpiredClaimsAndClaimPeriods(suite.ctx)
|
|
||||||
suite.keeper.ApplyRewardsToCdps(suite.ctx)
|
claim, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0])
|
||||||
suite.keeper.CreateAndDeleteRewardPeriods(suite.ctx)
|
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)
|
||||||
})
|
})
|
||||||
// each cdp should now have two claims
|
|
||||||
claims = types.Claims{}
|
rewardFactor, found := suite.keeper.GetRewardFactor(suite.ctx, tc.args.ctype)
|
||||||
suite.keeper.IterateClaims(suite.ctx, func(c types.Claim) (stop bool) {
|
suite.Require().Equal(tc.args.expectedRewardFactor, rewardFactor)
|
||||||
claims = append(claims, c)
|
|
||||||
return false
|
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)
|
||||||
})
|
})
|
||||||
suite.Equal(6, len(claims))
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *KeeperTestSuite) setupCdpChain() {
|
func TestRewardCalculation(t *testing.T) {
|
||||||
// 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
|
|
||||||
|
|
||||||
|
// 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()
|
tApp := app.NewTestApp()
|
||||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
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(
|
tApp.InitializeFromGenesisStates(
|
||||||
authGS,
|
NewPricefeedGenStateMulti(),
|
||||||
pricefeedAppGs,
|
NewCDPGenStateMulti(),
|
||||||
incentiveAppGs,
|
|
||||||
cdpAppGs,
|
|
||||||
)
|
)
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||||
|
keeper := tApp.GetIncentiveKeeper()
|
||||||
suite.app = tApp
|
suite.app = tApp
|
||||||
suite.keeper = tApp.GetIncentiveKeeper()
|
|
||||||
suite.ctx = ctx
|
suite.ctx = ctx
|
||||||
// create 3 cdps
|
suite.keeper = keeper
|
||||||
cdpKeeper := tApp.GetCDPKeeper()
|
suite.addrs = addrs
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid cluttering test cases with long function names
|
// newRepeatingSliceInt creates a slice of the specified length containing a single repeating element.
|
||||||
func i(in int64) sdk.Int { return sdk.NewInt(in) }
|
func newRepeatingSliceInt(element int, length int) []int {
|
||||||
func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) }
|
slice := make([]int, length)
|
||||||
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
|
for i := 0; i < length; i++ {
|
||||||
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
|
slice[i] = element
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
@ -2,11 +2,11 @@ package simulation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/libs/kv"
|
"github.com/tendermint/tendermint/libs/kv"
|
||||||
|
|
||||||
@ -16,35 +16,25 @@ import (
|
|||||||
// DecodeStore unmarshals the KVPair's Value to the module's corresponding type
|
// DecodeStore unmarshals the KVPair's Value to the module's corresponding type
|
||||||
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
|
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
|
||||||
switch {
|
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):
|
case bytes.Equal(kvA.Key[:1], types.ClaimKeyPrefix):
|
||||||
var claimA, claimB types.Claim
|
var claimA, claimB types.USDXMintingClaim
|
||||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &claimA)
|
cdc.MustUnmarshalBinaryBare(kvA.Value, &claimA)
|
||||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &claimB)
|
cdc.MustUnmarshalBinaryBare(kvB.Value, &claimB)
|
||||||
return fmt.Sprintf("%v\n%v", claimA, claimB)
|
return fmt.Sprintf("%v\n%v", claimA, claimB)
|
||||||
|
|
||||||
case bytes.Equal(kvA.Key[:1], types.NextClaimPeriodIDPrefix):
|
case bytes.Equal(kvA.Key[:1], types.BlockTimeKey):
|
||||||
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):
|
|
||||||
var timeA, timeB time.Time
|
var timeA, timeB time.Time
|
||||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &timeA)
|
cdc.MustUnmarshalBinaryBare(kvA.Value, &timeA)
|
||||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &timeB)
|
cdc.MustUnmarshalBinaryBare(kvB.Value, &timeB)
|
||||||
return fmt.Sprintf("%s\n%s", timeA, 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:
|
default:
|
||||||
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
|
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) {
|
func TestDecodeDistributionStore(t *testing.T) {
|
||||||
cdc := makeTestCodec()
|
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")
|
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()
|
prevBlockTime := time.Now().Add(time.Hour * -1).UTC()
|
||||||
|
factor := sdk.ZeroDec()
|
||||||
|
|
||||||
kvPairs := kv.Pairs{
|
kvPairs := kv.Pairs{
|
||||||
kv.Pair{Key: types.RewardPeriodKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&rewardPeriod)},
|
kv.Pair{Key: types.ClaimKeyPrefix, Value: cdc.MustMarshalBinaryBare(claim)},
|
||||||
kv.Pair{Key: types.ClaimPeriodKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&claimPeriod)},
|
kv.Pair{Key: []byte(types.BlockTimeKey), Value: cdc.MustMarshalBinaryBare(prevBlockTime)},
|
||||||
kv.Pair{Key: types.ClaimKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&claim)},
|
kv.Pair{Key: []byte(types.RewardFactorKey), Value: cdc.MustMarshalBinaryBare(factor)},
|
||||||
kv.Pair{Key: types.NextClaimPeriodIDPrefix, Value: sdk.Uint64ToBigEndian(10)},
|
|
||||||
kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevBlockTime)},
|
|
||||||
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
|
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,11 +40,9 @@ func TestDecodeDistributionStore(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
expectedLog 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)},
|
{"Claim", fmt.Sprintf("%v\n%v", claim, claim)},
|
||||||
{"NextClaimPeriodID", "10\n10"},
|
|
||||||
{"PreviousBlockTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)},
|
{"PreviousBlockTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)},
|
||||||
|
{"RewardFactor", fmt.Sprintf("%v\n%v", factor, factor)},
|
||||||
{"other", ""},
|
{"other", ""},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
|
@ -2,15 +2,11 @@ package simulation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/types/module"
|
"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"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,115 +19,9 @@ var (
|
|||||||
// RandomizedGenState generates a random GenesisState for incentive module
|
// RandomizedGenState generates a random GenesisState for incentive module
|
||||||
func RandomizedGenState(simState *module.SimulationState) {
|
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
|
// New genesis state holds valid, linked reward periods, claim periods, and claim period IDs
|
||||||
incentiveGenesis := types.NewGenesisState(params, types.DefaultPreviousBlockTime,
|
incentiveGenesis := types.DefaultGenesisState()
|
||||||
rewardPeriods, claimPeriods, types.Claims{}, claimPeriodIDs)
|
|
||||||
if err := incentiveGenesis.Validate(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, incentiveGenesis))
|
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)
|
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
|
package simulation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/simapp/helpers"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||||
|
|
||||||
appparams "github.com/kava-labs/kava/app/params"
|
appparams "github.com/kava-labs/kava/app/params"
|
||||||
"github.com/kava-labs/kava/x/incentive/keeper"
|
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||||
"github.com/kava-labs/kava/x/incentive/types"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
"github.com/kava-labs/kava/x/kavadist"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Simulation operation weights constants
|
// 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,
|
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
|
||||||
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
|
) (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,
|
return simulation.NewOperationMsgBasic(types.ModuleName,
|
||||||
"no-operation (no accounts currently have fulfillable claims)", "", false, nil), nil, nil
|
"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
|
package simulation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/incentive/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -23,16 +20,5 @@ func genActive(r *rand.Rand) bool {
|
|||||||
|
|
||||||
// ParamChanges defines the parameters that can be modified by param change proposals
|
// ParamChanges defines the parameters that can be modified by param change proposals
|
||||||
func ParamChanges(r *rand.Rand) []simulation.ParamChange {
|
func ParamChanges(r *rand.Rand) []simulation.ParamChange {
|
||||||
return []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))
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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
|
// RegisterCodec registers the necessary types for incentive module
|
||||||
func RegisterCodec(cdc *codec.Codec) {
|
func RegisterCodec(cdc *codec.Codec) {
|
||||||
cdc.RegisterConcrete(MsgClaimReward{}, "incentive/MsgClaimReward", nil)
|
cdc.RegisterConcrete(MsgClaimUSDXMintingReward{}, "incentive/MsgClaimUSDXMintingReward", 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)
|
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,12 @@ import (
|
|||||||
// Incentive module errors
|
// Incentive module errors
|
||||||
var (
|
var (
|
||||||
ErrClaimNotFound = sdkerrors.Register(ModuleName, 2, "no claim with input id found for owner and collateral type")
|
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")
|
ErrInvalidAccountType = sdkerrors.Register(ModuleName, 4, "account type not supported")
|
||||||
ErrNoClaimsFound = sdkerrors.Register(ModuleName, 5, "no claims with collateral type found for address")
|
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")
|
ErrInsufficientModAccountBalance = sdkerrors.Register(ModuleName, 6, "module account has insufficient balance to pay claim")
|
||||||
ErrAccountNotFound = sdkerrors.Register(ModuleName, 7, "account not found")
|
ErrAccountNotFound = sdkerrors.Register(ModuleName, 7, "account not found")
|
||||||
ErrInvalidMultiplier = sdkerrors.Register(ModuleName, 8, "invalid rewards multiplier")
|
ErrInvalidMultiplier = sdkerrors.Register(ModuleName, 8, "invalid rewards multiplier")
|
||||||
ErrZeroClaim = sdkerrors.Register(ModuleName, 9, "cannot claim - claim amount rounds to zero")
|
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
|
// CdpKeeper defines the expected cdp keeper for interacting with cdps
|
||||||
type CdpKeeper interface {
|
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)
|
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
|
// 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
|
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
|
||||||
SetAccount(ctx sdk.Context, acc 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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
"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.
|
// GenesisState is the state that must be provided at genesis.
|
||||||
type GenesisState struct {
|
type GenesisState struct {
|
||||||
Params Params `json:"params" yaml:"params"`
|
Params Params `json:"params" yaml:"params"`
|
||||||
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
|
PreviousAccumulationTimes GenesisAccumulationTimes `json:"previous_accumulation_times" yaml:"previous_accumulation_times"`
|
||||||
RewardPeriods RewardPeriods `json:"reward_periods" yaml:"reward_periods"`
|
USDXMintingClaims USDXMintingClaims `json:"usdx_minting_claims" yaml:"usdx_minting_claims"`
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGenesisState returns a new genesis state
|
// 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{
|
return GenesisState{
|
||||||
Params: params,
|
Params: params,
|
||||||
PreviousBlockTime: previousBlockTime,
|
PreviousAccumulationTimes: prevAccumTimes,
|
||||||
RewardPeriods: rp,
|
USDXMintingClaims: c,
|
||||||
ClaimPeriods: cp,
|
|
||||||
Claims: c,
|
|
||||||
NextClaimPeriodIDs: ids,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,11 +28,8 @@ func NewGenesisState(params Params, previousBlockTime time.Time, rp RewardPeriod
|
|||||||
func DefaultGenesisState() GenesisState {
|
func DefaultGenesisState() GenesisState {
|
||||||
return GenesisState{
|
return GenesisState{
|
||||||
Params: DefaultParams(),
|
Params: DefaultParams(),
|
||||||
PreviousBlockTime: DefaultPreviousBlockTime,
|
PreviousAccumulationTimes: GenesisAccumulationTimes{},
|
||||||
RewardPeriods: RewardPeriods{},
|
USDXMintingClaims: DefaultClaims,
|
||||||
ClaimPeriods: ClaimPeriods{},
|
|
||||||
Claims: Claims{},
|
|
||||||
NextClaimPeriodIDs: GenesisClaimPeriodIDs{},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,19 +39,11 @@ func (gs GenesisState) Validate() error {
|
|||||||
if err := gs.Params.Validate(); err != nil {
|
if err := gs.Params.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if gs.PreviousBlockTime.IsZero() {
|
if err := gs.PreviousAccumulationTimes.Validate(); err != nil {
|
||||||
return errors.New("previous block time cannot be 0")
|
|
||||||
}
|
|
||||||
if err := gs.RewardPeriods.Validate(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := gs.ClaimPeriods.Validate(); err != nil {
|
|
||||||
return err
|
return gs.USDXMintingClaims.Validate()
|
||||||
}
|
|
||||||
if err := gs.Claims.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return gs.NextClaimPeriodIDs.Validate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal checks whether two gov GenesisState structs are equivalent
|
// 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 {
|
func (gs GenesisState) IsEmpty() bool {
|
||||||
return gs.Equal(GenesisState{})
|
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
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
"github.com/tendermint/tendermint/crypto"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
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) {
|
func TestGenesisStateValidate(t *testing.T) {
|
||||||
now := time.Now()
|
type args struct {
|
||||||
mockPrivKey := tmtypes.NewMockPV()
|
params Params
|
||||||
pubkey, err := mockPrivKey.GetPubKey()
|
genAccTimes GenesisAccumulationTimes
|
||||||
require.NoError(t, err)
|
claims USDXMintingClaims
|
||||||
owner := sdk.AccAddress(pubkey.Address())
|
}
|
||||||
|
|
||||||
rewards := Rewards{
|
type errArgs struct {
|
||||||
NewReward(
|
expectPass bool
|
||||||
true, "bnb", sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
contains string
|
||||||
time.Hour*24*7, Multipliers{NewMultiplier(Small, 1, sdk.MustNewDecFromStr("0.33"))}, time.Hour*24*14,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
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 {
|
testCases := []struct {
|
||||||
msg string
|
name string
|
||||||
genesisState GenesisState
|
args args
|
||||||
expPass bool
|
errArgs errArgs
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
msg: "default",
|
name: "default",
|
||||||
genesisState: DefaultGenesisState(),
|
args: args{
|
||||||
expPass: true,
|
params: DefaultParams(),
|
||||||
|
genAccTimes: DefaultGenesisAccumulationTimes,
|
||||||
|
claims: DefaultClaims,
|
||||||
|
},
|
||||||
|
errArgs: errArgs{
|
||||||
|
expectPass: true,
|
||||||
|
contains: "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
msg: "valid genesis",
|
name: "valid",
|
||||||
genesisState: NewGenesisState(
|
args: args{
|
||||||
NewParams(true, rewards),
|
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)),
|
||||||
now, rewardPeriods, claimPeriods, claims, gcps,
|
genAccTimes: GenesisAccumulationTimes{GenesisAccumulationTime{
|
||||||
),
|
CollateralType: "bnb-a",
|
||||||
expPass: true,
|
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",
|
name: "invalid genesis accumulation time",
|
||||||
genesisState: GenesisState{
|
args: args{
|
||||||
Params: Params{
|
params: DefaultParams(),
|
||||||
Active: true,
|
genAccTimes: GenesisAccumulationTimes{
|
||||||
Rewards: Rewards{
|
{
|
||||||
Reward{},
|
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",
|
name: "invalid genesis accumulation time",
|
||||||
genesisState: GenesisState{
|
args: args{
|
||||||
PreviousBlockTime: time.Time{},
|
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",
|
name: "invalid claim",
|
||||||
genesisState: GenesisState{
|
args: args{
|
||||||
PreviousBlockTime: now,
|
params: DefaultParams(),
|
||||||
RewardPeriods: RewardPeriods{
|
genAccTimes: DefaultGenesisAccumulationTimes,
|
||||||
{Start: time.Time{}},
|
claims: USDXMintingClaims{
|
||||||
},
|
|
||||||
},
|
|
||||||
expPass: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
msg: "invalid ClaimPeriods",
|
Owner: sdk.AccAddress{},
|
||||||
genesisState: GenesisState{
|
Reward: sdk.NewCoin("ukava", sdk.NewInt(100000000)),
|
||||||
PreviousBlockTime: now,
|
|
||||||
ClaimPeriods: ClaimPeriods{
|
RewardIndexes: []RewardIndex{
|
||||||
{ID: 0},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expPass: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
msg: "invalid Claims",
|
CollateralType: "bnb-a",
|
||||||
genesisState: GenesisState{
|
RewardFactor: sdk.ZeroDec(),
|
||||||
PreviousBlockTime: now,
|
|
||||||
Claims: Claims{
|
|
||||||
{ClaimPeriodID: 0},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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 {
|
for _, tc := range testCases {
|
||||||
err := tc.genesisState.Validate()
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if tc.expPass {
|
gs := NewGenesisState(tc.args.params, tc.args.genAccTimes, tc.args.claims)
|
||||||
require.NoError(t, err, tc.msg)
|
err := gs.Validate()
|
||||||
|
if tc.errArgs.expectPass {
|
||||||
|
require.NoError(t, err, tc.name)
|
||||||
} else {
|
} 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
|
package types
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ModuleName The name that will be used throughout the module
|
// ModuleName The name that will be used throughout the module
|
||||||
ModuleName = "incentive"
|
ModuleName = "incentive"
|
||||||
@ -25,37 +19,9 @@ const (
|
|||||||
|
|
||||||
// Key Prefixes
|
// Key Prefixes
|
||||||
var (
|
var (
|
||||||
RewardPeriodKeyPrefix = []byte{0x01} // prefix for keys that store reward periods
|
ClaimKeyPrefix = []byte{0x01} // prefix for keys that store claims
|
||||||
ClaimPeriodKeyPrefix = []byte{0x02} // prefix for keys that store claim periods
|
BlockTimeKey = []byte{0x02} // prefix for key that stores the blocktime
|
||||||
ClaimKeyPrefix = []byte{0x03} // prefix for keys that store claims
|
RewardFactorKey = []byte{0x03} // prefix for key that stores reward factors
|
||||||
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
|
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
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
@ -9,48 +8,43 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ensure Msg interface compliance at compile time
|
// ensure Msg interface compliance at compile time
|
||||||
var _ sdk.Msg = &MsgClaimReward{}
|
var _ sdk.Msg = &MsgClaimUSDXMintingReward{}
|
||||||
|
|
||||||
// MsgClaimReward message type used to claim rewards
|
// MsgClaimUSDXMintingReward message type used to claim rewards
|
||||||
type MsgClaimReward struct {
|
type MsgClaimUSDXMintingReward struct {
|
||||||
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
||||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
|
||||||
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
|
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMsgClaimReward returns a new MsgClaimReward.
|
// NewMsgClaimUSDXMintingReward returns a new MsgClaimUSDXMintingReward.
|
||||||
func NewMsgClaimReward(sender sdk.AccAddress, collateralType, multiplierName string) MsgClaimReward {
|
func NewMsgClaimUSDXMintingReward(sender sdk.AccAddress, multiplierName string) MsgClaimUSDXMintingReward {
|
||||||
return MsgClaimReward{
|
return MsgClaimUSDXMintingReward{
|
||||||
Sender: sender,
|
Sender: sender,
|
||||||
CollateralType: collateralType,
|
|
||||||
MultiplierName: multiplierName,
|
MultiplierName: multiplierName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route return the message type used for routing the message.
|
// 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.
|
// 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.
|
// 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() {
|
if msg.Sender.Empty() {
|
||||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be 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()
|
return MultiplierName(strings.ToLower(msg.MultiplierName)).IsValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSignBytes gets the canonical byte representation of the Msg.
|
// GetSignBytes gets the canonical byte representation of the Msg.
|
||||||
func (msg MsgClaimReward) GetSignBytes() []byte {
|
func (msg MsgClaimUSDXMintingReward) GetSignBytes() []byte {
|
||||||
bz := ModuleCdc.MustMarshalJSON(msg)
|
bz := ModuleCdc.MustMarshalJSON(msg)
|
||||||
return sdk.MustSortJSON(bz)
|
return sdk.MustSortJSON(bz)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSigners returns the addresses of signers that must sign.
|
// 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}
|
return []sdk.AccAddress{msg.Sender}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
type msgTest struct {
|
type msgTest struct {
|
||||||
from sdk.AccAddress
|
from sdk.AccAddress
|
||||||
collateralType string
|
|
||||||
multiplierName string
|
multiplierName string
|
||||||
expectPass bool
|
expectPass bool
|
||||||
}
|
}
|
||||||
@ -29,25 +28,26 @@ func (suite *MsgTestSuite) SetupTest() {
|
|||||||
tests := []msgTest{
|
tests := []msgTest{
|
||||||
{
|
{
|
||||||
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
||||||
collateralType: "bnb",
|
|
||||||
multiplierName: "large",
|
multiplierName: "large",
|
||||||
expectPass: true,
|
expectPass: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
||||||
collateralType: "",
|
multiplierName: "medium",
|
||||||
|
expectPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
||||||
multiplierName: "small",
|
multiplierName: "small",
|
||||||
expectPass: false,
|
expectPass: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: sdk.AccAddress{},
|
from: sdk.AccAddress{},
|
||||||
collateralType: "bnb",
|
|
||||||
multiplierName: "medium",
|
multiplierName: "medium",
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
|
||||||
collateralType: "bnb",
|
|
||||||
multiplierName: "huge",
|
multiplierName: "huge",
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
},
|
},
|
||||||
@ -57,7 +57,7 @@ func (suite *MsgTestSuite) SetupTest() {
|
|||||||
|
|
||||||
func (suite *MsgTestSuite) TestMsgValidation() {
|
func (suite *MsgTestSuite) TestMsgValidation() {
|
||||||
for _, t := range suite.tests {
|
for _, t := range suite.tests {
|
||||||
msg := types.NewMsgClaimReward(t.from, t.collateralType, t.multiplierName)
|
msg := types.NewMsgClaimUSDXMintingReward(t.from, t.multiplierName)
|
||||||
err := msg.ValidateBasic()
|
err := msg.ValidateBasic()
|
||||||
if t.expectPass {
|
if t.expectPass {
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -24,10 +25,15 @@ const (
|
|||||||
// Parameter keys and default values
|
// Parameter keys and default values
|
||||||
var (
|
var (
|
||||||
KeyActive = []byte("Active")
|
KeyActive = []byte("Active")
|
||||||
KeyRewards = []byte("Rewards")
|
KeyRewards = []byte("RewardPeriods")
|
||||||
|
KeyClaimEnd = []byte("ClaimEnd")
|
||||||
|
KeyMultipliers = []byte("ClaimMultipliers")
|
||||||
DefaultActive = false
|
DefaultActive = false
|
||||||
DefaultRewards = Rewards{}
|
DefaultRewardPeriods = RewardPeriods{}
|
||||||
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
|
DefaultMultipliers = Multipliers{}
|
||||||
|
DefaultClaims = USDXMintingClaims{}
|
||||||
|
DefaultGenesisAccumulationTimes = GenesisAccumulationTimes{}
|
||||||
|
DefaultClaimEnd = tmtime.Canonical(time.Unix(0, 0))
|
||||||
GovDenom = cdptypes.DefaultGovDenom
|
GovDenom = cdptypes.DefaultGovDenom
|
||||||
PrincipalDenom = "usdx"
|
PrincipalDenom = "usdx"
|
||||||
IncentiveMacc = kavadistTypes.ModuleName
|
IncentiveMacc = kavadistTypes.ModuleName
|
||||||
@ -35,28 +41,32 @@ var (
|
|||||||
|
|
||||||
// Params governance parameters for the incentive module
|
// Params governance parameters for the incentive module
|
||||||
type Params struct {
|
type Params struct {
|
||||||
Active bool `json:"active" yaml:"active"` // top level governance switch to disable all rewards
|
RewardPeriods RewardPeriods `json:"reward_periods" yaml:"reward_periods"`
|
||||||
Rewards Rewards `json:"rewards" yaml:"rewards"`
|
ClaimMultipliers Multipliers `json:"claim_multipliers" yaml:"claim_multipliers"`
|
||||||
|
ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParams returns a new params object
|
// 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{
|
return Params{
|
||||||
Active: active,
|
RewardPeriods: rewards,
|
||||||
Rewards: rewards,
|
ClaimMultipliers: multipliers,
|
||||||
|
ClaimEnd: claimEnd,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultParams returns default params for incentive module
|
// DefaultParams returns default params for incentive module
|
||||||
func DefaultParams() Params {
|
func DefaultParams() Params {
|
||||||
return NewParams(DefaultActive, DefaultRewards)
|
return NewParams(DefaultRewardPeriods, DefaultMultipliers, DefaultClaimEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements fmt.Stringer
|
// String implements fmt.Stringer
|
||||||
func (p Params) String() string {
|
func (p Params) String() string {
|
||||||
return fmt.Sprintf(`Params:
|
return fmt.Sprintf(`Params:
|
||||||
Active: %t
|
Rewards: %s
|
||||||
Rewards: %s`, p.Active, p.Rewards)
|
Claim Multipliers :%s
|
||||||
|
Claim End Time: %s
|
||||||
|
`, p.RewardPeriods, p.ClaimMultipliers, p.ClaimEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParamKeyTable Key declaration for parameters
|
// 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
|
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
|
||||||
func (p *Params) ParamSetPairs() params.ParamSetPairs {
|
func (p *Params) ParamSetPairs() params.ParamSetPairs {
|
||||||
return params.ParamSetPairs{
|
return params.ParamSetPairs{
|
||||||
params.NewParamSetPair(KeyActive, &p.Active, validateActiveParam),
|
params.NewParamSetPair(KeyRewards, &p.RewardPeriods, validateRewardsParam),
|
||||||
params.NewParamSetPair(KeyRewards, &p.Rewards, validateRewardsParam),
|
params.NewParamSetPair(KeyClaimEnd, &p.ClaimEnd, validateClaimEndParam),
|
||||||
|
params.NewParamSetPair(KeyMultipliers, &p.ClaimMultipliers, validateMultipliersParam),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks that the parameters have valid values.
|
// Validate checks that the parameters have valid values.
|
||||||
func (p Params) Validate() error {
|
func (p Params) Validate() error {
|
||||||
if err := validateActiveParam(p.Active); err != nil {
|
if err := validateMultipliersParam(p.ClaimMultipliers); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return validateRewardsParam(p.Rewards)
|
return validateRewardsParam(p.RewardPeriods)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateActiveParam(i interface{}) error {
|
func validateActiveParam(i interface{}) error {
|
||||||
@ -90,7 +101,7 @@ func validateActiveParam(i interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateRewardsParam(i interface{}) error {
|
func validateRewardsParam(i interface{}) error {
|
||||||
rewards, ok := i.(Rewards)
|
rewards, ok := i.(RewardPeriods)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("invalid parameter type: %T", i)
|
return fmt.Errorf("invalid parameter type: %T", i)
|
||||||
}
|
}
|
||||||
@ -98,93 +109,97 @@ func validateRewardsParam(i interface{}) error {
|
|||||||
return rewards.Validate()
|
return rewards.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reward stores the specified state for a single reward period.
|
func validateMultipliersParam(i interface{}) error {
|
||||||
type Reward struct {
|
multipliers, ok := i.(Multipliers)
|
||||||
Active bool `json:"active" yaml:"active"` // governance switch to disable a period
|
if !ok {
|
||||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"` // the collateral type rewards apply to, must be found in the cdp collaterals
|
return fmt.Errorf("invalid parameter type: %T", i)
|
||||||
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
|
return multipliers.Validate()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReward returns a new Reward
|
func validateClaimEndParam(i interface{}) error {
|
||||||
func NewReward(active bool, collateralType string, reward sdk.Coin, duration time.Duration, multiplier Multipliers, claimDuration time.Duration) Reward {
|
endTime, ok := i.(time.Time)
|
||||||
return Reward{
|
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,
|
Active: active,
|
||||||
CollateralType: collateralType,
|
CollateralType: collateralType,
|
||||||
AvailableRewards: reward,
|
Start: start,
|
||||||
Duration: duration,
|
End: end,
|
||||||
ClaimMultipliers: multiplier,
|
RewardsPerSecond: reward,
|
||||||
ClaimDuration: claimDuration,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements fmt.Stringer
|
// Validate performs a basic check of a RewardPeriod fields.
|
||||||
func (r Reward) String() string {
|
func (rp RewardPeriod) Validate() error {
|
||||||
return fmt.Sprintf(`Reward:
|
if rp.Start.IsZero() {
|
||||||
Active: %t,
|
return errors.New("reward period start time cannot be 0")
|
||||||
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 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 !r.AvailableRewards.IsPositive() {
|
if rp.End.IsZero() {
|
||||||
return fmt.Errorf("reward amount must be positive, is %s for %s", r.AvailableRewards, r.CollateralType)
|
return errors.New("reward period end time cannot be 0")
|
||||||
}
|
}
|
||||||
if r.Duration <= 0 {
|
if rp.Start.After(rp.End) {
|
||||||
return fmt.Errorf("reward duration must be positive, is %s for %s", r.Duration, r.CollateralType)
|
return fmt.Errorf("end period time %s cannot be before start time %s", rp.End, rp.Start)
|
||||||
}
|
}
|
||||||
if err := r.ClaimMultipliers.Validate(); err != nil {
|
if !rp.RewardsPerSecond.IsValid() {
|
||||||
return err
|
return fmt.Errorf("invalid reward amount: %s", rp.RewardsPerSecond)
|
||||||
}
|
}
|
||||||
if r.ClaimDuration <= 0 {
|
if strings.TrimSpace(rp.CollateralType) == "" {
|
||||||
return fmt.Errorf("claim duration must be positive, is %s for %s", r.ClaimDuration, r.CollateralType)
|
return fmt.Errorf("reward period collateral type cannot be blank: %s", rp)
|
||||||
}
|
|
||||||
if strings.TrimSpace(r.CollateralType) == "" {
|
|
||||||
return fmt.Errorf("collateral type cannot be blank: %s", r)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rewards array of Reward
|
// RewardPeriods array of RewardPeriod
|
||||||
type Rewards []Reward
|
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.
|
// entries.
|
||||||
func (rs Rewards) Validate() error {
|
func (rps RewardPeriods) Validate() error {
|
||||||
rewardCollateralTypes := make(map[string]bool)
|
seenPeriods := make(map[string]bool)
|
||||||
for _, r := range rs {
|
for _, rp := range rps {
|
||||||
if rewardCollateralTypes[r.CollateralType] {
|
if seenPeriods[rp.CollateralType] {
|
||||||
return fmt.Errorf("cannot have duplicate reward collateral types: %s", r.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
|
return err
|
||||||
}
|
}
|
||||||
|
seenPeriods[rp.CollateralType] = true
|
||||||
rewardCollateralTypes[r.CollateralType] = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
// Multiplier amount the claim rewards get increased by, along with how long the claim rewards are locked
|
||||||
type Multiplier struct {
|
type Multiplier struct {
|
||||||
Name MultiplierName `json:"name" yaml:"name"`
|
Name MultiplierName `json:"name" yaml:"name"`
|
||||||
@ -209,6 +224,9 @@ func (m Multiplier) Validate() error {
|
|||||||
if m.MonthsLockup < 0 {
|
if m.MonthsLockup < 0 {
|
||||||
return fmt.Errorf("expected non-negative lockup, got %d", m.MonthsLockup)
|
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() {
|
if m.Factor.IsNegative() {
|
||||||
return fmt.Errorf("expected non-negative factor, got %s", m.Factor.String())
|
return fmt.Errorf("expected non-negative factor, got %s", m.Factor.String())
|
||||||
}
|
}
|
||||||
|
@ -5,212 +5,104 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/incentive/types"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type paramTest struct {
|
|
||||||
name string
|
|
||||||
params types.Params
|
|
||||||
errResult errResult
|
|
||||||
}
|
|
||||||
|
|
||||||
type errResult struct {
|
|
||||||
expectPass bool
|
|
||||||
contains string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParamTestSuite struct {
|
type ParamTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
|
|
||||||
tests []paramTest
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ParamTestSuite) SetupTest() {
|
func (suite *ParamTestSuite) SetupTest() {}
|
||||||
suite.tests = []paramTest{
|
|
||||||
|
func (suite *ParamTestSuite) TestParamValidation() {
|
||||||
|
type args struct {
|
||||||
|
rewardPeriods types.RewardPeriods
|
||||||
|
multipliers types.Multipliers
|
||||||
|
end time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type errArgs struct {
|
||||||
|
expectPass bool
|
||||||
|
contains string
|
||||||
|
}
|
||||||
|
type test struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
errArgs errArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []test{
|
||||||
{
|
{
|
||||||
name: "valid - active",
|
"default",
|
||||||
params: types.Params{
|
args{
|
||||||
Active: true,
|
rewardPeriods: types.DefaultRewardPeriods,
|
||||||
Rewards: types.Rewards{
|
multipliers: types.DefaultMultipliers,
|
||||||
types.Reward{
|
end: types.DefaultClaimEnd,
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
},
|
errArgs{
|
||||||
},
|
|
||||||
errResult: errResult{
|
|
||||||
expectPass: true,
|
expectPass: true,
|
||||||
contains: "",
|
contains: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "valid - inactive",
|
"valid",
|
||||||
params: types.Params{
|
args{
|
||||||
Active: false,
|
rewardPeriods: types.RewardPeriods{types.NewRewardPeriod(
|
||||||
Rewards: types.Rewards{
|
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),
|
||||||
types.Reward{
|
sdk.NewCoin(types.USDXMintingRewardDenom, sdk.NewInt(122354)))},
|
||||||
Active: true,
|
multipliers: types.Multipliers{
|
||||||
CollateralType: "bnb-a",
|
types.NewMultiplier(
|
||||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
types.Small, 1, sdk.MustNewDecFromStr("0.25"),
|
||||||
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"))},
|
types.NewMultiplier(
|
||||||
ClaimDuration: time.Hour * 24 * 14,
|
types.Large, 1, sdk.MustNewDecFromStr("1.0"),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
|
end: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||||
},
|
},
|
||||||
},
|
errArgs{
|
||||||
errResult: errResult{
|
|
||||||
expectPass: true,
|
expectPass: true,
|
||||||
contains: "",
|
contains: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "duplicate reward",
|
"invalid: empty reward factor",
|
||||||
params: types.Params{
|
args{
|
||||||
Active: true,
|
rewardPeriods: types.RewardPeriods{types.NewRewardPeriod(
|
||||||
Rewards: types.Rewards{
|
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),
|
||||||
types.Reward{
|
sdk.NewCoin(types.USDXMintingRewardDenom, sdk.NewInt(122354)))},
|
||||||
Active: true,
|
multipliers: types.Multipliers{
|
||||||
CollateralType: "bnb-a",
|
types.NewMultiplier(
|
||||||
AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)),
|
types.Small, 1, sdk.MustNewDecFromStr("0.25"),
|
||||||
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"))},
|
types.NewMultiplier(
|
||||||
ClaimDuration: time.Hour * 24 * 14,
|
types.Large, 1, sdk.Dec{},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
types.Reward{
|
end: time.Date(2025, 10, 15, 14, 0, 0, 0, time.UTC),
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
},
|
errArgs{
|
||||||
},
|
|
||||||
errResult: errResult{
|
|
||||||
expectPass: false,
|
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 _, tc := range testCases {
|
||||||
for _, t := range suite.tests {
|
suite.Run(tc.name, func() {
|
||||||
suite.Run(t.name, func() {
|
params := types.NewParams(
|
||||||
err := t.params.Validate()
|
tc.args.rewardPeriods, tc.args.multipliers, tc.args.end,
|
||||||
if t.errResult.expectPass {
|
)
|
||||||
|
err := params.Validate()
|
||||||
|
if tc.errArgs.expectPass {
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
} else {
|
} else {
|
||||||
suite.Require().Error(err)
|
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
|
// QueryClaimsParams params for query /incentive/claims
|
||||||
type QueryClaimsParams struct {
|
type QueryClaimsParams struct {
|
||||||
|
Page int `json:"page" yaml:"page"`
|
||||||
|
Limit int `json:"limit" yaml:"limit"`
|
||||||
Owner sdk.AccAddress
|
Owner sdk.AccAddress
|
||||||
CollateralType string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewQueryClaimsParams returns QueryClaimsParams
|
// NewQueryClaimsParams returns QueryClaimsParams
|
||||||
func NewQueryClaimsParams(owner sdk.AccAddress, collateralType string) QueryClaimsParams {
|
func NewQueryClaimsParams(page, limit int, owner sdk.AccAddress) QueryClaimsParams {
|
||||||
return QueryClaimsParams{
|
return QueryClaimsParams{
|
||||||
|
Page: page,
|
||||||
|
Limit: limit,
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
CollateralType: collateralType,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +35,5 @@ func NewQueryClaimsParams(owner sdk.AccAddress, collateralType string) QueryClai
|
|||||||
type PostClaimReq struct {
|
type PostClaimReq struct {
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||||
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
||||||
CollateralType string `json:"collateral_type" yaml:"collateral_type"`
|
|
||||||
MultiplierName string `json:"multiplier_name" yaml:"multiplier_name"`
|
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