Kind Realm (renderable)
Name emission
Namespace gnoswap
Exported functions n/a — Gnoland RPC unavailable (functions): rpc abci_query: HTTP 500
Module gno.land/r/gnoswap/emission
gno 0.9 distribution.gno gno
⧉
1 package emission 2 3 import ( 4 "chain" 5 "time" 6 7 ufmt "gno.land/p/nt/ufmt/v0" 8 Gnoland RPC unavailable (functions): rpc abci_query: HTTP 500
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.
9 gnsmath "gno.land/p/gnoswap/gnsmath/v1"
10 prbac "gno.land/p/gnoswap/rbac/v1"
11 "gno.land/p/gnoswap/utils/v1"
12
13 "gno.land/r/gnoswap/access/v1"
14 "gno.land/r/gnoswap/gns"
15 "gno.land/r/gnoswap/halt/v1"
16 )
17
18 const (
19 _ int = iota
20 LIQUIDITY_STAKER
21 DEVOPS
22 COMMUNITY_POOL
23 GOV_STAKER
24 )
25
26 var (
27 // Stores the percentage (in basis points) for each distribution target
28 // 1 basis point = 0.01%
29 // These percentages can be modified by admin or governance.
30 distributionBpsPct map [ int ] int64
31
32 distributedToStaker int64 // can be cleared by staker contract
33 distributedToDevOps int64
34 distributedToCommunityPool int64
35 distributedToGovStaker int64 // can be cleared by governance staker
36
37 // Historical total distributions (never reset)
38 accuDistributedToStaker int64
39 accuDistributedToDevOps int64
40 accuDistributedToCommunityPool int64
41 accuDistributedToGovStaker int64
42 )
43
44 // Initialize default distribution percentages:
45 // - Liquidity Stakers: 75%
46 // - DevOps: 20%
47 // - Community Pool: 5%
48 // - Governance Stakers: 0%
49 //
50 // ref: https://docs.gnoswap.io/gnoswap-token/emission
51 func init() {
52 distributionBpsPct = map [ int ] int64 {
53 LIQUIDITY_STAKER: 7500 ,
54 DEVOPS: 2000 ,
55 COMMUNITY_POOL: 500 ,
56 GOV_STAKER: 0 ,
57 }
58 }
59
60 // ChangeDistributionPct changes distribution percentages for emission targets.
61 //
62 // This function redistributes how newly minted GNS tokens are allocated across
63 // protocol components. Before applying new ratios, it distributes any accumulated
64 // emissions using the current ratios, ensuring emissions are distributed according
65 // to the ratios in effect when they were generated. This prevents retroactive
66 // application of new ratios to past emissions.
67 //
68 // Parameters:
69 // - cur: Current realm context; callers use cross(cur) when crossing into this realm.
70 // - liquidityStakerPct: percentage for liquidity stakers in basis points (100 = 1%, 10000 = 100%).
71 // - devOpsPct: percentage for DevOps in basis points (100 = 1%, 10000 = 100%).
72 // - communityPoolPct: percentage for the community pool in basis points (100 = 1%, 10000 = 100%).
73 // - govStakerPct: percentage for governance stakers in basis points (100 = 1%, 10000 = 100%).
74 //
75 // Requirements:
76 // - Percentages must sum to exactly 10000 (100%).
77 // - Each percentage must be between 0 and 10000 inclusive.
78 //
79 // Example:
80 //
81 // ChangeDistributionPct(
82 // 7000, // 70% to liquidity stakers
83 // 2000, // 20% to devops
84 // 1000, // 10% to community pool
85 // 0, // 0% to governance stakers
86 // )
87 //
88 // Only callable by admin or governance.
89 func ChangeDistributionPct(
90 cur realm,
91 liquidityStakerPct int64 ,
92 devOpsPct int64 ,
93 communityPoolPct int64 ,
94 govStakerPct int64 ,
95 ) {
96 halt.AssertIsNotHaltedEmission()
97
98 caller := cur.Previous().Address()
99 access.AssertIsAdminOrGovernance(caller)
100
101 assertValidDistributionPct(liquidityStakerPct, devOpsPct, communityPoolPct, govStakerPct)
102
103 // Distribute accumulated emissions with current ratios before changing ratios.
104 // This prevents retroactive application of new ratios to emissions that occurred
105 // under previous ratio configurations.
106 MintAndDistributeGns(cur)
107
108 currentTimestamp := time.Now().Unix()
109 stakerRewardPerSecond := GetEmissionAmountPerSecondBy(currentTimestamp, liquidityStakerPct)
110 govStakerRewardPerSecond := GetEmissionAmountPerSecondBy(currentTimestamp, govStakerPct)
111
112 if onDistributionPctChangeCallback != nil {
113 onDistributionPctChangeCallback(cross(cur), stakerRewardPerSecond)
114 }
115
116 changeDistributionPcts(liquidityStakerPct, devOpsPct, communityPoolPct, govStakerPct)
117
118 previousRealm := cur.Previous()
119 chain.Emit(
120 "ChangeDistributionPct" ,
121 "prevAddr" , previousRealm.Address().String(),
122 "prevRealm" , previousRealm.PkgPath(),
123 "liquidityStakerPct" , utils.FormatInt(liquidityStakerPct),
124 "devOpsPct" , utils.FormatInt(devOpsPct),
125 "communityPoolPct" , utils.FormatInt(communityPoolPct),
126 "govStakerPct" , utils.FormatInt(govStakerPct),
127 "stakerRewardPerSecond" , utils.FormatInt(stakerRewardPerSecond),
128 "govStakerRewardPerSecond" , utils.FormatInt(govStakerRewardPerSecond),
129 )
130 }
131
132 // changeDistributionPcts updates the distribution percentages for all targets.
133 func changeDistributionPcts(liquidityStakerPct, devOpsPct, communityPoolPct, govStakerPct int64 ) {
134 setDistributionBpsPct(LIQUIDITY_STAKER, liquidityStakerPct)
135 setDistributionBpsPct(DEVOPS, devOpsPct)
136 setDistributionBpsPct(COMMUNITY_POOL, communityPoolPct)
137 setDistributionBpsPct(GOV_STAKER, govStakerPct)
138 }
139
140 func calculateDistributableAmounts(amount int64 ) ( map [ int ] int64 , int64 ) {
141 distributable := make( map [ int ] int64 , 0 )
142 totalSent := int64 ( 0 )
143
144 for target, pct := range distributionBpsPct {
145 distAmount := calculateAmount(amount, pct)
146 if distAmount == 0 {
147 continue
148 }
149
150 distributable[target] = distAmount
151 totalSent = gnsmath.SafeAddInt64(totalSent, distAmount)
152 }
153
154 leftAmount := gnsmath.SafeSubInt64(amount, totalSent)
155 return distributable, leftAmount
156 }
157
158 // calculateAmount converts basis points to actual token amount.
159 func calculateAmount(amount, bptPct int64 ) int64 {
160 if amount < 0 || bptPct < 0 || bptPct > 10000 {
161 panic( "invalid amount or bptPct" )
162 }
163
164 // More precise overflow prevention
165 const maxInt64 = 9223372036854775807
166 if amount > maxInt64/ 10000 {
167 panic( "amount too large, would cause overflow" )
168 }
169
170 // Additional safety check for zero division
171 if bptPct == 0 {
172 return 0
173 }
174
175 return amount * bptPct / 10000
176 }
177
178 func applyDistribution(targets map [ int ] int64 ) ( map [address] int64 , error ) {
179 amountByAddress := make( map [address] int64 , 0 )
180
181 for target, amount := range targets {
182 var addr address
183
184 switch target {
185 case LIQUIDITY_STAKER:
186 distributedToStaker = gnsmath.SafeAddInt64(distributedToStaker, amount)
187 accuDistributedToStaker = gnsmath.SafeAddInt64(accuDistributedToStaker, amount)
188 addr = access.MustGetAddress(prbac.ROLE_STAKER.String())
189
190 case DEVOPS:
191 distributedToDevOps = gnsmath.SafeAddInt64(distributedToDevOps, amount)
192 accuDistributedToDevOps = gnsmath.SafeAddInt64(accuDistributedToDevOps, amount)
193 addr = access.MustGetAddress(prbac.ROLE_DEVOPS.String())
194
195 case COMMUNITY_POOL:
196 distributedToCommunityPool = gnsmath.SafeAddInt64(distributedToCommunityPool, amount)
197 accuDistributedToCommunityPool = gnsmath.SafeAddInt64(accuDistributedToCommunityPool, amount)
198 addr = access.MustGetAddress(prbac.ROLE_COMMUNITY_POOL.String())
199
200 case GOV_STAKER:
201 distributedToGovStaker = gnsmath.SafeAddInt64(distributedToGovStaker, amount)
202 accuDistributedToGovStaker = gnsmath.SafeAddInt64(accuDistributedToGovStaker, amount)
203 addr = access.MustGetAddress(prbac.ROLE_GOV_STAKER.String())
204
205 default :
206 return nil , makeErrorWithDetails(
207 errInvalidEmissionTarget,
208 ufmt.Sprintf( "invalid target(%d)" , target),
209 )
210 }
211
212 amountByAddress[addr] = gnsmath.SafeAddInt64(amountByAddress[addr], amount)
213 }
214
215 return amountByAddress, nil
216 }
217
218 func transferToTarget(_ int , rlm realm, targets map [address] int64 ) error {
219 for address, amount := range targets {
220 gns.Transfer(cross(rlm), address, amount)
221 }
222
223 return nil
224 }
225
226 // GetDistributionBpsPct returns the configured distribution percentage in basis points for a target.
227 //
228 // Parameters:
229 // - target: distribution target constant whose configured share is queried.
230 //
231 // Returns:
232 // - pct: configured target share in basis points (100 = 1%, 10000 = 100%).
233 // - error: nil when the target exists in the initialized map; otherwise an invalid-target error.
234 func GetDistributionBpsPct(target int ) ( int64 , error ) {
235 if err := validateDistributionTarget(target); err != nil {
236 return 0 , err
237 }
238
239 if distributionBpsPct == nil {
240 return 0 , makeErrorWithDetails(
241 errInvalidEmissionTarget,
242 ufmt.Sprintf( "distributionBpsPct is nil" ),
243 )
244 }
245
246 pct, exist := distributionBpsPct[target]
247 if !exist {
248 return 0 , makeErrorWithDetails(
249 errInvalidEmissionTarget,
250 ufmt.Sprintf( "invalid target(%d)" , target),
251 )
252 }
253
254 return pct, nil
255 }
256
257 // GetDistributedToStaker returns the pending GNS amount allocated to liquidity stakers.
258 //
259 // Returns:
260 // - amount: pending liquidity-staker allocation in GNS base units since the last clear.
261 func GetDistributedToStaker() int64 {
262 return distributedToStaker
263 }
264
265 // GetDistributedToDevOps returns the GNS amount currently accumulated for DevOps.
266 //
267 // Returns:
268 // - amount: accumulated DevOps allocation in GNS base units.
269 func GetDistributedToDevOps() int64 {
270 return distributedToDevOps
271 }
272
273 // GetDistributedToCommunityPool returns the GNS amount currently accumulated for the community pool.
274 //
275 // Returns:
276 // - amount: accumulated community-pool allocation in GNS base units.
277 func GetDistributedToCommunityPool() int64 {
278 return distributedToCommunityPool
279 }
280
281 // GetDistributedToGovStaker returns the pending GNS amount allocated to governance stakers.
282 //
283 // Returns:
284 // - amount: pending governance-staker allocation in GNS base units since the last clear.
285 func GetDistributedToGovStaker() int64 {
286 return distributedToGovStaker
287 }
288
289 // AccumulateDistributedInfo returns the current pending allocation for every distribution target.
290 //
291 // Returns:
292 // - toStaker: pending GNS allocation for liquidity stakers, in token base units.
293 // - toDevOps: accumulated GNS allocation for DevOps, in token base units.
294 // - toCommunityPool: accumulated GNS allocation for the community pool, in token base units.
295 // - toGovStaker: pending GNS allocation for governance stakers, in token base units.
296 func AccumulateDistributedInfo() (toStaker, toDevOps, toCommunityPool, toGovStaker int64 ) {
297 toStaker = GetDistributedToStaker()
298 toDevOps = GetDistributedToDevOps()
299 toCommunityPool = GetDistributedToCommunityPool()
300 toGovStaker = GetDistributedToGovStaker()
301 return
302 }
303
304 // GetAccuDistributedToStaker returns the total historical GNS amount allocated to liquidity stakers.
305 //
306 // Returns:
307 // - amount: cumulative liquidity-staker allocation in GNS base units; this value is not cleared.
308 func GetAccuDistributedToStaker() int64 {
309 return accuDistributedToStaker
310 }
311
312 // GetAccuDistributedToDevOps returns the total historical GNS amount allocated to DevOps.
313 //
314 // Returns:
315 // - amount: cumulative DevOps allocation in GNS base units; this value is not cleared.
316 func GetAccuDistributedToDevOps() int64 {
317 return accuDistributedToDevOps
318 }
319
320 // GetAccuDistributedToCommunityPool returns the total historical GNS amount allocated to the community pool.
321 //
322 // Returns:
323 // - amount: cumulative community-pool allocation in GNS base units; this value is not cleared.
324 func GetAccuDistributedToCommunityPool() int64 {
325 return accuDistributedToCommunityPool
326 }
327
328 // GetAccuDistributedToGovStaker returns the total historical GNS amount allocated to governance stakers.
329 //
330 // Returns:
331 // - amount: cumulative governance-staker allocation in GNS base units; this value is not cleared.
332 func GetAccuDistributedToGovStaker() int64 {
333 return accuDistributedToGovStaker
334 }
335
336 // GetEmissionAmountPerSecondBy returns the GNS emission rate per second for a timestamp and target share.
337 //
338 // Parameters:
339 // - timestamp: Unix timestamp at which the base emission rate is queried.
340 // - distributionPct: target share in basis points, from 0 through 10000 inclusive.
341 //
342 // Returns:
343 // - amountPerSecond: target's GNS emission rate in token base units per second, or zero outside the schedule.
344 func GetEmissionAmountPerSecondBy(timestamp, distributionPct int64 ) int64 {
345 return calculateAmount(gns.GetEmissionAmountPerSecondByTimestamp(timestamp), distributionPct)
346 }
347
348 // GetStakerEmissionAmountPerSecond returns the current GNS emission rate allocated to liquidity stakers.
349 //
350 // Returns:
351 // - amountPerSecond: current liquidity-staker emission rate in GNS base units per second.
352 // - error: nil when the liquidity-staker distribution share is available; otherwise the target lookup error.
353 func GetStakerEmissionAmountPerSecond() ( int64 , error ) {
354 currentTimestamp := time.Now().Unix()
355 pct, err := GetDistributionBpsPct(LIQUIDITY_STAKER)
356 if err != nil {
357 return 0 , err
358 }
359 return GetEmissionAmountPerSecondBy(currentTimestamp, pct), nil
360 }
361
362 // GetStakerEmissionAmountPerSecondInRange returns liquidity-staker emission-rate change points in a time range.
363 //
364 // Parameters:
365 // - start: inclusive start Unix timestamp for the range.
366 // - end: inclusive end Unix timestamp for the range.
367 //
368 // Returns:
369 // - timestamps: ordered timestamps in the range where the base emission rate changes.
370 // - amountsPerSecond: liquidity-staker GNS rates corresponding to timestamps, in token base units per second.
371 // - error: nil when the liquidity-staker distribution share is available; otherwise the target lookup error.
372 func GetStakerEmissionAmountPerSecondInRange(start, end int64 ) ([] int64 , [] int64 , error ) {
373 gnsHalvingBlocks, gnsHalvingEmissions := gns.GetEmissionAmountPerSecondInRange(start, end)
374 halvingBlocks := make([] int64 , len(gnsHalvingBlocks))
375 halvingEmissions := make([] int64 , len(gnsHalvingEmissions))
376
377 pct, err := GetDistributionBpsPct(LIQUIDITY_STAKER)
378 if err != nil {
379 return nil , nil , err
380 }
381
382 for i := range halvingBlocks {
383 halvingBlocks[i] = gnsHalvingBlocks[i]
384 // Applying staker ratio for past halving blocks
385 halvingEmissions[i] = calculateAmount(gnsHalvingEmissions[i], pct)
386 }
387
388 return halvingBlocks, halvingEmissions, nil
389 }
390
391 // ClearDistributedToStaker resets the pending distribution amount for liquidity stakers.
392 //
393 // Parameters:
394 // - cur: Current realm context; callers use cross(cur) when crossing into this realm.
395 //
396 // Only callable by staker contract.
397 func ClearDistributedToStaker(cur realm) {
398 caller := cur.Previous().Address()
399 access.AssertIsStaker(caller)
400
401 distributedToStaker = 0
402 }
403
404 // ClearDistributedToGovStaker resets the pending distribution amount for governance stakers.
405 //
406 // Parameters:
407 // - cur: Current realm context; callers use cross(cur) when crossing into this realm.
408 //
409 // Only callable by governance staker contract.
410 func ClearDistributedToGovStaker(cur realm) {
411 caller := cur.Previous().Address()
412 access.AssertIsGovStaker(caller)
413 distributedToGovStaker = 0
414 }
415
416 // setDistributionBpsPct changes percentage of each target for how much GNS it will get by emission.
417 // Creates new map if nil.
418 func setDistributionBpsPct(target int , pct int64 ) {
419 if distributionBpsPct == nil {
420 distributionBpsPct = make( map [ int ] int64 )
421 }
422
423 distributionBpsPct[target] = pct
424 }
425
426 // targetToStr converts target constant to string representation.
427 func targetToStr(target int ) string {
428 switch target {
429 case LIQUIDITY_STAKER:
430 return "LIQUIDITY_STAKER"
431 case DEVOPS:
432 return "DEVOPS"
433 case COMMUNITY_POOL:
434 return "COMMUNITY_POOL"
435 case GOV_STAKER:
436 return "GOV_STAKER"
437 default :
438 return "UNKNOWN"
439 }
440 }
441