gns.gno
9.10 Kb · 319 lines
1package gns
2
3import (
4 "chain"
5 "strings"
6 "time"
7
8 "gno.land/p/nt/grc20/v0"
9 "gno.land/p/nt/ufmt/v0"
10
11 "gno.land/r/gnoswap/access/v1"
12 "gno.land/r/nt/grc20reg/v0"
13
14 "gno.land/p/gnoswap/gnsmath/v1"
15 "gno.land/p/gnoswap/utils/v1"
16 _ "gno.land/r/gnoswap/rbac/v1"
17)
18
19const (
20 tokenID = 0
21 MAXIMUM_SUPPLY = int64(1_000_000_000_000_000)
22 INITIAL_MINT_AMOUNT = int64(100_000_000_000_000)
23 MAX_EMISSION_AMOUNT = int64(900_000_000_000_000) // MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT
24)
25
26var (
27 token *grc20.Token
28 privateLedger *grc20.PrivateLedger
29 userTeller grc20.Teller
30
31 leftEmissionAmount int64 // amount of GNS can be minted for emission
32 mintedEmissionAmount int64 // amount of GNS that has been minted for emission
33 lastMintedTimestamp int64 // last block time that gns was minted for emission
34)
35
36func init(cur realm) {
37 token, privateLedger = grc20.NewToken("Gnoswap", "GNS", 6, tokenID, cur)
38 userTeller = privateLedger.CallerTeller()
39
40 grc20reg.Register(cross(cur), token, "")
41
42 // Initial amount set to 900_000_000_000_000 (MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT).
43 // leftEmissionAmount will decrease as tokens are minted.
44 setLeftEmissionAmount(MAX_EMISSION_AMOUNT)
45 setMintedEmissionAmount(0)
46 setLastMintedTimestamp(0)
47
48 // Pre-mint the initial GNS supply.
49 setupPreMint(cur)
50}
51
52// Name returns the name of the GNS token.
53//
54// Returns:
55// - name: token name
56func Name() string { return token.GetName() }
57
58// Symbol returns the symbol of the GNS token.
59//
60// Returns:
61// - symbol: token symbol
62func Symbol() string { return token.GetSymbol() }
63
64// Decimals returns the number of decimal places for GNS token.
65//
66// Returns:
67// - decimals: number of decimal places
68func Decimals() int { return token.GetDecimals() }
69
70// TotalSupply returns the total supply of GNS tokens in circulation.
71//
72// Returns:
73// - supply: total token supply
74func TotalSupply() int64 { return token.TotalSupply() }
75
76// KnownAccounts returns the number of addresses that have held GNS.
77//
78// Returns:
79// - count: number of known accounts
80func KnownAccounts() int { return token.KnownAccounts() }
81
82// BalanceOf returns the GNS balance of a specific address.
83//
84// Parameters:
85// - owner: address to check balance for
86//
87// Returns:
88// - balance: token balance
89func BalanceOf(owner address) int64 { return token.BalanceOf(owner) }
90
91// Allowance returns the amount of GNS that a spender is allowed to transfer from an owner.
92//
93// Parameters:
94// - owner: token owner address
95// - spender: spender address
96//
97// Returns:
98// - allowance: approved amount
99func Allowance(owner, spender address) int64 { return token.Allowance(owner, spender) }
100
101// MintGns mints new GNS tokens according to the emission schedule.
102//
103// Parameters:
104// - cur: Current realm context; callers use cross(cur) when crossing into this realm.
105// - address: recipient address for minted tokens
106//
107// Returns:
108// - amount: GNS minted for the current timestamp; zero when emission is
109// already processed for that timestamp or the schedule has ended
110//
111// Only callable by emission contract.
112//
113// Note: Halt check is performed by the caller (emission.MintAndDistributeGns)
114// to allow graceful handling. This function assumes caller has already verified
115// halt status before invoking.
116func MintGns(cur realm, address address) int64 {
117 previousRealm := cur.Previous()
118 caller := previousRealm.Address()
119 access.AssertIsEmission(caller)
120
121 lastGNSMintedTimestamp := LastMintedTimestamp()
122 currentTime := time.Now().Unix()
123
124 // Skip if already minted this timestamp or emission ended.
125 if lastGNSMintedTimestamp == currentTime || lastGNSMintedTimestamp >= GetEmissionEndTimestamp() {
126 return 0
127 }
128
129 amountToMint, err := calculateAmountToMint(getEmissionState(), lastGNSMintedTimestamp+1, currentTime)
130 if err != nil {
131 panic(err)
132 }
133
134 err = validEmissionAmount(amountToMint)
135 if err != nil {
136 panic(err)
137 }
138
139 setLastMintedTimestamp(currentTime)
140 setMintedEmissionAmount(gnsmath.SafeAddInt64(MintedEmissionAmount(), amountToMint))
141 setLeftEmissionAmount(gnsmath.SafeSubInt64(LeftEmissionAmount(), amountToMint))
142
143 err = privateLedger.Mint(address, amountToMint)
144 if err != nil {
145 panic(err.Error())
146 }
147
148 chain.Emit(
149 "MintGNS",
150 "prevAddr", caller.String(),
151 "prevRealm", previousRealm.PkgPath(),
152 "mintedBlockTime", utils.FormatInt(currentTime),
153 "mintedGNSAmount", utils.FormatInt(amountToMint),
154 "accumMintedGNSAmount", utils.FormatInt(MintedEmissionAmount()),
155 "accumLeftMintGNSAmount", utils.FormatInt(LeftEmissionAmount()),
156 )
157
158 return amountToMint
159}
160
161// Transfer transfers GNS tokens from caller to recipient.
162//
163// Parameters:
164// - cur: Current realm context; callers use cross(cur) when crossing into this realm.
165// - to: recipient address
166// - amount: number of GNS base units to transfer
167func Transfer(cur realm, to address, amount int64) {
168 checkErr(userTeller.Transfer(0, cur, to, amount))
169}
170
171// Approve allows spender to transfer GNS tokens from caller's account.
172//
173// Parameters:
174// - cur: Current realm context; callers use cross(cur) when crossing into this realm.
175// - spender: address authorized to spend
176// - amount: maximum number of GNS base units spender can transfer
177func Approve(cur realm, spender address, amount int64) {
178 checkErr(userTeller.Approve(0, cur, spender, amount))
179}
180
181// TransferFrom transfers GNS tokens on behalf of owner.
182//
183// Parameters:
184// - cur: Current realm context; callers use cross(cur) when crossing into this realm.
185// - from: token owner address
186// - to: recipient address
187// - amount: number of GNS base units to transfer
188func TransferFrom(cur realm, from, to address, amount int64) {
189 checkErr(userTeller.TransferFrom(0, cur, from, to, amount))
190}
191
192// Render returns token information for web interface.
193//
194// Parameters:
195// - path: render path for specific views
196//
197// Returns:
198// - output: formatted token information
199func Render(path string) string {
200 parts := strings.Split(path, "/")
201 c := len(parts)
202
203 switch {
204 case path == "":
205 return token.RenderHome()
206 case c == 2 && parts[0] == "balance":
207 owner := address(parts[1])
208 balance := token.BalanceOf(owner)
209 return ufmt.Sprintf("%d\n", balance)
210 default:
211 return "404\n"
212 }
213}
214
215// checkErr panics if error is not nil.
216func checkErr(err error) {
217 if err != nil {
218 panic(err.Error())
219 }
220}
221
222// calculateAmountToMint calculates and allocates GNS tokens to mint for given timestamp range.
223// This function has side effects: it updates the accumulated and remaining amounts
224// for each halving year in the emission state.
225func calculateAmountToMint(state *EmissionState, fromTimestamp, toTimestamp int64) (int64, error) {
226 // Cache state to avoid repeated lookups
227 endTimestamp := state.getEndTimestamp()
228 if toTimestamp > endTimestamp {
229 toTimestamp = endTimestamp
230 }
231
232 if fromTimestamp > toTimestamp {
233 return 0, nil
234 }
235
236 startTimestamp := state.getStartTimestamp()
237 if fromTimestamp < startTimestamp {
238 fromTimestamp = startTimestamp
239 }
240
241 if toTimestamp < startTimestamp {
242 return 0, nil
243 }
244
245 fromYear := state.getCurrentYear(fromTimestamp)
246 toYear := state.getCurrentYear(toTimestamp)
247
248 if fromYear == 0 || toYear == 0 {
249 return 0, nil
250 }
251
252 totalAmountToMint := int64(0)
253
254 for year := fromYear; year <= toYear; year++ {
255 yearEndTimestamp := state.getHalvingYearEndTimestamp(year)
256 currentToTimestamp := i64Min(toTimestamp, yearEndTimestamp)
257
258 seconds := currentToTimestamp - fromTimestamp + 1
259 if seconds <= 0 {
260 break
261 }
262
263 amountPerSecond := state.getHalvingYearAmountPerSecond(year)
264 yearAmountToMint := gnsmath.SafeMulInt64(amountPerSecond, seconds)
265
266 if currentToTimestamp >= yearEndTimestamp {
267 leftover := gnsmath.SafeSubInt64(state.getHalvingYearLeftAmount(year), yearAmountToMint)
268 yearAmountToMint = gnsmath.SafeAddInt64(yearAmountToMint, leftover)
269 }
270
271 totalAmountToMint = gnsmath.SafeAddInt64(totalAmountToMint, yearAmountToMint)
272
273 err := state.addHalvingYearMintedAmount(year, yearAmountToMint)
274 if err != nil {
275 return 0, err
276 }
277
278 err = state.subHalvingYearLeftAmount(year, yearAmountToMint)
279 if err != nil {
280 return 0, err
281 }
282
283 fromTimestamp = currentToTimestamp + 1
284
285 if fromTimestamp > toTimestamp {
286 break
287 }
288 }
289
290 return totalAmountToMint, nil
291}
292
293// LastMintedTimestamp returns the timestamp of the last GNS emission mint.
294//
295// Returns:
296// - timestamp: last minted timestamp
297func LastMintedTimestamp() int64 { return lastMintedTimestamp }
298
299// LeftEmissionAmount returns the remaining GNS tokens available for emission.
300//
301// Returns:
302// - amount: remaining emission amount
303func LeftEmissionAmount() int64 { return leftEmissionAmount }
304
305// MintedEmissionAmount returns the total GNS tokens minted through emission,
306// excluding the initial mint amount.
307//
308// Returns:
309// - amount: total minted emission amount
310func MintedEmissionAmount() int64 { return mintedEmissionAmount }
311
312// setLastMintedTimestamp sets the timestamp of the last emission mint.
313func setLastMintedTimestamp(timestamp int64) { lastMintedTimestamp = timestamp }
314
315// setLeftEmissionAmount sets the remaining emission amount.
316func setLeftEmissionAmount(amount int64) { leftEmissionAmount = amount }
317
318// setMintedEmissionAmount sets the total minted emission amount.
319func setMintedEmissionAmount(amount int64) { mintedEmissionAmount = amount }