From 7136488f72d59a665c93ac971cd0ec4db95f5c6b Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Thu, 9 Jan 2025 21:20:21 +0900 Subject: [PATCH] paritally fix: `CollectRewardByProjectId` -- update `RewardState` --- launchpad/deposit.gno | 17 ++++++++++-- launchpad/json_builder.gno | 4 +-- launchpad/json_builder_test.gno | 4 +-- launchpad/launchpad.gno | 11 ++++++-- launchpad/reward.gno | 37 +++++++++++++++---------- launchpad/reward_calculation.gno | 47 ++++++++++++++++++++------------ launchpad/reward_test.gno | 36 +++++++++++------------- launchpad/type.gno | 2 +- 8 files changed, 96 insertions(+), 62 deletions(-) diff --git a/launchpad/deposit.gno b/launchpad/deposit.gno index 1971a03e..85cb91ff 100644 --- a/launchpad/deposit.gno +++ b/launchpad/deposit.gno @@ -167,7 +167,14 @@ func processFirstDeposit(info ProjectTierInfo) (Tier, error) { tier := info.Tier tier.started.height = info.Height tier.started.time = info.CurrentTime - tier.tierAmountPerBlockX96 = calcProjectTiersRewardPerBlockX96(tier) + tier.tierAmountPerBlockX128 = calcProjectTiersRewardPerBlockX128(tier) + + // should be update reward state here + rewardState := NewRewardState(tier.tierAmountPerBlockX128, info.Height, info.Tier.ended.height) + rewardStates.Set(info.Project.id, info.TierStr, rewardState) + + // Remove from projectTiersWithoutDeposit as it now has a deposit + delete(projectTiersWithoutDeposit, tier.id) return tier, nil } @@ -259,7 +266,7 @@ func DepositGns(targetProjectTierId string, amount uint64) string { "depositId", deposit.id, "claimableHeight", strconv.FormatUint(deposit.claimableHeight, 10), "claimableTime", strconv.FormatUint(deposit.claimableTime, 10), - "tierAmountPerBlockX96", updatedTier.tierAmountPerBlockX96.ToString(), + "tierAmountPerBlockX128", updatedTier.tierAmountPerBlockX128.ToString(), ) } @@ -271,6 +278,12 @@ func DepositGns(targetProjectTierId string, amount uint64) string { project.stats.actualParticipant++ projects[projectId] = project + rewardState, err := rewardStates.Get(projectId, tierStr) + if err != nil { + panic(err) + } + rewardState.AddStake(uint64(std.GetHeight()), deposit.id, amount) + return deposit.id } diff --git a/launchpad/json_builder.gno b/launchpad/json_builder.gno index d7d71657..e674aa93 100644 --- a/launchpad/json_builder.gno +++ b/launchpad/json_builder.gno @@ -37,8 +37,8 @@ func RefundInfoBuilder(b *json.NodeBuilder, info RefundInfo) *json.NodeBuilder { func TierBuilder(b *json.NodeBuilder, prefix string, tier Tier) *json.NodeBuilder { b.WriteString(prefix+"CollectWaitDuration", ufmt.Sprintf("%d", tier.collectWaitDuration)) b.WriteString(prefix+"TierAmount", ufmt.Sprintf("%d", tier.tierAmount)) - if tier.tierAmountPerBlockX96 != nil { - b.WriteString(prefix+"TierAmountPerBlockX96", tier.tierAmountPerBlockX96.ToString()) + if tier.tierAmountPerBlockX128 != nil { + b.WriteString(prefix+"TierAmountPerBlockX128", tier.tierAmountPerBlockX128.ToString()) } TimeInfoBuilder(b, prefix+"Started", tier.started) TimeInfoBuilder(b, prefix+"Ended", tier.ended) diff --git a/launchpad/json_builder_test.gno b/launchpad/json_builder_test.gno index c460d2dd..049dbd50 100644 --- a/launchpad/json_builder_test.gno +++ b/launchpad/json_builder_test.gno @@ -53,7 +53,7 @@ func TestTierBuilder(t *testing.T) { tier := Tier{ collectWaitDuration: 100, tierAmount: 5000, - tierAmountPerBlockX96: amountPerBlock, + tierAmountPerBlockX128: amountPerBlock, started: TimeInfo{height: 100, time: 1234567890}, ended: TimeInfo{height: 200, time: 1234567899}, totalDepositAmount: 1000, @@ -68,7 +68,7 @@ func TestTierBuilder(t *testing.T) { TierBuilder(b, "test", tier) result := b.Node() - expected := `{"testCollectWaitDuration":"100","testTierAmount":"5000","testTierAmountPerBlockX96":"1000000","testStartedHeight":"100","testStartedTime":"1234567890","testEndedHeight":"200","testEndedTime":"1234567899","testTotalDepositAmount":"1000","testActualDepositAmount":"800","testTotalParticipant":"50","testActualParticipant":"40","testUserCollectedAmount":"300","testCalculatedAmount":"400"}` + expected := `{"testCollectWaitDuration":"100","testTierAmount":"5000","testTierAmountPerBlockX128":"1000000","testStartedHeight":"100","testStartedTime":"1234567890","testEndedHeight":"200","testEndedTime":"1234567899","testTotalDepositAmount":"1000","testActualDepositAmount":"800","testTotalParticipant":"50","testActualParticipant":"40","testUserCollectedAmount":"300","testCalculatedAmount":"400"}` res, err := json.Marshal(result) uassert.NoError(t, err) diff --git a/launchpad/launchpad.gno b/launchpad/launchpad.gno index 62900051..43eac4c2 100644 --- a/launchpad/launchpad.gno +++ b/launchpad/launchpad.gno @@ -24,7 +24,7 @@ var ( ) var ( - q96 = u256.Zero() + q128 = u256.Zero() ) const ( @@ -32,7 +32,7 @@ const ( ) func init() { - q96 = u256.MustFromDecimal(consts.Q96) + q128 = u256.MustFromDecimal(consts.Q128) } // ProjectInput represents the input parameters for creating a new project @@ -90,6 +90,13 @@ func createTier(projectId string, duration uint64, amount uint64, startHeight ui }, } + // Calculate reward per block for the tier + tier.tierAmountPerBlockX128 = calcProjectTiersRewardPerBlockX128(tier) + + // Initialize reward state for this tier + rewardState := NewRewardState(tier.tierAmountPerBlockX128, startHeight, endHeight) + rewardStates.Set(projectId, strconv.FormatUint(duration, 10), rewardState) + projectTiersWithoutDeposit[tierId] = true return tier } diff --git a/launchpad/reward.gno b/launchpad/reward.gno index 7eb3e6ff..7d4612b0 100644 --- a/launchpad/reward.gno +++ b/launchpad/reward.gno @@ -47,11 +47,11 @@ func validateRewardCollection(deposit Deposit, height uint64) error { // calculateTierRewards calculates rewards for each tier func calculateTierRewards(tier Tier, currentHeight uint64, lastCalcHeight uint64) (*u256.Uint, uint64, error) { - if tier.tierAmountPerBlockX96 == nil { + if tier.tierAmountPerBlockX128 == nil { return u256.Zero(), 0, nil } - if tier.tierAmountPerBlockX96.IsZero() { + if tier.tierAmountPerBlockX128.IsZero() { return u256.Zero(), 0, nil } @@ -62,10 +62,10 @@ func calculateTierRewards(tier Tier, currentHeight uint64, lastCalcHeight uint64 return u256.Zero(), 0, nil } - rewardX96 := new(u256.Uint).Mul(tier.tierAmountPerBlockX96.NilToZero().Clone(), u256.NewUint(sinceLast)) - reward := new(u256.Uint).Div(rewardX96, q96).Uint64() + rewardX128 := new(u256.Uint).Mul(tier.tierAmountPerBlockX128.NilToZero().Clone(), u256.NewUint(sinceLast)) + reward := new(u256.Uint).Div(rewardX128, q128).Uint64() - return rewardX96, reward, nil + return rewardX128, reward, nil } /* @@ -94,12 +94,14 @@ func CollectRewardByProjectId(projectId string) uint64 { project, exists := projects[projectId] if !exists { + println("A: return 0") return 0 } caller := std.PrevRealm().Addr() depositIds, exists := depositsByUserByProject[caller][projectId] if !exists { + println("B: return 0") return 0 } @@ -110,24 +112,29 @@ func CollectRewardByProjectId(projectId string) uint64 { for _, depositId := range depositIds { deposit, exists := deposits[depositId] if !exists { + println("C: continue") continue } rewardState, err := rewardStates.Get(deposit.projectId, deposit.tier) if err != nil { + println("D: continue") continue } reward := rewardState.Claim(depositId, height) if reward == 0 { + println("E: continue") continue } project := projects[deposit.projectId] if project.id != projectId { + println("F: continue") continue } if err := validateRewardCollection(deposit, height); err != nil { + println("G: continue") continue } @@ -354,22 +361,22 @@ func calculateDepositReward() { } } */ -// calcDepositRatioX96 calculates the deposit ratio with Q96 precision -func calcDepositRatioX96(tierAmount uint64, amount uint64) *u256.Uint { - amountX96 := new(u256.Uint).Mul(u256.NewUint(amount), q96.Clone()) - amountX96x := new(u256.Uint).Mul(amountX96, u256.NewUint(1_000_000_000)) +// calcDepositRatioX128 calculates the deposit ratio with Q128 precision +func calcDepositRatioX128(tierAmount uint64, amount uint64) *u256.Uint { + amountX128 := new(u256.Uint).Mul(u256.NewUint(amount), q128.Clone()) + amountX128x := new(u256.Uint).Mul(amountX128, u256.NewUint(1_000_000_000)) - tierAmountX96 := new(u256.Uint).Mul(u256.NewUint(tierAmount), q96.Clone()) + tierAmountX128 := new(u256.Uint).Mul(u256.NewUint(tierAmount), q128.Clone()) - depositRatioX96 := new(u256.Uint).Div(amountX96x, tierAmountX96) - depositRatioX96 = depositRatioX96.Mul(depositRatioX96, q96.Clone()) + depositRatioX96 := new(u256.Uint).Div(amountX128x, tierAmountX128) + depositRatioX96 = depositRatioX96.Mul(depositRatioX96, q128.Clone()) depositRatioX96 = depositRatioX96.Div(depositRatioX96, u256.NewUint(1_000_000_000)) return depositRatioX96 } // calcProjectTiersRewardPerBlockX96 calculates the reward per block with Q96 precision -func calcProjectTiersRewardPerBlockX96(tier Tier) *u256.Uint { - tierAmountX96 := new(u256.Uint).Mul(u256.NewUint(tier.tierAmount), q96) - return new(u256.Uint).Div(tierAmountX96, u256.NewUint(tier.ended.height-tier.started.height)) +func calcProjectTiersRewardPerBlockX128(tier Tier) *u256.Uint { + tierAmountX128 := new(u256.Uint).Mul(u256.NewUint(tier.tierAmount), q128) + return new(u256.Uint).Div(tierAmountX128, u256.NewUint(tier.ended.height-tier.started.height)) } diff --git a/launchpad/reward_calculation.gno b/launchpad/reward_calculation.gno index 2711098c..1c4a4186 100644 --- a/launchpad/reward_calculation.gno +++ b/launchpad/reward_calculation.gno @@ -9,7 +9,7 @@ import ( type StakerRewardInfo struct { StartHeight uint64 // height when staker started staking - PriceDebt *u256.Uint // price debt per xGNS stake, Q96 + PriceDebt *u256.Uint // price debt per xGNS stake, Q128 Amount uint64 // amount of xGNS staked Claimed uint64 // amount of GNS reward claimed so far } @@ -19,15 +19,15 @@ func (self *StakerRewardInfo) Debug() string { } func (self *StakerRewardInfo) PriceDebtUint64() uint64 { - return u256.Zero().Rsh(self.PriceDebt, 96).Uint64() + return u256.Zero().Rsh(self.PriceDebt, 128).Uint64() } type RewardState struct { - PriceAccumulation *u256.Uint // claimable Launchpad reward per xGNS stake, Q96 + PriceAccumulation *u256.Uint // claimable Launchpad reward per xGNS stake, Q128 TotalStake uint64 // total xGNS staked LastHeight uint64 // last height when reward was calculated - RewardPerBlock *u256.Uint // reward per block, = Tier.tierAmountPerBlockX96 + RewardPerBlock *u256.Uint // reward per block, = Tier.tierAmountPerBlockX128 EndHeight uint64 TotalEmptyBlock uint64 @@ -46,6 +46,18 @@ func NewRewardState(rewardPerBlock *u256.Uint, startHeight uint64, endHeight uin } } +func (self *RewardState) PriceAccumulationUint64() uint64 { + return u256.Zero().Rsh(self.PriceAccumulation, 128).Uint64() +} + +func (self *RewardState) RewardPerBlockUint64() uint64 { + return u256.Zero().Rsh(self.RewardPerBlock, 128).Uint64() +} + +func (self *RewardState) Debug() string { + return ufmt.Sprintf("{ PriceAccumulation: %d, TotalStake: %d, LastHeight: %d, RewardPerBlock: %d, EndHeight: %d, info: len(%d) }", self.PriceAccumulationUint64(), self.TotalStake, self.LastHeight, self.RewardPerBlockUint64(), self.EndHeight, self.info.Size()) +} + type RewardStates struct { states *avl.Tree // projectId:tier string -> RewardState } @@ -63,7 +75,9 @@ func (states RewardStates) Get(projectId string, tierStr string) (*RewardState, return statesI.(*RewardState), nil } +/// XXXX func (states RewardStates) Set(projectId string, tierStr string, state *RewardState) { + println("pass set") key := projectId + ":" + tierStr states.states.Set(key, state) } @@ -91,14 +105,6 @@ func (states RewardStates) DeleteProject(projectId string) uint64 { return totalLeftover } -func (self *RewardState) Debug() string { - return ufmt.Sprintf("{ PriceAccumulation: %d, TotalStake: %d, LastHeight: %d, RewardPerBlock: %d, EndHeight: %d, info: len(%d) }", self.PriceAccumulationUint64(), self.TotalStake, self.LastHeight, self.RewardPerBlockUint64(), self.EndHeight, self.info.Size()) -} - -func (self *RewardState) RewardPerBlockUint64() uint64 { - return u256.Zero().Rsh(self.RewardPerBlock, 96).Uint64() -} - func (self *RewardState) Info(depositId string) StakerRewardInfo { infoI, exists := self.info.Get(depositId) if !exists { @@ -111,21 +117,18 @@ func (self *RewardState) CalculateReward(depositId string) uint64 { info := self.Info(depositId) stakerPrice := u256.Zero().Sub(self.PriceAccumulation, info.PriceDebt) reward := stakerPrice.Mul(stakerPrice, u256.NewUint(info.Amount)) - reward = reward.Rsh(reward, 96) + reward = reward.Rsh(reward,128) return reward.Uint64() - info.Claimed } -func (self *RewardState) PriceAccumulationUint64() uint64 { - return u256.Zero().Rsh(self.PriceAccumulation, 96).Uint64() -} - // amount MUST be less than or equal to the amount of xGNS staked // This function does not check it func (self *RewardState) deductReward(depositId string, currentHeight uint64) uint64 { + println("deductReward debug", self.Debug()) info := self.Info(depositId) stakerPrice := u256.Zero().Sub(self.PriceAccumulation, info.PriceDebt) reward := stakerPrice.Mul(stakerPrice, u256.NewUint(info.Amount)) - reward = reward.Rsh(reward, 96) + reward = reward.Rsh(reward, 128) reward64 := reward.Uint64() - info.Claimed info.Claimed += reward64 @@ -143,10 +146,15 @@ func (self *RewardState) finalize(currentHeight uint64) { // Not started yet return } + if self.LastHeight >= self.EndHeight { + // already ended + return + } if currentHeight > self.EndHeight { currentHeight = self.EndHeight } + // ????? height 차이에 의한 오차 고 delta := u256.NewUint(currentHeight - self.LastHeight) delta = delta.Mul(delta, self.RewardPerBlock) @@ -187,11 +195,14 @@ func (self *RewardState) AddStake(currentHeight uint64, depositId string, amount Claimed: 0, } + println("addstake info", info.Debug()) + self.info.Set(depositId, info) } func (self *RewardState) Claim(depositId string, currentHeight uint64) uint64 { if !self.info.Has(depositId) { + println("claim debug", self.Debug()) return 0 } diff --git a/launchpad/reward_test.gno b/launchpad/reward_test.gno index 1b731c6d..e33520a5 100644 --- a/launchpad/reward_test.gno +++ b/launchpad/reward_test.gno @@ -60,8 +60,8 @@ func TestValidateRewardCollection(t *testing.T) { } } -func TestCalculateDepositRatioX96(t *testing.T) { - q96 = u256.MustFromDecimal("79228162514264337593543950336") +func TestCalculateDepositRatioX128(t *testing.T) { + q128 = u256.MustFromDecimal("18446744073709551616") tests := []struct { name string @@ -97,7 +97,7 @@ func TestCalculateDepositRatioX96(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ratio := calcDepositRatioX96(tt.tierAmount, tt.amount) + ratio := calcDepositRatioX128(tt.tierAmount, tt.amount) expected := u256.MustFromDecimal(tt.expected) uassert.Equal(t, 0, ratio.Cmp(expected)) }) @@ -105,12 +105,10 @@ func TestCalculateDepositRatioX96(t *testing.T) { } func TestProcessDepositReward(t *testing.T) { - q96 = u256.MustFromDecimal("79228162514264337593543950336") - tests := []struct { name string deposit Deposit - rewardX96 *u256.Uint + rewardX128 *u256.Uint tierAmount uint64 expectedReward uint64 shouldError bool @@ -121,7 +119,7 @@ func TestProcessDepositReward(t *testing.T) { amount: 1000, //rewardAmount: 0, }, - rewardX96: u256.NewUint(1000).Mul(u256.NewUint(1000), q96), + rewardX128: u256.NewUint(1000).Mul(u256.NewUint(1000), q128), tierAmount: 2000, expectedReward: 500, // (1000/2000) * 1000 = 500 shouldError: false, @@ -144,7 +142,7 @@ func TestProcessDepositReward(t *testing.T) { amount: 1000, //rewardAmount: 100, }, - rewardX96: u256.NewUint(1000).Mul(u256.NewUint(1000), q96), + rewardX128: u256.NewUint(1000).Mul(u256.NewUint(1000), q128), tierAmount: 2000, expectedReward: 500, // 600, // Existing 100 + New reward 500 shouldError: false, @@ -153,7 +151,7 @@ func TestProcessDepositReward(t *testing.T) { for i, tt := range tests { t.Run(tt.name, func(t *testing.T) { - state := NewRewardState(tt.rewardX96, 1000, 2000) + state := NewRewardState(tt.rewardX128 , 1000, 2000) depositId := ufmt.Sprintf("depositId-%d", i) state.AddStake(1000, depositId, tt.deposit.amount) state.TotalStake = tt.tierAmount // force overriding total stake @@ -166,21 +164,19 @@ func TestProcessDepositReward(t *testing.T) { } func TestCalculateTierRewards(t *testing.T) { - q96 = u256.MustFromDecimal("79228162514264337593543950336") - tests := []struct { name string tier Tier currentHeight uint64 lastCalcHeight uint64 - expectedRewardX96 string + expectedRewardX128 string expectedReward uint64 shouldBeZero bool }{ { name: "tierAmountPerBlockX96 is 0", tier: Tier{ - tierAmountPerBlockX96: u256.Zero(), + tierAmountPerBlockX128: u256.Zero(), ended: TimeInfo{height: 1000}, }, currentHeight: 500, @@ -190,7 +186,7 @@ func TestCalculateTierRewards(t *testing.T) { { name: "sinceLast is 0", tier: Tier{ - tierAmountPerBlockX96: u256.NewUint(1000), + tierAmountPerBlockX128: u256.NewUint(1000), ended: TimeInfo{height: 1000}, }, currentHeight: 500, @@ -200,28 +196,28 @@ func TestCalculateTierRewards(t *testing.T) { { name: "Current height exceeds end height", tier: Tier{ - tierAmountPerBlockX96: u256.NewUint(1000), + tierAmountPerBlockX128: u256.NewUint(1000), ended: TimeInfo{height: 450}, }, currentHeight: 500, lastCalcHeight: 400, - expectedRewardX96: "50000", // 1000 * (450-400) + expectedRewardX128: "50000", // 1000 * (450-400) expectedReward: 0, // (50000 / Q96) }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rewardX96, reward, err := calculateTierRewards(tt.tier, tt.currentHeight, tt.lastCalcHeight) + rewardX128, reward, err := calculateTierRewards(tt.tier, tt.currentHeight, tt.lastCalcHeight) uassert.NoError(t, err) if tt.shouldBeZero { - uassert.Equal(t, true, rewardX96.IsZero()) + uassert.Equal(t, true, rewardX128.IsZero()) uassert.Equal(t, uint64(0), reward) } else { - expected := u256.MustFromDecimal(tt.expectedRewardX96) - uassert.Equal(t, 0, rewardX96.Cmp(expected)) + expected := u256.MustFromDecimal(tt.expectedRewardX128) + uassert.Equal(t, 0, rewardX128.Cmp(expected)) uassert.Equal(t, tt.expectedReward, reward) } }) diff --git a/launchpad/type.gno b/launchpad/type.gno index c3c1c36b..55a9ad7c 100644 --- a/launchpad/type.gno +++ b/launchpad/type.gno @@ -49,7 +49,7 @@ type Tier struct { id string // '{projectId}:duration' // duartion == 30, 90, 180 collectWaitDuration uint64 // block tierAmount uint64 - tierAmountPerBlockX96 *u256.Uint + tierAmountPerBlockX128 *u256.Uint started TimeInfo // first deposit height ended TimeInfo