From 4beaf0de54a59fd2c5ffff9705f7b0760152a28d Mon Sep 17 00:00:00 2001 From: Ruaridh Date: Tue, 13 Jul 2021 13:35:02 +0100 Subject: [PATCH] Swap users accumulate rewards (#950) * add swap claim type * add store methods for swap claims * add swap claims to genesis state * tidy up cdp and hard hook doc comments * add uncalled hooks to the swap keeper * add swap rewards sync method * add swap rewards init method * connect swap rewards via swap hooks * Update querier and client for swap claims (#951) * refactor querier to dedupe code * add swap claims querier endpoint * add swap claim querying to rest * add swap claim querying to cli * add keeper method to iterate swap reward indexes * simplify reward-factors query endpoint, add swap * update swap hook to match latest swap branch * rename func to not collide with latest swap branch * Squash merge swap-acceptance branch (#956) * add failing acceptance test for a user depositing into a pool * implement GetAccount test helper * implement swap.MsgDeposit for creating and adding liquidity to a pool * update aliases, add event types, and fix typo/compiler errors in handler test * use only aliases names in handler test (don't use swap types -- ensures we have run aliasgen), add assertion for even type message * implement account and module account balance checks in handler test * fill out handler assertions for testing keeper state and events * update signed json representation and register swap/MsgDeposit for proper encoding * fill out boilerplate to get handler test to compile * alias gen for pool * add handling of message type; fill in deposit keeper method for succesful compile; noop but test assertions now run up to module acc not nil check * add module account permissions for swap module -- fixes module account creation; pass account keeper and supply keeper into swap keeper to allow the ability to work with user and module accounts * implement create pool logic for msg deposit; allows creation of a of new pool, checking params to see if it is allowed. Initi shares are set, and senders number of shares are stored * Swap migrations scaffolding (#925) * swap module scaffolding * global swap fee * can't think of a reason for begin blocker; removing for abci.go for now; * test pair types; refactor pair name logic; simplify pairs validation and fix stack overflow error * check comparison * use test package * init swap module genesis * add basic marshall tests * remove reward apy from pairs * fix integration helpers * use max swap fee constant; fix validation of swap fee; add tests to cover param validation and param set setup * use noerror over nil * start genesis tests * test param set validation mirrors param validation * add genesis tests * remove print statement * add subtests for genesis test cases; add extra querier test for unknown route; add keeper params testing * add spec * update swagger * find replace hard -> swap in comments * remove unused method * rename pairs to allowed pools; pool is more commonly used, and allowedPool makes it more clear what swap parameter is for. In addition, we won't conflict with Pool data structure for storing a created pool in the store. * remove generated link * missed spec rename * validate token order for allowed pools * fix swagger * json should be snakecase; change allowedPools to allowed_pools * add legacy types * add swap genesis to v0_15 migration * add legacy types * add swap genesis to v0_15 migration * migration revisions Co-authored-by: Nick DeLuca * keeper todos * update keeper tests * type todos * update types tests * tx deposit cli cmd * tx deposit rest * Swap module simulation scaffolding (#924) * sims scaffolding * add noop operation * genesis revisions * add param changes * mvoe persistance methods to main keeper file, consolidate tests * make helper methods private. they are tested via deposit method, and unit testing them would make test suite brittle and refactoring difficult * use more clear coin variables * code 1 is reserved, use code 2 and sequence all errors * remove todo * Implement deadline for swap module module message. This is implemented in handler with a interface to easily apply to it to all messages, and separate msg validation concerns from the keeper * move allowed pools to params -- let pool and pool_test focus on pool domain logic, not parameter & governance concerns * update alias * add unitless implementatin of constant product liquidity pool to isolate and enapsulate liquidity logic. Swap methods interfaces are added, but implementation not yet added * nits and todos * add ErrInvalidPool * add tests for edge cases around pool depletion; add explicit panic for edge case that results in a pool reserve being zero; handle pool reinitialization if it is empty * touch up comments and flush out the rest of assertions * add data structures for keeper state storage separate from pool domain objects, and improve structure for easier querying * rename pool name to pool key for events * add support for a denominated pool that uses sdk.Coins and sdk.Coin arguments, keeping tracking of the units in the base pool. This gives nice separation between pool logic, and coin/denom logic * refactor keeper to use new records for storage, and implement pool deposit using the denominated pool * address previous PR comment - reminder for migration if changing account permissions * msg deposit should validate that denoms are not equal * add godoc comments * golint and some poolName -> poolID cleanup * implement adding liquidity to an existing pool * hardcode pools in sims * touch up comment * withdraw keeper logic * withdraw type updates * add withdraw msg tx handler * initial withdraw test * fix panic * use new denominated pool with existing shares * fix: check args on deposit cmd * add slippage limit check for depositing to an existing pool * send coins just before event emission * check liquidity returned is greater than zero for both coins; ensure returned number of shares are greater than zero * add deadline to msgwithdraw * register msgwithdraw * scaffold msgwithdraw types test * register the correct msg * modify swap functions to also return the amount paid for the pool swap fee. This will be used to calculate slippage and for event tracking * add slippage types * add expected withdrawal coins * calculate slippage against expected coins * update withdraw keeper tests * spelling, improve comments on add liquidity math * typo * typo * grammer * typo / grammer * remove pool_id from withdraw msg * add slippage to tx cmd * TestWithdraw_Partial * nit * add withdraw no pool, no deposit record tests * drop event check on partial withdraw test * fix broken link * fix broken link * resolve merge conflicts * ensure swap fee can not be equal to 1; add full implementation of swap pool methods; these implementation ensure that the pool invariant is always greater or equal to the previous invariant * refactor duplicated code into private swap methods * add runtime assertion to always ensure invariant is greater or equal to the previous invariant sub fee on swaps * improve comments for base pool swap functions * add swap exact input and output methods to denominated pool that wrap the base pool interface for swapping * comment touch ups * more comment touchups * fix msg deposit struct tag (#943) * use better name for swap calculation private methods * nits: golint * fix misspelling in method name * Add HARD token governance committee for Hard module (#941) * add hard gov token committee * revisions: update migration * revisions: update test/data file * initial revisions * add TokenCommittee JSONMarshal test * fix SetPermissions method * remove BaseCommittee Type field * add incentive params to allowed params * Add SWP token governance committee for Swap module (#946) * add swp token commitee to migration * update test, add gen export utility method * final revisions: add TODO * remove slippage from withdraw to use min values for coins; add additional validation test cases * update alias for swap module * add withdraw tests to handler for increased coverage; note: first pass, improvements still yet to be made here * refact withdraw keeper to use min amounts; panic for cases that do not happen in normal situations * lint fixes * use total shares to track if pool should be deleted; add more in depth withdraw comment * add exact args for withdraw cmd * extract record update methods * update depositor share record if it exists -- do not overwrite an existing record; ensures no loss of shares if the same address deposits more than once * Swap queries: deposit, pool, pools (#949) * query deposits types * implement deposit querier keeper methods * query deposits CLI * query deposits REST * query types for pool/pools * pool/pools querier keeper methods * pool/pools CLI * pool/pools REST * basic pool/pools query tests * basic deposit querier test * iterate share records via owner bytes * nit: add example for querying deposits by owner only Co-authored-by: karzak * feat: add REST tx handler for swap LP withdrawals Co-authored-by: Nick DeLuca Co-authored-by: Denali Marsh Co-authored-by: denalimarsh Co-authored-by: karzak * expand incentive cli query docs Co-authored-by: Nick DeLuca Co-authored-by: Denali Marsh Co-authored-by: denalimarsh Co-authored-by: karzak * minor update to godoc comment Co-authored-by: Nick DeLuca Co-authored-by: Denali Marsh Co-authored-by: denalimarsh Co-authored-by: karzak --- app/app.go | 8 +- migrate/v0_15/migrate.go | 3 +- x/cdp/keeper/keeper.go | 4 +- x/hard/keeper/keeper.go | 4 +- x/incentive/alias.go | 312 +++++++++--------- x/incentive/client/cli/query.go | 229 ++++---------- x/incentive/client/rest/query.go | 220 +++---------- x/incentive/genesis.go | 15 +- x/incentive/genesis_test.go | 1 + x/incentive/handler_test.go | 1 + x/incentive/keeper/hooks.go | 12 + x/incentive/keeper/keeper.go | 63 ++++ x/incentive/keeper/keeper_test.go | 79 +++++ x/incentive/keeper/querier.go | 284 ++++++----------- x/incentive/keeper/rewards_swap.go | 88 ++++- x/incentive/keeper/rewards_swap_init_test.go | 195 ++++++++++++ x/incentive/keeper/rewards_swap_sync_test.go | 317 +++++++++++++++++++ x/incentive/keeper/rewards_swap_test.go | 18 +- x/incentive/keeper/unit_test.go | 4 +- x/incentive/types/claims.go | 66 ++++ x/incentive/types/claims_test.go | 172 ++++++---- x/incentive/types/expected_keepers.go | 3 +- x/incentive/types/genesis.go | 16 +- x/incentive/types/genesis_test.go | 1 + x/incentive/types/keys.go | 5 +- x/incentive/types/params.go | 1 + x/incentive/types/querier.go | 189 +++-------- x/swap/keeper/hooks.go | 30 +- x/swap/keeper/keeper.go | 10 + x/swap/types/expected_keepers.go | 8 +- 30 files changed, 1427 insertions(+), 931 deletions(-) create mode 100644 x/incentive/keeper/rewards_swap_init_test.go create mode 100644 x/incentive/keeper/rewards_swap_sync_test.go diff --git a/app/app.go b/app/app.go index 9a12aa7b..3e2d3696 100644 --- a/app/app.go +++ b/app/app.go @@ -390,7 +390,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio app.accountKeeper, app.supplyKeeper, ) - app.swapKeeper = swap.NewKeeper( + swapKeeper := swap.NewKeeper( app.cdc, keys[swap.StoreKey], swapSubspace, @@ -406,11 +406,11 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio &hardKeeper, app.accountKeeper, &stakingKeeper, - app.swapKeeper, + &swapKeeper, ) // register the staking hooks - // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks + // NOTE: These keepers are passed by reference above, so they will contain these hooks. app.stakingKeeper = *stakingKeeper.SetHooks( staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks(), app.incentiveKeeper.Hooks())) @@ -418,6 +418,8 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio app.hardKeeper = *hardKeeper.SetHooks(hard.NewMultiHARDHooks(app.incentiveKeeper.Hooks())) + app.swapKeeper = *swapKeeper.SetHooks(app.incentiveKeeper.Hooks()) + // create the module manager (Note: Any module instantiated in the module manager that is later modified // must be passed by reference here.) app.mm = module.NewManager( diff --git a/migrate/v0_15/migrate.go b/migrate/v0_15/migrate.go index 6eeb981a..a0e3f8c8 100644 --- a/migrate/v0_15/migrate.go +++ b/migrate/v0_15/migrate.go @@ -448,9 +448,10 @@ func Incentive(incentiveGS v0_14incentive.GenesisState) v0_15incentive.GenesisSt hardSupplyAccumulationTimes, hardBorrowAccumulationTimes, hardDelegatorAccumulationTimes, - v0_15incentive.DefaultGenesisAccumulationTimes, // There is no previous swap rewards to accumulation starts at genesis time. + v0_15incentive.DefaultGenesisAccumulationTimes, // There is no previous swap rewards so accumulation starts at genesis time. usdxMintingClaims, hardClaims, delegatorClaims, + v0_15incentive.DefaultSwapClaims, ) } diff --git a/x/cdp/keeper/keeper.go b/x/cdp/keeper/keeper.go index 80bfb1c8..9bb5a329 100644 --- a/x/cdp/keeper/keeper.go +++ b/x/cdp/keeper/keeper.go @@ -45,10 +45,10 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, } } -// SetHooks sets the cdp keeper hooks +// SetHooks adds hooks to the keeper. func (k *Keeper) SetHooks(hooks types.CDPHooks) *Keeper { if k.hooks != nil { - panic("cannot set validator hooks twice") + panic("cannot set cdp hooks twice") } k.hooks = hooks return k diff --git a/x/hard/keeper/keeper.go b/x/hard/keeper/keeper.go index c728519d..0a24ec41 100644 --- a/x/hard/keeper/keeper.go +++ b/x/hard/keeper/keeper.go @@ -45,10 +45,10 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, } } -// SetHooks sets the cdp keeper hooks +// SetHooks adds hooks to the keeper. func (k *Keeper) SetHooks(hooks types.HARDHooks) *Keeper { if k.hooks != nil { - panic("cannot set validator hooks twice") + panic("cannot set hard hooks twice") } k.hooks = hooks return k diff --git a/x/incentive/alias.go b/x/incentive/alias.go index e02db75b..f916f791 100644 --- a/x/incentive/alias.go +++ b/x/incentive/alias.go @@ -1,8 +1,5 @@ -// nolint -// autogenerated code using github.com/rigelrozanski/multitool -// aliases generated for the following subdirectories: -// ALIASGEN: github.com/kava-labs/kava/x/incentive/types -// ALIASGEN: github.com/kava-labs/kava/x/incentive/keeper +// Code generated by aliasgen tool (github.com/rhuairahrighairidh/aliasgen) DO NOT EDIT. + package incentive import ( @@ -11,166 +8,175 @@ import ( ) const ( - USDXMintingClaimType = types.USDXMintingClaimType - HardLiquidityProviderClaimType = types.HardLiquidityProviderClaimType - BondDenom = types.BondDenom - EventTypeClaim = types.EventTypeClaim - EventTypeRewardPeriod = types.EventTypeRewardPeriod - EventTypeClaimPeriod = types.EventTypeClaimPeriod - EventTypeClaimPeriodExpiry = types.EventTypeClaimPeriodExpiry - AttributeValueCategory = types.AttributeValueCategory - AttributeKeyClaimedBy = types.AttributeKeyClaimedBy - AttributeKeyClaimAmount = types.AttributeKeyClaimAmount - AttributeKeyClaimType = types.AttributeKeyClaimType - AttributeKeyRewardPeriod = types.AttributeKeyRewardPeriod - AttributeKeyClaimPeriod = types.AttributeKeyClaimPeriod - ModuleName = types.ModuleName - StoreKey = types.StoreKey - RouterKey = types.RouterKey - DefaultParamspace = types.DefaultParamspace - QuerierRoute = types.QuerierRoute - Small = types.Small - Medium = types.Medium - Large = types.Large - QueryGetRewards = types.QueryGetRewards - QueryGetHardRewards = types.QueryGetHardRewards - QueryGetHardRewardsUnsynced = types.QueryGetHardRewardsUnsynced - QueryGetUSDXMintingRewards = types.QueryGetUSDXMintingRewards - QueryGetUSDXMintingRewardsUnsynced = types.QueryGetUSDXMintingRewardsUnsynced - QueryGetRewardFactors = types.QueryGetRewardFactors - QueryGetParams = types.QueryGetParams - QueryGetRewardPeriods = types.QueryGetRewardPeriods - QueryGetClaimPeriods = types.QueryGetClaimPeriods - RestClaimCollateralType = types.RestClaimCollateralType - RestClaimOwner = types.RestClaimOwner - RestClaimType = types.RestClaimType - RestUnsynced = types.RestUnsynced - BeginningOfMonth = keeper.BeginningOfMonth - MidMonth = keeper.MidMonth - PaymentHour = keeper.PaymentHour + BeginningOfMonth = keeper.BeginningOfMonth + MidMonth = keeper.MidMonth + PaymentHour = keeper.PaymentHour + AttributeKeyClaimAmount = types.AttributeKeyClaimAmount + AttributeKeyClaimPeriod = types.AttributeKeyClaimPeriod + AttributeKeyClaimType = types.AttributeKeyClaimType + AttributeKeyClaimedBy = types.AttributeKeyClaimedBy + AttributeKeyRewardPeriod = types.AttributeKeyRewardPeriod + AttributeValueCategory = types.AttributeValueCategory + BondDenom = types.BondDenom + DefaultParamspace = types.DefaultParamspace + DelegatorClaimType = types.DelegatorClaimType + EventTypeClaim = types.EventTypeClaim + EventTypeClaimPeriod = types.EventTypeClaimPeriod + EventTypeClaimPeriodExpiry = types.EventTypeClaimPeriodExpiry + EventTypeRewardPeriod = types.EventTypeRewardPeriod + HardLiquidityProviderClaimType = types.HardLiquidityProviderClaimType + Large = types.Large + Medium = types.Medium + ModuleName = types.ModuleName + QuerierRoute = types.QuerierRoute + QueryGetDelegatorRewards = types.QueryGetDelegatorRewards + QueryGetHardRewards = types.QueryGetHardRewards + QueryGetParams = types.QueryGetParams + QueryGetRewardFactors = types.QueryGetRewardFactors + QueryGetSwapRewards = types.QueryGetSwapRewards + QueryGetUSDXMintingRewards = types.QueryGetUSDXMintingRewards + RestClaimCollateralType = types.RestClaimCollateralType + RestClaimOwner = types.RestClaimOwner + RestClaimType = types.RestClaimType + RestUnsynced = types.RestUnsynced + RouterKey = types.RouterKey + Small = types.Small + StoreKey = types.StoreKey + SwapClaimType = types.SwapClaimType + USDXMintingClaimType = types.USDXMintingClaimType ) var ( - // functions aliases - GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength - NewUSDXMintingClaim = types.NewUSDXMintingClaim - NewHardLiquidityProviderClaim = types.NewHardLiquidityProviderClaim - NewMultiRewardPeriod = types.NewMultiRewardPeriod - NewRewardIndex = types.NewRewardIndex - NewMultiRewardIndex = types.NewMultiRewardIndex - RegisterCodec = types.RegisterCodec - NewGenesisState = types.NewGenesisState - DefaultGenesisState = types.DefaultGenesisState - NewGenesisAccumulationTime = types.NewGenesisAccumulationTime - NewMsgClaimUSDXMintingReward = types.NewMsgClaimUSDXMintingReward - NewMsgClaimUSDXMintingRewardVVesting = types.NewMsgClaimUSDXMintingRewardVVesting - NewMsgClaimHardReward = types.NewMsgClaimHardReward - NewMsgClaimHardRewardVVesting = types.NewMsgClaimHardRewardVVesting - NewParams = types.NewParams - DefaultParams = types.DefaultParams - ParamKeyTable = types.ParamKeyTable - NewRewardPeriod = types.NewRewardPeriod - NewMultiplier = types.NewMultiplier - NewPeriod = types.NewPeriod - NewQueryRewardsParams = types.NewQueryRewardsParams - NewQueryUSDXMintingRewardsParams = types.NewQueryUSDXMintingRewardsParams - NewQueryUSDXMintingRewardsUnsyncedParams = types.NewQueryUSDXMintingRewardsUnsyncedParams - NewQueryHardRewardsParams = types.NewQueryHardRewardsParams - NewQueryHardRewardsUnsyncedParams = types.NewQueryHardRewardsUnsyncedParams - NewQueryRewardFactorsParams = types.NewQueryRewardFactorsParams - NewRewardFactor = types.NewRewardFactor - NewKeeper = keeper.NewKeeper - NewQuerier = keeper.NewQuerier - CalculateTimeElapsed = keeper.CalculateTimeElapsed + // function aliases + CalculateTimeElapsed = keeper.CalculateTimeElapsed + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + DefaultGenesisState = types.DefaultGenesisState + DefaultParams = types.DefaultParams + GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength + NewAccumulator = types.NewAccumulator + NewDelegatorClaim = types.NewDelegatorClaim + NewGenesisAccumulationTime = types.NewGenesisAccumulationTime + NewGenesisState = types.NewGenesisState + NewHardLiquidityProviderClaim = types.NewHardLiquidityProviderClaim + NewMsgClaimDelegatorReward = types.NewMsgClaimDelegatorReward + NewMsgClaimDelegatorRewardVVesting = types.NewMsgClaimDelegatorRewardVVesting + NewMsgClaimHardReward = types.NewMsgClaimHardReward + NewMsgClaimHardRewardVVesting = types.NewMsgClaimHardRewardVVesting + NewMsgClaimUSDXMintingReward = types.NewMsgClaimUSDXMintingReward + NewMsgClaimUSDXMintingRewardVVesting = types.NewMsgClaimUSDXMintingRewardVVesting + NewMultiRewardIndex = types.NewMultiRewardIndex + NewMultiRewardPeriod = types.NewMultiRewardPeriod + NewMultiplier = types.NewMultiplier + NewParams = types.NewParams + NewPeriod = types.NewPeriod + NewQueryGetRewardFactorsResponse = types.NewQueryGetRewardFactorsResponse + NewQueryRewardsParams = types.NewQueryRewardsParams + NewRewardIndex = types.NewRewardIndex + NewRewardPeriod = types.NewRewardPeriod + NewSwapClaim = types.NewSwapClaim + NewUSDXMintingClaim = types.NewUSDXMintingClaim + ParamKeyTable = types.ParamKeyTable + RegisterCodec = types.RegisterCodec // variable aliases - ModuleCdc = types.ModuleCdc - ErrClaimNotFound = types.ErrClaimNotFound - ErrRewardPeriodNotFound = types.ErrRewardPeriodNotFound - ErrInvalidAccountType = types.ErrInvalidAccountType - ErrNoClaimsFound = types.ErrNoClaimsFound - ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance - ErrAccountNotFound = types.ErrAccountNotFound - ErrInvalidMultiplier = types.ErrInvalidMultiplier - ErrZeroClaim = types.ErrZeroClaim - ErrClaimExpired = types.ErrClaimExpired - ErrInvalidClaimType = types.ErrInvalidClaimType - ErrInvalidClaimOwner = types.ErrInvalidClaimOwner - USDXMintingClaimKeyPrefix = types.USDXMintingClaimKeyPrefix - USDXMintingRewardFactorKeyPrefix = types.USDXMintingRewardFactorKeyPrefix - PreviousUSDXMintingRewardAccrualTimeKeyPrefix = types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix - HardLiquidityClaimKeyPrefix = types.HardLiquidityClaimKeyPrefix - HardSupplyRewardIndexesKeyPrefix = types.HardSupplyRewardIndexesKeyPrefix - PreviousHardSupplyRewardAccrualTimeKeyPrefix = types.PreviousHardSupplyRewardAccrualTimeKeyPrefix - HardBorrowRewardIndexesKeyPrefix = types.HardBorrowRewardIndexesKeyPrefix - PreviousHardBorrowRewardAccrualTimeKeyPrefix = types.PreviousHardBorrowRewardAccrualTimeKeyPrefix - DelegatorRewardIndexesKeyPrefix = types.DelegatorRewardIndexesKeyPrefix - PreviousDelegatorRewardAccrualTimeKeyPrefix = types.PreviousDelegatorRewardAccrualTimeKeyPrefix - USDXMintingRewardDenom = types.USDXMintingRewardDenom - HardLiquidityRewardDenom = types.HardLiquidityRewardDenom - KeyUSDXMintingRewardPeriods = types.KeyUSDXMintingRewardPeriods - KeyHardSupplyRewardPeriods = types.KeyHardSupplyRewardPeriods - KeyHardBorrowRewardPeriods = types.KeyHardBorrowRewardPeriods - KeyDelegatorRewardPeriods = types.KeyDelegatorRewardPeriods - KeyClaimEnd = types.KeyClaimEnd - KeyMultipliers = types.KeyMultipliers DefaultActive = types.DefaultActive - DefaultRewardPeriods = types.DefaultRewardPeriods + DefaultClaimEnd = types.DefaultClaimEnd + DefaultDelegatorClaims = types.DefaultDelegatorClaims + DefaultGenesisAccumulationTimes = types.DefaultGenesisAccumulationTimes + DefaultHardClaims = types.DefaultHardClaims DefaultMultiRewardPeriods = types.DefaultMultiRewardPeriods DefaultMultipliers = types.DefaultMultipliers - DefaultDelegatorClaims = types.DefaultDelegatorClaims + DefaultRewardPeriods = types.DefaultRewardPeriods + DefaultSwapClaims = types.DefaultSwapClaims DefaultUSDXClaims = types.DefaultUSDXClaims - DefaultHardClaims = types.DefaultHardClaims - DefaultGenesisAccumulationTimes = types.DefaultGenesisAccumulationTimes - DefaultClaimEnd = types.DefaultClaimEnd + DelegatorClaimKeyPrefix = types.DelegatorClaimKeyPrefix + DelegatorRewardIndexesKeyPrefix = types.DelegatorRewardIndexesKeyPrefix + ErrAccountNotFound = types.ErrAccountNotFound + ErrClaimExpired = types.ErrClaimExpired + ErrClaimNotFound = types.ErrClaimNotFound + ErrDecreasingRewardFactor = types.ErrDecreasingRewardFactor + ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance + ErrInvalidAccountType = types.ErrInvalidAccountType + ErrInvalidClaimOwner = types.ErrInvalidClaimOwner + ErrInvalidClaimType = types.ErrInvalidClaimType + ErrInvalidMultiplier = types.ErrInvalidMultiplier + ErrNoClaimsFound = types.ErrNoClaimsFound + ErrRewardPeriodNotFound = types.ErrRewardPeriodNotFound + ErrZeroClaim = types.ErrZeroClaim GovDenom = types.GovDenom - PrincipalDenom = types.PrincipalDenom + HardBorrowRewardIndexesKeyPrefix = types.HardBorrowRewardIndexesKeyPrefix + HardLiquidityClaimKeyPrefix = types.HardLiquidityClaimKeyPrefix + HardLiquidityRewardDenom = types.HardLiquidityRewardDenom + HardSupplyRewardIndexesKeyPrefix = types.HardSupplyRewardIndexesKeyPrefix IncentiveMacc = types.IncentiveMacc + KeyClaimEnd = types.KeyClaimEnd + KeyDelegatorRewardPeriods = types.KeyDelegatorRewardPeriods + KeyHardBorrowRewardPeriods = types.KeyHardBorrowRewardPeriods + KeyHardSupplyRewardPeriods = types.KeyHardSupplyRewardPeriods + KeyMultipliers = types.KeyMultipliers + KeySwapRewardPeriods = types.KeySwapRewardPeriods + KeyUSDXMintingRewardPeriods = types.KeyUSDXMintingRewardPeriods + ModuleCdc = types.ModuleCdc + PreviousDelegatorRewardAccrualTimeKeyPrefix = types.PreviousDelegatorRewardAccrualTimeKeyPrefix + PreviousHardBorrowRewardAccrualTimeKeyPrefix = types.PreviousHardBorrowRewardAccrualTimeKeyPrefix + PreviousHardSupplyRewardAccrualTimeKeyPrefix = types.PreviousHardSupplyRewardAccrualTimeKeyPrefix + PreviousSwapRewardAccrualTimeKeyPrefix = types.PreviousSwapRewardAccrualTimeKeyPrefix + PreviousUSDXMintingRewardAccrualTimeKeyPrefix = types.PreviousUSDXMintingRewardAccrualTimeKeyPrefix + PrincipalDenom = types.PrincipalDenom + SwapClaimKeyPrefix = types.SwapClaimKeyPrefix + SwapRewardIndexesKeyPrefix = types.SwapRewardIndexesKeyPrefix + USDXMintingClaimKeyPrefix = types.USDXMintingClaimKeyPrefix + USDXMintingRewardDenom = types.USDXMintingRewardDenom + USDXMintingRewardFactorKeyPrefix = types.USDXMintingRewardFactorKeyPrefix ) type ( - Claim = types.Claim - Claims = types.Claims - BaseClaim = types.BaseClaim - BaseMultiClaim = types.BaseMultiClaim - USDXMintingClaim = types.USDXMintingClaim - USDXMintingClaims = types.USDXMintingClaims - HardLiquidityProviderClaim = types.HardLiquidityProviderClaim - HardLiquidityProviderClaims = types.HardLiquidityProviderClaims - MultiRewardPeriod = types.MultiRewardPeriod - MultiRewardPeriods = types.MultiRewardPeriods - RewardIndex = types.RewardIndex - RewardIndexes = types.RewardIndexes - MultiRewardIndex = types.MultiRewardIndex - MultiRewardIndexes = types.MultiRewardIndexes - SupplyKeeper = types.SupplyKeeper - StakingKeeper = types.StakingKeeper - CdpKeeper = types.CdpKeeper - HardKeeper = types.HardKeeper - AccountKeeper = types.AccountKeeper - CDPHooks = types.CDPHooks - HARDHooks = types.HARDHooks - GenesisState = types.GenesisState - GenesisAccumulationTime = types.GenesisAccumulationTime - GenesisAccumulationTimes = types.GenesisAccumulationTimes - MsgClaimUSDXMintingReward = types.MsgClaimUSDXMintingReward - MsgClaimUSDXMintingRewardVVesting = types.MsgClaimUSDXMintingRewardVVesting - MsgClaimHardReward = types.MsgClaimHardReward - MsgClaimHardRewardVVesting = types.MsgClaimHardRewardVVesting - Params = types.Params - RewardPeriod = types.RewardPeriod - RewardPeriods = types.RewardPeriods - Multiplier = types.Multiplier - Multipliers = types.Multipliers - MultiplierName = types.MultiplierName - QueryRewardsParams = types.QueryRewardsParams - QueryUSDXMintingRewardsParams = types.QueryUSDXMintingRewardsParams - QueryUSDXMintingRewardsUnsyncedParams = types.QueryUSDXMintingRewardsUnsyncedParams - QueryHardRewardsParams = types.QueryHardRewardsParams - QueryHardRewardsUnsyncedParams = types.QueryHardRewardsUnsyncedParams - QueryRewardFactorsParams = types.QueryRewardFactorsParams - RewardFactor = types.RewardFactor - RewardFactors = types.RewardFactors - Hooks = keeper.Hooks - Keeper = keeper.Keeper + Hooks = keeper.Hooks + Keeper = keeper.Keeper + AccountKeeper = types.AccountKeeper + Accumulator = types.Accumulator + BaseClaim = types.BaseClaim + BaseMultiClaim = types.BaseMultiClaim + CDPHooks = types.CDPHooks + CdpKeeper = types.CdpKeeper + Claim = types.Claim + Claims = types.Claims + DelegatorClaim = types.DelegatorClaim + DelegatorClaims = types.DelegatorClaims + GenesisAccumulationTime = types.GenesisAccumulationTime + GenesisAccumulationTimes = types.GenesisAccumulationTimes + GenesisState = types.GenesisState + HARDHooks = types.HARDHooks + HardKeeper = types.HardKeeper + HardLiquidityProviderClaim = types.HardLiquidityProviderClaim + HardLiquidityProviderClaims = types.HardLiquidityProviderClaims + MsgClaimDelegatorReward = types.MsgClaimDelegatorReward + MsgClaimDelegatorRewardVVesting = types.MsgClaimDelegatorRewardVVesting + MsgClaimHardReward = types.MsgClaimHardReward + MsgClaimHardRewardVVesting = types.MsgClaimHardRewardVVesting + MsgClaimUSDXMintingReward = types.MsgClaimUSDXMintingReward + MsgClaimUSDXMintingRewardVVesting = types.MsgClaimUSDXMintingRewardVVesting + MultiRewardIndex = types.MultiRewardIndex + MultiRewardIndexes = types.MultiRewardIndexes + MultiRewardPeriod = types.MultiRewardPeriod + MultiRewardPeriods = types.MultiRewardPeriods + Multiplier = types.Multiplier + MultiplierName = types.MultiplierName + Multipliers = types.Multipliers + ParamSubspace = types.ParamSubspace + Params = types.Params + QueryGetRewardFactorsResponse = types.QueryGetRewardFactorsResponse + QueryRewardsParams = types.QueryRewardsParams + RewardIndex = types.RewardIndex + RewardIndexes = types.RewardIndexes + RewardPeriod = types.RewardPeriod + RewardPeriods = types.RewardPeriods + StakingKeeper = types.StakingKeeper + SupplyKeeper = types.SupplyKeeper + SwapClaim = types.SwapClaim + SwapClaims = types.SwapClaims + SwapKeeper = types.SwapKeeper + USDXMintingClaim = types.USDXMintingClaim + USDXMintingClaims = types.USDXMintingClaims ) diff --git a/x/incentive/client/cli/query.go b/x/incentive/client/cli/query.go index 0c9e43f1..d4fe01c9 100644 --- a/x/incentive/client/cli/query.go +++ b/x/incentive/client/cli/query.go @@ -21,8 +21,15 @@ const ( flagType = "type" flagUnsynced = "unsynced" flagDenom = "denom" + + typeDelegator = "delegator" + typeHard = "hard" + typeUSDXMinting = "usdx-minting" + typeSwap = "swap" ) +var rewardTypes = []string{typeDelegator, typeHard, typeUSDXMinting, typeSwap} + // GetQueryCmd returns the cli query commands for the incentive module func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { incentiveQueryCmd := &cobra.Command{ @@ -52,13 +59,14 @@ func queryRewardsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { $ %s query %s rewards --type hard $ %s query %s rewards --type usdx-minting $ %s query %s rewards --type delegator + $ %s query %s rewards --type swap $ %s query %s rewards --type hard --owner kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw - $ %s query %s rewards --type hard --unsynced true + $ %s query %s rewards --type hard --unsynced `, version.ClientName, types.ModuleName, version.ClientName, types.ModuleName, version.ClientName, types.ModuleName, version.ClientName, types.ModuleName, version.ClientName, types.ModuleName, version.ClientName, types.ModuleName, - version.ClientName, types.ModuleName)), + version.ClientName, types.ModuleName, version.ClientName, types.ModuleName)), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) @@ -76,90 +84,52 @@ func queryRewardsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { } switch strings.ToLower(strType) { - case "hard": - var claims types.HardLiquidityProviderClaims - if boolUnsynced { - params := types.NewQueryHardRewardsUnsyncedParams(page, limit, owner) - claims, err = executeHardRewardsUnsyncedQuery(queryRoute, cdc, cliCtx, params) - if err != nil { - return err - } - } else { - params := types.NewQueryHardRewardsParams(page, limit, owner) - claims, err = executeHardRewardsQuery(queryRoute, cdc, cliCtx, params) - if err != nil { - return err - } + case typeHard: + params := types.NewQueryRewardsParams(page, limit, owner, boolUnsynced) + claims, err := executeHardRewardsQuery(queryRoute, cdc, cliCtx, params) + if err != nil { + return err } return cliCtx.PrintOutput(claims) - case "usdx-minting": - var claims types.USDXMintingClaims - if boolUnsynced { - params := types.NewQueryUSDXMintingRewardsUnsyncedParams(page, limit, owner) - claims, err = executeUSDXMintingRewardsUnsyncedQuery(queryRoute, cdc, cliCtx, params) - if err != nil { - return err - } - } else { - params := types.NewQueryUSDXMintingRewardsParams(page, limit, owner) - claims, err = executeUSDXMintingRewardsQuery(queryRoute, cdc, cliCtx, params) - if err != nil { - return err - } + case typeUSDXMinting: + params := types.NewQueryRewardsParams(page, limit, owner, boolUnsynced) + claims, err := executeUSDXMintingRewardsQuery(queryRoute, cdc, cliCtx, params) + if err != nil { + return err } return cliCtx.PrintOutput(claims) - case "delegator": - var claims types.DelegatorClaims - if boolUnsynced { - params := types.NewQueryDelegatorRewardsUnsyncedParams(page, limit, owner) - claims, err = executeDelegatorRewardsUnsyncedQuery(queryRoute, cdc, cliCtx, params) - if err != nil { - return err - } - } else { - params := types.NewQueryDelegatorRewardsParams(page, limit, owner) - claims, err = executeDelegatorRewardsQuery(queryRoute, cdc, cliCtx, params) - if err != nil { - return err - } + case typeDelegator: + params := types.NewQueryRewardsParams(page, limit, owner, boolUnsynced) + claims, err := executeDelegatorRewardsQuery(queryRoute, cdc, cliCtx, params) + if err != nil { + return err + } + return cliCtx.PrintOutput(claims) + case typeSwap: + params := types.NewQueryRewardsParams(page, limit, owner, boolUnsynced) + claims, err := executeSwapRewardsQuery(queryRoute, cdc, cliCtx, params) + if err != nil { + return err } return cliCtx.PrintOutput(claims) default: - var hardClaims types.HardLiquidityProviderClaims - var usdxMintingClaims types.USDXMintingClaims - var delegatorClaims types.DelegatorClaims - if boolUnsynced { - paramsHard := types.NewQueryHardRewardsUnsyncedParams(page, limit, owner) - hardClaims, err = executeHardRewardsUnsyncedQuery(queryRoute, cdc, cliCtx, paramsHard) - if err != nil { - return err - } - paramsUSDXMinting := types.NewQueryUSDXMintingRewardsUnsyncedParams(page, limit, owner) - usdxMintingClaims, err = executeUSDXMintingRewardsUnsyncedQuery(queryRoute, cdc, cliCtx, paramsUSDXMinting) - if err != nil { - return err - } - paramsDelegator := types.NewQueryDelegatorRewardsUnsyncedParams(page, limit, owner) - delegatorClaims, err = executeDelegatorRewardsUnsyncedQuery(queryRoute, cdc, cliCtx, paramsDelegator) - if err != nil { - return err - } - } else { - paramsHard := types.NewQueryHardRewardsParams(page, limit, owner) - hardClaims, err = executeHardRewardsQuery(queryRoute, cdc, cliCtx, paramsHard) - if err != nil { - return err - } - paramsUSDXMinting := types.NewQueryUSDXMintingRewardsParams(page, limit, owner) - usdxMintingClaims, err = executeUSDXMintingRewardsQuery(queryRoute, cdc, cliCtx, paramsUSDXMinting) - if err != nil { - return err - } - paramsDelegator := types.NewQueryDelegatorRewardsParams(page, limit, owner) - delegatorClaims, err = executeDelegatorRewardsQuery(queryRoute, cdc, cliCtx, paramsDelegator) - if err != nil { - return err - } + params := types.NewQueryRewardsParams(page, limit, owner, boolUnsynced) + + hardClaims, err := executeHardRewardsQuery(queryRoute, cdc, cliCtx, params) + if err != nil { + return err + } + usdxMintingClaims, err := executeUSDXMintingRewardsQuery(queryRoute, cdc, cliCtx, params) + if err != nil { + return err + } + delegatorClaims, err := executeDelegatorRewardsQuery(queryRoute, cdc, cliCtx, params) + if err != nil { + return err + } + swapClaims, err := executeSwapRewardsQuery(queryRoute, cdc, cliCtx, params) + if err != nil { + return err } if len(hardClaims) > 0 { cliCtx.PrintOutput(hardClaims) @@ -170,13 +140,16 @@ func queryRewardsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { if len(delegatorClaims) > 0 { cliCtx.PrintOutput(delegatorClaims) } + if len(swapClaims) > 0 { + cliCtx.PrintOutput(swapClaims) + } } return nil }, } cmd.Flags().String(flagOwner, "", "(optional) filter by owner address") - cmd.Flags().String(flagType, "", "(optional) filter by reward type") - cmd.Flags().String(flagUnsynced, "", "(optional) get unsynced claims") + cmd.Flags().String(flagType, "", fmt.Sprintf("(optional) filter by a reward type: %s", strings.Join(rewardTypes, "|"))) + cmd.Flags().Bool(flagUnsynced, false, "(optional) get unsynced claims") cmd.Flags().Int(flags.FlagPage, 1, "pagination page rewards of to to query for") cmd.Flags().Int(flags.FlagLimit, 100, "pagination limit of rewards to query for") return cmd @@ -213,47 +186,32 @@ func queryRewardFactorsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "reward-factors", Short: "get current global reward factors", - Long: strings.TrimSpace(`get current global reward factors: - - Example: - $ kvcli q hard reward-factors - $ kvcli q hard reward-factors --denom bnb`, - ), - Args: cobra.NoArgs, + Long: `Get current global reward factors for all reward types.`, + Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - denom := viper.GetString(flagDenom) - - // Construct query with params - params := types.NewQueryRewardFactorsParams(denom) - bz, err := cdc.MarshalJSON(params) - if err != nil { - return err - } - // Execute query route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetRewardFactors) - res, height, err := cliCtx.QueryWithData(route, bz) + res, height, err := cliCtx.QueryWithData(route, nil) if err != nil { return err } cliCtx = cliCtx.WithHeight(height) // Decode and print results - var rewardFactors types.RewardFactors - if err := cdc.UnmarshalJSON(res, &rewardFactors); err != nil { + var response types.QueryGetRewardFactorsResponse + if err := cdc.UnmarshalJSON(res, &response); err != nil { return fmt.Errorf("failed to unmarshal reward factors: %w", err) } - return cliCtx.PrintOutput(rewardFactors) + return cliCtx.PrintOutput(response) }, } cmd.Flags().String(flagDenom, "", "(optional) filter reward factors by denom") return cmd } -func executeHardRewardsQuery(queryRoute string, cdc *codec.Codec, cliCtx context.CLIContext, - params types.QueryHardRewardsParams) (types.HardLiquidityProviderClaims, error) { +func executeHardRewardsQuery(queryRoute string, cdc *codec.Codec, cliCtx context.CLIContext, params types.QueryRewardsParams) (types.HardLiquidityProviderClaims, error) { bz, err := cdc.MarshalJSON(params) if err != nil { return types.HardLiquidityProviderClaims{}, err @@ -275,31 +233,7 @@ func executeHardRewardsQuery(queryRoute string, cdc *codec.Codec, cliCtx context return claims, nil } -func executeHardRewardsUnsyncedQuery(queryRoute string, cdc *codec.Codec, cliCtx context.CLIContext, - params types.QueryHardRewardsUnsyncedParams) (types.HardLiquidityProviderClaims, error) { - bz, err := cdc.MarshalJSON(params) - if err != nil { - return types.HardLiquidityProviderClaims{}, err - } - - route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetHardRewardsUnsynced) - res, height, err := cliCtx.QueryWithData(route, bz) - if err != nil { - return types.HardLiquidityProviderClaims{}, err - } - - cliCtx = cliCtx.WithHeight(height) - - var claims types.HardLiquidityProviderClaims - if err := cdc.UnmarshalJSON(res, &claims); err != nil { - return types.HardLiquidityProviderClaims{}, fmt.Errorf("failed to unmarshal claims: %w", err) - } - - return claims, nil -} - -func executeUSDXMintingRewardsQuery(queryRoute string, cdc *codec.Codec, cliCtx context.CLIContext, - params types.QueryUSDXMintingRewardsParams) (types.USDXMintingClaims, error) { +func executeUSDXMintingRewardsQuery(queryRoute string, cdc *codec.Codec, cliCtx context.CLIContext, params types.QueryRewardsParams) (types.USDXMintingClaims, error) { bz, err := cdc.MarshalJSON(params) if err != nil { return types.USDXMintingClaims{}, err @@ -321,31 +255,7 @@ func executeUSDXMintingRewardsQuery(queryRoute string, cdc *codec.Codec, cliCtx return claims, nil } -func executeUSDXMintingRewardsUnsyncedQuery(queryRoute string, cdc *codec.Codec, cliCtx context.CLIContext, - params types.QueryUSDXMintingRewardsUnsyncedParams) (types.USDXMintingClaims, error) { - bz, err := cdc.MarshalJSON(params) - if err != nil { - return types.USDXMintingClaims{}, err - } - - route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetUSDXMintingRewardsUnsynced) - res, height, err := cliCtx.QueryWithData(route, bz) - if err != nil { - return types.USDXMintingClaims{}, err - } - - cliCtx = cliCtx.WithHeight(height) - - var claims types.USDXMintingClaims - if err := cdc.UnmarshalJSON(res, &claims); err != nil { - return types.USDXMintingClaims{}, fmt.Errorf("failed to unmarshal claims: %w", err) - } - - return claims, nil -} - -func executeDelegatorRewardsQuery(queryRoute string, cdc *codec.Codec, cliCtx context.CLIContext, - params types.QueryDelegatorRewardsParams) (types.DelegatorClaims, error) { +func executeDelegatorRewardsQuery(queryRoute string, cdc *codec.Codec, cliCtx context.CLIContext, params types.QueryRewardsParams) (types.DelegatorClaims, error) { bz, err := cdc.MarshalJSON(params) if err != nil { return types.DelegatorClaims{}, err @@ -367,24 +277,23 @@ func executeDelegatorRewardsQuery(queryRoute string, cdc *codec.Codec, cliCtx co return claims, nil } -func executeDelegatorRewardsUnsyncedQuery(queryRoute string, cdc *codec.Codec, cliCtx context.CLIContext, - params types.QueryDelegatorRewardsUnsyncedParams) (types.DelegatorClaims, error) { +func executeSwapRewardsQuery(queryRoute string, cdc *codec.Codec, cliCtx context.CLIContext, params types.QueryRewardsParams) (types.SwapClaims, error) { bz, err := cdc.MarshalJSON(params) if err != nil { - return types.DelegatorClaims{}, err + return types.SwapClaims{}, err } - route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetDelegatorRewardsUnsynced) + route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetSwapRewards) res, height, err := cliCtx.QueryWithData(route, bz) if err != nil { - return types.DelegatorClaims{}, err + return types.SwapClaims{}, err } cliCtx = cliCtx.WithHeight(height) - var claims types.DelegatorClaims + var claims types.SwapClaims if err := cdc.UnmarshalJSON(res, &claims); err != nil { - return types.DelegatorClaims{}, fmt.Errorf("failed to unmarshal claims: %w", err) + return types.SwapClaims{}, fmt.Errorf("failed to unmarshal claims: %w", err) } return claims, nil diff --git a/x/incentive/client/rest/query.go b/x/incentive/client/rest/query.go index 1701392b..c13110c5 100644 --- a/x/incentive/client/rest/query.go +++ b/x/incentive/client/rest/query.go @@ -58,40 +58,18 @@ func queryRewardsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { } } - if unsynced { - switch strings.ToLower(rewardType) { - case "hard": - params := types.NewQueryHardRewardsUnsyncedParams(page, limit, owner) - executeHardRewardsUnsyncedQuery(w, cliCtx, params) - case "usdx_minting": - params := types.NewQueryUSDXMintingRewardsUnsyncedParams(page, limit, owner) - executeUSDXMintingRewardsUnsyncedQuery(w, cliCtx, params) - case "delegator": - params := types.NewQueryDelegatorRewardsUnsyncedParams(page, limit, owner) - executeDelegatorRewardsUnsyncedQuery(w, cliCtx, params) - default: - hardParams := types.NewQueryHardRewardsUnsyncedParams(page, limit, owner) - usdxMintingParams := types.NewQueryUSDXMintingRewardsUnsyncedParams(page, limit, owner) - delegatorParams := types.NewQueryDelegatorRewardsUnsyncedParams(page, limit, owner) - executeAllUnsyncedRewardQueries(w, cliCtx, hardParams, usdxMintingParams, delegatorParams) - } - } else { - switch strings.ToLower(rewardType) { - case "hard": - params := types.NewQueryHardRewardsParams(page, limit, owner) - executeHardRewardsQuery(w, cliCtx, params) - case "usdx_minting": - params := types.NewQueryUSDXMintingRewardsParams(page, limit, owner) - executeUSDXMintingRewardsQuery(w, cliCtx, params) - case "delegator": - params := types.NewQueryDelegatorRewardsParams(page, limit, owner) - executeDelegatorRewardsQuery(w, cliCtx, params) - default: - hardParams := types.NewQueryHardRewardsParams(page, limit, owner) - usdxMintingParams := types.NewQueryUSDXMintingRewardsParams(page, limit, owner) - delegatorParams := types.NewQueryDelegatorRewardsParams(page, limit, owner) - executeAllRewardQueries(w, cliCtx, hardParams, usdxMintingParams, delegatorParams) - } + params := types.NewQueryRewardsParams(page, limit, owner, unsynced) + switch strings.ToLower(rewardType) { + case "hard": + executeHardRewardsQuery(w, cliCtx, params) + case "usdx_minting": + executeUSDXMintingRewardsQuery(w, cliCtx, params) + case "delegator": + executeDelegatorRewardsQuery(w, cliCtx, params) + case "swap": + executeSwapRewardsQuery(w, cliCtx, params) + default: + executeAllRewardQueries(w, cliCtx, params) } } } @@ -118,43 +96,25 @@ func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { func queryRewardFactorsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - _, _, _, err := rest.ParseHTTPArgsWithLimit(r, 0) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - // Parse the query height cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) if !ok { return } - var denom string - - if x := r.URL.Query().Get(RestDenom); len(x) != 0 { - denom = strings.TrimSpace(x) - } - - params := types.NewQueryRewardFactorsParams(denom) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - route := fmt.Sprintf("custom/%s/%s", types.ModuleName, types.QueryGetRewardFactors) - res, height, err := cliCtx.QueryWithData(route, bz) - cliCtx = cliCtx.WithHeight(height) + + 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 executeHardRewardsQuery(w http.ResponseWriter, cliCtx context.CLIContext, params types.QueryHardRewardsParams) { +func executeHardRewardsQuery(w http.ResponseWriter, cliCtx context.CLIContext, params types.QueryRewardsParams) { bz, err := cliCtx.Codec.MarshalJSON(params) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) @@ -171,24 +131,7 @@ func executeHardRewardsQuery(w http.ResponseWriter, cliCtx context.CLIContext, p rest.PostProcessResponse(w, cliCtx, res) } -func executeHardRewardsUnsyncedQuery(w http.ResponseWriter, cliCtx context.CLIContext, params types.QueryHardRewardsUnsyncedParams) { - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) - return - } - - res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetHardRewardsUnsynced), bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - cliCtx = cliCtx.WithHeight(height) - rest.PostProcessResponse(w, cliCtx, res) -} - -func executeUSDXMintingRewardsQuery(w http.ResponseWriter, cliCtx context.CLIContext, params types.QueryUSDXMintingRewardsParams) { +func executeUSDXMintingRewardsQuery(w http.ResponseWriter, cliCtx context.CLIContext, params types.QueryRewardsParams) { bz, err := cliCtx.Codec.MarshalJSON(params) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) @@ -205,24 +148,7 @@ func executeUSDXMintingRewardsQuery(w http.ResponseWriter, cliCtx context.CLICon rest.PostProcessResponse(w, cliCtx, res) } -func executeUSDXMintingRewardsUnsyncedQuery(w http.ResponseWriter, cliCtx context.CLIContext, params types.QueryUSDXMintingRewardsUnsyncedParams) { - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) - return - } - - res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetUSDXMintingRewardsUnsynced), bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - cliCtx = cliCtx.WithHeight(height) - rest.PostProcessResponse(w, cliCtx, res) -} - -func executeDelegatorRewardsQuery(w http.ResponseWriter, cliCtx context.CLIContext, params types.QueryDelegatorRewardsParams) { +func executeDelegatorRewardsQuery(w http.ResponseWriter, cliCtx context.CLIContext, params types.QueryRewardsParams) { bz, err := cliCtx.Codec.MarshalJSON(params) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) @@ -239,14 +165,14 @@ func executeDelegatorRewardsQuery(w http.ResponseWriter, cliCtx context.CLIConte rest.PostProcessResponse(w, cliCtx, res) } -func executeDelegatorRewardsUnsyncedQuery(w http.ResponseWriter, cliCtx context.CLIContext, params types.QueryDelegatorRewardsUnsyncedParams) { +func executeSwapRewardsQuery(w http.ResponseWriter, cliCtx context.CLIContext, params types.QueryRewardsParams) { bz, err := cliCtx.Codec.MarshalJSON(params) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) return } - res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetDelegatorRewardsUnsynced), bz) + res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetSwapRewards), bz) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -256,16 +182,14 @@ func executeDelegatorRewardsUnsyncedQuery(w http.ResponseWriter, cliCtx context. rest.PostProcessResponse(w, cliCtx, res) } -func executeAllRewardQueries(w http.ResponseWriter, cliCtx context.CLIContext, - hardParams types.QueryHardRewardsParams, usdxMintingParams types.QueryUSDXMintingRewardsParams, - delegatorParams types.QueryDelegatorRewardsParams) { - hardBz, err := cliCtx.Codec.MarshalJSON(hardParams) +func executeAllRewardQueries(w http.ResponseWriter, cliCtx context.CLIContext, params types.QueryRewardsParams) { + + paramsBz, err := cliCtx.Codec.MarshalJSON(params) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) return } - - hardRes, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetHardRewards), hardBz) + hardRes, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetHardRewards), paramsBz) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -273,13 +197,7 @@ func executeAllRewardQueries(w http.ResponseWriter, cliCtx context.CLIContext, var hardClaims types.HardLiquidityProviderClaims cliCtx.Codec.MustUnmarshalJSON(hardRes, &hardClaims) - usdxMintingBz, err := cliCtx.Codec.MarshalJSON(usdxMintingParams) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) - return - } - - usdxMintingRes, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetUSDXMintingRewards), usdxMintingBz) + usdxMintingRes, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetUSDXMintingRewards), paramsBz) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -287,81 +205,7 @@ func executeAllRewardQueries(w http.ResponseWriter, cliCtx context.CLIContext, var usdxMintingClaims types.USDXMintingClaims cliCtx.Codec.MustUnmarshalJSON(usdxMintingRes, &usdxMintingClaims) - delegatorBz, err := cliCtx.Codec.MarshalJSON(delegatorParams) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) - return - } - - delegatorRes, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetDelegatorRewards), delegatorBz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - var delegatorClaims types.DelegatorClaims - cliCtx.Codec.MustUnmarshalJSON(delegatorRes, &delegatorClaims) - - cliCtx = cliCtx.WithHeight(height) - - type rewardResult struct { - HardClaims types.HardLiquidityProviderClaims `json:"hard_claims" yaml:"hard_claims"` - UsdxMintingClaims types.USDXMintingClaims `json:"usdx_minting_claims" yaml:"usdx_minting_claims"` - DelegatorClaims types.DelegatorClaims `json:"delegator_claims" yaml:"delegator_claims"` - } - - res := rewardResult{ - HardClaims: hardClaims, - UsdxMintingClaims: usdxMintingClaims, - DelegatorClaims: delegatorClaims, - } - - resBz, err := cliCtx.Codec.MarshalJSON(res) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal result: %s", err)) - return - } - - rest.PostProcessResponse(w, cliCtx, resBz) -} - -func executeAllUnsyncedRewardQueries(w http.ResponseWriter, cliCtx context.CLIContext, - hardParams types.QueryHardRewardsUnsyncedParams, usdxMintingParams types.QueryUSDXMintingRewardsUnsyncedParams, - delegatorParams types.QueryDelegatorRewardsUnsyncedParams) { - hardBz, err := cliCtx.Codec.MarshalJSON(hardParams) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) - return - } - - hardRes, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetHardRewardsUnsynced), hardBz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - var hardClaims types.HardLiquidityProviderClaims - cliCtx.Codec.MustUnmarshalJSON(hardRes, &hardClaims) - - usdxMintingBz, err := cliCtx.Codec.MarshalJSON(usdxMintingParams) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) - return - } - - usdxMintingRes, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetUSDXMintingRewardsUnsynced), usdxMintingBz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - var usdxMintingClaims types.USDXMintingClaims - cliCtx.Codec.MustUnmarshalJSON(usdxMintingRes, &usdxMintingClaims) - - delegatorBz, err := cliCtx.Codec.MarshalJSON(delegatorParams) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to marshal query params: %s", err)) - return - } - - delegatorRes, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetDelegatorRewardsUnsynced), delegatorBz) + delegatorRes, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetDelegatorRewards), paramsBz) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -369,18 +213,28 @@ func executeAllUnsyncedRewardQueries(w http.ResponseWriter, cliCtx context.CLICo var delegatorClaims types.DelegatorClaims cliCtx.Codec.MustUnmarshalJSON(delegatorRes, &delegatorClaims) + swapRes, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetSwapRewards), paramsBz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + var swapClaims types.SwapClaims + cliCtx.Codec.MustUnmarshalJSON(swapRes, &swapClaims) + cliCtx = cliCtx.WithHeight(height) type rewardResult struct { HardClaims types.HardLiquidityProviderClaims `json:"hard_claims" yaml:"hard_claims"` UsdxMintingClaims types.USDXMintingClaims `json:"usdx_minting_claims" yaml:"usdx_minting_claims"` DelegatorClaims types.DelegatorClaims `json:"delegator_claims" yaml:"delegator_claims"` + SwapClaims types.SwapClaims `json:"swap_claims" yaml:"swap_claims"` } res := rewardResult{ HardClaims: hardClaims, UsdxMintingClaims: usdxMintingClaims, DelegatorClaims: delegatorClaims, + SwapClaims: swapClaims, } resBz, err := cliCtx.Codec.MarshalJSON(res) diff --git a/x/incentive/genesis.go b/x/incentive/genesis.go index 08b8abf7..e5dc225a 100644 --- a/x/incentive/genesis.go +++ b/x/incentive/genesis.go @@ -115,6 +115,11 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeep } k.SetDelegatorClaim(ctx, claim) } + + // TODO no synchronization of swap claims as it is about to be removed in a subsequent PR + for _, claim := range gs.SwapClaims { + k.SetSwapClaim(ctx, claim) + } } // ExportGenesis export genesis state for incentive module @@ -124,10 +129,12 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState { usdxClaims := k.GetAllUSDXMintingClaims(ctx) hardClaims := k.GetAllHardLiquidityProviderClaims(ctx) delegatorClaims := k.GetAllDelegatorClaims(ctx) + swapClaims := k.GetAllSwapClaims(ctx) synchronizedUsdxClaims := types.USDXMintingClaims{} synchronizedHardClaims := types.HardLiquidityProviderClaims{} synchronizedDelegatorClaims := types.DelegatorClaims{} + // TODO no synchronization of swap claims as it is about to be removed in a subsequent PR for _, usdxClaim := range usdxClaims { claim, err := k.SynchronizeUSDXMintingClaim(ctx, usdxClaim) @@ -222,7 +229,9 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState { swapGats = append(swapGats, gat) } - return types.NewGenesisState(params, usdxMintingGats, hardSupplyGats, - hardBorrowGats, delegatorGats, swapGats, synchronizedUsdxClaims, - synchronizedHardClaims, synchronizedDelegatorClaims) + return types.NewGenesisState( + params, + usdxMintingGats, hardSupplyGats, hardBorrowGats, delegatorGats, swapGats, + synchronizedUsdxClaims, synchronizedHardClaims, synchronizedDelegatorClaims, swapClaims, + ) } diff --git a/x/incentive/genesis_test.go b/x/incentive/genesis_test.go index 17f66aae..74c63428 100644 --- a/x/incentive/genesis_test.go +++ b/x/incentive/genesis_test.go @@ -77,6 +77,7 @@ func (suite *GenesisTestSuite) SetupTest() { incentive.DefaultUSDXClaims, incentive.DefaultHardClaims, incentive.DefaultDelegatorClaims, + incentive.DefaultSwapClaims, ) tApp.InitializeFromGenesisStatesWithTime( suite.genesisTime, diff --git a/x/incentive/handler_test.go b/x/incentive/handler_test.go index ba8b9845..bfa21348 100644 --- a/x/incentive/handler_test.go +++ b/x/incentive/handler_test.go @@ -60,6 +60,7 @@ func (suite *HandlerTestSuite) SetupTest() { incentive.DefaultUSDXClaims, incentive.DefaultHardClaims, incentive.DefaultDelegatorClaims, + incentive.DefaultSwapClaims, ) tApp.InitializeFromGenesisStates(authGS, app.GenesisState{incentive.ModuleName: incentive.ModuleCdc.MustMarshalJSON(incentiveGS)}, NewCDPGenStateMulti(), NewPricefeedGenStateMulti()) diff --git a/x/incentive/keeper/hooks.go b/x/incentive/keeper/hooks.go index 2264637e..7ea59886 100644 --- a/x/incentive/keeper/hooks.go +++ b/x/incentive/keeper/hooks.go @@ -6,6 +6,7 @@ import ( cdptypes "github.com/kava-labs/kava/x/cdp/types" hardtypes "github.com/kava-labs/kava/x/hard/types" + swaptypes "github.com/kava-labs/kava/x/swap/types" ) // Hooks wrapper struct for hooks @@ -16,6 +17,7 @@ type Hooks struct { var _ cdptypes.CDPHooks = Hooks{} var _ hardtypes.HARDHooks = Hooks{} var _ stakingtypes.StakingHooks = Hooks{} +var _ swaptypes.SwapHooks = Hooks{} // Hooks create new incentive hooks func (k Keeper) Hooks() Hooks { return Hooks{k} } @@ -149,3 +151,13 @@ func (h Hooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) // AfterValidatorRemoved runs after a validator is removed func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { } + +// ------------------- Swap Module Hooks ------------------- + +func (h Hooks) AfterPoolDepositCreated(ctx sdk.Context, poolID string, depositor sdk.AccAddress, _ sdk.Int) { + h.k.InitializeSwapReward(ctx, poolID, depositor) +} + +func (h Hooks) BeforePoolDepositModified(ctx sdk.Context, poolID string, depositor sdk.AccAddress, sharesOwned sdk.Int) { + h.k.SynchronizeSwapReward(ctx, poolID, depositor, sharesOwned) +} diff --git a/x/incentive/keeper/keeper.go b/x/incentive/keeper/keeper.go index 7ef1c6fa..14fe7d7b 100644 --- a/x/incentive/keeper/keeper.go +++ b/x/incentive/keeper/keeper.go @@ -259,6 +259,55 @@ func (k Keeper) GetAllDelegatorClaims(ctx sdk.Context) types.DelegatorClaims { return cs } +// GetSwapClaim returns the claim in the store corresponding the the input address. +func (k Keeper) GetSwapClaim(ctx sdk.Context, addr sdk.AccAddress) (types.SwapClaim, bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.SwapClaimKeyPrefix) + bz := store.Get(addr) + if bz == nil { + return types.SwapClaim{}, false + } + var c types.SwapClaim + k.cdc.MustUnmarshalBinaryBare(bz, &c) + return c, true +} + +// SetSwapClaim sets the claim in the store corresponding to the input address. +func (k Keeper) SetSwapClaim(ctx sdk.Context, c types.SwapClaim) { + store := prefix.NewStore(ctx.KVStore(k.key), types.SwapClaimKeyPrefix) + bz := k.cdc.MustMarshalBinaryBare(c) + store.Set(c.Owner, bz) +} + +// DeleteSwapClaim deletes the claim in the store corresponding to the input address. +func (k Keeper) DeleteSwapClaim(ctx sdk.Context, owner sdk.AccAddress) { + store := prefix.NewStore(ctx.KVStore(k.key), types.SwapClaimKeyPrefix) + store.Delete(owner) +} + +// IterateSwapClaims iterates over all claim objects in the store and preforms a callback function +func (k Keeper) IterateSwapClaims(ctx sdk.Context, cb func(c types.SwapClaim) (stop bool)) { + store := prefix.NewStore(ctx.KVStore(k.key), types.SwapClaimKeyPrefix) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var c types.SwapClaim + k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &c) + if cb(c) { + break + } + } +} + +// GetAllSwapClaims returns all Claim objects in the store +func (k Keeper) GetAllSwapClaims(ctx sdk.Context) types.SwapClaims { + cs := types.SwapClaims{} + k.IterateSwapClaims(ctx, func(c types.SwapClaim) (stop bool) { + cs = append(cs, c) + return false + }) + return cs +} + // SetHardSupplyRewardIndexes sets the current reward indexes for an individual denom func (k Keeper) SetHardSupplyRewardIndexes(ctx sdk.Context, denom string, indexes types.RewardIndexes) { store := prefix.NewStore(ctx.KVStore(k.key), types.HardSupplyRewardIndexesKeyPrefix) @@ -428,6 +477,20 @@ func (k Keeper) GetSwapRewardIndexes(ctx sdk.Context, poolID string) (types.Rewa return rewardIndexes, true } +// IterateSwapRewardIndexes iterates over all swap reward index objects in the store and preforms a callback function +func (k Keeper) IterateSwapRewardIndexes(ctx sdk.Context, cb func(poolID string, indexes types.RewardIndexes) (stop bool)) { + store := prefix.NewStore(ctx.KVStore(k.key), types.SwapRewardIndexesKeyPrefix) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var indexes types.RewardIndexes + k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &indexes) + if cb(string(iterator.Key()), indexes) { + break + } + } +} + // GetSwapRewardAccrualTime fetches the last time rewards were accrued for a swap pool. func (k Keeper) GetSwapRewardAccrualTime(ctx sdk.Context, poolID string) (blockTime time.Time, found bool) { store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousSwapRewardAccrualTimeKeyPrefix) diff --git a/x/incentive/keeper/keeper_test.go b/x/incentive/keeper/keeper_test.go index df3f3118..8fa24fca 100644 --- a/x/incentive/keeper/keeper_test.go +++ b/x/incentive/keeper/keeper_test.go @@ -82,6 +82,46 @@ func (suite *KeeperTestSuite) TestIterateUSDXMintingClaims() { suite.Require().Equal(len(suite.addrs), len(claims)) } +func (suite *KeeperTestSuite) TestGetSetDeleteSwapClaims() { + suite.SetupApp() + c := types.NewSwapClaim(suite.addrs[0], arbitraryCoins(), nonEmptyMultiRewardIndexes) + + _, found := suite.keeper.GetSwapClaim(suite.ctx, suite.addrs[0]) + suite.Require().False(found) + + suite.Require().NotPanics(func() { + suite.keeper.SetSwapClaim(suite.ctx, c) + }) + testC, found := suite.keeper.GetSwapClaim(suite.ctx, suite.addrs[0]) + suite.Require().True(found) + suite.Require().Equal(c, testC) + + suite.Require().NotPanics(func() { + suite.keeper.DeleteSwapClaim(suite.ctx, suite.addrs[0]) + }) + _, found = suite.keeper.GetSwapClaim(suite.ctx, suite.addrs[0]) + suite.Require().False(found) +} + +func (suite *KeeperTestSuite) TestIterateSwapClaims() { + suite.SetupApp() + claims := types.SwapClaims{ + types.NewSwapClaim(suite.addrs[0], arbitraryCoins(), nonEmptyMultiRewardIndexes), + types.NewSwapClaim(suite.addrs[1], nil, nil), // different claim to the first + } + for _, claim := range claims { + suite.keeper.SetSwapClaim(suite.ctx, claim) + } + + var actualClaims types.SwapClaims + suite.keeper.IterateSwapClaims(suite.ctx, func(c types.SwapClaim) bool { + actualClaims = append(actualClaims, c) + return false + }) + + suite.Require().Equal(claims, actualClaims) +} + func (suite *KeeperTestSuite) TestGetSetSwapRewardIndexes() { testCases := []struct { name string @@ -155,6 +195,45 @@ func (suite *KeeperTestSuite) TestGetSetSwapRewardIndexes() { } } +func (suite *KeeperTestSuite) TestIterateSwapRewardIndexes() { + suite.SetupApp() + multiIndexes := types.MultiRewardIndexes{ + { + CollateralType: "bnb/usdx", + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "swap", + RewardFactor: d("0.0000002"), + }, + { + CollateralType: "ukava", + RewardFactor: d("0.04"), + }, + }, + }, + { + CollateralType: "btcb/usdx", + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "hard", + RewardFactor: d("0.02"), + }, + }, + }, + } + for _, mi := range multiIndexes { + suite.keeper.SetSwapRewardIndexes(suite.ctx, mi.CollateralType, mi.RewardIndexes) + } + + var actualMultiIndexes types.MultiRewardIndexes + suite.keeper.IterateSwapRewardIndexes(suite.ctx, func(poolID string, i types.RewardIndexes) bool { + actualMultiIndexes = actualMultiIndexes.With(poolID, i) + return false + }) + + suite.Require().Equal(multiIndexes, actualMultiIndexes) +} + func (suite *KeeperTestSuite) TestGetSetSwapRewardAccrualTimes() { testCases := []struct { name string diff --git a/x/incentive/keeper/querier.go b/x/incentive/keeper/querier.go index ff593f5c..8a99cca0 100644 --- a/x/incentive/keeper/querier.go +++ b/x/incentive/keeper/querier.go @@ -17,18 +17,16 @@ func NewQuerier(k Keeper) sdk.Querier { switch path[0] { case types.QueryGetParams: return queryGetParams(ctx, req, k) + case types.QueryGetHardRewards: return queryGetHardRewards(ctx, req, k) - case types.QueryGetHardRewardsUnsynced: - return queryGetHardRewardsUnsynced(ctx, req, k) case types.QueryGetUSDXMintingRewards: return queryGetUSDXMintingRewards(ctx, req, k) - case types.QueryGetUSDXMintingRewardsUnsynced: - return queryGetUSDXMintingRewardsUnsynced(ctx, req, k) case types.QueryGetDelegatorRewards: return queryGetDelegatorRewards(ctx, req, k) - case types.QueryGetDelegatorRewardsUnsynced: - return queryGetDelegatorRewardsUnsynced(ctx, req, k) + case types.QueryGetSwapRewards: + return queryGetSwapRewards(ctx, req, k) + case types.QueryGetRewardFactors: return queryGetRewardFactors(ctx, req, k) default: @@ -51,7 +49,7 @@ func queryGetParams(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, e } func queryGetHardRewards(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { - var params types.QueryHardRewardsParams + var params types.QueryRewardsParams err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) @@ -77,45 +75,10 @@ func queryGetHardRewards(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]by paginatedHardClaims = hardClaims[startH:endH] } - var augmentedHardClaims types.HardLiquidityProviderClaims - for _, claim := range paginatedHardClaims { - augmentedClaim := k.SimulateHardSynchronization(ctx, claim) - augmentedHardClaims = append(augmentedHardClaims, augmentedClaim) - } - - // Marshal Hard claims - bz, err := codec.MarshalJSONIndent(k.cdc, augmentedHardClaims) - if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) - } - return bz, nil -} - -func queryGetHardRewardsUnsynced(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { - var params types.QueryHardRewardsUnsyncedParams - err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) - if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) - } - owner := len(params.Owner) > 0 - - var hardClaims types.HardLiquidityProviderClaims - switch { - case owner: - hardClaim, foundHardClaim := k.GetHardLiquidityProviderClaim(ctx, params.Owner) - if foundHardClaim { - hardClaims = append(hardClaims, hardClaim) + if !params.Unsynchronized { + for i, claim := range paginatedHardClaims { + paginatedHardClaims[i] = k.SimulateHardSynchronization(ctx, claim) } - default: - hardClaims = k.GetAllHardLiquidityProviderClaims(ctx) - } - - var paginatedHardClaims types.HardLiquidityProviderClaims - startH, endH := client.Paginate(len(hardClaims), params.Page, params.Limit, 100) - if startH < 0 || endH < 0 { - paginatedHardClaims = types.HardLiquidityProviderClaims{} - } else { - paginatedHardClaims = hardClaims[startH:endH] } // Marshal Hard claims @@ -127,7 +90,7 @@ func queryGetHardRewardsUnsynced(ctx sdk.Context, req abci.RequestQuery, k Keepe } func queryGetUSDXMintingRewards(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { - var params types.QueryUSDXMintingRewardsParams + var params types.QueryRewardsParams err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) @@ -153,45 +116,10 @@ func queryGetUSDXMintingRewards(ctx sdk.Context, req abci.RequestQuery, k Keeper paginatedUsdxMintingClaims = usdxMintingClaims[startU:endU] } - var augmentedUsdxMintingClaims types.USDXMintingClaims - for _, claim := range paginatedUsdxMintingClaims { - augmentedClaim := k.SimulateUSDXMintingSynchronization(ctx, claim) - augmentedUsdxMintingClaims = append(augmentedUsdxMintingClaims, augmentedClaim) - } - - // Marshal USDX minting claims - bz, err := codec.MarshalJSONIndent(k.cdc, augmentedUsdxMintingClaims) - if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) - } - return bz, nil -} - -func queryGetUSDXMintingRewardsUnsynced(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { - var params types.QueryUSDXMintingRewardsUnsyncedParams - err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) - if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) - } - owner := len(params.Owner) > 0 - - var usdxMintingClaims types.USDXMintingClaims - switch { - case owner: - usdxMintingClaim, foundUsdxMintingClaim := k.GetUSDXMintingClaim(ctx, params.Owner) - if foundUsdxMintingClaim { - usdxMintingClaims = append(usdxMintingClaims, usdxMintingClaim) + if !params.Unsynchronized { + for i, claim := range paginatedUsdxMintingClaims { + paginatedUsdxMintingClaims[i] = k.SimulateUSDXMintingSynchronization(ctx, claim) } - default: - usdxMintingClaims = k.GetAllUSDXMintingClaims(ctx) - } - - var paginatedUsdxMintingClaims types.USDXMintingClaims - startU, endU := client.Paginate(len(usdxMintingClaims), params.Page, params.Limit, 100) - if startU < 0 || endU < 0 { - paginatedUsdxMintingClaims = types.USDXMintingClaims{} - } else { - paginatedUsdxMintingClaims = usdxMintingClaims[startU:endU] } // Marshal USDX minting claims @@ -203,7 +131,7 @@ func queryGetUSDXMintingRewardsUnsynced(ctx sdk.Context, req abci.RequestQuery, } func queryGetDelegatorRewards(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { - var params types.QueryDelegatorRewardsParams + var params types.QueryRewardsParams err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) @@ -229,45 +157,10 @@ func queryGetDelegatorRewards(ctx sdk.Context, req abci.RequestQuery, k Keeper) paginatedDelegatorClaims = delegatorClaims[startH:endH] } - var augmentedDelegatorClaims types.DelegatorClaims - for _, claim := range paginatedDelegatorClaims { - augmentedClaim := k.SimulateDelegatorSynchronization(ctx, claim) - augmentedDelegatorClaims = append(augmentedDelegatorClaims, augmentedClaim) - } - - // Marshal Hard claims - bz, err := codec.MarshalJSONIndent(k.cdc, augmentedDelegatorClaims) - if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) - } - return bz, nil -} - -func queryGetDelegatorRewardsUnsynced(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { - var params types.QueryDelegatorRewardsUnsyncedParams - err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) - if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) - } - owner := len(params.Owner) > 0 - - var delegatorClaims types.DelegatorClaims - switch { - case owner: - delegatorClaim, foundHardClaim := k.GetDelegatorClaim(ctx, params.Owner) - if foundHardClaim { - delegatorClaims = append(delegatorClaims, delegatorClaim) + if !params.Unsynchronized { + for i, claim := range paginatedDelegatorClaims { + paginatedDelegatorClaims[i] = k.SimulateDelegatorSynchronization(ctx, claim) } - default: - delegatorClaims = k.GetAllDelegatorClaims(ctx) - } - - var paginatedDelegatorClaims types.DelegatorClaims - startH, endH := client.Paginate(len(delegatorClaims), params.Page, params.Limit, 100) - if startH < 0 || endH < 0 { - paginatedDelegatorClaims = types.DelegatorClaims{} - } else { - paginatedDelegatorClaims = delegatorClaims[startH:endH] } // Marshal Hard claims @@ -278,89 +171,92 @@ func queryGetDelegatorRewardsUnsynced(ctx sdk.Context, req abci.RequestQuery, k return bz, nil } -func queryGetRewardFactors(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { - var params types.QueryRewardFactorsParams +func queryGetSwapRewards(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { + var params types.QueryRewardsParams err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } + owner := len(params.Owner) > 0 - var rewardFactors types.RewardFactors - if len(params.Denom) > 0 { - // Fetch reward factors for a single denom - rewardFactor := types.RewardFactor{} - rewardFactor.Denom = params.Denom + var claims types.SwapClaims + switch { + case owner: + claim, found := k.GetSwapClaim(ctx, params.Owner) + if found { + claims = append(claims, claim) + } + default: + claims = k.GetAllSwapClaims(ctx) + } - usdxMintingRewardFactor, found := k.GetUSDXMintingRewardFactor(ctx, params.Denom) - if found { - rewardFactor.USDXMintingRewardFactor = usdxMintingRewardFactor - } - hardSupplyRewardIndexes, found := k.GetHardSupplyRewardIndexes(ctx, params.Denom) - if found { - rewardFactor.HardSupplyRewardFactors = hardSupplyRewardIndexes - } - hardBorrowRewardIndexes, found := k.GetHardBorrowRewardIndexes(ctx, params.Denom) - if found { - rewardFactor.HardBorrowRewardFactors = hardBorrowRewardIndexes - } - delegatorRewardIndexes, found := k.GetDelegatorRewardIndexes(ctx, params.Denom) - if found { - rewardFactor.DelegatorRewardFactors = delegatorRewardIndexes - } - rewardFactors = append(rewardFactors, rewardFactor) + var paginatedClaims types.SwapClaims + startH, endH := client.Paginate(len(claims), params.Page, params.Limit, 100) + if startH < 0 || endH < 0 { + paginatedClaims = types.SwapClaims{} } else { - rewardFactorMap := make(map[string]types.RewardFactor) + paginatedClaims = claims[startH:endH] + } - // Populate mapping with usdx minting reward factors - k.IterateUSDXMintingRewardFactors(ctx, func(denom string, factor sdk.Dec) (stop bool) { - rewardFactor := types.RewardFactor{Denom: denom, USDXMintingRewardFactor: factor} - rewardFactorMap[denom] = rewardFactor - return false - }) - - // Populate mapping with Hard supply reward factors - k.IterateHardSupplyRewardIndexes(ctx, func(denom string, indexes types.RewardIndexes) (stop bool) { - rewardFactor, ok := rewardFactorMap[denom] - if !ok { - rewardFactor = types.RewardFactor{Denom: denom, HardSupplyRewardFactors: indexes} - } else { - rewardFactor.HardSupplyRewardFactors = indexes + if !params.Unsynchronized { + for i, claim := range paginatedClaims { + syncedClaim, found := k.GetSynchronizedSwapClaim(ctx, claim.Owner) + if !found { + panic("previously found claim should still be found") } - rewardFactorMap[denom] = rewardFactor - return false - }) - - // Populate mapping with Hard borrow reward factors - k.IterateHardBorrowRewardIndexes(ctx, func(denom string, indexes types.RewardIndexes) (stop bool) { - rewardFactor, ok := rewardFactorMap[denom] - if !ok { - rewardFactor = types.RewardFactor{Denom: denom, HardBorrowRewardFactors: indexes} - } else { - rewardFactor.HardBorrowRewardFactors = indexes - } - rewardFactorMap[denom] = rewardFactor - return false - }) - - // Populate mapping with delegator reward factors - k.IterateDelegatorRewardIndexes(ctx, func(denom string, indexes types.RewardIndexes) (stop bool) { - rewardFactor, ok := rewardFactorMap[denom] - if !ok { - rewardFactor = types.RewardFactor{Denom: denom, DelegatorRewardFactors: indexes} - } else { - rewardFactor.DelegatorRewardFactors = indexes - } - rewardFactorMap[denom] = rewardFactor - return false - }) - - // Translate mapping to slice - for _, val := range rewardFactorMap { - rewardFactors = append(rewardFactors, val) + paginatedClaims[i] = syncedClaim } } - bz, err := codec.MarshalJSONIndent(types.ModuleCdc, rewardFactors) + // Marshal claims + bz, err := codec.MarshalJSONIndent(k.cdc, paginatedClaims) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + return bz, nil +} + +func queryGetRewardFactors(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { + + var usdxFactors types.RewardIndexes + k.IterateUSDXMintingRewardFactors(ctx, func(collateralType string, factor sdk.Dec) (stop bool) { + usdxFactors = usdxFactors.With(collateralType, factor) + return false + }) + + var supplyFactors types.MultiRewardIndexes + k.IterateHardSupplyRewardIndexes(ctx, func(denom string, indexes types.RewardIndexes) (stop bool) { + supplyFactors = supplyFactors.With(denom, indexes) + return false + }) + + var borrowFactors types.MultiRewardIndexes + k.IterateHardBorrowRewardIndexes(ctx, func(denom string, indexes types.RewardIndexes) (stop bool) { + borrowFactors = borrowFactors.With(denom, indexes) + return false + }) + + var delegatorFactors types.MultiRewardIndexes + k.IterateDelegatorRewardIndexes(ctx, func(denom string, indexes types.RewardIndexes) (stop bool) { + delegatorFactors = delegatorFactors.With(denom, indexes) + return false + }) + + var swapFactors types.MultiRewardIndexes + k.IterateSwapRewardIndexes(ctx, func(poolID string, indexes types.RewardIndexes) (stop bool) { + swapFactors = swapFactors.With(poolID, indexes) + return false + }) + + response := types.NewQueryGetRewardFactorsResponse( + usdxFactors, + supplyFactors, + borrowFactors, + delegatorFactors, + swapFactors, + ) + + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, response) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } diff --git a/x/incentive/keeper/rewards_swap.go b/x/incentive/keeper/rewards_swap.go index e5495169..3c20f3f0 100644 --- a/x/incentive/keeper/rewards_swap.go +++ b/x/incentive/keeper/rewards_swap.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kava-labs/kava/x/incentive/types" @@ -24,10 +26,10 @@ func (k Keeper) AccumulateSwapRewards(ctx sdk.Context, rewardPeriod types.MultiR totalShares, found := k.swapKeeper.GetPoolShares(ctx, rewardPeriod.CollateralType) if !found { - totalShares = sdk.ZeroDec() + totalShares = sdk.ZeroInt() } - acc.Accumulate(rewardPeriod, totalShares, ctx.BlockTime()) + acc.Accumulate(rewardPeriod, totalShares.ToDec(), ctx.BlockTime()) k.SetSwapRewardAccrualTime(ctx, rewardPeriod.CollateralType, acc.PreviousAccumulationTime) if len(acc.Indexes) > 0 { @@ -35,3 +37,85 @@ func (k Keeper) AccumulateSwapRewards(ctx sdk.Context, rewardPeriod types.MultiR k.SetSwapRewardIndexes(ctx, rewardPeriod.CollateralType, acc.Indexes) } } + +// InitializeSwapReward creates a new claim with zero rewards and indexes matching the global indexes. +// If the claim already exists it just updates the indexes. +func (k Keeper) InitializeSwapReward(ctx sdk.Context, poolID string, owner sdk.AccAddress) { + claim, found := k.GetSwapClaim(ctx, owner) + if !found { + claim = types.NewSwapClaim(owner, sdk.Coins{}, nil) + } + + globalRewardIndexes, found := k.GetSwapRewardIndexes(ctx, poolID) + if !found { + globalRewardIndexes = types.RewardIndexes{} + } + claim.RewardIndexes = claim.RewardIndexes.With(poolID, globalRewardIndexes) + + k.SetSwapClaim(ctx, claim) +} + +// SynchronizeSwapReward updates the claim object by adding any accumulated rewards +// and updating the reward index value. +func (k Keeper) SynchronizeSwapReward(ctx sdk.Context, poolID string, owner sdk.AccAddress, shares sdk.Int) { + claim, found := k.GetSwapClaim(ctx, owner) + if !found { + return + } + claim = k.synchronizeSwapReward(ctx, claim, poolID, owner, shares) + + k.SetSwapClaim(ctx, claim) +} + +// synchronizeSwapReward updates the reward in a swap claim for one pool. +func (k *Keeper) synchronizeSwapReward(ctx sdk.Context, claim types.SwapClaim, poolID string, owner sdk.AccAddress, shares sdk.Int) types.SwapClaim { + globalRewardIndexes, found := k.GetSwapRewardIndexes(ctx, poolID) + if !found { + // The global factor is only not found if + // - the pool has not started accumulating rewards yet (either there is no reward specified in params, or the reward start time hasn't been hit) + // - OR it was wrongly deleted from state (factors should never be removed while unsynced claims exist) + // If not found we could either skip this sync, or assume the global factor is zero. + // Skipping will avoid storing unnecessary factors in the claim for non rewarded pools. + // And in the event a global factor is wrongly deleted, it will avoid this function panicking when calculating rewards. + return claim + } + + userRewardIndexes, found := claim.RewardIndexes.Get(poolID) + if !found { + // Normally the reward indexes should always be found. + // But if a pool was not rewarded then becomes rewarded (ie a reward period is added to params), then the indexes will be missing from claims for that pool. + // So given the reward period was just added, assume the starting value for any global reward indexes, which is an empty slice. + userRewardIndexes = types.RewardIndexes{} + } + + newRewards, err := k.CalculateRewards(userRewardIndexes, globalRewardIndexes, shares.ToDec()) + if err != nil { + // Global reward factors should never decrease, as it would lead to a negative update to claim.Rewards. + // This panics if a global reward factor decreases or disappears between the old and new indexes. + panic(fmt.Sprintf("corrupted global reward indexes found: %v", err)) + } + + claim.Reward = claim.Reward.Add(newRewards...) + claim.RewardIndexes = claim.RewardIndexes.With(poolID, globalRewardIndexes) + + return claim +} + +// GetSynchronizedSwapClaim fetches a swap claim from the store and syncs rewards for all pools. +func (k Keeper) GetSynchronizedSwapClaim(ctx sdk.Context, owner sdk.AccAddress) (types.SwapClaim, bool) { + claim, found := k.GetSwapClaim(ctx, owner) + if !found { + return types.SwapClaim{}, false + } + for _, indexes := range claim.RewardIndexes { + poolID := indexes.CollateralType + + shares, found := k.swapKeeper.GetDepositorSharesInPool(ctx, owner, poolID) + if !found { + shares = sdk.ZeroInt() + } + + claim = k.synchronizeSwapReward(ctx, claim, poolID, owner, shares) + } + return claim, true +} diff --git a/x/incentive/keeper/rewards_swap_init_test.go b/x/incentive/keeper/rewards_swap_init_test.go new file mode 100644 index 00000000..e2815195 --- /dev/null +++ b/x/incentive/keeper/rewards_swap_init_test.go @@ -0,0 +1,195 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + + "github.com/kava-labs/kava/x/incentive/types" +) + +// InitializeSwapRewardTests runs unit tests for the keeper.InitializeSwapReward method +// +// inputs +// - claim in store if it exists +// - global indexes in store +// +// outputs +// - sets or creates a claim +type InitializeSwapRewardTests struct { + unitTester +} + +func TestInitializeSwapReward(t *testing.T) { + suite.Run(t, new(InitializeSwapRewardTests)) +} + +func (suite *InitializeSwapRewardTests) TestClaimAddedWhenClaimDoesNotExistAndNoRewards() { + // When a claim doesn't exist, and a user deposits to a non-rewarded pool; + // then a claim is added with no rewards and no indexes + + poolID := "base/quote" + + // no global indexes stored as this pool is not rewarded + + owner := arbitraryAddress() + + suite.keeper.InitializeSwapReward(suite.ctx, poolID, owner) + + syncedClaim, found := suite.keeper.GetSwapClaim(suite.ctx, owner) + suite.True(found) + // A new claim should have empty indexes. It doesn't strictly need the poolID either. + expectedIndexes := types.MultiRewardIndexes{{ + CollateralType: poolID, + RewardIndexes: nil, + }} + suite.Equal(expectedIndexes, syncedClaim.RewardIndexes) + // a new claim should start with 0 rewards + suite.Equal(sdk.Coins(nil), syncedClaim.Reward) +} + +func (suite *InitializeSwapRewardTests) TestClaimAddedWhenClaimDoesNotExistAndRewardsExist() { + // When a claim doesn't exist, and a user deposits to a rewarded pool; + // then a claim is added with no rewards and indexes matching the global indexes + + poolID := "base/quote" + + globalIndexes := types.MultiRewardIndexes{ + { + CollateralType: poolID, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "rewarddenom", + RewardFactor: d("1000.001"), + }, + }, + }, + } + suite.storeGlobalSwapIndexes(globalIndexes) + + owner := arbitraryAddress() + + suite.keeper.InitializeSwapReward(suite.ctx, poolID, owner) + + syncedClaim, found := suite.keeper.GetSwapClaim(suite.ctx, owner) + suite.True(found) + // a new claim should start with the current global indexes + suite.Equal(globalIndexes, syncedClaim.RewardIndexes) + // a new claim should start with 0 rewards + suite.Equal(sdk.Coins(nil), syncedClaim.Reward) +} + +func (suite *InitializeSwapRewardTests) TestClaimUpdatedWhenClaimExistsAndNoRewards() { + // When a claim exists, and a user deposits to a new non-rewarded pool; + // then the claim's rewards don't change + + preexistingPoolID := "preexisting" + preexistingIndexes := types.RewardIndexes{ + { + CollateralType: "rewarddenom", + RewardFactor: d("1000.001"), + }, + } + + newPoolID := "btcb/usdx" + + claim := types.SwapClaim{ + BaseMultiClaim: types.BaseMultiClaim{ + Owner: arbitraryAddress(), + Reward: arbitraryCoins(), + }, + RewardIndexes: types.MultiRewardIndexes{ + { + CollateralType: preexistingPoolID, + RewardIndexes: preexistingIndexes, + }, + }, + } + suite.storeSwapClaim(claim) + + // no global indexes stored as the new pool is not rewarded + + suite.keeper.InitializeSwapReward(suite.ctx, newPoolID, claim.Owner) + + syncedClaim, _ := suite.keeper.GetSwapClaim(suite.ctx, claim.Owner) + // The preexisting indexes shouldn't be changed. It doesn't strictly need the new poolID either. + expectedIndexes := types.MultiRewardIndexes{ + { + CollateralType: preexistingPoolID, + RewardIndexes: preexistingIndexes, + }, + { + CollateralType: newPoolID, + RewardIndexes: nil, + }, + } + suite.Equal(expectedIndexes, syncedClaim.RewardIndexes) + // init should never alter the rewards + suite.Equal(claim.Reward, syncedClaim.Reward) +} + +func (suite *InitializeSwapRewardTests) TestClaimUpdatedWhenClaimExistsAndRewardsExist() { + // When a claim exists, and a user deposits to a new rewarded pool; + // then the claim's rewards don't change and the indexes are updated to match the global indexes + + preexistingPoolID := "preexisting" + preexistingIndexes := types.RewardIndexes{ + { + CollateralType: "rewarddenom", + RewardFactor: d("1000.001"), + }, + } + + newPoolID := "btcb/usdx" + newIndexes := types.RewardIndexes{ + { + CollateralType: "otherrewarddenom", + RewardFactor: d("1000.001"), + }, + } + + claim := types.SwapClaim{ + BaseMultiClaim: types.BaseMultiClaim{ + Owner: arbitraryAddress(), + Reward: arbitraryCoins(), + }, + RewardIndexes: types.MultiRewardIndexes{ + { + CollateralType: preexistingPoolID, + RewardIndexes: preexistingIndexes, + }, + }, + } + suite.storeSwapClaim(claim) + + globalIndexes := types.MultiRewardIndexes{ + { + CollateralType: preexistingPoolID, + RewardIndexes: increaseRewardFactors(preexistingIndexes), + }, + { + CollateralType: newPoolID, + RewardIndexes: newIndexes, + }, + } + suite.storeGlobalSwapIndexes(globalIndexes) + + suite.keeper.InitializeSwapReward(suite.ctx, newPoolID, claim.Owner) + + syncedClaim, _ := suite.keeper.GetSwapClaim(suite.ctx, claim.Owner) + // only the indexes for the new pool should be updated + expectedIndexes := types.MultiRewardIndexes{ + { + CollateralType: preexistingPoolID, + RewardIndexes: preexistingIndexes, + }, + { + CollateralType: newPoolID, + RewardIndexes: newIndexes, + }, + } + suite.Equal(expectedIndexes, syncedClaim.RewardIndexes) + // init should never alter the rewards + suite.Equal(claim.Reward, syncedClaim.Reward) +} diff --git a/x/incentive/keeper/rewards_swap_sync_test.go b/x/incentive/keeper/rewards_swap_sync_test.go new file mode 100644 index 00000000..74b62afa --- /dev/null +++ b/x/incentive/keeper/rewards_swap_sync_test.go @@ -0,0 +1,317 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/kava-labs/kava/x/incentive/types" +) + +// SynchronizeSwapRewardTests runs unit tests for the keeper.SynchronizeSwapReward method +// +// inputs +// - claim in store (only claim.RewardIndexes, claim.Reward) +// - global indexes in store +// - shares function arg +// +// outputs +// - sets a claim +type SynchronizeSwapRewardTests struct { + unitTester +} + +func TestSynchronizeSwapReward(t *testing.T) { + suite.Run(t, new(SynchronizeSwapRewardTests)) +} + +func (suite *SynchronizeSwapRewardTests) TestClaimUpdatedWhenGlobalIndexesHaveIncreased() { + // This is the normal case + // Given some time has passed (meaning the global indexes have increased) + // When the claim is synced + // The user earns rewards for the time passed, and the claim indexes are updated + + originalReward := arbitraryCoins() + poolID := "base/quote" + + claim := types.SwapClaim{ + BaseMultiClaim: types.BaseMultiClaim{ + Owner: arbitraryAddress(), + Reward: originalReward, + }, + RewardIndexes: types.MultiRewardIndexes{ + { + CollateralType: poolID, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "rewarddenom", + RewardFactor: d("1000.001"), + }, + }, + }, + }, + } + suite.storeSwapClaim(claim) + + globalIndexes := types.MultiRewardIndexes{ + { + CollateralType: poolID, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "rewarddenom", + RewardFactor: d("2000.002"), + }, + }, + }, + } + suite.storeGlobalSwapIndexes(globalIndexes) + + userShares := i(1e9) + + suite.keeper.SynchronizeSwapReward(suite.ctx, poolID, claim.Owner, userShares) + + syncedClaim, _ := suite.keeper.GetSwapClaim(suite.ctx, claim.Owner) + // indexes updated from global + suite.Equal(globalIndexes, syncedClaim.RewardIndexes) + // new reward is (new index - old index) * user shares + suite.Equal( + cs(c("rewarddenom", 1_000_001_000_000)).Add(originalReward...), + syncedClaim.Reward, + ) +} + +func (suite *SynchronizeSwapRewardTests) TestClaimUnchangedWhenGlobalIndexesUnchanged() { + // It should be safe to call SynchronizeSwapReward multiple times + + poolID := "base/quote" + unchangingIndexes := types.MultiRewardIndexes{ + { + CollateralType: poolID, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "rewarddenom", + RewardFactor: d("1000.001"), + }, + }, + }, + } + + claim := types.SwapClaim{ + BaseMultiClaim: types.BaseMultiClaim{ + Owner: arbitraryAddress(), + Reward: arbitraryCoins(), + }, + RewardIndexes: unchangingIndexes, + } + suite.storeSwapClaim(claim) + + suite.storeGlobalSwapIndexes(unchangingIndexes) + + userShares := i(1e9) + + suite.keeper.SynchronizeSwapReward(suite.ctx, poolID, claim.Owner, userShares) + + syncedClaim, _ := suite.keeper.GetSwapClaim(suite.ctx, claim.Owner) + // claim should have the same rewards and indexes as before + suite.Equal(claim, syncedClaim) +} +func (suite *SynchronizeSwapRewardTests) TestClaimUpdatedWhenNewRewardAdded() { + // When a new reward is added (via gov) for a pool the user has already deposited to, and the claim is synced; + // Then the user earns rewards for the time since the reward was added, and the indexes are added to the claim. + + originalReward := arbitraryCoins() + newlyRewardPoolID := "newlyRewardedPool" + + claim := types.SwapClaim{ + BaseMultiClaim: types.BaseMultiClaim{ + Owner: arbitraryAddress(), + Reward: originalReward, + }, + RewardIndexes: types.MultiRewardIndexes{ + { + CollateralType: "currentlyRewardedPool", + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "reward", + RewardFactor: d("1000.001"), + }, + }, + }, + }, + } + suite.storeSwapClaim(claim) + + globalIndexes := types.MultiRewardIndexes{ + { + CollateralType: "currentlyRewardedPool", + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "reward", + RewardFactor: d("2000.002"), + }, + }, + }, + { + CollateralType: newlyRewardPoolID, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "otherreward", + // Indexes start at 0 when the reward is added by gov, + // so this represents the syncing happening some time later. + RewardFactor: d("1000.001"), + }, + }, + }, + } + suite.storeGlobalSwapIndexes(globalIndexes) + + userShares := i(1e9) + + suite.keeper.SynchronizeSwapReward(suite.ctx, newlyRewardPoolID, claim.Owner, userShares) + + syncedClaim, _ := suite.keeper.GetSwapClaim(suite.ctx, claim.Owner) + // the new indexes should be added to the claim, but the old ones should be unchanged + newlyRewrdedIndexes, _ := globalIndexes.Get(newlyRewardPoolID) + expectedIndexes := claim.RewardIndexes.With(newlyRewardPoolID, newlyRewrdedIndexes) + suite.Equal(expectedIndexes, syncedClaim.RewardIndexes) + // new reward is (new index - old index) * shares for the synced pool + // The old index for `newlyrewarded` isn't in the claim, so it's added starting at 0 for calculating the reward. + suite.Equal( + cs(c("otherreward", 1_000_001_000_000)).Add(originalReward...), + syncedClaim.Reward, + ) +} + +func (suite *SynchronizeSwapRewardTests) TestClaimUnchangedWhenNoReward() { + // When a pool is not rewarded but the user has deposited to that pool, and the claim is synced; + // Then the claim should be the same. + + claim := types.SwapClaim{ + BaseMultiClaim: types.BaseMultiClaim{ + Owner: arbitraryAddress(), + Reward: arbitraryCoins(), + }, + RewardIndexes: nonEmptyMultiRewardIndexes, + } + suite.storeSwapClaim(claim) + + poolID := "nonRewardPool" + // No global indexes stored as this pool is not rewarded + + userShares := i(1e9) + + suite.keeper.SynchronizeSwapReward(suite.ctx, poolID, claim.Owner, userShares) + + syncedClaim, _ := suite.keeper.GetSwapClaim(suite.ctx, claim.Owner) + suite.Equal(claim, syncedClaim) +} + +func (suite *SynchronizeSwapRewardTests) TestClaimUpdatedWhenNewRewardDenomAdded() { + // When a new reward coin is added (via gov) to an already rewarded pool (that the user has already deposited to), and the claim is synced; + // Then the user earns rewards for the time since the reward was added, and the new indexes are added. + + originalReward := arbitraryCoins() + poolID := "base/quote" + + claim := types.SwapClaim{ + BaseMultiClaim: types.BaseMultiClaim{ + Owner: arbitraryAddress(), + Reward: originalReward, + }, + RewardIndexes: types.MultiRewardIndexes{ + { + CollateralType: poolID, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "reward", + RewardFactor: d("1000.001"), + }, + }, + }, + }, + } + suite.storeSwapClaim(claim) + + globalIndexes := types.MultiRewardIndexes{ + { + CollateralType: poolID, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "reward", + RewardFactor: d("2000.002"), + }, + { + CollateralType: "otherreward", + // Indexes start at 0 when the reward is added by gov, + // so this represents the syncing happening some time later. + RewardFactor: d("1000.001"), + }, + }, + }, + } + suite.storeGlobalSwapIndexes(globalIndexes) + + userShares := i(1e9) + + suite.keeper.SynchronizeSwapReward(suite.ctx, poolID, claim.Owner, userShares) + + syncedClaim, _ := suite.keeper.GetSwapClaim(suite.ctx, claim.Owner) + // indexes should have the new reward denom added + suite.Equal(globalIndexes, syncedClaim.RewardIndexes) + // new reward is (new index - old index) * shares + // The old index for `otherreward` isn't in the claim, so it's added starting at 0 for calculating the reward. + suite.Equal( + cs(c("reward", 1_000_001_000_000), c("otherreward", 1_000_001_000_000)).Add(originalReward...), + syncedClaim.Reward, + ) +} + +func (suite *SynchronizeSwapRewardTests) TestClaimUpdatedWhenGlobalIndexesIncreasedAndSourceIsZero() { + // Given some time has passed (meaning the global indexes have increased) + // When the claim is synced, but the user has no shares + // The user earns no rewards for the time passed, but the claim indexes are updated + + poolID := "base/quote" + + claim := types.SwapClaim{ + BaseMultiClaim: types.BaseMultiClaim{ + Owner: arbitraryAddress(), + Reward: arbitraryCoins(), + }, + RewardIndexes: types.MultiRewardIndexes{ + { + CollateralType: poolID, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "rewarddenom", + RewardFactor: d("1000.001"), + }, + }, + }, + }, + } + suite.storeSwapClaim(claim) + + globalIndexes := types.MultiRewardIndexes{ + { + CollateralType: poolID, + RewardIndexes: types.RewardIndexes{ + { + CollateralType: "rewarddenom", + RewardFactor: d("2000.002"), + }, + }, + }, + } + suite.storeGlobalSwapIndexes(globalIndexes) + + userShares := i(0) + + suite.keeper.SynchronizeSwapReward(suite.ctx, poolID, claim.Owner, userShares) + + syncedClaim, _ := suite.keeper.GetSwapClaim(suite.ctx, claim.Owner) + // indexes updated from global + suite.Equal(globalIndexes, syncedClaim.RewardIndexes) + // reward is unchanged + suite.Equal(claim.Reward, syncedClaim.Reward) +} diff --git a/x/incentive/keeper/rewards_swap_test.go b/x/incentive/keeper/rewards_swap_test.go index f2c52595..ab97ff44 100644 --- a/x/incentive/keeper/rewards_swap_test.go +++ b/x/incentive/keeper/rewards_swap_test.go @@ -31,7 +31,7 @@ func TestAccumulateSwapRewards(t *testing.T) { } func (suite *AccumulateSwapRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreased() { - swapKeeper := &fakeSwapKeeper{d("1000000")} + swapKeeper := &fakeSwapKeeper{i(1e6)} suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) pool := "btc/usdx" @@ -84,7 +84,7 @@ func (suite *AccumulateSwapRewardsTests) TestStateUpdatedWhenBlockTimeHasIncreas } func (suite *AccumulateSwapRewardsTests) TestLimitsOfAccumulationPrecision() { - swapKeeper := &fakeSwapKeeper{d("100000000000000000")} // approximate shares in a $1B pool of 10^8 precision assets + swapKeeper := &fakeSwapKeeper{i(1e17)} // approximate shares in a $1B pool of 10^8 precision ~$1 asset suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) pool := "btc/usdx" @@ -130,7 +130,7 @@ func (suite *AccumulateSwapRewardsTests) TestLimitsOfAccumulationPrecision() { } func (suite *AccumulateSwapRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIncreased() { - swapKeeper := &fakeSwapKeeper{d("1000000")} + swapKeeper := &fakeSwapKeeper{i(1e6)} suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) pool := "btc/usdx" @@ -182,7 +182,7 @@ func (suite *AccumulateSwapRewardsTests) TestStateUnchangedWhenBlockTimeHasNotIn } func (suite *AccumulateSwapRewardsTests) TestStateAddedWhenStateDoesNotExist() { - swapKeeper := &fakeSwapKeeper{d("1000000")} + swapKeeper := &fakeSwapKeeper{i(1e6)} suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) pool := "btc/usdx" @@ -225,7 +225,7 @@ func (suite *AccumulateSwapRewardsTests) TestStateAddedWhenStateDoesNotExist() { suite.checkStoredIndexesEqual(pool, expectedIndexes) } func (suite *AccumulateSwapRewardsTests) TestNoPanicWhenStateDoesNotExist() { - swapKeeper := &fakeSwapKeeper{d("0")} + swapKeeper := &fakeSwapKeeper{i(0)} suite.keeper = suite.NewKeeper(&fakeParamSubspace{}, nil, nil, nil, nil, nil, swapKeeper) pool := "btc/usdx" @@ -252,12 +252,16 @@ func (suite *AccumulateSwapRewardsTests) TestNoPanicWhenStateDoesNotExist() { } type fakeSwapKeeper struct { - poolShares sdk.Dec + poolShares sdk.Int } -func (k fakeSwapKeeper) GetPoolShares(ctx sdk.Context, poolID string) (sdk.Dec, bool) { +func (k fakeSwapKeeper) GetPoolShares(ctx sdk.Context, poolID string) (sdk.Int, bool) { return k.poolShares, true } +func (k fakeSwapKeeper) GetDepositorSharesInPool(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (sdk.Int, bool) { + // This is just to implement the swap keeper interface. + return sdk.Int{}, false +} // note: amino panics when encoding times ≥ the start of year 10000. var distantFuture = time.Date(9000, 1, 1, 0, 0, 0, 0, time.UTC) diff --git a/x/incentive/keeper/unit_test.go b/x/incentive/keeper/unit_test.go index 0a6d7e06..41c29269 100644 --- a/x/incentive/keeper/unit_test.go +++ b/x/incentive/keeper/unit_test.go @@ -87,10 +87,12 @@ func (suite *unitTester) storeGlobalSwapIndexes(indexes types.MultiRewardIndexes func (suite *unitTester) storeHardClaim(claim types.HardLiquidityProviderClaim) { suite.keeper.SetHardLiquidityProviderClaim(suite.ctx, claim) } - func (suite *unitTester) storeDelegatorClaim(claim types.DelegatorClaim) { suite.keeper.SetDelegatorClaim(suite.ctx, claim) } +func (suite *unitTester) storeSwapClaim(claim types.SwapClaim) { + suite.keeper.SetSwapClaim(suite.ctx, claim) +} type fakeParamSubspace struct { params types.Params diff --git a/x/incentive/types/claims.go b/x/incentive/types/claims.go index d73146df..bf382a94 100644 --- a/x/incentive/types/claims.go +++ b/x/incentive/types/claims.go @@ -12,6 +12,7 @@ const ( USDXMintingClaimType = "usdx_minting" HardLiquidityProviderClaimType = "hard_liquidity_provider" DelegatorClaimType = "delegator_claim" + SwapClaimType = "swap" BondDenom = "ukava" ) @@ -314,6 +315,71 @@ func (cs DelegatorClaims) Validate() error { return nil } +// SwapClaim stores the swap rewards that can be claimed by owner +type SwapClaim struct { + BaseMultiClaim `json:"base_claim" yaml:"base_claim"` + RewardIndexes MultiRewardIndexes `json:"reward_indexes" yaml:"reward_indexes"` +} + +// NewSwapClaim returns a new SwapClaim +func NewSwapClaim(owner sdk.AccAddress, rewards sdk.Coins, rewardIndexes MultiRewardIndexes) SwapClaim { + return SwapClaim{ + BaseMultiClaim: BaseMultiClaim{ + Owner: owner, + Reward: rewards, + }, + RewardIndexes: rewardIndexes, + } +} + +// GetType returns the claim's type +func (c SwapClaim) GetType() string { return SwapClaimType } + +// GetReward returns the claim's reward coin +func (c SwapClaim) GetReward() sdk.Coins { return c.Reward } + +// GetOwner returns the claim's owner +func (c SwapClaim) GetOwner() sdk.AccAddress { return c.Owner } + +// Validate performs a basic check of a SwapClaim fields +func (c SwapClaim) Validate() error { + if err := c.RewardIndexes.Validate(); err != nil { + return err + } + return c.BaseMultiClaim.Validate() +} + +// String implements fmt.Stringer +func (c SwapClaim) String() string { + return fmt.Sprintf(`%s + Reward Indexes: %s, + `, c.BaseMultiClaim, c.RewardIndexes) +} + +// HasRewardIndex check if a claim has a reward index for the input pool ID. +func (c SwapClaim) HasRewardIndex(poolID string) (int64, bool) { + for index, ri := range c.RewardIndexes { + if ri.CollateralType == poolID { + return int64(index), true + } + } + return 0, false +} + +// SwapClaims slice of SwapClaim +type SwapClaims []SwapClaim + +// Validate checks if all the claims are valid. +func (cs SwapClaims) Validate() error { + for _, c := range cs { + if err := c.Validate(); err != nil { + return err + } + } + + return nil +} + // ---------------------- Reward indexes are used internally in the store ---------------------- // RewardIndex stores reward accumulation information diff --git a/x/incentive/types/claims_test.go b/x/incentive/types/claims_test.go index f902d791..58af0f9d 100644 --- a/x/incentive/types/claims_test.go +++ b/x/incentive/types/claims_test.go @@ -18,67 +18,125 @@ func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amo // c is a helper function for created sdk.Coins types in tests func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } -func TestClaimsValidate(t *testing.T) { +func TestClaims_Validate(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{ - USDXMintingClaim{ - BaseClaim: BaseClaim{ - Owner: nil, - }, - }, - }, - false, - }, - { - "invalid reward", - USDXMintingClaims{ - { - BaseClaim: BaseClaim{ - Owner: owner, - Reward: sdk.Coin{Denom: "", Amount: sdk.ZeroInt()}, - }, - }, - }, - false, - }, - { - "invalid collateral type", - USDXMintingClaims{ - { - BaseClaim: BaseClaim{ - Owner: owner, - Reward: sdk.NewCoin("bnb", sdk.OneInt()), - }, - RewardIndexes: []RewardIndex{{"", sdk.ZeroDec()}}, - }, - }, - false, - }, - } + t.Run("USDXMintingClaims", func(t *testing.T) { - for _, tc := range testCases { - err := tc.claims.Validate() - if tc.expPass { - require.NoError(t, err, tc.msg) - } else { - require.Error(t, err, tc.msg) + testCases := []struct { + name string + claims USDXMintingClaims + expPass bool + }{ + { + "valid", + USDXMintingClaims{ + NewUSDXMintingClaim(owner, sdk.NewCoin("bnb", sdk.OneInt()), RewardIndexes{NewRewardIndex("bnb-a", sdk.ZeroDec())}), + }, + true, + }, + { + "invalid owner", + USDXMintingClaims{ + USDXMintingClaim{ + BaseClaim: BaseClaim{ + Owner: nil, + }, + }, + }, + false, + }, + { + "invalid reward", + USDXMintingClaims{ + { + BaseClaim: BaseClaim{ + Owner: owner, + Reward: sdk.Coin{Denom: "", Amount: sdk.ZeroInt()}, + }, + }, + }, + false, + }, + { + "invalid collateral type", + USDXMintingClaims{ + { + BaseClaim: BaseClaim{ + Owner: owner, + Reward: sdk.NewCoin("bnb", sdk.OneInt()), + }, + RewardIndexes: []RewardIndex{{"", sdk.ZeroDec()}}, + }, + }, + false, + }, } - } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.claims.Validate() + if tc.expPass { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } + }) + t.Run("SwapClaims", func(t *testing.T) { + + validRewardIndexes := RewardIndexes{}.With("swap", d("0.002")) + validMultiRewardIndexes := MultiRewardIndexes{}.With("btcb/usdx", validRewardIndexes) + invalidRewardIndexes := RewardIndexes{}.With("swap", d("-0.002")) + invalidMultiRewardIndexes := MultiRewardIndexes{}.With("btcb/usdx", invalidRewardIndexes) + + testCases := []struct { + name string + claims SwapClaims + expPass bool + }{ + { + name: "valid", + claims: SwapClaims{ + NewSwapClaim(owner, cs(c("bnb", 1)), validMultiRewardIndexes), + }, + expPass: true, + }, + { + name: "invalid owner", + claims: SwapClaims{ + NewSwapClaim(nil, cs(c("bnb", 1)), validMultiRewardIndexes), + }, + expPass: false, + }, + { + name: "invalid reward", + claims: SwapClaims{ + NewSwapClaim(owner, sdk.Coins{sdk.Coin{Denom: "invalid😫"}}, validMultiRewardIndexes), + }, + expPass: false, + }, + { + name: "invalid indexes", + claims: SwapClaims{ + NewSwapClaim(nil, cs(c("bnb", 1)), invalidMultiRewardIndexes), + }, + expPass: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.claims.Validate() + if tc.expPass { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } + }) } func TestRewardIndexes(t *testing.T) { diff --git a/x/incentive/types/expected_keepers.go b/x/incentive/types/expected_keepers.go index 69f44dc3..97e76f5c 100644 --- a/x/incentive/types/expected_keepers.go +++ b/x/incentive/types/expected_keepers.go @@ -53,7 +53,8 @@ type HardKeeper interface { // SwapKeeper defines the required methods needed by this modules keeper type SwapKeeper interface { - GetPoolShares(ctx sdk.Context, poolID string) (shares sdk.Dec, found bool) + GetPoolShares(ctx sdk.Context, poolID string) (shares sdk.Int, found bool) + GetDepositorSharesInPool(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (shares sdk.Int, found bool) } // AccountKeeper defines the expected keeper interface for interacting with account diff --git a/x/incentive/types/genesis.go b/x/incentive/types/genesis.go index 8c2196ae..88c92c54 100644 --- a/x/incentive/types/genesis.go +++ b/x/incentive/types/genesis.go @@ -17,11 +17,12 @@ type GenesisState struct { USDXMintingClaims USDXMintingClaims `json:"usdx_minting_claims" yaml:"usdx_minting_claims"` HardLiquidityProviderClaims HardLiquidityProviderClaims `json:"hard_liquidity_provider_claims" yaml:"hard_liquidity_provider_claims"` DelegatorClaims DelegatorClaims `json:"delegator_claims" yaml:"delegator_claims"` + SwapClaims SwapClaims `json:"swap_claims" yaml:"swap_claims"` } // NewGenesisState returns a new genesis state func NewGenesisState(params Params, usdxAccumTimes, hardSupplyAccumTimes, hardBorrowAccumTimes, delegatorAccumTimes, - swapAccumTimes GenesisAccumulationTimes, c USDXMintingClaims, hc HardLiquidityProviderClaims, dc DelegatorClaims) GenesisState { + swapAccumTimes GenesisAccumulationTimes, c USDXMintingClaims, hc HardLiquidityProviderClaims, dc DelegatorClaims, sc SwapClaims) GenesisState { return GenesisState{ Params: params, USDXAccumulationTimes: usdxAccumTimes, @@ -32,6 +33,7 @@ func NewGenesisState(params Params, usdxAccumTimes, hardSupplyAccumTimes, hardBo USDXMintingClaims: c, HardLiquidityProviderClaims: hc, DelegatorClaims: dc, + SwapClaims: sc, } } @@ -47,6 +49,7 @@ func DefaultGenesisState() GenesisState { USDXMintingClaims: DefaultUSDXClaims, HardLiquidityProviderClaims: DefaultHardClaims, DelegatorClaims: DefaultDelegatorClaims, + SwapClaims: DefaultSwapClaims, } } @@ -72,13 +75,16 @@ func (gs GenesisState) Validate() error { return err } - if err := gs.HardLiquidityProviderClaims.Validate(); err != nil { - return err - } if err := gs.USDXMintingClaims.Validate(); err != nil { return err } - return gs.DelegatorClaims.Validate() + if err := gs.HardLiquidityProviderClaims.Validate(); err != nil { + return err + } + if err := gs.DelegatorClaims.Validate(); err != nil { + return err + } + return gs.SwapClaims.Validate() } // Equal checks whether two gov GenesisState structs are equivalent diff --git a/x/incentive/types/genesis_test.go b/x/incentive/types/genesis_test.go index 041e77d0..6292158d 100644 --- a/x/incentive/types/genesis_test.go +++ b/x/incentive/types/genesis_test.go @@ -141,6 +141,7 @@ func TestGenesisStateValidate(t *testing.T) { tc.args.claims, DefaultHardClaims, DefaultDelegatorClaims, + DefaultSwapClaims, ) err := gs.Validate() if tc.errArgs.expectPass { diff --git a/x/incentive/types/keys.go b/x/incentive/types/keys.go index 3e5c8391..27ed7d08 100644 --- a/x/incentive/types/keys.go +++ b/x/incentive/types/keys.go @@ -34,8 +34,9 @@ var ( DelegatorClaimKeyPrefix = []byte{0x09} // prefix for keys that store delegator claims DelegatorRewardIndexesKeyPrefix = []byte{0x10} // prefix for key that stores delegator reward indexes PreviousDelegatorRewardAccrualTimeKeyPrefix = []byte{0x11} // prefix for key that stores the previous time delegator rewards accrued - SwapRewardIndexesKeyPrefix = []byte{0x12} // prefix for key that stores swap reward indexes - PreviousSwapRewardAccrualTimeKeyPrefix = []byte{0x13} // prefix for key that stores the previous time swap rewards accrued + SwapClaimKeyPrefix = []byte{0x12} // prefix for keys that store swap claims + SwapRewardIndexesKeyPrefix = []byte{0x13} // prefix for key that stores swap reward indexes + PreviousSwapRewardAccrualTimeKeyPrefix = []byte{0x14} // prefix for key that stores the previous time swap rewards accrued USDXMintingRewardDenom = "ukava" HardLiquidityRewardDenom = "hard" diff --git a/x/incentive/types/params.go b/x/incentive/types/params.go index b86248c0..e9c30807 100644 --- a/x/incentive/types/params.go +++ b/x/incentive/types/params.go @@ -38,6 +38,7 @@ var ( DefaultUSDXClaims = USDXMintingClaims{} DefaultHardClaims = HardLiquidityProviderClaims{} DefaultDelegatorClaims = DelegatorClaims{} + DefaultSwapClaims = SwapClaims{} DefaultGenesisAccumulationTimes = GenesisAccumulationTimes{} DefaultClaimEnd = tmtime.Canonical(time.Unix(1, 0)) GovDenom = cdptypes.DefaultGovDenom diff --git a/x/incentive/types/querier.go b/x/incentive/types/querier.go index fa9f870c..5da6d23e 100644 --- a/x/incentive/types/querier.go +++ b/x/incentive/types/querier.go @@ -6,169 +6,54 @@ import ( // Querier routes for the incentive module const ( - QueryGetRewards = "rewards" - QueryGetHardRewards = "hard-rewards" - QueryGetHardRewardsUnsynced = "hard-rewards-unsynced" - QueryGetUSDXMintingRewards = "usdx-minting-rewards" - QueryGetUSDXMintingRewardsUnsynced = "usdx-minting-rewards-unsynced" - QueryGetDelegatorRewards = "delegator-rewards" - QueryGetDelegatorRewardsUnsynced = "delegator-rewards-unsynced" - QueryGetRewardFactors = "reward-factors" - QueryGetParams = "parameters" - QueryGetRewardPeriods = "reward-periods" - QueryGetClaimPeriods = "claim-periods" - RestClaimCollateralType = "collateral_type" - RestClaimOwner = "owner" - RestClaimType = "type" - RestUnsynced = "unsynced" + QueryGetHardRewards = "hard-rewards" + QueryGetUSDXMintingRewards = "usdx-minting-rewards" + QueryGetDelegatorRewards = "delegator-rewards" + QueryGetSwapRewards = "swap-rewards" + QueryGetRewardFactors = "reward-factors" + QueryGetParams = "parameters" + + RestClaimCollateralType = "collateral_type" + RestClaimOwner = "owner" + RestClaimType = "type" + RestUnsynced = "unsynced" ) -// QueryRewardsParams params for query /incentive/rewards +// QueryRewardsParams params for query /incentive/rewards/ type QueryRewardsParams struct { - Page int `json:"page" yaml:"page"` - Limit int `json:"limit" yaml:"limit"` - Owner sdk.AccAddress - Type string + Page int `json:"page" yaml:"page"` + Limit int `json:"limit" yaml:"limit"` + Owner sdk.AccAddress `json:"owner" yaml:"owner"` + Unsynchronized bool `json:"unsynchronized" yaml:"unsynchronized"` } // NewQueryRewardsParams returns QueryRewardsParams -func NewQueryRewardsParams(page, limit int, owner sdk.AccAddress, rewardType string) QueryRewardsParams { +func NewQueryRewardsParams(page, limit int, owner sdk.AccAddress, unsynchronized bool) QueryRewardsParams { return QueryRewardsParams{ - Page: page, - Limit: limit, - Owner: owner, - Type: rewardType, + Page: page, + Limit: limit, + Owner: owner, + Unsynchronized: unsynchronized, } } -// QueryUSDXMintingRewardsParams params for query /incentive/rewards type usdx-minting -type QueryUSDXMintingRewardsParams struct { - Page int `json:"page" yaml:"page"` - Limit int `json:"limit" yaml:"limit"` - Owner sdk.AccAddress +// QueryGetRewardFactorsResponse holds the response to a reward factor query +type QueryGetRewardFactorsResponse struct { + USDXMintingRewardFactors RewardIndexes `json:"usdx_minting_reward_factors" yaml:"usdx_minting_reward_factors"` + HardSupplyRewardFactors MultiRewardIndexes `json:"hard_supply_reward_factors" yaml:"hard_supply_reward_factors"` + HardBorrowRewardFactors MultiRewardIndexes `json:"hard_borrow_reward_factors" yaml:"hard_borrow_reward_factors"` + DelegatorRewardFactors MultiRewardIndexes `json:"delegator_reward_factors" yaml:"delegator_reward_factors"` + SwapRewardFactors MultiRewardIndexes `json:"swap_reward_factors" yaml:"swap_reward_factors"` } -// NewQueryUSDXMintingRewardsParams returns QueryUSDXMintingRewardsParams -func NewQueryUSDXMintingRewardsParams(page, limit int, owner sdk.AccAddress) QueryUSDXMintingRewardsParams { - return QueryUSDXMintingRewardsParams{ - Page: page, - Limit: limit, - Owner: owner, +// NewQueryGetRewardFactorsResponse returns a new instance of QueryAllRewardFactorsResponse +func NewQueryGetRewardFactorsResponse(usdxMintingFactors RewardIndexes, supplyFactors, + hardBorrowFactors, delegatorFactors, swapFactors MultiRewardIndexes) QueryGetRewardFactorsResponse { + return QueryGetRewardFactorsResponse{ + USDXMintingRewardFactors: usdxMintingFactors, + HardSupplyRewardFactors: supplyFactors, + HardBorrowRewardFactors: hardBorrowFactors, + DelegatorRewardFactors: delegatorFactors, + SwapRewardFactors: swapFactors, } } - -// QueryUSDXMintingRewardsUnsyncedParams params for query unsynced /incentive/rewards type usdx-minting -type QueryUSDXMintingRewardsUnsyncedParams struct { - Page int `json:"page" yaml:"page"` - Limit int `json:"limit" yaml:"limit"` - Owner sdk.AccAddress -} - -// NewQueryUSDXMintingRewardsUnsyncedParams returns QueryUSDXMintingRewardsUnsyncedParams -func NewQueryUSDXMintingRewardsUnsyncedParams(page, limit int, owner sdk.AccAddress) QueryUSDXMintingRewardsUnsyncedParams { - return QueryUSDXMintingRewardsUnsyncedParams{ - Page: page, - Limit: limit, - Owner: owner, - } -} - -// QueryHardRewardsParams params for query /incentive/rewards type hard -type QueryHardRewardsParams struct { - Page int `json:"page" yaml:"page"` - Limit int `json:"limit" yaml:"limit"` - Owner sdk.AccAddress -} - -// NewQueryHardRewardsParams returns QueryHardRewardsParams -func NewQueryHardRewardsParams(page, limit int, owner sdk.AccAddress) QueryHardRewardsParams { - return QueryHardRewardsParams{ - Page: page, - Limit: limit, - Owner: owner, - } -} - -// QueryHardRewardsUnsyncedParams params for query unsynced /incentive/rewards type hard -type QueryHardRewardsUnsyncedParams struct { - Page int `json:"page" yaml:"page"` - Limit int `json:"limit" yaml:"limit"` - Owner sdk.AccAddress -} - -// NewQueryHardRewardsUnsyncedParams returns QueryHardRewardsUnsyncedParams -func NewQueryHardRewardsUnsyncedParams(page, limit int, owner sdk.AccAddress) QueryHardRewardsUnsyncedParams { - return QueryHardRewardsUnsyncedParams{ - Page: page, - Limit: limit, - Owner: owner, - } -} - -// QueryRewardFactorsParams is the params for a filtered reward factors query -type QueryRewardFactorsParams struct { - Denom string `json:"denom" yaml:"denom"` -} - -// NewQueryRewardFactorsParams creates a new QueryRewardFactorsParams -func NewQueryRewardFactorsParams(denom string) QueryRewardFactorsParams { - return QueryRewardFactorsParams{ - Denom: denom, - } -} - -// QueryDelegatorRewardsParams params for query /incentive/rewards type delegator -type QueryDelegatorRewardsParams struct { - Page int `json:"page" yaml:"page"` - Limit int `json:"limit" yaml:"limit"` - Owner sdk.AccAddress -} - -// NewQueryDelegatorRewardsParams returns QueryDelegatorRewardsParams -func NewQueryDelegatorRewardsParams(page, limit int, owner sdk.AccAddress) QueryDelegatorRewardsParams { - return QueryDelegatorRewardsParams{ - Page: page, - Limit: limit, - Owner: owner, - } -} - -// QueryDelegatorRewardsUnsyncedParams params for query unsynced /incentive/rewards type delegator -type QueryDelegatorRewardsUnsyncedParams struct { - Page int `json:"page" yaml:"page"` - Limit int `json:"limit" yaml:"limit"` - Owner sdk.AccAddress -} - -// NewQueryDelegatorRewardsUnsyncedParams returns QueryDelegatorRewardsUnsyncedParams -func NewQueryDelegatorRewardsUnsyncedParams(page, limit int, owner sdk.AccAddress) QueryDelegatorRewardsUnsyncedParams { - return QueryDelegatorRewardsUnsyncedParams{ - Page: page, - Limit: limit, - Owner: owner, - } -} - -// RewardFactor is a unique type returned by reward factor queries -type RewardFactor struct { - Denom string `json:"denom" yaml:"denom"` - USDXMintingRewardFactor sdk.Dec `json:"usdx_minting_reward_factor" yaml:"usdx_minting_reward_factor"` - HardSupplyRewardFactors RewardIndexes `json:"hard_supply_reward_factors" yaml:"hard_supply_reward_factors"` - HardBorrowRewardFactors RewardIndexes `json:"hard_borrow_reward_factors" yaml:"hard_borrow_reward_factors"` - DelegatorRewardFactors RewardIndexes `json:"delegator_reward_factors" yaml:"delegator_reward_factors"` -} - -// NewRewardFactor returns a new instance of RewardFactor -func NewRewardFactor(denom string, usdxMintingRewardFactor sdk.Dec, hardSupplyRewardFactors, - hardBorrowRewardFactors, delegatorRewardFactors RewardIndexes) RewardFactor { - return RewardFactor{ - Denom: denom, - USDXMintingRewardFactor: usdxMintingRewardFactor, - HardSupplyRewardFactors: hardSupplyRewardFactors, - HardBorrowRewardFactors: hardBorrowRewardFactors, - DelegatorRewardFactors: delegatorRewardFactors, - } -} - -// RewardFactors is a slice of RewardFactor -type RewardFactors = []RewardFactor diff --git a/x/swap/keeper/hooks.go b/x/swap/keeper/hooks.go index 7267b99c..21945c2d 100644 --- a/x/swap/keeper/hooks.go +++ b/x/swap/keeper/hooks.go @@ -2,9 +2,35 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/swap/types" ) -func (k Keeper) GetPoolShares(ctx sdk.Context, poolID string) (sdk.Dec, bool) { +func (k Keeper) GetPoolShares(ctx sdk.Context, poolID string) (sdk.Int, bool) { // FIXME return pool shares once merged with acceptance branch - return sdk.Dec{}, false + return sdk.Int{}, false +} + +func (k *Keeper) GetDepositorSharesInPool(ctx sdk.Context, depositor sdk.AccAddress, poolID string) (sdk.Int, bool) { + // FIXME return depositor shares once merged with acceptance branch + return sdk.Int{}, false +} + +// Implements SwapHooks interface +var _ types.SwapHooks = Keeper{} + +// FIXME call hooks within pool logic + +// AfterPoolDepositCreated - call hook if registered +func (k Keeper) AfterPoolDepositCreated(ctx sdk.Context, poolID string, depositor sdk.AccAddress, sharesOwned sdk.Int) { + if k.hooks != nil { + k.hooks.AfterPoolDepositCreated(ctx, poolID, depositor, sharesOwned) + } +} + +// BeforePoolDepositModified - call hook if registered +func (k Keeper) BeforePoolDepositModified(ctx sdk.Context, poolID string, depositor sdk.AccAddress, sharesOwned sdk.Int) { + if k.hooks != nil { + k.hooks.BeforePoolDepositModified(ctx, poolID, depositor, sharesOwned) + } } diff --git a/x/swap/keeper/keeper.go b/x/swap/keeper/keeper.go index eb85851d..5384fb0b 100644 --- a/x/swap/keeper/keeper.go +++ b/x/swap/keeper/keeper.go @@ -14,6 +14,7 @@ type Keeper struct { key sdk.StoreKey cdc *codec.Codec paramSubspace subspace.Subspace + hooks types.SwapHooks accountKeeper types.AccountKeeper supplyKeeper types.SupplyKeeper } @@ -39,6 +40,15 @@ func NewKeeper( } } +// SetHooks adds hooks to the keeper. +func (k *Keeper) SetHooks(sh types.SwapHooks) *Keeper { + if k.hooks != nil { + panic("cannot set swap hooks twice") + } + k.hooks = sh + return k +} + // GetParams returns the params from the store func (k Keeper) GetParams(ctx sdk.Context) types.Params { var p types.Params diff --git a/x/swap/types/expected_keepers.go b/x/swap/types/expected_keepers.go index a3cd26b3..ad66e698 100644 --- a/x/swap/types/expected_keepers.go +++ b/x/swap/types/expected_keepers.go @@ -1,4 +1,4 @@ -package types // noalias +package types import ( sdk "github.com/cosmos/cosmos-sdk/types" @@ -20,3 +20,9 @@ type AccountKeeper interface { GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account SetAccount(ctx sdk.Context, acc authexported.Account) } + +// SwapHooks are event hooks called when a user's deposit to a swap pool changes. +type SwapHooks interface { + AfterPoolDepositCreated(ctx sdk.Context, poolID string, depositor sdk.AccAddress, sharedOwned sdk.Int) + BeforePoolDepositModified(ctx sdk.Context, poolID string, depositor sdk.AccAddress, sharedOwned sdk.Int) +}