Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,15 @@ func calculatePositionReward(param CalcPositionRewardParam) []Reward {
// while preventing new rewards after tier removal
internalRewards, internalPenalties = pool.RewardStateOf(deposit).calculateInternalReward(lastCollectTime, param.CurrentTime)

// Get all incentives including archived ones with unclaimed rewards
allIncentives := pool.incentives.GetIncentivesForRewardCalculation(lastCollectTime, param.CurrentTime)

// Initialize maps for external rewards
for i := range externalRewards {
externalRewards[i] = make(map[string]int64)
externalPenalties[i] = make(map[string]int64)
}

// Get all incentives using a wide time range to ensure we get all possible incentives
// We'll filter them individually based on their specific lastCollectTime
allIncentives := pool.incentives.GetAllInTimestamps(0, param.CurrentTime*2)

// Process each external incentive with lazy initialization:
// When a new incentive is created, we don't iterate through all deposits for performance.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestStakeToken_WithEndedIncentives(t *testing.T) {
}

// Verify that there are actually no active incentives
activeIncentives := pool.incentives.GetAllInTimestamps(currentTime, currentTime+3600)
activeIncentives := pool.incentives.GetIncentivesForRewardCalculation(currentTime, currentTime+3600)
if len(activeIncentives) != 0 {
t.Fatalf("expected 0 active incentives, got %d", len(activeIncentives))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ func (self *canonicalRewardState) EmulateCalcPositionReward(positionId uint64) (
externalPenalties[i] = make(map[string]int64)
}

allIncentives := pool.incentives.GetAllInTimestamps(lastCollectTimestamp, currentTimestamp)
allIncentives := pool.incentives.GetIncentivesForRewardCalculation(lastCollectTimestamp, currentTimestamp)

for _, incentive := range allIncentives {
externalReward, externalPenalty := pool.RewardStateOf(deposit).calculateExternalReward(lastCollectTimestamp, currentTimestamp, incentive)
Expand Down
43 changes: 28 additions & 15 deletions contract/r/gnoswap/v1/staker/reward_calculation_incentives.gno
Original file line number Diff line number Diff line change
Expand Up @@ -70,26 +70,39 @@ func retrieveIncentive(tree *avl.Tree, id string) (*ExternalIncentive, bool) {
return v, true
}

// Get all incentives that is active in given [startTimestamp, endTimestamp)
func (self *Incentives) GetAllInTimestamps(startTimestamp, endTimestamp int64) map[string]*ExternalIncentive {
// GetIncentivesForRewardCalculation returns all incentives (both active and archived)
// that have rewards claimable in the given [startTimestamp, endTimestamp) period
func (self *Incentives) GetIncentivesForRewardCalculation(startTimestamp, endTimestamp int64) map[string]*ExternalIncentive {
incentives := make(map[string]*ExternalIncentive)

// Only iterate active incentives (not archived)
self.incentives.Iterate("", "", func(key string, value any) bool {
incentive, ok := value.(*ExternalIncentive)
if !ok {
return false
}

// incentive is not active
if incentive.startTimestamp > endTimestamp || incentive.endTimestamp < startTimestamp {
// Helper function to collect incentives that overlap with the query period
collectIncentives := func(tree *avl.Tree, skipRefunded bool) {
tree.Iterate("", "", func(key string, value any) bool {
incentive, ok := value.(*ExternalIncentive)
if !ok {
return false
}

// Skip refunded incentives if requested (for archived incentives)
if skipRefunded && incentive.refunded {
return false
}

// Check if incentive period overlaps with query period
if incentive.startTimestamp > endTimestamp || incentive.endTimestamp < startTimestamp {
return false
}

incentives[incentive.incentiveId] = incentive
return false
}
})
}

incentives[incentive.incentiveId] = incentive
// Collect from active incentives (don't skip any)
collectIncentives(self.incentives, false)

return false
})
// Collect from archived incentives (skip refunded ones)
collectIncentives(self.archivedIncentives, true)

return incentives
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,76 +60,6 @@ func TestIncentives_ArchiveIncentive(t *testing.T) {
}
}

func TestIncentives_GetAllInTimestamps_ExcludesArchived(t *testing.T) {
poolPath := "test_pool_timestamps"
creator := testutils.TestAddress("creator")

incentives := NewIncentives(poolPath)
currentTime := time.Now().Unix()

activeIncentive := &ExternalIncentive{
incentiveId: "active_incentive",
targetPoolPath: poolPath,
startTimestamp: currentTime - 1800,
endTimestamp: currentTime + 1800,
rewardToken: GNS_PATH,
rewardAmount: 1000,
refundee: creator,
depositGnsAmount: 100,
rewardPerSecond: 1,
}

// Create ended incentive (to be archived)
endedIncentive := &ExternalIncentive{
incentiveId: "ended_incentive",
targetPoolPath: poolPath,
startTimestamp: currentTime - 7200,
endTimestamp: currentTime - 3600,
rewardToken: GNS_PATH,
rewardAmount: 1000,
refundee: creator,
depositGnsAmount: 100,
rewardPerSecond: 1,
}

// Add both incentives to active collection
incentives.create(creator, activeIncentive)
incentives.create(creator, endedIncentive)

// Before archiving: GetAllInTimestamps should return only the active incentive
activeIncentives := incentives.GetAllInTimestamps(currentTime, currentTime+1)
if len(activeIncentives) != 1 {
t.Fatalf("expected 1 active incentive, got %d", len(activeIncentives))
}
if _, exists := activeIncentives[activeIncentive.incentiveId]; !exists {
t.Fatal("active incentive should be included in GetAllInTimestamps")
}

// Archive the ended incentive
incentives.archiveIncentive(endedIncentive.incentiveId)

// After archiving: GetAllInTimestamps should still return only the active incentive
activeIncentives = incentives.GetAllInTimestamps(currentTime, currentTime+1)
if len(activeIncentives) != 1 {
t.Fatalf("expected 1 active incentive after archiving, got %d", len(activeIncentives))
}
if _, exists := activeIncentives[activeIncentive.incentiveId]; !exists {
t.Fatal("active incentive should still be included after archiving")
}
if _, exists := activeIncentives[endedIncentive.incentiveId]; exists {
t.Fatal("archived incentive should NOT be included in GetAllInTimestamps")
}

// Test with broader time range that would include the ended incentive
broadRangeIncentives := incentives.GetAllInTimestamps(currentTime-7200, currentTime+3600)
if len(broadRangeIncentives) != 1 {
t.Fatalf("expected 1 active incentive with broad range, got %d", len(broadRangeIncentives))
}
if _, exists := broadRangeIncentives[endedIncentive.incentiveId]; exists {
t.Fatal("archived incentive should NOT be included even with broad time range")
}
}

func TestIncentives_ArchiveEdgeCasesAndMultiple(t *testing.T) {
poolPath := "test_pool_combined"
creator := testutils.TestAddress("creator")
Expand Down
Loading