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