|
2 | 2 | package launchpad
|
3 | 3 |
|
4 | 4 | import (
|
5 |
| - "gno.land/p/demo/avl" |
6 |
| - "gno.land/p/demo/ufmt" |
7 |
| - u256 "gno.land/p/gnoswap/uint256" |
| 5 | + ppad "gno.land/p/gnoswap/v1/launchpad" |
8 | 6 | )
|
9 | 7 |
|
10 |
| -type StakerRewardInfo struct { |
11 |
| - StartHeight uint64 // height when launchpad started staking |
12 |
| - PriceDebt *u256.Uint // price debt per GNS stake, Q128 |
13 |
| - Amount uint64 // amount of GNS staked |
14 |
| - Claimed uint64 // amount of reward claimed so far |
15 |
| -} |
16 |
| - |
17 |
| -func (self *StakerRewardInfo) Debug() string { |
18 |
| - return ufmt.Sprintf("{ StartHeight: %d, PriceDebt: %d, Amount: %d, Claimed: %d }", self.StartHeight, self.PriceDebtUint64(), self.Amount, self.Claimed) |
19 |
| -} |
20 |
| - |
21 |
| -func (self *StakerRewardInfo) PriceDebtUint64() uint64 { |
22 |
| - return u256.Zero().Rsh(self.PriceDebt, 128).Uint64() |
23 |
| -} |
24 |
| - |
25 |
| -type RewardState struct { |
26 |
| - PriceAccumulation *u256.Uint // claimable Launchpad reward per xGNS stake, Q128 |
27 |
| - TotalStake uint64 // total xGNS staked |
28 |
| - |
29 |
| - LastHeight uint64 // last height when reward was calculated |
30 |
| - RewardPerBlock *u256.Uint // reward per block, = Tier.tierAmountPerBlockX128 |
31 |
| - EndHeight uint64 |
32 |
| - |
33 |
| - TotalEmptyBlock uint64 |
34 |
| - |
35 |
| - info *avl.Tree // depositId -> StakerRewardInfo |
36 |
| -} |
37 |
| - |
38 |
| -func NewRewardState(rewardPerBlock *u256.Uint, startHeight uint64, endHeight uint64) *RewardState { |
39 |
| - return &RewardState{ |
40 |
| - PriceAccumulation: u256.Zero(), |
41 |
| - TotalStake: 0, |
42 |
| - LastHeight: startHeight, |
43 |
| - RewardPerBlock: rewardPerBlock, |
44 |
| - EndHeight: endHeight, |
45 |
| - info: avl.NewTree(), |
46 |
| - } |
47 |
| -} |
48 |
| - |
49 |
| -func (self *RewardState) PriceAccumulationUint64() uint64 { |
50 |
| - return u256.Zero().Rsh(self.PriceAccumulation, 128).Uint64() |
51 |
| -} |
52 |
| - |
53 |
| -func (self *RewardState) RewardPerBlockUint64() uint64 { |
54 |
| - return u256.Zero().Rsh(self.RewardPerBlock, 128).Uint64() |
55 |
| -} |
56 |
| - |
57 |
| -func (self *RewardState) Debug() string { |
58 |
| - 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()) |
59 |
| -} |
60 |
| - |
61 |
| -type RewardStates struct { |
62 |
| - states *avl.Tree // projectId:tier string -> RewardState |
63 |
| -} |
64 |
| - |
65 |
| -var rewardStates = RewardStates{ |
66 |
| - states: avl.NewTree(), |
67 |
| -} |
68 |
| - |
69 |
| -func (states RewardStates) Get(projectId string, tierStr string) (*RewardState, error) { |
70 |
| - key := projectId + ":" + tierStr |
71 |
| - statesI, exists := states.states.Get(key) |
72 |
| - if !exists { |
73 |
| - return nil, ufmt.Errorf("reward state not found for projectId %s and tierStr %s", projectId, tierStr) |
74 |
| - } |
75 |
| - return statesI.(*RewardState), nil |
76 |
| -} |
77 |
| - |
78 |
| -func (states RewardStates) Set(projectId string, tierStr string, state *RewardState) { |
79 |
| - key := projectId + ":" + tierStr |
80 |
| - states.states.Set(key, state) |
81 |
| -} |
82 |
| - |
83 |
| -func (states RewardStates) DeleteProject(projectId string) uint64 { |
84 |
| - totalLeftover := uint64(0) |
85 |
| - keys := []string{} |
86 |
| - states.states.Iterate(projectId+":", projectId+";", func(key string, value interface{}) bool { |
87 |
| - state := value.(*RewardState) |
88 |
| - totalEmptyBlock := state.TotalEmptyBlock |
89 |
| - if state.TotalStake == 0 { |
90 |
| - totalEmptyBlock += state.EndHeight - state.LastHeight |
91 |
| - } |
92 |
| - totalEmptyBlockU256 := u256.NewUint(totalEmptyBlock) |
93 |
| - totalEmptyRewards := u256.Zero().Mul(totalEmptyBlockU256, state.RewardPerBlock) |
94 |
| - totalLeftover += totalEmptyRewards.Uint64() |
95 |
| - keys = append(keys, key) |
96 |
| - return false |
97 |
| - }) |
98 |
| - |
99 |
| - for _, key := range keys { |
100 |
| - states.states.Remove(key) |
101 |
| - } |
102 |
| - |
103 |
| - return totalLeftover |
104 |
| -} |
105 |
| - |
106 |
| -func (self *RewardState) Info(depositId string) StakerRewardInfo { |
107 |
| - infoI, exists := self.info.Get(depositId) |
108 |
| - if !exists { |
109 |
| - panic(addDetailToError( |
110 |
| - errNotExistDeposit, ufmt.Sprintf("(%s)", depositId))) |
111 |
| - } |
112 |
| - return infoI.(StakerRewardInfo) |
113 |
| -} |
114 |
| - |
115 |
| -func (self *RewardState) CalculateReward(depositId string) uint64 { |
116 |
| - info := self.Info(depositId) |
117 |
| - stakerPrice := u256.Zero().Sub(self.PriceAccumulation, info.PriceDebt) |
118 |
| - reward := stakerPrice.Mul(stakerPrice, u256.NewUint(info.Amount)) |
119 |
| - reward = reward.Rsh(reward, 128) |
120 |
| - return reward.Uint64() - info.Claimed |
121 |
| -} |
122 |
| - |
123 |
| -// amount MUST be less than or equal to the amount of xGNS staked |
124 |
| -// This function does not check it |
125 |
| -func (self *RewardState) deductReward(depositId string, currentHeight uint64) uint64 { |
126 |
| - if currentHeight < self.LastHeight { |
127 |
| - panic(addDetailToError( |
128 |
| - errInvalidRewardState, |
129 |
| - ufmt.Sprintf("currentHeight %d is less than LastHeight %d", currentHeight, self.LastHeight))) |
130 |
| - } |
131 |
| - |
132 |
| - deposit := deposits[depositId] |
133 |
| - if deposit.claimableHeight > currentHeight { |
134 |
| - panic(addDetailToError( |
135 |
| - errInvalidRewardState, |
136 |
| - ufmt.Sprintf("currentHeight %d is less than claimableHeight %d", currentHeight, deposit.claimableHeight))) |
137 |
| - } |
138 |
| - |
139 |
| - info := self.Info(depositId) |
140 |
| - if info.StartHeight > currentHeight { |
141 |
| - panic(addDetailToError( |
142 |
| - errInvalidRewardState, |
143 |
| - ufmt.Sprintf("currentHeight %d is less than StartHeight %d", currentHeight, info.StartHeight))) |
144 |
| - } |
145 |
| - reward64 := self.CalculateReward(depositId) |
146 |
| - |
147 |
| - if reward64 == 0 { |
148 |
| - return 0 |
149 |
| - } |
150 |
| - |
151 |
| - info.Claimed += reward64 |
152 |
| - self.info.Set(depositId, info) |
153 |
| - |
154 |
| - return reward64 |
155 |
| -} |
156 |
| - |
157 |
| -// This function MUST be called as a part of AddStake or RemoveStake |
158 |
| -// CurrentBalance / StakeChange / IsRemoveStake will be updated in those functions |
159 |
| -func (self *RewardState) finalize(currentHeight uint64) { |
160 |
| - if currentHeight <= self.LastHeight { |
161 |
| - // Not started yet |
162 |
| - return |
163 |
| - } |
164 |
| - if self.LastHeight >= self.EndHeight { |
165 |
| - // already ended |
166 |
| - return |
167 |
| - } |
168 |
| - if currentHeight > self.EndHeight { |
169 |
| - currentHeight = self.EndHeight |
170 |
| - } |
171 |
| - |
172 |
| - diff := currentHeight - self.LastHeight |
173 |
| - delta := u256.NewUint(diff) |
174 |
| - delta = delta.Mul(delta, self.RewardPerBlock) |
175 |
| - |
176 |
| - if self.TotalStake == uint64(0) { |
177 |
| - self.TotalEmptyBlock += diff |
178 |
| - self.LastHeight = currentHeight |
179 |
| - return |
180 |
| - } |
181 |
| - |
182 |
| - price := delta.Div(delta, u256.NewUint(self.TotalStake)) |
183 |
| - self.PriceAccumulation = u256.Zero().Add(self.PriceAccumulation, price) |
184 |
| - self.LastHeight = currentHeight |
185 |
| -} |
186 |
| - |
187 |
| -func (self *RewardState) AddStake(currentHeight uint64, depositId string, amount uint64) { |
188 |
| - if self.info.Has(depositId) { |
189 |
| - panic(addDetailToError( |
190 |
| - errAlreadyExistDeposit, |
191 |
| - ufmt.Sprintf("depositId %s already exists", depositId))) |
192 |
| - } |
193 |
| - |
194 |
| - self.finalize(currentHeight) |
195 |
| - |
196 |
| - self.TotalStake += amount |
197 |
| - |
198 |
| - if self.info.Has(depositId) { |
199 |
| - info := self.Info(depositId) |
200 |
| - info.PriceDebt.Add(info.PriceDebt, u256.NewUint(info.Amount)) |
201 |
| - info.PriceDebt.Add(info.PriceDebt, u256.Zero().Mul(self.PriceAccumulation, u256.NewUint(amount))) |
202 |
| - info.PriceDebt.Div(info.PriceDebt, u256.NewUint(self.TotalStake)) |
203 |
| - info.Amount += amount |
204 |
| - self.info.Set(depositId, info) |
205 |
| - return |
206 |
| - } |
207 |
| - |
208 |
| - info := StakerRewardInfo{ |
209 |
| - StartHeight: currentHeight, |
210 |
| - PriceDebt: self.PriceAccumulation.Clone(), |
211 |
| - Amount: amount, |
212 |
| - Claimed: 0, |
213 |
| - } |
214 |
| - |
215 |
| - self.info.Set(depositId, info) |
216 |
| -} |
217 |
| - |
218 |
| -func (self *RewardState) Claim(depositId string, currentHeight uint64) uint64 { |
219 |
| - if !self.info.Has(depositId) { |
220 |
| - panic(addDetailToError( |
221 |
| - errNotExistDeposit, |
222 |
| - ufmt.Sprintf("depositId %s", depositId))) |
223 |
| - } |
224 |
| - |
225 |
| - self.finalize(currentHeight) |
226 |
| - |
227 |
| - reward := self.deductReward(depositId, currentHeight) |
228 |
| - |
229 |
| - return reward |
230 |
| -} |
231 |
| - |
232 |
| -func (self *RewardState) RemoveStake(depositId string, amount uint64, currentHeight uint64) uint64 { |
233 |
| - if !self.info.Has(depositId) { |
234 |
| - panic(addDetailToError( |
235 |
| - errNotExistDeposit, |
236 |
| - ufmt.Sprintf("depositId %s", depositId))) |
237 |
| - } |
238 |
| - |
239 |
| - self.finalize(currentHeight) |
240 |
| - |
241 |
| - reward := self.deductReward(depositId, currentHeight) |
242 |
| - |
243 |
| - self.info.Remove(depositId) |
244 |
| - |
245 |
| - self.TotalStake -= amount |
246 |
| - |
247 |
| - return reward |
248 |
| -} |
| 8 | +var rewardStates = ppad.NewRewardStates() |
0 commit comments