diff --git a/_deploy/r/gnoswap/gns/errors.gno b/_deploy/r/gnoswap/gns/errors.gno index af5bb4e8c..de2dc57a4 100644 --- a/_deploy/r/gnoswap/gns/errors.gno +++ b/_deploy/r/gnoswap/gns/errors.gno @@ -7,8 +7,9 @@ import ( ) var ( - errInvalidYear = errors.New("[GNOSWAP-GNS-001] invalid year") - errTooManyEmission = errors.New("[GNOSWAP-GNS-002] too many emission reward") + errInvalidYear = errors.New("[GNOSWAP-GNS-001] invalid year") + errTooManyEmission = errors.New("[GNOSWAP-GNS-002] too many emission reward") + errCallbackEmissionChangeIsNil = errors.New("[GNOSWAP-GNS-003] callback emission change is nil") ) func addDetailToError(err error, detail string) string { diff --git a/_deploy/r/gnoswap/gns/gns.gno b/_deploy/r/gnoswap/gns/gns.gno index 9b75adb27..039b215ee 100644 --- a/_deploy/r/gnoswap/gns/gns.gno +++ b/_deploy/r/gnoswap/gns/gns.gno @@ -205,9 +205,6 @@ func checkErr(err error) { // It calculates the amount of gns to mint for each halving year for block range. // It also handles the left emission amount if the current block range includes halving year end block. func calculateAmountToMint(fromHeight, toHeight int64) uint64 { - prevHeight := fromHeight - currentHeight := toHeight - // if toHeight is greater than emission end height, set toHeight to emission end height endH := GetEndHeight() if toHeight > endH { @@ -259,13 +256,6 @@ func calculateAmountToMint(fromHeight, toHeight int64) uint64 { } } - if prevHeight == currentHeight { - addPerBlockMintUpdate(uint64(currentHeight), GetAmountPerBlockPerHalvingYear(GetHalvingYearByHeight(currentHeight))) - } else { - addPerBlockMintUpdate(uint64(prevHeight), GetAmountPerBlockPerHalvingYear(GetHalvingYearByHeight(prevHeight))) - addPerBlockMintUpdate(uint64(currentHeight), GetAmountPerBlockPerHalvingYear(GetHalvingYearByHeight(currentHeight))) - } - assertTooManyEmission(totalAmountToMint) return totalAmountToMint } diff --git a/_deploy/r/gnoswap/gns/halving.gno b/_deploy/r/gnoswap/gns/halving.gno index f634ae25f..1593081a8 100644 --- a/_deploy/r/gnoswap/gns/halving.gno +++ b/_deploy/r/gnoswap/gns/halving.gno @@ -7,7 +7,6 @@ import ( "gno.land/p/demo/avl" "gno.land/p/demo/json" - "gno.land/r/gnoswap/v1/common" "gno.land/r/gnoswap/v1/consts" ) @@ -62,6 +61,7 @@ type HalvingData struct { amountPerBlock []uint64 } + func (h *HalvingData) getStartBlockHeight(year int64) int64 { if year == 0 { return 0 @@ -195,6 +195,52 @@ func (h *HalvingData) initialize(startHeight, startTimestamp int64) { } } +var callbackEmissionChange func(amount uint64) + +func SetCallbackEmissionChange(callback func(amount uint64)) { + if callbackEmissionChange != nil { + panic("callbackEmissionChange already set") + } + callbackEmissionChange = callback +} + +// endHeight MUST be smaller than the current height(we don't read future halving blocks with this function) +func GetHalvingBlocksInRange(startHeight, endHeight int64) ([]int64, []uint64) { + halvingData := GetEmissionState().getHalvingData() + halvingBlocks := []int64{} + halvingEmissions := []uint64{} + for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { + startBlock := halvingData.getStartBlockHeight(year) + if startBlock < startHeight { + continue + } + if endHeight < startBlock { + break + } + halvingBlocks = append(halvingBlocks, startBlock) + halvingEmissions = append(halvingEmissions, halvingData.getAmountPerBlock(year)) + } + return halvingBlocks, halvingEmissions +} + +// height MUST be smaller than the current height(we don't read future emissions) +func GetEmission() uint64 { + halvingData := GetEmissionState().getHalvingData() + height := std.GetHeight() + + for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { + startBlock := halvingData.getStartBlockHeight(year) + endBlock := halvingData.getEndBlockHeight(year) + if startBlock <= height && height < endBlock { + return halvingData.getAmountPerBlock(year) + } + } + + // haven't reached the start of the halvings + // TODO: check if returning 0 is correct + return 0 +} + type EmissionState struct { startHeight int64 startTimestamp int64 @@ -343,7 +389,10 @@ func setAvgBlockTimeInMs(ms int64) { adjustedAmountPerBlock := amountLeft / uint64(blockLeft) // update it setAmountPerBlockPerHalvingYear(currentYear, adjustedAmountPerBlock) - addPerBlockMintUpdate(uint64(std.GetHeight()), adjustedAmountPerBlock) + if callbackEmissionChange == nil { + panic(errCallbackEmissionChangeIsNil.Error()) + } + callbackEmissionChange(adjustedAmountPerBlock) for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { if year < currentYear { @@ -371,6 +420,8 @@ func setAvgBlockTimeInMs(ms int64) { } avgBlockTimeMs = ms + + } // GetAmountByHeight returns the amount of gns to mint by height @@ -557,30 +608,3 @@ func getHalvingYearAndEndTimestamp(timestamp int64) (int64, int64) { return year, startTimestamp + (consts.TIMESTAMP_YEAR * year) } - -func GetCurrentEmission() uint64 { - emission := uint64(0) - perBlockMint.ReverseIterate("", common.EncodeUint(uint64(std.GetHeight())), func(key string, value interface{}) bool { - emission = value.(uint64) - return true - }) - return emission -} - -func EmissionUpdates(startHeight uint64, endHeight uint64) ([]uint64, []uint64) { - heights := make([]uint64, 0) - updates := make([]uint64, 0) - println("EmissionUpdates : ", startHeight, ", ", endHeight) - perBlockMint.Iterate(common.EncodeUint(startHeight), common.EncodeUint(endHeight), func(key string, value interface{}) bool { - println("EmissionUpdates : ", key, ", ", value) - heights = append(heights, common.DecodeUint(key)) - updates = append(updates, value.(uint64)) - return false - }) - - return heights, updates -} - -func addPerBlockMintUpdate(height uint64, amount uint64) { - perBlockMint.Set(common.EncodeUint(height), amount) -} diff --git a/_deploy/r/gnoswap/gns/halving_test.gno b/_deploy/r/gnoswap/gns/halving_test.gno index 478fade0f..93b57fa51 100644 --- a/_deploy/r/gnoswap/gns/halving_test.gno +++ b/_deploy/r/gnoswap/gns/halving_test.gno @@ -156,36 +156,48 @@ func TestGetHalvingInfo(t *testing.T) { func TestSetAvgBlockTimeInMsByAdmin(t *testing.T) { t.Run("panic if caller is not admin", func(t *testing.T) { + oldCallback := callbackEmissionChange + callbackEmissionChange = func(amount uint64) {} uassert.PanicsWithMessage(t, "caller(g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) has no permission", func() { SetAvgBlockTimeInMsByAdmin(1) }, ) + callbackEmissionChange = oldCallback }) t.Run("success if caller is admin", func(t *testing.T) { + oldCallback := callbackEmissionChange + callbackEmissionChange = func(amount uint64) {} std.TestSkipHeights(1) - std.TestSetRealm(adminRealm) + std.TestSetRealm(std.NewUserRealm(consts.ADMIN)) SetAvgBlockTimeInMsByAdmin(2) uassert.Equal(t, GetAvgBlockTimeInMs(), int64(2)) + callbackEmissionChange = oldCallback }) } func TestSetAvgBlockTimeInMs(t *testing.T) { t.Run("panic if caller is not governance contract", func(t *testing.T) { + oldCallback := callbackEmissionChange + callbackEmissionChange = func(amount uint64) {} uassert.PanicsWithMessage(t, "caller(g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) has no permission", func() { SetAvgBlockTimeInMs(3) }, ) + callbackEmissionChange = oldCallback }) t.Run("success if caller is governance contract", func(t *testing.T) { + oldCallback := callbackEmissionChange + callbackEmissionChange = func(amount uint64) {} std.TestSkipHeights(3) std.TestSetRealm(govRealm) SetAvgBlockTimeInMs(4) uassert.Equal(t, GetAvgBlockTimeInMs(), int64(4)) + callbackEmissionChange = oldCallback }) } diff --git a/emission/_test_helper.gno b/emission/_test_helper.gno index 6b9ae93c8..24dd4128e 100644 --- a/emission/_test_helper.gno +++ b/emission/_test_helper.gno @@ -22,10 +22,10 @@ func resetObject(t *testing.T) { t.Helper() distributionBpsPct = avl.NewTree() - distributionBpsPct.Set(strconv.Itoa(LIQUIDITY_STAKER), 7500) - distributionBpsPct.Set(strconv.Itoa(DEVOPS), 2000) - distributionBpsPct.Set(strconv.Itoa(COMMUNITY_POOL), 500) - distributionBpsPct.Set(strconv.Itoa(GOV_STAKER), 0) + distributionBpsPct.Set(strconv.Itoa(LIQUIDITY_STAKER), uint64(7500)) + distributionBpsPct.Set(strconv.Itoa(DEVOPS), uint64(2000)) + distributionBpsPct.Set(strconv.Itoa(COMMUNITY_POOL), uint64(500)) + distributionBpsPct.Set(strconv.Itoa(GOV_STAKER), uint64(0)) distributedToStaker = 0 distributedToDevOps = 0 diff --git a/emission/callback.gno b/emission/callback.gno new file mode 100644 index 000000000..1e436eeb3 --- /dev/null +++ b/emission/callback.gno @@ -0,0 +1,25 @@ +package emission + +import ( + "gno.land/r/gnoswap/v1/gns" +) + +var callbackStakerEmissionChange func(amount uint64) + +func SetCallbackStakerEmissionChange(callback func(amount uint64)) { + if callbackStakerEmissionChange != nil { + panic("callbackStakerEmissionChange already set") + } + callbackStakerEmissionChange = callback +} + +// Called when per-block emission is changed from the gns side. +// It does not process non-immediate emission changes, such as halving. +func callbackEmissionChange(amount uint64) { + callbackStakerEmissionChange(calculateAmount(amount, GetDistributionBpsPct(LIQUIDITY_STAKER))) +} + +func init() { + gns.SetCallbackEmissionChange(callbackEmissionChange) +} + diff --git a/emission/distribution.gno b/emission/distribution.gno index a16f03244..ad5808733 100644 --- a/emission/distribution.gno +++ b/emission/distribution.gno @@ -4,7 +4,6 @@ import ( "std" "strconv" - "gno.land/r/gnoswap/v1/common" "gno.land/r/gnoswap/v1/consts" "gno.land/r/gnoswap/v1/gns" @@ -47,97 +46,6 @@ func init() { distributionBpsPct.Set(strconv.Itoa(DEVOPS), uint64(2000)) distributionBpsPct.Set(strconv.Itoa(COMMUNITY_POOL), uint64(500)) distributionBpsPct.Set(strconv.Itoa(GOV_STAKER), uint64(0)) - - //addStakerPerBlockMintUpdate(uint64(std.GetHeight()), gns.GetCurrentEmission()*uint64(7500)) - lastGNSEmissionUpdateHeight = uint64(std.GetHeight()) -} - -var ( - stakerPerBlockMint = avl.NewTree() // height => uint64 -) - -var lastGNSEmissionUpdateHeight uint64 - -func updateStakerEmission() { - if lastGNSEmissionUpdateHeight == uint64(std.GetHeight()) { - return - } - - emissionHeights, emissionUpdates := gns.EmissionUpdates(lastGNSEmissionUpdateHeight, uint64(std.GetHeight())) - - for i, height := range emissionHeights { - println("*******************************[", height, "] updateStakerEmission : to ", emissionUpdates[i]*GetDistributionBpsPct(LIQUIDITY_STAKER)/10000) - stakerPerBlockMint.Set(common.EncodeUint(height), emissionUpdates[i]*GetDistributionBpsPct(LIQUIDITY_STAKER)/10000) - } - - println("lastGNSEmissionUpdateHeight : ", lastGNSEmissionUpdateHeight) - lastGNSEmissionUpdateHeight = uint64(std.GetHeight()) - println("lastGNSEmissionUpdateHeight : ", lastGNSEmissionUpdateHeight) -} - -func GetLatestStakerEmission(endHeight uint64) uint64 { - updateStakerEmission() - - var emission uint64 - stakerPerBlockMint.ReverseIterate("", common.EncodeUint(endHeight), func(key string, value interface{}) bool { - emission = value.(uint64) - return true - }) - - return emission -} - -func GetCurrentStakerEmission() uint64 { - updateStakerEmission() - - var emission uint64 - stakerPerBlockMint.ReverseIterate("", common.EncodeUint(uint64(std.GetHeight())), func(key string, value interface{}) bool { - emission = value.(uint64) - return true - }) - - return emission -} - -func GetStakerEmissionUpdates(startHeight uint64, endHeight uint64) EmissionUpdate { - updateStakerEmission() - - heights := make([]uint64, 0) - updates := make([]uint64, 0) - stakerPerBlockMint.Iterate(common.EncodeUint(startHeight), common.EncodeUint(endHeight), func(key string, value interface{}) bool { - heights = append(heights, common.DecodeUint(key)) - updates = append(updates, value.(uint64)) - return false - }) - - return EmissionUpdate{ - LastEmissionUpdate: GetLatestStakerEmission(startHeight), - EmissionUpdateHeights: heights, - EmissionUpdates: updates, - } -} - -// Stores the emission update information for a given interval. -// An EmissionUpdate is constructed by [startHeight, endHeight). -// -// Fields: -// - LastEmissionUpdate: the value of emission update, either at the startHeight, or before. -// - EmissionUpdateHeights: the heights of emission updates between [startHeight, endHeight). -// - EmissionUpdates: the values of emission updates between [startHeight, endHeight). -type EmissionUpdate struct { - LastEmissionUpdate uint64 - EmissionUpdateHeights []uint64 - EmissionUpdates []uint64 -} - -func (self *EmissionUpdate) IsEmpty() bool { - return len(self.EmissionUpdateHeights) == 0 && len(self.EmissionUpdates) == 0 -} - -func addStakerPerBlockMintUpdate(height uint64, amount uint64) { - println("[", height, "] addStakerPerBlockMintUpdate : to ", amount) - stakerPerBlockMint.Set(common.EncodeUint(height), amount/10000) - //stakerPerBlockMint.Set(common.EncodeUint(height), amount) } // ChangeDistributionPctByAdmin changes the distribution percentage for the given targets. @@ -202,13 +110,13 @@ func changeDistributionPcts( target03 int, pct03 uint64, target04 int, pct04 uint64, ) { + // First, cache the percentage of the staker just before it changes Callback if needed + // (check if the LIQUIDITY_STAKER was located between target01 and 04) setDistributionBpsPct(target01, pct01) setDistributionBpsPct(target02, pct02) setDistributionBpsPct(target03, pct03) setDistributionBpsPct(target04, pct04) - addStakerPerBlockMintUpdate(uint64(std.GetHeight()), gns.GetCurrentEmission()*pct01) - prevAddr, prevPkgPath := getPrevAsString() std.Emit( "ChangeDistributionPct", @@ -240,7 +148,7 @@ func distributeToTarget(amount uint64) uint64 { } pct := iPct.(uint64) - distAmount := calculateAmount(amount, pct) + distAmount := calculateAmount(amount, uint64(pct)) totalSent += distAmount transferToTarget(targetInt, distAmount) @@ -352,5 +260,31 @@ func ClearDistributedToGovStaker() { } func setDistributionBpsPct(target int, pct uint64) { + if target == LIQUIDITY_STAKER { + oldPct, exist := distributionBpsPct.Get(strconv.Itoa(target)) + if !exist { + panic("should not happen") + } + + if oldPct.(uint64) != pct { + callbackStakerEmissionChange(calculateAmount(gns.GetEmission(), pct)) + } + } + distributionBpsPct.Set(strconv.Itoa(target), pct) } + +func GetEmission() uint64 { + return calculateAmount(gns.GetEmission(), GetDistributionBpsPct(LIQUIDITY_STAKER)) +} + +// When there is a staker % change, it will trigger the StakerCallback first, which should "flush" the halving blocks first(by poolTier calling the GetHalvingBlocksInRange before it updates the emission). +// Therefore, if there are halving blocks exist from what we got from gns side, there must be no % change event between those blocks, which means we can use the current distribution pct to calculate the emissions for the past halving blocks. +func GetHalvingBlocksInRange(start, end int64) ([]int64, []uint64) { + halvingBlocks, halvingEmissions := gns.GetHalvingBlocksInRange(start, end) + for i := range halvingBlocks { + // Applying staker ratio for past halving blocks + halvingEmissions[i] = calculateAmount(halvingEmissions[i], GetDistributionBpsPct(LIQUIDITY_STAKER)) + } + return halvingBlocks, halvingEmissions +} diff --git a/emission/distribution_test.gno b/emission/distribution_test.gno index a2deb2bfa..a49bc16b7 100644 --- a/emission/distribution_test.gno +++ b/emission/distribution_test.gno @@ -13,6 +13,9 @@ import ( func TestChangeDistributionPctByAdmin(t *testing.T) { resetObject(t) + originCallback := callbackStakerEmissionChange + callbackStakerEmissionChange = func(amount uint64) {} + tests := []struct { name string shouldPanic bool @@ -53,10 +56,10 @@ func TestChangeDistributionPctByAdmin(t *testing.T) { targets: []int{1, 2, 3, 4}, pcts: []uint64{1000, 2000, 3000, 4000}, verify: func() { - uassert.Equal(t, uint64(1000), GetDistributionBpsPct(1)) - uassert.Equal(t, uint64(2000), GetDistributionBpsPct(2)) - uassert.Equal(t, uint64(3000), GetDistributionBpsPct(3)) - uassert.Equal(t, uint64(4000), GetDistributionBpsPct(4)) + uassert.Equal(t, uint64(1000), GetDistributionBpsPct(int(1))) + uassert.Equal(t, uint64(2000), GetDistributionBpsPct(int(2))) + uassert.Equal(t, uint64(3000), GetDistributionBpsPct(int(3))) + uassert.Equal(t, uint64(4000), GetDistributionBpsPct(int(4))) }, }, } @@ -96,11 +99,15 @@ func TestChangeDistributionPctByAdmin(t *testing.T) { } }) } + callbackStakerEmissionChange = originCallback } func TestChangeDistributionPct(t *testing.T) { resetObject(t) + originCallback := callbackStakerEmissionChange + callbackStakerEmissionChange = func(amount uint64) {} + tests := []struct { name string shouldPanic bool @@ -184,11 +191,15 @@ func TestChangeDistributionPct(t *testing.T) { } }) } + callbackStakerEmissionChange = originCallback } func TestChangeDistributionPcts(t *testing.T) { resetObject(t) + originCallback := callbackStakerEmissionChange + callbackStakerEmissionChange = func(amount uint64) {} + changeDistributionPcts( 1, 1000, 2, 2000, @@ -199,6 +210,8 @@ func TestChangeDistributionPcts(t *testing.T) { uassert.Equal(t, uint64(2000), GetDistributionBpsPct(2)) uassert.Equal(t, uint64(3000), GetDistributionBpsPct(3)) uassert.Equal(t, uint64(4000), GetDistributionBpsPct(4)) + + callbackStakerEmissionChange = originCallback } func TestCalculateAmount(t *testing.T) { @@ -324,7 +337,7 @@ func TestDistributeToTarget(t *testing.T) { shouldPanic: true, panicMsg: "[GNOSWAP-EMISSION-002] invalid emission target || invalid target(a)", setup: func() { - distributionBpsPct.Set("a", 1000) + distributionBpsPct.Set("a", uint64(1000)) }, amount: 100, }, diff --git a/emission/errors.gno b/emission/errors.gno index f97851de5..c657d0916 100644 --- a/emission/errors.gno +++ b/emission/errors.gno @@ -7,7 +7,7 @@ import ( ) var ( - errNoPermission = errors.New("[GNOSWAP-EMISSION-001] caller has no permission") + errCallbackIsNil = errors.New("[GNOSWAP-EMISSION-001] callback func is nil") errInvalidEmissionTarget = errors.New("[GNOSWAP-EMISSION-002] invalid emission target") errInvalidEmissionPct = errors.New("[GNOSWAP-EMISSION-003] invalid emission percentage") ) diff --git a/staker/README.md b/staker/README.md new file mode 100644 index 000000000..8ad9e6b2c --- /dev/null +++ b/staker/README.md @@ -0,0 +1,226 @@ +# Staker Reward + +## Abstract + +The **Staker** module handles the distribution of both **internal** (GNS emission) and **external** (user-provided) rewards to stakers: + +- **Internal rewards (GNS emission)** are allocated to “tiered” pools (tiers 1, 2, and 3). First, emission is split across the tiers according to the **TierRatio**. Then, within each tier, the emission is shared evenly among member pools and finally distributed proportionally to each staked position’s in-range liquidity. + +- **External rewards** (user-provided incentives) can be created for specific pools. Each external incentive emits a constant reward per block. Any user with in-range staked liquidity on that pool can claim a share of the reward, proportional to their staked liquidity. + +- If, during a given block, no staked liquidity is in range, the internal emission is diverted to the community pool, and any external reward for that block is returned to the incentive creator. + +- Every staked position has a designated warmup schedule. As it remains staked, the position progresses through multiple warmup periods. In each warmup period, a certain percentage of the reward is awarded to the position, and the remainder goes either to the community pool (for internal incentives) or is returned to the incentive creator (for external incentives). + +## Main Reward Calculation Logic + +Below is an example function that computes the rewards for a position. It does the following: + +1. Caches any pending per-pool internal incentive rewards up to the current block. +2. Retrieves or initializes the pool from `param.Pools`. +3. Accumulates internal and external rewards (and the corresponding penalties) for each warmup period. +4. Returns a list of rewards, one for each warmup period of the staked position. + +```go +func CalcPositionReward(param CalcPositionRewardParam) []Reward { + // cache per-pool rewards in the internal incentive(tiers) + param.PoolTier.cacheReward(param.CurrentHeight, param.Pools) + + deposit := param.Deposits.Get(param.TokenId) + poolPath := deposit.targetPoolPath + + pool, ok := param.Pools.Get(poolPath) + if !ok { + pool = NewPool(poolPath, param.CurrentHeight) + param.Pools.Set(poolPath, pool) + } + + lastCollectHeight := deposit.lastCollectHeight + + // Initialize arrays to hold reward & penalty data for each warmup + internalRewards := make([]uint64, len(deposit.warmups)) + internalPenalties := make([]uint64, len(deposit.warmups)) + externalRewards := make([]map[string]uint64, len(deposit.warmups)) + externalPenalties := make([]map[string]uint64, len(deposit.warmups)) + + if param.PoolTier.CurrentTier(poolPath) != 0 { + // Internal incentivized pool + internalRewards, internalPenalties = pool.RewardStateOf(deposit).CalculateInternalReward(lastCollectHeight, param.CurrentHeight) + } + + // Retrieve all active external incentives from lastCollectHeight to CurrentHeight + allIncentives := pool.incentives.GetAllInHeights(lastCollectHeight, param.CurrentHeight) + + for i := range externalRewards { + externalRewards[i] = make(map[string]uint64) + externalPenalties[i] = make(map[string]uint64) + } + + for incentiveId, incentive := range allIncentives { + // External incentivized pool + externalReward, externalPenalty := pool.RewardStateOf(deposit).CalculateExternalReward( + int64(lastCollectHeight), + int64(param.CurrentHeight), + incentive, + ) + + for i := range externalReward { + externalRewards[i][incentiveId] = externalReward[i] + externalPenalties[i][incentiveId] = externalPenalty[i] + } + } + + rewards := make([]Reward, len(internalRewards)) + for i := range internalRewards { + rewards[i] = Reward{ + Internal: internalRewards[i], + InternalPenalty: internalPenalties[i], + External: externalRewards[i], + ExternalPenalty: externalPenalties[i], + } + } + + return rewards +} +``` + +## TickCrossHook + +`TickCrossHook` is triggered whenever a swap crosses an initialized tick. If any staked position uses that tick, the hook: + +1. Updates the `stakedLiquidity` and `globalRewardRatioAccumulation`. +2. Sets the historical tick (for record-keeping and later calculations). +3. Depending on whether the total staked liquidity is now nonzero or zero, it begins or ends any unclaimable period. +4. Updates the `CurrentOutsideAccumulation` for the tick. + +The variable `globalRewardRatioAccumulation` holds the integral of $\(f(h) = 1 \div \text{TotalStakedLiquidity}(h)\)$, but only when \(\text{TotalStakedLiquidity}(h)\) is nonzero. Meanwhile, `CurrentOutsideAccumulation` tracks the same integral but over intervals where the pool tick is considered “outside” (i.e., on the opposite side of the current tick). When a tick cross occurs, this “outside” condition may flip, so the hook adjusts `CurrentOutsideAccumulation` by subtracting it from the latest `globalRewardRatioAccumulation`. + +## Internal Reward + +Internal rewards are distributed across tiers and then among pools. Each pool’s internal reward is determined as: + +```math +\text{poolReward}(\mathrm{pool}) += \frac{\text{emission} \,\times\, \mathrm{TierRatio}\!\bigl(\mathrm{tier}(\mathrm{pool})\bigr)} + {\mathrm{Count}\!\bigl(\mathrm{tier}(\mathrm{pool})\bigr)}. +``` + +The TierRatio is defined piecewise: + +```math +\mathrm{TierRatio}(t) \;=\; +\begin{cases} +[1,\,0,\,0]_{\,t-1}, +& \text{if } \mathrm{Count}(2) = 0 \;\land\; \mathrm{Count}(3) = 0, \\[8pt] +[0.8,\,0,\,0.2]_{\,t-1}, +& \text{if } \mathrm{Count}(2) = 0, \\[8pt] +[0.7,\,0.3,\,0]_{\,t-1}, +& \text{if } \mathrm{Count}(3) = 0, \\[8pt] +[0.5,\,0.3,\,0.2]_{\,t-1}, +& \text{otherwise}. +\end{cases} +``` + +The total emission used by the staker contract is: + +```math +\text{emission} += \mathrm{GNSEmissionPerSecond} + \times + \Bigl(\frac{\mathrm{avgMsPerBlock}}{1000}\Bigr) + \times + \mathrm{StakerEmissionRatio}. +``` + +> **Note:** +> - There is always at least one tier-1 pool. +> - `GNSEmissionPerSecond` is a constant apart from any halving events (ignored here). +> - When `avgMsPerBlock` or `StakerEmissionRatio` changes, a callback is triggered to cache rewards up to the current block and then update the emission rate. This also happens when a pool has its tier changed. + +### How Internal Rewards Are Cached and Distributed + +- **PoolTier.cacheReward** recalculates all reward-related data from the last cache height to the current block. + - **Halving blocks:** If there are halving events in this interval, the process “splits” the caching at each halving block, updates the staker emission accordingly, and continues. + - **Unclaimable period:** If the pool had no in-range stakers (currently in unclaimable state), it updates the unclaimable accumulation using the old emission rate, then starts a new period immediately so the future accumulation could be done based on the new emission rate. + - The function finally updates the `GlobalRewardRatioAccumulation`, which is used later to compute each position’s rewards. + +- After caching is updated to the current block, **CalculateInternalReward** computes the total claimable internal rewards for a position. Consider the following formulation, which handles multiple warmup intervals: + +```math +\begin{aligned} +\mathrm{TotalRewardRatio}(s,e) +&= + \sum_{i=0}^{m-1} + \Bigl[ + \Delta\mathrm{Raw}\bigl(\alpha_i,\, \beta_i\bigr) + \Bigr] + \times + r_i, +\\[6pt] +\alpha_i +&= + \max\!\bigl(s,\, H_{i-1}\bigr), +\quad +\beta_i += + \min\!\bigl(e,\, H_{i}\bigr), +\\[6pt] +\Delta\mathrm{Raw}(a, b) +&= + \mathrm{CalcRaw}(b) + \;-\; + \mathrm{CalcRaw}(a), +\\[6pt] +\mathrm{CalcRaw}(h) +&= + \begin{cases} + L(h) \;-\; U(h), + & \text{if } \mathrm{tick}(h) < \ell, \\[4pt] + U(h) \;-\; L(h), + & \text{if } \mathrm{tick}(h) \ge u, \\[4pt] + G(h) \;-\; \bigl(L(h) + U(h)\bigr), + & \text{otherwise}. + \end{cases} +\end{aligned} +``` + +where +- Each warmup interval $\(\bigl[H_{i-1},\,H_{i}\bigr]\)$ has a reward ratio $r_i$. +- $\alpha_i = \max(s,\, H_{i-1})$ and $\beta_i = \min(e,\, H_{i})$ slice the interval to fit $[s,e)$. If $\alpha_i \ge \beta_i$, that segment contributes zero. +- $\(L(h)\)$ = `tickLower.OutsideAccumulation(h)` +- $\(U(h)\)$ = `tickUpper.OutsideAccumulation(h)` +- $\(G(h)\)$ = `globalRewardRatioAccumulation(h)` +- $\(\ell\)$ = `tickLower.id`, $\(u\)$ = `tickUpper.id` + +The final reward for a position is the sum of each applicable `TotalRewardRatio` multiplied by `poolReward` and `positionLiquidity`: +```math +\begin{aligned} +\text{finalReward} +&= + \text{TotalRewardRatio} + \;\times\; + \text{poolReward} + \;\times\; + \text{positionLiquidity} +\\[6pt] +&= + \Bigl( + \int_{s}^{e} + \frac{1}{\mathrm{TotalStakedLiquidity}(h)} + \, dh + \Bigr) + \;\times\; + \text{poolReward} + \;\times\; + \text{positionLiquidity} +\\[6pt] +&= + \int_{s}^{e} + \frac{\text{poolReward}\;\times\;\text{positionLiquidity}}{\mathrm{TotalStakedLiquidity}(h)} + \, dh. +\end{aligned} +``` + +## External Reward + +External rewards emit a constant **reward per block** for their duration. To calculate the external reward for a specific incentive, we reuse the same approach of computing a `TotalRewardRatio` (similar to the internal reward method), but without any tier-based pooling or variable `poolReward`. Instead, we multiply the `TotalRewardRatio` by `ExternalIncentive.rewardPerBlock` and `positionLiquidity` for the relevant blocks. diff --git a/staker/__TEST_full_internal_external_test.gnoA b/staker/__TEST_full_internal_external_test.gnoA new file mode 100644 index 000000000..ce35fba7f --- /dev/null +++ b/staker/__TEST_full_internal_external_test.gnoA @@ -0,0 +1,1282 @@ +package staker + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + pusers "gno.land/p/demo/users" + + "gno.land/r/gnoswap/v1/common" + "gno.land/r/gnoswap/v1/consts" + + en "gno.land/r/gnoswap/v1/emission" + pl "gno.land/r/gnoswap/v1/pool" + pn "gno.land/r/gnoswap/v1/position" + rr "gno.land/r/gnoswap/v1/router" + + "gno.land/r/gnoswap/v1/gnft" + "gno.land/r/gnoswap/v1/gns" + + "gno.land/r/demo/wugnot" + "gno.land/r/onbloc/bar" + "gno.land/r/onbloc/baz" + "gno.land/r/onbloc/foo" + "gno.land/r/onbloc/qux" +) + +const ( + ugnotDenom string = "ugnot" + ugnotPath string = "gno.land/r/gnoswap/v1/pool:ugnot" + wugnotPath string = "gno.land/r/demo/wugnot" + gnsPath string = "gno.land/r/gnoswap/v1/gns" + barPath string = "gno.land/r/onbloc/bar" + bazPath string = "gno.land/r/onbloc/baz" + fooPath string = "gno.land/r/onbloc/foo" + oblPath string = "gno.land/r/onbloc/obl" + quxPath string = "gno.land/r/onbloc/qux" + + fee100 uint32 = 100 + fee500 uint32 = 500 + fee3000 uint32 = 3000 + maxApprove uint64 = 18446744073709551615 + max_timeout int64 = 9999999999 + + genesisBlockHeight = int64(123) +) + +var ( + wugnotAddr = consts.WUGNOT_ADDR + stakerAddr = consts.STAKER_ADDR + + externalCreator = testutils.TestAddress("externalCreator") +) + +func TestFullInternalExternal(t *testing.T) { + testInit(t) + testCreatePoolWugnotGns3000Tier01(t) + testCreateExternalIncentiveGns(t) // GNS로 리워드 생성 ( gnot:gns:0.3% 풀은 인터널도 GNS고 익스터널도 GNS ) + testMintPos01InRange(t) + + testCreateBarBaz500Tier02(t) // 포지션 아예 없어서 커뮤니티 풀로 빠져야 함 + testMintPos02InRange(t) // bar:baz:0.05% + testMintPos03OutRange(t) // gnot:gns:0.3% + testMintPos04OutRange(t) // bar:baz:0.05% + testMintPos05OutRange(t) // gnot:gns:0.3% + testMintPos06OutRange(t) // bar:baz:0.05% + + testStakeToken01(t) // 여기 직전까지는 스테이킹 된 포지션 없어서 wugnot gns 0.3% 풀에 쌓인 리워드 커뮤니티 풀로 빠져야 함 + testStakeToken02(t) // + testStakeToken03(t) // + testStakeToken04(t) // + testStakeToken05(t) // + testStakeToken06(t) // + + testCollectRewardAllForWarmUp30(t) // 10블록 쯤 증가시키고, 한 블록 내에서 1~6번 포지션 리워드 수령 (웜업 30% 구간) + testCollectRewardAllForWarmUp50(t) // 웜업 50% 까지 증가시키고, 한 블록 내에서 1~6번 포지션 리워드 수령 + testCollectRewardAllForWarmUp70(t) // 웜업 70% 까지 증가시키고, 한 블록 내에서 1~6번 포지션 리워드 수령 + testCollectRewardAllForWarmUp100(t) // 웜업 100% 까지 증가시키고, 한 블록 내에서 1~6번 포지션 리워드 수령 + + testSwapExactIn(t) + testSwapExactOut(t) + + testCreatePoolBarFoo100Tier03(t) // 틱 600000, 인터널 티어 #3 + testOneClickStakingPos07OutRange(t) // bar:foo:0.01%, 원클릭 스테이킹으로 애초에 outRange로 스테이킹 됨 => 블록 증가 후 7번 포지션 리워드 수령 해보면 outRange기 떄문에 커뮤 풀로 빠져야 함 + + testUnstakeToken01(t) // 전체 스테이킹 된 유동성 변경 + + // EXTERNAL ONLY + testCreatePoolBazQux3000ExternalOnly(t) // 풀 만드는데 익스터널 인센티브만 있는 풀임 + testCreateExternalIncentiveBaz(t) // 위에서 만든 풀 대상으로 baz 익스터널 인센티브 생성 + testMintPos08InRange(t) // 포지션 생성 + testStakeToken08(t) // 스테이킹 + testCollectReward08(t) // 10 블록 쯤 증가시키고 리워드 수령하면 0으로 나와야 함 (익스터널 아직 시작 안 됨) + testStartExternalIncentive(t) // 익스터널 인센티브 시작 + testCollectReward08AfterStart(t) // 10 블록 증가 후 리워드 수령하면 웜업 구간에 맞는 10 블록 만큼의 리워드 나와야 함 ( 몇 퍼센트 구간인지는 위에서 익스터널 시작시킬려고 몇 블록을 스킵했는지에 따라 다름) + testEndExternalIncentiveBaz(t) // baz 인센티브 종료 ( 페널티 수량 환불되야 함 ) + + testReStakeTokenPos01(t) // 스테이킹 해서 웝업 100% 찍었다가 언스테이킹 된 토큰 다시 스테이킹 ( 웜업 처음(30%)부터 적용되야 함 ) + + testChangeAvgBlockTimeTo4000(t) // 블록 시간 2배로 증가 + testChangeDistributionPctByAdmin(t) // 스테이커한테 가는 에미션 비율 변경 +} + +func testInit(t *testing.T) { + t.Run("initialize", func(t *testing.T) { + println("[", std.GetHeight(), "] [testInit]") + std.TestSetRealm(adminRealm) + + // 언스테이킹 수수료 0으로 바꿈 // 계산 편함 + SetUnstakingFeeByAdmin(0) + println("[", std.GetHeight(), "] [testInit] SetUnstakingFeeByAdmin(0)") + + // 에미션 분배 커뮤니티 비율 0%로 (대신 devOps 한테 많이 가게)) + // 에미션 기본 분배 대상 4개 중에 커뮤니티 풀한테 주는 비율이 이미 있어서 인터널 페널티 발생 시 잔액에 영향을 주기 때문에 애초에 커뮤니티 풀한테는 자동으로 안 풀리게 하고 테스트하는게 깔끔 + en.ChangeDistributionPctByAdmin( + 1, 7500, // 스테이커 + 2, 2500, // 데브옵스 + 3, 0, // 커뮤니티풀 + 4, 0, // xGNS + ) + println("[", std.GetHeight(), "] [testInit] ChangeDistributionPctByAdmin(75, 25, 0, 0)") + + // admin 계정한테 wugnot 토큰 미리 좀 넉넉하게 할당 + std.TestIssueCoins(adminAddr, std.Coins{{"ugnot", 100_000_000_000_000}}) + banker := std.GetBanker(std.BankerTypeRealmSend) + banker.SendCoins(adminAddr, wugnotAddr, std.Coins{{"ugnot", 50_000_000_000_000}}) + std.TestSetOrigSend(std.Coins{{"ugnot", 50_000_000_000_000}}, nil) + wugnot.Deposit() + std.TestSetOrigSend(nil, nil) + uassert.Equal(t, uint64(50_000_000_000_000), wugnot.BalanceOf(pusers.AddressOrName(adminAddr))) + uassert.Equal(t, "50000000000000ugnot", (banker.GetCoins(adminAddr).String())) + }) +} + +func testCreatePoolWugnotGns3000Tier01(t *testing.T) { + t.Run("create pool gnot:gns:0.3%", func(t *testing.T) { + println("[", std.GetHeight(), "] [create pool gnot:gns:0.3%]") + std.TestSetRealm(adminRealm) + + gns.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + pl.CreatePool(wugnotPath, gnsPath, 3000, common.TickMathGetSqrtRatioAtTick(0).ToString()) // current tick 0 + println("[", std.GetHeight(), "] Already Set PoolTier 1") + + uassert.Equal(t, uint64(100000000000000), gns.TotalSupply()) + uassert.Equal(t, uint64(0), gnsBalance(consts.EMISSION_ADDR)) + uassert.Equal(t, uint64(0), gnsBalance(consts.STAKER_ADDR)) + uassert.Equal(t, uint64(0), gnsBalance(consts.DEV_OPS)) + std.TestSkipHeights(3) + }) +} + +func testCreateExternalIncentiveGns(t *testing.T) { + t.Run("create external incentive gns for gnot:gns:0.3% pool", func(t *testing.T) { + + println("[", std.GetHeight(), "] [create external incentive gns for gnot:gns:0.3% pool]") + std.TestSetRealm(adminRealm) + + gns.Approve(a2u(consts.STAKER_ADDR), consts.UINT64_MAX) // this includes depositGnsAmount + CreateExternalIncentive( + "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000", + consts.GNS_PATH, + 20000000, + 1234569600, + 1234569600+TIMESTAMP_90DAYS, + ) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testMintPos01InRange(t *testing.T) { + t.Run("mint position 01 in range", func(t *testing.T) { + println("[", std.GetHeight(), "] [mint position 01 in range]") + std.TestSetRealm(adminRealm) + + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + pn.Mint( + wugnotPath, + gnsPath, + fee3000, + int32(-60), + int32(60), + "1000", + "1000", + "0", + "0", + max_timeout, + adminAddr, + adminAddr, + ) + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testCreateBarBaz500Tier02(t *testing.T) { + t.Run("create bar:baz:500 pool, and set internal emission tier #2", func(t *testing.T) { + println("[", std.GetHeight(), "] [create bar:baz:500 pool, and set internal emission tier #2]") + std.TestSetRealm(adminRealm) + + gns.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + pl.CreatePool(barPath, bazPath, 500, common.TickMathGetSqrtRatioAtTick(0).ToString()) + + println("[", std.GetHeight(), "] (gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:500) Set PoolTier 2") + SetPoolTierByAdmin("gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:500", 2) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testMintPos02InRange(t *testing.T) { + t.Run("mint position 02 in range", func(t *testing.T) { + println("[", std.GetHeight(), "] [mint position 02 in range]") + std.TestSetRealm(adminRealm) + + bar.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + baz.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + pn.Mint( + barPath, + bazPath, + fee500, + int32(-60), + int32(60), + "1000", + "1000", + "0", + "0", + max_timeout, + adminAddr, + adminAddr, + ) + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testMintPos03OutRange(t *testing.T) { + t.Run("mint position 03 out range", func(t *testing.T) { + println("[", std.GetHeight(), "] [mint position 03 out range]") + std.TestSetRealm(adminRealm) + + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + pn.Mint( + wugnotPath, + gnsPath, + fee3000, + int32(60), + int32(120), + "1000", + "1000", + "0", + "0", + max_timeout, + adminAddr, + adminAddr, + ) + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testMintPos04OutRange(t *testing.T) { + t.Run("mint position 04 out range", func(t *testing.T) { + println("[", std.GetHeight(), "] [mint position 04 out range]") + std.TestSetRealm(adminRealm) + + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + pn.Mint( + barPath, + bazPath, + fee500, + int32(60), + int32(120), + "1000", + "1000", + "0", + "0", + max_timeout, + adminAddr, + adminAddr, + ) + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testMintPos05OutRange(t *testing.T) { + t.Run("mint position 05 out range", func(t *testing.T) { + println("[", std.GetHeight(), "] [mint position 05 out range]") + std.TestSetRealm(adminRealm) + + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + pn.Mint( + wugnotPath, + gnsPath, + fee3000, + int32(-120), + int32(-60), + "1000", + "1000", + "0", + "0", + max_timeout, + adminAddr, + adminAddr, + ) + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testMintPos06OutRange(t *testing.T) { + t.Run("mint position 06 out range", func(t *testing.T) { + println("[", std.GetHeight(), "] [mint position 06 out range]") + std.TestSetRealm(adminRealm) + + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + pn.Mint( + barPath, + bazPath, + fee500, + int32(-120), + int32(-60), + "1000", + "1000", + "0", + "0", + max_timeout, + adminAddr, + adminAddr, + ) + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testStakeToken01(t *testing.T) { + t.Run("stake token 01", func(t *testing.T) { + println("[", std.GetHeight(), "][****** Reward Available ******] [stake token 01]") + std.TestSetRealm(adminRealm) + + gnft.Approve(stakerAddr, common.TokenIdFrom(1)) + StakeToken(1) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testStakeToken02(t *testing.T) { + t.Run("stake token 02", func(t *testing.T) { + println("[", std.GetHeight(), "][****** Reward Available ******] [stake token 02]") + std.TestSetRealm(adminRealm) + + gnft.Approve(stakerAddr, common.TokenIdFrom(2)) + StakeToken(2) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testStakeToken03(t *testing.T) { + t.Run("stake token 03", func(t *testing.T) { + println("[", std.GetHeight(), "][****** Reward No OutRange ******] [stake token 03]") + std.TestSetRealm(adminRealm) + + gnft.Approve(stakerAddr, common.TokenIdFrom(3)) + StakeToken(3) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testStakeToken04(t *testing.T) { + t.Run("stake token 04", func(t *testing.T) { + println("[", std.GetHeight(), "][****** Reward No OutRange ******] [stake token 04]") + std.TestSetRealm(adminRealm) + + gnft.Approve(stakerAddr, common.TokenIdFrom(4)) + StakeToken(4) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testStakeToken05(t *testing.T) { + t.Run("stake token 05", func(t *testing.T) { + println("[", std.GetHeight(), "][****** Reward No OutRange ******] [stake token 05]") + std.TestSetRealm(adminRealm) + + gnft.Approve(stakerAddr, common.TokenIdFrom(5)) + StakeToken(5) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testStakeToken06(t *testing.T) { + t.Run("stake token 06", func(t *testing.T) { + println("[", std.GetHeight(), "][****** Reward No OutRange ******] [stake token 06]") + std.TestSetRealm(adminRealm) + + gnft.Approve(stakerAddr, common.TokenIdFrom(6)) + StakeToken(6) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testCollectRewardAllForWarmUp30(t *testing.T) { + t.Run("collect reward for all position, while warm up is in 30", func(t *testing.T) { + println("[", std.GetHeight(), "] [collect reward for all position, while warm up is in 30%]") + std.TestSetRealm(adminRealm) + + CollectReward(1, false) + CollectReward(2, false) + CollectReward(3, false) + CollectReward(4, false) + CollectReward(5, false) + CollectReward(6, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testCollectRewardAllForWarmUp50(t *testing.T) { + msInDay := int64(86400000) + blocksInDay := msInDay / int64(gns.GetAvgBlockTimeInMs()) + blocksIn5Days := int64(5 * blocksInDay) + blocksIn10Days := int64(10 * blocksInDay) + blocksIn30Days := int64(30 * blocksInDay) + + t.Run("make warm up to 50% (currently reward for 30% and 50% are mixed)", func(t *testing.T) { + std.TestSkipHeights(blocksIn5Days) + println("[", std.GetHeight(), "] [made all staked position warm up to 50%]") + std.TestSetRealm(adminRealm) + + CollectReward(1, false) + CollectReward(2, false) + CollectReward(3, false) + CollectReward(4, false) + CollectReward(5, false) + CollectReward(6, false) + + std.TestSkipHeights(1) + }) + + t.Run("only single block for 50% warm up", func(t *testing.T) { + println("[", std.GetHeight(), "] [collect reward for all position, while warm up is in 50%]") + std.TestSetRealm(adminRealm) + + CollectReward(1, false) + CollectReward(2, false) + CollectReward(3, false) + CollectReward(4, false) + CollectReward(5, false) + CollectReward(6, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testCollectRewardAllForWarmUp70(t *testing.T) { + msInDay := int64(86400000) + blocksInDay := msInDay / int64(gns.GetAvgBlockTimeInMs()) + blocksIn5Days := int64(5 * blocksInDay) + blocksIn10Days := int64(10 * blocksInDay) + blocksIn30Days := int64(30 * blocksInDay) + + t.Run("make warm up to 70% (currently reward for 50% and 70% are mixed)", func(t *testing.T) { + std.TestSkipHeights(blocksIn10Days) + println("[", std.GetHeight(), "] [made all staked position warm up to 70%]") + std.TestSetRealm(adminRealm) + + CollectReward(1, false) + CollectReward(2, false) + CollectReward(3, false) + CollectReward(4, false) + CollectReward(5, false) + CollectReward(6, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("only single block for 70% warm up", func(t *testing.T) { + println("[", std.GetHeight(), "] [collect reward for all position, while warm up is in 70%]") + std.TestSetRealm(adminRealm) + + CollectReward(1, false) + CollectReward(2, false) + CollectReward(3, false) + CollectReward(4, false) + CollectReward(5, false) + CollectReward(6, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testCollectRewardAllForWarmUp100(t *testing.T) { + msInDay := int64(86400000) + blocksInDay := msInDay / int64(gns.GetAvgBlockTimeInMs()) + blocksIn5Days := int64(5 * blocksInDay) + blocksIn10Days := int64(10 * blocksInDay) + blocksIn30Days := int64(30 * blocksInDay) + + t.Run("make warm up to 100% (currently reward for 70% and 100% are mixed)", func(t *testing.T) { + std.TestSkipHeights(blocksIn30Days) + println("[", std.GetHeight(), "] [made all staked position warm up to 100%]") + std.TestSetRealm(adminRealm) + + CollectReward(1, false) + CollectReward(2, false) + CollectReward(3, false) + CollectReward(4, false) + CollectReward(5, false) + CollectReward(6, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("only single block for 100% warm up", func(t *testing.T) { + println("[", std.GetHeight(), "] [collect reward for all position, while warm up is in 100%]") + std.TestSetRealm(adminRealm) + + CollectReward(1, false) + CollectReward(2, false) + CollectReward(3, false) + CollectReward(4, false) + CollectReward(5, false) + CollectReward(6, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testSwapExactIn(t *testing.T) { + t.Run("swap token0 to token1 500 // swap #1", func(t *testing.T) { + std.TestSetRealm(adminRealm) + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + + wugnot.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + + tokenIn, tokenOut := rr.ExactInSwapRoute( + wugnotPath, // inputToken + gnsPath, // outputToken + "500", // finalAmountIn + "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000", // RouteArr + "100", // quoteArr + "0", // amountOutMin + max_timeout, // deadline + ) + // tick changed + // > from 0 to -30 + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("check reward for position 01 // #1", func(t *testing.T) { + // position-01 range + // > from -60 ~ 60 + // stil in range + // - there are some rewards + + std.TestSetRealm(adminRealm) + CollectReward(1, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("swap token0 to token1 500 // swap #2", func(t *testing.T) { + std.TestSetRealm(adminRealm) + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + + wugnot.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + + tokenIn, tokenOut := rr.ExactInSwapRoute( + wugnotPath, // inputToken + gnsPath, // outputToken + "500", // finalAmountIn + "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000", // RouteArr + "100", // quoteArr + "0", // amountOutMin + max_timeout, // deadline + ) + // tick changed + // > from 0 to -30 + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("check reward for position 01 // #2", func(t *testing.T) { + // position-01 range + // > from -60 ~ 60 + // stil in range + // - there are some rewards + + std.TestSetRealm(adminRealm) + CollectReward(1, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("swap token0 to token1 500 // swap #3", func(t *testing.T) { + std.TestSetRealm(adminRealm) + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + + wugnot.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + + tokenIn, tokenOut := rr.ExactInSwapRoute( + wugnotPath, // inputToken + gnsPath, // outputToken + "500", // finalAmountIn + "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000", // RouteArr + "100", // quoteArr + "0", // amountOutMin + max_timeout, // deadline + ) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + // tick changed + // > from -60 to -90 + + t.Run("check reward for position 01 // #3-1", func(t *testing.T) { + std.TestSetRealm(adminRealm) + CollectReward(1, false) // should have reward for block that exeucted swap + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + t.Run("check reward for position 01 // #3-2", func(t *testing.T) { + // position-01 range + // > from -60 ~ 60 + // out range + + std.TestSetRealm(adminRealm) + CollectReward(1, false) // should have no reward since position is out of range + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("swap token0 to token1 500 // swap #4", func(t *testing.T) { + std.TestSetRealm(adminRealm) + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + + wugnot.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + + tokenIn, tokenOut := rr.ExactInSwapRoute( + wugnotPath, // inputToken + gnsPath, // outputToken + "500", // finalAmountIn + "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000", // RouteArr + "100", // quoteArr + "0", // amountOutMin + max_timeout, // deadline + ) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + // tick changed + // > from -90 to -119 + // position-01 range + // > from -60 ~ 60 + // out range + t.Run("check reward for position 01 // #4", func(t *testing.T) { + std.TestSetRealm(adminRealm) + CollectReward(1, false) // should have reward for block that exeucted swap + std.TestSkipHeights(1) + }) +} + +func testSwapExactOut(t *testing.T) { + t.Run("swap token1 to token1 0 // swap #1", func(t *testing.T) { + std.TestSetRealm(adminRealm) + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + + wugnot.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + + tokenIn, tokenOut := rr.ExactOutSwapRoute( + gnsPath, // inputToken + wugnotPath, // outputToken + "500", // amountOut + "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:3000", // RouteArr + "100", // quoteArr + "1000", // amountInMax + max_timeout, // deadline + ) + // tick changed + // > from -119 ~ -89 + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("check reward for position 03 // #1", func(t *testing.T) { + // position-05 range + // > from 60 ~ 120 + // out range + + std.TestSetRealm(adminRealm) + CollectReward(3, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("swap token1 to token1 0 // swap #2", func(t *testing.T) { + std.TestSetRealm(adminRealm) + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + + wugnot.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + + tokenIn, tokenOut := rr.ExactOutSwapRoute( + gnsPath, // inputToken + wugnotPath, // outputToken + "500", // amountOut + "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:3000", // RouteArr + "100", // quoteArr + "1000", // amountInMax + max_timeout, // deadline + ) + // tick changed + // > from -89 ~ -60 + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("check reward for position 03 // #2", func(t *testing.T) { + // position-05 range + // > from 60 ~ 120 + // out range + + std.TestSetRealm(adminRealm) + CollectReward(3, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("swap token1 to token1 0 // swap #3", func(t *testing.T) { + std.TestSetRealm(adminRealm) + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + + wugnot.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + + tokenIn, tokenOut := rr.ExactOutSwapRoute( + gnsPath, // inputToken + wugnotPath, // outputToken + "500", // amountOut + "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:3000", // RouteArr + "100", // quoteArr + "1000", // amountInMax + max_timeout, // deadline + ) + // tick changed + // > from -60 ~ -30 + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("check reward for position 03 // #3", func(t *testing.T) { + // position-05 range + // > from 60 ~ 120 + // out range + + std.TestSetRealm(adminRealm) + CollectReward(3, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("swap token1 to token1 0 // swap #4", func(t *testing.T) { + std.TestSetRealm(adminRealm) + wugnot.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + + wugnot.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + gns.Approve(common.AddrToUser(consts.ROUTER_ADDR), consts.UINT64_MAX) + + tokenIn, tokenOut := rr.ExactOutSwapRoute( + gnsPath, // inputToken + wugnotPath, // outputToken + "500", // amountOut + "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:3000", // RouteArr + "100", // quoteArr + "1000", // amountInMax + max_timeout, // deadline + ) + // tick changed + // > from -30 ~ 0 + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("check reward for position 03 // #4", func(t *testing.T) { + // position-05 range + // > from 60 ~ 120 + // out range + + std.TestSetRealm(adminRealm) + CollectReward(3, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testCreatePoolBarFoo100Tier03(t *testing.T) { + t.Run("create bar:foo:100 pool, and set internal emission tier #3", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + pl.CreatePool(barPath, fooPath, fee100, common.TickMathGetSqrtRatioAtTick(600000).ToString()) + std.TestSkipHeights(1) + + // height 1944165 + SetPoolTierByAdmin("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:100", 3) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testOneClickStakingPos07OutRange(t *testing.T) { + t.Run("mint and stake grc20 pair (pos #7)", func(t *testing.T) { + std.TestSetRealm(adminRealm) + bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + foo.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + + lpTokenId, liquidity, amount0, amount1, poolPath := MintAndStake( + barPath, // token0 + fooPath, // token1 + fee100, // fee + int32(-60), // tickLower + int32(60), // tickUpper + "1000", // amount0Desired + "1000", // amount1Desired + "0", // amount0Min + "0", // amount1Min + max_timeout, + ) + uassert.Equal(t, lpTokenId, uint64(7)) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("collect reward for position 07", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldCommunityPool := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) + CollectReward(7, false) + // height 1944167 + + newCommunityPool := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) + uassert.Equal(t, uint64(4280820), newCommunityPool-oldCommunityPool) + // 현재 모든 인터널 티어에 대해서 풀이 존재 + // > 블록 당 에미션 리워드 스테이커 비율 10702054 + // > 티어 1, 50% ~= 5351027 + // > 티어 2, 30% ~= 3210616 + // > 티어 3, 20% ~= 2140410 + + // bar:foo:0.1% 풀이 1944165 블록에서 티어 3으로 설정되었고, 현재 블록은 1944167 + // 2 블록 만큼의 리워드(2140410 * 2 = 4280820)가 해당 풀까지 전달되었으나 분배 할 포지션이 없는 관계로 커뮤니티 풀로 빠져야 함 + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testUnstakeToken01(t *testing.T) { + t.Run("unstake token 01", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + UnstakeToken(1, false) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("collect reward for position 03 (has same pool with position 01)", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + // wugnot:gns:0.3% // currentTick: 0 + // 1번 포지션 inRange // -60 ~ 60 || 언스테이킹 됨 + // 3번 포지션 outRange // 60 ~ 120 + // 5번 포지션 outRange // -120 ~ -60 + // > 결국 해당 풀은 outRange 포지션만 있기에 해당 풀에 분배되는 에미션 리워드(티어1 = 5351027)는 커뮤니티 풀로 빠져야 함 + + oldCommuGns := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) + CollectReward(3, false) // 더미 수령 + newCommuGns := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) + uassert.Equal(t, uint64(5351027), newCommuGns-oldCommuGns) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + + }) +} + +func testCreatePoolBazQux3000ExternalOnly(t *testing.T) { + t.Run("create pool baz:qux:0.3%", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + pl.CreatePool(bazPath, quxPath, 3000, common.TickMathGetSqrtRatioAtTick(0).ToString()) // current tick 0 + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testCreateExternalIncentiveBaz(t *testing.T) { + t.Run("create external incentive baz for baz:qux:0.3% pool", func(t *testing.T) { + std.TestSetRealm(adminRealm) + baz.Transfer(common.AddrToUser(externalCreator), 20000000) // for external incentive + gns.Transfer(common.AddrToUser(externalCreator), depositGnsAmount) // for deposit + + std.TestSetRealm(std.NewUserRealm(externalCreator)) + baz.Approve(a2u(consts.STAKER_ADDR), 20000000) + gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount) + + CreateExternalIncentive( + "gno.land/r/onbloc/baz:gno.land/r/onbloc/qux:3000", + bazPath, + 20000000, + 1238457600, + 1238457600+TIMESTAMP_90DAYS, + ) + // startHeight 1944978 + // endHeight 5832978 + + // 5832978 - 1944978 = 3888000 ( incentive block duration ) + // 20000000 / 3888000 = 5.1440329218 ( amount of baz reward per block ) + + checkGnsBalance(t, depositGnsAmount) + std.TestSkipHeights(1) + }) +} + +func testMintPos08InRange(t *testing.T) { + t.Run("mint position 08 in range", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + baz.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + qux.Approve(common.AddrToUser(consts.POOL_ADDR), consts.UINT64_MAX) + + std.TestSkipHeights(1) + pn.Mint( + bazPath, + quxPath, + fee3000, + int32(-60), + int32(60), + "1000", + "1000", + "0", + "0", + max_timeout, + adminAddr, + adminAddr, + ) + + checkGnsBalance(t, depositGnsAmount) + std.TestSkipHeights(1) + }) +} + +func testStakeToken08(t *testing.T) { + t.Run("stake token 08", func(t *testing.T) { + std.TestSetRealm(adminRealm) + gnft.Approve(stakerAddr, common.TokenIdFrom(8)) + StakeToken(8) + + checkGnsBalance(t, depositGnsAmount) + std.TestSkipHeights(1) + }) +} + +func testCollectReward08(t *testing.T) { + t.Run("collect reward for 08 position, baz incentive not yet started", func(t *testing.T) { + std.TestSkipHeights(9) + std.TestSetRealm(adminRealm) + + oldBaz := baz.BalanceOf(common.AddrToUser(adminAddr)) + CollectReward(8, false) + + newBaz := baz.BalanceOf(common.AddrToUser(adminAddr)) + uassert.Equal(t, uint64(0), newBaz-oldBaz) + // 익스터널 인센티브가 시작되지 않았기에 리워드가 0 + + checkGnsBalance(t, depositGnsAmount) + std.TestSkipHeights(1) + }) +} + +func testStartExternalIncentive(t *testing.T) { + t.Run("external incentive start block skip", func(t *testing.T) { + std.TestSkipHeights(1944978 - std.GetHeight()) + std.TestSkipHeights(10) + }) +} + +func testCollectReward08AfterStart(t *testing.T) { + t.Run("collect reward for 08 position", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldBaz := baz.BalanceOf(common.AddrToUser(adminAddr)) + CollectReward(8, false) + newBaz := baz.BalanceOf(common.AddrToUser(adminAddr)) + + uassert.True(t, isNear(t, uint64(15), newBaz-oldBaz)) + // 5 * 10 * 30% = 15 // user receives + // > bar reward amount per block is 5.1440329218 == 5 + // > 10 block skipped after incentive start + // > position08 staked duration is 812 ( which is 30% warmUp period ) + + checkGnsBalance(t, depositGnsAmount) + std.TestSkipHeights(1) + }) +} + +func testEndExternalIncentiveBaz(t *testing.T) { + t.Run("end external incentive bar", func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(externalCreator)) + + // 종료 전 익스터널 만든 주소의 토큰 잔액 저장 + // - gns: 익스터널 생성 시 예치한 gns 환불 + // - baz: 익스터널로 제공한 수량 중 페널티 환불 + oldGns := gns.BalanceOf(common.AddrToUser(externalCreator)) + oldBaz := baz.BalanceOf(common.AddrToUser(externalCreator)) + + // 해당 포지션에 유동성 제공한 주소의 토큰 잔액 저장 + // - baz: 리워드 수령되는지 확인 + lpOldBaz := baz.BalanceOf(common.AddrToUser(adminAddr)) + + // 종료되도록 블록 증가 + std.TestSkipHeights(5832978 - std.GetHeight()) + std.TestSkipHeights(1) + + EndExternalIncentive( + externalCreator, + "gno.land/r/onbloc/baz:gno.land/r/onbloc/qux:3000", + bazPath, + 1238457600, + 1238457600+TIMESTAMP_90DAYS, + 1944978, + ) + std.TestSkipHeights(1) + + newGns := gns.BalanceOf(common.AddrToUser(externalCreator)) + newBaz := baz.BalanceOf(common.AddrToUser(externalCreator)) + + // 종료 후 확인해야 할꺼 + + // 1. 익스터널 생성 시 디파짓으로 넣은 gns 수량 환불 + refundGns := newGns - oldGns + uassert.Equal(t, depositGnsAmount, refundGns, "gns refund amount mismatch") + + // 2. 웜업에 따른 페널티 수량 환불 + // > 8번 포지션에서 50개의 익스터널 리워드 발생, 30%(15개)는 리워드로, 나머지 70%(35개)가 페널티로 + refundBaz := newBaz - oldBaz + uassert.Equal(t, uint64(35), refundBaz) + + // 3. 아직 리워드 수령하지 않은 유저는 계속 리워드 수령 가능 + std.TestSetRealm(adminRealm) + CollectReward(8, false) + std.TestSkipHeights(1) + + lpNewBaz := baz.BalanceOf(common.AddrToUser(adminAddr)) + uassert.True(t, lpNewBaz-lpOldBaz > 0) + + // 4. 익스터널 종료 이후 블록이 아무리 많이 생겨도 리워드가 추가적으로 발생하면 안 됨 + std.TestSkipHeights(123) + CollectReward(8, false) + lpNewBaz2 := baz.BalanceOf(common.AddrToUser(adminAddr)) + uassert.Equal(t, lpNewBaz, lpNewBaz2) + + checkGnsBalance(t, 0) + }) +} + +func testReStakeTokenPos01(t *testing.T) { + t.Run("re-stake token 01", func(t *testing.T) { + std.TestSetRealm(adminRealm) + gnft.Approve(stakerAddr, common.TokenIdFrom(1)) + StakeToken(1) + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) + + t.Run("collect reward for position 01", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldGns := gns.BalanceOf(common.AddrToUser(adminAddr)) + CollectReward(1, false) + newGns := gns.BalanceOf(common.AddrToUser(adminAddr)) + uassert.True(t, isNear(t, uint64(1605308), newGns-oldGns)) + // 1번 포지션 다시 스테이킹 하고 1 블록 지남 (웜업 30% 부터 계산) + // 현재 gnot:gns:0.3% 풀은 티어 1이며, 블록 당 5351027 개의 리워드가 할당 됨 + // 5351027 * 30% = 1605308.1 + + checkGnsBalance(t, 0) + std.TestSkipHeights(1) + }) +} + +func testChangeAvgBlockTimeTo4000(t *testing.T) { + // 원래 블록 시간 2초였으나 4초로 증가 + // 1 블록 당 민팅되는 GNS 수량 2배로 증가됨 + t.Run("change avg block time to 4000", func(t *testing.T) { + std.TestSetRealm(adminRealm) + gns.SetAvgBlockTimeInMsByAdmin(4000) + std.TestSkipHeights(1) + + // 리워드 초기화 + CollectReward(1, false) + }) + + t.Run("collect reward position 01", func(t *testing.T) { + std.TestSetRealm(adminRealm) + std.TestSkipHeights(1) + + oldGns := gns.BalanceOf(common.AddrToUser(adminAddr)) + CollectReward(1, false) // 1 블록 증가하고 리워드 확인해보면 얘한테 떨어지는 수량 거의 2배로 되야 함 + newGns := gns.BalanceOf(common.AddrToUser(adminAddr)) + uassert.True(t, isNear(t, uint64(3210616), newGns-oldGns)) + + // 블록 시간 2초일 때 블록 당 리워드 = 10702054 + // 블록 시간 4초일 때 블록 당 리워드 = 21404108 + // 티어 1, 2, 3 모두 풀이 있어서, 티어 1 풀은 50%인 10702054 할당 + // 그 중 warmUp 30% = 10702054 * 30% = 3210616.2 + std.TestSkipHeights(1) + + // 원복 + gns.SetAvgBlockTimeInMsByAdmin(2000) + }) +} + +func testChangeDistributionPctByAdmin(t *testing.T) { + t.Run("change staker's emission distribution pct to 50%", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + // 1번 포지션 리워드 수령 + CollectReward(1, false) + + en.ChangeDistributionPctByAdmin( + 1, 5000, // 스테이커 + 2, 5000, // 데브옵스 + 3, 0, // 커뮤니티풀 + 4, 0, // xGNS + ) + std.TestSkipHeights(1) // 1 블록 증가 했으나, 스테이커의 에미션은 비율 5% + + // 리워드 초기화 + CollectReward(1, false) + }) + + t.Run("collect reward position 01", func(t *testing.T) { + std.TestSetRealm(adminRealm) + std.TestSkipHeights(1) + + oldGns := gns.BalanceOf(common.AddrToUser(adminAddr)) + CollectReward(1, false) + newGns := gns.BalanceOf(common.AddrToUser(adminAddr)) + uassert.True(t, isNear(t, uint64(1070205), newGns-oldGns)) + // uassert.True(t, isNear(t, uint64(0), newGns-oldGns)) + // 1 블록 당 발행되는 에미션 수량 14269406 + // (원래) 75% 할당 = 10702054 + // (변경) 50% 할당 = 7134703 + + // 티어 1 풀은 50% 만큼 분배 = 7134703 * 50% = 3567351.5 + // 그 중 warmUp 30% = 3567351.5 * 30% = 1070205.45 + + // 원복 + en.ChangeDistributionPctByAdmin( + 1, 7500, // 스테이커 + 2, 2500, // 데브옵스 + 3, 0, // 커뮤니티풀 + 4, 0, // xGNS + ) + std.TestSkipHeights(1) + }) +} + +func checkGnsBalance(t *testing.T, adjustAmount uint64) { + t.Helper() + + currentHeight := std.GetHeight() + blockPassedFromGenesis := uint64(currentHeight - genesisBlockHeight) + + oneBlockEmissionAmount := uint64(14269406) + + oneBlockStakerAmount := uint64(10702054) // 14269406*0.75 + initialStakerAmount := uint64(32106163) // 14269406*0.75*3 + + oneBlockDevOpsAmount := uint64(3567351) // 14269406*0.25 + initialDevOpsAmount := uint64(10702054) // 14269406*0.25*3 + + externalIncentiveDeposit := uint64(20000000) + depositGnsAmount // externalReward + depositGns + emissionLeft := blockPassedFromGenesis / 2 + gnsPer2Block := uint64(1) + + uassert.Equal(t, uint64(100000000000000)+(blockPassedFromGenesis*oneBlockEmissionAmount), gns.TotalSupply()) + + uassert.True(t, isNear(t, uint64(0), gnsBalance(consts.EMISSION_ADDR))) + + expectStakerBalance := uint64(0) + initialStakerAmount + ((blockPassedFromGenesis - 3) * oneBlockStakerAmount) + externalIncentiveDeposit + adjustAmount + uassert.True(t, isNear(t, expectStakerBalance+(uint64((currentHeight-126))*gnsPer2Block), gnsBalance(consts.STAKER_ADDR)+totalEmissionSent)) + + expectedDevOpsBalance := uint64(0) + initialDevOpsAmount + ((blockPassedFromGenesis - 3) * oneBlockDevOpsAmount) + uassert.True(t, isNear(t, expectedDevOpsBalance, gnsBalance(consts.DEV_OPS))) +} + +// check whether the actual value is within 99.999% of the expected value +func isNear(t *testing.T, expected, actual uint64) bool { + t.Helper() + + // 99.999% + lower := expected * 99999 / 100000 + upper := expected * 100001 / 100000 + + if lower <= actual && actual <= upper { + return true + } + + lower = expected - 1 + if lower > expected { + lower = 0 + } + upper = expected + 1 + + if lower <= actual && actual <= upper { + return true + } + + return false +} diff --git a/staker/__TEST_more_01_single_position_for_each_warmup_tier_total_4_position_internal_only_test.gnoA b/staker/__TEST_more_01_single_position_for_each_warmup_tier_total_4_position_internal_only_test.gnoA index 62bda79cc..eb9fbe3f6 100644 --- a/staker/__TEST_more_01_single_position_for_each_warmup_tier_total_4_position_internal_only_test.gnoA +++ b/staker/__TEST_more_01_single_position_for_each_warmup_tier_total_4_position_internal_only_test.gnoA @@ -4,6 +4,8 @@ import ( "std" "testing" + "gno.land/p/demo/uassert" + "gno.land/r/gnoswap/v1/common" "gno.land/r/gnoswap/v1/consts" @@ -29,13 +31,10 @@ func TestMore01(t *testing.T) { testMintBarBaz100Pos02(t) testMintBarBaz100Pos03(t) testMintBarBaz100Pos04(t) - testPrintWarmup(t) - testStakeTokenPos01(t) // FIXME #L249 - // testStakeTokenPos02(t) - // testStakeTokenPos03(t) - // testStakeTokenPos04(t) - // testCollecRewardAll(t) - // testSkip1BlockAndCollectReward(t) + testStakeTokenPos01(t) + testStakeTokenPos02To04(t) + testCollecRewardAll(t) + testSkip1BlockAndCollectReward(t) } func testInit(t *testing.T) { @@ -61,11 +60,12 @@ func testInit(t *testing.T) { func testCreatePool(t *testing.T) { t.Run("create pool", func(t *testing.T) { std.TestSetRealm(adminRealm) + // set pool creation fee to 0 pl.SetPoolCreationFeeByAdmin(0) pl.CreatePool(barPath, bazPath, 100, "79228162514264337593543950337") - addPoolTier(t, poolPath, 1) + SetPoolTierByAdmin(poolPath, 1) std.TestSkipHeights(1) }) } @@ -170,15 +170,6 @@ func testMintBarBaz100Pos04(t *testing.T) { }) } -func testPrintWarmup(t *testing.T) { - t.Run("print warmup", func(t *testing.T) { - println("30", warmupTemplate[0].BlockDuration) - println("50", warmupTemplate[1].BlockDuration) - println("70", warmupTemplate[2].BlockDuration) - println("100", warmupTemplate[3].BlockDuration) - }) -} - func testStakeTokenPos01(t *testing.T) { t.Run("stake position 01", func(t *testing.T) { std.TestSetRealm(adminRealm) @@ -193,21 +184,21 @@ func testStakeTokenPos01(t *testing.T) { userOldGns := gns.BalanceOf(admin) communityOldGns := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) - println("userOldGns", userOldGns) // 100000000000000 - println("communityOldGns", communityOldGns) // 0 + uassert.Equal(t, uint64(100000000000000), userOldGns) + uassert.Equal(t, uint64(0), communityOldGns) CollectReward(1, false) userNewGns := gns.BalanceOf(admin) communityNewGns := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) - println("userNewGns", userNewGns) // 100000003210616 + uassert.True(t, isInErrorRange(uint64(3210616), userNewGns-userOldGns)) // increased 3210616 // staker receives 10702054 gns from emission // position 01 is in 30% warm up period // 30% is reward + 70% is penalty // 10702054 * 30% = 3210616 - println("communityNewGns", communityNewGns) // 50299653 + uassert.True(t, isInErrorRange(uint64(50299653), communityNewGns-communityOldGns)) // increased 50299653 // staker did received 4 block of gns emission when there is no staked position // 10702054 * 4 = 42808216 @@ -228,42 +219,149 @@ func testStakeTokenPos01(t *testing.T) { std.TestSkipHeights(1) // 1 block for 50% warm up userOldGns := gns.BalanceOf(admin) communityOldGns := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) - println("userOldGns", userOldGns) // 100693509152279 - println("communityOldGns", communityOldGns) // 1618220128150 CollectReward(1, false) userNewGns := gns.BalanceOf(admin) communityNewGns := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) - println("userNewGns", userNewGns) // 100693514503305 - // increased 5351026 (100693514503305 - 100693509152279) + + uassert.True(t, isInErrorRange(uint64(5351026), userNewGns-userOldGns)) + // increased 5351026 // staker receives 10702054 gns from emission // position 01 is in 50% warm up period - // 50% is reward + 50% is penalty - println("communityNewGns", communityNewGns) // 1618225479177 - // increased 5351027 (1618225479177 - 1618220128150) + // 50% is reward + 50% is penalty + uassert.True(t, isInErrorRange(uint64(5351026), communityNewGns-communityOldGns)) }) t.Run("make it warm up 70%", func(t *testing.T) { std.TestSetRealm(adminRealm) - std.TestSkipHeights(2) + std.TestSkipHeights(432001) + CollectReward(1, false) + + std.TestSkipHeights(1) // 1 block for 70% warm up + userOldGns := gns.BalanceOf(admin) + communityOldGns := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) + + CollectReward(1, false) + + userNewGns := gns.BalanceOf(admin) + communityNewGns := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) + + uassert.True(t, isInErrorRange(uint64(7491437), userNewGns-userOldGns)) + // increased 7491437 + // staker receives 10702054 gns from emission + // position 01 is in 70% warm up period + + uassert.True(t, isInErrorRange(uint64(3210616), communityNewGns-communityOldGns)) + // 30% is penalty + // 10702054 * 30% = 3210616 + }) + + t.Run("make it warm up 100%", func(t *testing.T) { + std.TestSetRealm(adminRealm) + std.TestSkipHeights(1728000) + CollectReward(1, false) + + std.TestSkipHeights(1) // 1 block for 100% warm up + userOldGns := gns.BalanceOf(admin) + communityOldGns := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) + CollectReward(1, false) - // FIXME - // 251 라인에서 1블록 스킵하면 CollectReward 정상 동작, 그러나 2 블록 이상 스킵하면 insufficientBalance 패닉 발생 - // 터미널에 출력되는 로그 보면 unClaimableReward가 10702054 로 계산되고 있음 (이게 원인인듯) - // > 스테이킹 포지션 변동 없이 그냥 블록만 증가시켰는데 갑자기 unclaimable이 저만큼 잡혀서 부족한걸로 추정 됨 - // >>>>>>>>>>>> internalUnClaimable : 10702054 , externalUnClaimable : map{} - // unClaimableInternal : 1070205 + + userNewGns := gns.BalanceOf(admin) + communityNewGns := gns.BalanceOf(common.AddrToUser(consts.COMMUNITY_POOL_ADDR)) + + uassert.True(t, isInErrorRange(uint64(10702054), userNewGns-userOldGns)) + // increased 10702054 + // staker receives 10702054 gns from emission + + uassert.Equal(t, uint64(0), communityNewGns-communityOldGns) + // no penalty }) } -func testStakeTokenPos02(t *testing.T) { +func testStakeTokenPos02To04(t *testing.T) { t.Run("stake position 02", func(t *testing.T) { std.TestSetRealm(adminRealm) gnft.Approve(consts.STAKER_ADDR, tid(2)) StakeToken(2) + }) + + t.Run("stake position 03", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + gnft.Approve(consts.STAKER_ADDR, tid(3)) + StakeToken(3) + std.TestSkipHeights(1) + }) + + t.Run("stake position 04", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + gnft.Approve(consts.STAKER_ADDR, tid(4)) + StakeToken(4) + std.TestSkipHeights(1) + }) +} + +func testCollecRewardAll(t *testing.T) { + t.Run("collect reward for all", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + CollectReward(1, false) + CollectReward(2, false) + CollectReward(3, false) + CollectReward(4, false) + }) +} + +func testSkip1BlockAndCollectReward(t *testing.T) { + t.Run("skip 1 block", func(t *testing.T) { std.TestSkipHeights(1) }) + + t.Run("collect reward for pos01", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(1, false) + afterGns := gns.BalanceOf(admin) + + uassert.True(t, isInErrorRange(uint64(2675513), afterGns-beforeGns)) + // 1 block of emission reward for staker = 10702054 + // 4 in-range position staked with same liquidity + // each position receives 10702054 * 25% = 2675513.5 + }) + + t.Run("collect reward for pos02", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(2, false) + afterGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(uint64(2675513*30/100), afterGns-beforeGns)) + // warm up 30% + }) + + t.Run("collect reward for pos03", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(3, false) + afterGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(uint64(2675513*30/100), afterGns-beforeGns)) + // warm up 30% + }) + + t.Run("collect reward for pos04", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(4, false) + afterGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(uint64(2675513*30/100), afterGns-beforeGns)) + // warm up 30% + }) } diff --git a/staker/__TEST_more_02_single_position_for_each_warmup_tier_total_4_position_two_external_test.gnoA b/staker/__TEST_more_02_single_position_for_each_warmup_tier_total_4_position_two_external_test.gnoA index add43aae4..2cc3be613 100644 --- a/staker/__TEST_more_02_single_position_for_each_warmup_tier_total_4_position_two_external_test.gnoA +++ b/staker/__TEST_more_02_single_position_for_each_warmup_tier_total_4_position_two_external_test.gnoA @@ -4,6 +4,8 @@ import ( "std" "testing" + "gno.land/p/demo/uassert" + "gno.land/r/gnoswap/v1/consts" en "gno.land/r/gnoswap/v1/emission" @@ -28,7 +30,6 @@ func TestMore02(t *testing.T) { testMintBarBaz100Pos02(t) testMintBarBaz100Pos03(t) testMintBarBaz100Pos04(t) - testPrintWarmup(t) testCreateBarExternal(t) testCreateBazExternal(t) testStakeTokenPos01ToWarmUp100(t) @@ -165,15 +166,6 @@ func testMintBarBaz100Pos04(t *testing.T) { }) } -func testPrintWarmup(t *testing.T) { - t.Run("print warmup", func(t *testing.T) { - println("30", warmupTemplate[0].BlockDuration) - println("50", warmupTemplate[1].BlockDuration) - println("70", warmupTemplate[2].BlockDuration) - println("100", warmupTemplate[3].BlockDuration) - }) -} - func testCreateBarExternal(t *testing.T) { t.Run("create external incentive bar 365 days", func(t *testing.T) { std.TestSetRealm(adminRealm) @@ -237,13 +229,13 @@ func testStakeTokenPos01ToWarmUp100(t *testing.T) { userNewBar := bar.BalanceOf(admin) userNewBaz := baz.BalanceOf(admin) - println("bar reward", userNewBar-userOldBar) + uassert.True(t, isInErrorRange(uint64(6944), userNewBar-userOldBar)) // increased 6944 // position 01 is in 30% warm up period // 30% is reward // 23148(rewardPerBlock) * 30% = 6944.4 - println("baz reward", userNewBaz-userOldBaz) + uassert.True(t, isInErrorRange(uint64(7), userNewBaz-userOldBaz)) // increased 6 // position 01 is in 30% warm up period // 30% is reward @@ -266,13 +258,13 @@ func testStakeTokenPos01ToWarmUp100(t *testing.T) { userNewBar := bar.BalanceOf(admin) userNewBaz := baz.BalanceOf(admin) - println("bar reward", userNewBar-userOldBar) + uassert.True(t, isInErrorRange(uint64(11574), userNewBar-userOldBar)) // increased 11573 // position 01 is in 50% warm up period // 50% is reward // 23148(rewardPerBlock) * 50% = 11574 - println("baz reward", userNewBaz-userOldBaz) + uassert.True(t, isInErrorRange(uint64(12), userNewBaz-userOldBaz)) // increased 11 // position 01 is in 50% warm up period // 50% is reward @@ -296,13 +288,13 @@ func testStakeTokenPos01ToWarmUp100(t *testing.T) { userNewBar := bar.BalanceOf(admin) userNewBaz := baz.BalanceOf(admin) - println("bar reward", userNewBar-userOldBar) + uassert.True(t, isInErrorRange(uint64(16204), userNewBar-userOldBar)) // increased 16203 // position 01 is in 70% warm up period // 70% is reward // 23148(rewardPerBlock) * 70% = 16203.6 - println("baz reward", userNewBaz-userOldBaz) + uassert.True(t, isInErrorRange(uint64(16), userNewBaz-userOldBaz)) // increased 16 // position 01 is in 70% warm up period // 70% is reward @@ -325,12 +317,12 @@ func testStakeTokenPos01ToWarmUp100(t *testing.T) { userNewBar := bar.BalanceOf(admin) userNewBaz := baz.BalanceOf(admin) - println("bar reward", userNewBar-userOldBar) + uassert.True(t, isInErrorRange(uint64(23148), userNewBar-userOldBar)) // increased 23147 // position 01 is in 100% warm up period // 100% is reward - println("baz reward", userNewBaz-userOldBaz) + uassert.True(t, isInErrorRange(uint64(23), userNewBaz-userOldBaz)) // increased 22 // position 01 is in 100% warm up period // 100% is reward @@ -396,12 +388,12 @@ func testSkip1BlockAndCollectReward(t *testing.T) { newBar := bar.BalanceOf(admin) newBaz := baz.BalanceOf(admin) - println("bar reward", newBar-oldBar) + uassert.True(t, isInErrorRange(uint64(5787), newBar-oldBar)) // 5786 // position 01 is in 100% warm up period // 100% is reward (5787 * 100%) - println("baz reward", newBaz-oldBaz) + uassert.True(t, isInErrorRange(uint64(6), newBaz-oldBaz)) // 5 // position 01 is in 100% warm up period // 100% is reward (5.75 * 100%) @@ -417,12 +409,12 @@ func testSkip1BlockAndCollectReward(t *testing.T) { newBar := bar.BalanceOf(admin) newBaz := baz.BalanceOf(admin) - println("bar reward", newBar-oldBar) + uassert.True(t, isInErrorRange(uint64(4050), newBar-oldBar)) // 4050 // position 02 is in 70% warm up period // 70% is reward (5787 * 70%) - println("baz reward", newBaz-oldBaz) + uassert.True(t, isInErrorRange(uint64(4), newBaz-oldBaz)) // 4 // position 02 is in 70% warm up period // 70% is reward (5.75 * 70%) @@ -438,12 +430,12 @@ func testSkip1BlockAndCollectReward(t *testing.T) { newBar := bar.BalanceOf(admin) newBaz := baz.BalanceOf(admin) - println("bar reward", newBar-oldBar) + uassert.True(t, isInErrorRange(uint64(2893), newBar-oldBar)) // 2893 // position 03 is in 50% warm up period // 50% is reward (5787 * 50%) - println("baz reward", newBaz-oldBaz) + uassert.True(t, isInErrorRange(uint64(3), newBaz-oldBaz)) // 2 // position 03 is in 50% warm up period // 50% is reward (5.75 * 50%) @@ -459,15 +451,14 @@ func testSkip1BlockAndCollectReward(t *testing.T) { newBar := bar.BalanceOf(admin) newBaz := baz.BalanceOf(admin) - println("bar reward", newBar-oldBar) + uassert.True(t, isInErrorRange(uint64(1736), newBar-oldBar)) // 1736 // position 04 is in 30% warm up period // 30% is reward (5787 * 30%) - println("baz reward", newBaz-oldBaz) + uassert.True(t, isInErrorRange(uint64(2), newBaz-oldBaz)) // 1 // position 04 is in 30% warm up period // 30% is reward (5.75 * 30%) }) - } diff --git a/staker/__TEST_more_04_positions_with_different_liquidity_and_in_range_chane_by_swap_test.gnoA b/staker/__TEST_more_04_positions_with_different_liquidity_and_in_range_chane_by_swap_test.gnoA index df370e21e..25adfdf90 100644 --- a/staker/__TEST_more_04_positions_with_different_liquidity_and_in_range_chane_by_swap_test.gnoA +++ b/staker/__TEST_more_04_positions_with_different_liquidity_and_in_range_chane_by_swap_test.gnoA @@ -4,6 +4,8 @@ import ( "std" "testing" + "gno.land/p/demo/uassert" + "gno.land/r/gnoswap/v1/consts" en "gno.land/r/gnoswap/v1/emission" @@ -28,9 +30,9 @@ func TestMore04(t *testing.T) { testMintBarBaz100Pos03(t) testMintBarBaz100Pos04(t) testMintBarBaz100Pos05(t) - testPrintWarmup(t) testStakeTokenPos01(t) testStakeTokenPos02ToWarmUp70_Pos03ToWarmUp50_Pos04ToWarmUp30_Pos05(t) + testCollectRewardFor01Block(t) } func testInit(t *testing.T) { @@ -69,7 +71,7 @@ func testCreatePool(t *testing.T) { bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) baz.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) pl.CreatePool(barPath, bazPath, 100, "79228162514264337593543950337") // price ratio 1:1 - addPoolTier(t, poolPath, 1) + SetPoolTierByAdmin(poolPath, 1) std.TestSkipHeights(1) }) } @@ -199,15 +201,6 @@ func testMintBarBaz100Pos05(t *testing.T) { }) } -func testPrintWarmup(t *testing.T) { - t.Run("print warmup", func(t *testing.T) { - println("30", warmupTemplate[0].BlockDuration) - println("50", warmupTemplate[1].BlockDuration) - println("70", warmupTemplate[2].BlockDuration) - println("100", warmupTemplate[3].BlockDuration) - }) -} - func testStakeTokenPos01(t *testing.T) { t.Run("stake position 01", func(t *testing.T) { std.TestSetRealm(adminRealm) @@ -258,11 +251,115 @@ func testStakeTokenPos02ToWarmUp70_Pos03ToWarmUp50_Pos04ToWarmUp30_Pos05(t *test CollectReward(1, false) // toUser 13458934078339 CollectReward(2, false) // toUser 2139767397225 + CollectReward(3, false) // toUser 331211442847 CollectReward(4, false) // toUser 0 (out of range) CollectReward(5, false) // toUser 0 (out of range) + }) +} + +func testCollectRewardFor01Block(t *testing.T) { + t.Run("skip 01 block", func(t *testing.T) { + std.TestSkipHeights(1) + }) + + /* + - staker gets 10702054 for 1 block emission reward + - total 5 positions are staked + + position-01 + > liquidity: 3000 + > range: in + > warmUp: 100% + + position-02 + > liquidity: 20484 + > range: in + > warmUp: 70% + + position-03 + > liquidity: 21469 + > range: in + > warmUp: 50% + + position-04 + > liquidity: 2400 + > range: out + > warmUp: 30% + + position-05 + > liquidity: 1844236186985805146768743 + > range: out + > warmUp: 30% + + - total inRange liquidity: 3000 + 20484 + 21469 = 44953 + - liquidity ratio + > position-01: 3000 / 44953 = 6.6736369097% + > position-02: 20484 / 44953 = 45.5675928192% + > position-03: 21469 / 44953 = 47.7587702712% + */ + + t.Run("collect reward for position 1", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(1, false) + afterGns := gns.BalanceOf(admin) + + uassert.True(t, isInErrorRange(uint64(714216), afterGns-beforeGns)) + // reward per block: 10702054 + // position-01 liquditiy ratio: 6.6736369097% + // position-01 reward: 10702054 * 6.6736369097% = 714216.2258400252 + // warmUp: 100% + // position-01 reward: 714216.2258400252 * 100% = 714216.2258400252 + }) + + t.Run("collect reward for position 2", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(2, false) + afterGns := gns.BalanceOf(admin) + + uassert.True(t, isInErrorRange(uint64(3413667), afterGns-beforeGns)) + // reward per block: 10702054 + // position-02 liquidity ratio: 45.5675928192% + // position-02 reward: 10702054 * 45.5675928192% = 4876668.3900109064 + // warmUp: 70% + // position-02 reward: 4876668.3900109064 * 70% = 3413667.8730076345 + }) + + t.Run("collect reward for position 3", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(3, false) + afterGns := gns.BalanceOf(admin) + + uassert.True(t, isInErrorRange(uint64(2555584), afterGns-beforeGns)) + // reward per block: 10702054 + // position-03 liquidity ratio: 47.7587702712% + // position-03 reward: 10702054 * 47.7587702712% = 5111169.3841597704 + // warmUp: 50% + // position-03 reward: 5111169.3841597704 * 50% = 2555584.6920798852 + }) + + t.Run("collect reward for position 4", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(4, false) + afterGns := gns.BalanceOf(admin) + uassert.Equal(t, uint64(0), afterGns-beforeGns) + // out range + }) + + t.Run("collect reward for position 5", func(t *testing.T) { + std.TestSetRealm(adminRealm) - // CollectReward(3, false) // toUser 331211442847 // FIXME: insufficient balance - // 테스트 실행하면 3번 포지션에 대해 유저한테 리워드 주는거까지는 통과 - // 그러나 페널티를 커뮤니티 풀에 줄 때 스테이커의 GNS 잔액보다 더 높게 계산되어 있는 페널티를 전송할 때 잔액 부족 발생 중 + beforeGns := gns.BalanceOf(admin) + CollectReward(5, false) + afterGns := gns.BalanceOf(admin) + uassert.Equal(t, uint64(0), afterGns-beforeGns) + // out range }) } diff --git a/staker/__TEST_short_wramup_internal_gnot_gns_3000_test.gnoA b/staker/__TEST_short_warmup_internal_gnot_gns_3000_test.gnoA similarity index 74% rename from staker/__TEST_short_wramup_internal_gnot_gns_3000_test.gnoA rename to staker/__TEST_short_warmup_internal_gnot_gns_3000_test.gnoA index c0e5ccd8a..e1565abf7 100644 --- a/staker/__TEST_short_wramup_internal_gnot_gns_3000_test.gnoA +++ b/staker/__TEST_short_warmup_internal_gnot_gns_3000_test.gnoA @@ -122,52 +122,6 @@ func testStakeToken01(t *testing.T) { func testCollectReward01(t *testing.T) { t.Run("collect reward", func(t *testing.T) { std.TestSetRealm(adminRealm) - // println(getPrintInfo(t)) - /* - { - "height": "127", - "time": "1234567898", - "gns": { - "staker": "42808219", - "devOps": "14269404", - "communityPool": "0", - "govStaker": "0", - "protocolFee": "0", - "GnoswapAdmin": "99999999999950" - }, - "pool": [ - { - "poolPath": "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000", - "tier": "1", - "numPoolSameTier": "1", - "position": [ - { - "lpTokenId": "1", - "stakedHeight": "126", - "stakedTimestamp": "1234567896", - "stakedDuration": "1", - "fullAmount": "10702053", - "ratio": "30", - "warmUpAmount": "3210616", - "full30": "10702053", - "give30": "3210616", - "penalty30": "7491437", - "full50": "0", - "give50": "0", - "penalty50": "0", - "full70": "0", - "give70": "0", - "penalty70": "0", - "full100": "0", - "give100": "0", - "penalty100": "0" - } - ] - } - ] - } - */ - // 3 block of staker's emission reward == 32106164 // should be sent to community pool // - it is duration when no position is staked @@ -178,7 +132,8 @@ func testCollectReward01(t *testing.T) { gnsBefore := gns.BalanceOf(admin) CollectReward(1, false) gnsAfter := gns.BalanceOf(admin) - uassert.Equal(t, gnsAfter-gnsBefore, uint64(3210616)) + + uassert.True(t, isInErrorRange(uint64(3210616), gnsAfter-gnsBefore)) std.TestSkipHeights(1) }) diff --git a/staker/__TEST_staker_NFT_transfer_01_test.gnoA b/staker/__TEST_staker_NFT_transfer_01_test.gnoA index 953f0ce0d..085ae917c 100644 --- a/staker/__TEST_staker_NFT_transfer_01_test.gnoA +++ b/staker/__TEST_staker_NFT_transfer_01_test.gnoA @@ -83,7 +83,7 @@ func testPoolCreatePool(t *testing.T) { std.TestSetRealm(adminRealm) pl.CreatePool(barPath, quxPath, 500, "130621891405341611593710811006") // tick 10_000 ≈ x2.7 // tier 1 - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500`, 1) + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500`, 1) std.TestSkipHeights(1) }) } diff --git a/staker/__TEST_staker_NFT_transfer_02_test.gnoA b/staker/__TEST_staker_NFT_transfer_02_test.gnoA index 9c6f7d0bc..3127ac5c2 100644 --- a/staker/__TEST_staker_NFT_transfer_02_test.gnoA +++ b/staker/__TEST_staker_NFT_transfer_02_test.gnoA @@ -4,10 +4,11 @@ package staker import ( - "gno.land/p/demo/uassert" "std" "testing" + "gno.land/p/demo/uassert" + "gno.land/r/gnoswap/v1/consts" pl "gno.land/r/gnoswap/v1/pool" @@ -76,7 +77,7 @@ func testPoolCreatePool(t *testing.T) { std.TestSetRealm(adminRealm) pl.CreatePool(barPath, quxPath, 500, "130621891405341611593710811006") // tick 10_000 ≈ x2.7 // tier 1 - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500`, 1) + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500`, 1) std.TestSkipHeights(1) }) } diff --git a/staker/__TEST_staker_NFT_transfer_03_test.gnoA b/staker/__TEST_staker_NFT_transfer_03_test.gnoA index 1a19716c6..a3b5bbcb3 100644 --- a/staker/__TEST_staker_NFT_transfer_03_test.gnoA +++ b/staker/__TEST_staker_NFT_transfer_03_test.gnoA @@ -7,11 +7,12 @@ package staker import ( - "gno.land/p/demo/testutils" - "gno.land/p/demo/uassert" "std" "testing" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/r/gnoswap/v1/consts" pl "gno.land/r/gnoswap/v1/pool" @@ -88,7 +89,7 @@ func testPoolCreatePool(t *testing.T) { std.TestSetRealm(adminRealm) pl.CreatePool(barPath, quxPath, 500, "130621891405341611593710811006") // tick 10_000 ≈ x2.7 // tier 1 - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500`, 1) + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500`, 1) std.TestSkipHeights(1) }) } @@ -174,7 +175,7 @@ func testCollectReward01(t *testing.T) { t.Run("admin can not collect reward(not a owner)", func(t *testing.T) { uassert.PanicsWithMessage( t, - `[GNOSWAP-STAKER-001] caller has no permission: caller is not owner of tokenId(1)`, + `[GNOSWAP-STAKER-001] caller has no permission || caller is not owner of tokenId(1)`, func() { std.TestSetRealm(adminRealm) CollectReward(1, false) diff --git a/staker/__TEST_staker_emission_and_external_incentive_test.gnoA b/staker/__TEST_staker_emission_and_external_incentive_test.gnoA deleted file mode 100644 index a5ae08380..000000000 --- a/staker/__TEST_staker_emission_and_external_incentive_test.gnoA +++ /dev/null @@ -1,617 +0,0 @@ -package staker - -import ( - "std" - "strconv" - "testing" - - "gno.land/p/demo/uassert" - "gno.land/r/demo/users" - - en "gno.land/r/gnoswap/v1/emission" - pl "gno.land/r/gnoswap/v1/pool" - pn "gno.land/r/gnoswap/v1/position" - - "gno.land/r/demo/wugnot" - "gno.land/r/gnoswap/v1/gns" - "gno.land/r/onbloc/bar" - "gno.land/r/onbloc/obl" - "gno.land/r/onbloc/qux" - - "gno.land/r/gnoswap/v1/consts" - "gno.land/r/gnoswap/v1/gnft" -) - -func TestStakerWithEmissionAmount(t *testing.T) { - testInit(t) - testCreatPool_WUGNOT_GNS(t) - //testCreatePool_BAR_QUX(t) - testPositionMintPos01Tier01(t) - //testPositionMintPos02Tier01(t) - //testPositionMintPos03Tier01(t) - //testCreateExternalIncentive(t) - testStakeToken01(t) - //testStakeToken02(t) - //testStakeToken03(t) - //testSameHeightCalculation(t) - testCollectReward01(t) - //testUnstakeToken01(t) - //testExternalIncentiveReward(t) -} - -func testInit(t *testing.T) { - t.Run("init pool tiers", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - // init pool tiers - // tier 1 - // deletePoolTier(t, MUST_EXISTS_IN_TIER_1) - //println("GENESIS", std.GetHeight()) - std.TestSkipHeights(1) - - // set unstaking fee to 0 - SetUnstakingFeeByAdmin(0) - - // set pool creation fee to 0 - //pl.SetPoolCreationFeeByAdmin(0) - println("height", std.GetHeight()) - - // set community pool distribution to 0% (give it to devOps) - en.ChangeDistributionPctByAdmin( - 1, 7500, - 2, 2500, - 3, 0, - 4, 0, - ) - - // prepare wugnot - std.TestIssueCoins(adminAddr, std.Coins{{"ugnot", 100_000_000_000_000}}) - banker := std.GetBanker(std.BankerTypeRealmSend) - banker.SendCoins(adminAddr, consts.WUGNOT_ADDR, std.Coins{{"ugnot", 50_000_000_000_000}}) - std.TestSetOrigSend(std.Coins{{"ugnot", 50_000_000_000_000}}, nil) - wugnot.Deposit() - std.TestSetOrigSend(nil, nil) - - std.TestSkipHeights(1) - }) -} - -func testCreatPool_WUGNOT_GNS(t *testing.T) { - t.Run("create pool wugnot-gns-3000", func(t *testing.T) { - println("[", std.GetHeight(), "]", "create pool wugnot-gns-3000") - uassert.Equal(t, uint64(100000000000000), gns.TotalSupply()) - uassert.Equal(t, uint64(0), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, uint64(0), gnsBalance(consts.STAKER_ADDR)) - uassert.Equal(t, uint64(0), gnsBalance(consts.DEV_OPS)) - std.TestSetRealm(adminRealm) - - gns.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - wugnot.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - pl.CreatePool(consts.WUGNOT_PATH, consts.GNS_PATH, 3000, "79228162514264337593543950337") // tick 0 ≈ x1 - uassert.Equal(t, uint64(100000000), gnsBalance(consts.PROTOCOL_FEE_ADDR)) - oneBlockEmissionAmount := uint64(14269406) - uassert.Equal(t, uint64(100000000000000)+(2*oneBlockEmissionAmount), gns.TotalSupply()) - - std.TestSkipHeights(1) - }) -} - -func testCreatePool_BAR_QUX(t *testing.T) { - t.Run("create pool bra-qux-500", func(t *testing.T) { - println("[ ", std.GetHeight(), "]", "create pool bar-qux-500") - std.TestSetRealm(adminRealm) - oneBlockEmissionAmount := uint64(14269406) - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - pl.CreatePool(barPath, quxPath, 500, "130621891405341611593710811006") // tick 10_000 ≈ x2.7 - uassert.Equal(t, uint64(200000000), gnsBalance(consts.PROTOCOL_FEE_ADDR)) - // 3block - uassert.Equal(t, uint64(100000000000000)+(3*oneBlockEmissionAmount), gns.TotalSupply()) - oneBlockStakerAmount := uint64(10702054) - // left 1 gns / 2block for staker - uassert.Equal(t, (3*oneBlockStakerAmount)+1, gnsBalance(consts.STAKER_ADDR)) - oneBlockDevOpsAmount := uint64(3567351) - uassert.Equal(t, (3*oneBlockDevOpsAmount)+1, gnsBalance(consts.DEV_OPS)) - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - - println("[", std.GetHeight(), "]", "addPoolTier ", barPath, quxPath, 500, "to : 1tier") - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500`, 1) - std.TestSkipHeights(1) - }) -} - -func testPositionMintPos01Tier01(t *testing.T) { - t.Run("mint position 01, gns:wugnot:3000", func(t *testing.T) { - // mint position in tier 1 pool - // gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000 - std.TestSetRealm(adminRealm) - gns.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - wugnot.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - lpTokenId, liquidity, amount0, amount1 := pn.Mint( - "gno.land/r/gnoswap/v1/gns", // token0 - "gno.land/r/demo/wugnot", // token1 - fee3000, // fee - int32(-60), // tickLower - int32(60), // tickUpper - "1000", // amount0Desired - "1000", // amount1Desired - "0", // amount0Min - "0", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - println("lpTokenId : ", lpTokenId, ", liquidity : ", liquidity, ", amount0 : ", amount0, ", amount1 : ", amount1) - oneBlockEmissionAmount := uint64(14269406) - uassert.Equal(t, uint64(100000000000000)+(6*oneBlockEmissionAmount), gns.TotalSupply()) - oneBlockStakerAmount := uint64(10702054) - uassert.Equal(t, (6*oneBlockStakerAmount)+3, gnsBalance(consts.STAKER_ADDR)) - oneBlockDevOpsAmount := uint64(3567351) - uassert.Equal(t, (6*oneBlockDevOpsAmount)+2, gnsBalance(consts.DEV_OPS)) - uassert.Equal(t, uint64(0), gnsBalance(consts.COMMUNITY_POOL_ADDR)) - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, uint64(1), lpTokenId) - uassert.Equal(t, gnft.MustOwnerOf(tid(lpTokenId)), users.Resolve(admin)) - uassert.Equal(t, amount0, "1000") - uassert.Equal(t, amount1, "1000") - - std.TestSkipHeights(1) - // approve nft to staker for staking - std.TestSetRealm(adminRealm) - gnft.Approve(consts.STAKER_ADDR, tid(lpTokenId)) - std.TestSkipHeights(1) - }) -} - -func testPositionMintPos02Tier01(t *testing.T) { - t.Run("mint position 02, bar:qux:500", func(t *testing.T) { - std.TestSetRealm(adminRealm) - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - lpTokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - fee500, // fee - int32(9000), // tickLower - int32(11000), // tickUpper - "1000", // amount0Desired - "1000", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - println("lpTokenId : ", lpTokenId, ", liquidity : ", liquidity, ", amount0 : ", amount0, ", amount1 : ", amount1) - oneBlockEmissionAmount := uint64(14269406) - oneBlockStakerAmount := uint64(10702054) - oneBlockDevOpsAmount := uint64(3567351) - - uassert.Equal(t, uint64(100000000000000)+(10*oneBlockEmissionAmount), gns.TotalSupply()) - uassert.Equal(t, (10*oneBlockStakerAmount)+5, gnsBalance(consts.STAKER_ADDR)) - uassert.Equal(t, (10*oneBlockDevOpsAmount)+4, gnsBalance(consts.DEV_OPS)) - uassert.Equal(t, uint64(0), gnsBalance(consts.COMMUNITY_POOL_ADDR)) - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, uint64(2), lpTokenId) - uassert.Equal(t, gnft.MustOwnerOf(tid(lpTokenId)), users.Resolve(admin)) - uassert.Equal(t, amount0, "368") - uassert.Equal(t, amount1, "1000") - - std.TestSkipHeights(1) - - // approve nft to staker for staking - std.TestSetRealm(adminRealm) - gnft.Approve(consts.STAKER_ADDR, tid(lpTokenId)) - std.TestSkipHeights(1) - }) -} - -func testPositionMintPos03Tier01(t *testing.T) { - t.Run("mint position 03, bar:qux:500", func(t *testing.T) { - std.TestSetRealm(adminRealm) - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - lpTokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - fee500, // fee - int32(9100), // tickLower - int32(12000), // tickUpper - "5000", // amount0Desired - "5000", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - println("lpTokenId : ", lpTokenId, ", liquidity : ", liquidity, ", amount0 : ", amount0, ", amount1 : ", amount1) - - oneBlockEmissionAmount := uint64(14269406) - oneBlockStakerAmount := uint64(10702054) - oneBlockDevOpsAmount := uint64(3567351) - - uassert.Equal(t, uint64(100000000000000)+(14*oneBlockEmissionAmount), gns.TotalSupply()) - uassert.Equal(t, (14*oneBlockStakerAmount)+7, gnsBalance(consts.STAKER_ADDR)) - uassert.Equal(t, (14*oneBlockDevOpsAmount)+6, gnsBalance(consts.DEV_OPS)) - uassert.Equal(t, uint64(0), gnsBalance(consts.COMMUNITY_POOL_ADDR)) - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, uint64(3), lpTokenId) - uassert.Equal(t, gnft.MustOwnerOf(tid(lpTokenId)), users.Resolve(admin)) - uassert.Equal(t, amount0, "3979") - uassert.Equal(t, amount1, "5000") - - std.TestSkipHeights(1) - - // approve nft to staker - std.TestSetRealm(adminRealm) - gnft.Approve(consts.STAKER_ADDR, tid(lpTokenId)) - std.TestSkipHeights(1) - }) -} - -func testCreateExternalIncentive(t *testing.T) { - t.Run("create external incentive", func(t *testing.T) { - std.TestSetRealm(adminRealm) - obl.Approve(a2u(consts.STAKER_ADDR), 10_000_000_000) - gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount) - AddToken(oblPath) - CreateExternalIncentive( - "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500", // targetPoolPath - oblPath, // rewardToken - 1000000000, // rewardAmount - 1234569600, // startTimestamp - 1234569600+TIMESTAMP_90DAYS, // endTimestamp - ) - - oneBlockEmissionAmount := uint64(14269406) - oneBlockStakerAmount := uint64(10702054) - oneBlockDevOpsAmount := uint64(3567351) - - uassert.Equal(t, uint64(100000000000000)+(16*oneBlockEmissionAmount), gns.TotalSupply()) - externalInecntiveDeposit := depositGnsAmount - - beforeGNSForStaker := uint64(10702054) + uint64(32106164) + uint64(42808219) + uint64(42808218) + uint64(21404109) - transferAmountForCommunityPoolByPool1 := uint64(beforeGNSForStaker / 2) - transferAmountForCommunityPoolByPool2 := uint64(beforeGNSForStaker / 2) - expectedGNSForStaker := beforeGNSForStaker + externalInecntiveDeposit - transferAmountForCommunityPoolByPool1 - transferAmountForCommunityPoolByPool2 - - uassert.Equal(t, (16*oneBlockStakerAmount)+8+externalInecntiveDeposit, gnsBalance(consts.STAKER_ADDR)) - uassert.Equal(t, (16*oneBlockDevOpsAmount)+7, gnsBalance(consts.DEV_OPS)) - beforeGNSForCommunityPool := uint64(713470) + uint64(2140410) + uint64(2853881) + uint64(2853881) + uint64(1426940) - uassert.Equal(t, uint64(0), gnsBalance(consts.COMMUNITY_POOL_ADDR)) - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, uint64(1000000000), obl.BalanceOf(a2u(consts.STAKER_ADDR))) - - std.TestSkipHeights(1) - }) -} - -func testStakeToken01(t *testing.T) { - t.Run("stake token 01", func(t *testing.T) { - std.TestSetRealm(adminRealm) - println("height", std.GetHeight(), " duration : ", std.GetHeight()-123) - StakeToken(1) // GNFT tokenId - - uassert.Equal(t, consts.STAKER_ADDR, gnft.MustOwnerOf(tid(1))) // staker - uassert.Equal(t, 1, deposits.Size()) - - oneBlockEmissionAmount := uint64(14269406) - oneBlockStakerAmount := uint64(10702054) - oneBlockDevOpsAmount := uint64(3567351) - - uassert.Equal(t, uint64(100000000000000)+(17*oneBlockEmissionAmount), gns.TotalSupply()) - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, (17*oneBlockDevOpsAmount)+7, gnsBalance(consts.DEV_OPS)) - - prevGNSForStaker := uint64(0) + depositGnsAmount - externalInecntiveDeposit := uint64(1000000000) - uassert.Equal(t, (17*oneBlockStakerAmount)+9+externalInecntiveDeposit, gnsBalance(consts.STAKER_ADDR)) - prevGNSForCommunityPool := uint64(159817346) - currGNSForCommunityPool := prevGNSForCommunityPool + uint64(713470) - transferAmountForCommunityPoolByPool1 := uint64(10702056) / 2 - transferAmountForCommunityPoolByPool2 := uint64(10702056) / 2 - uassert.Equal(t, uint64(0), gnsBalance(consts.COMMUNITY_POOL_ADDR)) - - std.TestSkipHeights(500) - }) -} - -func testStakeToken02(t *testing.T) { - t.Run("stake token 02", func(t *testing.T) { - std.TestSetRealm(adminRealm) - println(">>>>>>>>> StakeToken02", "height", std.GetHeight(), " duration : ", std.GetHeight()-123) - StakeToken(2) // GNFT tokenId - - uassert.Equal(t, consts.STAKER_ADDR, gnft.MustOwnerOf(tid(2))) // staker - uassert.Equal(t, 2, deposits.Size()) - - oneBlockEmissionAmount := uint64(14269406) - oneBlockStakerAmount := uint64(10702054) - oneBlockDevOpsAmount := uint64(3567351) - externalInecntiveDeposit := uint64(1000000000) - - prevGNSTotalBalance := uint64(100000214041090) - currGNSTotalBalance := prevGNSTotalBalance + uint64(7134703000) // 500 block gns minted - uassert.Equal(t, uint64(100000000000000)+(517*oneBlockEmissionAmount), gns.TotalSupply()) - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, (517*oneBlockDevOpsAmount)+257, gnsBalance(consts.DEV_OPS)) - uassert.Equal(t, uint64(0), gnsBalance(consts.COMMUNITY_POOL_ADDR)) - - prevGNSForStaker := uint64(0) + depositGnsAmount - positionWarmUp := uint64(802654087) - positionWarmUpPenalty := uint64(1872859538) - currGNSForStaker := prevGNSForStaker + positionWarmUp + positionWarmUpPenalty - uassert.Equal(t, (517*oneBlockStakerAmount)+259+externalInecntiveDeposit, gnsBalance(consts.STAKER_ADDR)) - uassert.Equal(t, GetOrigPkgAddr(), gnft.MustOwnerOf(tid(2))) // staker - uassert.Equal(t, 2, deposits.Size()) - - std.TestSkipHeights(1) - }) -} - -func testStakeToken03(t *testing.T) { - t.Run("stake token 03", func(t *testing.T) { - std.TestSetRealm(adminRealm) - StakeToken(3) // GNFT tokenId - - uassert.Equal(t, consts.STAKER_ADDR, gnft.MustOwnerOf(tid(3))) // staker - uassert.Equal(t, 3, deposits.Size()) - - oneBlockEmissionAmount := uint64(14269406) - oneBlockStakerAmount := uint64(10702054) - oneBlockDevOpsAmount := uint64(3567351) - externalInecntiveDeposit := uint64(1000000000) - - prevGNSTotalBalance := uint64(100007348744090) - currGNSTotalBalance := prevGNSTotalBalance + uint64(14269406) // 1 block gns minted - uassert.Equal(t, uint64(100000000000000)+(518*oneBlockEmissionAmount), gns.TotalSupply()) - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, (518*oneBlockDevOpsAmount)+257, gnsBalance(consts.DEV_OPS)) - uassert.Equal(t, uint64(0), gnsBalance(consts.COMMUNITY_POOL_ADDR)) - - prevGNSForStaker := uint64(0) + depositGnsAmount + uint64(802654087) + uint64(1872859538) - positionWarmUp := uint64(10702055) * 30 / 100 - positionWarmUpPenalty := uint64(10702055) - positionWarmUp - currGNSForStaker := prevGNSForStaker + positionWarmUp + positionWarmUpPenalty - uassert.Equal(t, (518*oneBlockStakerAmount)+260+externalInecntiveDeposit, gnsBalance(consts.STAKER_ADDR)) - }) -} - -func testSameHeightCalculation(t *testing.T) { - t.Run("same height calculation", func(t *testing.T) { - - println("height", std.GetHeight()) - oneBlockEmissionAmount := uint64(14269406) - oneBlockStakerAmount := uint64(10702054) - oneBlockDevOpsAmount := uint64(3567351) - externalInecntiveDeposit := uint64(1000000000) - - uassert.Equal(t, (518*oneBlockDevOpsAmount)+257, gnsBalance(consts.DEV_OPS)) - uassert.Equal(t, uint64(0), gnsBalance(consts.COMMUNITY_POOL_ADDR)) - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, uint64(100000000000000)+(518*oneBlockEmissionAmount), gns.TotalSupply()) - uassert.Equal(t, (518*oneBlockStakerAmount)+260+externalInecntiveDeposit, gnsBalance(consts.STAKER_ADDR)) - - std.TestSkipHeights(1) - }) -} - -func testCollectReward01(t *testing.T) { - t.Run("collect reward 01", func(t *testing.T) { - std.TestSetRealm(adminRealm) - beforeGNSForAdmin := gnsBalance(consts.ADMIN) - - println("height", std.GetHeight()) - oneBlockEmissionAmount := uint64(14269406) - oneBlockStakerAmount := uint64(10702054) - oneBlockDevOpsAmount := uint64(3567351) - externalInecntiveDeposit := uint64(1000000000) - - println("1") - r1, p1 := CollectReward(1, false) - //println("2") - //r2, p2 := CollectReward(2, false) - //println("3") - //r3, p3 := CollectReward(3, false) - - println("r1", r1) - println("p1", p1) - //println("r2", r2) - //println("p2", p2) - //println("r3", r3) - //println("p3", p3) - - n1, _ := strconv.ParseUint(r1, 10, 64) - //n2, _ := strconv.ParseUint(r2, 10, 64) - //n3, _ := strconv.ParseUint(r3, 10, 64) - //rsum := n1 + n2 + n3 - rsum := n1 - - m1, _ := strconv.ParseUint(p1, 10, 64) - //m2, _ := strconv.ParseUint(p2, 10, 64) - //m3, _ := strconv.ParseUint(p3, 10, 64) - //psum := m1 + m2 + m3 - psum := m1 - println("sum : ", rsum) - println("sum : ", psum) - println("sum : ", rsum+psum) - gnsStaker := (518 * oneBlockStakerAmount) + 260 - println("gns staker : ", strconv.FormatUint(gnsStaker, 10)) - - u1Gap := 642 - 140 - println(gnsStaker / 7) - - token2Amount := uint64(2096082) - token2Penalty := uint64(4890861) - token3Amount := uint64(2719841) - token3Penalty := uint64(6346297) - leftAmount := uint64(1) - expectedPoolAmount := token2Amount + token2Penalty + token3Amount + token3Penalty + leftAmount - - println("A") - rewardForUser := uint64(1884096693) - rewardForPenalty := uint64(807470014) - rewardFee := rewardForUser * 100 / 10000 - println("1") - uassert.Equal(t, uint64(18840966), rewardFee) - rewardForUserWithFee := rewardForUser - rewardFee - println("2") - uassert.Equal(t, uint64(1865255727), rewardForUserWithFee) - println("3") - uassert.Equal(t, rewardForUserWithFee, gnsBalance(consts.ADMIN)-beforeGNSForAdmin) - println("before GNS For Admin : ", beforeGNSForAdmin) - println("after GNS For Admin : ", gnsBalance(consts.ADMIN)) - println("actual reward GNS For Admin : ", gnsBalance(consts.ADMIN)-beforeGNSForAdmin) - - // 5425941631 = 1605308099 + 3745718900 + 53510270 (74914378) - // 5425941631 - // 5425941377 - - prevGNSForCommunityPool := uint64(3204195117) + uint64(1426940) - currGNSForCommunityPool := prevGNSForCommunityPool + rewardForPenalty - println("4") - uassert.Equal(t, currGNSForCommunityPool, gnsBalance(consts.COMMUNITY_POOL_ADDR)) // 4013092071 - - prevGNSForStaker := uint64(3686215680) - currentGNSForStaker := prevGNSForStaker + uint64(21404109) - rewardForUser - rewardForPenalty - println("5") - uassert.Equal(t, currentGNSForStaker, gnsBalance(consts.STAKER_ADDR)) // 1016053082 - println("6") - uassert.Equal(t, uint64(1472602698)+uint64(5707762), gnsBalance(consts.DEV_OPS)) // 1478310460 - println("7") - uassert.Equal(t, uint64(200000000)+rewardFee, gnsBalance(consts.PROTOCOL_FEE_ADDR)) // 218840966 - println("8") - uassert.Equal(t, uint64(100007363013496)+uint64(28538812), gns.TotalSupply()) // 100007391552308 - println("9") - uassert.Equal(t, uint64(2), gnsBalance(consts.EMISSION_ADDR)) // 2 - - std.TestSkipHeights(1) - }) -} - -func testUnstakeToken01(t *testing.T) { - t.Run("unstake token 01", func(t *testing.T) { - std.TestSetRealm(adminRealm) - beforeGNSForAdmin := gnsBalance(consts.ADMIN) - UnstakeToken(1, false) - uassert.Equal(t, deposits.Size(), 2) - uassert.Equal(t, uint64(100007391552308)+uint64(14269406), gns.TotalSupply()) - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, uint64(1478310460)+uint64(2853881), gnsBalance(consts.DEV_OPS)) // 1481164341 - - rewardForUser := uint64(3745719) - rewardForPenalty := uint64(1605309) - rewardFee := rewardForUser * 100 / 10000 - uassert.Equal(t, uint64(37457), rewardFee) - rewardForUserWithFee := rewardForUser - rewardFee - uassert.Equal(t, rewardForUserWithFee, gnsBalance(consts.ADMIN)-beforeGNSForAdmin) - uassert.Equal(t, uint64(218840966)+rewardFee, gnsBalance(consts.PROTOCOL_FEE_ADDR)) // 218878423 - - prevGNSForStaker := uint64(1016053082) - currGNSForStaker := prevGNSForStaker + uint64(10702056) - rewardForUser - rewardForPenalty - uassert.Equal(t, currGNSForStaker, gnsBalance(consts.STAKER_ADDR)) // 1021404110 - uassert.Equal(t, uint64(4013092071)+uint64(713470)+uint64(rewardForPenalty), gnsBalance(consts.COMMUNITY_POOL_ADDR)) // 4015410850 - - std.TestSkipHeights(1) - }) -} - -func testExternalIncentiveReward(t *testing.T) { - t.Run("create external incentive gns & reward test", func(t *testing.T) { - std.TestSetRealm(adminRealm) - gns.Approve(a2u(consts.STAKER_ADDR), consts.UINT64_MAX) // this includes depositGnsAmount - CreateExternalIncentive( - "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000", // targetPoolPath string, - consts.GNS_PATH, // rewardToken string, // token path should be registered - 20000000, // _rewardAmount string, - 1234656000, - 1234656000+TIMESTAMP_90DAYS, - ) - // 1block minting - uassert.Equal(t, uint64(100007405821714)+uint64(14269406), gns.TotalSupply()) // 100007420091120 - externalInecntiveDeposit := depositGnsAmount - externalIncentiveReward := uint64(20000000) - toCommunityPoolByPool1 := uint64(5351027) // minted gns for staker(10702055) / 2(pool num) - expectedGNSForStaker := uint64(1021404110) + uint64(10702055) + externalInecntiveDeposit + externalIncentiveReward - toCommunityPoolByPool1 - uassert.Equal(t, expectedGNSForStaker, gnsBalance(consts.STAKER_ADDR)) // 2046755138 - uassert.Equal(t, uint64(1481164341)+uint64(2853881), gnsBalance(consts.DEV_OPS)) // 1484018222 - uassert.Equal(t, uint64(4015410850)+uint64(713470)+toCommunityPoolByPool1, gnsBalance(consts.COMMUNITY_POOL_ADDR)) // 4021475347 - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, uint64(218878423), gnsBalance(consts.PROTOCOL_FEE_ADDR)) // 218878423 - - curHeight := std.GetHeight() - externalStartHeight := int64(855) - gapHeight := externalStartHeight - curHeight - std.TestSkipHeights(gapHeight) - - lpTokenId, liquidity, amount0, amount1 := pn.Mint( - "gno.land/r/gnoswap/v1/gns", // token0 - "gno.land/r/demo/wugnot", // token1 - fee3000, // fee - int32(0), // tickLower - int32(60), // tickUpper - "1000", // amount0Desired - "1000", // amount1Desired - "0", // amount0Min - "0", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - gnft.Approve(consts.STAKER_ADDR, tid(lpTokenId)) - // 222 block mint - uassert.Equal(t, uint64(100007420091120)+uint64(3025114072), gns.TotalSupply()) // 100010445205192 - uassert.Equal(t, uint64(1484018222)+uint64(605022814), gnsBalance(consts.DEV_OPS)) // 2089041036 - uassert.Equal(t, uint64(2046755138)+uint64(2268835554), gnsBalance(consts.STAKER_ADDR)) // 4315590692 - uassert.Equal(t, uint64(4021475347)+uint64(151255703), gnsBalance(consts.COMMUNITY_POOL_ADDR)) // 4172731050 - uassert.Equal(t, uint64(2), gnsBalance(consts.EMISSION_ADDR)) - uassert.Equal(t, uint64(4), lpTokenId) - uassert.Equal(t, gnft.MustOwnerOf(tid(lpTokenId)), admin) - - std.TestSkipHeights(1) - - StakeToken(4) // GNFT tokenId - // 1 block mint - uassert.Equal(t, uint64(100010445205192)+uint64(14269406), gns.TotalSupply()) // 100010459474598 - uassert.Equal(t, uint64(2089041036)+uint64(2853881), gnsBalance(consts.DEV_OPS)) // 2091894917 - - toCommunityPoolByPool1 = uint64(1139768805) // prev minted for tier1 pool = 1134417777 + 5351028 - uassert.Equal(t, uint64(4315590692)+uint64(10702056)-toCommunityPoolByPool1, gnsBalance(consts.STAKER_ADDR)) // 4331643775 - uassert.Equal(t, uint64(4172731050)+uint64(713470)+toCommunityPoolByPool1, gnsBalance(consts.COMMUNITY_POOL_ADDR)) // 4168093493 - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - std.TestSkipHeights(1) - - CollectReward(4, false) - // 1 block mint - uassert.Equal(t, uint64(100010459474598)+uint64(14269406), gns.TotalSupply()) // 100010473743904 - uassert.Equal(t, uint64(2091894917)+uint64(2853881), gnsBalance(consts.DEV_OPS)) // 2094748800 - userReward := uint64(1605308) - userPenalty := uint64(3745720) - userFee := userReward * 100 / 10000 - communityPoolSent := uint64(5351027) + uint64(1139768805) - uassert.Equal(t, uint64(4331643775)+uint64(10702055)-userReward-userPenalty-communityPoolSent, gnsBalance(consts.STAKER_ADDR)) // 4332755831 - toCommunityPool := communityPoolSent + userPenalty - uassert.Equal(t, uint64(4168093493)+uint64(713470)+toCommunityPool, gnsBalance(consts.COMMUNITY_POOL_ADDR)) // 4168093493 - uassert.Equal(t, uint64(1), gnsBalance(consts.EMISSION_ADDR)) - - std.TestSkipHeights(122) - beforeExternalReward := obl.BalanceOf(a2u(consts.ADMIN)) - CollectReward(2, false) - atferExternalReward := obl.BalanceOf(a2u(consts.ADMIN)) - uassert.Equal(t, uint64(11), atferExternalReward-beforeExternalReward) - - std.TestSkipHeights(43199) - std.TestSkipHeights(1) - beforeGNSExternalReward := gns.BalanceOf(a2u(consts.ADMIN)) - CollectReward(4, false) - atferGNSExternalReward := gns.BalanceOf(a2u(consts.ADMIN)) - uassert.Equal(t, int64(1), atferGNSExternalReward-beforeGNSExternalReward) - }) -} diff --git a/staker/__TEST_staker_external_native_coin_test.gnoA b/staker/__TEST_staker_external_native_coin_test.gnoA index 5e40e8030..fad32d767 100644 --- a/staker/__TEST_staker_external_native_coin_test.gnoA +++ b/staker/__TEST_staker_external_native_coin_test.gnoA @@ -100,23 +100,26 @@ func testStakeToken(t *testing.T) { } func testCollectExternalReward_1_Unwrap(t *testing.T) { + t.Run("collect all rewards", func(t *testing.T) { + std.TestSetRealm(adminRealm) + std.TestSkipHeights(900) + CollectReward(1, false) + }) + t.Run("collect external reward 01, unwrap", func(t *testing.T) { std.TestSetRealm(adminRealm) + std.TestSkipHeights(1) oldUgnotBal := ugnotBalanceOf(t, adminAddr) - uassert.Equal(t, oldUgnotBal, uint64(99999900000000)) - oldWugnotBal := wugnot.BalanceOf(admin) - uassert.Equal(t, oldWugnotBal, uint64(0)) - std.TestSkipHeights(900) CollectReward(1, true) newUgnotBal := ugnotBalanceOf(t, adminAddr) - uassert.Equal(t, newUgnotBal, uint64(99999900000000+415)) - newWugnotBal := wugnot.BalanceOf(admin) - uassert.Equal(t, newWugnotBal, uint64(0)) + + uassert.True(t, newUgnotBal > oldUgnotBal) // unwrapping true increases ugnot balance + uassert.True(t, newWugnotBal == oldWugnotBal) // wugnot stays }) } @@ -125,18 +128,15 @@ func testCollectExternalReward_1_NoUnWrap(t *testing.T) { std.TestSetRealm(adminRealm) oldUgnotBal := ugnotBalanceOf(t, adminAddr) - uassert.Equal(t, oldUgnotBal, uint64(99999900000415)) - oldWugnotBal := wugnot.BalanceOf(admin) - uassert.Equal(t, oldWugnotBal, uint64(0)) std.TestSkipHeights(1) CollectReward(1, false) newUgnotBal := ugnotBalanceOf(t, adminAddr) - uassert.Equal(t, newUgnotBal, uint64(99999900000415)) - newWugnotBal := wugnot.BalanceOf(admin) - uassert.Equal(t, newWugnotBal, uint64(7)) + + uassert.True(t, newWugnotBal > oldWugnotBal) // unwrapping false increases wugnot balance + uassert.True(t, newUgnotBal == oldUgnotBal) // ugnot stays }) } diff --git a/staker/__TEST_staker_full_with_emission_test.gnoA b/staker/__TEST_staker_full_with_emission_test.gnoA deleted file mode 100644 index 16c93d688..000000000 --- a/staker/__TEST_staker_full_with_emission_test.gnoA +++ /dev/null @@ -1,296 +0,0 @@ -package staker - -import ( - "std" - "testing" - - "gno.land/p/demo/uassert" - - pl "gno.land/r/gnoswap/v1/pool" - pn "gno.land/r/gnoswap/v1/position" - - "gno.land/r/demo/wugnot" - "gno.land/r/gnoswap/v1/gns" - "gno.land/r/onbloc/bar" - "gno.land/r/onbloc/obl" - "gno.land/r/onbloc/qux" - - "gno.land/r/gnoswap/v1/gnft" - - "gno.land/r/gnoswap/v1/consts" -) - -func TestStakerWithEmissionAmount(t *testing.T) { - testInit(t) - testCreatePool(t) - testPositionMintPos01Tier01(t) - testPositionMintPos02Tier01(t) - testPositionMintPos03Tier01(t) - testCreateExternalIncentive(t) - testStakeToken01(t) - testStakeToken02(t) - testStakeToken03(t) - testSameHeightCalculation(t) - testCollectReward01(t) - testUnstakeToken01(t) - testUnstakeToken02(t) - testCollectReward02(t) -} - -func testInit(t *testing.T) { - t.Run("initial", func(t *testing.T) { - // set pool create fee to 0 for testing - std.TestSetRealm(adminRealm) - pl.SetPoolCreationFeeByAdmin(0) - }) -} - -func testCreatePool(t *testing.T) { - t.Run("create pool", func(t *testing.T) { - uassert.Equal(t, gns.TotalSupply(), uint64(100000000000000)) - uassert.Equal(t, gnsBalance(consts.EMISSION_ADDR), uint64(0)) - uassert.Equal(t, gnsBalance(consts.STAKER_ADDR), uint64(0)) - uassert.Equal(t, gnsBalance(consts.DEV_OPS), uint64(0)) - - std.TestSetRealm(adminRealm) - - gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()) - std.TestSkipHeights(1) - - pl.CreatePool(consts.WUGNOT_PATH, consts.GNS_PATH, 3000, "79228162514264337593543950336") // tick 0 ≈ x1 - pl.CreatePool(barPath, quxPath, 500, "130621891405341611593710811006") // tick 10_000 ≈ x2.7 - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500`, 1) - std.TestSkipHeights(2) - - uassert.Equal(t, gns.TotalSupply(), uint64(100000014269406)) // + 14269406 - uassert.Equal(t, gnsBalance(consts.EMISSION_ADDR), uint64(1)) - uassert.Equal(t, gnsBalance(consts.STAKER_ADDR), uint64(10702054)) // + 10702054 - uassert.Equal(t, gnsBalance(consts.DEV_OPS), uint64(2853881)) // + 2853881 - }) -} - -func testPositionMintPos01Tier01(t *testing.T) { - t.Run("mint position 01, gns:wugnot:3000", func(t *testing.T) { - // mint position in tier 1 pool - // gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000 - - std.TestSetRealm(adminRealm) - gns.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - wugnot.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - lpTokenId, liquidity, amount0, amount1 := pn.Mint( - "gno.land/r/gnoswap/v1/gns", // token0 - "gno.land/r/demo/wugnot", // token1 - fee3000, // fee - int32(1020), // tickLower - int32(5040), // tickUpper - "1000", // amount0Desired - "1000", // amount1Desired - "0", // amount0Min - "0", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - std.TestSkipHeights(1) - - // approve nft to staker for staking - std.TestSetRealm(adminRealm) - gnft.Approve(consts.STAKER_ADDR, tid(lpTokenId)) - std.TestSkipHeights(1) - - // 4 block passed - uassert.Equal(t, gns.TotalSupply(), uint64(100000071347030)) // + 57077624 - uassert.Equal(t, gnsBalance(consts.EMISSION_ADDR), uint64(1)) - uassert.Equal(t, gnsBalance(consts.STAKER_ADDR), uint64(53510272)) // + 42808218 - uassert.Equal(t, gnsBalance(consts.DEV_OPS), uint64(14269406)) // + 11415525 - }) -} - -func testPositionMintPos02Tier01(t *testing.T) { - t.Run("mint position 02, bar:qux:500", func(t *testing.T) { - std.TestSetRealm(adminRealm) - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - lpTokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - fee500, // fee - int32(9000), // tickLower - int32(11000), // tickUpper - "1000", // amount0Desired - "1000", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - - std.TestSkipHeights(1) - - // approve nft to staker for staking - std.TestSetRealm(adminRealm) - gnft.Approve(consts.STAKER_ADDR, tid(lpTokenId)) - std.TestSkipHeights(1) - - uassert.Equal(t, gns.TotalSupply(), uint64(100000128424654)) - uassert.Equal(t, gnsBalance(consts.EMISSION_ADDR), uint64(1)) - uassert.Equal(t, gnsBalance(consts.STAKER_ADDR), uint64(96318490)) - uassert.Equal(t, gnsBalance(consts.DEV_OPS), uint64(25684931)) - }) -} - -func testPositionMintPos03Tier01(t *testing.T) { - t.Run("mint position 03, bar:qux:500", func(t *testing.T) { - std.TestSetRealm(adminRealm) - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - lpTokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - fee500, // fee - int32(9100), // tickLower - int32(12000), // tickUpper - "5000", // amount0Desired - "5000", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - - std.TestSkipHeights(1) - - // approve nft to staker - std.TestSetRealm(adminRealm) - gnft.Approve(consts.STAKER_ADDR, tid(lpTokenId)) - std.TestSkipHeights(1) - - uassert.Equal(t, gns.TotalSupply(), uint64(100000185502278)) - uassert.Equal(t, gnsBalance(consts.EMISSION_ADDR), uint64(1)) - uassert.Equal(t, gnsBalance(consts.STAKER_ADDR), uint64(139126708)) - uassert.Equal(t, gnsBalance(consts.DEV_OPS), uint64(37100456)) - }) -} - -func testCreateExternalIncentive(t *testing.T) { - t.Run("create external incentive", func(t *testing.T) { - std.TestSetRealm(adminRealm) - obl.Approve(a2u(consts.STAKER_ADDR), 10_000_000_000) - - gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount) - - AddToken(oblPath) - CreateExternalIncentive( - "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500", // targetPoolPath - oblPath, // rewardToken - 1000000000, // rewardAmount - 1234569600, // startTimestamp - 1234569600+TIMESTAMP_90DAYS, // endTimestamp - ) - std.TestSkipHeights(1) - - obl.Approve(a2u(consts.STAKER_ADDR), 10_000_000_000) - gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount) - }) -} - -func testStakeToken01(t *testing.T) { - t.Run("stake token 01", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - uassert.Equal(t, gnsBalance(consts.COMMUNITY_POOL_ADDR), uint64(10702053)) - StakeToken(1) // GNFT tokenId - std.TestSkipHeights(1) - - uassert.Equal(t, gns.TotalSupply(), uint64(100000228310496)) - uassert.Equal(t, gnsBalance(consts.EMISSION_ADDR), uint64(1)) - uassert.Equal(t, gnsBalance(consts.STAKER_ADDR), uint64(1171232873)) - uassert.Equal(t, gnsBalance(consts.DEV_OPS), uint64(45662099)) - }) -} - -func testStakeToken02(t *testing.T) { - t.Run("stake token 02", func(t *testing.T) { - std.TestSetRealm(adminRealm) - StakeToken(2) // GNFT tokenId - - std.TestSkipHeights(1) - - uassert.Equal(t, gns.TotalSupply(), uint64(100000242579902)) - uassert.Equal(t, gnsBalance(consts.EMISSION_ADDR), uint64(1)) - uassert.Equal(t, gnsBalance(consts.STAKER_ADDR), uint64(1181934928)) - uassert.Equal(t, gnsBalance(consts.DEV_OPS), uint64(48515980)) - }) -} - -func testStakeToken03(t *testing.T) { - t.Run("stake token 03", func(t *testing.T) { - std.TestSetRealm(adminRealm) - StakeToken(3) // GNFT tokenId - - std.TestSkipHeights(1) - - uassert.Equal(t, gns.TotalSupply(), uint64(100000256849308)) - uassert.Equal(t, gnsBalance(consts.EMISSION_ADDR), uint64(1)) - uassert.Equal(t, gnsBalance(consts.STAKER_ADDR), uint64(1192636983)) - uassert.Equal(t, gnsBalance(consts.DEV_OPS), uint64(51369861)) - }) -} - -func testSameHeightCalculation(t *testing.T) { -} - -func testCollectReward01(t *testing.T) { - t.Run("collect reward 01", func(t *testing.T) { - std.TestSetRealm(adminRealm) - CollectReward(1, false) - std.TestSkipHeights(1) - }) -} - -func testUnstakeToken01(t *testing.T) { - t.Run("unstake token 01", func(t *testing.T) { - std.TestSetRealm(adminRealm) - UnstakeToken(1, false) - std.TestSkipHeights(1) - - uassert.Equal(t, gns.TotalSupply(), uint64(100000285388120)) - uassert.Equal(t, gnsBalance(consts.EMISSION_ADDR), uint64(1)) - uassert.Equal(t, gnsBalance(consts.STAKER_ADDR), uint64(1112371580)) - uassert.Equal(t, gnsBalance(consts.DEV_OPS), uint64(57077623)) - }) -} - -func testUnstakeToken02(t *testing.T) { - t.Run("unstake token 02", func(t *testing.T) { - std.TestSetRealm(adminRealm) - UnstakeToken(2, false) - std.TestSkipHeights(1) - - // 1 block passed - uassert.Equal(t, gns.TotalSupply(), uint64(100000299657526)) // + 14269406 - uassert.Equal(t, gnsBalance(consts.EMISSION_ADDR), uint64(1)) - uassert.Equal(t, uint64(1029652302), gnsBalance(consts.STAKER_ADDR)) - uassert.Equal(t, gnsBalance(consts.DEV_OPS), uint64(59931504)) // + 2853881 - }) -} - -func testCollectReward02(t *testing.T) { - t.Run("collect reward from unstaked position should panic", func(t *testing.T) { - uassert.PanicsWithMessage( - t, - "[GNOSWAP-STAKER-022] requested data not found || tokenId(2) not found", - func() { - CollectReward(2, false) - }, - ) - }) -} diff --git a/staker/__TEST_staker_manage_pool_tiers_test.gnoA b/staker/__TEST_staker_manage_pool_tiers_test.gnoA index cbfb86f81..4dffe6621 100644 --- a/staker/__TEST_staker_manage_pool_tiers_test.gnoA +++ b/staker/__TEST_staker_manage_pool_tiers_test.gnoA @@ -4,8 +4,6 @@ import ( "std" "testing" - "gno.land/p/demo/uassert" - pl "gno.land/r/gnoswap/v1/pool" "gno.land/r/gnoswap/v1/common" @@ -17,13 +15,9 @@ import ( func TestManagePoolTiers(t *testing.T) { testCreatePool(t) testSetPoolTierByAdmin(t) - testGetPoolsWithTierStruct(t) testChangePoolTierByAdmin(t) - testGetPoolsWithTier(t) - testGetPoolsWithEmissionGnsAmount(t) testSetAnotherPoolTier3(t) testRemovePoolTierByAdmin(t) - testGetPoolsWithTierAfterRemove(t) } func testCreatePool(t *testing.T) { @@ -45,59 +39,31 @@ func testSetPoolTierByAdmin(t *testing.T) { std.TestSetRealm(adminRealm) SetPoolTierByAdmin("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500", 2) - if poolTiers["gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500"].tier != 2 { + if poolTier.CurrentTier("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500") != 2 { t.Error("Expected tier to be 2") } }) } -func testGetPoolsWithTierStruct(t *testing.T) { - t.Run("get pools with tier struct", func(t *testing.T) { - poolTiers := GetPoolsWithTierStruct() - if len(poolTiers) != 2 { - t.Error("Expected 2 pools") - } - }) -} - func testChangePoolTierByAdmin(t *testing.T) { t.Run("change pool tier by admin", func(t *testing.T) { std.TestSetRealm(adminRealm) ChangePoolTierByAdmin("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500", 3) - if poolTiers["gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500"].tier != 3 { + if poolTier.CurrentTier("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500") != 3 { t.Error("Expected tier to be 3") } }) } -func testGetPoolsWithTier(t *testing.T) { - t.Run("get pools with tier", func(t *testing.T) { - poolTiers := GetPoolsWithTier() - uassert.Equal(t, len(poolTiers), 2) - uassert.Equal(t, poolTiers[0], "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000_1") - uassert.Equal(t, poolTiers[1], "gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500_3") - }) -} - -func testGetPoolsWithEmissionGnsAmount(t *testing.T) { - t.Run("get pools with emission gns amount", func(t *testing.T) { - poolTiers := GetPoolsWithEmissionGnsAmount() - uassert.Equal(t, poolTiers, `{"stat":{"height":125,"timestamp":1234567894},"response":[{"poolPath":"gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000","rewardToken":"gno.land/r/gnoswap/v1/gns","startTimestamp":1234567890,"tier":1,"amount":540000000000000},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500","rewardToken":"gno.land/r/gnoswap/v1/gns","startTimestamp":1234567894,"tier":3,"amount":135000000000000}]}`) - }) -} - func testSetAnotherPoolTier3(t *testing.T) { t.Run("set another pool tier 3", func(t *testing.T) { std.TestSetRealm(adminRealm) SetPoolTierByAdmin("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:100", 3) - if poolTiers["gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:100"].tier != 3 { + if poolTier.CurrentTier("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:100") != 3 { t.Error("Expected tier to be 3") } - - poolTiers := GetPoolsWithEmissionGnsAmount() - uassert.Equal(t, poolTiers, `{"stat":{"height":125,"timestamp":1234567894},"response":[{"poolPath":"gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000","rewardToken":"gno.land/r/gnoswap/v1/gns","startTimestamp":1234567890,"tier":1,"amount":540000000000000},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500","rewardToken":"gno.land/r/gnoswap/v1/gns","startTimestamp":1234567894,"tier":3,"amount":67500000000000},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:100","rewardToken":"gno.land/r/gnoswap/v1/gns","startTimestamp":1234567894,"tier":3,"amount":67500000000000}]}`) }) } @@ -106,17 +72,8 @@ func testRemovePoolTierByAdmin(t *testing.T) { std.TestSetRealm(adminRealm) RemovePoolTierByAdmin("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500") - if len(poolTiers) != 2 { - t.Error("Expected 2 pool") + if poolTier.CurrentTier("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500") != 0 { + t.Error("Expected tier to be 0") } }) } - -func testGetPoolsWithTierAfterRemove(t *testing.T) { - t.Run("get pools with tier after remove", func(t *testing.T) { - poolTiers := GetPoolsWithTier() - uassert.Equal(t, len(poolTiers), 2) - uassert.Equal(t, poolTiers[0], "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000_1") - uassert.Equal(t, poolTiers[1], "gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:100_3") - }) -} diff --git a/staker/__TEST_staker_mint_and_stake_test.gnoA b/staker/__TEST_staker_mint_and_stake_test.gnoA index d9d048230..1abd74e25 100644 --- a/staker/__TEST_staker_mint_and_stake_test.gnoA +++ b/staker/__TEST_staker_mint_and_stake_test.gnoA @@ -25,12 +25,9 @@ func TestMintAndStake(t *testing.T) { func testInit(t *testing.T) { t.Run("initial", func(t *testing.T) { - // init pool tiers - // tier 1 - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:3000`, 1) + std.TestSetRealm(adminRealm) // set pool create fee to 0 for testing - std.TestSetRealm(adminRealm) pl.SetPoolCreationFeeByAdmin(0) }) } @@ -42,10 +39,11 @@ func testCreatePool(t *testing.T) { gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()) std.TestSkipHeights(1) - pl.CreatePool(barPath, quxPath, 3000, "130621891405341611593710811006") // tick 10_000 ≈ x2.7 - pl.CreatePool(consts.GNOT, consts.GNS_PATH, 3000, "79228162514264337593543950337") //x1 + pl.CreatePool(barPath, quxPath, 3000, "130621891405341611593710811006") // tick 10_000 ≈ x2.7 + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:3000`, 1) + std.TestSkipHeights(1) }) } diff --git a/staker/__TEST_staker_native_create_collect_unstake_test.gnoA b/staker/__TEST_staker_native_create_collect_unstake_test.gnoA index b4b3bc75b..703c21ac7 100644 --- a/staker/__TEST_staker_native_create_collect_unstake_test.gnoA +++ b/staker/__TEST_staker_native_create_collect_unstake_test.gnoA @@ -36,10 +36,6 @@ func TestNativeCreateAndCollectUnstake(t *testing.T) { func testInit_NativeCreateCollectUnstake(t *testing.T) { std.TestSetRealm(adminRealm) - // init pool tiers - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500`, 1) - addPoolTier(t, `gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:500`, 2) - // override warm-up period for testing changeWarmup(t, 0, 150) changeWarmup(t, 1, 300) @@ -67,8 +63,11 @@ func testCreatePool_NativeCreateCollectUnstake(t *testing.T) { gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()) std.TestSkipHeights(1) + pl.CreatePool(barPath, fooPath, uint32(500), common.TickMathGetSqrtRatioAtTick(0).ToString()) pl.CreatePool(consts.WUGNOT_PATH, consts.GNS_PATH, uint32(500), common.TickMathGetSqrtRatioAtTick(-10000).ToString()) - std.TestSkipHeights(1) + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500`, 1) + SetPoolTierByAdmin(`gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:500`, 2) + std.TestSkipHeights(18) }) } @@ -296,13 +295,9 @@ func testEndExternalIncentive(t *testing.T) { 1234569600+TIMESTAMP_90DAYS, // endTimestamp 134, ) - std.TestSkipHeights(1) - // FIXME: @mconcat - // left reward CAN NOT BE Zero - // there should be some reward left to refund - uassert.Equal(t, wugnot.BalanceOf(admin), uint64(622937)) - uassert.Equal(t, ugnotBalanceOf(t, adminAddr), uint64(99998010)) // always refund + uassert.Equal(t, wugnot.BalanceOf(admin), uint64(622937)) // stays same + uassert.Equal(t, ugnotBalanceOf(t, adminAddr), uint64(100264985)) // did get some refund }) } diff --git a/staker/__TEST_staker_short-warmup_period_collect_reward_test.gnoA b/staker/__TEST_staker_short-warmup_period_collect_reward_test.gnoA deleted file mode 100644 index f70e4df89..000000000 --- a/staker/__TEST_staker_short-warmup_period_collect_reward_test.gnoA +++ /dev/null @@ -1,308 +0,0 @@ -package staker - -import ( - "math" - "std" - "testing" - "time" - - "gno.land/p/demo/uassert" - pusers "gno.land/p/demo/users" - "gno.land/r/demo/users" - - "gno.land/r/gnoswap/v1/consts" - - pl "gno.land/r/gnoswap/v1/pool" - pn "gno.land/r/gnoswap/v1/position" - - "gno.land/r/gnoswap/v1/gnft" - - "gno.land/r/onbloc/bar" - "gno.land/r/onbloc/qux" - - "gno.land/r/gnoswap/v1/gns" - "gno.land/r/onbloc/obl" -) - -var anoAdmin = users.Resolve(admin) - -func TestCollectReward_Full(t *testing.T) { - testInit(t) - testPoolCreatePool(t) - testPositionMint01(t) - testPositionMint02(t) - testCreateExternalIncentive(t) - testStakeToken01(t) - testStakeToken02(t) - testCollectReward01_External(t) - testUnstakeToken01(t) - testUnstakeToken02(t) - testEndExternalIncentive(t) -} - -func testInit(t *testing.T) { - t.Run("initial", func(t *testing.T) { - std.TestSetRealm(adminRealm) - pl.SetPoolCreationFeeByAdmin(0) - - // override warm-up period for testing - changeWarmup(t, 0, 150) - changeWarmup(t, 1, 300) - changeWarmup(t, 2, 900) - changeWarmup(t, 3, math.MaxInt64) - - // set unstaking fee to 0 - // SetUnstakingFeeByAdmin(0) - }) -} - -func testPoolCreatePool(t *testing.T) { - t.Run("create pool", func(t *testing.T) { - std.TestSetRealm(adminRealm) - gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()) - - pl.CreatePool(barPath, quxPath, 500, "130621891405341611593710811006") // tick 10_000 ≈ x2.7 - - std.TestSkipHeights(1) - }) -} - -func testPositionMint01(t *testing.T) { - t.Run("mint position 01, bar:qux:500", func(t *testing.T) { - std.TestSetRealm(adminRealm) - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - lpTokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - uint32(500), // fee - int32(9000), // tickLower - int32(11000), // tickUpper - "1000", // amount0Desired - "1000", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, // deadline - anoAdmin, - anoAdmin, - ) - - std.TestSkipHeights(1) - - uassert.Equal(t, lpTokenId, uint64(1)) - uassert.Equal(t, amount0, "368") - uassert.Equal(t, amount1, "1000") - - // approve nft to staker - std.TestSetRealm(adminRealm) - gnft.Approve(GetOrigPkgAddr(), tid(lpTokenId)) - std.TestSkipHeights(1) - }) -} - -func testPositionMint02(t *testing.T) { - t.Run("mint position 02, bar:qux:500", func(t *testing.T) { - std.TestSetRealm(adminRealm) - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - lpTokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - uint32(500), // fee - int32(9100), // tickLower - int32(12000), // tickUpper - "5000", // amount0Desired - "5000", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, // deadline - anoAdmin, - anoAdmin, - ) - - std.TestSkipHeights(1) - - uassert.Equal(t, lpTokenId, uint64(2)) - uassert.Equal(t, amount0, "3979") - uassert.Equal(t, amount1, "5000") - - // approve nft to staker - std.TestSetRealm(adminRealm) - gnft.Approve(GetOrigPkgAddr(), tid(lpTokenId)) - std.TestSkipHeights(1) - }) -} - -func testCreateExternalIncentive(t *testing.T) { - t.Run("not allowed for external reward", func(t *testing.T) { - std.TestSetRealm(adminRealm) - obl.Approve(a2u(consts.STAKER_ADDR), uint64(10_000_000_000)) - std.TestSkipHeights(1) - - gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount) - - // obl token isnt't allowed for external reward, so panic - uassert.PanicsWithMessage( - t, - "[GNOSWAP-STAKER-026] not allowed for external reward: tokenPath(gno.land/r/onbloc/obl) is not allowed for external reward for poolPath(gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500)", - func() { - CreateExternalIncentive( - "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500", // targetPoolPath - "gno.land/r/onbloc/obl", // rewardToken - 1000000000, // rewardAmount 10_000_000_000 - 1234569600, // startTimestamp - 1234569600+TIMESTAMP_90DAYS, // endTimestamp - ) - }, - ) - }) - - t.Run("allow obl to be used as external reward token, then create incentive", func(t *testing.T) { - // allow obl to be used as external reward token - std.TestSetRealm(adminRealm) - AddToken(oblPath) - gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount) - - CreateExternalIncentive( - "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500", // targetPoolPath - "gno.land/r/onbloc/obl", // rewardToken - 1000000000, // rewardAmount 10_000_000_000 - 1234569600, // startTimestamp - 1234569600+TIMESTAMP_90DAYS, // endTimestamp - ) - std.TestSkipHeights(1) - - obl.Approve(pusers.AddressOrName(consts.STAKER_ADDR), uint64(10_000_000_000)) - std.TestSkipHeights(1) - }) -} - -func testStakeToken01(t *testing.T) { - t.Run("stake position 01, bar:qux:500", func(t *testing.T) { - std.TestSetRealm(adminRealm) - StakeToken(1) // GNFT tokenId - - std.TestSkipHeights(1) - - owner, err := gnft.OwnerOf(tid(1)) - uassert.NoError(t, err) - uassert.Equal(t, owner, GetOrigPkgAddr()) - uassert.Equal(t, deposits.Size(), 1) - }) -} - -func testStakeToken02(t *testing.T) { - t.Run("stake position 02, bar:qux:500", func(t *testing.T) { - std.TestSetRealm(adminRealm) - StakeToken(2) // GNFT tokenId - - std.TestSkipHeights(1) - - owner, err := gnft.OwnerOf(tid(2)) - uassert.NoError(t, err) - uassert.Equal(t, owner, GetOrigPkgAddr()) - uassert.Equal(t, deposits.Size(), 2) - }) -} - -func testCollectReward01_External(t *testing.T) { - t.Run("collect reward 01, external", func(t *testing.T) { - std.TestSkipHeights(1) - - // before claim - oblOld := obl.BalanceOf(admin) - std.TestSkipHeights(1) - uassert.Equal(t, oblOld, uint64(99999000000000)) - - std.TestSetRealm(adminRealm) - CollectReward(1, true) // GNFT tokenId - - std.TestSkipHeights(1) // not enough time to claim external reward - - oblNew := obl.BalanceOf(admin) - std.TestSkipHeights(1) - uassert.Equal(t, oblNew, uint64(99999000000000)) - }) -} - -func testUnstakeToken01(t *testing.T) { - t.Run("unstake position 01, bar:qux:500", func(t *testing.T) { - std.TestSetRealm(adminRealm) - std.TestSkipHeights(900) // enough time to claim external reward - - // check reward balance before unstake - uassert.Equal(t, gns.BalanceOf(admin), uint64(99999000000000)) // internal - uassert.Equal(t, obl.BalanceOf(admin), uint64(99999000000000)) // external - - // FIXME: remove comment after api is implemented - // response := ApiGetRewardsByLpTokenId(1) - // uassert.Equal(t, response, `{"stat":{"height":1041,"timestamp":1234569726},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":445623695,"stakeTimestamp":1234567914,"stakeHeight":135,"incentiveStart":1234567914},{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjUwMDpnbm8ubGFuZC9yL29uYmxvYy9vYmw6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEzMw==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500","rewardTokenPath":"gno.land/r/onbloc/obl","rewardTokenAmount":743,"stakeTimestamp":1234567914,"stakeHeight":135,"incentiveStart":1234569600}]}]}`) - - UnstakeToken(1, false) // GNFT tokenId - std.TestSkipHeights(1) - - owner, err := gnft.OwnerOf(tid(1)) - uassert.NoError(t, err) - uassert.Equal(t, owner, adminAddr) - - // check reward balance after unstake - uassert.Equal(t, gns.BalanceOf(admin), uint64(99999000000000)) // this pool is not internal reward pool - uassert.Equal(t, obl.BalanceOf(admin), uint64(99999000001715)) // only external reward is collected - }) -} - -func testUnstakeToken02(t *testing.T) { - t.Run("unstake position 02, bar:qux:500", func(t *testing.T) { - std.TestSetRealm(adminRealm) - now := uint64(time.Now().Unix()) - gap := now - 1234569600 - - // 0.4586225023 - // 514 - // 235.9169250514 - beforeBalance := obl.BalanceOf(a2u(consts.STAKER_ADDR)) - beforeObl := obl.BalanceOf(admin) - UnstakeToken(2, true) // GNFT tokenId - afterBalance := obl.BalanceOf(a2u(consts.STAKER_ADDR)) - afterObl := obl.BalanceOf(admin) - - std.TestSkipHeights(1) - - owner, err := gnft.OwnerOf(tid(2)) - uassert.NoError(t, err) - uassert.Equal(t, owner, adminAddr) - - // check reward - uassert.Equal(t, gns.BalanceOf(admin), uint64(99999000000000)) - uassert.Equal(t, obl.BalanceOf(admin), uint64(99999000011399)) // external + 9684 - }) -} - -func testEndExternalIncentive(t *testing.T) { - t.Run("end external incentive", func(t *testing.T) { - std.TestSetRealm(adminRealm) - std.TestSkipHeights(9999999) - - beforeObl := obl.BalanceOf(admin) - // use same parameter as CreateExternalIncentive() - EndExternalIncentive( - anoAdmin, - "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:500", - "gno.land/r/onbloc/obl", - 1234569600, // startTimestamp - 1234569600+TIMESTAMP_90DAYS, // endTimestamp - 133, - ) - - afterObl := obl.BalanceOf(admin) - - refunded := afterObl - beforeObl - if refunded == 0 { - panic("no refund amount") - } - }) -} diff --git a/staker/__TEST_staker_short_warmup_period_calculate_pool_position_reward_API_test.gnoA b/staker/__TEST_staker_short_warmup_period_calculate_pool_position_reward_API_test.gnoA new file mode 100644 index 000000000..691c977f5 --- /dev/null +++ b/staker/__TEST_staker_short_warmup_period_calculate_pool_position_reward_API_test.gnoA @@ -0,0 +1,362 @@ +package staker + +import ( + "math" + "std" + "testing" + + "gno.land/p/demo/uassert" + + "gno.land/r/gnoswap/v1/consts" + + pl "gno.land/r/gnoswap/v1/pool" + + "gno.land/r/gnoswap/v1/gns" + + "gno.land/r/onbloc/bar" + "gno.land/r/onbloc/baz" + "gno.land/r/onbloc/foo" + "gno.land/r/onbloc/qux" +) + +func TestCalcPoolPositionRewardGetter(t *testing.T) { + testInit(t) + + testPoolCreatePoolOnlyInternalBarFoo(t) // bar:foo:100 only GNS + + testPoolCreatePoolOnlyExternalBarBaz(t) // bar:baz:100 BAR and BAZ + testCreateExternalIncentiveBarAndBaz(t) + + testPoolCreatePoolBothInternalAndExternalBarQux(t) // bar:qux:100 QUX and GNS + testCreateExternalIncentiveQux(t) + + testMintAndStakeOnlyInternal01And02(t) + testMintAndStakeOnlyExternal03And04(t) + testMintAndStakeBothInternalAndExternal05(t) + + testMakeExternalIncentiveStart(t) + + // APIs + // all api tests just print result, most likely it is for debugging + testApiGetRewardTokens(t) + testApiGetRewardTokensByPoolPath(t) + testApiGetExternalIncentives(t) + testApiGetExternalIncentiveById(t) + testApiGetExternalIncentivesByPoolPath(t) + testApiGetExternalIncentivesByRewardTokenPath(t) + testApiGetInternalIncentives(t) + testApiGetInternalIncentivesByPoolPath(t) + testApiGetInternalIncentivesByTiers(t) + testApiGetRewardsByLpTokenId(t) + testApiGetRewardsByAddress(t) + testApiGetStakes(t) + testApiGetStakesByLpTokenId(t) + testApiGetStakesByAddress(t) + testIsStaked(t) +} + +func testInit(t *testing.T) { + t.Run("initial", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + deletePoolTier(t, MUST_EXISTS_IN_TIER_1) + + // override warm-up period for testing + changeWarmup(t, 0, 150) + changeWarmup(t, 1, 300) + changeWarmup(t, 2, 900) + changeWarmup(t, 3, math.MaxInt64) + + // no pool creation fee + pl.SetPoolCreationFeeByAdmin(0) + + // no unstaking fee + SetUnstakingFeeByAdmin(0) + + std.TestSkipHeights(1) + }) +} + +func testPoolCreatePoolOnlyInternalBarFoo(t *testing.T) { + t.Run("create bar:foo:100 pool for internal tier #1", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + pl.CreatePool(barPath, fooPath, 100, "79228162514264337593543950337") + SetPoolTierByAdmin("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:100", 1) + std.TestSkipHeights(1) + }) +} + +func testPoolCreatePoolOnlyExternalBarBaz(t *testing.T) { + t.Run("create pool bar:baz:100 for external", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + pl.CreatePool(barPath, bazPath, 100, "79228162514264337593543950337") + std.TestSkipHeights(1) + }) +} + +func testCreateExternalIncentiveBarAndBaz(t *testing.T) { + t.Run("create external incentive for bar:baz:100", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + bar.Approve(a2u(consts.STAKER_ADDR), consts.UINT64_MAX) + baz.Approve(a2u(consts.STAKER_ADDR), consts.UINT64_MAX) + gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount*2) + + CreateExternalIncentive( + "gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:100", + barPath, + 900000000, + 1234569600, + 1234569600+TIMESTAMP_90DAYS, + ) + + CreateExternalIncentive( + "gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:100", + bazPath, + 1800000000, + 1234569600, + 1234569600+TIMESTAMP_90DAYS, + ) + + std.TestSkipHeights(1) + }) +} + +func testPoolCreatePoolBothInternalAndExternalBarQux(t *testing.T) { + t.Run("create pool bar:qux:100 for internal tier #1 and external", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + pl.CreatePool(barPath, quxPath, 100, "79228162514264337593543950337") + SetPoolTierByAdmin("gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100", 1) + std.TestSkipHeights(1) + }) +} + +func testCreateExternalIncentiveQux(t *testing.T) { + t.Run("create external incentive for bar:qux:100", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + qux.Approve(a2u(consts.STAKER_ADDR), consts.UINT64_MAX) + gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount) + + CreateExternalIncentive( + "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100", + quxPath, + 900000000, + 1234569600, + 1234569600+TIMESTAMP_90DAYS, + ) + }) +} + +func testMintAndStakeOnlyInternal01And02(t *testing.T) { + t.Run("mint and stake position 01 and 02", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + // token to provide liquidity + bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + foo.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + + // position01 + lpTokenId, _, _, _, _ := MintAndStake( + barPath, + fooPath, + fee100, + int32(-1000), + int32(1000), + "50", + "50", + "1", + "1", + max_timeout, + ) + uassert.Equal(t, lpTokenId, uint64(1)) + std.TestSkipHeights(1) + + // position02 + lpTokenId, _, _, _, _ = MintAndStake( + barPath, + fooPath, + fee100, + int32(-1000), + int32(1000), + "50", + "50", + "1", + "1", + max_timeout, + ) + uassert.Equal(t, lpTokenId, uint64(2)) + std.TestSkipHeights(1) + }) +} + +func testMintAndStakeOnlyExternal03And04(t *testing.T) { + t.Run("mint and stake position 03 and 04", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + // token to provide liquidity + bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + baz.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + + // position03 + lpTokenId, _, _, _, _ := MintAndStake( + barPath, + bazPath, + fee100, + int32(-1000), + int32(1000), + "50", + "50", + "1", + "1", + max_timeout, + ) + uassert.Equal(t, lpTokenId, uint64(3)) + std.TestSkipHeights(1) + + // position04 + lpTokenId, _, _, _, _ = MintAndStake( + barPath, + bazPath, + fee100, + int32(-1000), + int32(1000), + "50", + "50", + "1", + "1", + max_timeout, + ) + uassert.Equal(t, lpTokenId, uint64(4)) + std.TestSkipHeights(1) + }) +} + +func testMintAndStakeBothInternalAndExternal05(t *testing.T) { + t.Run("mint and stake position 05", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + // token to provide liquidity + bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) + + // position05 + lpTokenId, _, _, _, _ := MintAndStake( + barPath, + quxPath, + fee100, + int32(-1000), + int32(1000), + "50", + "50", + "1", + "1", + max_timeout, + ) + uassert.Equal(t, lpTokenId, uint64(5)) + std.TestSkipHeights(1) + }) +} + +func testMakeExternalIncentiveStart(t *testing.T) { + t.Run("make external incentive start", func(t *testing.T) { + std.TestSkipHeights(100000) + }) +} + +func testApiGetRewardTokens(t *testing.T) { + res := ApiGetRewardTokens() + println(res) + println() +} + +func testApiGetRewardTokensByPoolPath(t *testing.T) { + res := ApiGetRewardTokensByPoolPath("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:100") + println(res) + println() +} + +func testApiGetExternalIncentives(t *testing.T) { + res := ApiGetExternalIncentives() + println(res) + println() +} + +func testApiGetExternalIncentiveById(t *testing.T) { + res := ApiGetExternalIncentiveById( + "gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:100", + "00000000001234569600:00000000001242345600:g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d:gno.land/r/onbloc/bar", + ) + println(res) + println() +} + +func testApiGetExternalIncentivesByPoolPath(t *testing.T) { + res := ApiGetExternalIncentivesByPoolPath("gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:100") + println(res) + println() +} + +func testApiGetExternalIncentivesByRewardTokenPath(t *testing.T) { + res := ApiGetExternalIncentivesByRewardTokenPath("gno.land/r/onbloc/qux") + println(res) + println() +} + +func testApiGetInternalIncentives(t *testing.T) { + res := ApiGetInternalIncentives() + println(res) + println() +} + +func testApiGetInternalIncentivesByPoolPath(t *testing.T) { + res := ApiGetInternalIncentivesByPoolPath("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:100") + println(res) + println() +} + +func testApiGetInternalIncentivesByTiers(t *testing.T) { + res := ApiGetInternalIncentivesByTiers(1) + println(res) + println() +} + +func testApiGetRewardsByLpTokenId(t *testing.T) { + res := ApiGetRewardsByLpTokenId(1) + println(res) + println() +} + +func testApiGetRewardsByAddress(t *testing.T) { + res := ApiGetRewardsByAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") + println(res) + println() +} + +func testApiGetStakes(t *testing.T) { + res := ApiGetStakes() + println(res) + println() +} + +func testApiGetStakesByLpTokenId(t *testing.T) { + res := ApiGetStakesByLpTokenId(1) + println(res) + println() +} + +func testApiGetStakesByAddress(t *testing.T) { + res := ApiGetStakesByAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") + println(res) + println() +} + +func testIsStaked(t *testing.T) { + res := IsStaked(1) + if !res { + t.Errorf("IsStaked(1) = false; want true") + } +} diff --git a/staker/__TEST_staker_short_warmup_period_calculate_pool_position_reward_GETTER_test.gnoXX b/staker/__TEST_staker_short_warmup_period_calculate_pool_position_reward_GETTER_test.gnoXX deleted file mode 100644 index b9fa47d39..000000000 --- a/staker/__TEST_staker_short_warmup_period_calculate_pool_position_reward_GETTER_test.gnoXX +++ /dev/null @@ -1,556 +0,0 @@ -package staker - -import ( - "std" - "testing" - "time" - - "gno.land/p/demo/uassert" - - "gno.land/r/gnoswap/v1/consts" - - en "gno.land/r/gnoswap/v1/emission" - pl "gno.land/r/gnoswap/v1/pool" - pn "gno.land/r/gnoswap/v1/position" - - "gno.land/r/gnoswap/v1/gnft" - "gno.land/r/gnoswap/v1/gns" - - "gno.land/r/onbloc/bar" - "gno.land/r/onbloc/qux" -) - -func TestCalcPoolPositionRewardGetter(t *testing.T) { - testInit(t) - testDoubleMint(t) - testPoolCreatePool(t) - testMintBarQux100_1(t) - testMintBarQux100_2(t) - - // stake position 01 + getter tests - testStakeToken_1(t) - testGetPoolGns(t) - testGetPoolCurrentBlockGns(t) - testGetPoolLastTmpGns(t) - testGetPoolAccuGns(t) - testGetPositionGns(t) - testGetPositionLastGns(t) - testGetPositionLastExternal(t) - testGetExternalLastCalculatedTimestamp(t) - testGetExternalGns(t) - testGetPoolTotalStakedLiquidity(t) - testGetPositionsLiquidityRatio(t) - testGetPoolsPositions(t) - - // stake position 02 + getter tests - testStakeToken_2(t) - testGetPoolGns2(t) - testGetPoolCurrentBlockGns2(t) - testGetPoolLastTmpGns2(t) - testGetPoolAccuGns2(t) - testGetPositionGns2(t) - testGetPositionLastGns2(t) - testGetPositionLastExternal2(t) - testGetExternalLastCalculatedTimestamp2(t) - testGetExternalGns2(t) - testGetPoolTotalStakedLiquidity2(t) - testGetPositionsLiquidityRatio2(t) - testGetPoolsPositions2(t) - - // etc getter tests - testGetHeight(t) - testGetTimeNowUnix(t) - testGetExternalGnsAmount(t) - testGetStakerGnsBalance(t) - testGetStakerEmissionGnsBalance(t) - testGetLastCalculatedBalance(t) - testGetLastCalculatedHeight(t) - testGetMintedGnsAmount(t) - testGetNumPoolTiers(t) - testGetTiersRatio(t) - testGetWarmUpPeriods(t) - testGetGetPositionsInternalWarmUpAmount(t) - - // create external incentive + getter tests - testCreateExternalIncentive_90_180(t) - testGetPositionsExternalWarmUpAmount(t) - testGetPositionsExternalLastCalculatedHeight(t) - testGetExternalLastCalculatedTimestamp3(t) - testGetNecessary(t) - testGetSingleData(t) - testGetPoolGnsData(t) - testGetPositionGnsData(t) - testGetPositionExternalData(t) -} - -func testInit(t *testing.T) { - t.Run("initial", func(t *testing.T) { - // init pool tiers - // tier 1 - delete(poolTiers, MUST_EXISTS_IN_TIER_1) - - poolTiers["gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100"] = InternalTier{ - tier: 1, - startTimestamp: time.Now().Unix(), - } - - std.TestSkipHeights(1) - - // override warm-up period for testing - warmUp[100] = 901 // 30m ~ - warmUp[70] = 301 // 10m ~ 30m - warmUp[50] = 151 // 5m ~ 10m - warmUp[30] = 1 // ~ 5m - }) -} - -func testDoubleMint(t *testing.T) { - t.Run("two mint(and distribute) in same block", func(t *testing.T) { - en.MintAndDistributeGns() - en.MintAndDistributeGns() - - std.TestSkipHeights(1) - }) -} - -func testPoolCreatePool(t *testing.T) { - t.Run("create pool", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()*3) - - pl.CreatePool(barPath, quxPath, 100, "79228162514264337593543950337") - pl.CreatePool(barPath, bazPath, 3000, "79228162514264337593543950337") - - std.TestSkipHeights(1) - }) -} - -func testMintBarQux100_1(t *testing.T) { - t.Run("mint position 01, bar:qux:100", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - - tokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - fee100, // fee - int32(-1000), // tickLower - int32(1000), // tickUpper - "50", // amount0Desired - "50", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - admin, - admin, - ) - std.TestSkipHeights(1) - }) -} - -func testMintBarQux100_2(t *testing.T) { - t.Run("mint position 02, bar:qux:100", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - - tokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - fee100, // fee - int32(-1000), // tickLower - int32(1000), // tickUpper - "300", // amount0Desired - "300", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - admin, - admin, - ) - std.TestSkipHeights(1) - }) -} - -func testStakeToken_1(t *testing.T) { - t.Run("stake token 01", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - gnft.Approve(a2u(GetOrigPkgAddr()), tid(1)) - StakeToken(1) - - gpi := GetPrintInfo() - uassert.Equal(t, gpi, `{"height":128,"time":1234567900,"gns":{"staker":0,"devOps":14269405,"communityPool":57077624,"govStaker":0,"protocolFee":200000000,"GnoswapAdmin":99999800000000},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","startTimestamp":1234567890,"tier":1,"numPoolSameTier":1,"poolReward":0,"position":[{"lpTokenId":1,"stakedHeight":128,"stakedTimestamp":1234567900,"stakedDuration":0,"fullAmount":0,"ratio":0,"warmUpAmount":0,"full30":0,"give30":0,"full50":0,"give50":0,"full70":0,"give70":0,"full100":0}]}]}`) - - std.TestSkipHeights(1) - }) -} - -func testGetPoolGns(t *testing.T) { - t.Run("staked 1, get pool gns", func(t *testing.T) { - jsonStr := GetPoolGns() - uassert.Equal(t, jsonStr, `[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","gnsAmount":"0"}]`) - }) -} - -func testGetPoolCurrentBlockGns(t *testing.T) { - t.Run("staked 1, get pool current block gns", func(t *testing.T) { - jsonStr := GetPoolCurrentBlockGns() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetPoolLastTmpGns(t *testing.T) { - t.Run("staked 1, get pool last tmp gns", func(t *testing.T) { - jsonStr := GetPoolLastTmpGns() - uassert.Equal(t, jsonStr, `[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","gnsAmount":"0"}]`) - }) -} - -func testGetPoolAccuGns(t *testing.T) { - t.Run("staked 1, get pool accu gns", func(t *testing.T) { - jsonStr := GetPoolAccuGns() - uassert.Equal(t, jsonStr, `[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","gnsAmount":"53510274"}]`) - }) -} - -func testGetPositionGns(t *testing.T) { - t.Run("staked 1, get position gns", func(t *testing.T) { - jsonStr := GetPositionGns() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetPositionLastGns(t *testing.T) { - t.Run("staked 1, get position last gns", func(t *testing.T) { - jsonStr := GetPositionLastGns() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetPositionLastExternal(t *testing.T) { - t.Run("staked 1, get position last external", func(t *testing.T) { - jsonStr := GetPositionLastExternal() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetExternalLastCalculatedTimestamp(t *testing.T) { - t.Run("staked 1, get external last calculated timestamp", func(t *testing.T) { - jsonStr := GetExternalLastCalculatedTimestamp() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetExternalGns(t *testing.T) { - t.Run("staked 1, get external gns", func(t *testing.T) { - jsonStr := GetExternalGns() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetPoolTotalStakedLiquidity(t *testing.T) { - t.Run("staked 1, get pool total staked liquidity", func(t *testing.T) { - jsonStr := GetPoolTotalStakedLiquidity() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetPositionsLiquidityRatio(t *testing.T) { - t.Run("staked 1, get positions liquidity ratio", func(t *testing.T) { - jsonStr := GetPositionsLiquidityRatio() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetPoolsPositions(t *testing.T) { - t.Run("staked 1, get pools positions", func(t *testing.T) { - jsonStr := GetPoolsPositions() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testStakeToken_2(t *testing.T) { - t.Run("stake token 02", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - gnft.Approve(a2u(GetOrigPkgAddr()), tid(2)) - StakeToken(2) - - gpi := GetPrintInfo() - uassert.Equal(t, gpi, `{"height":129,"time":1234567902,"gns":{"staker":10702055,"devOps":17123286,"communityPool":57791094,"govStaker":0,"protocolFee":200000000,"GnoswapAdmin":99999800000000},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","startTimestamp":1234567890,"tier":1,"numPoolSameTier":1,"poolReward":10702055,"position":[{"lpTokenId":1,"stakedHeight":128,"stakedTimestamp":1234567900,"stakedDuration":1,"fullAmount":10702055,"ratio":30,"warmUpAmount":3210616,"full30":10702055,"give30":3210616,"full50":0,"give50":0,"full70":0,"give70":0,"full100":0},{"lpTokenId":2,"stakedHeight":129,"stakedTimestamp":1234567902,"stakedDuration":0,"fullAmount":0,"ratio":0,"warmUpAmount":0,"full30":0,"give30":0,"full50":0,"give50":0,"full70":0,"give70":0,"full100":0}]}]}`) - - std.TestSkipHeights(1) - }) -} - -func testGetPoolGns2(t *testing.T) { - t.Run("staked 2, get pool gns", func(t *testing.T) { - jsonStr := GetPoolGns() - uassert.Equal(t, jsonStr, `[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","gnsAmount":"10702055"}]`) - }) -} - -func testGetPoolCurrentBlockGns2(t *testing.T) { - t.Run("staked 2, get pool current block gns", func(t *testing.T) { - jsonStr := GetPoolCurrentBlockGns() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetPoolLastTmpGns2(t *testing.T) { - t.Run("staked 2, get pool last tmp gns", func(t *testing.T) { - jsonStr := GetPoolLastTmpGns() - uassert.Equal(t, jsonStr, `[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","gnsAmount":"0"}]`) - }) -} - -func testGetPoolAccuGns2(t *testing.T) { - t.Run("staked 2, get pool accu gns", func(t *testing.T) { - jsonStr := GetPoolAccuGns() - uassert.Equal(t, jsonStr, `[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","gnsAmount":"64212329"}]`) - }) -} - -func testGetPositionGns2(t *testing.T) { - t.Run("staked 2, get position gns", func(t *testing.T) { - jsonStr := GetPositionGns() - uassert.Equal(t, jsonStr, `[{"tokenId":"1","gnsAmount":"10702055"}]`) - }) -} - -func testGetPositionLastGns2(t *testing.T) { - t.Run("staked 2, get position last gns", func(t *testing.T) { - jsonStr := GetPositionLastGns() - uassert.Equal(t, jsonStr, `[{"tokenId":"1","gnsAmount":"0"}]`) - }) -} - -func testGetPositionLastExternal2(t *testing.T) { - t.Run("staked 2, get position last external", func(t *testing.T) { - jsonStr := GetPositionLastExternal() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetExternalLastCalculatedTimestamp2(t *testing.T) { - t.Run("staked 2, get external last calculated timestamp", func(t *testing.T) { - jsonStr := GetExternalLastCalculatedTimestamp() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetExternalGns2(t *testing.T) { - t.Run("staked 2, get external gns", func(t *testing.T) { - jsonStr := GetExternalGns() - uassert.Equal(t, jsonStr, ``) - }) -} - -func testGetPoolTotalStakedLiquidity2(t *testing.T) { - t.Run("staked 2, get pool total staked liquidity", func(t *testing.T) { - jsonStr := GetPoolTotalStakedLiquidity() - uassert.Equal(t, jsonStr, `[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","totalStakedLiquidity":"1025"}]`) - }) -} - -func testGetPositionsLiquidityRatio2(t *testing.T) { - t.Run("staked 2, get positions liquidity ratio", func(t *testing.T) { - jsonStr := GetPositionsLiquidityRatio() - uassert.Equal(t, jsonStr, `[{"tokenId":"1","positionRatio":"79228162514264337593543950336"}]`) - }) -} - -func testGetPoolsPositions2(t *testing.T) { - t.Run("staked 2, get pools positions", func(t *testing.T) { - jsonStr := GetPoolsPositions() - uassert.Equal(t, jsonStr, `[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tokenIds":["1"]}]`) - }) -} - -// START, ETC GETTER TEST -func testGetHeight(t *testing.T) { - t.Run("get height", func(t *testing.T) { - res := GetHeight() - uassert.Equal(t, res, int64(130)) - }) -} - -func testGetTimeNowUnix(t *testing.T) { - t.Run("get time now unix", func(t *testing.T) { - res := GetTimeNowUnix() - uassert.Equal(t, res, int64(1234567904)) - }) -} - -func testGetExternalGnsAmount(t *testing.T) { - t.Run("get external gns amount", func(t *testing.T) { - res := GetExternalGnsAmount() - uassert.Equal(t, res, uint64(0)) - }) -} - -func testGetStakerGnsBalance(t *testing.T) { - t.Run("get staker gns balance", func(t *testing.T) { - res := GetStakerGnsBalance() - uassert.Equal(t, res, uint64(21404110)) - }) -} - -func testGetStakerEmissionGnsBalance(t *testing.T) { - t.Run("get staker emission gns balance", func(t *testing.T) { - res := GetStakerEmissionGnsBalance() - uassert.Equal(t, res, uint64(21404110)) - }) -} - -func testGetLastCalculatedBalance(t *testing.T) { - t.Run("get last calculated balance", func(t *testing.T) { - res := GetLastCalculatedBalance() - uassert.Equal(t, res, uint64(10702055)) - }) -} - -func testGetLastCalculatedHeight(t *testing.T) { - t.Run("get last calculated height", func(t *testing.T) { - res := GetLastCalculatedHeight() - uassert.Equal(t, res, int64(129)) - }) -} - -func testGetMintedGnsAmount(t *testing.T) { - t.Run("get minted gns amount", func(t *testing.T) { - res := GetMintedGnsAmount() - uassert.Equal(t, res, uint64(10702055)) - }) -} - -func testGetNumPoolTiers(t *testing.T) { - t.Run("get num pool tiers", func(t *testing.T) { - res := GetNumPoolTiers() - uassert.Equal(t, res, `1*STAKER*0*STAKER*0`) - }) -} - -func testGetTiersRatio(t *testing.T) { - t.Run("get tiers ratio", func(t *testing.T) { - res := GetTiersRatio() - uassert.Equal(t, res, `100*STAKER*0*STAKER*0`) - }) -} - -func testGetWarmUpPeriods(t *testing.T) { - t.Run("get warm up periods", func(t *testing.T) { - res := GetWarmUpPeriods() - uassert.Equal(t, res, `1*STAKER*151*STAKER*301*STAKER*901`) - }) -} - -func testGetGetPositionsInternalWarmUpAmount(t *testing.T) { - t.Run("get positions internal warm up amount", func(t *testing.T) { - res := GetPositionsInternalWarmUpAmount() - uassert.Equal(t, res, `[{"tokenId":"1","full30":"10702055","give30":"3210616","left30":"7491439","full50":"0","give50":"0","left50":"0","full70":"0","give70":"0","left70":"0","full100":"0"},{"tokenId":"2","full30":"0","give30":"0","left30":"0","full50":"0","give50":"0","left50":"0","full70":"0","give70":"0","left70":"0","full100":"0"}]`) - }) -} - -// CREATE EXTERNAL INCENTIVE -func testCreateExternalIncentive_90_180(t *testing.T) { - t.Run("create external incentive 90 days", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - bar.Approve(a2u(consts.STAKER_ADDR), consts.UINT64_MAX) - gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount) - - CreateExternalIncentive( - "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100", // targetPoolPath string, - barPath, // rewardToken string, // token path should be registered - "900000000", // _rewardAmount string, - 1234569600, - 1234569600+TIMESTAMP_90DAYS, - ) - }) - - t.Run("create external incentive 180 days", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - qux.Approve(a2u(consts.STAKER_ADDR), consts.UINT64_MAX) - gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount) - - CreateExternalIncentive( - "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100", // targetPoolPath string, - quxPath, // rewardToken string, // token path should be registered - "1230000000", // _rewardAmount string, - 1234569600, - 1234569600+TIMESTAMP_180DAYS, - ) - }) -} - -func testGetPositionsExternalWarmUpAmount(t *testing.T) { - t.Run("get positions external warm up amount", func(t *testing.T) { - std.TestSkipHeights(1000) - - en.MintAndDistributeGns() - if consts.EMISSION_REFACTORED { - CalcPoolPositionRefactor() - } else { - CalcPoolPosition() - } - - res := GetPositionsExternalWarmUpAmount() - uassert.Equal(t, res, `[{"tokenId":"1","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEzMA==","full30":"4959","give30":"1487","left30":"3472","full50":"66","give50":"33","left50":"33","full70":"0","give70":"0","left70":"0","full100":"0"},{"tokenId":"1","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9xdXg6MTIzNDU2OTYwMDoxMjUwMTIxNjAwOjEzMA==","full30":"3428","give30":"1028","left30":"2400","full50":"6","give50":"3","left50":"3","full70":"0","give70":"0","left70":"0","full100":"0"},{"tokenId":"2","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEzMA==","full30":"29762","give30":"8928","left30":"20834","full50":"396","give50":"198","left50":"198","full70":"0","give70":"0","left70":"0","full100":"0"},{"tokenId":"2","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9xdXg6MTIzNDU2OTYwMDoxMjUwMTIxNjAwOjEzMA==","full30":"20572","give30":"6171","left30":"14401","full50":"36","give50":"18","left50":"18","full70":"0","give70":"0","left70":"0","full100":"0"}]`) - }) -} - -func testGetPositionsExternalLastCalculatedHeight(t *testing.T) { - t.Run("get positions external last calculated height", func(t *testing.T) { - res := GetPositionsExternalLastCalculatedHeight() - uassert.Equal(t, res, `[{"tokenId":"1","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEzMA==","lastCalculatedHeight":"1130"},{"tokenId":"1","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9xdXg6MTIzNDU2OTYwMDoxMjUwMTIxNjAwOjEzMA==","lastCalculatedHeight":"1130"},{"tokenId":"2","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEzMA==","lastCalculatedHeight":"1130"},{"tokenId":"2","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9xdXg6MTIzNDU2OTYwMDoxMjUwMTIxNjAwOjEzMA==","lastCalculatedHeight":"1130"}]`) - }) -} - -func testGetExternalLastCalculatedTimestamp3(t *testing.T) { - t.Run("get external last calculated timestamp", func(t *testing.T) { - jsonStr := GetExternalLastCalculatedTimestamp() - uassert.Equal(t, jsonStr, `[{"incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEzMA==","lastCalculatedTimestamp":"1234569904"},{"incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9xdXg6MTIzNDU2OTYwMDoxMjUwMTIxNjAwOjEzMA==","lastCalculatedTimestamp":"1234569904"}]`) - }) -} - -func testGetNecessary(t *testing.T) { - t.Run("get necessary", func(t *testing.T) { - res := GetNecessary() - uassert.Equal(t, res, `{"height":"1130","now":"1234569904","blockGenerationInterval":"2","lastCalculatedHeight":"1130","lastCalculatedBalance":"10723458610","externalGnsBalance":"0","depositGnsAmount":"2000000000","gnsBalance":"12723458610","numPoolTiers":"1*STAKER*0*STAKER*0","tiersRatio":"100*STAKER*0*STAKER*0","warmUpPeriods":"1*STAKER*151*STAKER*301*STAKER*901","poolGns":"W3sicG9vbFBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMCIsImduc0Ftb3VudCI6IjEwNzIzNDU4NjEwIn1d","poolAccuGns":"W3sicG9vbFBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMCIsImduc0Ftb3VudCI6IjEwNzc2OTY4ODg0In1d","poolLastTmpGns":"W3sicG9vbFBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMCIsImduc0Ftb3VudCI6IjEyIn1d","positionGns":"W3sidG9rZW5JZCI6IjEiLCJnbnNBbW91bnQiOiIxNTQwODgyNTc5In0seyJ0b2tlbklkIjoiMiIsImduc0Ftb3VudCI6IjkxODI1NzYwMTkifV0=","positionLastGns":"W3sidG9rZW5JZCI6IjEiLCJnbnNBbW91bnQiOiIxMjIzMDcwNiJ9LHsidG9rZW5JZCI6IjIiLCJnbnNBbW91bnQiOiI5MTczNDAzIn1d","positionsInternalWarmUpAmount":"W3sidG9rZW5JZCI6IjEiLCJmdWxsMzAiOiIyMzg0NzExODMiLCJnaXZlMzAiOiI3MTU0MTM1NCIsImxlZnQzMCI6IjE2NjkyOTgyOSIsImZ1bGw1MCI6IjIyOTI5Nzc4MCIsImdpdmU1MCI6IjExNDY0ODg5MCIsImxlZnQ1MCI6IjExNDY0ODg5MCIsImZ1bGw3MCI6IjkxNzE5MTEyMyIsImdpdmU3MCI6IjY0MjAzMzc4NiIsImxlZnQ3MCI6IjI3NTE1NzMzNyIsImZ1bGwxMDAiOiIxNTU5MjI0OTEifSx7InRva2VuSWQiOiIyIiwiZnVsbDMwIjoiMTM3NjAxMDM5MiIsImdpdmUzMCI6IjQxMjgwMzExNiIsImxlZnQzMCI6Ijk2MzIwNzI3NiIsImZ1bGw1MCI6IjEzNzYwMTAzOTIiLCJnaXZlNTAiOiI2ODgwMDUxOTYiLCJsZWZ0NTAiOiI2ODgwMDUxOTYiLCJmdWxsNzAiOiI1NTA0MDQxNTY5IiwiZ2l2ZTcwIjoiMzg1MjgyOTA5OCIsImxlZnQ3MCI6IjE2NTEyMTI0NzEiLCJmdWxsMTAwIjoiOTI2NTEzNjY0In1d","positionExternal":"W3sidG9rZW5JZCI6IjEiLCJleHRlcm5hbHMiOlt7ImluY2VudGl2ZUlkIjoiWnpGc2JYWnljbkp5TkdWeU1uVnpPRFJvTWpjek1uTnlkVGMyWXpsNmJESnVkbXR1YUdFNFl6cG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2WjI1dkxteGhibVF2Y2k5dmJtSnNiMk12Y1hWNE9qRXdNRHBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNk1USXpORFUyT1RZd01Eb3hNalF5TXpRMU5qQXdPakV6TUE9PSIsInBvb2xQYXRoIjoiZ25vLmxhbmQvci9vbmJsb2MvYmFyOmduby5sYW5kL3Ivb25ibG9jL3F1eDoxMDAiLCJ0b2tlblBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9iYXIiLCJ0b2tlbkFtb3VudFg5NiI6IjM5ODE4MTI5OTQxNzk1Mzk5MDMzMDM2ODYzNDQ1MTgzNCIsInRva2VuQW1vdW50RnVsbCI6IjAiLCJ0b2tlbkFtb3VudFRvR2l2ZSI6IjAifSx7ImluY2VudGl2ZUlkIjoiWnpGc2JYWnljbkp5TkdWeU1uVnpPRFJvTWpjek1uTnlkVGMyWXpsNmJESnVkbXR1YUdFNFl6cG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2WjI1dkxteGhibVF2Y2k5dmJtSnNiMk12Y1hWNE9qRXdNRHBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTl4ZFhnNk1USXpORFUyT1RZd01Eb3hNalV3TVRJeE5qQXdPakV6TUE9PSIsInBvb2xQYXRoIjoiZ25vLmxhbmQvci9vbmJsb2MvYmFyOmduby5sYW5kL3Ivb25ibG9jL3F1eDoxMDAiLCJ0b2tlblBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9xdXgiLCJ0b2tlbkFtb3VudFg5NiI6IjI3MjA5MDU1NDYwMjI2ODU2MDA1OTA4NTIzMzU0MjA5NiIsInRva2VuQW1vdW50RnVsbCI6IjAiLCJ0b2tlbkFtb3VudFRvR2l2ZSI6IjAifV19LHsidG9rZW5JZCI6IjIiLCJleHRlcm5hbHMiOlt7ImluY2VudGl2ZUlkIjoiWnpGc2JYWnljbkp5TkdWeU1uVnpPRFJvTWpjek1uTnlkVGMyWXpsNmJESnVkbXR1YUdFNFl6cG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2WjI1dkxteGhibVF2Y2k5dmJtSnNiMk12Y1hWNE9qRXdNRHBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNk1USXpORFUyT1RZd01Eb3hNalF5TXpRMU5qQXdPakV6TUE9PSIsInBvb2xQYXRoIjoiZ25vLmxhbmQvci9vbmJsb2MvYmFyOmduby5sYW5kL3Ivb25ibG9jL3F1eDoxMDAiLCJ0b2tlblBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9iYXIiLCJ0b2tlbkFtb3VudFg5NiI6IjIzODk0NzYyNjc3NDA3MjYyNDM5NDA1ODA3MDI3MzEzNzUiLCJ0b2tlbkFtb3VudEZ1bGwiOiIwIiwidG9rZW5BbW91bnRUb0dpdmUiOiIwIn0seyJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5eGRYZzZNVEl6TkRVMk9UWXdNRG94TWpVd01USXhOakF3T2pFek1BPT0iLCJwb29sUGF0aCI6Imduby5sYW5kL3Ivb25ibG9jL2Jhcjpnbm8ubGFuZC9yL29uYmxvYy9xdXg6MTAwIiwidG9rZW5QYXRoIjoiZ25vLmxhbmQvci9vbmJsb2MvcXV4IiwidG9rZW5BbW91bnRYOTYiOiIxNjMyODA4NzgyOTU2MTYyOTMzMzU5Mzk2ODEzNTMzMTYyIiwidG9rZW5BbW91bnRGdWxsIjoiMCIsInRva2VuQW1vdW50VG9HaXZlIjoiMCJ9XX1d","positionLastExternal":"W3sidG9rZW5JZCI6IjEiLCJsYXN0RXh0ZXJuYWxzIjpbeyJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZNVEl6TkRVMk9UWXdNRG94TWpReU16UTFOakF3T2pFek1BPT0iLCJsYXN0UmV3YXJkQW1vdW50IjoiMCJ9LHsiaW5jZW50aXZlSWQiOiJaekZzYlhaeWNuSnlOR1Z5TW5Wek9EUm9NamN6TW5OeWRUYzJZemw2YkRKdWRtdHVhR0U0WXpwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZaMjV2TG14aGJtUXZjaTl2Ym1Kc2IyTXZjWFY0T2pFd01EcG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OXhkWGc2TVRJek5EVTJPVFl3TURveE1qVXdNVEl4TmpBd09qRXpNQT09IiwibGFzdFJld2FyZEFtb3VudCI6IjAifV19LHsidG9rZW5JZCI6IjIiLCJsYXN0RXh0ZXJuYWxzIjpbeyJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZNVEl6TkRVMk9UWXdNRG94TWpReU16UTFOakF3T2pFek1BPT0iLCJsYXN0UmV3YXJkQW1vdW50IjoiMCJ9LHsiaW5jZW50aXZlSWQiOiJaekZzYlhaeWNuSnlOR1Z5TW5Wek9EUm9NamN6TW5OeWRUYzJZemw2YkRKdWRtdHVhR0U0WXpwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZaMjV2TG14aGJtUXZjaTl2Ym1Kc2IyTXZjWFY0T2pFd01EcG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OXhkWGc2TVRJek5EVTJPVFl3TURveE1qVXdNVEl4TmpBd09qRXpNQT09IiwibGFzdFJld2FyZEFtb3VudCI6IjAifV19XQ==","positionsExternalWarmUpAmount":"W3sidG9rZW5JZCI6IjEiLCJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZNVEl6TkRVMk9UWXdNRG94TWpReU16UTFOakF3T2pFek1BPT0iLCJmdWxsMzAiOiI0OTU5IiwiZ2l2ZTMwIjoiMTQ4NyIsImxlZnQzMCI6IjM0NzIiLCJmdWxsNTAiOiI2NiIsImdpdmU1MCI6IjMzIiwibGVmdDUwIjoiMzMiLCJmdWxsNzAiOiIwIiwiZ2l2ZTcwIjoiMCIsImxlZnQ3MCI6IjAiLCJmdWxsMTAwIjoiMCJ9LHsidG9rZW5JZCI6IjEiLCJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5eGRYZzZNVEl6TkRVMk9UWXdNRG94TWpVd01USXhOakF3T2pFek1BPT0iLCJmdWxsMzAiOiIzNDI4IiwiZ2l2ZTMwIjoiMTAyOCIsImxlZnQzMCI6IjI0MDAiLCJmdWxsNTAiOiI2IiwiZ2l2ZTUwIjoiMyIsImxlZnQ1MCI6IjMiLCJmdWxsNzAiOiIwIiwiZ2l2ZTcwIjoiMCIsImxlZnQ3MCI6IjAiLCJmdWxsMTAwIjoiMCJ9LHsidG9rZW5JZCI6IjIiLCJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZNVEl6TkRVMk9UWXdNRG94TWpReU16UTFOakF3T2pFek1BPT0iLCJmdWxsMzAiOiIyOTc2MiIsImdpdmUzMCI6Ijg5MjgiLCJsZWZ0MzAiOiIyMDgzNCIsImZ1bGw1MCI6IjM5NiIsImdpdmU1MCI6IjE5OCIsImxlZnQ1MCI6IjE5OCIsImZ1bGw3MCI6IjAiLCJnaXZlNzAiOiIwIiwibGVmdDcwIjoiMCIsImZ1bGwxMDAiOiIwIn0seyJ0b2tlbklkIjoiMiIsImluY2VudGl2ZUlkIjoiWnpGc2JYWnljbkp5TkdWeU1uVnpPRFJvTWpjek1uTnlkVGMyWXpsNmJESnVkbXR1YUdFNFl6cG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2WjI1dkxteGhibVF2Y2k5dmJtSnNiMk12Y1hWNE9qRXdNRHBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTl4ZFhnNk1USXpORFUyT1RZd01Eb3hNalV3TVRJeE5qQXdPakV6TUE9PSIsImZ1bGwzMCI6IjIwNTcyIiwiZ2l2ZTMwIjoiNjE3MSIsImxlZnQzMCI6IjE0NDAxIiwiZnVsbDUwIjoiMzYiLCJnaXZlNTAiOiIxOCIsImxlZnQ1MCI6IjE4IiwiZnVsbDcwIjoiMCIsImdpdmU3MCI6IjAiLCJsZWZ0NzAiOiIwIiwiZnVsbDEwMCI6IjAifV0=","positionsExternalLastCalculatedHeight":"W3sidG9rZW5JZCI6IjEiLCJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZNVEl6TkRVMk9UWXdNRG94TWpReU16UTFOakF3T2pFek1BPT0iLCJsYXN0Q2FsY3VsYXRlZEhlaWdodCI6IjExMzAifSx7InRva2VuSWQiOiIxIiwiaW5jZW50aXZlSWQiOiJaekZzYlhaeWNuSnlOR1Z5TW5Wek9EUm9NamN6TW5OeWRUYzJZemw2YkRKdWRtdHVhR0U0WXpwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZaMjV2TG14aGJtUXZjaTl2Ym1Kc2IyTXZjWFY0T2pFd01EcG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OXhkWGc2TVRJek5EVTJPVFl3TURveE1qVXdNVEl4TmpBd09qRXpNQT09IiwibGFzdENhbGN1bGF0ZWRIZWlnaHQiOiIxMTMwIn0seyJ0b2tlbklkIjoiMiIsImluY2VudGl2ZUlkIjoiWnpGc2JYWnljbkp5TkdWeU1uVnpPRFJvTWpjek1uTnlkVGMyWXpsNmJESnVkbXR1YUdFNFl6cG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2WjI1dkxteGhibVF2Y2k5dmJtSnNiMk12Y1hWNE9qRXdNRHBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNk1USXpORFUyT1RZd01Eb3hNalF5TXpRMU5qQXdPakV6TUE9PSIsImxhc3RDYWxjdWxhdGVkSGVpZ2h0IjoiMTEzMCJ9LHsidG9rZW5JZCI6IjIiLCJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5eGRYZzZNVEl6TkRVMk9UWXdNRG94TWpVd01USXhOakF3T2pFek1BPT0iLCJsYXN0Q2FsY3VsYXRlZEhlaWdodCI6IjExMzAifV0=","externalLastCalculatedTimestamp":"W3siaW5jZW50aXZlSWQiOiJaekZzYlhaeWNuSnlOR1Z5TW5Wek9EUm9NamN6TW5OeWRUYzJZemw2YkRKdWRtdHVhR0U0WXpwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZaMjV2TG14aGJtUXZjaTl2Ym1Kc2IyTXZjWFY0T2pFd01EcG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2TVRJek5EVTJPVFl3TURveE1qUXlNelExTmpBd09qRXpNQT09IiwibGFzdENhbGN1bGF0ZWRUaW1lc3RhbXAiOiIxMjM0NTY5OTA0In0seyJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5eGRYZzZNVEl6TkRVMk9UWXdNRG94TWpVd01USXhOakF3T2pFek1BPT0iLCJsYXN0Q2FsY3VsYXRlZFRpbWVzdGFtcCI6IjEyMzQ1Njk5MDQifV0="}`) - }) -} - -func testGetSingleData(t *testing.T) { - t.Run("get single data", func(t *testing.T) { - res := GetSingleData() - uassert.Equal(t, res, `{"height":"1130","now":"1234569904","blockGenerationInterval":"2","lastCalculatedHeight":"1130","lastCalculatedBalance":"10723458610","externalGnsBalance":"0","depositGnsAmount":"2000000000","gnsBalance":"12723458610","numPoolTiers":"1*STAKER*0*STAKER*0","tiersRatio":"100*STAKER*0*STAKER*0","warmUpPeriods":"1*STAKER*151*STAKER*301*STAKER*901"}`) - }) -} - -func testGetPoolGnsData(t *testing.T) { - t.Run("get pool gns data", func(t *testing.T) { - res := GetPoolGnsData() - uassert.Equal(t, res, `{"height":"1130","now":"1234569904","poolGns":"W3sicG9vbFBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMCIsImduc0Ftb3VudCI6IjEwNzIzNDU4NjEwIn1d","poolAccuGns":"W3sicG9vbFBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMCIsImduc0Ftb3VudCI6IjEwNzc2OTY4ODg0In1d","poolLastTmpGns":"W3sicG9vbFBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMCIsImduc0Ftb3VudCI6IjEyIn1d"}`) - }) -} - -func testGetPositionGnsData(t *testing.T) { - t.Run("get position gns data", func(t *testing.T) { - res := GetPositionGnsData() - uassert.Equal(t, res, `{"height":"1130","now":"1234569904","positionGns":"W3sidG9rZW5JZCI6IjEiLCJnbnNBbW91bnQiOiIxNTQwODgyNTc5In0seyJ0b2tlbklkIjoiMiIsImduc0Ftb3VudCI6IjkxODI1NzYwMTkifV0=","positionLastGns":"W3sidG9rZW5JZCI6IjEiLCJnbnNBbW91bnQiOiIxMjIzMDcwNiJ9LHsidG9rZW5JZCI6IjIiLCJnbnNBbW91bnQiOiI5MTczNDAzIn1d","positionsInternalWarmUpAmount":"W3sidG9rZW5JZCI6IjEiLCJmdWxsMzAiOiIyMzg0NzExODMiLCJnaXZlMzAiOiI3MTU0MTM1NCIsImxlZnQzMCI6IjE2NjkyOTgyOSIsImZ1bGw1MCI6IjIyOTI5Nzc4MCIsImdpdmU1MCI6IjExNDY0ODg5MCIsImxlZnQ1MCI6IjExNDY0ODg5MCIsImZ1bGw3MCI6IjkxNzE5MTEyMyIsImdpdmU3MCI6IjY0MjAzMzc4NiIsImxlZnQ3MCI6IjI3NTE1NzMzNyIsImZ1bGwxMDAiOiIxNTU5MjI0OTEifSx7InRva2VuSWQiOiIyIiwiZnVsbDMwIjoiMTM3NjAxMDM5MiIsImdpdmUzMCI6IjQxMjgwMzExNiIsImxlZnQzMCI6Ijk2MzIwNzI3NiIsImZ1bGw1MCI6IjEzNzYwMTAzOTIiLCJnaXZlNTAiOiI2ODgwMDUxOTYiLCJsZWZ0NTAiOiI2ODgwMDUxOTYiLCJmdWxsNzAiOiI1NTA0MDQxNTY5IiwiZ2l2ZTcwIjoiMzg1MjgyOTA5OCIsImxlZnQ3MCI6IjE2NTEyMTI0NzEiLCJmdWxsMTAwIjoiOTI2NTEzNjY0In1d"}`) - }) -} - -func testGetPositionExternalData(t *testing.T) { - t.Run("get position external data", func(t *testing.T) { - res := GetPositionExternalData() - uassert.Equal(t, res, `{"height":"1130","now":"1234569904","positionExternal":"W3sidG9rZW5JZCI6IjEiLCJleHRlcm5hbHMiOlt7ImluY2VudGl2ZUlkIjoiWnpGc2JYWnljbkp5TkdWeU1uVnpPRFJvTWpjek1uTnlkVGMyWXpsNmJESnVkbXR1YUdFNFl6cG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2WjI1dkxteGhibVF2Y2k5dmJtSnNiMk12Y1hWNE9qRXdNRHBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNk1USXpORFUyT1RZd01Eb3hNalF5TXpRMU5qQXdPakV6TUE9PSIsInBvb2xQYXRoIjoiZ25vLmxhbmQvci9vbmJsb2MvYmFyOmduby5sYW5kL3Ivb25ibG9jL3F1eDoxMDAiLCJ0b2tlblBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9iYXIiLCJ0b2tlbkFtb3VudFg5NiI6IjM5ODE4MTI5OTQxNzk1Mzk5MDMzMDM2ODYzNDQ1MTgzNCIsInRva2VuQW1vdW50RnVsbCI6IjAiLCJ0b2tlbkFtb3VudFRvR2l2ZSI6IjAifSx7ImluY2VudGl2ZUlkIjoiWnpGc2JYWnljbkp5TkdWeU1uVnpPRFJvTWpjek1uTnlkVGMyWXpsNmJESnVkbXR1YUdFNFl6cG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2WjI1dkxteGhibVF2Y2k5dmJtSnNiMk12Y1hWNE9qRXdNRHBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTl4ZFhnNk1USXpORFUyT1RZd01Eb3hNalV3TVRJeE5qQXdPakV6TUE9PSIsInBvb2xQYXRoIjoiZ25vLmxhbmQvci9vbmJsb2MvYmFyOmduby5sYW5kL3Ivb25ibG9jL3F1eDoxMDAiLCJ0b2tlblBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9xdXgiLCJ0b2tlbkFtb3VudFg5NiI6IjI3MjA5MDU1NDYwMjI2ODU2MDA1OTA4NTIzMzU0MjA5NiIsInRva2VuQW1vdW50RnVsbCI6IjAiLCJ0b2tlbkFtb3VudFRvR2l2ZSI6IjAifV19LHsidG9rZW5JZCI6IjIiLCJleHRlcm5hbHMiOlt7ImluY2VudGl2ZUlkIjoiWnpGc2JYWnljbkp5TkdWeU1uVnpPRFJvTWpjek1uTnlkVGMyWXpsNmJESnVkbXR1YUdFNFl6cG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2WjI1dkxteGhibVF2Y2k5dmJtSnNiMk12Y1hWNE9qRXdNRHBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNk1USXpORFUyT1RZd01Eb3hNalF5TXpRMU5qQXdPakV6TUE9PSIsInBvb2xQYXRoIjoiZ25vLmxhbmQvci9vbmJsb2MvYmFyOmduby5sYW5kL3Ivb25ibG9jL3F1eDoxMDAiLCJ0b2tlblBhdGgiOiJnbm8ubGFuZC9yL29uYmxvYy9iYXIiLCJ0b2tlbkFtb3VudFg5NiI6IjIzODk0NzYyNjc3NDA3MjYyNDM5NDA1ODA3MDI3MzEzNzUiLCJ0b2tlbkFtb3VudEZ1bGwiOiIwIiwidG9rZW5BbW91bnRUb0dpdmUiOiIwIn0seyJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5eGRYZzZNVEl6TkRVMk9UWXdNRG94TWpVd01USXhOakF3T2pFek1BPT0iLCJwb29sUGF0aCI6Imduby5sYW5kL3Ivb25ibG9jL2Jhcjpnbm8ubGFuZC9yL29uYmxvYy9xdXg6MTAwIiwidG9rZW5QYXRoIjoiZ25vLmxhbmQvci9vbmJsb2MvcXV4IiwidG9rZW5BbW91bnRYOTYiOiIxNjMyODA4NzgyOTU2MTYyOTMzMzU5Mzk2ODEzNTMzMTYyIiwidG9rZW5BbW91bnRGdWxsIjoiMCIsInRva2VuQW1vdW50VG9HaXZlIjoiMCJ9XX1d","positionLastExternal":"W3sidG9rZW5JZCI6IjEiLCJsYXN0RXh0ZXJuYWxzIjpbeyJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZNVEl6TkRVMk9UWXdNRG94TWpReU16UTFOakF3T2pFek1BPT0iLCJsYXN0UmV3YXJkQW1vdW50IjoiMCJ9LHsiaW5jZW50aXZlSWQiOiJaekZzYlhaeWNuSnlOR1Z5TW5Wek9EUm9NamN6TW5OeWRUYzJZemw2YkRKdWRtdHVhR0U0WXpwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZaMjV2TG14aGJtUXZjaTl2Ym1Kc2IyTXZjWFY0T2pFd01EcG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OXhkWGc2TVRJek5EVTJPVFl3TURveE1qVXdNVEl4TmpBd09qRXpNQT09IiwibGFzdFJld2FyZEFtb3VudCI6IjAifV19LHsidG9rZW5JZCI6IjIiLCJsYXN0RXh0ZXJuYWxzIjpbeyJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZNVEl6TkRVMk9UWXdNRG94TWpReU16UTFOakF3T2pFek1BPT0iLCJsYXN0UmV3YXJkQW1vdW50IjoiMCJ9LHsiaW5jZW50aXZlSWQiOiJaekZzYlhaeWNuSnlOR1Z5TW5Wek9EUm9NamN6TW5OeWRUYzJZemw2YkRKdWRtdHVhR0U0WXpwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZaMjV2TG14aGJtUXZjaTl2Ym1Kc2IyTXZjWFY0T2pFd01EcG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OXhkWGc2TVRJek5EVTJPVFl3TURveE1qVXdNVEl4TmpBd09qRXpNQT09IiwibGFzdFJld2FyZEFtb3VudCI6IjAifV19XQ==","positionsExternalWarmUpAmount":"W3sidG9rZW5JZCI6IjEiLCJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZNVEl6TkRVMk9UWXdNRG94TWpReU16UTFOakF3T2pFek1BPT0iLCJmdWxsMzAiOiI0OTU5IiwiZ2l2ZTMwIjoiMTQ4NyIsImxlZnQzMCI6IjM0NzIiLCJmdWxsNTAiOiI2NiIsImdpdmU1MCI6IjMzIiwibGVmdDUwIjoiMzMiLCJmdWxsNzAiOiIwIiwiZ2l2ZTcwIjoiMCIsImxlZnQ3MCI6IjAiLCJmdWxsMTAwIjoiMCJ9LHsidG9rZW5JZCI6IjEiLCJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5eGRYZzZNVEl6TkRVMk9UWXdNRG94TWpVd01USXhOakF3T2pFek1BPT0iLCJmdWxsMzAiOiIzNDI4IiwiZ2l2ZTMwIjoiMTAyOCIsImxlZnQzMCI6IjI0MDAiLCJmdWxsNTAiOiI2IiwiZ2l2ZTUwIjoiMyIsImxlZnQ1MCI6IjMiLCJmdWxsNzAiOiIwIiwiZ2l2ZTcwIjoiMCIsImxlZnQ3MCI6IjAiLCJmdWxsMTAwIjoiMCJ9LHsidG9rZW5JZCI6IjIiLCJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZNVEl6TkRVMk9UWXdNRG94TWpReU16UTFOakF3T2pFek1BPT0iLCJmdWxsMzAiOiIyOTc2MiIsImdpdmUzMCI6Ijg5MjgiLCJsZWZ0MzAiOiIyMDgzNCIsImZ1bGw1MCI6IjM5NiIsImdpdmU1MCI6IjE5OCIsImxlZnQ1MCI6IjE5OCIsImZ1bGw3MCI6IjAiLCJnaXZlNzAiOiIwIiwibGVmdDcwIjoiMCIsImZ1bGwxMDAiOiIwIn0seyJ0b2tlbklkIjoiMiIsImluY2VudGl2ZUlkIjoiWnpGc2JYWnljbkp5TkdWeU1uVnpPRFJvTWpjek1uTnlkVGMyWXpsNmJESnVkbXR1YUdFNFl6cG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2WjI1dkxteGhibVF2Y2k5dmJtSnNiMk12Y1hWNE9qRXdNRHBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTl4ZFhnNk1USXpORFUyT1RZd01Eb3hNalV3TVRJeE5qQXdPakV6TUE9PSIsImZ1bGwzMCI6IjIwNTcyIiwiZ2l2ZTMwIjoiNjE3MSIsImxlZnQzMCI6IjE0NDAxIiwiZnVsbDUwIjoiMzYiLCJnaXZlNTAiOiIxOCIsImxlZnQ1MCI6IjE4IiwiZnVsbDcwIjoiMCIsImdpdmU3MCI6IjAiLCJsZWZ0NzAiOiIwIiwiZnVsbDEwMCI6IjAifV0=","positionsExternalLastCalculatedHeight":"W3sidG9rZW5JZCI6IjEiLCJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZNVEl6TkRVMk9UWXdNRG94TWpReU16UTFOakF3T2pFek1BPT0iLCJsYXN0Q2FsY3VsYXRlZEhlaWdodCI6IjExMzAifSx7InRva2VuSWQiOiIxIiwiaW5jZW50aXZlSWQiOiJaekZzYlhaeWNuSnlOR1Z5TW5Wek9EUm9NamN6TW5OeWRUYzJZemw2YkRKdWRtdHVhR0U0WXpwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZaMjV2TG14aGJtUXZjaTl2Ym1Kc2IyTXZjWFY0T2pFd01EcG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OXhkWGc2TVRJek5EVTJPVFl3TURveE1qVXdNVEl4TmpBd09qRXpNQT09IiwibGFzdENhbGN1bGF0ZWRIZWlnaHQiOiIxMTMwIn0seyJ0b2tlbklkIjoiMiIsImluY2VudGl2ZUlkIjoiWnpGc2JYWnljbkp5TkdWeU1uVnpPRFJvTWpjek1uTnlkVGMyWXpsNmJESnVkbXR1YUdFNFl6cG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2WjI1dkxteGhibVF2Y2k5dmJtSnNiMk12Y1hWNE9qRXdNRHBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNk1USXpORFUyT1RZd01Eb3hNalF5TXpRMU5qQXdPakV6TUE9PSIsImxhc3RDYWxjdWxhdGVkSGVpZ2h0IjoiMTEzMCJ9LHsidG9rZW5JZCI6IjIiLCJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5eGRYZzZNVEl6TkRVMk9UWXdNRG94TWpVd01USXhOakF3T2pFek1BPT0iLCJsYXN0Q2FsY3VsYXRlZEhlaWdodCI6IjExMzAifV0=","externalLastCalculatedTimestamp":"W3siaW5jZW50aXZlSWQiOiJaekZzYlhaeWNuSnlOR1Z5TW5Wek9EUm9NamN6TW5OeWRUYzJZemw2YkRKdWRtdHVhR0U0WXpwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5aVlYSTZaMjV2TG14aGJtUXZjaTl2Ym1Kc2IyTXZjWFY0T2pFd01EcG5ibTh1YkdGdVpDOXlMMjl1WW14dll5OWlZWEk2TVRJek5EVTJPVFl3TURveE1qUXlNelExTmpBd09qRXpNQT09IiwibGFzdENhbGN1bGF0ZWRUaW1lc3RhbXAiOiIxMjM0NTY5OTA0In0seyJpbmNlbnRpdmVJZCI6Ilp6RnNiWFp5Y25KeU5HVnlNblZ6T0RSb01qY3pNbk55ZFRjMll6bDZiREp1ZG10dWFHRTRZenBuYm04dWJHRnVaQzl5TDI5dVlteHZZeTlpWVhJNloyNXZMbXhoYm1RdmNpOXZibUpzYjJNdmNYVjRPakV3TURwbmJtOHViR0Z1WkM5eUwyOXVZbXh2WXk5eGRYZzZNVEl6TkRVMk9UWXdNRG94TWpVd01USXhOakF3T2pFek1BPT0iLCJsYXN0Q2FsY3VsYXRlZFRpbWVzdGFtcCI6IjEyMzQ1Njk5MDQifV0="}`) - }) -} diff --git a/staker/__TEST_staker_short_warmup_period_external_10_test.gnoA b/staker/__TEST_staker_short_warmup_period_external_10_test.gnoA index 1bd5644f5..4a77757fd 100644 --- a/staker/__TEST_staker_short_warmup_period_external_10_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_external_10_test.gnoA @@ -34,12 +34,13 @@ func TestShortWarmUpExternal(t *testing.T) { testCollectReward(t) testMintBarQux100_2(t) testStakeToken_2(t) - testSingleBlock_TwoPosition(t) testCollectRewardAll(t) } func testInit(t *testing.T) { t.Run("initialize", func(t *testing.T) { + std.TestSetRealm(adminRealm) + // override warm-up period for testing changeWarmup(t, 0, 150) changeWarmup(t, 1, 300) @@ -133,18 +134,12 @@ func testAfterActive(t *testing.T) { std.TestSkipHeights(849) // in active std.TestSkipHeights(1) // active // but no block passed since active std.TestSkipHeights(50) // skip 50 more block - - // pei := GetPrintExternalInfo() - // uassert.Equal(t, pei, `{"height":1028,"time":1234569700,"position":[{"lpTokenId":1,"stakedHeight":126,"stakedTimestamp":1234567896,"incentive":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardToken":"gno.land/r/onbloc/bar","rewardAmount":"20000000","startTimestamp":1234569600,"endTimestamp":1242345600,"rewardPerBlockX96":"407552276307944123423579991440","stakedOrExternalDuration":50,"rewardPerBlock":"5","refundee":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","tokenAmountFull":257,"tokenAmountToGive":77,"full30":257,"give30":77,"full50":0,"give50":0,"full70":0,"give70":0,"full100":0}]}]}`) }) } func testDuratino200(t *testing.T) { - t.Run("duration 200", func(t *testing.T) { - std.TestSkipHeights(199) // skip 1 + 199 = 200 more block - - // pei := GetPrintExternalInfo() - // uassert.Equal(t, pei, `{"height":1227,"time":1234570098,"position":[{"lpTokenId":1,"stakedHeight":126,"stakedTimestamp":1234567896,"incentive":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardToken":"gno.land/r/onbloc/bar","rewardAmount":"20000000","startTimestamp":1234569600,"endTimestamp":1242345600,"rewardPerBlockX96":"407552276307944123423579991440","stakedOrExternalDuration":249,"rewardPerBlock":"5","refundee":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","tokenAmountFull":1280,"tokenAmountToGive":485,"full30":771,"give30":231,"full50":509,"give50":254,"full70":0,"give70":0,"full100":0}]}]}`) + t.Run("skip duration 200", func(t *testing.T) { + std.TestSkipHeights(200) }) } @@ -154,14 +149,22 @@ func testCollectReward(t *testing.T) { oldBar := bar.BalanceOf(admin) CollectReward(1, false) - std.TestSkipHeights(1) newBar := bar.BalanceOf(admin) - println("collected bar", newBar-oldBar) - // uassert.Equal(t, newBar-oldBar, uint64(481)) + uassert.True(t, isInErrorRange(4025, newBar-oldBar)) + // 4025 received + // 1725 penalty + // 5750 (total) + + // > block per reward is 23 + // > position staked 250 duration + // > 23 * 250 = 5750 + + // position warm up is 70% ( 5750 * 0.7 = 4025 ) + // penalty is 30% ( 5750 * 0.3 = 1725 ) + + std.TestSkipHeights(1) - // pei := GetPrintExternalInfo() - // uassert.Equal(t, pei, `{"height":1228,"time":1234570100,"position":[{"lpTokenId":1,"stakedHeight":126,"stakedTimestamp":1234567896,"incentive":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardToken":"gno.land/r/onbloc/bar","rewardAmount":"20000000","startTimestamp":1234569600,"endTimestamp":1242345600,"rewardPerBlockX96":"407552276307944123423579991440","stakedOrExternalDuration":250,"rewardPerBlock":"5","refundee":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","tokenAmountFull":5,"tokenAmountToGive":2,"full30":0,"give30":0,"full50":5,"give50":2,"full70":0,"give70":0,"full100":0}]}]}`) }) } @@ -200,39 +203,35 @@ func testStakeToken_2(t *testing.T) { gnft.Approve(consts.STAKER_ADDR, tid(2)) StakeToken(2) - - std.TestSkipHeights(1) - }) -} - -func testSingleBlock_TwoPosition(t *testing.T) { - t.Run("single block, two position", func(t *testing.T) { - // skipped 1 block from previous test - - // pei := GetPrintExternalInfo() - // uassert.Equal(t, pei, `{"height":1230,"time":1234570104,"position":[{"lpTokenId":1,"stakedHeight":126,"stakedTimestamp":1234567896,"incentive":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardToken":"gno.land/r/onbloc/bar","rewardAmount":"20000000","startTimestamp":1234569600,"endTimestamp":1242345600,"rewardPerBlockX96":"407552276307944123423579991440","stakedOrExternalDuration":252,"rewardPerBlock":"5","refundee":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","tokenAmountFull":12,"tokenAmountToGive":5,"full30":0,"give30":0,"full50":12,"give50":5,"full70":0,"give70":0,"full100":0}]},{"lpTokenId":2,"stakedHeight":1229,"stakedTimestamp":1234570102,"incentive":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardToken":"gno.land/r/onbloc/bar","rewardAmount":"20000000","startTimestamp":1234569600,"endTimestamp":1242345600,"rewardPerBlockX96":"407552276307944123423579991440","stakedOrExternalDuration":1,"rewardPerBlock":"5","refundee":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","tokenAmountFull":2,"tokenAmountToGive":0,"full30":2,"give30":0,"full50":0,"give50":0,"full70":0,"give70":0,"full100":0}]}]}`) - std.TestSkipHeights(1) }) } func testCollectRewardAll(t *testing.T) { t.Run("collect reward all", func(t *testing.T) { - std.TestSkipHeights(10) + std.TestSetRealm(adminRealm) - // pei := GetPrintExternalInfo() - // uassert.Equal(t, pei, `{"height":1241,"time":1234570126,"position":[{"lpTokenId":1,"stakedHeight":126,"stakedTimestamp":1234567896,"incentive":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardToken":"gno.land/r/onbloc/bar","rewardAmount":"20000000","startTimestamp":1234569600,"endTimestamp":1242345600,"rewardPerBlockX96":"407552276307944123423579991440","stakedOrExternalDuration":263,"rewardPerBlock":"5","refundee":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","tokenAmountFull":40,"tokenAmountToGive":19,"full30":0,"give30":0,"full50":40,"give50":19,"full70":0,"give70":0,"full100":0}]},{"lpTokenId":2,"stakedHeight":1229,"stakedTimestamp":1234570102,"incentive":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardToken":"gno.land/r/onbloc/bar","rewardAmount":"20000000","startTimestamp":1234569600,"endTimestamp":1242345600,"rewardPerBlockX96":"407552276307944123423579991440","stakedOrExternalDuration":12,"rewardPerBlock":"5","refundee":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","tokenAmountFull":30,"tokenAmountToGive":8,"full30":30,"give30":8,"full50":0,"give50":0,"full70":0,"give70":0,"full100":0}]}]}`) + // clear rewards + CollectReward(1, false) + CollectReward(2, false) - std.TestSetRealm(adminRealm) + std.TestSkipHeights(10) + // > block per reward is 23 + // > skip 10 block => reward 230 + + // position 01 and 02 has same liquidity + // each position's 100% reward = (230 / 2) = 115 + // position 01 warmup 70% = 115 * 0.7 = 80.5 + // position 02 warmup 30% = 115 * 0.3 = 34.5 oldBar := bar.BalanceOf(admin) CollectReward(1, false) newBar := bar.BalanceOf(admin) - uassert.Equal(t, newBar-oldBar, uint64(19)) + uassert.True(t, isInErrorRange(80, newBar-oldBar)) oldBar = newBar CollectReward(2, false) newBar = bar.BalanceOf(admin) - uassert.Equal(t, newBar-oldBar, uint64(8)) + uassert.True(t, isInErrorRange(34, newBar-oldBar)) }) } diff --git a/staker/__TEST_staker_short_warmup_period_external_12_test.gnoA b/staker/__TEST_staker_short_warmup_period_external_12_test.gnoA index d301ede86..55af2e3ca 100644 --- a/staker/__TEST_staker_short_warmup_period_external_12_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_external_12_test.gnoA @@ -5,12 +5,12 @@ package staker import ( + "math" "std" "testing" "gno.land/p/demo/uassert" "gno.land/r/demo/users" - pusers "gno.land/p/demo/users" "gno.land/r/gnoswap/v1/consts" @@ -40,10 +40,13 @@ func TestShortWarmUpTWoExternalIncentive(t *testing.T) { func testInit(t *testing.T) { t.Run("initialize", func(t *testing.T) { - changeWarmup(t, 100, 901) - changeWarmup(t, 70, 301) - changeWarmup(t, 50, 151) - changeWarmup(t, 30, 1) + std.TestSetRealm(adminRealm) + + // override warm-up period for testing + changeWarmup(t, 0, 150) + changeWarmup(t, 1, 300) + changeWarmup(t, 2, 900) + changeWarmup(t, 3, math.MaxInt64) // set unstaking fee to 0 SetUnstakingFeeByAdmin(0) @@ -86,7 +89,7 @@ func testMintBarQux100_1(t *testing.T) { ) uassert.Equal(t, tokenId, uint64(1)) - uassert.Equal(t, gnft.OwnerOf(tid(tokenId)), admin) + uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) std.TestSkipHeights(1) }) diff --git a/staker/__TEST_staker_short_warmup_period_external_13_gns_external_test.gnoA b/staker/__TEST_staker_short_warmup_period_external_13_gns_external_test.gnoA index 0789cc360..b57223b2e 100644 --- a/staker/__TEST_staker_short_warmup_period_external_13_gns_external_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_external_13_gns_external_test.gnoA @@ -40,6 +40,8 @@ func TestShortWarmUpTwoExternalIncentive_OneOfThemIsGNS(t *testing.T) { func testInit(t *testing.T) { t.Run("initialize", func(t *testing.T) { + std.TestSetRealm(adminRealm) + // override warm-up period for testing changeWarmup(t, 0, 150) changeWarmup(t, 1, 300) @@ -87,7 +89,7 @@ func testMintBarQux100_1(t *testing.T) { ) uassert.Equal(t, tokenId, uint64(1)) - uassert.Equal(t, gnft.OwnerOf(tid(tokenId)), admin) + uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) std.TestSkipHeights(1) }) @@ -178,8 +180,8 @@ func testCollectReward(t *testing.T) { newBar := bar.BalanceOf(admin) newGns := gns.BalanceOf(admin) - uassert.Equal(t, newBar-oldBar, uint64(485)) - uassert.Equal(t, newGns-oldGns, uint64(485)) + uassert.Equal(t, newBar-oldBar, uint64(878)) + uassert.Equal(t, newGns-oldGns, uint64(878)) uassert.True(t, newBar > oldBar) uassert.True(t, newGns > oldGns) diff --git a/staker/__TEST_staker_short_warmup_period_external_14_position_in_out_range_changed_by_swap_test.gnoA b/staker/__TEST_staker_short_warmup_period_external_14_position_in_out_range_changed_by_swap_test.gnoA index 1bc32cdf0..171fbe0b7 100644 --- a/staker/__TEST_staker_short_warmup_period_external_14_position_in_out_range_changed_by_swap_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_external_14_position_in_out_range_changed_by_swap_test.gnoA @@ -30,23 +30,26 @@ func TestShortWarmUpExternalPositionInOutRangeChangedBySwap(t *testing.T) { testCreatePool(t) testMintBarQux100_1(t) testMintBarQux100_2(t) - testCreateExternalIncentive(t) + testCreateExternalIncentiveBar(t) testStakeToken_1_AND_2(t) testBeforeActive(t) - testAfter849Blocks(t) - testAfter1Block(t) testAfter50Blocks(t) testMakePosition1OutRange(t) - testRewardNow(t) testRewardNowAfter1Block(t) } func testInit(t *testing.T) { t.Run("initialize", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + // override warm-up period for testing changeWarmup(t, 0, 150) changeWarmup(t, 1, 300) changeWarmup(t, 2, 900) changeWarmup(t, 3, math.MaxInt64) + + // set unstaking fee to 0 + SetUnstakingFeeByAdmin(0) }) } @@ -127,7 +130,7 @@ func testMintBarQux100_2(t *testing.T) { }) } -func testCreateExternalIncentive(t *testing.T) { +func testCreateExternalIncentiveBar(t *testing.T) { t.Run("create external incentive", func(t *testing.T) { std.TestSetRealm(adminRealm) @@ -162,50 +165,53 @@ func testStakeToken_1_AND_2(t *testing.T) { func testBeforeActive(t *testing.T) { t.Run("before active", func(t *testing.T) { - // FIXME: remove comments after api is fixed - // pei := GetPrintExternalInfo() - // uassert.Equal(t, pei, `{"height":128,"time":1234567900,"position":[]}`) - - // lp01ExternalRewards := ApiGetRewardsByLpTokenId(1) - // uassert.Equal(t, lp01ExternalRewards, `{"stat":{"height":128,"timestamp":1234567900},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[]}]}`) - - // lp02ExternalRewards := ApiGetRewardsByLpTokenId(2) - // uassert.Equal(t, lp02ExternalRewards, `{"stat":{"height":128,"timestamp":1234567900},"response":[{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[]}]}`) - }) -} + std.TestSetRealm(adminRealm) -func testAfter849Blocks(t *testing.T) { - t.Run("after 849 blocks", func(t *testing.T) { - std.TestSkipHeights(849) // in-active - // FIXME: remove comments after api is fixed - // lp01ExternalRewards := ApiGetRewardsByLpTokenId(1) - // uassert.Equal(t, lp01ExternalRewards, `{"stat":{"height":977,"timestamp":1234569598},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[]}]}`) + oldBar := bar.BalanceOf(admin) - // lp02ExternalRewards := ApiGetRewardsByLpTokenId(2) - // uassert.Equal(t, lp02ExternalRewards, `{"stat":{"height":977,"timestamp":1234569598},"response":[{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[]}]}`) - }) -} + CollectReward(1, false) + CollectReward(2, false) -func testAfter1Block(t *testing.T) { - t.Run("after 1 block", func(t *testing.T) { - std.TestSkipHeights(1) // active, but no block passed since active - // FIXME: remove comments after api is fixed - // lp01ExternalRewards := ApiGetRewardsByLpTokenId(1) - // uassert.Equal(t, lp01ExternalRewards, `{"stat":{"height":978,"timestamp":1234569600},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[]}]}`) + newBar := bar.BalanceOf(admin) - // lp02ExternalRewards := ApiGetRewardsByLpTokenId(2) - // uassert.Equal(t, lp02ExternalRewards, `{"stat":{"height":978,"timestamp":1234569600},"response":[{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[]}]}`) + uassert.Equal(t, uint64(0), newBar-oldBar) // incentive not started yet, no reward }) } func testAfter50Blocks(t *testing.T) { + t.Run("make external start", func(t *testing.T) { + std.TestSkipHeights(850) + }) t.Run("after 50 blocks", func(t *testing.T) { + std.TestSetRealm(adminRealm) std.TestSkipHeights(50) - // lp01ExternalRewards := ApiGetRewardsByLpTokenId(1) - // uassert.Equal(t, lp01ExternalRewards, `{"stat":{"height":1028,"timestamp":1234569700},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":67,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) - - // lp02ExternalRewards := ApiGetRewardsByLpTokenId(2) - // uassert.Equal(t, lp02ExternalRewards, `{"stat":{"height":1028,"timestamp":1234569700},"response":[{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":34654,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) + // block per reward is 2314 + // skip 50 block after active + // 2314 * 50 = 115700 + + // total inrange staked liquidity: 10272609 + // position-01 (inRange) liquditiy: 20026 => 0.1949456073% + // > 115700 * 0.1949456073% = 225.5520676461 + + // position-02 (inRange) liquditiy: 10252583 => 99.8050543927% + // > 115700 * 99.8050543927% = 115474.4479323539 + + // both of position is in warm up 70% + + oldBar := bar.BalanceOf(admin) + CollectReward(1, false) + newBar := bar.BalanceOf(admin) + uassert.True(t, isInErrorRange(157, newBar-oldBar)) + // reward 157 + // penalty 68 + // total 225 + + CollectReward(2, false) + newBar2 := bar.BalanceOf(admin) + uassert.True(t, isInErrorRange(80831, newBar2-newBar)) + // reward 80831 + // penalty 34643 + // total 115474 }) } @@ -224,12 +230,13 @@ func testMakePosition1OutRange(t *testing.T) { qux.Approve(a2u(consts.ROUTER_ADDR), consts.UINT64_MAX) tokenIn, tokenOut := rr.ExactInSwapRoute( - barPath, // inputToken - quxPath, // outputToken - "100000", // amountSpecified + barPath, // inputToken + quxPath, // outputToken + "100000", // amountSpecified "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100", // strRouteArr "100", // quoteArr "0", // tokenAmountLimit + max_timeout, ) uassert.Equal(t, tokenIn, "100000") uassert.Equal(t, tokenOut, "-98873") @@ -239,39 +246,26 @@ func testMakePosition1OutRange(t *testing.T) { }) } -// FIXME: remove comments after api is fixed -func testRewardNow(t *testing.T) { - t.Run("check reward", func(t *testing.T) { - // lp01ExternalRewards := ApiGetRewardsByLpTokenId(1) - // uassert.Equal(t, lp01ExternalRewards, `{"stat":{"height":1028,"timestamp":1234569700},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":67,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) - - // lp02ExternalRewards := ApiGetRewardsByLpTokenId(2) - // uassert.Equal(t, lp02ExternalRewards, `{"stat":{"height":1028,"timestamp":1234569700},"response":[{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":34654,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) - }) -} - func testRewardNowAfter1Block(t *testing.T) { t.Run("check reward after 1 block", func(t *testing.T) { - std.TestSkipHeights(1) + std.TestSetRealm(adminRealm) + CollectReward(1, false) + CollectReward(2, false) - // lp01ExternalRewards := ApiGetRewardsByLpTokenId(1) - // uassert.Equal(t, lp01ExternalRewards, `{"stat":{"height":1029,"timestamp":1234569702},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":67,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) + std.TestSkipHeights(1) - // lp02ExternalRewards := ApiGetRewardsByLpTokenId(2) - // uassert.Equal(t, lp02ExternalRewards, `{"stat":{"height":1029,"timestamp":1234569702},"response":[{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":35348,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) + oldBar := bar.BalanceOf(admin) + CollectReward(1, false) // + newBar := bar.BalanceOf(admin) + uassert.Equal(t, uint64(0), newBar-oldBar) + // out range, no reward + + CollectReward(2, false) + newBar2 := bar.BalanceOf(admin) + uassert.True(t, isInErrorRange(1619, newBar2-newBar)) + // 1619 + // only in-range position + // reward per block is 2314 + // warm up 70% = 2314 * 0.7 = 1619.8 }) - - // POSITION #1 PREVIOUS REWARD - // `{"stat":{"height":1028,"timestamp":1234569700},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":67,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) - - // POSITION #2 PREVIOUS REWARD - // `{"stat":{"height":1028,"timestamp":1234569700},"response":[{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":34654,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) - - /* - PREVIOUS REWARD -> NOW - - POSITION #1 - 67 > 67 - - POSITION #2 - 34654 > 35348 - */ } diff --git a/staker/__TEST_staker_short_warmup_period_external_15_90d_test.gnoA b/staker/__TEST_staker_short_warmup_period_external_15_90d_test.gnoA index ca3516e47..0c5171eee 100644 --- a/staker/__TEST_staker_short_warmup_period_external_15_90d_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_external_15_90d_test.gnoA @@ -9,10 +9,10 @@ import ( "testing" "gno.land/p/demo/uassert" - "gno.land/r/demo/users" - u256 "gno.land/p/gnoswap/uint256" + "gno.land/r/demo/users" + "gno.land/r/gnoswap/v1/consts" en "gno.land/r/gnoswap/v1/emission" @@ -34,11 +34,13 @@ func TestShortWarmUp90DayExternal(t *testing.T) { testCreateExternalIncentiveQux90(t) testStakeToken_1_4(t) testBeforeActive(t) - test23HoursAfterActive(t) + testRewardFor1Block(t) } func testInit(t *testing.T) { t.Run("override warm-up period", func(t *testing.T) { + std.TestSetRealm(adminRealm) + changeWarmup(t, 0, 150) changeWarmup(t, 1, 300) changeWarmup(t, 2, 900) @@ -144,10 +146,81 @@ func testBeforeActive(t *testing.T) { }) } -func test23HoursAfterActive(t *testing.T) { - t.Run("23 hours after active", func(t *testing.T) { - std.TestSkipHeights(849) // in active - std.TestSkipHeights(1) // active // but no block passed since active - std.TestSkipHeights(41400) // skip 23 hours of block +func testRewardFor1Block(t *testing.T) { + t.Run("reward for 1 block of 90 days external", func(t *testing.T) { + std.TestSkipHeights(849) // in active + std.TestSkipHeights(1) // active // but no block passed since active + std.TestSetRealm(adminRealm) + + CollectReward(1, false) + CollectReward(2, false) + CollectReward(3, false) + CollectReward(4, false) + }) + + t.Run("skip 1 block", func(t *testing.T) { + std.TestSkipHeights(1) + std.TestSetRealm(adminRealm) + + /* + - each staked position's liquidity ratio + > position01: 13.6317% + > position02: 84.3710% + > position03: 1.9902% + > position04: 0.0069% + + - block per reward 12860 + */ + + t.Run("collect reward position 01", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(1, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(1227))) + // reward 1227 + // penalty 526 + // total 1753 + // 12860 * 13.6317% = 1753.03662 + }) + + t.Run("collect reward position 02", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(2, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(7595))) + // reward 7595 + // penalty 3255 + // total 10850 + // 12860 * 84.3710% = 10850.1106 + }) + + t.Run("collect reward position 03", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(3, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(178))) + // reward 178 + // penalty 77 + // total 255 + // 12860 * 1.9902% = 255.93972 + }) + + t.Run("collect reward position 04", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(4, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(0))) + // reward 0 + // penalty 0 + // 12860 * 0.0069% = 0.88734 + }) }) } diff --git a/staker/__TEST_staker_short_warmup_period_external_16_180d_test.gnoA b/staker/__TEST_staker_short_warmup_period_external_16_180d_test.gnoA index 72e5b75a0..e3bb7e2d0 100644 --- a/staker/__TEST_staker_short_warmup_period_external_16_180d_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_external_16_180d_test.gnoA @@ -9,10 +9,10 @@ import ( "testing" "gno.land/p/demo/uassert" - "gno.land/r/demo/users" - u256 "gno.land/p/gnoswap/uint256" + "gno.land/r/demo/users" + "gno.land/r/gnoswap/v1/consts" en "gno.land/r/gnoswap/v1/emission" @@ -34,11 +34,13 @@ func TestShortWarmUp180DayExternal(t *testing.T) { testCreateExternalIncentiveQux180(t) testStakeToken_1_4(t) testBeforeActive(t) - test23HoursAfterActive(t) + testRewardFor1Block(t) } func testInit(t *testing.T) { t.Run("override warm-up period", func(t *testing.T) { + std.TestSetRealm(adminRealm) + changeWarmup(t, 0, 150) changeWarmup(t, 1, 300) changeWarmup(t, 2, 900) @@ -121,16 +123,16 @@ func testStakeToken_1_4(t *testing.T) { t.Run("stake token 1 ~ 4", func(t *testing.T) { std.TestSetRealm(adminRealm) - gnft.Approve(a2u(GetOrigPkgAddr()), tid(1)) + gnft.Approve(GetOrigPkgAddr(), tid(1)) StakeToken(1) - gnft.Approve(a2u(GetOrigPkgAddr()), tid(2)) + gnft.Approve(GetOrigPkgAddr(), tid(2)) StakeToken(2) - gnft.Approve(a2u(GetOrigPkgAddr()), tid(3)) + gnft.Approve(GetOrigPkgAddr(), tid(3)) StakeToken(3) - gnft.Approve(a2u(GetOrigPkgAddr()), tid(4)) + gnft.Approve(GetOrigPkgAddr(), tid(4)) StakeToken(4) std.TestSkipHeights(1) @@ -144,10 +146,82 @@ func testBeforeActive(t *testing.T) { }) } -func test23HoursAfterActive(t *testing.T) { - t.Run("23 hours after active", func(t *testing.T) { - std.TestSkipHeights(849) // in active - std.TestSkipHeights(1) // active // but no block passed since active - std.TestSkipHeights(41400) // skip 23 hours of block +func testRewardFor1Block(t *testing.T) { + t.Run("reward for 1 block of 90 days external", func(t *testing.T) { + std.TestSkipHeights(849) // in active + std.TestSkipHeights(1) // active // but no block passed since active + std.TestSetRealm(adminRealm) + + CollectReward(1, false) + CollectReward(2, false) + CollectReward(3, false) + CollectReward(4, false) + }) + + t.Run("skip 1 block", func(t *testing.T) { + std.TestSkipHeights(1) + std.TestSetRealm(adminRealm) + + /* + - each staked position's liquidity ratio + > position01: 13.6317% + > position02: 84.3710% + > position03: 1.9902% + > position04: 0.0069% + + - block per reward 1286008 + */ + + t.Run("collect reward position 01", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(1, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(122713))) + // reward 122713 + // penalty 52592 + // total 175305 + // 1286008 * 13.6317% = 175304.752536 + }) + + t.Run("collect reward position 02", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(2, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(759511))) + // reward 759511 + // penalty 325506 + // total 1085017 + // 1286008 * 84.3710% = 1085017.80968 + }) + + t.Run("collect reward position 03", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(3, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(17915))) + // reward 17915 + // penalty 7679 + // total 25594 + // 1286008 * 1.9902% = 25594.131216 + }) + + t.Run("collect reward position 04", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(4, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(62))) + // reward 62 + // penalty 27 + // total 89 + // 1286008 * 0.0069% = 88.734552 + }) }) } diff --git a/staker/__TEST_staker_short_warmup_period_external_17_365d_test.gnoA b/staker/__TEST_staker_short_warmup_period_external_17_365d_test.gnoA index 6db12740f..3b95c85a0 100644 --- a/staker/__TEST_staker_short_warmup_period_external_17_365d_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_external_17_365d_test.gnoA @@ -5,11 +5,11 @@ import ( "std" "testing" - "gno.land/r/demo/users" "gno.land/p/demo/uassert" - u256 "gno.land/p/gnoswap/uint256" + "gno.land/r/demo/users" + "gno.land/r/gnoswap/v1/consts" en "gno.land/r/gnoswap/v1/emission" @@ -31,11 +31,13 @@ func TestShortWarmUp365DayExternal(t *testing.T) { testCreateExternalIncentiveQux365(t) testStakeToken_1_4(t) testBeforeActive(t) - test23HoursAfterActive(t) + testRewardFor1Block(t) } func testInit(t *testing.T) { t.Run("override warm-up period", func(t *testing.T) { + std.TestSetRealm(adminRealm) + changeWarmup(t, 0, 150) changeWarmup(t, 1, 300) changeWarmup(t, 2, 900) @@ -138,10 +140,82 @@ func testBeforeActive(t *testing.T) { }) } -func test23HoursAfterActive(t *testing.T) { - t.Run("23 hours after active", func(t *testing.T) { - std.TestSkipHeights(849) // in active - std.TestSkipHeights(1) // active // but no block passed since active - std.TestSkipHeights(41400) // skip 23 hours of block +func testRewardFor1Block(t *testing.T) { + t.Run("reward for 1 block of 90 days external", func(t *testing.T) { + std.TestSkipHeights(849) // in active + std.TestSkipHeights(1) // active // but no block passed since active + std.TestSetRealm(adminRealm) + + CollectReward(1, false) + CollectReward(2, false) + CollectReward(3, false) + CollectReward(4, false) + }) + + t.Run("skip 1 block", func(t *testing.T) { + std.TestSkipHeights(1) + std.TestSetRealm(adminRealm) + + /* + - each staked position's liquidity ratio + > position01: 13.6317% + > position02: 84.3710% + > position03: 1.9902% + > position04: 0.0069% + + - block per reward 634195 + */ + + t.Run("collect reward position 01", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(1, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(59911))) + // reward 59911 + // penalty 25936 + // total 85847 + // 634195 * 13.6317% = 86451.559815 + }) + + t.Run("collect reward position 02", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(2, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(370808))) + // reward 370808 + // penalty 160523 + // total 531331 + // 634195 * 84.3710% = 535076.66345 + }) + + t.Run("collect reward position 03", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(3, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(8747))) + // reward 8747 + // penalty 3787 + // total 12534 + // 634195 * 1.9902% = 12621.74889 + }) + + t.Run("collect reward position 04", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + oldQux := qux.BalanceOf(admin) + CollectReward(4, false) + newQux := qux.BalanceOf(admin) + uassert.True(t, isInErrorRange(newQux-oldQux, uint64(30))) + // reward 30 + // penalty 14 + // total 44 + // 634195 * 0.0069% = 43.759455 + }) }) } diff --git a/staker/__TEST_staker_short_warmup_period_internal_01_test.gnoA b/staker/__TEST_staker_short_warmup_period_internal_01_test.gnoA index efc5ad7e9..adc78512f 100644 --- a/staker/__TEST_staker_short_warmup_period_internal_01_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_internal_01_test.gnoA @@ -14,6 +14,7 @@ import ( pn "gno.land/r/gnoswap/v1/position" "gno.land/r/gnoswap/v1/gnft" + "gno.land/r/gnoswap/v1/gns" "gno.land/r/onbloc/bar" "gno.land/r/onbloc/baz" @@ -35,12 +36,6 @@ func testInit(t *testing.T) { t.Run("init pool tiers", func(t *testing.T) { std.TestSetRealm(adminRealm) - // init pool tiers - // tier 1 - deletePoolTier(t, MUST_EXISTS_IN_TIER_1) - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100`, 1) - std.TestSkipHeights(1) - // override warm-up period for testing changeWarmup(t, 0, 150) changeWarmup(t, 1, 300) @@ -74,7 +69,9 @@ func testCreatePool(t *testing.T) { t.Run("create pool", func(t *testing.T) { std.TestSetRealm(adminRealm) - pl.CreatePool(barPath, quxPath, 100, "79228162514264337593543950337") // current tier 1 + pl.CreatePool(barPath, quxPath, 100, "79228162514264337593543950337") // current tier 1 + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100`, 1) + pl.CreatePool(barPath, bazPath, 3000, "79228162514264337593543950337") // will be tier 2 std.TestSkipHeights(1) @@ -106,10 +103,6 @@ func testMintBarQux100_1(t *testing.T) { uassert.Equal(t, tokenId, uint64(1)) uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) - gpi := getPrintInfo(t) - println(gpi) - // {"height":"126","time":"1234567905","gns":{"staker":"32106164","devOps":"10702053","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[]}]} - std.TestSkipHeights(1) }) } @@ -139,9 +132,6 @@ func testMintBarBaz100_2(t *testing.T) { uassert.Equal(t, tokenId, uint64(2)) uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) - gpi := getPrintInfo(t) - // {"height":"127","time":"1234567910","gns":{"staker":"42808219","devOps":"14269404","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[]}]} - std.TestSkipHeights(1) }) } @@ -153,8 +143,27 @@ func testStakeToken_1(t *testing.T) { gnft.Approve(consts.STAKER_ADDR, tid(1)) StakeToken(1) - gpi := getPrintInfo(t) - // {"height":"128","time":"1234567915","gns":{"staker":"53510274","devOps":"17836755","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"128","stakedTimestamp":"1234567915","stakedDuration":"0","fullAmount":"0","ratio":"30","warmUpAmount":"0","full30":"0","give30":"0","penalty30":"0","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} + std.TestSkipHeights(1) + }) + + t.Run("collect reward position 01", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(1, false) + afterGns := gns.BalanceOf(admin) + println(afterGns - beforeGns) + uassert.True(t, isInErrorRange(uint64(1605308), afterGns-beforeGns)) + // reward 1605307 + // penalty 3745719 + // total 5351026 + + // reward per block is 10702054 + // 2 pools have tier 1 + // each pool will have 10702054 * 50% = 5351027 + + // position warm up is 30% + // 5351027 * 30% = 1605308.1 std.TestSkipHeights(1) }) @@ -164,27 +173,57 @@ func testSetPoolTier(t *testing.T) { t.Run("set pool tier", func(t *testing.T) { std.TestSetRealm(adminRealm) - addPoolTier(t, "gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000", 2) - gpi := getPrintInfo(t) - // {"height":"129","time":"1234567920","gns":{"staker":"64212329","devOps":"21404106","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"2","numPoolSameTier":"2","position":[]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"128","stakedTimestamp":"1234567915","stakedDuration":"1","fullAmount":"10702053","ratio":"30","warmUpAmount":"3210616","full30":"10702053","give30":"3210616","penalty30":"7491437","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000`, 2) std.TestSkipHeights(1) }) } func testStakeToken_2(t *testing.T) { t.Run("stake token 2", func(t *testing.T) { - std.TestSetRealm(adminRealm) gnft.Approve(consts.STAKER_ADDR, tid(2)) StakeToken(2) - // @mconcat FIXME - // when emission pool has no target position to distribute, it needs to sent to community pool - - gpi := getPrintInfo(t) - // {"height":"130","time":"1234567925","gns":{"staker":"74914384","devOps":"24971457","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"2","numPoolSameTier":"2","position":[{"lpTokenId":"2","stakedHeight":"130","stakedTimestamp":"1234567925","stakedDuration":"0","fullAmount":"0","ratio":"30","warmUpAmount":"0","full30":"0","give30":"0","penalty30":"0","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"128","stakedTimestamp":"1234567915","stakedDuration":"2","fullAmount":"14982873","ratio":"30","warmUpAmount":"4494862","full30":"14982873","give30":"4494862","penalty30":"10488011","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} + std.TestSkipHeights(1) + // clear reward + CollectReward(1, false) + CollectReward(2, false) std.TestSkipHeights(1) }) + + t.Run("collect reward position 01", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(1, false) + afterGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(uint64(1123715), afterGns-beforeGns)) + // reward 1123715 + // penalty 2622002 + // total 3745717 + + // reward per block is 10702054 + // 2 pools have tier 1 + 1 pools have tier 2 + // each pool in tier 1 will have 10702054 * 70% * 50% = 3745718.9 + // > warm up 30% = 3745718.9 * 30% = 1123715.67 + + // each pool in tier 2 will have 10702054 * 30% = 3210616.2 + // > warm up 30% = 3210616.2 * 30% = 963184.86 + }) + + t.Run("collect reward position 02", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(2, false) + afterGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(uint64(963184), afterGns-beforeGns)) + + // reward 963184 + // penalty 2247431 + // total 3210615 + + }) } diff --git a/staker/__TEST_staker_short_warmup_period_internal_02_small_liq_test.gnoA b/staker/__TEST_staker_short_warmup_period_internal_02_small_liq_test.gnoA index 6fc0407a5..f73449477 100644 --- a/staker/__TEST_staker_short_warmup_period_internal_02_small_liq_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_internal_02_small_liq_test.gnoA @@ -27,7 +27,6 @@ func TestShortWarmUpInternalSmallLiq(t *testing.T) { testMintBarQux100_1(t) testMintBarQux100_2(t) testStakeToken_1_2(t) - testNow(t) testCollectRewardBoth(t) } @@ -35,11 +34,8 @@ func testInit(t *testing.T) { t.Run("init pool tiers", func(t *testing.T) { std.TestSetRealm(adminRealm) - // init pool tiers - // tier 1 + // delete default pool deletePoolTier(t, MUST_EXISTS_IN_TIER_1) - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100`, 1) - std.TestSkipHeights(1) // override warm-up period for testing changeWarmup(t, 0, 150) @@ -79,7 +75,7 @@ func testCreatePool(t *testing.T) { gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()*3) pl.CreatePool(barPath, quxPath, 100, "79228162514264337593543950337") - pl.CreatePool(barPath, bazPath, 3000, "79228162514264337593543950337") + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100`, 1) std.TestSkipHeights(1) }) @@ -110,9 +106,6 @@ func testMintBarQux100_1(t *testing.T) { uassert.Equal(t, tokenId, uint64(1)) uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) - gpi := getPrintInfo(t) - // {"height":"126","time":"1234567896","gns":{"staker":"32106164","devOps":"10702053","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[]}]} - std.TestSkipHeights(1) }) } @@ -130,8 +123,8 @@ func testMintBarQux100_2(t *testing.T) { fee100, // fee int32(-1000), // tickLower int32(1000), // tickUpper - "50", // amount0Desired - "50", // amount1Desired + "500", // amount0Desired + "500", // amount1Desired "1", // amount0Min "1", // amount1Min max_timeout, @@ -142,9 +135,6 @@ func testMintBarQux100_2(t *testing.T) { uassert.Equal(t, tokenId, uint64(2)) uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) - gpi := getPrintInfo(t) - // {"height":"127","time":"1234567898","gns":{"staker":"42808219","devOps":"14269404","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[]}]} - std.TestSkipHeights(1) }) } @@ -159,34 +149,41 @@ func testStakeToken_1_2(t *testing.T) { gnft.Approve(consts.STAKER_ADDR, tid(2)) StakeToken(2) - gpi := getPrintInfo(t) - // {"height":"128","time":"1234567900","gns":{"staker":"53510274","devOps":"17836755","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"128","stakedTimestamp":"1234567900","stakedDuration":"0","fullAmount":"0","ratio":"30","warmUpAmount":"0","full30":"0","give30":"0","penalty30":"0","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"},{"lpTokenId":"2","stakedHeight":"128","stakedTimestamp":"1234567900","stakedDuration":"0","fullAmount":"0","ratio":"30","warmUpAmount":"0","full30":"0","give30":"0","penalty30":"0","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} - std.TestSkipHeights(1) }) } -func testNow(t *testing.T) { - t.Run("now", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - gpi := getPrintInfo(t) - // {"height":"129","time":"1234567902","gns":{"staker":"64212329","devOps":"21404106","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"128","stakedTimestamp":"1234567900","stakedDuration":"1","fullAmount":"10702052","ratio":"30","warmUpAmount":"3210615","full30":"10702052","give30":"3210615","penalty30":"7491437","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"},{"lpTokenId":"2","stakedHeight":"128","stakedTimestamp":"1234567900","stakedDuration":"1","fullAmount":"1","ratio":"30","warmUpAmount":"0","full30":"1","give30":"0","penalty30":"1","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} +func testCollectRewardBoth(t *testing.T) { + // reward per block 10702054 - std.TestSkipHeights(1) - }) -} + // total staked liquidity = 10252593386 + // > position-01: 10252583134 // ratio 99.9999000058% + // > 10702054 * 99.9999000058% = 10702043.2985667191 + // > warm up = 10702043.2985667191 * 30% = 3210612.9895700157 -func testCollectRewardBoth(t *testing.T) { - t.Run("collect reward for position 01 and 02", func(t *testing.T) { + // > position-02: 10252 // ratio 0.0000999942% + // > 10702054 * 0.0000999942% = 10.7014332809 + // > warm up = 10.7014332809 * 30% = 3.2104299843 + t.Run("collect reward for position 01", func(t *testing.T) { std.TestSetRealm(adminRealm) + beforeGns := gns.BalanceOf(admin) CollectReward(1, false) - CollectReward(2, false) + newGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(newGns-beforeGns, 3210614)) + // reward 3210612 + // penalty 7491431 + // total 10702043 + }) - gpi := getPrintInfo(t) - // {"height":"130","time":"1234567904","gns":{"staker":"53510277","devOps":"24971457","communityPool":"14982876","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000006421231"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"128","stakedTimestamp":"1234567900","stakedDuration":"2","fullAmount":"0","ratio":"30","warmUpAmount":"0","full30":"0","give30":"0","penalty30":"0","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"},{"lpTokenId":"2","stakedHeight":"128","stakedTimestamp":"1234567900","stakedDuration":"2","fullAmount":"0","ratio":"30","warmUpAmount":"0","full30":"0","give30":"0","penalty30":"0","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} + t.Run("collect reward for position 02", func(t *testing.T) { + std.TestSetRealm(adminRealm) - std.TestSkipHeights(1) + beforeGns := gns.BalanceOf(admin) + CollectReward(2, false) + newGns := gns.BalanceOf(admin) + // reward 3 + // penalty 7 + // total 10 }) } diff --git a/staker/__TEST_staker_short_warmup_period_internal_03_change_tier_test.gnoA b/staker/__TEST_staker_short_warmup_period_internal_03_change_tier_test.gnoA index 9d39af86f..fca66bef6 100644 --- a/staker/__TEST_staker_short_warmup_period_internal_03_change_tier_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_internal_03_change_tier_test.gnoA @@ -31,9 +31,7 @@ func TestShortWarmUpChangeTier(t *testing.T) { testStakeToken_1(t) testSetPoolTier(t) testStakeToken_2(t) - testNow(t) testChangePoolTier(t) - testNow2(t) } func testInit(t *testing.T) { @@ -43,7 +41,6 @@ func testInit(t *testing.T) { // init pool tiers // tier 1 deletePoolTier(t, MUST_EXISTS_IN_TIER_1) - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100`, 1) std.TestSkipHeights(1) // override warm-up period for testing @@ -84,6 +81,8 @@ func testCreatePool(t *testing.T) { gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()*3) pl.CreatePool(barPath, quxPath, 100, "79228162514264337593543950337") + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100`, 1) + pl.CreatePool(barPath, bazPath, 3000, "79228162514264337593543950337") std.TestSkipHeights(1) @@ -115,9 +114,6 @@ func testMintBarQux100_1(t *testing.T) { uassert.Equal(t, tokenId, uint64(1)) uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) - gpi := getPrintInfo(t) - // {"height":"126","time":"1234567896","gns":{"staker":"32106164","devOps":"10702053","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[]}]} - std.TestSkipHeights(1) }) } @@ -147,8 +143,6 @@ func testMintBarBaz100_2(t *testing.T) { uassert.Equal(t, tokenId, uint64(2)) uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) - gpi := getPrintInfo(t) - // {"height":"127","time":"1234567898","gns":{"staker":"42808219","devOps":"14269404","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[]}]} std.TestSkipHeights(1) }) } @@ -157,9 +151,6 @@ func testSkip100Height(t *testing.T) { t.Run("skip 100 heights", func(t *testing.T) { std.TestSkipHeights(100) - gpi := getPrintInfo(t) - // {"height":"228","time":"1234568100","gns":{"staker":"1123715724","devOps":"374571905","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[]}]} - std.TestSkipHeights(1) }) } @@ -171,9 +162,6 @@ func testStakeToken_1(t *testing.T) { gnft.Approve(consts.STAKER_ADDR, tid(1)) StakeToken(1) - gpi := getPrintInfo(t) - // {"height":"229","time":"1234568102","gns":{"staker":"1134417779","devOps":"378139256","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"0","fullAmount":"0","ratio":"30","warmUpAmount":"0","full30":"0","give30":"0","penalty30":"0","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} - std.TestSkipHeights(1) }) } @@ -184,9 +172,7 @@ func testSetPoolTier(t *testing.T) { std.TestSetRealm(adminRealm) - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000`, 2) - gpi := getPrintInfo(t) - // {"height":"330","time":"1234568304","gns":{"staker":"2215325284","devOps":"738441757","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"2","numPoolSameTier":"2","position":[]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"101","fullAmount":"1080907453","ratio":"30","warmUpAmount":"324272236","full30":"1080907453","give30":"324272236","penalty30":"756635217","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000`, 2) std.TestSkipHeights(1) }) } @@ -198,39 +184,51 @@ func testStakeToken_2(t *testing.T) { gnft.Approve(consts.STAKER_ADDR, tid(2)) StakeToken(2) - gpi := getPrintInfo(t) - // {"height":"331","time":"1234568306","gns":{"staker":"2226027339","devOps":"742009108","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"2","numPoolSameTier":"2","position":[{"lpTokenId":"2","stakedHeight":"331","stakedTimestamp":"1234568306","stakedDuration":"0","fullAmount":"0","ratio":"30","warmUpAmount":"0","full30":"0","give30":"0","penalty30":"0","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"102","fullAmount":"764126573","ratio":"30","warmUpAmount":"229237972","full30":"764126573","give30":"229237972","penalty30":"534888601","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} std.TestSkipHeights(1) }) -} -func testNow(t *testing.T) { - t.Run("now", func(t *testing.T) { + t.Run("collect reward when tier is 2", func(t *testing.T) { std.TestSetRealm(adminRealm) - gpi := getPrintInfo(t) - // {"height":"332","time":"1234568308","gns":{"staker":"2236729394","devOps":"745576459","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"2","numPoolSameTier":"2","position":[{"lpTokenId":"2","stakedHeight":"331","stakedTimestamp":"1234568306","stakedDuration":"1","fullAmount":"3210615","ratio":"30","warmUpAmount":"963184","full30":"3210615","give30":"963184","penalty30":"2247431","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"103","fullAmount":"771618010","ratio":"30","warmUpAmount":"231485403","full30":"771618010","give30":"231485403","penalty30":"540132607","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} + beforeGns := gns.BalanceOf(admin) + CollectReward(2, false) + afterGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(afterGns-beforeGns, 963184)) + // reward 963184 + // penalty 2247431 + // total 3210615 + + // reward per block 10702054 + // tier2 will have 30% = 3210616.2 + // warm up 30% = 963184.86 std.TestSkipHeights(1) }) } func testChangePoolTier(t *testing.T) { - t.Run("change pool tier", func(t *testing.T) { + t.Run("change pool tier to 1", func(t *testing.T) { std.TestSetRealm(adminRealm) - changePoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000`, 1) - - gpi := getPrintInfo(t) - // {"height":"333","time":"1234568310","gns":{"staker":"2247431449","devOps":"749143810","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"1","numPoolSameTier":"2","position":[{"lpTokenId":"2","stakedHeight":"331","stakedTimestamp":"1234568306","stakedDuration":"2","fullAmount":"6421231","ratio":"30","warmUpAmount":"1926369","full30":"6421231","give30":"1926369","penalty30":"4494862","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"2","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"104","fullAmount":"779109447","ratio":"30","warmUpAmount":"233732834","full30":"779109447","give30":"233732834","penalty30":"545376613","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} - - std.TestSkipHeights(1) + ChangePoolTier(`gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000`, 1) + CollectReward(2, false) }) -} -func testNow2(t *testing.T) { - std.TestSetRealm(adminRealm) + t.Run("check reward when tier is 1", func(t *testing.T) { + std.TestSkipHeights(1) + std.TestSetRealm(adminRealm) - gpi := getPrintInfo(t) - // {"height":"334","time":"1234568312","gns":{"staker":"2258133504","devOps":"752711161","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"1","numPoolSameTier":"2","position":[{"lpTokenId":"2","stakedHeight":"331","stakedTimestamp":"1234568306","stakedDuration":"3","fullAmount":"5351026","ratio":"30","warmUpAmount":"1605308","full30":"5351026","give30":"1605308","penalty30":"3745718","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"2","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"105","fullAmount":"399721625","ratio":"30","warmUpAmount":"119916487","full30":"399721625","give30":"119916487","penalty30":"279805138","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} + beforeGns := gns.BalanceOf(admin) + CollectReward(2, false) + afterGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(afterGns-beforeGns, 1605308)) + // reward 1605307 + // penalty 3745719 + // total 5351026 + + // reward per block 10702054 + // 2 pool exists in tier 1 + // each pool will have 10702054 / 2 = 5351027 + // warm up 30% = 5351027 * 30% = 1605308.1 + }) } diff --git a/staker/__TEST_staker_short_warmup_period_internal_04_remove_tier_test.gnoA b/staker/__TEST_staker_short_warmup_period_internal_04_remove_tier_test.gnoA index f1084a7a8..a468dfd70 100644 --- a/staker/__TEST_staker_short_warmup_period_internal_04_remove_tier_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_internal_04_remove_tier_test.gnoA @@ -27,13 +27,10 @@ func TestShortWarmUpRemoveTier(t *testing.T) { testPoolInitCreatePool(t) testMintBarQux100_1(t) testMintBarBaz100_2(t) - testSkip100Height(t) testStakeToken_1(t) testSetPoolTier(t) testStakeToken_2(t) - testNow(t) testRemovePoolTier(t) - testNow2(t) } func testInit(t *testing.T) { @@ -43,7 +40,6 @@ func testInit(t *testing.T) { // init pool tiers // tier 1 deletePoolTier(t, MUST_EXISTS_IN_TIER_1) - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100`, 1) std.TestSkipHeights(1) // override warm-up period for testing @@ -85,6 +81,8 @@ func testPoolInitCreatePool(t *testing.T) { gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()*3) pl.CreatePool(barPath, quxPath, 100, "79228162514264337593543950337") + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100`, 1) + pl.CreatePool(barPath, bazPath, 3000, "79228162514264337593543950337") std.TestSkipHeights(1) @@ -116,9 +114,6 @@ func testMintBarQux100_1(t *testing.T) { uassert.Equal(t, tokenId, uint64(1)) uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) - gpi := getPrintInfo(t) - // {"height":"126","time":"1234567896","gns":{"staker":"32106164","devOps":"10702053","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[]}]} - std.TestSkipHeights(1) }) } @@ -148,20 +143,6 @@ func testMintBarBaz100_2(t *testing.T) { uassert.Equal(t, tokenId, uint64(2)) uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) - gpi := getPrintInfo(t) - // {"height":"127","time":"1234567898","gns":{"staker":"42808219","devOps":"14269404","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[]}]} - - std.TestSkipHeights(1) - }) -} - -func testSkip100Height(t *testing.T) { - t.Run("skip 100 heights", func(t *testing.T) { - std.TestSkipHeights(100) - - gpi := getPrintInfo(t) - // {"height":"228","time":"1234568100","gns":{"staker":"1123715724","devOps":"374571905","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[]}]} - std.TestSkipHeights(1) }) } @@ -173,9 +154,6 @@ func testStakeToken_1(t *testing.T) { gnft.Approve(consts.STAKER_ADDR, tid(1)) StakeToken(1) - gpi := getPrintInfo(t) - // {"height":"229","time":"1234568102","gns":{"staker":"1134417779","devOps":"378139256","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"0","fullAmount":"0","ratio":"30","warmUpAmount":"0","full30":"0","give30":"0","penalty30":"0","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} - std.TestSkipHeights(1) }) } @@ -185,10 +163,7 @@ func testSetPoolTier(t *testing.T) { std.TestSkipHeights(100) // this reward should go to bar:qux:100 std.TestSetRealm(adminRealm) - addPoolTier(t, "gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000", 2) - - gpi := getPrintInfo(t) - // {"height":"330","time":"1234568304","gns":{"staker":"2215325284","devOps":"738441757","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"2","numPoolSameTier":"2","position":[]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"101","fullAmount":"1080907453","ratio":"30","warmUpAmount":"324272236","full30":"1080907453","give30":"324272236","penalty30":"756635217","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000`, 2) std.TestSkipHeights(1) }) @@ -201,40 +176,47 @@ func testStakeToken_2(t *testing.T) { gnft.Approve(consts.STAKER_ADDR, tid(2)) StakeToken(2) - gpi := getPrintInfo(t) - // {"height":"331","time":"1234568306","gns":{"staker":"2226027339","devOps":"742009108","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"2","numPoolSameTier":"2","position":[{"lpTokenId":"2","stakedHeight":"331","stakedTimestamp":"1234568306","stakedDuration":"0","fullAmount":"0","ratio":"30","warmUpAmount":"0","full30":"0","give30":"0","penalty30":"0","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"102","fullAmount":"764126573","ratio":"30","warmUpAmount":"229237972","full30":"764126573","give30":"229237972","penalty30":"534888601","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} - std.TestSkipHeights(1) - }) -} + // clear reward + CollectReward(1, false) + CollectReward(2, false) -func testNow(t *testing.T) { - t.Run("now", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - gpi := getPrintInfo(t) - // {"height":"332","time":"1234568308","gns":{"staker":"2236729394","devOps":"745576459","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"2","numPoolSameTier":"2","position":[{"lpTokenId":"2","stakedHeight":"331","stakedTimestamp":"1234568306","stakedDuration":"1","fullAmount":"3210615","ratio":"30","warmUpAmount":"963184","full30":"3210615","give30":"963184","penalty30":"2247431","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"103","fullAmount":"771618010","ratio":"30","warmUpAmount":"231485403","full30":"771618010","give30":"231485403","penalty30":"540132607","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} std.TestSkipHeights(1) }) } func testRemovePoolTier(t *testing.T) { - t.Run("remove pool tier", func(t *testing.T) { + t.Run("check reward for position 01", func(t *testing.T) { std.TestSetRealm(adminRealm) - deletePoolTier(t, "gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000") - - gpi := getPrintInfo(t) - // {"height":"333","time":"1234568310","gns":{"staker":"2247431449","devOps":"749143810","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"0","numPoolSameTier":"0","position":[{"lpTokenId":"2","stakedHeight":"331","stakedTimestamp":"1234568306","stakedDuration":"2","fullAmount":"6421231","ratio":"30","warmUpAmount":"1926369","full30":"6421231","give30":"1926369","penalty30":"4494862","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"104","fullAmount":"779109447","ratio":"30","warmUpAmount":"233732834","full30":"779109447","give30":"233732834","penalty30":"545376613","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} - std.TestSkipHeights(1) + beforeGns := gns.BalanceOf(admin) + CollectReward(1, false) // position 01 is in tier1 pool + afterGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(afterGns-beforeGns, 2247431)) + // reward 2247430 + // penalty 5244006 + // total 7491436 + + // reward per block 10702054 + // tier1 will have 70% = 7491437.8 + // > warm up 30% = 2247431.34 + // tier2 will have 30% = 3210616.2 }) -} -func testNow2(t *testing.T) { - t.Run("now 2", func(t *testing.T) { + t.Run("remove pool tier", func(t *testing.T) { std.TestSetRealm(adminRealm) - - gpi := getPrintInfo(t) - // {"height":"334","time":"1234568312","gns":{"staker":"2258133504","devOps":"752711161","communityPool":"0","govStaker":"0","protocolFee":"0","GnoswapAdmin":"100000000000000"},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000","tier":"0","numPoolSameTier":"0","position":[{"lpTokenId":"2","stakedHeight":"331","stakedTimestamp":"1234568306","stakedDuration":"3","fullAmount":"6421231","ratio":"30","warmUpAmount":"1926369","full30":"6421231","give30":"1926369","penalty30":"4494862","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]},{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","tier":"1","numPoolSameTier":"1","position":[{"lpTokenId":"1","stakedHeight":"229","stakedTimestamp":"1234568102","stakedDuration":"105","fullAmount":"799443352","ratio":"30","warmUpAmount":"239833005","full30":"799443352","give30":"239833005","penalty30":"559610347","full50":"0","give50":"0","penalty50":"0","full70":"0","give70":"0","penalty70":"0","full100":"0","give100":"0","penalty100":"0"}]}]} + RemovePoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000`) std.TestSkipHeights(1) + // tier2 pool has been removed, only 1 pool exist in tier 1 + + beforeGns := gns.BalanceOf(admin) + CollectReward(1, false) // position 01 is in tier1 pool + afterGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(afterGns-beforeGns, 3210616)) + // reward 3210615 + // penalty 7491438 + // total 10702053 + + // block reward per pool 10702054 + // warm up 30% = 3210616.2 }) } diff --git a/staker/__TEST_staker_short_warmup_period_internal_05_collect_rewards_test.gnoA b/staker/__TEST_staker_short_warmup_period_internal_05_collect_rewards_test.gnoA deleted file mode 100644 index c66e48aab..000000000 --- a/staker/__TEST_staker_short_warmup_period_internal_05_collect_rewards_test.gnoA +++ /dev/null @@ -1,288 +0,0 @@ -package staker - -import ( - "math" - "std" - "testing" - - "gno.land/p/demo/uassert" - - "gno.land/r/gnoswap/v1/consts" - - en "gno.land/r/gnoswap/v1/emission" - pl "gno.land/r/gnoswap/v1/pool" - pn "gno.land/r/gnoswap/v1/position" - - "gno.land/r/gnoswap/v1/gnft" - "gno.land/r/gnoswap/v1/gns" - - "gno.land/r/onbloc/bar" - "gno.land/r/onbloc/baz" - "gno.land/r/onbloc/qux" -) - -func TestShortWarmUpInternalCollectRewards(t *testing.T) { - testInit(t) - testDoubleMint(t) - testCreatePool(t) - testMintBarQux100_1(t) - testMintBarBaz100_2(t) - testStakeToken_1(t) - testSetPoolTier(t) - testStakeToken_2(t) - testCollect1_1(t) - testCollect1_2(t) - testCollect1_3(t) - testCollect1_4(t) - testCollect2_1(t) - testCollectAll_1(t) - testCollectNow(t) - testCollectSameBlock(t) - testCollectAfterSingleBlock(t) -} - -func testInit(t *testing.T) { - t.Run("init pool tiers", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - // init pool tiers - // tier 1 - deletePoolTier(t, MUST_EXISTS_IN_TIER_1) - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100`, 1) - std.TestSkipHeights(1) - - // override warm-up period for testing - changeWarmup(t, 0, 150) - changeWarmup(t, 1, 300) - changeWarmup(t, 2, 900) - changeWarmup(t, 3, math.MaxInt64) - - // set unstaking fee to 0 - SetUnstakingFeeByAdmin(0) - - // set pool creation fee to 0 - pl.SetPoolCreationFeeByAdmin(0) - - // set community pool distribution to 0% (give it to devOps) - en.ChangeDistributionPctByAdmin( - 1, 7500, - 2, 2500, - 3, 0, - 4, 0, - ) - }) -} - -func testDoubleMint(t *testing.T) { - t.Run("double mint", func(t *testing.T) { - en.MintAndDistributeGns() - en.MintAndDistributeGns() - - std.TestSkipHeights(1) - }) -} - -func testCreatePool(t *testing.T) { - t.Run("create pool", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()*3) - - pl.CreatePool(barPath, quxPath, 100, "79228162514264337593543950337") - pl.CreatePool(barPath, bazPath, 3000, "79228162514264337593543950337") - - std.TestSkipHeights(1) - }) -} - -func testMintBarQux100_1(t *testing.T) { - - t.Run("mint position 01, bar:qux:100", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - - tokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - fee100, // fee - int32(-1000), // tickLower - int32(1000), // tickUpper - "50", // amount0Desired - "50", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - - uassert.Equal(t, tokenId, uint64(1)) - uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) - - gpi := getPrintInfo(t) - // uassert.Equal(t, gpi, `{"height":126,"time":1234567896,"gns":{"staker":0,"devOps":8561643,"communityPool":34246574,"govStaker":0,"protocolFee":200000000,"GnoswapAdmin":99999800000000},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","startTimestamp":1234567890,"tier":1,"numPoolSameTier":1,"poolReward":0,"position":[]}]}`) - - std.TestSkipHeights(1) - }) -} - -func testMintBarBaz100_2(t *testing.T) { - t.Run("mint position 02, bar:baz:3000", func(t *testing.T) { - - std.TestSetRealm(adminRealm) - - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - baz.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - - tokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - bazPath, // token1 - fee3000, // fee - int32(-1020), // tickLower - int32(1020), // tickUpper - "50", // amount0Desired - "50", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - - uassert.Equal(t, tokenId, uint64(2)) - uassert.Equal(t, gnft.MustOwnerOf(tid(tokenId)), adminAddr) - - std.TestSkipHeights(1) - }) -} - -func testStakeToken_1(t *testing.T) { - t.Run("stake token 01", func(t *testing.T) { - - std.TestSetRealm(adminRealm) - - gnft.Approve(consts.STAKER_ADDR, tid(1)) - StakeToken(1) - - std.TestSkipHeights(1) - }) -} - -func testSetPoolTier(t *testing.T) { - t.Run("set pool tier", func(t *testing.T) { - - std.TestSkipHeights(800) // this reward should go to bar:qux:100 - - std.TestSetRealm(adminRealm) - addPoolTier(t, "gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:3000", 2) - - std.TestSkipHeights(1) - }) -} - -func testStakeToken_2(t *testing.T) { - t.Run("stake token 02", func(t *testing.T) { - - std.TestSetRealm(adminRealm) - - gnft.Approve(consts.STAKER_ADDR, tid(2)) - StakeToken(2) - - std.TestSkipHeights(1) - }) -} - -func testCollect1_1(t *testing.T) { - t.Run("collect reward for position 01, 1st time", func(t *testing.T) { - - std.TestSetRealm(adminRealm) - - CollectReward(1, false) - - std.TestSkipHeights(5) - }) -} - -func testCollect1_2(t *testing.T) { - t.Run("collect reward for position 01, 2nd time", func(t *testing.T) { - - std.TestSetRealm(adminRealm) - - CollectReward(1, false) - - std.TestSkipHeights(500) - }) -} - -func testCollect1_3(t *testing.T) { - t.Run("collect reward for position 01, 3rd time", func(t *testing.T) { - - std.TestSetRealm(adminRealm) - - CollectReward(1, false) - - std.TestSkipHeights(50) - }) -} - -func testCollect1_4(t *testing.T) { - t.Run("collect reward for position 01, 4th time", func(t *testing.T) { - - std.TestSetRealm(adminRealm) - - CollectReward(1, false) - - std.TestSkipHeights(50) - }) -} - -func testCollect2_1(t *testing.T) { - t.Run("collect reward for position 02, 1st time", func(t *testing.T) { - - std.TestSetRealm(adminRealm) - - CollectReward(2, false) - - std.TestSkipHeights(5) - }) -} - -func testCollectAll_1(t *testing.T) { - t.Run("collect reward for all positions, 1st time", func(t *testing.T) { - - std.TestSetRealm(adminRealm) - - CollectReward(1, false) - CollectReward(2, false) - - std.TestSkipHeights(5) - }) -} - -func testCollectNow(t *testing.T) { - t.Run("collect reward for position 01, now", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - CollectReward(1, false) - }) -} - -func testCollectSameBlock(t *testing.T) { - t.Run("collect reward for position 01, same block", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - CollectReward(1, false) - }) -} - -func testCollectAfterSingleBlock(t *testing.T) { - t.Run("collect reward for position 01, after single block", func(t *testing.T) { - std.TestSkipHeights(1) - - std.TestSetRealm(adminRealm) - - CollectReward(1, false) - }) -} diff --git a/staker/__TEST_staker_short_warmup_period_internal_06_position_in_out_range_changed_by_swap_test.gnoA b/staker/__TEST_staker_short_warmup_period_internal_05_position_in_out_range_changed_by_swap_test.gnoA similarity index 50% rename from staker/__TEST_staker_short_warmup_period_internal_06_position_in_out_range_changed_by_swap_test.gnoA rename to staker/__TEST_staker_short_warmup_period_internal_05_position_in_out_range_changed_by_swap_test.gnoA index 99f976c08..55b93b1ad 100644 --- a/staker/__TEST_staker_short_warmup_period_internal_06_position_in_out_range_changed_by_swap_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_internal_05_position_in_out_range_changed_by_swap_test.gnoA @@ -1,9 +1,9 @@ package staker import ( + "math" "std" "testing" - "time" "gno.land/p/demo/uassert" @@ -29,20 +29,24 @@ func TestShortWarmUpInternalPositionInOutRangeChangedBySwap(t *testing.T) { testMintBarQux100_2(t) testStakeToken_1_2(t) testMakePosition1OutRange(t) - // testCheckRewardAfter1Block(t) + testCheckRewardAfter1Block(t) } func testInit(t *testing.T) { t.Run("initialize", func(t *testing.T) { - deletePoolTier(t, MUST_EXISTS_IN_TIER_1) + std.TestSetRealm(adminRealm) - addPoolTier(t, poolPath, 1) + deletePoolTier(t, MUST_EXISTS_IN_TIER_1) std.TestSkipHeights(1) - changeWarmup(t, 0, 901) - changeWarmup(t, 1, 301) - changeWarmup(t, 2, 151) - changeWarmup(t, 3, 1) + // override warm-up period for testing + changeWarmup(t, 0, 150) + changeWarmup(t, 1, 300) + changeWarmup(t, 2, 900) + changeWarmup(t, 3, math.MaxInt64) + + // set unstaking fee to 0 + SetUnstakingFeeByAdmin(0) }) } @@ -53,6 +57,7 @@ func testCreatePool(t *testing.T) { gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()*3) pl.CreatePool(barPath, quxPath, 100, "79228162514264337593543950337") + SetPoolTierByAdmin(poolPath, 1) std.TestSkipHeights(1) }) @@ -79,13 +84,9 @@ func testMintBarQux100_1(t *testing.T) { adminAddr, adminAddr, ) + println(tokenId, liquidity) uassert.Equal(t, tokenId, uint64(1)) - // uassert.Equal(t, gnft.OwnerOf(tid(tokenId)), admin) - - // gpi := GetPrintInfo() - // uassert.Equal(t, gpi, `{"height":125,"time":1234567894,"gns":{"staker":0,"devOps":5707762,"communityPool":22831049,"govStaker":0,"protocolFee":100000000,"GnoswapAdmin":99999900000000},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","startTimestamp":1234567890,"tier":1,"numPoolSameTier":1,"poolReward":0,"position":[]}]}`) - std.TestSkipHeights(1) }) } @@ -111,13 +112,9 @@ func testMintBarQux100_2(t *testing.T) { adminAddr, adminAddr, ) + println(tokenId, liquidity) uassert.Equal(t, tokenId, uint64(2)) - // uassert.Equal(t, gnft.OwnerOf(tid(tokenId)), admin) - - // gpi := GetPrintInfo() - // uassert.Equal(t, gpi, `{"height":126,"time":1234567896,"gns":{"staker":0,"devOps":8561643,"communityPool":34246574,"govStaker":0,"protocolFee":100000000,"GnoswapAdmin":99999900000000},"pool":[{"poolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","startTimestamp":1234567890,"tier":1,"numPoolSameTier":1,"poolReward":0,"position":[]}]}`) - std.TestSkipHeights(1) }) } @@ -133,10 +130,52 @@ func testStakeToken_1_2(t *testing.T) { StakeToken(2) std.TestSkipHeights(1) + + // clear reward + CollectReward(1, false) + CollectReward(2, false) + + std.TestSkipHeights(1) + }) + + /* + total staked liquidity: 1058618 + > position 01: 33360 || 3.1512783648% + > position 02: 1025258 || 96.8487216352% + + reward per block: 10702054 + > position 01: 10702054 * 3.1512783648% = 337251.512291213 + > warmUp 30% => 337251.512291213 * 30% = 101175.4536873639 + > position 02: 10702054 * 96.8487216352% = 10364802.487708787 + > warmUp 30% => 10364802.487708787 * 30% = 3109440.7463126361 + */ + + t.Run("collect reward position 01, while in-range", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(1, false) + afterGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(afterGns-beforeGns, 101175)) + // reward 101175 + // penalty 236076 + // total 337251 + + }) + + t.Run("collect reward position 02, while in-range", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + beforeGns := gns.BalanceOf(admin) + CollectReward(2, false) + afterGns := gns.BalanceOf(admin) + uassert.True(t, isInErrorRange(afterGns-beforeGns, 3109440)) + // reward 3109440 + // penalty 7255362 + // total 10364802 }) } -// XXX func testMakePosition1OutRange(t *testing.T) { t.Run("make position 01 out of range", func(t *testing.T) { poolTick := pl.PoolGetSlot0Tick(poolPath) @@ -151,7 +190,6 @@ func testMakePosition1OutRange(t *testing.T) { bar.Approve(a2u(consts.ROUTER_ADDR), consts.UINT64_MAX) qux.Approve(a2u(consts.ROUTER_ADDR), consts.UINT64_MAX) - // PANIC: ./staker: test pkg: panic: runtime error: invalid memory address or nil pointer dereference tokenIn, tokenOut := rr.ExactInSwapRoute( barPath, quxPath, @@ -159,40 +197,38 @@ func testMakePosition1OutRange(t *testing.T) { "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100", "100", "0", - time.Now().Unix()+100, - // max_timeout, + max_timeout, ) uassert.Equal(t, tokenIn, "10000") uassert.Equal(t, tokenOut, "-9884") std.TestSkipHeights(1) + + // position-01 became out-range + // position-01 is only in-range position }) } -// XXX func testCheckRewardAfter1Block(t *testing.T) { - // t.Run("check reward after 1 block", func(t *testing.T) { - // poolTick := pl.PoolGetSlot0Tick(poolPath) - // uassert.Equal(t, poolTick, int32(-194)) - // // AT THIS POINT position #1 is out of range - - // lpToken01Rewards := ApiGetRewardsByLpTokenId(1) - // uassert.Equal(t, lpToken01Rewards, `{"stat":{"height":129,"timestamp":1234567902},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":101175,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898}]}]}`) + t.Run("check reward position 01, out-range", func(t *testing.T) { + std.TestSetRealm(adminRealm) - // lpToken02Rewards := ApiGetRewardsByLpTokenId(2) - // uassert.Equal(t, lpToken02Rewards, `{"stat":{"height":129,"timestamp":1234567902},"response":[{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":6320056,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898}]}]}`) + beforeGns := gns.BalanceOf(admin) + CollectReward(1, false) + afterGns := gns.BalanceOf(admin) + uassert.Equal(t, uint64(0), afterGns-beforeGns) // out range has no reward + }) - // POSITION #1 PREVIOUS REWARD - // `{"stat":{"height":128,"timestamp":1234567900},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":101175,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898}]}]}`) + t.Run("check reward position 02, in-range", func(t *testing.T) { + std.TestSetRealm(adminRealm) - // POSITION #2 PREVIOUS REWARD - // `{"stat":{"height":128,"timestamp":1234567900},"response":[{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":3109440,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898}]}]}`) + beforeGns := gns.BalanceOf(admin) + CollectReward(2, false) + afterGns := gns.BalanceOf(admin) + // reward 3210615 + // penalty 7491438 + // total 10702053 - /* - PREVIOUS REWARD -> NOW - - POSITION #1 - 101175 > 101175 - - POSITION #2 - 3109440 > 6320056 - */ - // }) + // since position-01 has become out-range, position-02 is the only in-range position + // so position-02 will get entire block reward + }) } diff --git a/staker/__TEST_staker_short_warmup_period_internal_external_91_test.gnoA b/staker/__TEST_staker_short_warmup_period_internal_external_91_test.gnoA deleted file mode 100644 index d38f79e47..000000000 --- a/staker/__TEST_staker_short_warmup_period_internal_external_91_test.gnoA +++ /dev/null @@ -1,260 +0,0 @@ -// internal and external incentive + warm up period testing -// with one external incentives for same pool -// bar -// with internal incentive for same pool -// and position range will -// 1. in-range -// 2. out-range -// 3. in-range - -package staker - -import ( - "math" - "std" - "testing" - "time" - - "gno.land/r/demo/users" - "gno.land/p/demo/uassert" - - "gno.land/r/gnoswap/v1/consts" - - en "gno.land/r/gnoswap/v1/emission" - pl "gno.land/r/gnoswap/v1/pool" - pn "gno.land/r/gnoswap/v1/position" - - "gno.land/r/gnoswap/v1/gnft" - "gno.land/r/gnoswap/v1/gns" - - "gno.land/r/onbloc/bar" - "gno.land/r/onbloc/qux" -) - -var ( - rouRealm = std.NewCodeRealm(consts.ROUTER_PATH) - anoAdmin = users.Resolve(admin) -) - -func TestShortWarmUpInternalAndExternalPositionInRangeAndOutRange(t *testing.T) { - testInit(t) - testCreatePool(t) - testMintBarQux100_1(t) - testMintBarQux100_2(t) - testCreateExternalIncentiveBar(t) - testStakeToken_1_AND_2(t) - testCheckCurrentReward(t) - testMakePositionOutRange(t) - testCheckReward(t) - testMakePositionInRange(t) -} - -func testInit(t *testing.T) { - t.Run("initialize", func(t *testing.T) { - // override warm-up period for testing - changeWarmup(t, 0, 150) - changeWarmup(t, 1, 300) - changeWarmup(t, 2, 900) - changeWarmup(t, 3, math.MaxInt64) - - // set unstaking fee to 0 - // SetUnstakingFeeByAdmin(0) - }) -} - -func testCreatePool(t *testing.T) { - t.Run("create pool", func(t *testing.T) { - std.TestSetRealm(adminRealm) - gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()*3) - pl.CreatePool(barPath, quxPath, 100, "79228162514264337593543950337") - std.TestSkipHeights(1) - }) -} - -func testMintBarQux100_1(t *testing.T) { - t.Run("mint position 01, bar:qux:100", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - - tokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - fee100, // fee - int32(-50), // tickLower - int32(50), // tickUpper - "50", // amount0Desired - "50", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - anoAdmin, - anoAdmin, - ) - - uassert.Equal(t, tokenId, uint64(1)) - owner, err := gnft.OwnerOf(tid(tokenId)) - uassert.NoError(t, err) - uassert.Equal(t, owner, users.Resolve(admin)) - uassert.Equal(t, liquidity, "20026") - - std.TestSkipHeights(1) - }) -} - -func testMintBarQux100_2(t *testing.T) { - t.Run("mint position 02, bar:qux:100", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - - tokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - fee100, // fee - int32(-5000), // tickLower - int32(5000), // tickUpper - "5000000", // amount0Desired - "5000000", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - anoAdmin, - anoAdmin, - ) - - uassert.Equal(t, tokenId, uint64(2)) - owner, err := gnft.OwnerOf(tid(tokenId)) - uassert.NoError(t, err) - uassert.Equal(t, owner, users.Resolve(admin)) - uassert.Equal(t, liquidity, "22605053") - - std.TestSkipHeights(1) - }) -} - -func testCreateExternalIncentiveBar(t *testing.T) { - t.Run("create external incentive, bar", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - bar.Approve(a2u(consts.STAKER_ADDR), consts.UINT64_MAX) - gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount) - - CreateExternalIncentive( - "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100", - barPath, - 900000000, - 1234569600, - 1234569600+TIMESTAMP_90DAYS, - ) - std.TestSkipHeights(1) - }) -} - -func testStakeToken_1_AND_2(t *testing.T) { - t.Run("stake position 01 and 02", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - gnft.Approve(GetOrigPkgAddr(), tid(1)) - StakeToken(1) - - gnft.Approve(GetOrigPkgAddr(), tid(2)) - StakeToken(2) - - std.TestSkipHeights(1) - }) -} - -func testBeforeActive(t *testing.T) { - t.Run("before active", func(t *testing.T) { - en.MintAndDistributeGns() - std.TestSkipHeights(1) - }) -} - -func testAfterActive(t *testing.T) { - t.Run("after active", func(t *testing.T) { - std.TestSkipHeights(849) // in active - std.TestSkipHeights(1) // active // but no block passed since active - std.TestSkipHeights(50) // skip 50 more block - std.TestSkipHeights(1) - }) -} - -func testCheckCurrentReward(t *testing.T) { - t.Run("check current reward", func(t *testing.T) { - std.TestSkipHeights(199) // skip 1 + 199 = 200 more block - - // agr := ApiGetRewards() - // uassert.Equal(t, agr, `{"stat":{"height":1229,"timestamp":1234570102},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":7028690,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898},{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":19,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]},{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":7933895743,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898},{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":22085,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) - - // check if position is in range - poolCurrentTick := pl.PoolGetSlot0Tick("gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100") - uassert.Equal(t, poolCurrentTick, int32(0)) // pool's current tick is 0 - }) -} - -func testMakePositionOutRange(t *testing.T) { - t.Run("make position out of range", func(t *testing.T) { - std.TestSetRealm(rouRealm) - amount0, amount1 := pl.Swap( - barPath, - quxPath, - fee100, - anoAdmin, - true, - "70000", - consts.MIN_PRICE, - anoAdmin, - ) - uassert.Equal(t, amount0, "70000") - uassert.Equal(t, amount1, "-69773") - std.TestSkipHeights(1) - - // check if position is in range - poolCurrentTick := pl.PoolGetSlot0Tick("gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100") - uassert.Equal(t, poolCurrentTick, int32(-62)) // pool's current tick is 0 - - // agr := ApiGetRewards() - // uassert.Equal(t, agr, `{"stat":{"height":1230,"timestamp":1234570104},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":7028690,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898},{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":19,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]},{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":7944597802,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898},{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":22200,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) - }) -} - -func testCheckReward(t *testing.T) { - t.Run("check reward", func(t *testing.T) { - std.TestSkipHeights(100) - - // only position 2's reward should be increase - // position 1 is out of range - // agr := ApiGetRewards() - // uassert.Equal(t, agr, `{"stat":{"height":1330,"timestamp":1234570304},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":7028690,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898},{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":19,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]},{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":9014803252,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898},{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":36180,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) - }) -} - -func testMakePositionInRange(t *testing.T) { - t.Run("make position in range", func(t *testing.T) { - std.TestSetRealm(rouRealm) - amount0, amount1 := pl.Swap( - barPath, - quxPath, - fee100, - anoAdmin, - false, - "70000", - consts.MAX_PRICE, - anoAdmin, - ) - // uassert.Equal(t, amount0, "70000") - // uassert.Equal(t, amount1, "-69775") - std.TestSkipHeights(100) - - // check if position is in range - poolCurrentTick := pl.PoolGetSlot0Tick("gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100") - uassert.Equal(t, poolCurrentTick, int32(0)) // pool's current tick is 0 - - // agr := ApiGetRewards() - // uassert.Equal(t, agr, `{"stat":{"height":1430,"timestamp":1234570504},"response":[{"lpTokenId":1,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":7975952,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898},{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":31,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]},{"lpTokenId":2,"address":"g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d","rewards":[{"incentiveType":"INTERNAL","incentiveId":"","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/gnoswap/v1/gns","rewardTokenAmount":10084061436,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234567898},{"incentiveType":"EXTERNAL","incentiveId":"ZzFsbXZycnJyNGVyMnVzODRoMjczMnNydTc2Yzl6bDJudmtuaGE4Yzpnbm8ubGFuZC9yL29uYmxvYy9iYXI6Z25vLmxhbmQvci9vbmJsb2MvcXV4OjEwMDpnbm8ubGFuZC9yL29uYmxvYy9iYXI6MTIzNDU2OTYwMDoxMjQyMzQ1NjAwOjEyNg==","targetPoolPath":"gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100","rewardTokenPath":"gno.land/r/onbloc/bar","rewardTokenAmount":52369,"stakeTimestamp":1234567898,"stakeHeight":127,"incentiveStart":1234569600}]}]}`) - }) -} diff --git a/staker/__TEST_staker_short_warmup_period_internal_external_90_test.gnoA b/staker/__TEST_staker_short_warmup_period_internal_gns_and_external_gns_90_test.gnoA similarity index 79% rename from staker/__TEST_staker_short_warmup_period_internal_external_90_test.gnoA rename to staker/__TEST_staker_short_warmup_period_internal_gns_and_external_gns_90_test.gnoA index aa06fbb41..45d3b27f0 100644 --- a/staker/__TEST_staker_short_warmup_period_internal_external_90_test.gnoA +++ b/staker/__TEST_staker_short_warmup_period_internal_gns_and_external_gns_90_test.gnoA @@ -15,7 +15,6 @@ import ( "gno.land/r/gnoswap/v1/consts" - en "gno.land/r/gnoswap/v1/emission" pl "gno.land/r/gnoswap/v1/pool" pn "gno.land/r/gnoswap/v1/position" @@ -33,16 +32,15 @@ func TestShortWarmUpInternalAndExternalAllPositionInRange(t *testing.T) { testCreateExternalIncentiveBar(t) testCreateExternalIncentiveGns(t) testStakeToken_1(t) - testDuration200(t) - testCollectReward(t) - testCollectRewardSameBlockNoReward(t) + testAfterActive(t) testCollectRewardSingleBlock(t) + // testCollectRewardSameBlockNoReward(t) } func testInit(t *testing.T) { t.Run("initialize", func(t *testing.T) { + std.TestSetRealm(adminRealm) deletePoolTier(t, MUST_EXISTS_IN_TIER_1) - addPoolTier(t, `gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100`, 1) // override warm-up period for testing changeWarmup(t, 0, 150) @@ -67,8 +65,9 @@ func testCreatePool(t *testing.T) { gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()*3) pl.CreatePool(barPath, quxPath, 100, "79228162514264337593543950337") - pl.CreatePool(barPath, bazPath, 3000, "79228162514264337593543950337") + SetPoolTierByAdmin(`gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100`, 1) + pl.CreatePool(barPath, bazPath, 3000, "79228162514264337593543950337") std.TestSkipHeights(1) }) } @@ -114,7 +113,7 @@ func testCreateExternalIncentiveBar(t *testing.T) { CreateExternalIncentive( "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100", barPath, - 20000000, + 200000000, 1234569600, 1234569600+TIMESTAMP_90DAYS, ) @@ -132,7 +131,7 @@ func testCreateExternalIncentiveGns(t *testing.T) { CreateExternalIncentive( "gno.land/r/onbloc/bar:gno.land/r/onbloc/qux:100", consts.GNS_PATH, - 20000000, + 500000000, 1234569600, 1234569600+TIMESTAMP_90DAYS, ) @@ -153,60 +152,62 @@ func testStakeToken_1(t *testing.T) { func testAfterActive(t *testing.T) { t.Run("after active", func(t *testing.T) { - std.TestSkipHeights(849) // in active - std.TestSkipHeights(1) // active // but no block passed since active - std.TestSkipHeights(50) // skip 50 more block + std.TestSkipHeights(978 - std.GetHeight() - 1) // in active + std.TestSkipHeights(1) // active // but no block passed since active + std.TestSkipHeights(50) // skip 50 more block std.TestSkipHeights(1) - }) -} -func testDuration200(t *testing.T) { - t.Run("duration 200", func(t *testing.T) { - std.TestSkipHeights(199) // skip 1 + 199 = 200 more block - en.MintAndDistributeGns() + // clear reward + std.TestSetRealm(adminRealm) + CollectReward(1, false) }) } -func testCollectReward(t *testing.T) { +func testCollectRewardSingleBlock(t *testing.T) { t.Run("collect reward", func(t *testing.T) { std.TestSetRealm(adminRealm) oldBar := bar.BalanceOf(admin) oldGns := gns.BalanceOf(admin) + std.TestSkipHeights(1) CollectReward(1, false) + // 1 block passed + // position warmup is 70% newBar := bar.BalanceOf(admin) newGns := gns.BalanceOf(admin) - uassert.Equal(t, newBar-oldBar, uint64(485)) - uassert.Equal(t, newGns-oldGns, uint64(7861515679)) - }) -} + // external: bar ( this incentive program reward per block is 51) + // reward 35 + // penalty 15 -func testCollectRewardSameBlockNoReward(t *testing.T) { - t.Run("collect reward same block no reward", func(t *testing.T) { - std.TestSetRealm(adminRealm) + // external: gns ( this incentive program reward per block is 128) + // reward 88 + // penalty 39 - oldBar := bar.BalanceOf(admin) - oldGns := gns.BalanceOf(admin) + // internal: gns + // reward 7491437 + // penalty 3210616 + // total 10702053 - CollectReward(1, false) + // gns increased by 2 reasons + // internal + external - newBar := bar.BalanceOf(admin) - newGns := gns.BalanceOf(admin) + // bar increased by 1 reason + // external - // same block no change - uassert.Equal(t, newBar-oldBar, uint64(0)) - uassert.Equal(t, newGns-oldGns, uint64(0)) + barReward := newBar - oldBar + uassert.Equal(t, barReward, uint64(35)) + + gnsReward := newGns - oldGns + uassert.Equal(t, gnsReward, uint64(88+7491437)) }) } -func testCollectRewardSingleBlock(t *testing.T) { - t.Run("collect reward single block", func(t *testing.T) { - std.TestSkipHeights(1) - +func testCollectRewardSameBlockNoReward(t *testing.T) { + t.Run("collect reward same block no reward", func(t *testing.T) { std.TestSetRealm(adminRealm) oldBar := bar.BalanceOf(admin) @@ -217,7 +218,8 @@ func testCollectRewardSingleBlock(t *testing.T) { newBar := bar.BalanceOf(admin) newGns := gns.BalanceOf(admin) - uassert.Equal(t, newBar-oldBar, uint64(2)) // 2 bar from external incentive - uassert.Equal(t, newGns-oldGns, uint64(10595037)) // 10595035 gns from internal + 2 gns from external + // same block, no reward + uassert.Equal(t, newBar-oldBar, uint64(0)) + uassert.Equal(t, newGns-oldGns, uint64(0)) }) } diff --git a/staker/__TEST_staker_short_wramup_period_staker_external_test.gnoA b/staker/__TEST_staker_short_wramup_period_staker_external_test.gnoA deleted file mode 100644 index a1316ddf2..000000000 --- a/staker/__TEST_staker_short_wramup_period_staker_external_test.gnoA +++ /dev/null @@ -1,233 +0,0 @@ -package staker - -import ( - "std" - "testing" - - "gno.land/p/demo/uassert" - "gno.land/r/demo/users" - "gno.land/r/gnoswap/v1/consts" - - pl "gno.land/r/gnoswap/v1/pool" - pn "gno.land/r/gnoswap/v1/position" - - "gno.land/r/gnoswap/v1/gns" - "gno.land/r/onbloc/bar" - "gno.land/r/onbloc/baz" - "gno.land/r/onbloc/obl" - "gno.land/r/onbloc/qux" - - "gno.land/r/gnoswap/v1/gnft" -) - -func TestExternalIncentive(t *testing.T) { - testInit(t) - testPoolCreatePool(t) - testMintBarQux500_1(t) - testMintBarBaz100_2(t) - testMintBarBaz100_3(t) - testCreateExternalIncentive(t) - testStakeExternal_2(t) - testStakeExternal_3(t) - testCollectExternalReward_2(t) - testCollectExternalReward_3(t) -} - -func testInit(t *testing.T) { - t.Run("initialize", func(t *testing.T) { - changeWarmup(t, 3, 901) - changeWarmup(t, 2, 301) - changeWarmup(t, 1, 151) - changeWarmup(t, 0, 1) - }) -} - -func testPoolCreatePool(t *testing.T) { - t.Run("pool create pool", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()*2) - std.TestSkipHeights(1) - - pl.CreatePool(barPath, quxPath, 500, "130621891405341611593710811006") // internal, tier 1 // tick 10_000 ≈ x2.7 - pl.CreatePool(barPath, bazPath, 100, "79228162514264337593543950337") // will be external - - std.TestSkipHeights(3) - }) -} - -func testMintBarQux500_1(t *testing.T) { - t.Run("mint position 01, bar:qux:500", func(t *testing.T) { - std.TestSetRealm(adminRealm) - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - qux.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - tokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - quxPath, // token1 - fee500, // fee - int32(9000), // tickLower - int32(11000), // tickUpper - "100000", // amount0Desired - "100000", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - std.TestSkipHeights(1) - - uassert.Equal(t, tokenId, uint64(1)) - uassert.Equal(t, amount0, "36790") - uassert.Equal(t, amount1, "100000") - }) -} - -func testMintBarBaz100_2(t *testing.T) { - t.Run("mint position 02, bar:baz:100", func(t *testing.T) { - std.TestSetRealm(adminRealm) - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - baz.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - tokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - bazPath, // token1 - fee100, // fee - int32(-1000), // tickLower - int32(1000), // tickUpper - "100000", // amount0Desired - "100000", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - std.TestSkipHeights(1) - - uassert.Equal(t, tokenId, uint64(2)) - uassert.Equal(t, amount0, "100000") - uassert.Equal(t, amount1, "100000") - }) -} - -func testMintBarBaz100_3(t *testing.T) { - t.Run("mint position 03, bar:baz:100", func(t *testing.T) { - std.TestSetRealm(adminRealm) - bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - baz.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - std.TestSkipHeights(2) - - tokenId, liquidity, amount0, amount1 := pn.Mint( - barPath, // token0 - bazPath, // token1 - fee100, // fee - int32(-1000), // tickLower - int32(1000), // tickUpper - "100000", // amount0Desired - "100000", // amount1Desired - "1", // amount0Min - "1", // amount1Min - max_timeout, - adminAddr, - adminAddr, - ) - std.TestSkipHeights(1) - - uassert.Equal(t, tokenId, uint64(3)) - uassert.Equal(t, amount0, "100000") - uassert.Equal(t, amount1, "100000") - }) -} - -func testCreateExternalIncentive(t *testing.T) { - t.Run("create external incentive", func(t *testing.T) { - std.TestSetRealm(adminRealm) - obl.Approve(a2u(consts.STAKER_ADDR), uint64(100_000_000)) - std.TestSkipHeights(1) - - gns.Approve(a2u(consts.STAKER_ADDR), depositGnsAmount) - - AddToken(oblPath) - CreateExternalIncentive( - "gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:100", // targetPoolPath - "gno.land/r/onbloc/obl", // rewardToken - uint64(100000000), // rewardAmount - int64(1234569600), // startTimestamp - int64(1234569600+TIMESTAMP_90DAYS), // endTimestamp - ) - - std.TestSkipHeights(1) - }) -} - -func testStakeExternal_2(t *testing.T) { - t.Run("stake position 02, bar:baz:100", func(t *testing.T) { - std.TestSkipHeights(900) // active - - std.TestSetRealm(adminRealm) - gnft.Approve(GetOrigPkgAddr(), tid(2)) - StakeToken(2) - - std.TestSkipHeights(2) - - owner, err := gnft.OwnerOf(tid(2)) - uassert.NoError(t, err) - uassert.Equal(t, owner, GetOrigPkgAddr()) - uassert.Equal(t, deposits.Size(), 1) - }) -} - -func testStakeExternal_3(t *testing.T) { - t.Run("stake position 03, bar:baz:100", func(t *testing.T) { - std.TestSetRealm(adminRealm) - gnft.Approve(GetOrigPkgAddr(), tid(3)) - StakeToken(3) - - std.TestSkipHeights(2) - - owner, err := gnft.OwnerOf(tid(3)) - uassert.NoError(t, err) - uassert.Equal(t, owner, GetOrigPkgAddr()) - uassert.Equal(t, deposits.Size(), 2) - }) -} - -func testCollectExternalReward_2(t *testing.T) { - t.Run("collect external reward 02", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - // before claim - oblOld := obl.BalanceOf(a2u(adminAddr)) - uassert.Equal(t, oblOld, uint64(99999900000000)) - - std.TestSkipHeights(777601) // 45 days + 1 block - CollectReward(2, false) // XXXXX - - std.TestSkipHeights(1) - - oblNew := obl.BalanceOf(a2u(adminAddr)) - uassert.Equal(t, oblNew-oblOld, uint64(9895486)) - }) -} - -func testCollectExternalReward_3(t *testing.T) { - t.Run("collect external reward 03", func(t *testing.T) { - std.TestSetRealm(adminRealm) - - // before claim - oblOld := obl.BalanceOf(a2u(adminAddr)) - uassert.Equal(t, oblOld, uint64(99999909895486)) - - std.TestSkipHeights(1) - CollectReward(3, false) - - std.TestSkipHeights(1) - - oblNew := obl.BalanceOf(a2u(adminAddr)) - uassert.Equal(t, oblNew-oblOld, uint64(9895478)) - }) -} diff --git a/staker/__TEST_token_uri_in_same_block_test.gnoA b/staker/__TEST_token_uri_in_same_block_test.gnoA index 1fc2e6980..d1c7e8f60 100644 --- a/staker/__TEST_token_uri_in_same_block_test.gnoA +++ b/staker/__TEST_token_uri_in_same_block_test.gnoA @@ -23,14 +23,15 @@ func TestPoolInitCreatePool(t *testing.T) { gns.Approve(a2u(consts.POOL_ADDR), pl.GetPoolCreationFee()) pl.CreatePool(barPath, fooPath, fee500, "130621891405341611593710811006") // tick = 10000 + SetPoolTierByAdmin("gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500", 1) + + std.TestSkipHeights(1) } func TestMintPositionAndCheckURI(t *testing.T) { std.TestSetRealm(adminRealm) bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) foo.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX) - poolPath := "gno.land/r/onbloc/bar:gno.land/r/onbloc/foo:500" - addPoolTier(t, poolPath, 1) tokenId1, _, _, _, _ := MintAndStake( barPath, diff --git a/staker/_helper_test.gno b/staker/_helper_test.gno index 7cd1a874d..14c5a0e87 100644 --- a/staker/_helper_test.gno +++ b/staker/_helper_test.gno @@ -444,7 +444,7 @@ func burnAllNFT(t *testing.T) { func deletePoolTier(t *testing.T, poolPath string) { t.Helper() if poolTier != nil { - poolTier.changeTier(uint64(std.GetHeight()), pools, poolPath, 0) + poolTier.changeTier(std.GetHeight(), pools, poolPath, 0) } else { panic("poolTier is nil") } @@ -459,11 +459,11 @@ func addPoolTier(t *testing.T, poolPath string, tier uint64) { panic(addDetailToError( errPoolNotFound, ufmt.Sprintf("unknown error - pools is nil"))) } - poolTier.changeTier(uint64(std.GetHeight()), pools, poolPath, tier) + poolTier.changeTier(std.GetHeight(), pools, poolPath, tier) } else { - poolTier = NewPoolTier(uint64(std.GetHeight()), poolPath, en.GetStakerEmissionUpdates) + poolTier = NewPoolTier(pools, std.GetHeight(), poolPath, en.GetEmission, en.GetHalvingBlocksInRange) pools.GetOrCreate(poolPath) // must update pools tree - poolTier.changeTier(uint64(std.GetHeight()), pools, poolPath, tier) + poolTier.changeTier(std.GetHeight(), pools, poolPath, tier) } } @@ -481,7 +481,7 @@ func getNumPoolTiers(t *testing.T) (uint64, uint64, uint64) { } type gnsBalanceTracker struct { - height uint64 + height int64 stakerBalance uint64 devOpsBalance uint64 communityPoolBalance uint64 @@ -494,7 +494,7 @@ func gnsBalanceCheck(t *testing.T, beforeBalance gnsBalanceTracker, printChange t.Helper() caller := std.PrevRealm().Addr() - height := uint64(std.GetHeight()) + height := std.GetHeight() stakerBalance := gns.BalanceOf(a2u(consts.STAKER_ADDR)) devOpsBalance := gns.BalanceOf(a2u(consts.DEV_OPS)) communityPoolBalance := gns.BalanceOf(a2u(consts.COMMUNITY_POOL_ADDR)) @@ -502,19 +502,6 @@ func gnsBalanceCheck(t *testing.T, beforeBalance gnsBalanceTracker, printChange protocolFeeBalance := gns.BalanceOf(a2u(consts.PROTOCOL_FEE_ADDR)) callerBalance := gns.BalanceOf(a2u(caller)) - if printChange { - println("[START] gnsBalanceCheck") - println("height", beforeBalance.height, "->", height, "|| diff", height-beforeBalance.height) - println("stakerBalance", beforeBalance.stakerBalance, "->", stakerBalance, "|| diff", stakerBalance-beforeBalance.stakerBalance) - println("devOpsBalance", beforeBalance.devOpsBalance, "->", devOpsBalance, "|| diff", devOpsBalance-beforeBalance.devOpsBalance) - println("communityPoolBalance", beforeBalance.communityPoolBalance, "->", communityPoolBalance, "|| diff", communityPoolBalance-beforeBalance.communityPoolBalance) - println("govStakerBalance", beforeBalance.govStakerBalance, "->", govStakerBalance, "|| diff", govStakerBalance-beforeBalance.govStakerBalance) - println("protocolFeeBalance", beforeBalance.protocolFeeBalance, "->", protocolFeeBalance, "|| diff", protocolFeeBalance-beforeBalance.protocolFeeBalance) - println("callerBalance", beforeBalance.callerBalance, "->", callerBalance, "|| diff", callerBalance-beforeBalance.callerBalance) - println("[END] gnsBalanceCheck") - println() - } - return gnsBalanceTracker{ height: height, stakerBalance: stakerBalance, @@ -696,7 +683,7 @@ func makePositionsNode(t *testing.T, poolPath string) []*json.Node { stakedDuration := std.GetHeight() - deposit.stakeHeight ratio := getRewardRatio(t, stakedDuration) - rewardByWarmup := calcPositionRewardByWarmups(uint64(std.GetHeight()), uint64(tokenId)) + rewardByWarmup := calcPositionRewardByWarmups(std.GetHeight(), tokenId) if len(rewardByWarmup) != 4 { panic("len(rewardByWarmup) != 4") } diff --git a/staker/api.gno b/staker/api.gno index c3793bc72..94fd61900 100644 --- a/staker/api.gno +++ b/staker/api.gno @@ -1,7 +1,16 @@ package staker import ( + "math" "std" + "strconv" + "time" + + "gno.land/p/demo/json" + "gno.land/p/demo/ufmt" + + "gno.land/r/gnoswap/v1/consts" + en "gno.land/r/gnoswap/v1/emission" ) type RewardToken struct { @@ -13,8 +22,8 @@ type ApiExternalIncentive struct { IncentiveId string `json:"incentiveId"` PoolPath string `json:"poolPath"` RewardToken string `json:"rewardToken"` - RewardAmount string `json:"rewardAmount"` - RewardLeft string `json:"rewardLeft"` + RewardAmount uint64 `json:"rewardAmount"` + RewardLeft uint64 `json:"rewardLeft"` StartTimestamp int64 `json:"startTimestamp"` EndTimestamp int64 `json:"endTimestamp"` Active bool `json:"active"` @@ -30,1321 +39,1008 @@ type ApiInternalIncentive struct { RewardPerBlock string `json:"rewardPerBlock"` } -// func ApiGetRewardTokens() string { -// en.MintAndDistributeGns() -// -// rewardTokens := []RewardToken{} -// -// poolList := pl.PoolGetPoolList() -// for _, poolPath := range poolList { -// thisPoolRewardTokens := []string{} -// -// // HANDLE INTERNAL -// if isExistPoolTiers(poolPath) { -// thisPoolRewardTokens = append(thisPoolRewardTokens, consts.GNS_PATH) -// } -// -// // HANDLE EXTERNAL -// ictvList, exists := poolIncentives.Get(poolPath) -// if !exists { -// continue -// } -// -// for _, incentiveId := range ictvList { -// ictv, exist := incentives.Get(incentiveId) -// if !exist { -// continue -// } -// if ictv.RewardToken() == "" { -// continue -// } -// thisPoolRewardTokens = append(thisPoolRewardTokens, ictv.RewardToken()) -// } -// -// if len(thisPoolRewardTokens) == 0 { -// continue -// } -// -// rewardTokens = append(rewardTokens, RewardToken{ -// PoolPath: poolPath, -// RewardsTokenList: thisPoolRewardTokens, -// }) -// } -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, rewardToken := range rewardTokens { -// _rewardTokenNode := json.ObjectNode("", map[string]*json.Node{ -// "poolPath": json.StringNode("poolPath", rewardToken.PoolPath), -// "tokens": json.ArrayNode("tokens", makeRewardTokensArray(rewardToken.RewardsTokenList)), -// }) -// responses.AppendArray(_rewardTokenNode) -// } -// -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetRewardTokensByPoolPath(targetPoolPath string) string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// rewardTokens := []RewardToken{} -// -// poolList := pl.PoolGetPoolList() -// for _, poolPath := range poolList { -// if poolPath != targetPoolPath { -// continue -// } -// -// thisPoolRewardTokens := []string{} -// -// // HANDLE INTERNAL -// if isExistPoolTiers(poolPath) { -// thisPoolRewardTokens = append(thisPoolRewardTokens, consts.GNS_PATH) -// } -// -// // HANDLE EXTERNAL -// ictvList, exists := poolIncentives.Get(poolPath) -// if !exists { -// continue -// } -// -// for _, incentiveId := range ictvList { -// ictv, exist := incentives.Get(incentiveId) -// if !exist { -// continue -// } -// thisPoolRewardTokens = append(thisPoolRewardTokens, ictv.RewardToken()) -// } -// -// rewardTokens = append(rewardTokens, RewardToken{ -// PoolPath: poolPath, -// RewardsTokenList: thisPoolRewardTokens, -// }) -// } -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, rewardToken := range rewardTokens { -// _rewardTokenNode := json.ObjectNode("", map[string]*json.Node{ -// "poolPath": json.StringNode("poolPath", rewardToken.PoolPath), -// "tokens": json.ArrayNode("tokens", makeRewardTokensArray(rewardToken.RewardsTokenList)), -// }) -// responses.AppendArray(_rewardTokenNode) -// } -// -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetExternalIncentives() string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// updateExternalIncentiveLeftAmount() -// -// apiExternalIncentives := []ApiExternalIncentive{} -// -// for incentiveId, incentive := range incentives { -// apiExternalIncentives = append(apiExternalIncentives, ApiExternalIncentive{ -// IncentiveId: incentiveId, -// PoolPath: incentive.targetPoolPath, -// RewardToken: incentive.rewardToken, -// RewardAmount: incentive.rewardAmount.ToString(), -// RewardLeft: incentive.rewardLeft.ToString(), -// StartTimestamp: incentive.startTimestamp, -// EndTimestamp: incentive.endTimestamp, -// Refundee: incentive.refundee.String(), -// CreatedHeight: incentive.createdHeight, -// DepositGnsAmount: incentive.depositGnsAmount, -// }) -// } -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, incentive := range apiExternalIncentives { -// active := false -// if time.Now().Unix() >= incentive.StartTimestamp && time.Now().Unix() <= incentive.EndTimestamp { -// active = true -// } -// -// _incentiveNode := json.ObjectNode("", map[string]*json.Node{ -// "incentiveId": json.StringNode("incentiveId", incentive.IncentiveId), -// "poolPath": json.StringNode("poolPath", incentive.PoolPath), -// "rewardToken": json.StringNode("rewardToken", incentive.RewardToken), -// "rewardAmount": json.StringNode("rewardAmount", incentive.RewardAmount), -// "rewardLeft": json.StringNode("rewardLeft", incentive.RewardLeft), -// "startTimestamp": json.NumberNode("startTimestamp", float64(incentive.StartTimestamp)), -// "endTimestamp": json.NumberNode("endTimestamp", float64(incentive.EndTimestamp)), -// "active": json.BoolNode("active", active), -// "refundee": json.StringNode("refundee", incentive.Refundee), -// "createdHeight": json.NumberNode("createdHeight", float64(incentive.CreatedHeight)), -// "depositGnsAmount": json.NumberNode("depositGnsAmount", float64(incentive.DepositGnsAmount)), -// }) -// responses.AppendArray(_incentiveNode) -// } -// -// // RETURN -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetExternalIncentiveById(incentiveId string) string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// updateExternalIncentiveLeftAmount() -// -// apiExternalIncentives := []ApiExternalIncentive{} -// -// incentive, exist := incentives.Get(incentiveId) -// if !exist { -// panic(addDetailToError( -// errDataNotFound, -// ufmt.Sprintf("incentive(%s) not found", incentiveId), -// )) -// } -// -// apiExternalIncentives = append(apiExternalIncentives, ApiExternalIncentive{ -// IncentiveId: incentiveId, -// PoolPath: incentive.targetPoolPath, -// RewardToken: incentive.rewardToken, -// RewardAmount: incentive.rewardAmount.ToString(), -// RewardLeft: incentive.rewardLeft.ToString(), -// StartTimestamp: incentive.startTimestamp, -// EndTimestamp: incentive.endTimestamp, -// Refundee: incentive.refundee.String(), -// CreatedHeight: incentive.createdHeight, -// DepositGnsAmount: incentive.depositGnsAmount, -// }) -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, incentive := range apiExternalIncentives { -// active := false -// if time.Now().Unix() >= incentive.StartTimestamp && time.Now().Unix() <= incentive.EndTimestamp { -// active = true -// } -// -// _incentiveNode := json.ObjectNode("", map[string]*json.Node{ -// "incentiveId": json.StringNode("incentiveId", incentive.IncentiveId), -// "poolPath": json.StringNode("poolPath", incentive.PoolPath), -// "rewardToken": json.StringNode("rewardToken", incentive.RewardToken), -// "rewardAmount": json.StringNode("rewardAmount", incentive.RewardAmount), -// "rewardLeft": json.StringNode("rewardLeft", incentive.RewardLeft), -// "startTimestamp": json.NumberNode("startTimestamp", float64(incentive.StartTimestamp)), -// "endTimestamp": json.NumberNode("endTimestamp", float64(incentive.EndTimestamp)), -// "active": json.BoolNode("active", active), -// "refundee": json.StringNode("refundee", incentive.Refundee), -// "createdHeight": json.NumberNode("createdHeight", float64(incentive.CreatedHeight)), -// "depositGnsAmount": json.NumberNode("depositGnsAmount", float64(incentive.DepositGnsAmount)), -// }) -// responses.AppendArray(_incentiveNode) -// } -// -// // RETURN -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetExternalIncentivesByPoolPath(targetPoolPath string) string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// updateExternalIncentiveLeftAmount() -// -// apiExternalIncentives := []ApiExternalIncentive{} -// -// for incentiveId, incentive := range incentives { -// if incentive.targetPoolPath != targetPoolPath { -// continue -// } -// -// apiExternalIncentives = append(apiExternalIncentives, ApiExternalIncentive{ -// IncentiveId: incentiveId, -// PoolPath: incentive.targetPoolPath, -// RewardToken: incentive.rewardToken, -// RewardAmount: incentive.rewardAmount.ToString(), -// RewardLeft: incentive.rewardLeft.ToString(), -// StartTimestamp: incentive.startTimestamp, -// EndTimestamp: incentive.endTimestamp, -// Refundee: incentive.refundee.String(), -// CreatedHeight: incentive.createdHeight, -// DepositGnsAmount: incentive.depositGnsAmount, -// }) -// } -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, incentive := range apiExternalIncentives { -// active := false -// if time.Now().Unix() >= incentive.StartTimestamp && time.Now().Unix() <= incentive.EndTimestamp { -// active = true -// } -// -// _incentiveNode := json.ObjectNode("", map[string]*json.Node{ -// "incentiveId": json.StringNode("incentiveId", incentive.IncentiveId), -// "poolPath": json.StringNode("poolPath", incentive.PoolPath), -// "rewardToken": json.StringNode("rewardToken", incentive.RewardToken), -// "rewardAmount": json.StringNode("rewardAmount", incentive.RewardAmount), -// "rewardLeft": json.StringNode("rewardLeft", incentive.RewardLeft), -// "startTimestamp": json.NumberNode("startTimestamp", float64(incentive.StartTimestamp)), -// "endTimestamp": json.NumberNode("endTimestamp", float64(incentive.EndTimestamp)), -// "active": json.BoolNode("active", active), -// "refundee": json.StringNode("refundee", incentive.Refundee), -// "createdHeight": json.NumberNode("createdHeight", float64(incentive.CreatedHeight)), -// "depositGnsAmount": json.NumberNode("depositGnsAmount", float64(incentive.DepositGnsAmount)), -// }) -// responses.AppendArray(_incentiveNode) -// } -// -// // RETURN -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetExternalIncentivesByRewardTokenPath(rewardTokenPath string) string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// updateExternalIncentiveLeftAmount() -// -// apiExternalIncentives := []ApiExternalIncentive{} -// -// for incentiveId, incentive := range incentives { -// if incentive.rewardToken != rewardTokenPath { -// continue -// } -// -// apiExternalIncentives = append(apiExternalIncentives, ApiExternalIncentive{ -// IncentiveId: incentiveId, -// PoolPath: incentive.targetPoolPath, -// RewardToken: incentive.rewardToken, -// RewardAmount: incentive.rewardAmount.ToString(), -// RewardLeft: incentive.rewardLeft.ToString(), -// StartTimestamp: incentive.startTimestamp, -// EndTimestamp: incentive.endTimestamp, -// Refundee: incentive.refundee.String(), -// CreatedHeight: incentive.createdHeight, -// DepositGnsAmount: incentive.depositGnsAmount, -// }) -// } -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, incentive := range apiExternalIncentives { -// active := false -// if time.Now().Unix() >= incentive.StartTimestamp && time.Now().Unix() <= incentive.EndTimestamp { -// active = true -// } -// -// _incentiveNode := json.ObjectNode("", map[string]*json.Node{ -// "incentiveId": json.StringNode("incentiveId", incentive.IncentiveId), -// "poolPath": json.StringNode("poolPath", incentive.PoolPath), -// "rewardToken": json.StringNode("rewardToken", incentive.RewardToken), -// "rewardAmount": json.StringNode("rewardAmount", incentive.RewardAmount), -// "rewardLeft": json.StringNode("rewardLeft", incentive.RewardLeft), -// "startTimestamp": json.NumberNode("startTimestamp", float64(incentive.StartTimestamp)), -// "endTimestamp": json.NumberNode("endTimestamp", float64(incentive.EndTimestamp)), -// "active": json.BoolNode("active", active), -// "refundee": json.StringNode("refundee", incentive.Refundee), -// "createdHeight": json.NumberNode("createdHeight", float64(incentive.CreatedHeight)), -// "depositGnsAmount": json.NumberNode("depositGnsAmount", float64(incentive.DepositGnsAmount)), -// }) -// responses.AppendArray(_incentiveNode) -// } -// -// // RETURN -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetInternalIncentives() string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// apiInternalIncentives := []ApiInternalIncentive{} -// -// poolTiers.Iter(func(poolPath string, internalTier InternalTier) { -// apiInternalIncentives = append(apiInternalIncentives, ApiInternalIncentive{ -// PoolPath: poolPath, -// Tier: internalTier.tier, -// StartTimestamp: internalTier.startTimestamp, -// RewardPerBlock: calculateInternalRewardPerBlockByPoolPath(poolPath), -// }) -// }) -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, incentive := range apiInternalIncentives { -// _incentiveNode := json.ObjectNode("", map[string]*json.Node{ -// "poolPath": json.StringNode("poolPath", incentive.PoolPath), -// "rewardToken": json.StringNode("rewardToken", consts.GNS_PATH), -// "tier": json.NumberNode("tier", float64(incentive.Tier)), -// "startTimestamp": json.NumberNode("startTimestamp", float64(incentive.StartTimestamp)), -// "rewardPerBlock": json.StringNode("rewardPerBlock", incentive.RewardPerBlock), -// "accuGns": json.NumberNode("accuGns", float64(poolAccuGns[incentive.PoolPath])), -// }) -// responses.AppendArray(_incentiveNode) -// } -// -// // RETURN -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetInternalIncentivesByPoolPath(targetPoolPath string) string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// apiInternalIncentives := []ApiInternalIncentive{} -// -// poolTiers.Iter(func(poolPath string, internalTier InternalTier) { -// if poolPath != targetPoolPath { -// return -// } -// -// apiInternalIncentives = append(apiInternalIncentives, ApiInternalIncentive{ -// PoolPath: poolPath, -// Tier: internalTier.tier, -// StartTimestamp: internalTier.startTimestamp, -// RewardPerBlock: calculateInternalRewardPerBlockByPoolPath(poolPath), -// }) -// }) -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, incentive := range apiInternalIncentives { -// _incentiveNode := json.ObjectNode("", map[string]*json.Node{ -// "poolPath": json.StringNode("poolPath", incentive.PoolPath), -// "rewardToken": json.StringNode("rewardToken", consts.GNS_PATH), -// "tier": json.NumberNode("tier", float64(incentive.Tier)), -// "startTimestamp": json.NumberNode("startTimestamp", float64(incentive.StartTimestamp)), -// "rewardPerBlock": json.StringNode("rewardPerBlock", incentive.RewardPerBlock), -// "accuGns": json.NumberNode("accuGns", float64(poolAccuGns[targetPoolPath])), -// }) -// responses.AppendArray(_incentiveNode) -// } -// -// // RETURN -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetInternalIncentivesByTiers(targetTier uint64) string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// apiInternalIncentives := []ApiInternalIncentive{} -// -// poolTiers.Iter(func(poolPath string, internalTier InternalTier) { -// if internalTier.tier != targetTier { -// return -// } -// -// apiInternalIncentives = append(apiInternalIncentives, ApiInternalIncentive{ -// PoolPath: poolPath, -// Tier: internalTier.tier, -// StartTimestamp: internalTier.startTimestamp, -// RewardPerBlock: calculateInternalRewardPerBlockByPoolPath(poolPath), -// }) -// }) -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, incentive := range apiInternalIncentives { -// _incentiveNode := json.ObjectNode("", map[string]*json.Node{ -// "poolPath": json.StringNode("poolPath", incentive.PoolPath), -// "rewardToken": json.StringNode("rewardToken", consts.GNS_PATH), -// "tier": json.NumberNode("tier", float64(incentive.Tier)), -// "startTimestamp": json.NumberNode("startTimestamp", float64(incentive.StartTimestamp)), -// "rewardPerBlock": json.StringNode("rewardPerBlock", incentive.RewardPerBlock), -// "accuGns": json.NumberNode("accuGns", float64(poolAccuGns[incentive.PoolPath])), -// }) -// responses.AppendArray(_incentiveNode) -// } -// -// // RETURN -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func makeRewardTokensArray(rewardsTokenList []string) []*json.Node { -// rewardsTokenArray := make([]*json.Node, len(rewardsTokenList)) -// for i, rewardToken := range rewardsTokenList { -// rewardsTokenArray[i] = json.StringNode("", rewardToken) -// } -// return rewardsTokenArray -//} -// -//func calculateInternalRewardPerBlockByPoolPath(poolPath string) string { -// nowHeight := std.GetHeight() -// fullGnsForThisHeight := gns.GetAmountByHeight(nowHeight) -// -// // staker distribution pct -// bpsPct := en.GetDistributionPct(1) -// -// // calculate reward per block -// stakerGns := fullGnsForThisHeight * bpsPct / 10000 -// -// tier1Amount, tier2Amount, tier3Amount := getTiersAmount(stakerGns) -// tier1Num, tier2Num, tier3Num := getNumPoolTiers() -// -// internalTier, exist := poolTiers.Get(poolPath) -// if !exist { -// return "0" -// } -// -// tier := internalTier.Tier() -// -// if tier == 1 { -// return ufmt.Sprintf("%d", tier1Amount/tier1Num) -// } else if tier == 2 { -// return ufmt.Sprintf("%d", tier2Amount/tier2Num) -// } else if tier == 3 { -// return ufmt.Sprintf("%d", tier3Amount/tier3Num) -// } -// -// return "0" -//} -// -//func updateExternalIncentiveLeftAmount() { -// // external incentive reward left update -// for _, positionWarmUpAmount := range positionsExternalWarmUpAmount { -// for incentiveId, warmUpAmount := range positionWarmUpAmount { -// -// full := warmUpAmount.full100 + warmUpAmount.full70 + warmUpAmount.full50 + warmUpAmount.full30 -// -// ictv, exist := incentives.Get(incentiveId) -// if !exist { -// continue -// } -// ictv.rewardLeft = new(u256.Uint).Sub(ictv.rewardLeft, u256.NewUint(full)) -// incentives.Set(incentiveId, ictv) -// } -// } -//} - -func updateExternalIncentiveLeftAmount() { - // external incentive reward left update - externalIncentives.tree.Iterate("", "", func(key string, value interface{}) bool { - incentiveId := key +func ApiGetRewardTokens() string { + en.MintAndDistributeGns() + + rewardTokens := []RewardToken{} + + pools.IterateAll(func(key string, pool *Pool) bool { + thisPoolRewardTokens := []string{} + + // HANDLE INTERNAL + if poolTier.IsInternallyIncentivizedPool(pool.poolPath) { + thisPoolRewardTokens = append(thisPoolRewardTokens, consts.GNS_PATH) + } + + // HANDLE EXTERNAL + if pool.IsExternallyIncentivizedPool() { + pool.incentives.byTime.Iterate("", "", func(key string, value interface{}) bool { + ictv := value.(*ExternalIncentive) + if ictv.RewardToken() == "" { + return false + } + thisPoolRewardTokens = append(thisPoolRewardTokens, ictv.RewardToken()) + return false + }) + } + + if len(thisPoolRewardTokens) == 0 { + return false + } + + rewardTokens = append(rewardTokens, RewardToken{ + PoolPath: pool.poolPath, + RewardsTokenList: thisPoolRewardTokens, + }) + return false + }) + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, rewardToken := range rewardTokens { + _rewardTokenNode := json.ObjectNode("", map[string]*json.Node{ + "poolPath": json.StringNode("poolPath", rewardToken.PoolPath), + "tokens": json.ArrayNode("tokens", makeRewardTokensArray(rewardToken.RewardsTokenList)), + }) + responses.AppendArray(_rewardTokenNode) + } + + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func ApiGetRewardTokensByPoolPath(targetPoolPath string) string { + en.MintAndDistributeGns() + + rewardTokens := []RewardToken{} + + pool, ok := pools.Get(targetPoolPath) + if !ok { + return "" + } + + thisPoolRewardTokens := []string{} + + // HANDLE INTERNAL + if poolTier.IsInternallyIncentivizedPool(pool.poolPath) { + thisPoolRewardTokens = append(thisPoolRewardTokens, consts.GNS_PATH) + } + + // HANDLE EXTERNAL + if pool.IsExternallyIncentivizedPool() { + pool.incentives.byTime.Iterate("", "", func(key string, value interface{}) bool { + ictv := value.(*ExternalIncentive) + if ictv.RewardToken() == "" { + return false + } + thisPoolRewardTokens = append(thisPoolRewardTokens, ictv.RewardToken()) + return false + }) + } + + rewardTokens = append(rewardTokens, RewardToken{ + PoolPath: pool.poolPath, + RewardsTokenList: thisPoolRewardTokens, + }) + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, rewardToken := range rewardTokens { + _rewardTokenNode := json.ObjectNode("", map[string]*json.Node{ + "poolPath": json.StringNode("poolPath", rewardToken.PoolPath), + "tokens": json.ArrayNode("tokens", makeRewardTokensArray(rewardToken.RewardsTokenList)), + }) + responses.AppendArray(_rewardTokenNode) + } + + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func ApiGetExternalIncentives() string { + en.MintAndDistributeGns() + + apiExternalIncentives := []ApiExternalIncentive{} + + pools.tree.Iterate("", "", func(key string, value interface{}) bool { + pool := value.(*Pool) + pool.incentives.byTime.Iterate("", "", func(key string, value interface{}) bool { + ictv := value.(*ExternalIncentive) + apiExternalIncentives = append(apiExternalIncentives, ApiExternalIncentive{ + IncentiveId: ictv.incentiveId, + PoolPath: ictv.targetPoolPath, + RewardToken: ictv.rewardToken, + RewardAmount: ictv.rewardAmount, + RewardLeft: ictv.rewardLeft, + StartTimestamp: ictv.startTimestamp, + EndTimestamp: ictv.endTimestamp, + Refundee: ictv.refundee.String(), + CreatedHeight: ictv.createdHeight, + DepositGnsAmount: ictv.depositGnsAmount, + }) + return false + }) + return false + }) + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, incentive := range apiExternalIncentives { + active := false + if time.Now().Unix() >= incentive.StartTimestamp && time.Now().Unix() <= incentive.EndTimestamp { + active = true + } + + _incentiveNode := json.ObjectNode("", map[string]*json.Node{ + "incentiveId": json.StringNode("incentiveId", incentive.IncentiveId), + "poolPath": json.StringNode("poolPath", incentive.PoolPath), + "rewardToken": json.StringNode("rewardToken", incentive.RewardToken), + "rewardAmount": json.StringNode("rewardAmount", strconv.FormatUint(incentive.RewardAmount, 10)), + "rewardLeft": json.StringNode("rewardLeft", strconv.FormatUint(incentive.RewardLeft, 10)), + "startTimestamp": json.NumberNode("startTimestamp", float64(incentive.StartTimestamp)), + "endTimestamp": json.NumberNode("endTimestamp", float64(incentive.EndTimestamp)), + "active": json.BoolNode("active", active), + "refundee": json.StringNode("refundee", incentive.Refundee), + "createdHeight": json.NumberNode("createdHeight", float64(incentive.CreatedHeight)), + "depositGnsAmount": json.NumberNode("depositGnsAmount", float64(incentive.DepositGnsAmount)), + }) + responses.AppendArray(_incentiveNode) + } + + // RETURN + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func ApiGetExternalIncentiveById(poolPath, incentiveId string) string { + en.MintAndDistributeGns() + + apiExternalIncentives := []ApiExternalIncentive{} + + pool, ok := pools.Get(poolPath) + if !ok { + panic(addDetailToError( + errDataNotFound, + ufmt.Sprintf("pool(%s) not found", poolPath), + )) + } + + incentive, exist := pool.incentives.GetByIncentiveId(incentiveId) + if !exist { + panic(addDetailToError( + errDataNotFound, + ufmt.Sprintf("incentive(%s) not found", incentiveId), + )) + } + + apiExternalIncentives = append(apiExternalIncentives, ApiExternalIncentive{ + IncentiveId: incentiveId, + PoolPath: poolPath, + RewardToken: incentive.rewardToken, + RewardAmount: incentive.rewardAmount, + RewardLeft: incentive.rewardLeft, + StartTimestamp: incentive.startTimestamp, + EndTimestamp: incentive.endTimestamp, + Refundee: incentive.refundee.String(), + CreatedHeight: incentive.createdHeight, + DepositGnsAmount: incentive.depositGnsAmount, + }) + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, incentive := range apiExternalIncentives { + active := false + if time.Now().Unix() >= incentive.StartTimestamp && time.Now().Unix() <= incentive.EndTimestamp { + active = true + } + + _incentiveNode := json.ObjectNode("", map[string]*json.Node{ + "incentiveId": json.StringNode("incentiveId", incentive.IncentiveId), + "poolPath": json.StringNode("poolPath", incentive.PoolPath), + "rewardToken": json.StringNode("rewardToken", incentive.RewardToken), + "rewardAmount": json.StringNode("rewardAmount", strconv.FormatUint(incentive.RewardAmount, 10)), + "rewardLeft": json.StringNode("rewardLeft", strconv.FormatUint(incentive.RewardLeft, 10)), + "startTimestamp": json.NumberNode("startTimestamp", float64(incentive.StartTimestamp)), + "endTimestamp": json.NumberNode("endTimestamp", float64(incentive.EndTimestamp)), + "active": json.BoolNode("active", active), + "refundee": json.StringNode("refundee", incentive.Refundee), + "createdHeight": json.NumberNode("createdHeight", float64(incentive.CreatedHeight)), + "depositGnsAmount": json.NumberNode("depositGnsAmount", float64(incentive.DepositGnsAmount)), + }) + responses.AppendArray(_incentiveNode) + } + + // RETURN + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func ApiGetExternalIncentivesByPoolPath(targetPoolPath string) string { + en.MintAndDistributeGns() + + apiExternalIncentives := []ApiExternalIncentive{} + + pool, ok := pools.Get(targetPoolPath) + if !ok { + panic(addDetailToError( + errDataNotFound, + ufmt.Sprintf("pool(%s) not found", targetPoolPath), + )) + } + + pool.incentives.byTime.Iterate("", "", func(key string, value interface{}) bool { incentive := value.(*ExternalIncentive) + if incentive.targetPoolPath != targetPoolPath { + return false + } + + apiExternalIncentives = append(apiExternalIncentives, ApiExternalIncentive{ + IncentiveId: incentive.incentiveId, + PoolPath: incentive.targetPoolPath, + RewardToken: incentive.rewardToken, + RewardAmount: incentive.rewardAmount, + RewardLeft: incentive.rewardLeft, + StartTimestamp: incentive.startTimestamp, + EndTimestamp: incentive.endTimestamp, + Refundee: incentive.refundee.String(), + CreatedHeight: incentive.createdHeight, + DepositGnsAmount: incentive.depositGnsAmount, + }) + return false + }) + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, incentive := range apiExternalIncentives { + active := false + if time.Now().Unix() >= incentive.StartTimestamp && time.Now().Unix() <= incentive.EndTimestamp { + active = true + } + + _incentiveNode := json.ObjectNode("", map[string]*json.Node{ + "incentiveId": json.StringNode("incentiveId", incentive.IncentiveId), + "poolPath": json.StringNode("poolPath", incentive.PoolPath), + "rewardToken": json.StringNode("rewardToken", incentive.RewardToken), + "rewardAmount": json.StringNode("rewardAmount", strconv.FormatUint(incentive.RewardAmount, 10)), + "rewardLeft": json.StringNode("rewardLeft", strconv.FormatUint(incentive.RewardLeft, 10)), + "startTimestamp": json.NumberNode("startTimestamp", float64(incentive.StartTimestamp)), + "endTimestamp": json.NumberNode("endTimestamp", float64(incentive.EndTimestamp)), + "active": json.BoolNode("active", active), + "refundee": json.StringNode("refundee", incentive.Refundee), + "createdHeight": json.NumberNode("createdHeight", float64(incentive.CreatedHeight)), + "depositGnsAmount": json.NumberNode("depositGnsAmount", float64(incentive.DepositGnsAmount)), + }) + responses.AppendArray(_incentiveNode) + } + + // RETURN + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func ApiGetExternalIncentivesByRewardTokenPath(rewardTokenPath string) string { + en.MintAndDistributeGns() + + apiExternalIncentives := []ApiExternalIncentive{} + + pools.tree.Iterate("", "", func(key string, value interface{}) bool { + pool := value.(*Pool) + pool.incentives.byTime.Iterate("", "", func(key string, value interface{}) bool { + incentive := value.(*ExternalIncentive) + if incentive.rewardToken != rewardTokenPath { + return false + } + + apiExternalIncentives = append(apiExternalIncentives, ApiExternalIncentive{ + IncentiveId: incentive.incentiveId, + PoolPath: pool.poolPath, + RewardToken: incentive.rewardToken, + RewardAmount: incentive.rewardAmount, + RewardLeft: incentive.rewardLeft, + StartTimestamp: incentive.startTimestamp, + EndTimestamp: incentive.endTimestamp, + Refundee: incentive.refundee.String(), + CreatedHeight: incentive.createdHeight, + DepositGnsAmount: incentive.depositGnsAmount, + }) + return false + }) + return false + }) - if incentiveId != incentive.incentiveId { - panic("incentiveId != incentive.incentiveId") + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, incentive := range apiExternalIncentives { + active := false + if time.Now().Unix() >= incentive.StartTimestamp && time.Now().Unix() <= incentive.EndTimestamp { + active = true } - // TODO: check if this is correct - incentive.rewardLeft = incentive.RewardLeft(uint64(std.GetHeight())) - externalIncentives.tree.Set(key, incentive) + _incentiveNode := json.ObjectNode("", map[string]*json.Node{ + "incentiveId": json.StringNode("incentiveId", incentive.IncentiveId), + "poolPath": json.StringNode("poolPath", incentive.PoolPath), + "rewardToken": json.StringNode("rewardToken", incentive.RewardToken), + "rewardAmount": json.StringNode("rewardAmount", strconv.FormatUint(incentive.RewardAmount, 10)), + "rewardLeft": json.StringNode("rewardLeft", strconv.FormatUint(incentive.RewardLeft, 10)), + "startTimestamp": json.NumberNode("startTimestamp", float64(incentive.StartTimestamp)), + "endTimestamp": json.NumberNode("endTimestamp", float64(incentive.EndTimestamp)), + "active": json.BoolNode("active", active), + "refundee": json.StringNode("refundee", incentive.Refundee), + "createdHeight": json.NumberNode("createdHeight", float64(incentive.CreatedHeight)), + "depositGnsAmount": json.NumberNode("depositGnsAmount", float64(incentive.DepositGnsAmount)), + }) + responses.AppendArray(_incentiveNode) + } + + // RETURN + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func ApiGetInternalIncentives() string { + en.MintAndDistributeGns() + + apiInternalIncentives := []ApiInternalIncentive{} + + poolTier.membership.Iterate("", "", func(key string, value interface{}) bool { + poolPath := key + internalTier := value.(uint64) + apiInternalIncentives = append(apiInternalIncentives, ApiInternalIncentive{ + PoolPath: poolPath, + Tier: internalTier, + RewardPerBlock: calculateInternalRewardPerBlockByPoolPath(poolPath), + }) return false }) + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, incentive := range apiInternalIncentives { + _incentiveNode := json.ObjectNode("", map[string]*json.Node{ + "poolPath": json.StringNode("poolPath", incentive.PoolPath), + "rewardToken": json.StringNode("rewardToken", consts.GNS_PATH), + "tier": json.NumberNode("tier", float64(incentive.Tier)), + "rewardPerBlock": json.StringNode("rewardPerBlock", incentive.RewardPerBlock), + }) + responses.AppendArray(_incentiveNode) + } + + // RETURN + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func ApiGetInternalIncentivesByPoolPath(targetPoolPath string) string { + en.MintAndDistributeGns() + + apiInternalIncentives := []ApiInternalIncentive{} + + tier := poolTier.CurrentTier(targetPoolPath) + if tier == 0 { + return "" + } + + apiInternalIncentives = append(apiInternalIncentives, ApiInternalIncentive{ + PoolPath: targetPoolPath, + Tier: tier, + RewardPerBlock: calculateInternalRewardPerBlockByPoolPath(targetPoolPath), + }) + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, incentive := range apiInternalIncentives { + _incentiveNode := json.ObjectNode("", map[string]*json.Node{ + "poolPath": json.StringNode("poolPath", incentive.PoolPath), + "rewardToken": json.StringNode("rewardToken", consts.GNS_PATH), + "tier": json.NumberNode("tier", float64(incentive.Tier)), + "rewardPerBlock": json.StringNode("rewardPerBlock", incentive.RewardPerBlock), + }) + responses.AppendArray(_incentiveNode) + } + + // RETURN + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) } -// -//// LpTokenReward represents the rewards associated with a specific LP token -//type LpTokenReward struct { -// LpTokenId uint64 `json:"lpTokenId"` // The ID of the LP token -// Address string `json:"address"` // The address associated with the LP token -// Rewards []Reward `json:"rewards"` -//} -// -//// Reward represents a single reward for a staked LP token -//type Reward struct { -// IncentiveType string `json:"incentiveType"` // The type of incentive (INTERNAL or EXTERNAL) -// IncentiveId string `json:"incentiveId"` // The unique identifier of the incentive -// TargetPoolPath string `json:"targetPoolPath"` // The path of the target pool for the reward -// RewardTokenPath string `json:"rewardTokenPath"` // The pathe of the reward token -// RewardTokenAmount uint64 `json:"rewardTokenAmount"` // The amount of the reward token -// StakeTimestamp int64 `json:"stakeTimestamp"` // The timestamp when the LP token was staked -// StakeHeight int64 `json:"stakeHeight"` // The block height when the LP token was staked -// IncentiveStart int64 `json:"incentiveStart"` // The timestamp when the incentive started -//} -// -//// Stake represents a single stake -//type Stake struct { -// TokenId uint64 `json:"tokenId"` // The ID of the staked LP token -// Owner std.Address `json:"owner"` // The address of the owner of the staked LP token -// NumberOfStakes uint64 `json:"numberOfStakes"` // The number of times this LP token has been staked -// StakeTimestamp int64 `json:"stakeTimestamp"` // The timestamp when the LP token was staked -// StakeHeight int64 `json:"stakeHeight"` // The block height when the LP token was staked -// TargetPoolPath string `json:"targetPoolPath"` // The path of the target pool for the stake -//} -// -//// ResponseQueryBase contains basic information about a query response. -//type ResponseQueryBase struct { -// Height int64 `json:"height"` // The block height at the time of the query -// Timestamp int64 `json:"timestamp"` // The timestamp at the time of the query -//} -// -//// ResponseApiGetRewards represents the API response for getting rewards. -//type ResponseApiGetRewards struct { -// Stat ResponseQueryBase `json:"stat"` // Basic query information -// Response []LpTokenReward `json:"response"` // A slice of LpTokenReward structs -//} -// -//// ResponseApiGetRewardByLpTokenId represents the API response for getting rewards for a specific LP token. -//type ResponseApiGetRewardByLpTokenId struct { -// Stat ResponseQueryBase `json:"stat"` // Basic query information -// Response LpTokenReward `json:"response"` // The LpTokenReward for the specified LP token -//} -// -//// ResponseApiGetStakes represents the API response for getting stakes. -//type ResponseApiGetStakes struct { -// Stat ResponseQueryBase `json:"stat"` // Basic query information -// Response []Stake `json:"response"` // A slice of Stake structs -//} -// -//func ApiGetRewards() string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// lpTokenRewards := []LpTokenReward{} -// -// // TODO: extract as function -// deposits.Iter(func(tokenId uint64, deposit Deposit) { -// rewards := []Reward{} -// -// // get internal gns reward -// internalWarmUpAmount, exist := positionsInternalWarmUpAmount[tokenId] -// if !exist { -// return -// } -// internalGNS := internalWarmUpAmount.give30 + internalWarmUpAmount.give50 + internalWarmUpAmount.give70 + internalWarmUpAmount.full100 -// -// if internalGNS > 0 { -// rewards = append(rewards, Reward{ -// IncentiveType: "INTERNAL", -// IncentiveId: "", -// TargetPoolPath: deposit.targetPoolPath, -// RewardTokenPath: consts.GNS_PATH, -// RewardTokenAmount: internalGNS, -// StakeTimestamp: deposit.stakeTimestamp, -// StakeHeight: deposit.stakeHeight, -// IncentiveStart: deposit.stakeTimestamp, -// }) -// } -// -// // find all external reward list for poolPath which lpTokenId is staked -// ictvList, exists := poolIncentives.Get(deposit.targetPoolPath) -// if !exists { -// return -// } -// -// for _, ictvId := range ictvList { -// ictv, exists := incentives.Get(ictvId) -// if !exists { -// return -// } -// -// stakedOrCreatedAt := common.I64Max(deposit.stakeTimestamp, ictv.startTimestamp) -// now := time.Now().Unix() -// if now < stakedOrCreatedAt { -// return -// } -// -// externalWarmUpAmount, exist := positionsExternalWarmUpAmount[tokenId][ictvId] -// if !exist { -// return -// } -// externalReward := externalWarmUpAmount.give30 + externalWarmUpAmount.give50 + externalWarmUpAmount.give70 + externalWarmUpAmount.full100 -// if externalReward >= 0 { -// rewards = append(rewards, Reward{ -// IncentiveType: "EXTERNAL", -// IncentiveId: ictvId, -// TargetPoolPath: deposit.targetPoolPath, -// RewardTokenPath: ictv.rewardToken, -// RewardTokenAmount: externalReward, -// StakeTimestamp: deposit.stakeTimestamp, -// StakeHeight: deposit.stakeHeight, -// IncentiveStart: ictv.startTimestamp, -// }) -// } -// } -// -// if len(rewards) > 0 { -// lpTokenReward := LpTokenReward{ -// LpTokenId: tokenId, -// Address: deposit.owner.String(), -// Rewards: rewards, -// } -// lpTokenRewards = append(lpTokenRewards, lpTokenReward) -// } -// }) -// -// qb := ResponseQueryBase{ -// Height: std.GetHeight(), -// Timestamp: time.Now().Unix(), -// } -// -// r := ResponseApiGetRewards{ -// Stat: qb, -// Response: lpTokenRewards, -// } -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, reward := range r.Response { -// _rewardNode := json.ObjectNode("", map[string]*json.Node{ -// "lpTokenId": json.NumberNode("lpTokenId", float64(reward.LpTokenId)), -// "address": json.StringNode("address", reward.Address), -// "rewards": json.ArrayNode("rewards", makeRewardsArray(reward.Rewards)), -// }) -// responses.AppendArray(_rewardNode) -// } -// -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetRewardsByLpTokenId(targetLpTokenId uint64) string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// lpTokenRewards := []LpTokenReward{} -// -// deposits.Iter(func(tokenId uint64, deposit Deposit) { -// if tokenId != targetLpTokenId { -// return -// } -// -// rewards := []Reward{} -// -// // get internal gns reward -// internalWarmUpAmount, exist := positionsInternalWarmUpAmount[tokenId] -// if !exist { -// return -// } -// internalGNS := internalWarmUpAmount.give30 + internalWarmUpAmount.give50 + internalWarmUpAmount.give70 + internalWarmUpAmount.full100 -// -// if internalGNS > 0 { -// rewards = append(rewards, Reward{ -// IncentiveType: "INTERNAL", -// IncentiveId: "", -// TargetPoolPath: deposit.targetPoolPath, -// RewardTokenPath: consts.GNS_PATH, -// RewardTokenAmount: internalGNS, -// StakeTimestamp: deposit.stakeTimestamp, -// StakeHeight: deposit.stakeHeight, -// IncentiveStart: deposit.stakeTimestamp, -// }) -// } -// -// // find all external reward list for poolPath which lpTokenId is staked -// ictvList, exists := poolIncentives.Get(deposit.targetPoolPath) -// if !exists { -// return -// } -// -// for _, ictvId := range ictvList { -// ictv, exists := incentives.Get(ictvId) -// if !exists { -// return -// } -// -// stakedOrCreatedAt := common.I64Max(deposit.stakeTimestamp, ictv.startTimestamp) -// now := time.Now().Unix() -// if now < stakedOrCreatedAt { -// return -// } -// -// externalWarmUpAmount, exist := positionsExternalWarmUpAmount[tokenId][ictvId] -// if !exist { -// return -// } -// externalReward := externalWarmUpAmount.give30 + externalWarmUpAmount.give50 + externalWarmUpAmount.give70 + externalWarmUpAmount.full100 -// if externalReward > 0 { -// rewards = append(rewards, Reward{ -// IncentiveType: "EXTERNAL", -// IncentiveId: ictvId, -// TargetPoolPath: deposit.targetPoolPath, -// RewardTokenPath: ictv.rewardToken, -// RewardTokenAmount: externalReward, -// StakeTimestamp: deposit.stakeTimestamp, -// StakeHeight: deposit.stakeHeight, -// IncentiveStart: ictv.startTimestamp, -// }) -// } -// } -// -// lpTokenReward := LpTokenReward{ -// LpTokenId: tokenId, -// Address: deposit.owner.String(), -// Rewards: rewards, -// } -// lpTokenRewards = append(lpTokenRewards, lpTokenReward) -// }) -// -// qb := ResponseQueryBase{ -// Height: std.GetHeight(), -// Timestamp: time.Now().Unix(), -// } -// -// r := ResponseApiGetRewards{ -// Stat: qb, -// Response: lpTokenRewards, -// } -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, reward := range r.Response { -// _rewardNode := json.ObjectNode("", map[string]*json.Node{ -// "lpTokenId": json.NumberNode("lpTokenId", float64(reward.LpTokenId)), -// "address": json.StringNode("address", reward.Address), -// "rewards": json.ArrayNode("rewards", makeRewardsArray(reward.Rewards)), -// }) -// responses.AppendArray(_rewardNode) -// } -// -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetRewardsByAddress(targetAddress string) string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// lpTokenRewards := []LpTokenReward{} -// -// deposits.Iter(func(tokenId uint64, deposit Deposit) { -// if deposit.owner.String() != targetAddress { -// return -// } -// -// rewards := []Reward{} -// -// // get internal gns reward -// internalWarmUpAmount, exist := positionsInternalWarmUpAmount[tokenId] -// if !exist { -// return -// } -// internalGNS := internalWarmUpAmount.give30 + internalWarmUpAmount.give50 + internalWarmUpAmount.give70 + internalWarmUpAmount.full100 -// -// if internalGNS > 0 { -// rewards = append(rewards, Reward{ -// IncentiveType: "INTERNAL", -// IncentiveId: "", -// TargetPoolPath: deposit.targetPoolPath, -// RewardTokenPath: consts.GNS_PATH, -// RewardTokenAmount: internalGNS, -// StakeTimestamp: deposit.stakeTimestamp, -// StakeHeight: deposit.stakeHeight, -// IncentiveStart: deposit.stakeTimestamp, -// }) -// } -// -// // find all external reward list for poolPath which lpTokenId is staked -// ictvList, exists := poolIncentives.Get(deposit.targetPoolPath) -// if !exists { -// return -// } -// -// for _, ictvId := range ictvList { -// ictv, exists := incentives.Get(ictvId) -// if !exists { -// return -// } -// -// stakedOrCreatedAt := common.I64Max(deposit.stakeTimestamp, ictv.startTimestamp) -// now := time.Now().Unix() -// if now < stakedOrCreatedAt { -// return -// } -// -// externalWarmUpAmount, exist := positionsExternalWarmUpAmount[tokenId][ictvId] -// if !exist { -// return -// } -// externalReward := externalWarmUpAmount.give30 + externalWarmUpAmount.give50 + externalWarmUpAmount.give70 + externalWarmUpAmount.full100 -// rewards = append(rewards, Reward{ -// IncentiveType: "EXTERNAL", -// IncentiveId: ictvId, -// TargetPoolPath: deposit.targetPoolPath, -// RewardTokenPath: ictv.rewardToken, -// RewardTokenAmount: externalReward, -// StakeTimestamp: deposit.stakeTimestamp, -// StakeHeight: deposit.stakeHeight, -// IncentiveStart: ictv.startTimestamp, -// }) -// } -// lpTokenReward := LpTokenReward{ -// LpTokenId: tokenId, -// Address: deposit.owner.String(), -// Rewards: rewards, -// } -// lpTokenRewards = append(lpTokenRewards, lpTokenReward) -// }) -// -// qb := ResponseQueryBase{ -// Height: std.GetHeight(), -// Timestamp: time.Now().Unix(), -// } -// -// r := ResponseApiGetRewards{ -// Stat: qb, -// Response: lpTokenRewards, -// } -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, reward := range r.Response { -// _rewardNode := json.ObjectNode("", map[string]*json.Node{ -// "lpTokenId": json.NumberNode("lpTokenId", float64(reward.LpTokenId)), -// "address": json.StringNode("address", reward.Address), -// "rewards": json.ArrayNode("rewards", makeRewardsArray(reward.Rewards)), -// }) -// responses.AppendArray(_rewardNode) -// } -// -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetStakes() string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// stakes := []Stake{} -// deposits.Iter(func(tokenId uint64, deposit Deposit) { -// stakes = append(stakes, Stake{ -// TokenId: tokenId, -// Owner: deposit.owner, -// NumberOfStakes: deposit.numberOfStakes, -// StakeTimestamp: deposit.stakeTimestamp, -// StakeHeight: deposit.stakeHeight, -// TargetPoolPath: deposit.targetPoolPath, -// }) -// }) -// -// qb := ResponseQueryBase{ -// Height: std.GetHeight(), -// Timestamp: time.Now().Unix(), -// } -// -// r := ResponseApiGetStakes{ -// Stat: qb, -// Response: stakes, -// } -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, stake := range r.Response { -// _stakeNode := json.ObjectNode("", map[string]*json.Node{ -// "tokenId": json.NumberNode("tokenId", float64(stake.TokenId)), -// "owner": json.StringNode("owner", stake.Owner.String()), -// "numberOfStakes": json.NumberNode("numberOfStakes", float64(stake.NumberOfStakes)), -// "stakeTimestamp": json.NumberNode("stakeTimestamp", float64(stake.StakeTimestamp)), -// "stakeHeight": json.NumberNode("stakeHeight", float64(stake.StakeHeight)), -// "targetPoolPath": json.StringNode("targetPoolPath", stake.TargetPoolPath), -// }) -// responses.AppendArray(_stakeNode) -// } -// -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetStakesByLpTokenId(targetLpTokenId uint64) string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// stakes := []Stake{} -// -// deposits.Iter(func(tokenId uint64, deposit Deposit) { -// if tokenId != targetLpTokenId { -// return -// } -// -// stakes = append(stakes, Stake{ -// TokenId: tokenId, -// Owner: deposit.owner, -// NumberOfStakes: deposit.numberOfStakes, -// StakeTimestamp: deposit.stakeTimestamp, -// StakeHeight: deposit.stakeHeight, -// TargetPoolPath: deposit.targetPoolPath, -// }) -// }) -// -// qb := ResponseQueryBase{ -// Height: std.GetHeight(), -// Timestamp: time.Now().Unix(), -// } -// -// r := ResponseApiGetStakes{ -// Stat: qb, -// Response: stakes, -// } -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, stake := range r.Response { -// _stakeNode := json.ObjectNode("", map[string]*json.Node{ -// "tokenId": json.NumberNode("tokenId", float64(stake.TokenId)), -// "owner": json.StringNode("owner", stake.Owner.String()), -// "numberOfStakes": json.NumberNode("numberOfStakes", float64(stake.NumberOfStakes)), -// "stakeTimestamp": json.NumberNode("stakeTimestamp", float64(stake.StakeTimestamp)), -// "stakeHeight": json.NumberNode("stakeHeight", float64(stake.StakeHeight)), -// "targetPoolPath": json.StringNode("targetPoolPath", stake.TargetPoolPath), -// }) -// responses.AppendArray(_stakeNode) -// } -// -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//func ApiGetStakesByAddress(targetAddress string) string { -// en.MintAndDistributeGns() -// if consts.EMISSION_REFACTORED { -// CalcPoolPositionRefactor() -// } else { -// CalcPoolPosition() -// } -// -// stakes := []Stake{} -// -// deposits.Iter(func(tokenId uint64, deposit Deposit) { -// if deposit.owner.String() != targetAddress { -// return -// } -// -// stakes = append(stakes, Stake{ -// TokenId: tokenId, -// Owner: deposit.owner, -// NumberOfStakes: deposit.numberOfStakes, -// StakeTimestamp: deposit.stakeTimestamp, -// StakeHeight: deposit.stakeHeight, -// TargetPoolPath: deposit.targetPoolPath, -// }) -// }) -// -// qb := ResponseQueryBase{ -// Height: std.GetHeight(), -// Timestamp: time.Now().Unix(), -// } -// -// r := ResponseApiGetStakes{ -// Stat: qb, -// Response: stakes, -// } -// -// // STAT NODE -// _stat := json.ObjectNode("", map[string]*json.Node{ -// "height": json.NumberNode("height", float64(std.GetHeight())), -// "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), -// }) -// -// // RESPONSE (ARRAY) NODE -// responses := json.ArrayNode("", []*json.Node{}) -// for _, stake := range r.Response { -// _stakeNode := json.ObjectNode("", map[string]*json.Node{ -// "tokenId": json.NumberNode("tokenId", float64(stake.TokenId)), -// "owner": json.StringNode("owner", stake.Owner.String()), -// "numberOfStakes": json.NumberNode("numberOfStakes", float64(stake.NumberOfStakes)), -// "stakeTimestamp": json.NumberNode("stakeTimestamp", float64(stake.StakeTimestamp)), -// "stakeHeight": json.NumberNode("stakeHeight", float64(stake.StakeHeight)), -// "targetPoolPath": json.StringNode("targetPoolPath", stake.TargetPoolPath), -// }) -// responses.AppendArray(_stakeNode) -// } -// -// node := json.ObjectNode("", map[string]*json.Node{ -// "stat": _stat, -// "response": responses, -// }) -// -// b, err := json.Marshal(node) -// if err != nil { -// panic(err.Error()) -// } -// -// return string(b) -//} -// -//// for off chain to check if lpTokenId is staked via RPC -//func IsStaked(tokenId uint64) bool { -// _, exist := deposits[tokenId] -// return exist -//} -// -//func makeRewardsArray(rewards []Reward) []*json.Node { -// rewardsArray := make([]*json.Node, len(rewards)) -// -// for i, reward := range rewards { -// rewardsArray[i] = json.ObjectNode("", map[string]*json.Node{ -// "incentiveType": json.StringNode("incentiveType", reward.IncentiveType), -// "incentiveId": json.StringNode("incentiveId", reward.IncentiveId), -// "targetPoolPath": json.StringNode("targetPoolPath", reward.TargetPoolPath), -// "rewardTokenPath": json.StringNode("rewardTokenPath", reward.RewardTokenPath), -// "rewardTokenAmount": json.NumberNode("rewardTokenAmount", float64(reward.RewardTokenAmount)), -// "stakeTimestamp": json.NumberNode("stakeTimestamp", float64(reward.StakeTimestamp)), -// "stakeHeight": json.NumberNode("stakeHeight", float64(reward.StakeHeight)), -// "incentiveStart": json.NumberNode("incentiveStart", float64(reward.IncentiveStart)), -// }) -// } -// return rewardsArray -//} +func ApiGetInternalIncentivesByTiers(targetTier uint64) string { + en.MintAndDistributeGns() + + apiInternalIncentives := []ApiInternalIncentive{} + + poolTier.membership.Iterate("", "", func(key string, value interface{}) bool { + poolPath := key + internalTier := value.(uint64) + if internalTier != targetTier { + return false + } + + apiInternalIncentives = append(apiInternalIncentives, ApiInternalIncentive{ + PoolPath: poolPath, + Tier: internalTier, + RewardPerBlock: calculateInternalRewardPerBlockByPoolPath(poolPath), + }) + + return false + }) + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, incentive := range apiInternalIncentives { + _incentiveNode := json.ObjectNode("", map[string]*json.Node{ + "poolPath": json.StringNode("poolPath", incentive.PoolPath), + "rewardToken": json.StringNode("rewardToken", consts.GNS_PATH), + "tier": json.NumberNode("tier", float64(incentive.Tier)), + "rewardPerBlock": json.StringNode("rewardPerBlock", incentive.RewardPerBlock), + }) + responses.AppendArray(_incentiveNode) + } + + // RETURN + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func makeRewardTokensArray(rewardsTokenList []string) []*json.Node { + rewardsTokenArray := make([]*json.Node, len(rewardsTokenList)) + for i, rewardToken := range rewardsTokenList { + rewardsTokenArray[i] = json.StringNode("", rewardToken) + } + return rewardsTokenArray +} + +func calculateInternalRewardPerBlockByPoolPath(poolPath string) string { + reward := poolTier.CurrentRewardPerPool(poolPath) + return ufmt.Sprintf("%d", reward) +} + +// LpTokenReward represents the rewards associated with a specific LP token +type LpTokenReward struct { + LpTokenId uint64 `json:"lpTokenId"` // The ID of the LP token + Address string `json:"address"` // The address associated with the LP token + Rewards []ApiReward `json:"rewards"` +} + +// Reward represents a single reward for a staked LP token +type ApiReward struct { + IncentiveType string `json:"incentiveType"` // The type of incentive (INTERNAL or EXTERNAL) + IncentiveId string `json:"incentiveId"` // The unique identifier of the incentive + TargetPoolPath string `json:"targetPoolPath"` // The path of the target pool for the reward + RewardTokenPath string `json:"rewardTokenPath"` // The pathe of the reward token + RewardTokenAmount uint64 `json:"rewardTokenAmount"` // The amount of the reward token + StakeTimestamp int64 `json:"stakeTimestamp"` // The timestamp when the LP token was staked + StakeHeight int64 `json:"stakeHeight"` // The block height when the LP token was staked + IncentiveStart int64 `json:"incentiveStart"` // The timestamp when the incentive started +} + +// Stake represents a single stake +type ApiStake struct { + TokenId uint64 `json:"tokenId"` // The ID of the staked LP token + Owner std.Address `json:"owner"` // The address of the owner of the staked LP token + NumberOfStakes uint64 `json:"numberOfStakes"` // The number of times this LP token has been staked + StakeTimestamp int64 `json:"stakeTimestamp"` // The timestamp when the LP token was staked + StakeHeight int64 `json:"stakeHeight"` // The block height when the LP token was staked + TargetPoolPath string `json:"targetPoolPath"` // The path of the target pool for the stake +} + +// ResponseQueryBase contains basic information about a query response. +type ResponseQueryBase struct { + Height int64 `json:"height"` // The block height at the time of the query + Timestamp int64 `json:"timestamp"` // The timestamp at the time of the query +} + +// ResponseApiGetRewards represents the API response for getting rewards. +type ResponseApiGetRewards struct { + Stat ResponseQueryBase `json:"stat"` // Basic query information + Response []LpTokenReward `json:"response"` // A slice of LpTokenReward structs +} + +// ResponseApiGetRewardByLpTokenId represents the API response for getting rewards for a specific LP token. +type ResponseApiGetRewardByLpTokenId struct { + Stat ResponseQueryBase `json:"stat"` // Basic query information + Response LpTokenReward `json:"response"` // The LpTokenReward for the specified LP token +} + +// ResponseApiGetStakes represents the API response for getting stakes. +type ResponseApiGetStakes struct { + Stat ResponseQueryBase `json:"stat"` // Basic query information + Response []ApiStake `json:"response"` // A slice of Stake structs +} + +func ApiGetRewardsByLpTokenId(targetLpTokenId uint64) string { + en.MintAndDistributeGns() + + deposit := deposits.Get(targetLpTokenId) + + reward := calcPositionReward(std.GetHeight(), targetLpTokenId) + + rewards := []ApiReward{} + + if reward.Internal > 0 { + rewards = append(rewards, ApiReward{ + IncentiveType: "INTERNAL", + IncentiveId: "", + TargetPoolPath: deposit.targetPoolPath, + RewardTokenPath: consts.GNS_PATH, + RewardTokenAmount: reward.Internal, + StakeTimestamp: deposit.stakeTimestamp, + StakeHeight: deposit.stakeHeight, + IncentiveStart: deposit.stakeTimestamp, + }) + } + + for incentiveId, externalReward := range reward.External { + rewards = append(rewards, ApiReward{ + IncentiveType: "EXTERNAL", + IncentiveId: incentiveId, + TargetPoolPath: deposit.targetPoolPath, + RewardTokenPath: consts.GNS_PATH, + RewardTokenAmount: externalReward, + StakeTimestamp: deposit.stakeTimestamp, + StakeHeight: deposit.stakeHeight, + IncentiveStart: deposit.stakeTimestamp, + }) + } + + qb := ResponseQueryBase{ + Height: std.GetHeight(), + Timestamp: time.Now().Unix(), + } + + r := ResponseApiGetRewards{ + Stat: qb, + Response: []LpTokenReward{ + { + LpTokenId: targetLpTokenId, + Address: deposit.owner.String(), + Rewards: rewards, + }, + }, + } + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, reward := range r.Response { + _rewardNode := json.ObjectNode("", map[string]*json.Node{ + "lpTokenId": json.NumberNode("lpTokenId", float64(reward.LpTokenId)), + "address": json.StringNode("address", reward.Address), + "rewards": json.ArrayNode("rewards", makeRewardsArray(reward.Rewards)), + }) + responses.AppendArray(_rewardNode) + } + + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func ApiGetRewardsByAddress(targetAddress string) string { + en.MintAndDistributeGns() + + lpTokenRewards := []LpTokenReward{} + + stakers.IterateAll(std.Address(targetAddress), func(tokenId uint64, deposit *Deposit) bool { + rewards := []ApiReward{} + + reward := calcPositionReward(std.GetHeight(), tokenId) + + // get internal gns reward + if reward.Internal > 0 { + rewards = append(rewards, ApiReward{ + IncentiveType: "INTERNAL", + IncentiveId: "", + TargetPoolPath: deposit.targetPoolPath, + RewardTokenPath: consts.GNS_PATH, + RewardTokenAmount: reward.Internal, + StakeTimestamp: deposit.stakeTimestamp, + StakeHeight: deposit.stakeHeight, + IncentiveStart: deposit.stakeTimestamp, + }) + } + + // find all external reward list for poolPath which lpTokenId is staked + for incentiveId, externalReward := range reward.External { + rewards = append(rewards, ApiReward{ + IncentiveType: "EXTERNAL", + IncentiveId: incentiveId, + TargetPoolPath: deposit.targetPoolPath, + RewardTokenPath: consts.GNS_PATH, + RewardTokenAmount: externalReward, + StakeTimestamp: deposit.stakeTimestamp, + StakeHeight: deposit.stakeHeight, + IncentiveStart: deposit.stakeTimestamp, + }) + } + lpTokenReward := LpTokenReward{ + LpTokenId: tokenId, + Address: deposit.owner.String(), + Rewards: rewards, + } + lpTokenRewards = append(lpTokenRewards, lpTokenReward) + + return false + }) + + qb := ResponseQueryBase{ + Height: std.GetHeight(), + Timestamp: time.Now().Unix(), + } + + r := ResponseApiGetRewards{ + Stat: qb, + Response: lpTokenRewards, + } + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, reward := range r.Response { + _rewardNode := json.ObjectNode("", map[string]*json.Node{ + "lpTokenId": json.NumberNode("lpTokenId", float64(reward.LpTokenId)), + "address": json.StringNode("address", reward.Address), + "rewards": json.ArrayNode("rewards", makeRewardsArray(reward.Rewards)), + }) + responses.AppendArray(_rewardNode) + } + + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func ApiGetStakes() string { + en.MintAndDistributeGns() + + stakes := []ApiStake{} + deposits.Iterate(0, math.MaxUint64, func(tokenId uint64, deposit *Deposit) bool { + stakes = append(stakes, ApiStake{ + TokenId: tokenId, + Owner: deposit.owner, + StakeTimestamp: deposit.stakeTimestamp, + StakeHeight: deposit.stakeHeight, + TargetPoolPath: deposit.targetPoolPath, + }) + return false + }) + + qb := ResponseQueryBase{ + Height: std.GetHeight(), + Timestamp: time.Now().Unix(), + } + + r := ResponseApiGetStakes{ + Stat: qb, + Response: stakes, + } + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, stake := range r.Response { + _stakeNode := json.ObjectNode("", map[string]*json.Node{ + "tokenId": json.NumberNode("tokenId", float64(stake.TokenId)), + "owner": json.StringNode("owner", stake.Owner.String()), + "stakeTimestamp": json.NumberNode("stakeTimestamp", float64(stake.StakeTimestamp)), + "stakeHeight": json.NumberNode("stakeHeight", float64(stake.StakeHeight)), + "targetPoolPath": json.StringNode("targetPoolPath", stake.TargetPoolPath), + }) + responses.AppendArray(_stakeNode) + } + + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func ApiGetStakesByLpTokenId(targetLpTokenId uint64) string { + en.MintAndDistributeGns() + + stakes := []ApiStake{} + + deposit := deposits.Get(targetLpTokenId) + stakes = append(stakes, ApiStake{ + TokenId: targetLpTokenId, + Owner: deposit.owner, + StakeTimestamp: deposit.stakeTimestamp, + StakeHeight: deposit.stakeHeight, + TargetPoolPath: deposit.targetPoolPath, + }) + + qb := ResponseQueryBase{ + Height: std.GetHeight(), + Timestamp: time.Now().Unix(), + } + + r := ResponseApiGetStakes{ + Stat: qb, + Response: stakes, + } + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, stake := range r.Response { + _stakeNode := json.ObjectNode("", map[string]*json.Node{ + "tokenId": json.NumberNode("tokenId", float64(stake.TokenId)), + "owner": json.StringNode("owner", stake.Owner.String()), + "numberOfStakes": json.NumberNode("numberOfStakes", float64(stake.NumberOfStakes)), + "stakeTimestamp": json.NumberNode("stakeTimestamp", float64(stake.StakeTimestamp)), + "stakeHeight": json.NumberNode("stakeHeight", float64(stake.StakeHeight)), + "targetPoolPath": json.StringNode("targetPoolPath", stake.TargetPoolPath), + }) + responses.AppendArray(_stakeNode) + } + + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func ApiGetStakesByAddress(targetAddress string) string { + en.MintAndDistributeGns() + + stakes := []ApiStake{} + + stakers.IterateAll(std.Address(targetAddress), func(tokenId uint64, deposit *Deposit) bool { + stakes = append(stakes, ApiStake{ + TokenId: tokenId, + Owner: deposit.owner, + StakeTimestamp: deposit.stakeTimestamp, + StakeHeight: deposit.stakeHeight, + TargetPoolPath: deposit.targetPoolPath, + }) + + return false + }) + + qb := ResponseQueryBase{ + Height: std.GetHeight(), + Timestamp: time.Now().Unix(), + } + + r := ResponseApiGetStakes{ + Stat: qb, + Response: stakes, + } + + // STAT NODE + _stat := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(std.GetHeight())), + "timestamp": json.NumberNode("timestamp", float64(time.Now().Unix())), + }) + + // RESPONSE (ARRAY) NODE + responses := json.ArrayNode("", []*json.Node{}) + for _, stake := range r.Response { + _stakeNode := json.ObjectNode("", map[string]*json.Node{ + "tokenId": json.NumberNode("tokenId", float64(stake.TokenId)), + "owner": json.StringNode("owner", stake.Owner.String()), + "stakeTimestamp": json.NumberNode("stakeTimestamp", float64(stake.StakeTimestamp)), + "stakeHeight": json.NumberNode("stakeHeight", float64(stake.StakeHeight)), + "targetPoolPath": json.StringNode("targetPoolPath", stake.TargetPoolPath), + }) + responses.AppendArray(_stakeNode) + } + + node := json.ObjectNode("", map[string]*json.Node{ + "stat": _stat, + "response": responses, + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +// for off chain to check if lpTokenId is staked via RPC +func IsStaked(tokenId uint64) bool { + return deposits.Has(tokenId) +} + +func makeRewardsArray(rewards []ApiReward) []*json.Node { + rewardsArray := make([]*json.Node, len(rewards)) + + for i, reward := range rewards { + rewardsArray[i] = json.ObjectNode("", map[string]*json.Node{ + "incentiveType": json.StringNode("incentiveType", reward.IncentiveType), + "incentiveId": json.StringNode("incentiveId", reward.IncentiveId), + "targetPoolPath": json.StringNode("targetPoolPath", reward.TargetPoolPath), + "rewardTokenPath": json.StringNode("rewardTokenPath", reward.RewardTokenPath), + "rewardTokenAmount": json.NumberNode("rewardTokenAmount", float64(reward.RewardTokenAmount)), + "stakeTimestamp": json.NumberNode("stakeTimestamp", float64(reward.StakeTimestamp)), + "stakeHeight": json.NumberNode("stakeHeight", float64(reward.StakeHeight)), + "incentiveStart": json.NumberNode("incentiveStart", float64(reward.IncentiveStart)), + }) + } + return rewardsArray +} diff --git a/staker/calculate_pool_position_reward.gno b/staker/calculate_pool_position_reward.gno index 8153ed6a4..e0de8cc38 100644 --- a/staker/calculate_pool_position_reward.gno +++ b/staker/calculate_pool_position_reward.gno @@ -2,8 +2,7 @@ package staker import ( "gno.land/r/gnoswap/v1/consts" - "gno.land/r/gnoswap/v1/gns" - en "gno.land/r/gnoswap/v1/emission" + pl "gno.land/r/gnoswap/v1/pool" u256 "gno.land/p/gnoswap/uint256" ) @@ -18,32 +17,37 @@ func isAbleToCalculateEmissionReward(prev int64, current int64) bool { return true } +// Reward is a struct for storing reward for a position. +// Internal reward is the GNS reward, external reward is the reward for other incentives. +// Penalties are the amount that is deducted from the reward due to the position's warmup. type Reward struct { Internal uint64 InternalPenalty uint64 - External map[string]uint64 - ExternalPenalty map[string]uint64 + External map[string]uint64 // Incentive ID -> TokenAmount + ExternalPenalty map[string]uint64 // Incentive ID -> TokenAmount } -func calcPositionRewardByWarmups(currentHeight uint64, tokenId uint64) []Reward { +// calculate position reward by each warmup +func calcPositionRewardByWarmups(currentHeight int64, tokenId uint64) []Reward { rewards := CalcPositionReward(CalcPositionRewardParam{ CurrentHeight: currentHeight, - Deposits: deposits, - Pools: pools, - PoolTier: poolTier, - TokenId: tokenId, + Deposits: deposits, + Pools: pools, + PoolTier: poolTier, + TokenId: tokenId, }) return rewards } -func calcPositionReward(currentHeight uint64, tokenId uint64) Reward { +// calculate total position rewards and penalties +func calcPositionReward(currentHeight int64, tokenId uint64) Reward { rewards := CalcPositionReward(CalcPositionRewardParam{ CurrentHeight: currentHeight, - Deposits: deposits, - Pools: pools, - PoolTier: poolTier, - TokenId: tokenId, + Deposits: deposits, + Pools: pools, + PoolTier: poolTier, + TokenId: tokenId, }) internal := uint64(0) @@ -82,12 +86,13 @@ func calcPositionReward(currentHeight uint64, tokenId uint64) Reward { } } +// CalcPositionRewardParam is a struct for calculating position reward type CalcPositionRewardParam struct { // Environmental variables - CurrentHeight uint64 - Deposits *Deposits - Pools *Pools - PoolTier *PoolTier + CurrentHeight int64 + Deposits *Deposits + Pools *Pools + PoolTier *PoolTier // Position variables TokenId uint64 @@ -106,53 +111,57 @@ func CalcPositionReward(param CalcPositionRewardParam) []Reward { param.Pools.Set(poolPath, pool) } - // cacheInternalReward is called by poolTier.cacheReward - pool.cacheExternalReward(param.CurrentHeight) - // eligible(in-range) intervals for a position - // XXX: Tick ordering code, memoing for future - tickUpper := deposit.tickUpper - tickLower := deposit.tickLower - token0, token1, _ := poolPathDivide(poolPath) - if token1 < token0 { - tickUpper, tickLower = -tickLower, -tickUpper - } - upperTick := pool.ticks.Get(tickUpper) - lowerTick := pool.ticks.Get(tickLower) - // XXX: Tick ordering code, memoing for future - lastCollectHeight := deposit.lastCollectHeight - initialUpperCross := upperTick.previousCross(lastCollectHeight) - initialLowerCross := lowerTick.previousCross(lastCollectHeight) - currentlyInRange := initialUpperCross && !initialLowerCross + // Initializes reward/penalty arrays for rewards and penalties for each warmup + internalRewards := make([]uint64, len(deposit.warmups)) + internalPenalties := make([]uint64, len(deposit.warmups)) + externalRewards := make([]map[string]uint64, len(deposit.warmups)) + externalPenalties := make([]map[string]uint64, len(deposit.warmups)) + + if param.PoolTier.CurrentTier(poolPath) != 0 { + // Internal incentivized pool. + // Calculate reward for each warmup + internalRewards, internalPenalties = pool.RewardStateOf(deposit).CalculateInternalReward(lastCollectHeight, param.CurrentHeight) + } - tickUpperCrosses := upperTick.crossInfo(lastCollectHeight, param.CurrentHeight) - tickLowerCrosses := lowerTick.crossInfo(lastCollectHeight, param.CurrentHeight) + // All active incentives + allIncentives := pool.incentives.GetAllInHeights(lastCollectHeight, param.CurrentHeight) - internalRewards, internalPenalties := pool.InternalRewardOf(deposit).Calculate(int64(lastCollectHeight), int64(param.CurrentHeight), currentlyInRange, tickUpperCrosses, tickLowerCrosses) + for i := range externalRewards { + externalRewards[i] = make(map[string]uint64) + externalPenalties[i] = make(map[string]uint64) + } - externalRewards, externalPenalties := pool.ExternalRewardOf(deposit).Calculate(int64(lastCollectHeight), int64(param.CurrentHeight), currentlyInRange, tickUpperCrosses, tickLowerCrosses) + for incentiveId, incentive := range allIncentives { + // External incentivized pool. + // Calculate reward for each warmup + externalReward, externalPenalty := pool.RewardStateOf(deposit).CalculateExternalReward(int64(lastCollectHeight), int64(param.CurrentHeight), incentive) + + for i := range externalReward { + externalRewards[i][incentiveId] = externalReward[i] + externalPenalties[i][incentiveId] = externalPenalty[i] + } + } rewards := make([]Reward, len(internalRewards)) for i := range internalRewards { rewards[i] = Reward{ Internal: internalRewards[i], InternalPenalty: internalPenalties[i], - } - if externalRewards != nil { - if len(externalRewards[i]) > 0 { - rewards[i].External = externalRewards[i] - rewards[i].ExternalPenalty = externalPenalties[i] - } + External: externalRewards[i], + ExternalPenalty: externalPenalties[i], } } + return rewards } -func ProcessUnClaimableReward(poolPath string, endHeight uint64) (uint64, map[string]uint64) { +// calculates internal unclaimable reward for the pool +func ProcessUnClaimableReward(poolPath string, endHeight int64) uint64 { pool, ok := pools.Get(poolPath) if !ok { - return 0, make(map[string]uint64) + return 0 } return pool.processUnclaimableReward(poolTier, endHeight) } diff --git a/staker/callback.gno b/staker/callback.gno new file mode 100644 index 000000000..77fba4884 --- /dev/null +++ b/staker/callback.gno @@ -0,0 +1,21 @@ +package staker + +import ( + "std" +) + +// callbackStakerEmissionChange is called by emission when +// - msPerBlock is changed +// - staker emission % is changed +// it does NOT get called in regards of halving(manually handled in cacheReward). +// +// Initially, it is passed to emission in staker.gno#init(). +// +// For the parameter `emission`, which is a per-block emission for staker contract, +// - It first caches the reward until the current block height. +// - Then, it updates the `currentEmission` of the poolTier, +// which will be applied for future blocks thereafter. +func callbackStakerEmissionChange(emission uint64) { + poolTier.cacheReward(std.GetHeight(), pools) + poolTier.currentEmission = emission +} diff --git a/staker/getter.gno b/staker/getter.gno index 514268842..55f0484d4 100644 --- a/staker/getter.gno +++ b/staker/getter.gno @@ -13,8 +13,6 @@ import ( "gno.land/r/gnoswap/v1/gns" ) -//! TODO: Change function names to follow a naming convention - // GetPoolByPoolPath retrieves the pool's path using the provided pool path. // // Parameters: @@ -425,7 +423,7 @@ func GetDepositLiquidityAsString(lpTokenId uint64) string { // // Returns: // - uint64: The last collection height of the deposit. -func GetDepositLastCollectHeight(lpTokenId uint64) uint64 { +func GetDepositLastCollectHeight(lpTokenId uint64) int64 { deposit := GetDeposit(lpTokenId) return deposit.lastCollectHeight @@ -503,52 +501,7 @@ func GetPoolTierCount(tier uint64) uint64 { // Returns: // - uint64: The current reward amount for the specified tier. func GetPoolReward(tier uint64) uint64 { - rewardDenominators := poolTier.tierRatio.IntoRewardDenominators(poolTier.CurrentAllTierCounts()) - currentEmission := poolTier.emissionUpdate(uint64(std.GetHeight()), uint64(std.GetHeight())).LastEmissionUpdate - return ApplyDenominator(currentEmission, rewardDenominators[tier]) -} - -// GetRewardCacheOf retrieves the reward cache tree for the specified pool tier. -// -// This function returns the `RewardCacheTree` associated with the given tier, -// which contains cached reward data for the tier. -// -// Parameters: -// - tier (uint64): The tier for which the reward cache is retrieved. -// -// Returns: -// - *RewardCacheTree: A pointer to the reward cache tree for the specified tier. -//func GetRewardCacheOf(tier uint64) *RewardCacheTree { -// return poolTier.rewardCacheOf(tier) -//} - -func printExternalInfo(poolPath string) { - currentHeight := std.GetHeight() - println("***********************") - println("> height:", currentHeight) - println("> time:", time.Now().Unix()) - println("[ START ] GET_EXTERNAL INCENTIVE") - - externalIncentives.tree.Iterate("", "", func(key string, value interface{}) bool { - incentive := value.(*ExternalIncentive) - if incentive.targetPoolPath == poolPath { - println(" > incentiveId:", incentive.incentiveId) - println(" > targetPoolPath:", incentive.targetPoolPath) - println(" > rewardToken:", incentive.rewardToken) - println(" > rewardAmount:", strconv.FormatUint(incentive.rewardAmount, 10)) - println(" > rewardLeft:", strconv.FormatUint(incentive.rewardLeft, 10)) - println(" > startTimestamp:", strconv.FormatInt(incentive.startTimestamp, 10)) - println(" > endTimestamp:", strconv.FormatInt(incentive.endTimestamp, 10)) - rewardPerBlockU256 := u256.MustFromDecimal(strconv.FormatUint(incentive.rewardPerBlock, 10)) - q96 := u256.MustFromDecimal(consts.Q96) - rewardPerBlockX96 := u256.Zero().Mul(rewardPerBlockU256, q96) - println(" > rewardPerBlockX96:", rewardPerBlockX96.ToString()) - println(" > refundee:", incentive.refundee.String()) - } - return false - }) - - println("[ END ] GET_EXTERNAL INCENTIVE") + return poolTier.CurrentReward(tier) } // GetExternalIncentive retrieves an external incentive by its ID. diff --git a/staker/incentive_id.gno b/staker/incentive_id.gno index e89267eb2..567aef1f4 100644 --- a/staker/incentive_id.gno +++ b/staker/incentive_id.gno @@ -52,9 +52,9 @@ func incentiveIdCompute(caller std.Address, targetPoolPath, rewardToken string, // Example: // Input: startTime=12345, endTime=67890, creator="g1xyz", rewardToken="gns" // Output: "000000000000012345:000000000000067890:g1xyz:gns" -func incentiveIdByTime(startTime, endTime uint64, creator std.Address, rewardToken string) string { - startTimeEncode := EncodeUint(startTime) - endTimeEncode := EncodeUint(endTime) +func incentiveIdByTime(startTime, endTime int64, creator std.Address, rewardToken string) string { + startTimeEncode := EncodeUint(uint64(startTime)) + endTimeEncode := EncodeUint(uint64(endTime)) creatorEncode := creator.String() byTimeId := []byte(startTimeEncode) @@ -90,9 +90,9 @@ func incentiveIdByTime(startTime, endTime uint64, creator std.Address, rewardTok // Example: // Input: startHeight=123, endHeight=456, creator="g1xyz", rewardToken="gns" // Output: "000000000000000123:000000000000000456:g1xyz:gns", "g1xyz:000000000000000123:000000000000000456:gns" -func incentiveIdByHeight(startHeight, endHeight uint64, creator std.Address, rewardToken string) (string, string) { - startHeightEncode := EncodeUint(startHeight) - endHeightEncode := EncodeUint(endHeight) +func incentiveIdByHeight(startHeight, endHeight int64, creator std.Address, rewardToken string) (string, string) { + startHeightEncode := EncodeUint(uint64(startHeight)) + endHeightEncode := EncodeUint(uint64(endHeight)) creatorEncode := creator.String() byHeightId := []byte(startHeightEncode) diff --git a/staker/incentive_id_test.gno b/staker/incentive_id_test.gno index dcf66e9cd..2f54572aa 100644 --- a/staker/incentive_id_test.gno +++ b/staker/incentive_id_test.gno @@ -23,8 +23,8 @@ func TestIncentiveIdCompute(t *testing.T) { } func TestIncentiveIdByTime(t *testing.T) { - startTime := uint64(12345) - endTime := uint64(67890) + startTime := int64(12345) + endTime := int64(67890) creator := std.Address("g1xyz") rewardToken := "gns" @@ -37,8 +37,8 @@ func TestIncentiveIdByTime(t *testing.T) { } func TestIncentiveIdByHeight(t *testing.T) { - startHeight := uint64(123) - endHeight := uint64(456) + startHeight := int64(123) + endHeight := int64(456) creator := std.Address("g1xyz") rewardToken := "gns" diff --git a/staker/manage_pool_tier_and_warmup.gno b/staker/manage_pool_tier_and_warmup.gno index c62c9017a..fc89a3e5e 100644 --- a/staker/manage_pool_tier_and_warmup.gno +++ b/staker/manage_pool_tier_and_warmup.gno @@ -34,8 +34,8 @@ func SetPoolTier(poolPath string, tier uint64) { func setPoolTier(poolPath string, tier uint64) { en.MintAndDistributeGns() - poolTier.changeTier(uint64(std.GetHeight()), pools, poolPath, tier) pools.GetOrCreate(poolPath) + poolTier.changeTier(std.GetHeight(), pools, poolPath, tier) prevAddr, prevRealm := getPrev() std.Emit( @@ -66,7 +66,7 @@ func ChangePoolTier(poolPath string, tier uint64) { func changePoolTier(poolPath string, tier uint64) { en.MintAndDistributeGns() - poolTier.changeTier(uint64(std.GetHeight()), pools, poolPath, tier) + poolTier.changeTier(std.GetHeight(), pools, poolPath, tier) prevAddr, prevRealm := getPrev() std.Emit( @@ -97,7 +97,7 @@ func RemovePoolTier(poolPath string) { func removePoolTier(poolPath string) { en.MintAndDistributeGns() - poolTier.changeTier(uint64(std.GetHeight()), pools, poolPath, NOT_EMISSION_TARGET_TIER) + poolTier.changeTier(std.GetHeight(), pools, poolPath, NOT_EMISSION_TARGET_TIER) prevAddr, prevRealm := getPrev() std.Emit( diff --git a/staker/query.gno b/staker/query.gno index f396092ce..98f73fcce 100644 --- a/staker/query.gno +++ b/staker/query.gno @@ -40,7 +40,7 @@ func QueryPoolData(poolPath string) (*PoolData, error) { return nil, ufmt.Errorf("pool %s not found", poolPath) } - currentHeight := uint64(std.GetHeight()) + currentHeight := std.GetHeight() tier := poolTier.CurrentTier(poolPath) ictvIds := filterActiveIncentives(pool, currentHeight) @@ -49,7 +49,7 @@ func QueryPoolData(poolPath string) (*PoolData, error) { PoolPath: poolPath, Tier: tier, ActiveIncentives: ictvIds, - StakedLiquidity: pool.CurrentStakedLiquidity(currentHeight), + StakedLiquidity: pool.CurrentStakedLiquidity(std.GetHeight()), }, nil } @@ -106,14 +106,14 @@ func QueryDepositData(lpTokenId uint64) (*DepositData, error) { }, nil } -func filterActiveIncentives(pool *Pool, currentHeight uint64) []string { +func filterActiveIncentives(pool *Pool, currentHeight int64) []string { ictvIds := make([]string, 0) pool.incentives.byHeight.Iterate("", "", func(key string, value interface{}) bool { ictv := value.(*ExternalIncentive) - if uint64(ictv.startHeight) <= currentHeight && currentHeight < uint64(ictv.endHeight) { + if ictv.startHeight <= currentHeight && currentHeight < ictv.endHeight { ictvId := incentiveIdByTime( - uint64(ictv.startHeight), - uint64(ictv.endHeight), + ictv.startHeight, + ictv.endHeight, ictv.refundee, ictv.rewardToken, ) diff --git a/staker/query_test.gno b/staker/query_test.gno index 0d9a79284..0a8c3ba97 100644 --- a/staker/query_test.gno +++ b/staker/query_test.gno @@ -14,7 +14,7 @@ func TestQueryPoolData(t *testing.T) { testAddr := testutils.TestAddress("test_address") poolPath := "test_pool" - pool := NewPool(poolPath, uint64(std.GetHeight())) + pool := NewPool(poolPath, std.GetHeight()) pools.Set(poolPath, pool) // Add test incentive to pool @@ -26,8 +26,8 @@ func TestQueryPoolData(t *testing.T) { incentive := &ExternalIncentive{ startTimestamp: startTime, endTimestamp: endTime, - startHeight: int64(std.GetHeight()), - endHeight: int64(std.GetHeight() + 100), + startHeight: std.GetHeight(), + endHeight: std.GetHeight() + 100, rewardToken: rewardToken, rewardAmount: rewardAmount, refundee: testAddr, diff --git a/staker/reward_calculation_canonical_env_test.gno b/staker/reward_calculation_canonical_env_test.gno index 543214c19..e71afef13 100644 --- a/staker/reward_calculation_canonical_env_test.gno +++ b/staker/reward_calculation_canonical_env_test.gno @@ -26,22 +26,22 @@ type canonicalPool struct { tickCrossHook func(poolPath string, tickId int32, zeroForOne bool) } -func (self *canonicalPool) InternalReward(emission uint64, ratio *TierRatio) uint64 { +func (self *canonicalPool) InternalReward(emission uint64, ratio TierRatio, count uint64) uint64 { switch self.tier { case 0: return 0 case 1: - return emission * ratio.Tier1 / 100 + return emission * ratio.Tier1 / count / 100 case 2: - return emission * ratio.Tier2 / 100 + return emission * ratio.Tier2 / count / 100 case 3: - return emission * ratio.Tier3 / 100 + return emission * ratio.Tier3 / count / 100 default: panic("invalid tier") } } -func (self *canonicalPool) ExternalReward(currentHeight int) map[string]uint64 { +func (self *canonicalPool) ExternalReward(currentHeight int64) map[string]uint64 { reward := make(map[string]uint64) for _, incentive := range self.incentive { @@ -63,7 +63,7 @@ type canonicalRewardState struct { Pool map[string]*canonicalPool tickCrossHook func(poolPath string, tickId int32, zeroForOne bool) - Reward [][]Reward // blockNumber -> depositId -> reward + Reward []map[uint64]Reward // blockNumber -> depositId -> reward emissionUpdates *UintTree @@ -71,9 +71,14 @@ type canonicalRewardState struct { PerBlockEmission uint64 CurrentTimestamp int64 + + // emulated reward claimed by unstake + emulatedClaimedReward map[uint64]uint64 } func NewCanonicalRewardState(t *testing.T, pools *Pools, deposits *Deposits, tickCrossHook func(pools *Pools, height func() int64) func(poolPath string, tickId int32, zeroForOne bool)) *canonicalRewardState { + std.TestSkipHeights(1) + result := &canonicalRewardState{ t: t, global: &emulatedGlobalState{ @@ -82,21 +87,38 @@ func NewCanonicalRewardState(t *testing.T, pools *Pools, deposits *Deposits, tic deposits: deposits, }, Pool: make(map[string]*canonicalPool), - Reward: make([][]Reward, 0), + Reward: make([]map[uint64]Reward, 124), emissionUpdates: NewUintTree(), MsPerBlock: 1000, PerBlockEmission: 1000000000, CurrentTimestamp: 0, + + emulatedClaimedReward: make(map[uint64]uint64), } result.tickCrossHook = tickCrossHook(pools, func() int64 { return int64(result.CurrentHeight()) }) - result.global.poolTier = NewPoolTier(1, test_gnousdc, result.EmissionUpdates) + + // Technically, the block number being provided to the NewPoolTier should be 124, but because of duplicate creation of the default pool, we put 123 here to just make rewardCache work for the testing. + result.global.poolTier = NewPoolTier(pools, 123, test_gnousdc, func() uint64 { return result.PerBlockEmission }, func(start, end int64) ([]int64, []uint64) { + heights := make([]int64, 0) + emissions := make([]uint64, 0) + result.emissionUpdates.Iterate(start, end, func(key int64, value interface{}) bool { + heights = append(heights, key) + emissions = append(emissions, value.(uint64)) + return false + }) + return heights, emissions + }) result.NextBlock() // must skip height 0 result.SetEmissionUpdate(1000000000) + if std.GetHeight() != result.CurrentHeight() { + panic(ufmt.Sprintf("height mismatch: %d != %d", std.GetHeight(), result.CurrentHeight())) + } + return result } @@ -111,43 +133,14 @@ func (self *canonicalRewardState) isInRange(deposit *Deposit) bool { return deposit.tickLower <= tick && tick < deposit.tickUpper } -func (self *canonicalRewardState) GetLatestEmissionUpdate() uint64 { - var emission uint64 - self.emissionUpdates.ReverseIterate(0, uint64(self.CurrentHeight()), func(key uint64, value interface{}) bool { - emission = value.(uint64) - return true - }) - return emission -} - -func (self *canonicalRewardState) EmissionUpdates(startHeight uint64, endHeight uint64) en.EmissionUpdate { - heights := make([]uint64, 0) - updates := make([]uint64, 0) - self.emissionUpdates.Iterate(startHeight, endHeight, func(key uint64, value interface{}) bool { - heights = append(heights, key) - updates = append(updates, value.(uint64)) - return false - }) - - return en.EmissionUpdate{ - LastEmissionUpdate: self.GetLatestEmissionUpdate(), - EmissionUpdateHeights: heights, - EmissionUpdates: updates, - } -} - func (self *canonicalRewardState) SetEmissionUpdate(emission uint64) { - self.emissionUpdates.Set(uint64(self.CurrentHeight()), emission) + self.emissionUpdates.Set(self.CurrentHeight(), emission) self.PerBlockEmission = emission } func (self *canonicalRewardState) LiquidityPerPool() (map[string]*u256.Uint) { liquidity := make(map[string]*u256.Uint) self.global.deposits.Iterate(0, math.MaxUint64, func(tokenId uint64, deposit *Deposit) bool { - if deposit.liquidity.IsZero() { // removed deposit - return false - } - if !self.isInRange(deposit) { return false } @@ -175,13 +168,13 @@ func (self *canonicalRewardState) InternalRewardPerPool(emission uint64) map[str ratio := TierRatioFromCounts(tierCount[1], tierCount[2], tierCount[3]) for _, pool := range self.Pool { - reward[pool.poolPath] = pool.InternalReward(emission, ratio) + reward[pool.poolPath] = pool.InternalReward(emission, ratio, tierCount[pool.tier]) } return reward } -func (self *canonicalRewardState) ExternalRewardPerPool(currentHeight int) map[string]map[string]uint64 { +func (self *canonicalRewardState) ExternalRewardPerPool(currentHeight int64) map[string]map[string]uint64 { reward := make(map[string]map[string]uint64) for _, pool := range self.Pool { @@ -191,26 +184,22 @@ func (self *canonicalRewardState) ExternalRewardPerPool(currentHeight int) map[s return reward } -func (self *canonicalRewardState) CurrentHeight() int { - return len(self.Reward) +func (self *canonicalRewardState) CurrentHeight() int64 { + return int64(len(self.Reward)) // due to testing requirement } // Process block with canonical reward calculation -func (self *canonicalRewardState) CalculateCanonicalReward() []Reward { +func (self *canonicalRewardState) CalculateCanonicalReward() map[uint64]Reward { currentHeight := self.CurrentHeight() + 1 - rewards := make([]Reward, self.global.deposits.Size()) + rewards := make(map[uint64]Reward) liquidityPerPool := self.LiquidityPerPool() internalRewardPerPool := self.InternalRewardPerPool(self.PerBlockEmission) - externalRewardPerPool := self.ExternalRewardPerPool(currentHeight) + externalRewardPerPool := self.ExternalRewardPerPool(int64(currentHeight)) - for i := 0; i < self.global.deposits.Size(); i++ { - deposit := self.global.deposits.Get(uint64(i)) - if deposit.liquidity.IsZero() { // removed deposit - continue - } + self.global.deposits.Iterate(0, math.MaxUint64, func(tokenId uint64, deposit *Deposit) bool { if !self.isInRange(deposit) { - continue + return false } warmup := deposit.warmups[deposit.FindWarmup(int64(currentHeight))] @@ -223,13 +212,14 @@ func (self *canonicalRewardState) CalculateCanonicalReward() []Reward { externals[key] = external externalPenalties[key] = externalPenalty } - rewards[i] = Reward{ + rewards[tokenId] = Reward{ Internal: internal, External: externals, InternalPenalty: internalPenalty, ExternalPenalty: externalPenalties, } - } + return false + }) return rewards } @@ -237,90 +227,126 @@ func (self *canonicalRewardState) CalculateCanonicalReward() []Reward { func (self *canonicalRewardState) NextBlock() { self.Reward = append(self.Reward, self.CalculateCanonicalReward()) self.CurrentTimestamp += self.MsPerBlock + std.TestSkipHeights(1) + if int64(self.CurrentHeight()) != std.GetHeight() { + panic(ufmt.Sprintf("height mismatch: %d != %d", self.CurrentHeight(), std.GetHeight())) + } } func (self *canonicalRewardState) NextBlockNoCanonical() { self.Reward = append(self.Reward, nil) // just placeholder + self.CurrentTimestamp += self.MsPerBlock + std.TestSkipHeights(1) + if int64(self.CurrentHeight()) != std.GetHeight() { + panic(ufmt.Sprintf("height mismatch: %d != %d", self.CurrentHeight(), std.GetHeight())) + } +} + +func (self *canonicalRewardState) UnclaimableExternalRewardOf(depositId uint64, incentiveId string) uint64 { + pool, ok := self.global.pools.Get(self.global.deposits.Get(depositId).targetPoolPath) + if !ok { + panic("pool not found") + } + + return pool.incentives.calculateUnclaimableReward(incentiveId) } func (self *canonicalRewardState) CanonicalRewardOf(depositId uint64) Reward { return self.Reward[self.CurrentHeight()-1][depositId] } +func (self *canonicalRewardState) SafeCanonicalRewardOf(depositId uint64) (Reward, bool) { + rewards := self.Reward[self.CurrentHeight()-1] + reward, ok := rewards[depositId] + return reward, ok +} + func (self *canonicalRewardState) CanonicalRewardOfHeight(depositId uint64, height uint64) Reward { return self.Reward[height][depositId] } func (self *canonicalRewardState) EmulateCalcPositionReward(tokenId uint64) ([]uint64, []uint64, []map[string]uint64, []map[string]uint64) { - currentHeight := uint64(self.CurrentHeight()) - emissionUpdate := self.EmissionUpdates(*self.global.poolTier.lastRewardCacheHeight, currentHeight) - - deposits := self.global.deposits - pools := self.global.pools - poolTier := self.global.poolTier + currentHeight := self.CurrentHeight() // cache per-tier and per-pool rewards - poolTier.cacheReward(currentHeight, pools) + self.global.poolTier.cacheReward(currentHeight, self.global.pools) + + deposit := self.global.deposits.Get(tokenId) + - deposit := deposits.Get(tokenId) poolPath := deposit.targetPoolPath - pool, ok := pools.Get(poolPath) + pool, ok := self.global.pools.Get(poolPath) if !ok { pool = NewPool(poolPath, currentHeight) - pools.Set(poolPath, pool) + self.global.pools.Set(poolPath, pool) } - // cacheInternalReward is called by poolTier.cacheReward - pool.cacheExternalReward(currentHeight) - - // eligible(in-range) intervals for a position - upperTick := pool.ticks.Get(deposit.tickUpper) - lowerTick := pool.ticks.Get(deposit.tickLower) - lastCollectHeight := deposit.lastCollectHeight - initialUpperCross := upperTick.previousCross(lastCollectHeight) - initialLowerCross := lowerTick.previousCross(lastCollectHeight) + internalRewards, internalPenalties := pool.RewardStateOf(deposit).CalculateInternalReward(int64(lastCollectHeight), int64(currentHeight)) - currentlyInRange := initialUpperCross && !initialLowerCross + externalRewards := make([]map[string]uint64, 4) + externalPenalties := make([]map[string]uint64, 4) + for i := range externalRewards { + externalRewards[i] = make(map[string]uint64) + externalPenalties[i] = make(map[string]uint64) + } - tickUpperCrosses := upperTick.crossInfo(lastCollectHeight, currentHeight) - tickLowerCrosses := lowerTick.crossInfo(lastCollectHeight, currentHeight) + allIncentives := pool.incentives.GetAllInHeights(int64(lastCollectHeight), int64(currentHeight)) - internalRewards, internalPenalties := pool.InternalRewardOf(deposit).Calculate(int64(lastCollectHeight), int64(currentHeight), currentlyInRange, tickUpperCrosses, tickLowerCrosses) + for incentiveId, incentive := range allIncentives { + externalReward, externalPenalty := pool.RewardStateOf(deposit).CalculateExternalReward(int64(lastCollectHeight), int64(currentHeight), incentive) - externalRewards, externalPenalties := pool.ExternalRewardOf(deposit).Calculate(int64(lastCollectHeight), int64(currentHeight), currentlyInRange, tickUpperCrosses, tickLowerCrosses) + for i := range externalReward { + externalRewards[i][incentive.incentiveId] = externalReward[i] + externalPenalties[i][incentive.incentiveId] = externalPenalty[i] + } + } return internalRewards, internalPenalties, externalRewards, externalPenalties } func (self *canonicalRewardState) EmulatedRewardOf(depositId uint64) Reward { - // emissionUpdateHeights, emissionUpdates := self.EmissionUpdates(*self.global.poolTier.lastRewardCacheHeight, uint64(self.CurrentHeight())) + if !self.global.deposits.Has(depositId) { + claimed := self.emulatedClaimedReward[depositId] + self.emulatedClaimedReward[depositId] = 0 + return Reward{ + Internal: claimed, + InternalPenalty: 0, + External: make(map[string]uint64), + ExternalPenalty: make(map[string]uint64), + } + } rewards, penalties, externalRewards, externalPenalties := self.EmulateCalcPositionReward(depositId) deposit := self.global.deposits.Get(depositId) - deposit.lastCollectHeight = uint64(self.CurrentHeight()) + deposit.lastCollectHeight = self.CurrentHeight() internal := uint64(0) for _, reward := range rewards { internal += reward } + claimed, ok := self.emulatedClaimedReward[depositId] + if ok { + internal += claimed + self.emulatedClaimedReward[depositId] = 0 + } internalPenalty := uint64(0) for _, penalty := range penalties { internalPenalty += penalty } external := make(map[string]uint64) for _, er := range externalRewards { - for tokenId, reward := range er { - external[tokenId] += reward + for incentiveId, reward := range er { + external[incentiveId] += reward } } externalPenalty := make(map[string]uint64) for _, ep := range externalPenalties { - for tokenId, penalty := range ep { - externalPenalty[tokenId] += penalty + for incentiveId, penalty := range ep { + externalPenalty[incentiveId] += penalty } } @@ -332,18 +358,14 @@ func (self *canonicalRewardState) EmulatedRewardOf(depositId uint64) Reward { } } -// Emulation of gns.gno emission changes -func (self *canonicalRewardState) SetMsPerBlock(msPerBlock int64) { - // TODO: implement -} - -func (self *canonicalRewardState) SetPerBlockEmission(perBlockEmission uint64) { - // TODO: implement -} - // Emulation of staker.gno public entrypoints func (self *canonicalRewardState) StakeToken(tokenId uint64, targetPoolPath string, owner std.Address, tickLower int32, tickUpper int32, liquidity *u256.Uint) error { - currentHeight := uint64(self.CurrentHeight()) + currentHeight := self.CurrentHeight() + pool, ok := self.global.pools.Get(targetPoolPath) + if !ok { + panic("should not happen 1") + } + deposit := &Deposit{ owner: owner, stakeHeight: int64(currentHeight), @@ -351,8 +373,8 @@ func (self *canonicalRewardState) StakeToken(tokenId uint64, targetPoolPath stri tickLower: tickLower, tickUpper: tickUpper, liquidity: liquidity, - lastCollectHeight: uint64(currentHeight), - warmups: InstantiateWarmup(int64(currentHeight)), + lastCollectHeight: currentHeight, + warmups: InstantiateWarmup(currentHeight), } canonicalPool, ok := self.Pool[deposit.targetPoolPath] if !ok { @@ -364,48 +386,48 @@ func (self *canonicalRewardState) StakeToken(tokenId uint64, targetPoolPath stri // update global state self.global.deposits.Set(tokenId, deposit) - pool, ok := self.global.pools.Get(deposit.targetPoolPath) - if !ok { - panic("should not happen") - } + + self.global.poolTier.cacheReward(currentHeight, self.global.pools) + signedLiquidity := i256.FromUint256(deposit.liquidity) if self.isInRange(deposit) { - pool.modifyDeposit(tokenId, signedLiquidity, currentHeight) + pool.modifyDeposit(signedLiquidity, currentHeight, canonicalPool.tick) } + // historical tick must be set regardless of the deposit's range + pool.historicalTick.Set(currentHeight, canonicalPool.tick) + pool.ticks.Get(deposit.tickLower).modifyDepositLower(currentHeight, canonicalPool.tick, signedLiquidity) pool.ticks.Get(deposit.tickUpper).modifyDepositUpper(currentHeight, canonicalPool.tick, signedLiquidity) return nil } -func (self *canonicalRewardState) CollectReward(tokenId uint64) { - // TODO: implement -} - func (self *canonicalRewardState) UnstakeToken(tokenId uint64) { deposit := self.global.deposits.Get(tokenId) - currentHeight := uint64(self.CurrentHeight()) + currentHeight := self.CurrentHeight() canonicalPool, ok := self.Pool[deposit.targetPoolPath] if !ok { - panic("should not happen") + panic("should not happen 2") } + // Emulating CollectReward() + reward := self.EmulatedRewardOf(tokenId) + self.emulatedClaimedReward[tokenId] += reward.Internal + // update global state // we will not gonna actually remove the deposit in sake of logic simplicity - // self.global.deposits.Remove(tokenId) - self.global.deposits.Set(tokenId, &Deposit{ - liquidity: u256.Zero(), - }) + self.global.deposits.Remove(tokenId) + pool, ok := self.global.pools.Get(deposit.targetPoolPath) if !ok { - panic("should not happen") + panic("should not happen 3") } signedLiquidity := i256.FromUint256(deposit.liquidity) signedLiquidity = signedLiquidity.Neg(signedLiquidity) if self.isInRange(deposit) { - pool.modifyDeposit(tokenId, signedLiquidity, currentHeight) + pool.modifyDeposit(signedLiquidity, currentHeight, canonicalPool.tick) } pool.ticks.Get(deposit.tickLower).modifyDepositLower(currentHeight, canonicalPool.tick, signedLiquidity) pool.ticks.Get(deposit.tickUpper).modifyDepositUpper(currentHeight, canonicalPool.tick, signedLiquidity) @@ -423,7 +445,10 @@ func newExternalIncentiveByHeight( ) *ExternalIncentive { rewardPerBlock := rewardAmount / uint64(endHeight-startHeight) + byTimeId := incentiveIdByTime(startTimestamp, endTimestamp, refundee, rewardToken) + return &ExternalIncentive{ + incentiveId: byTimeId, targetPoolPath: targetPoolPath, rewardToken: rewardToken, rewardAmount: rewardAmount, @@ -460,10 +485,6 @@ func (self *canonicalRewardState) CreateExternalIncentive(targetPoolPath string, return incentive.incentiveId } -func (self *canonicalRewardState) EndExternalIncentive(targetPoolPath string, rewardToken string) { - // TODO: implement -} - func (self *canonicalRewardState) ChangePoolTier(poolPath string, tier uint64) { // update canonical state pool, ok := self.Pool[poolPath] @@ -481,9 +502,9 @@ func (self *canonicalRewardState) ChangePoolTier(poolPath string, tier uint64) { // update global state if !self.global.pools.Has(poolPath) { - self.global.pools.Set(poolPath, NewPool(poolPath, uint64(self.CurrentHeight()))) + self.global.pools.Set(poolPath, NewPool(poolPath, self.CurrentHeight())) } - self.global.poolTier.changeTier(uint64(self.CurrentHeight()), self.global.pools, poolPath, tier) + self.global.poolTier.changeTier(self.CurrentHeight(), self.global.pools, poolPath, tier) } func (self *canonicalRewardState) CreatePool(poolPath string, initialTier uint64, initialTick int32) { @@ -494,25 +515,25 @@ func (self *canonicalRewardState) CreatePool(poolPath string, initialTier uint64 incentive: make([]*ExternalIncentive, 0), tickCrossHook: self.tickCrossHook, } - self.global.pools.Set(poolPath, NewPool(poolPath, uint64(self.CurrentHeight()))) - self.global.poolTier.changeTier(uint64(self.CurrentHeight()), self.global.pools, poolPath, initialTier) + self.global.pools.Set(poolPath, NewPool(poolPath, self.CurrentHeight())) + self.global.poolTier.changeTier(self.CurrentHeight(), self.global.pools, poolPath, initialTier) } func (self *canonicalRewardState) MoveTick(poolPath string, tick int32) { pool, ok := self.Pool[poolPath] if !ok { - panic("should not happen") + panic("should not happen 4") } globalPool, ok := self.global.pools.Get(poolPath) if !ok { - panic("should not happen") + panic("should not happen 5") } if pool.tick == tick { return } - self.t.Logf("tick: %d, moving to %d", pool.tick, tick) + self.t.Logf(" [%d] (%d->%d) %s", self.CurrentHeight(), pool.tick, tick, pool.poolPath) zeroForOne := tick < pool.tick // true if moving left, false if moving right if zeroForOne { @@ -570,14 +591,15 @@ func isInErrorRange(expected uint64, actual uint64) bool { func (self *canonicalRewardState) AssertEmulatedRewardOf(depositId uint64, expected uint64) { reward := self.EmulatedRewardOf(depositId) if !isInErrorRange(expected, reward.Internal) { - self.t.Errorf("emulated reward of %d mismatch: expected %d, got %d(%s@%d)", depositId, expected, reward.Internal, self.Pool[self.global.deposits.Get(depositId).targetPoolPath].poolPath, self.Pool[self.global.deposits.Get(depositId).targetPoolPath].tick) + self.t.Errorf("emulated reward of %d mismatch: expected %d, got %d", depositId, expected, reward.Internal) + panic("emulated reward mismatch") } } -func (self *canonicalRewardState) AssertEmulatedExternalRewardOf(depositId uint64, tokenId string, expected uint64) { +func (self *canonicalRewardState) AssertEmulatedExternalRewardOf(depositId uint64, incentiveId string, expected uint64) { reward := self.EmulatedRewardOf(depositId) - if !isInErrorRange(expected, reward.External[tokenId]) { - self.t.Errorf("emulated external reward of %d mismatch: expected %d, got %d", depositId, expected, reward.External[tokenId]) + if !isInErrorRange(expected, reward.External[incentiveId]) { + self.t.Errorf("!!!!emulated external reward of %d mismatch: expected %d, got %d", depositId, expected, reward.External[incentiveId]) } } diff --git a/staker/reward_calculation_canonical_test.gnoA b/staker/reward_calculation_canonical_test.gnoA index 30e95e84a..ade109f72 100644 --- a/staker/reward_calculation_canonical_test.gnoA +++ b/staker/reward_calculation_canonical_test.gnoA @@ -20,7 +20,7 @@ func Setup(t *testing.T) *canonicalRewardState { return NewCanonicalRewardState(t, pools, deposits, TickCrossHook) } -func TestSimple(t *testing.T) { +func TestCanonicalSimple(t *testing.T) { canonical := Setup(t) gnousdc := test_gnousdc @@ -50,7 +50,7 @@ func TestSimple(t *testing.T) { } // To check precision error -func TestLargeStakedLiquidity(t *testing.T) { +func TestCanonicalLargeStakedLiquidity(t *testing.T) { canonical := Setup(t) gnousdc := test_gnousdc @@ -75,7 +75,7 @@ func TestLargeStakedLiquidity(t *testing.T) { } // To check precision error -func TestLargeStakedLiquidity_2(t *testing.T) { +func TestCanonicalLargeStakedLiquidity_2(t *testing.T) { canonical := Setup(t) gnousdc := test_gnousdc @@ -119,7 +119,7 @@ func TestLargeStakedLiquidity_2(t *testing.T) { } // Tests simple case with tick crossing -func TestTickCross_0(t *testing.T) { +func TestCanonicalTickCross_0(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -197,7 +197,7 @@ func TestTickCross_0(t *testing.T) { } // Tests tick crossing with lazy evaluation of position reward -func TestTickCross_1(t *testing.T) { +func TestCanonicalTickCross_1(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -248,7 +248,7 @@ func TestTickCross_1(t *testing.T) { } // Test tick crossing with multiple positions with same tick, same liquidity -func TestTickCross_2(t *testing.T) { +func TestCanonicalTickCross_2(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -267,7 +267,7 @@ func TestTickCross_2(t *testing.T) { t.Errorf("StakeToken failed: %s", err.Error()) } - err = canonical.StakeToken( + err = canonical.StakeToken( 1, gnousdc, std.Address("gno1qyqszqgpqyqszqgpqyqszqgpqyqszqgq"), @@ -310,7 +310,7 @@ func TestTickCross_2(t *testing.T) { } // Test tick crossing with multiple positions with same tick, different liquidity -func TestTickCross_3(t *testing.T) { +func TestCanonicalTickCross_3(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -324,11 +324,10 @@ func TestTickCross_3(t *testing.T) { 200, u256.NewUint(1000000000000000000), ) - if err != nil { t.Errorf("StakeToken failed: %s", err.Error()) } - + err = canonical.StakeToken( 1, gnousdc, @@ -372,7 +371,7 @@ func TestTickCross_3(t *testing.T) { } // Test tick crossing with multiple positions with different tick, same liquidity -func TestTickCross_4(t *testing.T) { +func TestCanonicalTickCross_4(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -443,7 +442,7 @@ func TestTickCross_4(t *testing.T) { } // Test tick crossing at tick boundaries, forward direction -func TestTickCross_5(t *testing.T) { +func TestCanonicalTickCross_5(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -513,7 +512,7 @@ func TestTickCross_5(t *testing.T) { } // Test tick crossing at tick boundaries, backward direction -func TestTickCross_6(t *testing.T) { +func TestCanonicalTickCross_6(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -571,13 +570,14 @@ func TestTickCross_6(t *testing.T) { canonical.AssertEmulatedRewardOf(0, 0) } -func TestExternalReward_1(t *testing.T) { +func TestCanonicalExternalReward_1(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) canonical.CreatePool(gnousdc, 1, 200) - incentiveId := canonical.CreateExternalIncentive(gnousdc, gnsPath, 100000000, 1, 5, 1, 5, std.Address("gno1qyqszqgpqyqszqgpqyqszqgpqyqszqgp")) + currentHeight := int64(canonical.CurrentHeight()) + incentiveId := canonical.CreateExternalIncentive(gnousdc, gnsPath, 100000000, currentHeight+1, currentHeight+5, currentHeight+1, currentHeight+5, std.Address("gno1qyqszqgpqyqszqgpqyqszqgpqyqszqgp")) err := canonical.StakeToken( 0, @@ -594,12 +594,106 @@ func TestExternalReward_1(t *testing.T) { canonical.NextBlock() + // Incentive has been started, but but we can collect rewards accumulated until the previous block + + canonical.NextBlock() + + // now there is a single block worth of reward + expected := uint64(100000000/4) * 30 / 100 canonical.AssertEmulatedExternalRewardOf(uint64(0), incentiveId, expected) } -func TestMultiplePool_1(t *testing.T) { + +func TestCanonicalExternalReward_2(t *testing.T) { + canonical := Setup(t) + + gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) + canonical.CreatePool(gnousdc, 1, 200) + + currentHeight := int64(canonical.CurrentHeight()) + incentiveId := canonical.CreateExternalIncentive(gnousdc, gnsPath, 100000000, currentHeight+1, currentHeight+5, currentHeight+1, currentHeight+5, std.Address("gno1qyqszqgpqyqszqgpqyqszqgpqyqszqgp")) + + canonical.NextBlock() + canonical.NextBlock() + + // Incentive has been already started + + err := canonical.StakeToken( + 0, + gnousdc, + std.Address("gno1qyqszqgpqyqszqgpqyqszqgpqyqszqgg"), + 100, + 300, + u256.NewUint(1000000000000000000), + ) + + if err != nil { + t.Errorf("StakeToken failed: %s", err.Error()) + } + + canonical.NextBlock() + + // now there is a single block worth of reward + + expected := uint64(100000000/4) * 30 / 100 + + canonical.AssertEmulatedExternalRewardOf(uint64(0), incentiveId, expected) +} + +func TestCanonicalExternalReward_3(t *testing.T) { + canonical := Setup(t) + + gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) + canonical.CreatePool(gnousdc, 1, 200) + + currentHeight := int64(canonical.CurrentHeight()) + incentiveId := canonical.CreateExternalIncentive(gnousdc, gnsPath, 100000000, currentHeight+1, currentHeight+5, currentHeight+1, currentHeight+5, std.Address("gno1qyqszqgpqyqszqgpqyqszqgpqyqszqgp")) + + err := canonical.StakeToken( + 0, + gnousdc, + std.Address("gno1qyqszqgpqyqszqgpqyqszqgpqyqszqgg"), + 100, + 300, + u256.NewUint(1000000000000000000), + ) + + if err != nil { + t.Errorf("StakeToken failed: %s", err.Error()) + } + canonical.NextBlock() + + // eligible + + canonical.NextBlock() + + // eligible + + canonical.NextBlock() + + // not eligible + + canonical.MoveTick(gnousdc, 400) + + // not eligible + + canonical.NextBlock() + + // incentive has been ended + + expected := uint64(100000000/4*2) * 30 / 100 + + canonical.AssertEmulatedExternalRewardOf(uint64(0), incentiveId, expected) + + if canonical.UnclaimableExternalRewardOf(uint64(0), incentiveId) != uint64(100000000/4*2) { + t.Errorf("UnclaimableExternalRewardOf(uint64(0), incentiveId) = %d; want %d", canonical.UnclaimableExternalRewardOf(uint64(0), incentiveId), uint64(100000000/4*2)) + } +} + + +func TestCanonicalMultiplePool_1(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -662,7 +756,7 @@ func TestMultiplePool_1(t *testing.T) { canonical.AssertEmulatedRewardOf(1, expected/2) } -func TestMultiplePool_2(t *testing.T) { +func TestCanonicalMultiplePool_2(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -746,7 +840,7 @@ func TestMultiplePool_2(t *testing.T) { } // Large number of blocks passed -func TestLargeBlocksPassed(t *testing.T) { +func TestCanonicalLargeBlocksPassed(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -780,7 +874,7 @@ func GetPoolPath(token0Path, token1Path string, fee uint32) string { return ufmt.Sprintf("%s:%s:%d", token0Path, token1Path, fee) } -func TestWarmup_1(t *testing.T) { +func TestCanonicalWarmup_1(t *testing.T) { modifyWarmup(0, 10) modifyWarmup(1, 10) modifyWarmup(2, 10) @@ -858,9 +952,10 @@ func TestWarmup_1(t *testing.T) { canonical.NextBlock() canonical.NextBlock() canonical.AssertEmulatedRewardOf(0, expected*10) + } -func TestWarmup_2(t *testing.T) { +func TestCanonicalWarmup_2(t *testing.T) { modifyWarmup(0, 10) modifyWarmup(1, 10) modifyWarmup(2, 10) @@ -943,7 +1038,7 @@ func TestWarmup_2(t *testing.T) { // randomized // Test tick crossing at tick boundaries, random direction -func TestTickCross_7(t *testing.T) { +func TestCanonicalTickCross_7(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -987,7 +1082,7 @@ func TestTickCross_7(t *testing.T) { // Test tick crossing with multiple positions with different tick, different liquidity // Equivalence test -func TestTickCross_8(t *testing.T) { +func TestCanonicalTickCross_8(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -1046,7 +1141,7 @@ func TestTickCross_8(t *testing.T) { // Test tick crossing with multiple positions with different tick, different liquidity, emulated reward flushed for every 100 blocks // Equivalence test -func TestTickCross_9(t *testing.T) { +func TestCanonicalTickCross_9(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -1106,10 +1201,10 @@ func TestTickCross_9(t *testing.T) { } } -/* + // Test tick crossing with multiple positions with different tick, different liquidity, emulated reward flushed for every 100 blocks // Equivalence test -func TestTickCross_10(t *testing.T) { +func TestCanonicalTickCross_10(t *testing.T) { canonical := Setup(t) gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) @@ -1119,8 +1214,8 @@ func TestTickCross_10(t *testing.T) { 0, gnousdc, std.Address("gno1qyqszqgpqyqszqgpqyqszqgpqyqszqgp"), - -100, -300, + -100, u256.NewUint(1000000000000000000), ) @@ -1132,8 +1227,8 @@ func TestTickCross_10(t *testing.T) { 1, gnousdc, std.Address("gno1qyqszqgpqyqszqgpqyqszqgpqyqszqgq"), - -200, -400, + -200, u256.NewUint(2000000000000000000), ) @@ -1145,8 +1240,8 @@ func TestTickCross_10(t *testing.T) { 2, gnousdc, std.Address("gno1qyqszqgpqyqszqgpqyqszqgpqyqszqgz"), - -300, -500, + -300, u256.NewUint(3000000000000000000), ) @@ -1168,4 +1263,288 @@ func TestTickCross_10(t *testing.T) { canonical.AssertEmulatedRewardMap(canonicalRewardMap) } } - */ \ No newline at end of file + +func TestCanonicalEmissionChange_0(t *testing.T) { + canonical := Setup(t) + + gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) + canonical.CreatePool(gnousdc, 1, 200) + + err := canonical.StakeToken( + 0, + gnousdc, + std.Address("gno1qyqszqgpqyqszqgpqyqszqgpqyqszqgp"), + 100, + 300, + u256.NewUint(1000000000000000000), + ) + + canonical.NextBlock() + + expected0 := canonical.PerBlockEmission * 30 / 100 + + t.Logf("expected0: %d", expected0) + + canonical.SetEmissionUpdate(canonical.PerBlockEmission * 10 / 100) + + canonical.NextBlock() + + expected1 := canonical.PerBlockEmission * 30 / 100 + + t.Logf("expected1: %d", expected1) + + canonical.AssertEmulatedRewardOf(0, expected0+expected1) +} + +type TestEventCreatePool struct { + poolPath string + initialTier uint64 + initialTick int32 +} + +func (event *TestEventCreatePool) Apply(canonical *canonicalRewardState) { + canonical.CreatePool(event.poolPath, event.initialTier, event.initialTick) +} + +func (event *TestEventCreatePool) IsValid(canonical *canonicalRewardState) bool { + _, ok := canonical.global.pools.Get(event.poolPath) + return !ok +} + +func (event *TestEventCreatePool) String() string { + return ufmt.Sprintf("CreatePool(%s, %d, %d)", event.poolPath, event.initialTier, event.initialTick) +} + +type TestEventChangeTier struct { + poolPath string + targetTier uint64 +} + +func (event *TestEventChangeTier) IsValid(canonical *canonicalRewardState) bool { + _, ok := canonical.global.pools.Get(event.poolPath) + return ok +} + +func (event *TestEventChangeTier) Apply(canonical *canonicalRewardState) { + canonical.ChangePoolTier(event.poolPath, event.targetTier) +} + +func (event *TestEventChangeTier) String() string { + return ufmt.Sprintf("ChangeTier(%s, %d)", event.poolPath, event.targetTier) +} + +type TestEventStakeToken struct { + tokenId uint64 + poolPath string + address std.Address + liquidity *u256.Uint + tickLower int32 + tickUpper int32 +} + +func (event *TestEventStakeToken) Apply(canonical *canonicalRewardState) { + if canonical.global.deposits.Has(event.tokenId) { + if canonical.global.deposits.Get(event.tokenId).liquidity.IsZero() { + canonical.StakeToken(event.tokenId, event.poolPath, event.address, event.tickLower, event.tickUpper, event.liquidity) + } else { + canonical.UnstakeToken(event.tokenId) + } + } else { + canonical.StakeToken(event.tokenId, event.poolPath, event.address, event.tickLower, event.tickUpper, event.liquidity) + } +} + +func (event *TestEventStakeToken) IsValid(canonical *canonicalRewardState) bool { + _, ok := canonical.global.pools.Get(event.poolPath) + return ok +} + +func (event *TestEventStakeToken) String() string { + return ufmt.Sprintf("StakeToken(%d, %s, %s, %d, %d, %s)", event.tokenId, event.poolPath, event.address, event.tickLower, event.tickUpper, event.liquidity.ToString()) +} + +type TestEventMoveTick struct { + poolPath string + tick int32 +} + +func (event *TestEventMoveTick) Apply(canonical *canonicalRewardState) { + canonical.MoveTick(event.poolPath, event.tick) +} + +func (event *TestEventMoveTick) IsValid(canonical *canonicalRewardState) bool { + _, ok := canonical.global.pools.Get(event.poolPath) + return ok +} + +func (event *TestEventMoveTick) String() string { + return ufmt.Sprintf("MoveTick(%s, %d)", event.poolPath, event.tick) +} + +type TestEventSetEmissionUpdate struct { + emission uint64 +} + +func (event *TestEventSetEmissionUpdate) Apply(canonical *canonicalRewardState) { + canonical.SetEmissionUpdate(event.emission) +} + +func (event *TestEventSetEmissionUpdate) IsValid(canonical *canonicalRewardState) bool { + return true +} + +func (event *TestEventSetEmissionUpdate) String() string { + return ufmt.Sprintf("SetEmissionUpdate(%d)", event.emission) +} + +type SimulationEvent interface { + IsValid(canonical *canonicalRewardState) bool + Apply(canonical *canonicalRewardState) + String() string +} + +type tokenStake struct { + tokenId uint64 + address std.Address + liquidity *u256.Uint + tickLower int32 + tickUpper int32 +} + +func events( + poolPath string, + initialTier uint64, + initialTick int32, + tokenStakes []tokenStake, + moveTicks []int32, +) []SimulationEvent { + result := []SimulationEvent{ + &TestEventCreatePool{ + poolPath: poolPath, + initialTier: initialTier, + initialTick: initialTick, + }, + } + for i := 0; i < len(tokenStakes); i++ { + result = append(result, &TestEventStakeToken{ + tokenId: tokenStakes[i].tokenId, + poolPath: poolPath, + address: tokenStakes[i].address, + liquidity: tokenStakes[i].liquidity, + tickLower: tokenStakes[i].tickLower, + tickUpper: tokenStakes[i].tickUpper, + }) + } + for i := 0; i < len(moveTicks); i++ { + result = append(result, &TestEventMoveTick{ + poolPath: poolPath, + tick: moveTicks[i], + }) + } + if poolPath == "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000" { + return result + } + for i := 0; i < 4; i++ { + result = append(result, &TestEventChangeTier{ + poolPath: poolPath, + targetTier: uint64(i), + }) + } + return result +} +func TestCanonicalSimulation_0(t *testing.T) { + wugnotgns := events( + "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000", + 1, + 200, + []tokenStake{ + {0, std.Address("gno1token0"), u256.NewUint(1000000000000000000), 100, 300}, + {1, std.Address("gno1token1"), u256.NewUint(2000000000000000000), 200, 400}, + {2, std.Address("gno1token2"), u256.NewUint(3000000000000000000), 300, 500}, + }, + []int32{50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750}, + ) + + barbaz := events( + "gno.land/r/demo/bar:gno.land/r/gnoswap/v1/baz:3000", + 0, + 200, + []tokenStake{ + {3, std.Address("gno1token3"), u256.NewUint(4000000000000000000), 50, 100}, + {4, std.Address("gno1token4"), u256.NewUint(5000000000000000000), 100, 200}, + {5, std.Address("gno1token5"), u256.NewUint(6000000000000000000), 300, 500}, + }, + []int32{50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750}, + ) + + bazquux := events( + "gno.land/r/demo/baz:gno.land/r/gnoswap/v1/quux:3000", + 0, + 200, + []tokenStake{ + {6, std.Address("gno1token6"), u256.NewUint(7000000000000000000), 400, 600}, + {7, std.Address("gno1token7"), u256.NewUint(8000000000000000000), 600, 800}, + {8, std.Address("gno1token8"), u256.NewUint(9000000000000000000), 700, 900}, + }, + []int32{50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750}, + ) + + quuxgns := events( + "gno.land/r/demo/quux:gno.land/r/gnoswap/v1/gns:3000", + 0, + 200, + []tokenStake{ + {9, std.Address("gno1token9"), u256.NewUint(1000000000000000000), 50, 100}, + {10, std.Address("gno1token10"), u256.NewUint(2000000000000000000), 100, 200}, + {11, std.Address("gno1token11"), u256.NewUint(3000000000000000000), 200, 300}, + }, + []int32{50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750}, + ) + + events := []SimulationEvent{} + events = append(events, wugnotgns...) + events = append(events, barbaz...) + events = append(events, bazquux...) + events = append(events, quuxgns...) + + canonical := Setup(t) + + // required to match with the default tier 1 + gnousdc := GetPoolPath(wugnotPath, gnsPath, 3000) + canonical.CreatePool(gnousdc, 1, 200) + + eventId := 0 + + for i := 0; i < 10; i++ { + println("=======ITERATION : ", i, "========") + + canonicalRewardMap := make(map[uint64]uint64) + for j := 0; j < 100; j++ { + var event SimulationEvent + for { + eventId = (eventId + 17) % len(events) + event = events[eventId] + if event.IsValid(canonical) { + break + } + } + println("=======HEIGHT : ", std.GetHeight(), "========") + println(" ", eventId, " : ", event.String()) + event.Apply(canonical) + canonical.NextBlock() + for i := 0; i < 12; i++ { + reward, ok := canonical.SafeCanonicalRewardOf(uint64(i)) + if !ok { + continue // no reward for this deposit + } + canonicalRewardMap[uint64(i)] += reward.Internal + } + } + + println("======ASSERTING========") + for k, v := range canonicalRewardMap { + println(" ", k, " : ", v) + } + canonical.AssertEmulatedRewardMap(canonicalRewardMap) + } +} \ No newline at end of file diff --git a/staker/reward_calculation_incentives.gno b/staker/reward_calculation_incentives.gno index eb4413ca7..471150229 100644 --- a/staker/reward_calculation_incentives.gno +++ b/staker/reward_calculation_incentives.gno @@ -10,50 +10,46 @@ import ( u256 "gno.land/p/gnoswap/uint256" ) -type IncentiveBound struct { - Incentive ExternalIncentive - IsEnter bool -} - -type IncentiveRewardEntry struct { - ActiveIncentives []string - TotalStakedLiquidity *u256.Uint -} -// per-pool incentives +// Incentives is a collection of external incentives for a given pool. +// +// Fields: +// - byTime: ExternalIncentive primarily indexed by startTime +// - byHeight: ExternalIncentive primarily indexed by startHeight +// - byEndHeight: ExternalIncentive primarily indexed by endHeight +// - byCreator: ExternalIncentive primarily indexed by creator +// +// - unclaimablePeriods: +// For each unclaimable period(start, end) for this pool, +// it stores (key: start) => (value: end) +// if end is 0, it means the unclaimable period is ongoing. type Incentives struct { byTime *avl.Tree // (startTime, endTime, creator, rewardToken) => ExternalIncentive byHeight *avl.Tree // (startHeight, endHeight, creator, rewardToken) => ExternalIncentive + byEndHeight *avl.Tree // (endHeight, startHeight, creator, rewardToken) => ExternalIncentive byCreator *avl.Tree // (creator, startHeight, endHeight, rewardToken) => ExternalIncentive - incentiveBound *UintTree // blockNumber -> []IncentiveBound - - // currentIncentives []string - - rewardCache *RewardCacheTree // blockNumber -> IncentiveRewardEntry - lastRewardCacheHeight *uint64 + unclaimablePeriods *UintTree // startHeight => endHeight } -func NewIncentives(currentHeight uint64) Incentives { - return Incentives{ +// NewIncentives creates a new Incentives instance. +func NewIncentives() Incentives { + result := Incentives{ byTime: avl.NewTree(), byHeight: avl.NewTree(), + byEndHeight: avl.NewTree(), byCreator: avl.NewTree(), - - incentiveBound: NewUintTree(), - - rewardCache: NewRewardCacheTree(), - lastRewardCacheHeight: ¤tHeight, + unclaimablePeriods: NewUintTree(), } -} -func (self *Incentives) Exists(startTime, endTime int64, creator std.Address, rewardToken string) bool { - byTimeId := incentiveIdByTime(uint64(startTime), uint64(endTime), creator, rewardToken) - return self.byTime.Has(byTimeId) + // initial unclaimable period starts, as there cannot be any staked positions yet. + result.unclaimablePeriods.Set(std.GetHeight(), int64(0)) + return result } +// Get incentive by time func (self *Incentives) Get(startTime, endTime int64, creator std.Address, rewardToken string) (*ExternalIncentive, bool) { - byTimeId := incentiveIdByTime(uint64(startTime), uint64(endTime), creator, rewardToken) + byTimeId := incentiveIdByTime(startTime, endTime, creator, rewardToken) value, ok := self.byTime.Get(byTimeId) if !ok { return nil, false @@ -61,6 +57,7 @@ func (self *Incentives) Get(startTime, endTime int64, creator std.Address, rewar return value.(*ExternalIncentive), true } +// Get incentive by full incentiveId(by time) func (self *Incentives) GetByIncentiveId(incentiveId string) (*ExternalIncentive, bool) { value, ok := self.byTime.Get(incentiveId) if !ok { @@ -69,41 +66,50 @@ func (self *Incentives) GetByIncentiveId(incentiveId string) (*ExternalIncentive return value.(*ExternalIncentive), true } -// MUST be called after std.GetHeight() > endHeight -func (self *Incentives) remove(incentive *ExternalIncentive) { - byTimeId := incentiveIdByTime(uint64(incentive.startTimestamp), uint64(incentive.endTimestamp), incentive.refundee, incentive.rewardToken) - self.byTime.Remove(byTimeId) - - byHeightId, byCreatorId := incentiveIdByHeight(uint64(incentive.startHeight), uint64(incentive.endHeight), incentive.refundee, incentive.rewardToken) - self.byHeight.Remove(byHeightId) - self.byCreator.Remove(byCreatorId) -} - -func (self *Incentives) GetBound(height uint64) []IncentiveBound { - value, ok := self.incentiveBound.Get(height) - if !ok { - return []IncentiveBound{} - } - return value.([]IncentiveBound) -} +// Get all incentives that is active in given [startHeight, endHeight) +func (self *Incentives) GetAllInHeights(startHeight, endHeight int64) map[string]*ExternalIncentive { + incentives := make(map[string]*ExternalIncentive) + // Iterate all incentives that has start height less than endHeight + self.byHeight.ReverseIterate( + "", + EncodeUint(uint64(endHeight)), + func(key string, value interface{}) bool { + incentive := value.(*ExternalIncentive) + if incentive.endHeight < startHeight { + // incentive is already ended + return false + } + + incentives[incentive.incentiveId] = incentive + return false + }, + ) + // Iterate all incentives that has end height greater than startHeight + self.byEndHeight.Iterate( + EncodeUint(uint64(startHeight)), + "", + func(key string, value interface{}) bool { + incentive := value.(*ExternalIncentive) + if incentive.startHeight > endHeight { + // incentive is not started yet + return false + } -// cacheReward() MUST be called before this function -func (self *Incentives) CurrentReward(currentHeight uint64) IncentiveRewardEntry { - value := self.rewardCache.CurrentReward(currentHeight) - if value == nil { - return IncentiveRewardEntry{ - ActiveIncentives: []string{}, - TotalStakedLiquidity: u256.Zero(), - } - } - return value.(IncentiveRewardEntry) + incentives[incentive.incentiveId] = incentive + return false + }, + ) + return incentives } +// Create a new external incentive +// Panics if the incentive already exists. +// Sets to byTime, byHeight, byEndHeight, byCreator func (self *Incentives) create( creator std.Address, incentive *ExternalIncentive, ) { - byTimeId := incentiveIdByTime(uint64(incentive.startTimestamp), uint64(incentive.endTimestamp), creator, incentive.rewardToken) + byTimeId := incentiveIdByTime(incentive.startTimestamp, incentive.endTimestamp, creator, incentive.rewardToken) if self.byTime.Has(byTimeId) { panic(addDetailToError( errIncentiveAlreadyExists, @@ -111,72 +117,78 @@ func (self *Incentives) create( )) } - byHeightId, byCreatorId := incentiveIdByHeight(uint64(incentive.startHeight), uint64(incentive.endHeight), creator, incentive.rewardToken) + byHeightId, byCreatorId := incentiveIdByHeight(incentive.startHeight, incentive.endHeight, creator, incentive.rewardToken) + byEndHeightId, _ := incentiveIdByHeight(incentive.endHeight, incentive.startHeight, creator, incentive.rewardToken) self.byTime.Set(byTimeId, incentive) self.byHeight.Set(byHeightId, incentive) + self.byEndHeight.Set(byEndHeightId, incentive) self.byCreator.Set(byCreatorId, incentive) +} - startIncentiveBound := self.GetBound(uint64(incentive.startHeight)) - startIncentiveBound = append(startIncentiveBound, IncentiveBound{ - Incentive: *incentive, - IsEnter: true, - }) - self.incentiveBound.Set(uint64(incentive.startHeight), startIncentiveBound) +// starts incentive unclaimable period for this pool +func (self *Incentives) startUnclaimablePeriod(startHeight int64) { + self.unclaimablePeriods.Set(startHeight, int64(0)) +} - endHeight := uint64(incentive.endHeight) - endIncentiveBound := self.GetBound(endHeight) - endIncentiveBound = append(endIncentiveBound, IncentiveBound{ - Incentive: *incentive, - IsEnter: false, +// ends incentive unclaimable period for this pool +// ignores if currently not in unclaimable period +func (self *Incentives) endUnclaimablePeriod(endHeight int64) { + startHeight := int64(0) + self.unclaimablePeriods.ReverseIterate(0, endHeight, func(key int64, value interface{}) bool { + if value.(int64) != 0 { + // Already ended, no need to update + // keeping startHeight as 0 to indicate this + return true + } + startHeight = key + return true }) - self.incentiveBound.Set(endHeight, endIncentiveBound) + + if startHeight == 0 { + // No ongoing unclaimable period found + return + } + + if startHeight == endHeight { + self.unclaimablePeriods.Remove(startHeight) + } else { + self.unclaimablePeriods.Set(startHeight, endHeight) + } } -// endHeight MUST be less than or equal to the current block height -func (self *Incentives) cacheRewardPerLiquidityUnit(startHeight, endHeight uint64, stakedLiquidity *u256.Uint) { - currentReward := self.CurrentReward(startHeight) +// calculate unclaimable reward by checking unclaimable periods +func (self *Incentives) calculateUnclaimableReward(incentiveId string) uint64 { + incentive, ok := self.GetByIncentiveId(incentiveId) + if !ok { + return 0 + } + + blocks := int64(0) + - self.incentiveBound.Iterate(startHeight, endHeight, func(key uint64, value interface{}) bool { - bound := value.([]IncentiveBound) - reward := IncentiveRewardEntry{ - ActiveIncentives: make([]string, len(currentReward.ActiveIncentives)), - TotalStakedLiquidity: currentReward.TotalStakedLiquidity.Clone(), + self.unclaimablePeriods.ReverseIterate(0, incentive.startHeight, func(key int64, value interface{}) bool { + endHeight := value.(int64) + if endHeight == 0 { + endHeight = incentive.endHeight } - for i, incentiveId := range currentReward.ActiveIncentives { - reward.ActiveIncentives[i] = incentiveId + if endHeight <= incentive.startHeight { + return true } - for _, bound := range bound { - if bound.IsEnter { - reward.ActiveIncentives = append(reward.ActiveIncentives, bound.Incentive.incentiveId) - } else { - for i, incentiveId := range reward.ActiveIncentives { - if incentiveId == bound.Incentive.incentiveId { - reward.ActiveIncentives = append(reward.ActiveIncentives[:i], reward.ActiveIncentives[i+1:]...) - break - } - } - } + blocks += endHeight - incentive.startHeight + return true + }) + + self.unclaimablePeriods.Iterate(incentive.startHeight, incentive.endHeight, func(key int64, value interface{}) bool { + startHeight := key + endHeight := value.(int64) + if endHeight == 0 { + endHeight = incentive.endHeight } - self.rewardCache.Set(key, reward) - currentReward = reward + blocks += endHeight - startHeight return false }) - *self.lastRewardCacheHeight = endHeight -} - -func (self *Incentives) updateRewardByLiquidityChange(currentHeight uint64, liquidity *u256.Uint) { - currentReward := self.CurrentReward(currentHeight) - - entry := IncentiveRewardEntry{ - ActiveIncentives: make([]string, len(currentReward.ActiveIncentives)), - TotalStakedLiquidity: liquidity.Clone(), - } - - for i, incentiveId := range currentReward.ActiveIncentives { - entry.ActiveIncentives[i] = incentiveId - } - self.rewardCache.Set(currentHeight, entry) + return uint64(blocks) * incentive.rewardPerBlock } diff --git a/staker/reward_calculation_pool.gno b/staker/reward_calculation_pool.gno index a3ea20496..f3c6ee47c 100644 --- a/staker/reward_calculation_pool.gno +++ b/staker/reward_calculation_pool.gno @@ -10,6 +10,8 @@ import ( "gno.land/r/gnoswap/v1/consts" en "gno.land/r/gnoswap/v1/emission" + + pl "gno.land/r/gnoswap/v1/pool" ) var ( @@ -26,6 +28,7 @@ func init() { pools = NewPools() } +// Pools represents the global pool storage type Pools struct { tree *avl.Tree // string poolPath -> pool } @@ -36,6 +39,7 @@ func NewPools() *Pools { } } +// Get returns the pool for the given poolPath func (self *Pools) Get(poolPath string) (*Pool, bool) { v, ok := self.tree.Get(poolPath) if !ok { @@ -44,502 +48,431 @@ func (self *Pools) Get(poolPath string) (*Pool, bool) { return v.(*Pool), true } +// GetOrCreate returns the pool for the given poolPath, or creates a new pool if it does not exist func (self *Pools) GetOrCreate(poolPath string) *Pool { pool, ok := self.Get(poolPath) if !ok { - pool = NewPool(poolPath, uint64(std.GetHeight())) + pool = NewPool(poolPath, std.GetHeight()) self.Set(poolPath, pool) } return pool } +// Set sets the pool for the given poolPath func (self *Pools) Set(poolPath string, pool *Pool) { self.tree.Set(poolPath, pool) } +// Has returns true if the pool exists for the given poolPath func (self *Pools) Has(poolPath string) bool { return self.tree.Has(poolPath) } +func (self *Pools) IterateAll(fn func(key string, pool *Pool) bool) { + self.tree.Iterate("", "", func(key string, value interface{}) bool { + return fn(key, value.(*Pool)) + }) +} + +// Pool is a struct for storing an incentivized pool information +// Each pool stores Incentives and Ticks associated with it. +// +// Fields: +// - poolPath: The path of the pool. +// +// - currentStakedLiquidity: +// The current total staked liquidity of the in-range positions for the pool. +// Updated when tick cross happens or stake/unstake happens. +// Used to calculate the global reward ratio accumulation or +// decide whether to enter/exit unclaimable period. +// +// - lastUnclaimableHeight: +// The height at which the unclaimable period started. +// Set to 0 when the pool is not in an unclaimable period. +// +// - unclaimableAcc: +// The accumulated undisributed unclaimable reward. +// Reset to 0 when processUnclaimableReward is called and sent to community pool. +// +// - rewardCache: +// The cached per-block reward emitted for this pool. +// Stores new entry only when the reward is changed. +// PoolTier.cacheReward() updates this. +// +// - incentives: The external incentives associated with the pool. +// +// - ticks: The Ticks associated with the pool. +// +// - globalRewardRatioAccumulation: +// Global ratio of BlockNumber / TotalStake accumulation(since the pool creation) +// Stores new entry only when tick cross or stake/unstake happens. +// It is used to calculate the reward for a staked position at certain height. +// +// - historicalTick: +// The historical tick for the pool at a given height. +// It does not reflect the exact tick at the blockNumber, +// but it provides correct ordering for the staked position's ticks. +// Therefore, you should not compare it for equality, only for ordering. +// Set when tick cross happens or a new position is created. type Pool struct { poolPath string - // 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 - unclaimableAcc *uint64 + stakedLiquidity *UintTree // uint64 blockNumber -> *u256.Uint(Q128) - tierRewardTotal *uint64 // current total internal reward per block, used for unclaimable reward calculation + lastUnclaimableHeight int64 + unclaimableAcc uint64 - rewardCache *RewardCacheTree // blockNumber -> *u256.Uint - lastRewardCacheHeight *uint64 + rewardCache *UintTree // uint64 blockNumber -> uint64 gnsReward incentives Incentives - ticks *Ticks -} + ticks Ticks // int32 tickId -> Tick tick + + globalRewardRatioAccumulation *UintTree // uint64 blockNumber -> *u256.Uint(Q128) rewardRatioAccumulation -func NewPool(poolPath string, currentHeight uint64) *Pool { - unclaimableAcc := uint64(0) - tierRewardTotal := uint64(0) + historicalTick *UintTree // uint64 blockNumber -> int32 tickId +} - return &Pool{ +// NewPool creates a new pool with the given poolPath and currentHeight. +func NewPool(poolPath string, currentHeight int64) *Pool { + pool := &Pool{ poolPath: poolPath, stakedLiquidity: NewUintTree(), - lastUnclaimableHeight: ¤tHeight, - unclaimableAcc: &unclaimableAcc, - tierRewardTotal: &tierRewardTotal, - rewardCache: NewRewardCacheTree(), - lastRewardCacheHeight: ¤tHeight, - incentives: NewIncentives(currentHeight), + lastUnclaimableHeight: currentHeight, + unclaimableAcc: 0, + rewardCache: NewUintTree(), + incentives: NewIncentives(), ticks: NewTicks(), + globalRewardRatioAccumulation: NewUintTree(), + historicalTick: NewUintTree(), } + + pool.globalRewardRatioAccumulation.Set(currentHeight, u256.Zero()) + pool.rewardCache.Set(currentHeight, uint64(0)) + pool.stakedLiquidity.Set(currentHeight, u256.Zero()) + + return pool } -// Returns the latest staked liquidity at the height equal or before the current height -func (self *Pool) CurrentStakedLiquidity(currentHeight uint64) *u256.Uint { - stakedLiquidity := u256.Zero() - self.stakedLiquidity.ReverseIterate(0, currentHeight, func(key uint64, value interface{}) bool { - stakedLiquidity = value.(*u256.Uint) - println("[", currentHeight, "] >>>>>>>>>>>> stakedLiquidity : ", stakedLiquidity.ToString(), ", key : ", key) +// Get the latest global reward ratio accumulation in [0, currentHeight] range. +// Returns the height and the accumulation. +func (self *Pool) CurrentGlobalRewardRatioAccumulation(currentHeight int64) (int64, *u256.Uint) { + var height int64 + var acc *u256.Uint + self.globalRewardRatioAccumulation.ReverseIterate(0, currentHeight, func(key int64, value interface{}) bool { + height = key + acc = value.(*u256.Uint) return true }) - return stakedLiquidity -} - -func (self *Pool) CurrentReward(currentHeight uint64) *u256.Uint { - reward := self.rewardCache.CurrentReward(currentHeight) - if reward == nil { - return u256.Zero() + if acc == nil { + panic("should not happen, globalRewardRatioAccumulation must be set when pool is created") } - return reward.(*u256.Uint) + return height, acc } -// cacheReward() MUST be called before this function -func (self *Pool) IsExternallyIncentivizedPool(currentHeight uint64) bool { - return self.incentives.byTime.Size() != 0 +// Get the latest tick in [0, currentHeight] range. +// Returns the tick. +func (self *Pool) CurrentTick(currentHeight int64) int32 { + var tick int32 + self.historicalTick.ReverseIterate(0, currentHeight, func(key int64, value interface{}) bool { + tick = value.(int32) + return true + }) + return tick } -func (self *Pool) updateRewardByLiquidityChange(currentTierReward uint64, currentHeight uint64, liquidity *u256.Uint) { - ratio := u256.NewUint(currentTierReward) - ratio = u256.Zero().Mul(ratio, q192) - ratio = u256.Zero().Div(ratio, liquidity) - self.rewardCache.Set(currentHeight, ratio) +func (self *Pool) CurrentStakedLiquidity(currentHeight int64) *u256.Uint { + var liquidity *u256.Uint + self.stakedLiquidity.ReverseIterate(0, currentHeight, func(key int64, value interface{}) bool { + liquidity = value.(*u256.Uint) + return true + }) + return liquidity +} - self.incentives.updateRewardByLiquidityChange(currentHeight, liquidity) +// IsExternallyIncentivizedPool returns true if the pool has any external incentives. +func (self *Pool) IsExternallyIncentivizedPool() bool { + return self.incentives.byTime.Size() != 0 } -func (self *Pool) cacheRewardPerLiquidityUnit(startHeight, endHeight uint64, currentTierReward uint64) { - println("\t\t\t->cacheRewardPerLiquidityUnit Start : [", startHeight, "], [", endHeight, "], (", currentTierReward, "), lastUnclaimableHeight ", *self.lastUnclaimableHeight) - // Unclaimable reward calculation uses tierRewardTotal, so we need to update it - if *self.lastUnclaimableHeight != 0 { - self.endInternalUnclaimablePeriod(startHeight) - *self.tierRewardTotal = currentTierReward - self.startInternalUnclaimablePeriod(startHeight) - } +// Get the latest reward in [0, currentHeight] range. +// Returns the reward. +func (self *Pool) CurrentReward(currentHeight int64) uint64 { + var reward uint64 + self.rewardCache.ReverseIterate(0, currentHeight, func(key int64, value interface{}) bool { + reward = value.(uint64) + return true + }) + return reward +} - if currentTierReward == 0 { - self.rewardCache.Set(startHeight, u256.Zero()) +// cacheReward sets the current reward for the pool +// If the pool is in unclaimable period, it will end the unclaimable period, updates the reward, and start the unclaimable period again. +func (self *Pool) cacheReward(currentHeight int64, currentTierReward uint64) { + oldTierReward := self.CurrentReward(currentHeight) + if oldTierReward == currentTierReward { return } - stakedLiquidity := self.CurrentStakedLiquidity(startHeight) - - self.updateRewardByLiquidityChange(currentTierReward, startHeight, stakedLiquidity) + isInUnclaimable := self.CurrentStakedLiquidity(currentHeight).IsZero() + if isInUnclaimable { + self.endUnclaimablePeriod(currentHeight) + } - self.stakedLiquidity.Iterate(startHeight, endHeight, func(height uint64, value interface{}) bool { - stakedLiquidity := value.(*u256.Uint) - self.updateRewardByLiquidityChange(currentTierReward, height, stakedLiquidity) + self.rewardCache.Set(currentHeight, currentTierReward) - return false - }) - self.stakedLiquidity.Iterate(startHeight, endHeight, func(height uint64, value interface{}) bool { - stakedLiquidity := value.(*u256.Uint) - println("[", startHeight, "], [", endHeight, "], key : ", height, ", value : ", stakedLiquidity.ToString()) - return false - }) - println("\t\t\t->cacheRewardPerLiquidityUnit End : [", startHeight, "], [", endHeight, "], (", currentTierReward, "), lastUnclaimableHeight ", *self.lastUnclaimableHeight) + if isInUnclaimable { + self.startUnclaimablePeriod(currentHeight) + } } -func (self *Pool) cacheInternalReward(currentHeight uint64, emissionUpdate en.EmissionUpdate, rewardDenominator uint64) { - println("\t[", currentHeight, "] cacheInternalReward Strat ") - currentEmission := emissionUpdate.LastEmissionUpdate - println("\t\tcurrentEmission to Staker per block: ", currentEmission) - - startHeight := *self.lastRewardCacheHeight - println("\t\tstartHeight : ", startHeight) - - for i, height := range emissionUpdate.EmissionUpdateHeights { - emission := emissionUpdate.EmissionUpdates[i] - println("\t\theight : ", height, ", emission : ", emission) - - self.cacheRewardPerLiquidityUnit(startHeight, height, ApplyDenominator(currentEmission, rewardDenominator)) - startHeight = height +// cacheInternalReward caches the current emission and updates the global reward ratio accumulation. +func (self *Pool) cacheInternalReward(currentHeight int64, currentEmission uint64) { + self.cacheReward(currentHeight, currentEmission) - currentEmission = emission - } - - self.cacheRewardPerLiquidityUnit(startHeight, currentHeight, ApplyDenominator(currentEmission, rewardDenominator)) - if *self.lastUnclaimableHeight != 0 && self.CurrentStakedLiquidity(currentHeight).IsZero() { - self.endInternalUnclaimablePeriod(currentHeight) - self.startInternalUnclaimablePeriod(currentHeight) + currentStakedLiquidity := self.CurrentStakedLiquidity(currentHeight) + if currentStakedLiquidity.IsZero() { + self.endUnclaimablePeriod(currentHeight) + self.startUnclaimablePeriod(currentHeight) } - *self.lastRewardCacheHeight = currentHeight - println("\t\tlastRewardCacheHeight : ", *self.lastRewardCacheHeight) - println("\t[", currentHeight, "] cacheInternalReward End ") + self.updateGlobalRewardRatioAccumulation(currentHeight, currentStakedLiquidity) } -func (self *Pool) cacheExternalReward(endHeight uint64) { - self.stakedLiquidity.Iterate(0, 9999999, func(key uint64, value interface{}) bool { - return false - }) +func (self *Pool) calculateGlobalRewardRatioAccumulation(currentHeight int64, currentStakedLiquidity *u256.Uint) *u256.Uint { + oldAccHeight, oldAcc := self.CurrentGlobalRewardRatioAccumulation(currentHeight) + blockDiff := currentHeight - oldAccHeight + if blockDiff == 0 { + return oldAcc.Clone() + } - startHeight := *self.incentives.lastRewardCacheHeight - currentStakedLiquidity := self.CurrentStakedLiquidity(startHeight) + acc := u256.NewUint(uint64(blockDiff)) + acc = acc.Mul(acc, q128) + acc = acc.Div(acc, currentStakedLiquidity) - self.stakedLiquidity.Iterate(startHeight, endHeight, func(height uint64, value interface{}) bool { - self.incentives.cacheRewardPerLiquidityUnit(startHeight, height, currentStakedLiquidity) - currentStakedLiquidity = value.(*u256.Uint) - startHeight = height - return false - }) + return u256.Zero().Add(oldAcc, acc) +} - self.incentives.cacheRewardPerLiquidityUnit(startHeight, endHeight, currentStakedLiquidity) +// updateGlobalRewardRatioAccumulation updates the global reward ratio accumulation and returns the new accumulation. +func (self *Pool) updateGlobalRewardRatioAccumulation(currentHeight int64, currentStakedLiquidity *u256.Uint) *u256.Uint { + newAcc := self.calculateGlobalRewardRatioAccumulation(currentHeight, currentStakedLiquidity) - self.incentives.rewardCache.Iterate(0, 9999999, func(key uint64, value interface{}) bool { - return false - }) + self.globalRewardRatioAccumulation.Set(currentHeight, newAcc) + return newAcc } -type ExternalRewardState struct { - pool *Pool +// RewardState is a struct for storing the intermediate state for reward calculation. +type RewardState struct { + pool *Pool deposit *Deposit - currentWarmup Warmup - rewards []map[string]*u256.Uint - penalties []map[string]*u256.Uint + + // accumulated rewards for each warmup + rewards []uint64 + penalties []uint64 } -func (self *Pool) ExternalRewardOf(deposit *Deposit) *ExternalRewardState { - result := &ExternalRewardState{ - pool: self, +// RewardStateOf initializes a new RewardState for the given deposit. +func (self *Pool) RewardStateOf(deposit *Deposit) *RewardState { + result := &RewardState{ + pool: self, deposit: deposit, - currentWarmup: deposit.warmups[0], - rewards: make([]map[string]*u256.Uint, len(deposit.warmups)), - penalties: make([]map[string]*u256.Uint, len(deposit.warmups)), + rewards: make([]uint64, len(deposit.warmups)), + penalties: make([]uint64, len(deposit.warmups)), } for i := range result.rewards { - result.rewards[i] = make(map[string]*u256.Uint) - result.penalties[i] = make(map[string]*u256.Uint) + result.rewards[i] = 0 + result.penalties[i] = 0 } return result } -func (self *ExternalRewardState) Calculate(startHeight, endHeight int64, currentlyInRange bool, tickUpperCrosses []int64, tickLowerCrosses []int64) ([]map[string]uint64, []map[string]uint64) { - if !self.pool.IsExternallyIncentivizedPool(uint64(startHeight)) { - return nil, nil - } - - self.TickCrossesToExternalReward(startHeight, endHeight, currentlyInRange, tickUpperCrosses, tickLowerCrosses) - - rewards := make([]map[string]uint64, len(self.deposit.warmups)) - penalties := make([]map[string]uint64, len(self.deposit.warmups)) - - for i := range self.rewards { - rewards[i] = make(map[string]uint64) - penalties[i] = make(map[string]uint64) - for incentiveId, reward := range self.rewards[i] { - rewards[i][incentiveId] = reward.Uint64() - } - for incentiveId, penalty := range self.penalties[i] { - penalties[i][incentiveId] = penalty.Uint64() - } - } - - return rewards, penalties -} - -func (self *ExternalRewardState) AccumulateReward(startHeight, endHeight uint64) { - self.pool.incentives.rewardCache.RewardPerInterval(startHeight, endHeight, func(blockNumber uint64, poolRewardI interface{}) { - incentiveRewardEntry := poolRewardI.(IncentiveRewardEntry) - for _, incentiveId := range incentiveRewardEntry.ActiveIncentives { - incentive, ok := self.pool.incentives.GetByIncentiveId(incentiveId) - if !ok { - panic("incentive not found") - } - rewardRatio := u256.NewUint(incentive.rewardPerBlock) - rewardRatio.Mul(rewardRatio, q192) - rewardRatio.Div(rewardRatio, incentiveRewardEntry.TotalStakedLiquidity) - positionReward := u256.Zero().Mul(self.deposit.liquidity, rewardRatio) - positionReward = u256.Zero().Mul(positionReward, u256.NewUint(blockNumber)) - acc, ok := self.rewards[self.currentWarmup.Index][incentiveId] - if !ok { - acc = u256.Zero() - } - acc.Add(acc, positionReward) - self.rewards[self.currentWarmup.Index][incentiveId] = acc - } +// CalculateInternalReward calculates the internal reward for the deposit. +// It calls RewardPerWarmup for each rewardCache interval, applies warmup, and returns the rewards and penalties. +func (self *RewardState) CalculateInternalReward(startHeight, endHeight int64) ([]uint64, []uint64) { + currentReward := self.pool.CurrentReward(startHeight) + self.pool.rewardCache.Iterate(startHeight, endHeight, func(key int64, value interface{}) bool { + // we calculate per-position reward + self.RewardPerWarmup(startHeight, int64(key), currentReward) + currentReward = value.(uint64) + startHeight = int64(key) + return false }) -} - -func (self *ExternalRewardState) ApplyWarmup() { - for i, warmup := range self.deposit.warmups { - for incentiveId, reward := range self.rewards[i] { - if reward.IsZero() { - continue - } - warmupReward := u256.Zero() - warmupReward = warmupReward.Mul(reward, u256.NewUint(warmup.WarmupRatio)) - warmupReward = warmupReward.Div(warmupReward, u256.NewUint(100)) - - warmupPenalty := u256.Zero().Sub(reward, warmupReward) - - warmupReward = warmupReward.Div(warmupReward, q192) - warmupPenalty = warmupPenalty.Div(warmupPenalty, q192) - - self.rewards[i][incentiveId] = warmupReward - self.penalties[i][incentiveId] = warmupPenalty - } - } -} - -func (self *ExternalRewardState) TickCrossesToExternalReward(startHeight, endHeight int64, currentlyInRange bool, tickUpperCrosses []int64, tickLowerCrosses []int64) { - for _, warmup := range self.deposit.warmups { - self.currentWarmup = warmup - - if startHeight >= warmup.NextWarmupHeight { - // passed the warmup - continue - } - - if endHeight < warmup.NextWarmupHeight { - // fully submerged in the current warmup - currentlyInRange, tickUpperCrosses, tickLowerCrosses = ForEachEligibleInterval( - startHeight, - endHeight, - currentlyInRange, - tickUpperCrosses, - tickLowerCrosses, - self.AccumulateReward, - ) - - // done - break - } - - // partially included in the current warmup - currentlyInRange, tickUpperCrosses, tickLowerCrosses = ForEachEligibleInterval( - startHeight, - warmup.NextWarmupHeight, - currentlyInRange, - tickUpperCrosses, - tickLowerCrosses, - self.AccumulateReward, - ) - startHeight = warmup.NextWarmupHeight + if startHeight < endHeight { + self.RewardPerWarmup(startHeight, endHeight, currentReward) } self.ApplyWarmup() -} -type InternalRewardState struct { - pool *Pool - deposit *Deposit - currentWarmup Warmup - rewards []*u256.Uint - penalties []*u256.Uint + return self.rewards, self.penalties } -func (self *Pool) InternalRewardOf(deposit *Deposit) *InternalRewardState { - result := &InternalRewardState{ - pool: self, - deposit: deposit, - currentWarmup: deposit.warmups[0], - rewards: make([]*u256.Uint, len(deposit.warmups)), - penalties: make([]*u256.Uint, len(deposit.warmups)), +// CalculateExternalReward calculates the external reward for the deposit. +// It calls RewardPerWarmup for startHeight to endHeight(clamped to the incentive period), applies warmup and returns the rewards and penalties. +func (self *RewardState) CalculateExternalReward(startHeight, endHeight int64, incentive *ExternalIncentive) ([]uint64, []uint64) { + if startHeight < int64(self.deposit.lastCollectHeight) { + // This must not happen, but adding some guards just in case. + startHeight = int64(self.deposit.lastCollectHeight) } - for i := range result.rewards { - result.rewards[i] = u256.Zero() - result.penalties[i] = u256.Zero() + if endHeight < incentive.startHeight { + return nil, nil // Not started yet } - return result -} - -func (self *InternalRewardState) Calculate(startHeight, endHeight int64, currentlyInRange bool, tickUpperCrosses []int64, tickLowerCrosses []int64) ([]uint64, []uint64) { - self.TickCrossesToInternalReward(startHeight, endHeight, currentlyInRange, tickUpperCrosses, tickLowerCrosses) + if startHeight < incentive.startHeight { + startHeight = incentive.startHeight + } - rewards := make([]uint64, len(self.deposit.warmups)) - penalties := make([]uint64, len(self.deposit.warmups)) + if endHeight > incentive.endHeight { + endHeight = incentive.endHeight + } - for i, reward := range self.rewards { - rewards[i] = reward.Uint64() - penalties[i] = self.penalties[i].Uint64() + if startHeight > incentive.endHeight { + return nil, nil // Already ended } - return rewards, penalties -} + rewardPerBlock := incentive.rewardPerBlock -func (self *InternalRewardState) AccumulateReward(startHeight, endHeight uint64) { - self.pool.rewardCache.RewardPerInterval(startHeight, endHeight, func(blockNumber uint64, poolRewardI interface{}) { - poolRewardRatio := poolRewardI.(*u256.Uint) - positionReward := u256.Zero().Mul(self.deposit.liquidity, poolRewardRatio) - positionReward = u256.Zero().Mul(positionReward, u256.NewUint(blockNumber)) + self.RewardPerWarmup(startHeight, endHeight, rewardPerBlock) - acc := self.rewards[self.currentWarmup.Index] - acc.Add(acc, positionReward) - self.rewards[self.currentWarmup.Index] = acc - }) + self.ApplyWarmup() + + return self.rewards, self.penalties } -func (self *InternalRewardState) ApplyWarmup() { +// ApplyWarmup applies the warmup to the rewards and calculate penalties. +func (self *RewardState) ApplyWarmup() { for i, warmup := range self.deposit.warmups { - if self.rewards[i].IsZero() { - continue - } - reward := u256.Zero() - totalReward := u256.Zero().Div(self.rewards[i], q192) - - reward = reward.Mul(self.rewards[i], u256.NewUint(warmup.WarmupRatio)) - reward = reward.Div(reward, u256.NewUint(100)) - reward = reward.Div(reward, q192) - - penalty := u256.Zero().Sub(totalReward, reward) - - self.rewards[i] = reward - self.penalties[i] = penalty + refactorReward := self.rewards[i] + self.rewards[i] = refactorReward * warmup.WarmupRatio / 100 + self.penalties[i] = refactorReward - self.rewards[i] } } -func (self *InternalRewardState) TickCrossesToInternalReward(startHeight, endHeight int64, currentlyInRange bool, tickUpperCrosses []int64, tickLowerCrosses []int64) { - - for _, warmup := range self.deposit.warmups { - self.currentWarmup = warmup - +// RewardPerWarmup calculates the reward for each warmup, adds to the RewardState's rewards array. +func (self *RewardState) RewardPerWarmup(startHeight, endHeight int64, rewardPerBlock uint64) { + for i, warmup := range self.deposit.warmups { if startHeight >= warmup.NextWarmupHeight { // passed the warmup continue } if endHeight < warmup.NextWarmupHeight { - // fully submerged in the current warmup - currentlyInRange, tickUpperCrosses, tickLowerCrosses = ForEachEligibleInterval( - startHeight, - endHeight, - currentlyInRange, - tickUpperCrosses, - tickLowerCrosses, - self.AccumulateReward, - ) + rewardAcc := self.pool.CalculateRewardForPosition(startHeight, self.pool.CurrentTick(startHeight), endHeight, self.pool.CurrentTick(endHeight), self.deposit) + + rewardAcc = rewardAcc.Mul(rewardAcc, self.deposit.liquidity) + rewardAcc = rewardAcc.Mul(rewardAcc, u256.NewUint(rewardPerBlock)) + rewardAcc = rewardAcc.Div(rewardAcc, q128) + self.rewards[i] += rewardAcc.Uint64() // done break } - // partially included in the current warmup - currentlyInRange, tickUpperCrosses, tickLowerCrosses = ForEachEligibleInterval( - startHeight, - warmup.NextWarmupHeight, - currentlyInRange, - tickUpperCrosses, - tickLowerCrosses, - self.AccumulateReward, - ) + rewardAcc := self.pool.CalculateRewardForPosition(startHeight, self.pool.CurrentTick(startHeight), warmup.NextWarmupHeight, self.pool.CurrentTick(warmup.NextWarmupHeight), self.deposit) + + rewardAcc = rewardAcc.Mul(rewardAcc, self.deposit.liquidity) + rewardAcc = rewardAcc.Mul(rewardAcc, u256.NewUint(rewardPerBlock)) + rewardAcc = rewardAcc.Div(rewardAcc, q128) + self.rewards[i] += rewardAcc.Uint64() + startHeight = warmup.NextWarmupHeight } - - self.ApplyWarmup() } -func (self *Pool) modifyDeposit(tokenId uint64, delta *i256.Int, currentHeight uint64) { +// modifyDeposit updates the pool's staked liquidity and returns the new staked liquidity. +// updates when there is a change in the staked liquidity(tick cross, stake, unstake) +func (self *Pool) modifyDeposit(delta *i256.Int, currentHeight int64, nextTick int32) *u256.Uint { // update staker side pool info lastStakedLiquidity := self.CurrentStakedLiquidity(currentHeight) deltaApplied := liquidityMathAddDelta(lastStakedLiquidity, delta) + result := self.updateGlobalRewardRatioAccumulation(currentHeight, lastStakedLiquidity) + + // historical tick does NOT actually reflect the tick at the blockNumber, but it provides correct ordering for the staked positions + // because TickCrossHook is assured to be called for the staked-initialized ticks + self.historicalTick.Set(currentHeight, nextTick) + + switch deltaApplied.Sign() { + case -1: + panic("stakedLiquidity is less than 0, should not happen") + case 0: + if lastStakedLiquidity.Sign() == 1 { + // StakedLiquidity moved from positive to zero, start unclaimable period + self.startUnclaimablePeriod(currentHeight) + self.incentives.startUnclaimablePeriod(currentHeight) + } + case 1: + if lastStakedLiquidity.Sign() == 0 { + // StakedLiquidity moved from zero to positive, end unclaimable period + self.endUnclaimablePeriod(currentHeight) + self.incentives.endUnclaimablePeriod(currentHeight) + } + } + self.stakedLiquidity.Set(currentHeight, deltaApplied) - self.updateRewardByLiquidityChange(*self.tierRewardTotal, currentHeight, deltaApplied) + + return result } -func (self *Pool) startInternalUnclaimablePeriod(currentHeight uint64) { - println("\t\t\t[", currentHeight, "] startInternalUnclaimablePeriod Start ") - if *self.lastUnclaimableHeight == 0 { +// startUnclaimablePeriod starts the unclaimable period. +func (self *Pool) startUnclaimablePeriod(currentHeight int64) { + if self.lastUnclaimableHeight == 0 { // We set only if it's the first time entering(0 indicates not set yet) - // PoolTier can set lastUnclaimable other than tickCrossHook when - // cacheInternalReward() updates currentTierReward - *self.lastUnclaimableHeight = currentHeight - println("\t\t\tChange lastUnclaimableHeight : ", *self.lastUnclaimableHeight) + self.lastUnclaimableHeight = currentHeight } - println("\t\t\t[", currentHeight, "] startInternalUnclaimablePeriod End ") } -func (self *Pool) endInternalUnclaimablePeriod(currentHeight uint64) { - println("\t\t\t[", currentHeight, "] endInternalUnclaimablePeriod Start ") - if *self.lastUnclaimableHeight == 0 { +// endUnclaimablePeriod ends the unclaimable period. +// Accumulates to unclaimableAcc and resets lastUnclaimableHeight to 0. +func (self *Pool) endUnclaimablePeriod(currentHeight int64) { + if self.lastUnclaimableHeight == 0 { // This should not happen, but guarding just in case return } - println("\t\t\tcurrentHeight : ", currentHeight, ", lastUnclaimableHeight : ", *self.lastUnclaimableHeight) - unclaimableHeights := currentHeight - *self.lastUnclaimableHeight - *self.lastUnclaimableHeight = 0 - *self.unclaimableAcc += unclaimableHeights * *self.tierRewardTotal - println("\t\t\tunclaimableAcc : ", *self.unclaimableAcc, ", unclaimableHeights : ", unclaimableHeights, ", tierRewardTotal : ", *self.tierRewardTotal) - println("\t\t\t[", currentHeight, "] endInternalUnclaimablePeriod End ") -} - -func (self *Pool) UnclaimableExternalReward(incentiveId string, startHeight, endHeight uint64) uint64 { - incentive, ok := self.incentives.GetByIncentiveId(incentiveId) - if !ok { - return 0 + unclaimableHeights := currentHeight - self.lastUnclaimableHeight + self.unclaimableAcc += uint64(unclaimableHeights) * self.CurrentReward(self.lastUnclaimableHeight) + self.lastUnclaimableHeight = 0 +} + +// processUnclaimableReward processes the unclaimable reward and returns the accumulated reward. +// It resets unclaimableAcc to 0 and updates lastUnclaimableHeight to endHeight. +func (self *Pool) processUnclaimableReward(poolTier *PoolTier, endHeight int64) uint64 { + internalUnClaimable := self.unclaimableAcc + self.unclaimableAcc = 0 + self.lastUnclaimableHeight = endHeight + return internalUnClaimable +} + +// Calculates reward for a position *without* considering debt or warmup +// It calculates the theoretical total reward for the position if it has been staked since the pool creation +func (self *Pool) CalculateRawRewardForPosition(currentHeight int64, currentTick int32, deposit *Deposit) *u256.Uint { + var rewardAcc *u256.Uint + + globalAcc := self.calculateGlobalRewardRatioAccumulation(currentHeight, self.CurrentStakedLiquidity(currentHeight)) + lowerAcc := self.ticks.Get(deposit.tickLower).CurrentOutsideAccumulation(currentHeight) + upperAcc := self.ticks.Get(deposit.tickUpper).CurrentOutsideAccumulation(currentHeight) + if currentTick < deposit.tickLower { + rewardAcc = u256.Zero().Sub(lowerAcc, upperAcc) + } else if currentTick >= deposit.tickUpper { + rewardAcc = u256.Zero().Sub(upperAcc, lowerAcc) + } else { + rewardAcc = u256.Zero().Sub(globalAcc, lowerAcc) + rewardAcc = rewardAcc.Sub(rewardAcc, upperAcc) } - 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) + return rewardAcc +} - 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 - }) +// Calculate actual reward in [startHeight, endHeight) for a position by +// subtracting the startHeight's raw reward from the endHeight's raw reward +func (self *Pool) CalculateRewardForPosition(startHeight int64, startTick int32, endHeight int64, endTick int32, deposit *Deposit) *u256.Uint { + rewardAcc := self.CalculateRawRewardForPosition(endHeight, endTick, deposit) - if currentStakedLiquidity.IsZero() { - unclaimable += rewardPerBlock * (endHeight - startHeight) - } + debtAcc := self.CalculateRawRewardForPosition(startHeight, startTick, deposit) - return unclaimable -} + rewardAcc = rewardAcc.Sub(rewardAcc, debtAcc) -func (self *Pool) processUnclaimableReward(poolTier *PoolTier, endHeight uint64) (uint64, map[string]uint64) { - startHeight := *self.lastUnclaimableHeight - internalUnClaimable := *self.unclaimableAcc - *self.unclaimableAcc = 0 - externalUnClaimable := make(map[string]uint64) - for _, incentiveId := range self.incentives.CurrentReward(startHeight).ActiveIncentives { - externalUnClaimable[incentiveId] = self.UnclaimableExternalReward(incentiveId, startHeight, endHeight) - } - println(">>>>>>>>>>>> internalUnClaimable : ", internalUnClaimable, ", externalUnClaimable : ", externalUnClaimable) - self.lastUnclaimableHeight = &endHeight - return internalUnClaimable, externalUnClaimable + return rewardAcc } diff --git a/staker/reward_calculation_pool_tier.gno b/staker/reward_calculation_pool_tier.gno index 70e707c74..6bc588a66 100644 --- a/staker/reward_calculation_pool_tier.gno +++ b/staker/reward_calculation_pool_tier.gno @@ -10,7 +10,6 @@ import ( ) const ( - TierRatioLCM = 8400 // LCM(20, 30, 50, 70, 80, 100) AllTierCount = 4 // 0, 1, 2, 3 Tier1 = 1 Tier2 = 2 @@ -35,68 +34,38 @@ type TierRatio struct { // - tier3Count (uint64): Number of pools in tier 3. // // Returns: -// - *TierRatio: The ratio distribution across tier 1, 2, and 3. -func TierRatioFromCounts(tier1Count, tier2Count, tier3Count uint64) *TierRatio { +// - TierRatio: The ratio distribution across tier 1, 2, and 3, scaled up by 100. +func TierRatioFromCounts(tier1Count, tier2Count, tier3Count uint64) TierRatio { // tier1 always exists if tier2Count == 0 && tier3Count == 0 { - return &TierRatio{ + return TierRatio{ Tier1: 100, Tier2: 0, Tier3: 0, } } if tier2Count == 0 { - return &TierRatio{ + return TierRatio{ Tier1: 80, Tier2: 0, Tier3: 20, } } if tier3Count == 0 { - return &TierRatio{ + return TierRatio{ Tier1: 70, Tier2: 30, Tier3: 0, } } - return &TierRatio{ + return TierRatio{ Tier1: 50, Tier2: 30, Tier3: 20, } } -// RewardDenominator[i] = TierCount[i] * TierRatioLCM / (TierRatio[i] * 100) -// TierReward[i] = Emission * TierRatioLCM / RewardDenominator[i] / 100 -func (self *TierRatio) IntoRewardDenominators(counts []uint64) []uint64 { - if len(counts) != AllTierCount { - panic(addDetailToError( - errInvalidPoolTier, ufmt.Sprintf("invalid tier count length(%d)", len(counts))), - ) - } - - println("\tIntoRewardDenominators : , All tier counts len : ", len(counts)) - result := make([]uint64, AllTierCount) - for i := Tier1; i < AllTierCount; i++ { - if counts[i] == 0 { - result[i] = 0 - } else { - result[i] = counts[i] * TierRatioLCM / (self.Get(uint64(i))) - } - println("\t[", i, "] : ", counts[i], ", ratio : ", self.Get(uint64(i)), ", result : ", result[i]) - } - return result -} - -func ApplyDenominator(emission uint64, denominator uint64) uint64 { - println("\tApplyDenominator : emission : ", emission, ", denominator : ", denominator, ", return : ", emission*TierRatioLCM/denominator/100) - if denominator == 0 { - return 0 - } - - return emission * TierRatioLCM / denominator / 100 -} - +// Get returns the ratio(scaled up by 100) for the given tier. func (self *TierRatio) Get(tier uint64) uint64 { switch tier { case Tier1: @@ -115,7 +84,6 @@ func (self *TierRatio) Get(tier uint64) uint64 { // // Fields: // - membership: Tracks which tier a pool belongs to (poolPath -> blockNumber -> tier). -// - rewardCache: Stores cached rewards for each tier (blockNumber -> reward). // // Methods: // - CurrentCount: Returns the current count of pools in a tier at a specific height. @@ -126,49 +94,54 @@ func (self *TierRatio) Get(tier uint64) uint64 { type PoolTier struct { membership *avl.Tree // poolPath -> tier(1, 2, 3) - tierRatio *TierRatio + tierRatio TierRatio + + lastRewardCacheHeight int64 + + currentEmission uint64 - // rewardCache is used to calculate internal reward for each tier - // rewardCache = (per block emission) / (number of pools in tier) * (tier's ratio) - // rewardCache [4]*RewardCacheTree // blockNumber -> uint64 - lastRewardCacheHeight *uint64 + // returns current emission. + getEmission func() uint64 + + // returns a list of halving blocks within the interval [start, end) in ascending order + // there MUST NOT be any emission amount change between start and end - those had to be handled by the CallbackStakerEmissionChange. + getHalvingBlocksInRange func(start, end int64) ([]int64, []uint64) - emissionUpdate func(uint64, uint64) en.EmissionUpdate } -func NewPoolTier(currentHeight uint64, initialPoolPath string, emissionUpdate func(uint64, uint64) en.EmissionUpdate) *PoolTier { +// NewPoolTier creates a new PoolTier instance with single initial 1 tier pool. +// +// Parameters: +// - pools: The pool collection. +// - currentHeight: The current block height. +// - initialPoolPath: The path of the initial pool. +// - getEmission: A function that returns the current emission to the staker contract. +// - getHalvingBlocksInRange: A function that returns a list of halving blocks within the interval [start, end) in ascending order. +// +// Returns: +// - *PoolTier: The new PoolTier instance. +func NewPoolTier(pools *Pools, currentHeight int64, initialPoolPath string, getEmission func() uint64, getHalvingBlocksInRange func(start, end int64) ([]int64, []uint64)) *PoolTier { result := &PoolTier{ membership: avl.NewTree(), tierRatio: TierRatioFromCounts(1, 0, 0), - emissionUpdate: emissionUpdate, - lastRewardCacheHeight: ¤tHeight, + lastRewardCacheHeight: currentHeight + 1, + getEmission: getEmission, + getHalvingBlocksInRange: getHalvingBlocksInRange, + currentEmission: getEmission(), } - result.createPool(pools, initialPoolPath, 1, currentHeight) + pools.Set(initialPoolPath, NewPool(initialPoolPath, currentHeight+1)) + result.changeTier(currentHeight+1, pools, initialPoolPath, 1) return result } -func (self *PoolTier) membershipOf(poolPath string) *UintTree { - v, ok := self.membership.Get(poolPath) - if !ok { - tree := NewUintTree() - self.membership.Set(poolPath, tree) - return tree - } - return v.(*UintTree) -} - -func (self *PoolTier) createPool(pools *Pools, poolPath string, initialTier uint64, currentHeight uint64) { - _ = pools.GetOrCreate(poolPath) - - self.changeTier(currentHeight, pools, poolPath, initialTier) -} - +// CurrentReward returns the current per-pool reward for the given tier. func (self *PoolTier) CurrentReward(tier uint64) uint64 { - currentEmission := self.emissionUpdate(uint64(std.GetHeight()), uint64(std.GetHeight())).LastEmissionUpdate + currentEmission := self.getEmission() return currentEmission * self.tierRatio.Get(tier) / uint64(self.CurrentCount(tier)) / 100 } +// CurrentCount returns the current count of pools in the given tier. func (self *PoolTier) CurrentCount(tier uint64) int { count := 0 self.membership.Iterate("", "", func(key string, value interface{}) bool { @@ -180,6 +153,7 @@ func (self *PoolTier) CurrentCount(tier uint64) int { return count } +// CurrentAllTierCounts returns the current count of pools in each tier. func (self *PoolTier) CurrentAllTierCounts() []uint64 { count := make([]uint64, AllTierCount) self.membership.Iterate("", "", func(key string, value interface{}) bool { @@ -189,6 +163,7 @@ func (self *PoolTier) CurrentAllTierCounts() []uint64 { return count } +// CurrentTier returns the tier of the given pool. func (self *PoolTier) CurrentTier(poolPath string) uint64 { tier, ok := self.membership.Get(poolPath) if !ok { @@ -197,24 +172,27 @@ func (self *PoolTier) CurrentTier(poolPath string) uint64 { return tier.(uint64) } -func (self *PoolTier) changeTier(currentHeight uint64, pools *Pools, poolPath string, nextTier uint64) { - // TODO: - // poolPath validation check - - println("[", currentHeight, "] changeTier Start -> poolPath : ", poolPath, ", nextTier : ", nextTier) +// changeTier updates the tier of a pool, recalculates ratios, and applies +// updated per-pool reward to each of the pools. +func (self *PoolTier) changeTier(currentHeight int64, pools *Pools, poolPath string, nextTier uint64) { self.cacheReward(currentHeight, pools) - + + // same as prev. no need to update currentTier := self.CurrentTier(poolPath) if currentTier == nextTier { + // no change, return return } if nextTier == 0 { + // removed from the tier self.membership.Remove(poolPath) - _, ok := pools.Get(poolPath) + pool, ok := pools.Get(poolPath) if !ok { panic("changeTier: pool not found") } + // caching reward to 0 + pool.cacheReward(currentHeight, 0) } else { self.membership.Set(poolPath, nextTier) } @@ -222,67 +200,86 @@ func (self *PoolTier) changeTier(currentHeight uint64, pools *Pools, poolPath st counts := self.CurrentAllTierCounts() self.tierRatio = TierRatioFromCounts(counts[Tier1], counts[Tier2], counts[Tier3]) - rewardDenominators := self.tierRatio.IntoRewardDenominators(counts) - - // We only care about the current emission update at the current height. - // Any emission update were already handled by the cacheReward() at the start of changeTier(). - currentEmission := self.emissionUpdate(currentHeight, currentHeight).LastEmissionUpdate + currentEmission := self.getEmission() + // Cache updated reward for each tiered pool self.membership.Iterate("", "", func(key string, value interface{}) bool { pool, ok := pools.Get(key) if !ok { panic("changeTier: pool not found") } - pool.cacheRewardPerLiquidityUnit(currentHeight, currentHeight, ApplyDenominator(currentEmission, rewardDenominators[value.(uint64)])) + tier := value.(uint64) + poolReward := currentEmission * self.tierRatio.Get(tier) / 100 / counts[tier] + pool.cacheReward(currentHeight, poolReward) return false }) -} -func CalculateTierReward(currentEmission uint64, tierRatio *TierRatio, tier uint64, poolCount uint64) uint64 { - return currentEmission * tierRatio.Get(tier) / 100 / poolCount + self.currentEmission = currentEmission } // cacheReward MUST be called before calculating any position reward -// There MUST be no ratio/count/tier change during the emissionUpdates -// (due to calling cacheReward() at the start of changeTier(), this holds true). -func (self *PoolTier) cacheReward(currentHeight uint64, pools *Pools) { - // PoolTier.cacheReward is executed only when emissionUpdate is not empty, which means: - // - there is a new halving event - // - msPerBlock is changed - // - emission distribution ratio is changed - // during the period of PoolTier.lastRewardCacheHeight to currentHeight - - println("[", currentHeight, "] cacheReward Start -> lastRewardCacheHeight : ", *self.lastRewardCacheHeight) - if *self.lastRewardCacheHeight == currentHeight { - println("lastRewardCacheHeight is same with currentHeight") +// cacheReward updates the reward cache for each pools, accounting for any halving event in between the last cached height and the current height. +func (self *PoolTier) cacheReward(currentHeight int64, pools *Pools) { + lastHeight := self.lastRewardCacheHeight + + if currentHeight <= lastHeight { + // no need to check return } - emissionUpdate := self.emissionUpdate(*self.lastRewardCacheHeight, currentHeight) - if emissionUpdate.IsEmpty() { - println("emissionUpdate is empty") + // find halving blocks in range + halvingBlocks, halvingEmissions := self.getHalvingBlocksInRange(lastHeight, currentHeight) + + if len(halvingBlocks) == 0 { + self.applyCacheToAllPools(pools, currentHeight, self.currentEmission) return } - rewardDenominators := self.tierRatio.IntoRewardDenominators(self.CurrentAllTierCounts()) + for i, hvBlock := range halvingBlocks { + // caching: [lastHeight, hvBlock) + self.applyCacheToAllPools(pools, hvBlock, halvingEmissions[i]) + + // halve emissions when halvingBlock is reached + self.currentEmission = halvingEmissions[i] + } + + // remaining range [lastHalvingBlock, currentHeight) + self.applyCacheToAllPools(pools, currentHeight, self.currentEmission) + + // update lastRewardCacheHeight and currentEmission + self.lastRewardCacheHeight = currentHeight +} + +// applyCacheToAllPools applies the cached reward to all tiered pools. +func (self *PoolTier) applyCacheToAllPools(pools *Pools, currentBlock int64, emissionInThisInterval uint64) { + // calculate denominator and number of pools in each tier + counts := self.CurrentAllTierCounts() + // apply cache to all pools self.membership.Iterate("", "", func(key string, value interface{}) bool { + tierNum := value.(uint64) pool, ok := pools.Get(key) if !ok { - panic("pool not found") + return false } - println("") - println("\tmembership : ", key, ", ", value.(uint64)) - pool.cacheInternalReward(currentHeight, emissionUpdate, rewardDenominators[value.(uint64)]) - + // real reward + reward := emissionInThisInterval * self.tierRatio.Get(tierNum) / 100 / counts[tierNum] + // accumulate the reward for the interval (startBlock to endBlock) in the Pool + pool.cacheInternalReward(currentBlock, reward) return false }) - - *self.lastRewardCacheHeight = currentHeight - println("[", currentHeight, "] cacheReward End : lastRewardCacheHeight ", *self.lastRewardCacheHeight) } -func (self *PoolTier) IsInternallyIncentivizedPool(currentHeight uint64, poolPath string) bool { +// IsInternallyIncentivizedPool returns true if the pool is in a tier. +func (self *PoolTier) IsInternallyIncentivizedPool(poolPath string) bool { return self.CurrentTier(poolPath) > 0 } + +func (self *PoolTier) CurrentRewardPerPool(poolPath string) uint64 { + emission := self.getEmission() + counts := self.CurrentAllTierCounts() + tierNum := self.CurrentTier(poolPath) + reward := emission * self.tierRatio.Get(tierNum) / 100 / counts[tierNum] + return reward +} diff --git a/staker/reward_calculation_pool_tier_test.gno b/staker/reward_calculation_pool_tier_test.gno index 488eac71d..6c6a8d04f 100644 --- a/staker/reward_calculation_pool_tier_test.gno +++ b/staker/reward_calculation_pool_tier_test.gno @@ -41,7 +41,7 @@ func TestTierRatioFromCounts(t *testing.T) { for _, tt := range tests { result := TierRatioFromCounts(tt.tier1Count, tt.tier2Count, tt.tier3Count) - if *result != tt.expected { + if result != tt.expected { t.Errorf("TierRatioFromCounts(%d, %d, %d) = %v; want %v", tt.tier1Count, tt.tier2Count, tt.tier3Count, result, tt.expected) } @@ -49,10 +49,10 @@ func TestTierRatioFromCounts(t *testing.T) { } func TestNewPoolTier(t *testing.T) { - currentHeight := uint64(100) + currentHeight := int64(100) mustExistsInTier1 := "testPool" - poolTier := NewPoolTier(currentHeight, mustExistsInTier1, en.GetStakerEmissionUpdates) + poolTier := NewPoolTier(NewPools(), currentHeight, mustExistsInTier1, func() uint64 { return 0 }, func(start, end int64) ([]int64, []uint64) { return nil, nil }) // Test initial counts if count := poolTier.CurrentCount(1); count != 1 { @@ -72,14 +72,10 @@ func TestNewPoolTier(t *testing.T) { } func TestCacheReward(t *testing.T) { - currentHeight := uint64(250) + currentHeight := int64(250) // Simulate emission updates - poolTier := NewPoolTier(currentHeight, "testPool", func(startHeight uint64, endHeight uint64) en.EmissionUpdate { - return en.EmissionUpdate { - LastEmissionUpdate: 1000, - EmissionUpdateHeights: []uint64{100, 150, 200}, - EmissionUpdates: []uint64{1000, 500, 250}, - } + poolTier := NewPoolTier(NewPools(), currentHeight, "testPool", func() uint64 { return 1000 }, func(startHeight int64, endHeight int64) ([]int64, []uint64) { + return []int64{100, 150, 200}, []uint64{1000, 500, 250} }) // Cache rewards @@ -95,7 +91,7 @@ func TestCacheReward(t *testing.T) { var test_gnousdc = pl.GetPoolPath("gno.land/r/demo/wugnot", "gno.land/r/gnoswap/v1/gns", 3000) func SetupPoolTier(t *testing.T) *PoolTier { - poolTier := NewPoolTier(1, test_gnousdc, en.GetStakerEmissionUpdates) + poolTier := NewPoolTier(NewPools(), 1, test_gnousdc, func() uint64 { return 1000 }, func(start, end int64) ([]int64, []uint64) { return nil, nil }) poolTier.changeTier(1, pools, test_gnousdc, 1) return poolTier diff --git a/staker/reward_calculation_tick.gno b/staker/reward_calculation_tick.gno index a59aad6fb..2fab09994 100644 --- a/staker/reward_calculation_tick.gno +++ b/staker/reward_calculation_tick.gno @@ -49,8 +49,8 @@ type Ticks struct { tree *avl.Tree // int32 tickId -> tick } -func NewTicks() *Ticks { - return &Ticks{ +func NewTicks() Ticks { + return Ticks{ tree: avl.NewTree(), } } @@ -62,7 +62,7 @@ func (self *Ticks) Get(tickId int32) *Tick { id: tickId, stakedLiquidityGross: u256.Zero(), stakedLiquidityDelta: i256.Zero(), - cross: NewUintTree(), + outsideAccumulation: NewUintTree(), } self.tree.Set(EncodeInt(tickId), tick) return tick @@ -72,8 +72,6 @@ func (self *Ticks) Get(tickId int32) *Tick { func (self *Ticks) Set(tickId int32, tick *Tick) { if tick.stakedLiquidityGross.IsZero() { - // TODO: check if this could cause memory leak of GC halt - // because tick.cross being dropped may overload the VM GC self.tree.Remove(EncodeInt(tickId)) return } @@ -90,7 +88,7 @@ func (self *Ticks) Has(tickId int32) bool { // - id (int32): The ID of the tick. // - stakedLiquidityGross (*u256.Uint): Total gross staked liquidity at this tick. // - stakedLiquidityDelta (*i256.Int): Net change in staked liquidity at this tick. -// - cross (*UintTree): Tracks tick crossing events (block number -> zeroForOne). +// - outsideAccumulation (*UintTree): RewardRatioAccumulation outside the tick. type Tick struct { id int32 @@ -100,230 +98,53 @@ type Tick struct { // conceptually equal with Pool.liquidityNet but only for the staked positions stakedLiquidityDelta *i256.Int - // Notes for future optimizations. - // - // During swap, the states with the number of ticks that has been crossed are updated to store cross information. - // Considering one tick is ~0.01%, if there is a 0.5% price change for a single swap(in common max slippage setting), - // 50 ticks are crossed and 50 state write has to be done. - // Considering that this number is capped by the max slippage in most of the cases, the scalability might not be a problem. - // If this turns out to cause a gas cost issue, you may consider batching the - // multiple tick's cross state into single state, similar to tick bitmap. - // e.g. instead of having avl.Tree for each tick as key, use a segment of 32 ticks as bulk key. - // The value can be a 64-bit integer, where each 2-bit represents 00(no-update), 01(backward-cross), 10(forward-cross), 11(no-update). - - // block number -> zeroForOne - cross *UintTree -} - -// TickCrosses are encoded as int64, where negative value is backward cross(zeroForOne=true), positive value is forward cross(zeroForOne=false). -func (self *Tick) crossInfo(startHeight, endHeight uint64) []int64 { - tickCrosses := make([]int64, 0) - - self.cross.Iterate(startHeight, endHeight, func(key uint64, value interface{}) bool { - println("key : ", key, ", value : ", value.(bool)) - zeroForOne := value.(bool) - if zeroForOne { - tickCrosses = append(tickCrosses, -int64(key)) - } else { - tickCrosses = append(tickCrosses, int64(key)) - } - return false - }) - - return tickCrosses -} - -func (self *Tick) updateCross(blockNumber uint64, zeroForOne bool) { - self.cross.Set(blockNumber, zeroForOne) - println("updateCross ", blockNumber, ", ", zeroForOne) + // currentOutsideAccumulation is the accumulation of the blockNumber / TotalStake outside the tick. + // It is calculated by subtracting the current tick's currentOutsideAccumulation from the global reward ratio accumulation. + outsideAccumulation *UintTree // blockNumber -> *u256.Uint } -func (self *Tick) previousCross(currentHeight uint64) bool { - // There MUST be at least one cross, set when the position is staked - cross := false - self.cross.ReverseIterate(0, currentHeight-1, func(key uint64, value interface{}) bool { - println(">>>>>>>>>>>>>>>>> previousCross ", key, value.(bool)) - cross = value.(bool) +// CurrentOutsideAccumulation returns the latest outside accumulation for the tick +func (self *Tick) CurrentOutsideAccumulation(blockNumber int64) *u256.Uint { + var acc *u256.Uint + self.outsideAccumulation.ReverseIterate(0, blockNumber, func(key int64, value interface{}) bool { + acc = value.(*u256.Uint) return true }) - return cross + if acc == nil { + acc = u256.Zero() + } + return acc } -func (self *Tick) modifyDepositLower(currentHeight uint64, currentTick int32, liquidity *i256.Int) { +// modifyDepositLower updates the tick's liquidity info by treating the deposit as a lower tick +func (self *Tick) modifyDepositLower(currentHeight int64, currentTick int32, liquidity *i256.Int) { // update staker side tick info - println("stakedLiquidityGross ", self.stakedLiquidityGross.ToString(), ", liquidity ", liquidity.ToString()) self.stakedLiquidityGross = liquidityMathAddDelta(self.stakedLiquidityGross, liquidity) if self.stakedLiquidityGross.Lt(u256.Zero()) { panic("stakedLiquidityGross is negative") } self.stakedLiquidityDelta = i256.Zero().Add(self.stakedLiquidityDelta, liquidity) - - println("modifyDepositLower: currentTick =", currentTick, "tickId =", self.id, "stakedLiquidityGross =", self.stakedLiquidityGross.ToString(), "stakedLiquidityDelta =", self.stakedLiquidityDelta.ToString()) - self.updateCross(currentHeight, currentTick < self.id) - // ticks.Set(self.id, self) } -func (self *Tick) modifyDepositUpper(currentHeight uint64, currentTick int32, liquidity *i256.Int) { +// modifyDepositUpper updates the tick's liquidity info by treating the deposit as an upper tick +func (self *Tick) modifyDepositUpper(currentHeight int64, currentTick int32, liquidity *i256.Int) { self.stakedLiquidityGross = liquidityMathAddDelta(self.stakedLiquidityGross, liquidity) if self.stakedLiquidityGross.Lt(u256.Zero()) { panic("stakedLiquidityGross is negative") } self.stakedLiquidityDelta = i256.Zero().Sub(self.stakedLiquidityDelta, liquidity) - - println("modifyDepositUpper: currentTick =", currentTick, "tickId =", self.id, "stakedLiquidityGross =", self.stakedLiquidityGross.ToString(), "stakedLiquidityDelta =", self.stakedLiquidityDelta.ToString()) - self.updateCross(currentHeight, currentTick < self.id) - // ticks.Set(self.id, self) } -type PerIntervalFunc = func(startHeight, endHeight uint64) - -// Gas optimization: encoding { height: int64, inRange: bool } as int64. -// height will be negative if inRange is false. -func ForEachEligibleInterval(startHeight, endHeight int64, currentInRange bool, tickUpperCross []int64, tickLowerCross []int64, f PerIntervalFunc) (bool, []int64, []int64) { - tickUpperCrossI := 0 - tickLowerCrossI := 0 - tickUpperCrossLen := len(tickUpperCross) - tickLowerCrossLen := len(tickLowerCross) - - for tickUpperCrossI < tickUpperCrossLen && tickLowerCrossI < tickLowerCrossLen { - upperCross := tickUpperCross[tickUpperCrossI] - lowerCross := tickLowerCross[tickLowerCrossI] - - lowerHeight := lowerCross - if lowerHeight < 0 { - lowerHeight = -lowerHeight - } - - upperHeight := upperCross - if upperHeight < 0 { - upperHeight = -upperHeight - } - - // reached the end height - if lowerHeight >= endHeight || upperHeight >= endHeight { - break - } - - // If the heights are the same, and they have the same zeroForOne, the tick has passed through the whole position range(outrange) - if upperCross == lowerCross { - if currentInRange { - // exit range - f(uint64(startHeight), uint64(lowerHeight)) - currentInRange = false - } - tickUpperCrossI++ - tickLowerCrossI++ - continue - } - - // If the height is the same, and zeroForOne is different, the tick has entered the position and then "bounced" back(inrange) - if upperCross == -lowerCross { - if !currentInRange { - // enter range - startHeight = lowerHeight - currentInRange = true - } - tickUpperCrossI++ - tickLowerCrossI++ - continue - } - - // Upper tick passed, - // - if zeroForOne, backward cross, negative height, inrange - // - If !zeroForOne, forward cross, positive height, outrange - // => inRange == upperCross < 0 - if upperHeight < lowerHeight { - if upperCross < 0 { - // enter range - if !currentInRange { - startHeight = upperHeight - currentInRange = true - } - } else { - // exit range - if currentInRange { - f(uint64(startHeight), uint64(upperHeight)) - currentInRange = false - } - } - tickUpperCrossI++ - continue - } - - // Lower tick passed, - // - if zeroForOne, backward cross, negative height, outrange - // - If !zeroForOne, forward cross, positive height, inrange - // => inRange == lowerCross > 0 - if lowerHeight < upperHeight { - if lowerCross > 0 { - // enter range - if !currentInRange { - startHeight = lowerHeight - currentInRange = true - } - } else { - // exit range - if currentInRange { - f(uint64(startHeight), uint64(lowerHeight)) - currentInRange = false - } - } - tickLowerCrossI++ - continue - } - - panic("unreachable") - } - - for ; tickUpperCrossI < len(tickUpperCross); tickUpperCrossI++ { - cross := tickUpperCross[tickUpperCrossI] - // reached the end height - if cross >= endHeight || -cross >= endHeight { - break - } - if cross < 0 { - // enter range - if !currentInRange { - startHeight = -cross - currentInRange = true - } - } else { - // exit range - if currentInRange { - f(uint64(startHeight), uint64(cross)) - currentInRange = false - } - } - } - - for ; tickLowerCrossI < len(tickLowerCross); tickLowerCrossI++ { - cross := tickLowerCross[tickLowerCrossI] - // reached the end height - if cross >= endHeight || -cross >= endHeight { - break - } - if cross > 0 { - // enter range - if !currentInRange { - startHeight = cross - currentInRange = true - } - } else { - // exit range - if currentInRange { - f(uint64(startHeight), uint64(-cross)) - currentInRange = false - } - } - } - - if currentInRange { - f(uint64(startHeight), uint64(endHeight)) - } - - return currentInRange, tickUpperCross[tickUpperCrossI:], tickLowerCross[tickLowerCrossI:] +// updateCurrentOutsideAccumulation updates the tick's outside accumulation +// It "flips" the accumulation's inside/outside by subtracting the current outside accumulation from the global accumulation +func (self *Tick) updateCurrentOutsideAccumulation(blockNumber int64, acc *u256.Uint) { + currentOutsideAccumulation := self.CurrentOutsideAccumulation(blockNumber) + newOutsideAccumulation := u256.Zero().Sub(acc, currentOutsideAccumulation) + self.outsideAccumulation.Set(blockNumber, newOutsideAccumulation) } +// TickCrossHook is a hook that is called when a tick is crossed. +// Modifies the tick's outside accumulation and updates the tick's liquidity info func TickCrossHook(pools *Pools, height func() int64) func(poolPath string, tickId int32, zeroForOne bool) { return func(poolPath string, tickId int32, zeroForOne bool) { pool, ok := pools.Get(poolPath) @@ -333,8 +154,14 @@ func TickCrossHook(pools *Pools, height func() int64) func(poolPath string, tick tick := pool.ticks.Get(tickId) - blockNumber := uint64(height()) - tick.updateCross(blockNumber, zeroForOne) + blockNumber := height() + + var nextTick int32 + if zeroForOne { + nextTick = tickId - 1 + } else { + nextTick = tickId + } liquidityInRangeDelta := tick.stakedLiquidityDelta if liquidityInRangeDelta.Sign() == 0 { @@ -344,34 +171,12 @@ func TickCrossHook(pools *Pools, height func() int64) func(poolPath string, tick if zeroForOne { liquidityInRangeDelta = i256.Zero().Neg(liquidityInRangeDelta) } - stakedLiquidity := pool.CurrentStakedLiquidity(blockNumber) - deltaApplied := liquidityMathAddDelta(stakedLiquidity, liquidityInRangeDelta) - - - switch deltaApplied.Sign() { - case -1: - panic("stakedLiquidity is less than 0") - case 0: - if stakedLiquidity.Sign() == 1 { - // StakedLiquidity moved from positive to zero, start unclaimable period - pool.startInternalUnclaimablePeriod(blockNumber) - } - case 1: - if stakedLiquidity.Sign() == 0 { - // StakedLiquidity moved from zero to positive, end unclaimable period - pool.endInternalUnclaimablePeriod(blockNumber) - } - } - - pool.updateRewardByLiquidityChange(*pool.tierRewardTotal, blockNumber, deltaApplied) - - pool.stakedLiquidity.Set(blockNumber, deltaApplied) - - // I'm not sure if this is needed. We may not need to update the pool info because the stakedLiquidity pointer itself has not changed. - // pools.Set(poolPath, pool) + newAcc := pool.modifyDeposit(liquidityInRangeDelta, blockNumber, nextTick) + tick.updateCurrentOutsideAccumulation(blockNumber, newAcc) } } func init() { + // Sets tick cross hook for pool contract pl.SetTickCrossHook(TickCrossHook(pools, std.GetHeight)) } diff --git a/staker/reward_calculation_tick_test.gno b/staker/reward_calculation_tick_test.gno index b91a8ca85..354349f60 100644 --- a/staker/reward_calculation_tick_test.gno +++ b/staker/reward_calculation_tick_test.gno @@ -46,24 +46,6 @@ func TestTicks(t *testing.T) { uassert.False(t, ticks.Has(100)) } -func TestTick(t *testing.T) { - tick := &Tick{ - id: 100, - stakedLiquidityGross: u256.Zero(), - stakedLiquidityDelta: i256.Zero(), - cross: NewUintTree(), - } - - tick.updateCross(10, true) - tick.updateCross(20, false) - crosses := tick.crossInfo(0, 30) - - expected := []int64{-10, 20} - for i, v := range crosses { - uassert.Equal(t, v, expected[i]) - } -} - func TestTicksBasic(t *testing.T) { ticks := NewTicks() @@ -80,49 +62,6 @@ func TestTicksBasic(t *testing.T) { uassert.False(t, ticks.Has(100)) } -func TestTickCrossInfo(t *testing.T) { - ticks := NewTicks() - tick := ticks.Get(42) - - tick.updateCross(10, true) // backward-cross - tick.updateCross(20, false) // forward-cross - tick.updateCross(30, true) // backward-cross - tick.updateCross(50, false) // forward-cross - - // 1 <= block <= 40 - crosses := tick.crossInfo(1, 40) - // block=10, zeroForOne=true => -10 - // block=20, zeroForOne=false => +20 - // block=30, zeroForOne=true => -30 - // block=50 (out of range. no need to care about this) - - expected := []int64{-10, 20, -30} - if !compareInt64Slices(t, crosses, expected) { - t.Errorf("crossInfo(1, 40) = %v; want %v", crosses, expected) - } - - crosses2 := tick.crossInfo(25, 100) - expected2 := []int64{-30, 50} - if !compareInt64Slices(t, crosses2, expected2) { - t.Errorf("crossInfo(25, 100) = %v; want %v", crosses2, expected2) - } -} - -func TestTickPreviousCross(t *testing.T) { - ticks := NewTicks() - tick := ticks.Get(1) - - tick.updateCross(10, false) - - tick.updateCross(20, true) - - uassert.False(t, tick.previousCross(1)) - uassert.False(t, tick.previousCross(10)) - uassert.False(t, tick.previousCross(11)) - uassert.False(t, tick.previousCross(20)) - uassert.True(t, tick.previousCross(21)) -} - func TestModifyDepositLower(t *testing.T) { ticks := NewTicks() tick := ticks.Get(100) @@ -166,96 +105,6 @@ func TestModifyDepositUpper(t *testing.T) { } } -func TestForEachEligibleInterval(t *testing.T) { - type CrossTest struct { - startHeight int64 - endHeight int64 - currentInRange bool - tickUpperCross []int64 - tickLowerCross []int64 - wantRanges [][2]uint64 - wantFinalInRange bool - } - - tests := []CrossTest{ - { - startHeight: 0, - endHeight: 100, - currentInRange: false, - // block=10(backward cross=>enter range), block=30(forward cross=>exit range) - tickUpperCross: []int64{-10, 30}, - tickLowerCross: []int64{}, - wantRanges: [][2]uint64{{10, 30}}, - wantFinalInRange: false, - }, - { - startHeight: 0, - endHeight: 40, - currentInRange: false, - // block=5(backward->enter), block=10(backward->enter again?), block=35(forward->exit) - tickUpperCross: []int64{-5, -10, 35}, - tickLowerCross: []int64{}, - // -5 => block=5(backward => enter), -10 => block=10(backward => enter, already in-range but duplicate enter), - // 35 => block=35(forward => exit). Therefore, a single interval (5..35). - wantRanges: [][2]uint64{{5, 35}}, - wantFinalInRange: false, - }, - { - startHeight: 0, - endHeight: 100, - currentInRange: false, - tickUpperCross: []int64{-10, 20, -30, 40}, - tickLowerCross: []int64{}, - // Interpretation of tickUpperCross: - // -10 => block=10, backward cross => enter range - // 20 => block=20, forward cross => exit range => (10..20) - // -30 => block=30, backward cross => enter range => (30..??) - // 40 => block=40, forward cross => exit range => (30..40) - // Ultimately, the intervals (10..20) and (30..40) become in-range and then end. - // wantFinalInRange=false - wantRanges: [][2]uint64{{10, 20}, {30, 40}}, - wantFinalInRange: false, - }, - { - startHeight: 0, - endHeight: 50, - currentInRange: false, - tickUpperCross: []int64{-10, 20}, - tickLowerCross: []int64{30}, - // tickUpperCross: - // -10 => block=10, backward => enter range => (10..??) - // 20 => block=20, forward => exit range => (10..20) - // tickLowerCross: - // 30 => block=30, (positive=forward cross) => enter range => (30..??) - // endHeight=50 => ends without exit => (30..50) - wantRanges: [][2]uint64{{10, 20}, {30, 50}}, - wantFinalInRange: true, - }, - } - - for i, tt := range tests { - var gotRanges [][2]uint64 - f := func(s, e uint64) { - gotRanges = append(gotRanges, [2]uint64{s, e}) - } - inRange, remUpper, remLower := ForEachEligibleInterval( - tt.startHeight, tt.endHeight, tt.currentInRange, - tt.tickUpperCross, tt.tickLowerCross, - f, - ) - if inRange != tt.wantFinalInRange { - t.Errorf("Test #%d: final inRange=%v; want %v", i, inRange, tt.wantFinalInRange) - } - if len(remUpper) != 0 || len(remLower) != 0 { - t.Errorf("Test #%d: expected no leftover crosses, got remUpper=%v, remLower=%v", - i, remUpper, remLower) - } - if !compareRangeSlices(t, gotRanges, tt.wantRanges) { - t.Errorf("Test #%d: got ranges=%v; want %v", i, gotRanges, tt.wantRanges) - } - } -} - func compareRangeSlices(t *testing.T, a, b [][2]uint64) bool { t.Helper() if len(a) != len(b) { diff --git a/staker/reward_calculation_types.gno b/staker/reward_calculation_types.gno index 2de82e5ff..258901643 100644 --- a/staker/reward_calculation_types.gno +++ b/staker/reward_calculation_types.gno @@ -50,7 +50,8 @@ func DecodeUint(s string) uint64 { return num } -// UintTree is a wrapper around an AVL tree for storing uint64 keys as strings. +// UintTree is a wrapper around an AVL tree for storing block heights as strings. +// Since block heights are defined as int64, we take int64 and convert it to uint64 for the tree. // // Methods: // - Get: Retrieves a value associated with a uint64 key. @@ -70,112 +71,34 @@ func NewUintTree() *UintTree { } } -func (self *UintTree) Get(key uint64) (interface{}, bool) { - v, ok := self.tree.Get(EncodeUint(key)) +func (self *UintTree) Get(key int64) (interface{}, bool) { + v, ok := self.tree.Get(EncodeUint(uint64(key))) if !ok { return nil, false } return v, true } -func (self *UintTree) Set(key uint64, value interface{}) { - self.tree.Set(EncodeUint(key), value) +func (self *UintTree) Set(key int64, value interface{}) { + self.tree.Set(EncodeUint(uint64(key)), value) } -func (self *UintTree) Has(key uint64) bool { - return self.tree.Has(EncodeUint(key)) +func (self *UintTree) Has(key int64) bool { + return self.tree.Has(EncodeUint(uint64(key))) } -func (self *UintTree) Remove(key uint64) { - self.tree.Remove(EncodeUint(key)) +func (self *UintTree) Remove(key int64) { + self.tree.Remove(EncodeUint(uint64(key))) } -func (self *UintTree) Iterate(start, end uint64, fn func(key uint64, value interface{}) bool) { - self.tree.Iterate(EncodeUint(start), EncodeUint(end), func(key string, value interface{}) bool { - return fn(DecodeUint(key), value) +func (self *UintTree) Iterate(start, end int64, fn func(key int64, value interface{}) bool) { + self.tree.Iterate(EncodeUint(uint64(start)), EncodeUint(uint64(end)), func(key string, value interface{}) bool { + return fn(int64(DecodeUint(key)), value) }) } -func (self *UintTree) ReverseIterate(start, end uint64, fn func(key uint64, value interface{}) bool) { - self.tree.ReverseIterate(EncodeUint(start), EncodeUint(end), func(key string, value interface{}) bool { - return fn(DecodeUint(key), value) +func (self *UintTree) ReverseIterate(start, end int64, fn func(key int64, value interface{}) bool) { + self.tree.ReverseIterate(EncodeUint(uint64(start)), EncodeUint(uint64(end)), func(key string, value interface{}) bool { + return fn(int64(DecodeUint(key)), value) }) -} - -// RewardCacheTree is a wrapper around an AVL tree for managing reward data. -// -// Methods: -// - Get: Retrieves a reward associated with a uint64 key. -// - Set: Stores a reward with a uint64 key. -// - Has: Checks if a uint64 key exists in the tree. -// - Iterate: Iterates over rewards in a range. -// - CurrentReward: Gets the most recent reward for a given height. -// - CurrentRewardAt: Gets the exact reward for a given height (panics if not found). -// - RewardPerInterval: Calculates rewards over an interval using a callback. -type RewardCacheTree struct { - tree *avl.Tree -} - -func NewRewardCacheTree() *RewardCacheTree { - return &RewardCacheTree{ - tree: avl.NewTree(), - } -} - -func (self *RewardCacheTree) Get(key uint64) (interface{}, bool) { - v, ok := self.tree.Get(EncodeUint(key)) - if !ok { - return nil, false - } - return v, true -} - -func (self *RewardCacheTree) Set(key uint64, value interface{}) { - self.tree.Set(EncodeUint(key), value) -} - -func (self *RewardCacheTree) Has(key uint64) bool { - return self.tree.Has(EncodeUint(key)) -} - -func (self *RewardCacheTree) Iterate(start, end uint64, fn func(key uint64, value interface{}) bool) { - self.tree.Iterate(EncodeUint(start), EncodeUint(end), func(key string, value interface{}) bool { - return fn(DecodeUint(key), value) - }) -} - -func (self *RewardCacheTree) CurrentReward(currentHeight uint64) interface{} { - result, ok := self.tree.Get(EncodeUint(currentHeight)) - if ok { - return result - } - self.tree.ReverseIterate("", EncodeUint(currentHeight), func(key string, value interface{}) bool { - result = value - return true - }) - return result -} - -func (self *RewardCacheTree) CurrentRewardAt(currentHeight uint64) interface{} { - result, ok := self.tree.Get(EncodeUint(currentHeight)) - if ok { - return result - } - panic(ufmt.Sprintf("RewardCacheTree.CurrentRewardAt() || currentHeight: %d, not found", currentHeight)) -} - -type RewardCalculationFunc = func(blockNumbers uint64, poolReward interface{}) - -func (self *RewardCacheTree) RewardPerInterval(startHeight, endHeight uint64, f RewardCalculationFunc) { - currentPoolReward := self.CurrentReward(startHeight) - currentHeight := startHeight - self.Iterate(startHeight, endHeight, func(height uint64, poolReward interface{}) bool { - f(height-currentHeight, currentPoolReward) - currentHeight = height - currentPoolReward = poolReward - return false - }) - if endHeight > currentHeight { - f(endHeight-currentHeight, currentPoolReward) - } } \ No newline at end of file diff --git a/staker/reward_calculation_types_test.gno b/staker/reward_calculation_types_test.gno index 3d6e41d8f..7f165820d 100644 --- a/staker/reward_calculation_types_test.gno +++ b/staker/reward_calculation_types_test.gno @@ -78,8 +78,8 @@ func TestUintTree(t *testing.T) { var keys []uint64 var values []interface{} - tree.Iterate(100, 300, func(key uint64, value interface{}) bool { - keys = append(keys, key) + tree.Iterate(100, 300, func(key int64, value interface{}) bool { + keys = append(keys, uint64(key)) values = append(values, value) return false }) @@ -93,60 +93,6 @@ func TestUintTree(t *testing.T) { } } -func TestRewardCacheTree(t *testing.T) { - tree := NewRewardCacheTree() - - // Test Set and Get - tree.Set(100, "reward1") - tree.Set(200, "reward2") - if value, ok := tree.Get(100); !ok || value != "reward1" { - t.Errorf("RewardCacheTree.Get(100) = %v, %v; want reward1, true", value, ok) - } - - // Test CurrentReward - tree.Set(300, "reward3") - reward := tree.CurrentReward(250) - if reward != "reward2" { - t.Errorf("RewardCacheTree.CurrentReward(250) = %v; want reward2", reward) - } - - // Test CurrentRewardAt - defer func() { - if r := recover(); r == nil { - t.Errorf("RewardCacheTree.CurrentRewardAt(400) did not panic") - } - }() - tree.CurrentRewardAt(400) -} - -func TestReverseIterateEmptyStringStart(t *testing.T) { - tree := NewRewardCacheTree() - - tree.Set(100, "reward1") - tree.Set(200, "reward2") - tree.Set(300, "reward3") - tree.Set(400, "reward4") - tree.Set(500, "reward5") - - var resEmptyString interface{} - var resZeroPadded interface{} - - tree.tree.ReverseIterate("", EncodeUint(250), func(k string, v interface{}) bool { - resEmptyString = v - return true - }) - - tree.tree.ReverseIterate(EncodeUint(0), EncodeUint(250), func(k string, v interface{}) bool { - resZeroPadded = v - return true - }) - - if resEmptyString != resZeroPadded { - t.Errorf("ReverseIterate with '' got %v, while zero-padded got %v; want same result", - resEmptyString, resZeroPadded) - } -} - // Helper function to compare slices of uint64 func compareUintSlices(t *testing.T, a, b []uint64) bool { t.Helper() diff --git a/staker/staker.gno b/staker/staker.gno index d3aeb0eda..2d16f8f2c 100644 --- a/staker/staker.gno +++ b/staker/staker.gno @@ -30,7 +30,7 @@ type Deposits struct { func NewDeposits() *Deposits { return &Deposits{ - tree: avl.NewTree(), // tokenId -> Deposit + tree: avl.NewTree(), // tokenId -> *Deposit } } @@ -107,6 +107,48 @@ func (self *ExternalIncentives) Size() int { return self.tree.Size() } +type Stakers struct { + tree *avl.Tree // address -> depositId -> *Deposit +} + +func NewStakers() *Stakers { + return &Stakers{ + tree: avl.NewTree(), + } +} + +func (self *Stakers) IterateAll(address std.Address, fn func(depositId uint64, deposit *Deposit) bool) { + depositTreeI, ok := self.tree.Get(address.String()) + if !ok { + return + } + depositTree := depositTreeI.(*avl.Tree) + depositTree.Iterate("", "", func(depositId string, depositI interface{}) bool { + deposit := depositI.(*Deposit) + return fn(DecodeUint(depositId), deposit) + }) +} + +func (self *Stakers) AddDeposit(address std.Address, depositId uint64, deposit *Deposit) { + depositTreeI, ok := self.tree.Get(address.String()) + if !ok { + depositTree := avl.NewTree() + self.tree.Set(address.String(), depositTree) + depositTreeI = depositTree + } + depositTree := depositTreeI.(*avl.Tree) + depositTree.Set(EncodeUint(depositId), deposit) +} + +func (self *Stakers) RemoveDeposit(address std.Address, depositId uint64) { + depositTreeI, ok := self.tree.Get(address.String()) + if !ok { + return + } + depositTree := depositTreeI.(*avl.Tree) + depositTree.Remove(EncodeUint(depositId)) +} + var ( // deposits stores deposit information for each tokenId deposits *Deposits = NewDeposits() @@ -114,8 +156,18 @@ var ( // externalIncentives stores external incentive information for each incentiveId externalIncentives *ExternalIncentives = NewExternalIncentives() + // stakers stores staker information for each address + stakers *Stakers = NewStakers() + // poolTier stores pool tier information poolTier *PoolTier + + // totalEmissionSent is the total amount of GNS emission sent from staker to user(and community pool if penalty exists) + // which includes following + // 1. reward sent to user (which also includes protocol_fee) + // 2. penalty sent to community pool + // 3. unclaimable reward + totalEmissionSent uint64 ) const ( @@ -136,8 +188,9 @@ func init() { // tier 1 // ONLY GNOT:GNS 0.3% - poolTier = NewPoolTier(uint64(std.GetHeight()), MUST_EXISTS_IN_TIER_1, en.GetStakerEmissionUpdates) - pools.GetOrCreate(MUST_EXISTS_IN_TIER_1) // must update pools tree + pools.GetOrCreate(MUST_EXISTS_IN_TIER_1) + poolTier = NewPoolTier(pools, std.GetHeight(), MUST_EXISTS_IN_TIER_1, en.GetEmission, en.GetHalvingBlocksInRange) + en.SetCallbackStakerEmissionChange(callbackStakerEmissionChange) } // StakeToken stakes an LP token into the staker contract. It transfer the LP token @@ -164,8 +217,6 @@ func init() { // // ref: https://docs.gnoswap.io/contracts/staker/staker.gno#staketoken func StakeToken(tokenId uint64) (string, string, string) { - - println("================== Stake Token (", tokenId, ") ==================") assertOnlyNotHalted() assertOnlyNotStaked(tokenId) @@ -202,10 +253,14 @@ func StakeToken(tokenId uint64) (string, string, string) { tickLower: tickLower, tickUpper: tickUpper, liquidity: liquidity, - lastCollectHeight: uint64(currentHeight), + lastCollectHeight: currentHeight, warmups: InstantiateWarmup(currentHeight), } + + currentTick := pl.PoolGetSlot0Tick(poolPath) + deposits.Set(tokenId, deposit) + stakers.AddDeposit(caller, tokenId, deposit) if caller == owner { // if caller is owner, transfer NFT ownership to staker contract if err := transferDeposit(tokenId, owner, caller, consts.STAKER_ADDR); err != nil { @@ -217,33 +272,24 @@ func StakeToken(tokenId uint64) (string, string, string) { pn.SetPositionOperator(tokenId, caller) signedLiquidity := i256.FromUint256(liquidity) - currentTick := pl.PoolGetSlot0Tick(poolPath) isInRange := false - println("[", currentHeight, "][", tokenId, "] currentTick: ", currentTick) - // TODO: call cache functions at the every staker related state change - poolTier.cacheReward(uint64(currentHeight), pools) - pool.cacheExternalReward(uint64(currentHeight)) + poolTier.cacheReward(currentHeight, pools) if pn.PositionIsInRange(tokenId) { isInRange = true - pool.modifyDeposit(tokenId, signedLiquidity, uint64(currentHeight)) + pool.modifyDeposit(signedLiquidity, currentHeight, currentTick) } + // historical tick must be set regardless of the deposit's range + pool.historicalTick.Set(currentHeight, currentTick) // this could happen because of how position stores the ticks. // ticks are negated if the token1 < token0 upperTick := pool.ticks.Get(tickUpper) lowerTick := pool.ticks.Get(tickLower) - upperTick.modifyDepositUpper(uint64(currentHeight), currentTick, signedLiquidity) - println("upperTick id : ", upperTick.id) - println("upperTick stakedLiquidityGross: ", upperTick.stakedLiquidityGross.ToString()) - println("upperTick stakedLiquidityDelta: ", upperTick.stakedLiquidityDelta.ToString()) - println("upperTick Size: ", upperTick.cross.tree.Size()) - lowerTick.modifyDepositLower(uint64(currentHeight), currentTick, signedLiquidity) - println("lowerTick id: ", lowerTick.id) - println("lowerTick stakedLiquidityGross: ", lowerTick.stakedLiquidityGross.ToString()) - println("lowerTick stakedLiquidityDelta: ", lowerTick.stakedLiquidityDelta.ToString()) - println("lowerTick Size: ", lowerTick.cross.tree.Size()) + + upperTick.modifyDepositUpper(currentHeight, currentTick, signedLiquidity) + lowerTick.modifyDepositLower(currentHeight, currentTick, signedLiquidity) prevAddr, prevPkgPath := getPrev() @@ -330,7 +376,7 @@ func transferDeposit(tokenId uint64, owner, caller, to std.Address) error { //////////////////////////////////////////////////////////// -// CollectReward harvests accumulated rewards for a staked position. This includes both +// ectReward harvests accumulated rewards for a staked position. This includes both // inernal GNS emission and external incentive rewards. // // State Transition: @@ -353,13 +399,6 @@ func transferDeposit(tokenId uint64, owner, caller, to std.Address) error { // // ref: https://docs.gnoswap.io/contracts/staker/staker.gno#collectreward func CollectReward(tokenId uint64, unwrapResult bool) (string, string) { - - println("") - println("") - println("") - println("") - println("================== CollectReward (", tokenId, ") ==================") - assertOnlyNotHalted() en.MintAndDistributeGns() @@ -372,10 +411,10 @@ func CollectReward(tokenId uint64, unwrapResult bool) (string, string) { currentHeight := std.GetHeight() // get all internal and external rewards - reward := calcPositionReward(uint64(currentHeight), tokenId) + reward := calcPositionReward(currentHeight, tokenId) // update lastCollectHeight to current height - deposit.lastCollectHeight = uint64(currentHeight) + deposit.lastCollectHeight = currentHeight // transfer external rewards to user externalReward := reward.External @@ -389,57 +428,39 @@ func CollectReward(tokenId uint64, unwrapResult bool) (string, string) { )) } incentive.rewardAmount -= amount - incentive.rewardLeft = incentive.rewardAmount externalIncentives.Set(incentiveId, incentive) toUser := handleUnstakingFee(rewardToken, amount, false, tokenId, incentive.targetPoolPath) teller := common.GetTokenTeller(rewardToken) teller.Transfer(deposit.owner, toUser) - } - - // do nothing for external penalty - // it will be stored in staker, and when external ends will be refunded - externalIncentivePenalty := reward.ExternalPenalty - externalIncentiveReward := reward.External - // TODO: - // externalPenalty should be stored in staker - // And update reward ledger + externalPenalty := reward.ExternalPenalty[incentiveId] + incentive.rewardLeft += externalPenalty - // println("externalPenalty", externalPenalty) + // unwrap if necessary + if unwrapResult && rewardToken == consts.WUGNOT_PATH { + unwrap(toUser) + } + } // internal reward to user toUser := handleUnstakingFee(consts.GNS_PATH, reward.Internal, true, tokenId, deposit.targetPoolPath) - println("toUser", toUser) - if toUser > 0 { - println("gns balance (staker) : ", gns.BalanceOf(pusers.AddressOrName(consts.STAKER_ADDR))) + totalEmissionSent += toUser - println("sending reward", toUser, "to", deposit.owner, "// gns balance (staker) :", gns.BalanceOf(pusers.AddressOrName(consts.STAKER_ADDR))) + if toUser > 0 { gns.Transfer(a2u(deposit.owner), toUser) - println("sending reward ok") - println() // internal penalty to community pool - println("sending penalty", reward.InternalPenalty, "to community pool // gns balance (staker) :", gns.BalanceOf(pusers.AddressOrName(consts.STAKER_ADDR))) gns.Transfer(a2u(consts.COMMUNITY_POOL_ADDR), reward.InternalPenalty) - println("sending penalty ok") - println() - - // TODO: - // reward ledger update!! + totalEmissionSent += reward.InternalPenalty } - // FIXME @mconcat - // panic: slice index out of bounds: 0 (len=0) - unClaimableInternal, unClaimableExternal := ProcessUnClaimableReward(deposit.targetPoolPath, uint64(currentHeight)) - println("unClaimableInternal : ", unClaimableInternal) + unClaimableInternal := ProcessUnClaimableReward(deposit.targetPoolPath, currentHeight) + totalEmissionSent += unClaimableInternal if unClaimableInternal > 0 { // internal unclaimable to community pool - println("sending unClaimableInternal", unClaimableInternal, " to community pool // gns balance (staker) :", gns.BalanceOf(pusers.AddressOrName(consts.STAKER_ADDR))) gns.Transfer(a2u(consts.COMMUNITY_POOL_ADDR), unClaimableInternal) - println("sending unClaimableInternal ok") - println() } prevAddr, prevPkgPath := getPrev() @@ -462,37 +483,6 @@ func CollectReward(tokenId uint64, unwrapResult bool) (string, string) { return strconv.FormatUint(toUser, 10), strconv.FormatUint(reward.InternalPenalty, 10) } -/* -func applyCollectReward( - result *collectResult, - unwrap bool, -) ([]externalRewardResult, internalRewardResult) { - rewardResults := make([]externalRewardResult, 0) - - // apply external rewards - result.externalRewards.Iterate("", "", func(ictvId string, value interface{}) bool { - reward := value.(externalRewardInfo) - rewardResult := applyExternalReward(result.tokenId, reward, result.owner, unwrap) - rewardResults = append(rewardResults, rewardResult) - return false // continue to iterate - }) - - // apply internal rewards - internalRewardResult := applyInternalReward(result.tokenId, result.internalRewards, result.owner) - - // update staker GNS balance - lastCalculatedBalance = calculateGnsBalance() - - return rewardResults, internalRewardResult -} - -func calculateGnsBalance() uint64 { - return gnsBalance(consts.STAKER_ADDR) - externalGnsAmount() - externalDepositGnsAmount() -} -*/ - -//////////////////////////////////////////////////////////// - // UnstakeToken withdraws an LP token from staking, collecting all pending rewards // and returning the token to its original owner. // @@ -565,12 +555,12 @@ func applyUnStake(tokenId uint64) { )) } - currentHeight := uint64(std.GetHeight()) + currentHeight := std.GetHeight() currentTick := pl.PoolGetSlot0Tick(deposit.targetPoolPath) signedLiquidity := i256.FromUint256(deposit.liquidity) signedLiquidity = signedLiquidity.Neg(signedLiquidity) if pn.PositionIsInRange(tokenId) { - pool.modifyDeposit(tokenId, signedLiquidity, currentHeight) + pool.modifyDeposit(signedLiquidity, currentHeight, currentTick) } upperTick := pool.ticks.Get(deposit.tickUpper) @@ -579,6 +569,7 @@ func applyUnStake(tokenId uint64) { lowerTick.modifyDepositLower(currentHeight, currentTick, signedLiquidity) deposits.Remove(tokenId) + stakers.RemoveDeposit(deposit.owner, tokenId) owner := gnft.MustOwnerOf(tid(tokenId)) caller := getPrevAddr() @@ -623,8 +614,8 @@ func poolHasIncentives(poolPath string) error { errNonIncentivizedPool, poolPath, ) } - hasInternal := poolTier.IsInternallyIncentivizedPool(uint64(std.GetHeight()), poolPath) - hasExternal := pool.IsExternallyIncentivizedPool(uint64(std.GetHeight())) + hasInternal := poolTier.IsInternallyIncentivizedPool(poolPath) + hasExternal := pool.IsExternallyIncentivizedPool() if hasInternal == false && hasExternal == false { return ufmt.Errorf( "%v: can not stake position to non incentivized pool(%s)", @@ -681,7 +672,6 @@ func getTokenPairBalanceFromPosition(poolPath string, tokenId uint64) (string, s if token1Balance == "" { token1Balance = "0" } - println("[", tokenId, "]=== token0Balance: ", token0Balance, ", token1Balance: ", token1Balance) return token0Balance, token1Balance } @@ -692,9 +682,4 @@ func getTickOf(tokenId uint64) (int32, int32) { panic(ufmt.Sprintf("tickUpper(%d) is less than tickLower(%d)", tickUpper, tickLower)) } return tickLower, tickUpper -} - -// TODO: -// SetTier -// RemoveTier -// ChangeTier +} \ No newline at end of file diff --git a/staker/staker_external_incentive.gno b/staker/staker_external_incentive.gno index ca3686529..48f981740 100644 --- a/staker/staker_external_incentive.gno +++ b/staker/staker_external_incentive.gno @@ -84,7 +84,7 @@ func CreateExternalIncentive( caller := std.PrevRealm().Addr() // incentiveId := incentiveIdCompute(std.PrevRealm().Addr(), targetPoolPath, rewardToken, startTimestamp, endTimestamp, std.GetHeight()) - incentiveId := incentiveIdByTime(uint64(startTimestamp), uint64(endTimestamp), caller, rewardToken) + incentiveId := incentiveIdByTime(startTimestamp, endTimestamp, caller, rewardToken) externalDuration := uint64(endTimestamp - startTimestamp) if err := isValidIncentiveDuration(externalDuration); err != nil { @@ -162,11 +162,11 @@ func EndExternalIncentive(refundee std.Address, targetPoolPath, rewardToken stri )) } - now := time.Now().Unix() - if now < ictv.endTimestamp { + now := std.GetHeight() + if now < ictv.endHeight { panic(addDetailToError( errCannotEndIncentive, - ufmt.Sprintf("staker.gno__EndExternalIncentive() || cannot end incentive before endTimestamp(%d), current(%d)", ictv.endTimestamp, now), + ufmt.Sprintf("staker.gno__EndExternalIncentive() || cannot end incentive before endHeight(%d), current(%d)", ictv.endHeight, now), )) } @@ -183,6 +183,11 @@ func EndExternalIncentive(refundee std.Address, targetPoolPath, rewardToken stri // when incentive ended, refund remaining reward refund := ictv.rewardLeft + if !ictv.unclaimableRefunded { + refund += pool.incentives.calculateUnclaimableReward(ictv.incentiveId) + ictv.unclaimableRefunded = true + } + poolLeftExternalRewardAmount := common.BalanceOf(ictv.rewardToken, consts.STAKER_ADDR) if poolLeftExternalRewardAmount < refund { refund = poolLeftExternalRewardAmount @@ -199,8 +204,6 @@ func EndExternalIncentive(refundee std.Address, targetPoolPath, rewardToken stri // also refund deposit gns amount gns.Transfer(a2u(ictv.refundee), ictv.depositGnsAmount) - pool.incentives.remove(ictv) - prevAddr, prevRealm := getPrev() std.Emit( "EndExternalIncentive", diff --git a/staker/type.gno b/staker/type.gno index e39f877a2..c6adb6432 100644 --- a/staker/type.gno +++ b/staker/type.gno @@ -6,27 +6,7 @@ import ( u256 "gno.land/p/gnoswap/uint256" ) -type InternalTier struct { - tier uint64 // internal reward tier - startTimestamp int64 // start time for internal reward -} - -func (i InternalTier) Tier() uint64 { - return i.tier -} - -func (i InternalTier) StartTimestamp() int64 { - return i.startTimestamp -} - -// newInternalTier creates a new internal tier -func newInternalTier(tier uint64, startTimestamp int64) InternalTier { - return InternalTier{ - tier: tier, - startTimestamp: startTimestamp, - } -} - +// ExternalIncentive is a struct for storing external incentive information. type ExternalIncentive struct { incentiveId string // incentive id startTimestamp int64 // start time for external reward @@ -41,6 +21,8 @@ type ExternalIncentive struct { endHeight int64 // end height for external reward rewardPerBlock uint64 // reward per block refundee std.Address // refundee address + + unclaimableRefunded bool // whether unclaimable reward is refunded } func (e ExternalIncentive) StartTimestamp() int64 { @@ -107,15 +89,12 @@ func NewExternalIncentive( incentiveDuration := endTimestamp - startTimestamp incentiveBlock := incentiveDuration * 1000 / msPerBlock rewardPerBlock := rewardAmount / uint64(incentiveBlock) - println("rewardPerBlock", rewardPerBlock) blocksLeftUntilStartHeight := (startTimestamp - currentTime) * 1000 / msPerBlock blocksLeftUntilEndHeight := (endTimestamp - currentTime) * 1000 / msPerBlock startHeight := std.GetHeight() + blocksLeftUntilStartHeight - println("startHeight", startHeight) endHeight := std.GetHeight() + blocksLeftUntilEndHeight - println("endHeight", endHeight) return &ExternalIncentive{ incentiveId: incentiveId, @@ -130,6 +109,7 @@ func NewExternalIncentive( refundee: refundee, createdHeight: createdHeight, depositGnsAmount: depositGnsAmount, + unclaimableRefunded: false, } } @@ -141,6 +121,7 @@ type Deposit struct { tickLower int32 // tick lower tickUpper int32 // tick upper liquidity *u256.Uint // liquidity - lastCollectHeight uint64 // last collect block height - warmups []Warmup // warmup information + lastCollectHeight int64 // last collect block height + warmups []Warmup // warmup information } +