diff --git a/staker/calculate_pool_position_reward.gno b/staker/calculate_pool_position_reward.gno index cd34621a8..06f642de5 100644 --- a/staker/calculate_pool_position_reward.gno +++ b/staker/calculate_pool_position_reward.gno @@ -7,6 +7,8 @@ import ( "gno.land/r/gnoswap/v1/gns" + ufmt "gno.land/p/demo/ufmt" + //pn "gno.land/r/gnoswap/v1/position" u256 "gno.land/p/gnoswap/uint256" @@ -99,7 +101,6 @@ func CalcPositionReward(currentHeight uint64, tokenId uint64, deposits *Deposits pool.cacheInternalReward(poolTier, currentHeight) - // println(ufmt.Sprintf("pool.currentInternalReward || poolPath(%s), poolTier(%d), currentHeight(%d), currentReward(%s)", poolPath, poolTier.CurrentTier(poolPath, currentHeight), currentHeight, pool.rewardCache.CurrentReward(currentHeight).(*u256.Uint).ToString())) pool.cacheExternalReward(currentHeight) @@ -122,3 +123,12 @@ func CalcPositionReward(currentHeight uint64, tokenId uint64, deposits *Deposits return internalRewards, internalPenalties, externalRewards, externalPenalties } + +func ProcessUnclaimableReward(poolPath string, endHeight uint64) (uint64, map[string]uint64) { + pool, ok := pools.Get(poolPath) + if !ok { + return 0, make(map[string]uint64) + } + return pool.processUnclaimableReward(poolTier, endHeight) +} + diff --git a/staker/reward_calculation_pool.gno b/staker/reward_calculation_pool.gno index a6ff07ff5..3c081fd6a 100644 --- a/staker/reward_calculation_pool.gno +++ b/staker/reward_calculation_pool.gno @@ -7,6 +7,8 @@ import ( "gno.land/p/demo/avl" + ufmt "gno.land/p/demo/ufmt" + i256 "gno.land/p/gnoswap/int256" u256 "gno.land/p/gnoswap/uint256" ) @@ -57,6 +59,7 @@ type Pool struct { // conceptually equal with Pool.liquidity but only for the staked positions // updated each time when the pool crosses a staked tick stakedLiquidity *UintTree // blockNumber -> *u256.Uint + lastUnclaimableHeight *uint64 // cache of the internal reward per liquidity for each pool in tier // value: (internal reward / total staked liquidity) * Q192 @@ -72,6 +75,7 @@ func NewPool(poolPath string, currentHeight uint64) *Pool { return &Pool{ poolPath: poolPath, stakedLiquidity: NewUintTree(), + lastUnclaimableHeight: ¤tHeight, rewardCache: NewRewardCacheTree(), lastRewardCacheHeight: ¤tHeight, incentives: NewIncentives(currentHeight), @@ -402,6 +406,94 @@ func (self *Pool) modifyDeposit(tokenId uint64, liquidity *i256.Int, currentHeig self.stakedLiquidity.Set(currentHeight, liquidityMathAddDelta(lastStakedLiquidity, liquidity)) } +func (self *Pool) UnclaimableInternalReward(poolTier *PoolTier, startHeight, endHeight uint64) uint64 { + tierRewardHeights, tierRewardUpdates := poolTier.TierRewardUpdates(self.poolPath, startHeight, endHeight) + + currentTierReward := tierRewardUpdates[0] + + unclaimable := uint64(0) + + currentStakedLiquidity := self.CurrentStakedLiquidity(startHeight) + + for i := 1; i < len(tierRewardHeights); i++ { + endHeight := tierRewardHeights[i] + + currentStakedLiquidity = self.CurrentStakedLiquidity(startHeight) + self.stakedLiquidity.Iterate(startHeight, endHeight, func(height uint64, value interface{}) bool { + if currentStakedLiquidity.IsZero() { + unclaimable += currentTierReward * (height - startHeight) + } + startHeight = height + currentStakedLiquidity = value.(*u256.Uint) + return false + }) + + if currentStakedLiquidity.IsZero() { + unclaimable += currentTierReward * (endHeight - startHeight) + } + + startHeight = endHeight + currentTierReward = tierRewardUpdates[i] + } + + if currentStakedLiquidity.IsZero() { + unclaimable += currentTierReward * (endHeight - startHeight) + } + + return unclaimable +} + +func (self *Pool) UnclaimableExternalReward(incentiveId string, startHeight, endHeight uint64) uint64 { + incentive, ok := self.incentives.GetByIncentiveId(incentiveId) + if !ok { + return 0 + } + + if startHeight > uint64(incentive.endHeight) || endHeight < uint64(incentive.startHeight) { + return 0 + } + + if startHeight < uint64(incentive.startHeight) { + startHeight = uint64(incentive.startHeight) + } + + if endHeight > uint64(incentive.endHeight) { + endHeight = uint64(incentive.endHeight) + } + + rewardPerBlock := incentive.rewardPerBlock + + unclaimable := uint64(0) + + currentStakedLiquidity := self.CurrentStakedLiquidity(startHeight) + + self.stakedLiquidity.Iterate(startHeight, endHeight, func(height uint64, value interface{}) bool { + if currentStakedLiquidity.IsZero() { + unclaimable += rewardPerBlock * (height - startHeight) + } + startHeight = height + currentStakedLiquidity = value.(*u256.Uint) + return false + }) + + if currentStakedLiquidity.IsZero() { + unclaimable += rewardPerBlock * (endHeight - startHeight) + } + + return unclaimable +} + +func (self *Pool) processUnclaimableReward(poolTier *PoolTier, endHeight uint64) (uint64, map[string]uint64) { + startHeight := *self.lastUnclaimableHeight + internalUnclaimable := self.UnclaimableInternalReward(poolTier, startHeight, endHeight) + externalUnclaimable := make(map[string]uint64) + for incentiveId := range self.incentives.CurrentReward(startHeight) { + externalUnclaimable[incentiveId] = self.UnclaimableExternalReward(incentiveId, startHeight, endHeight) + } + self.lastUnclaimableHeight = &endHeight + return internalUnclaimable, externalUnclaimable +} + // ============================================ // API/helpers // ============================================ diff --git a/staker/staker.gno b/staker/staker.gno index f8b09825f..e9d3c9edd 100644 --- a/staker/staker.gno +++ b/staker/staker.gno @@ -382,6 +382,11 @@ func CollectReward(tokenId uint64, unwrapResult bool) string { // internal penalty to community pool gns.Transfer(a2u(consts.COMMUNITY_POOL_ADDR), reward.InternalPenalty) + unclaimableInternal, unclaimableExternal := ProcessUnclaimableReward(deposit.targetPoolPath, uint64(std.GetHeight())) + + // internal unclaimable to community pool + gns.Transfer(a2u(consts.COMMUNITY_POOL_ADDR), unclaimableInternal) + prevAddr, prevRealm := getPrev() std.Emit( "CollectRewardEmission", @@ -396,6 +401,7 @@ func CollectReward(tokenId uint64, unwrapResult bool) string { "internal_amount", ufmt.Sprintf("%d", toUser), "internal_unstakingFee", ufmt.Sprintf("%d", reward.Internal-toUser), "internal_left", ufmt.Sprintf("%d", reward.InternalPenalty), + "internal_unclaimable", ufmt.Sprintf("%d", unclaimableInternal), ) return ""