gns.gno
7.79 Kb · 270 lines
1package gns
2
3import (
4 "chain"
5 "strings"
6 "time"
7
8 "gno.land/p/demo/tokens/grc20"
9 ufmt "gno.land/p/nt/ufmt/v0"
10
11 "gno.land/r/demo/defi/grc20reg"
12 "gno.land/r/gnoswap/access"
13
14 prabc "gno.land/p/gnoswap/rbac"
15 _ "gno.land/r/gnoswap/rbac"
16)
17
18const (
19 MAXIMUM_SUPPLY = int64(1_000_000_000_000_000)
20 INITIAL_MINT_AMOUNT = int64(100_000_000_000_000)
21 MAX_EMISSION_AMOUNT = int64(900_000_000_000_000) // MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT
22)
23
24var (
25 token *grc20.Token
26 privateLedger *grc20.PrivateLedger
27 userTeller grc20.Teller
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(cur realm) {
35 token, privateLedger = grc20.NewToken(0, cur, "Gnoswap", "GNS", 6)
36 userTeller = token.CallerTeller()
37
38 adminAddr := access.MustGetAddress(prabc.ROLE_ADMIN.String())
39 err := privateLedger.Mint(adminAddr, INITIAL_MINT_AMOUNT)
40 if err != nil {
41 panic(err.Error())
42 }
43
44 grc20reg.Register(cross(cur), token, "")
45
46 // Initial amount set to 900_000_000_000_000 (MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT).
47 // leftEmissionAmount will decrease as tokens are minted.
48 setLeftEmissionAmount(MAX_EMISSION_AMOUNT)
49 setMintedEmissionAmount(0)
50 setLastMintedTimestamp(0)
51}
52
53// Name returns the name of the GNS token.
54func Name() string { return token.GetName() }
55
56// Symbol returns the symbol of the GNS token.
57func Symbol() string { return token.GetSymbol() }
58
59// Decimals returns the number of decimal places for GNS token.
60func Decimals() int { return token.GetDecimals() }
61
62// TotalSupply returns the total supply of GNS tokens in circulation.
63func TotalSupply() int64 { return token.TotalSupply() }
64
65// KnownAccounts returns the number of addresses that have held GNS.
66func KnownAccounts() int { return token.KnownAccounts() }
67
68// BalanceOf returns the GNS balance of a specific address.
69func BalanceOf(owner address) int64 { return token.BalanceOf(owner) }
70
71// Allowance returns the amount of GNS that a spender is allowed to transfer from an owner.
72func Allowance(owner, spender address) int64 { return token.Allowance(owner, spender) }
73
74// MintGns mints new GNS tokens according to the emission schedule.
75//
76// Parameters:
77// - address: recipient address for minted tokens
78//
79// Returns amount minted.
80// Only callable by emission contract.
81//
82// Note: Halt check is performed by the caller (emission.MintAndDistributeGns)
83// to allow graceful handling. This function assumes caller has already verified
84// halt status before invoking.
85func MintGns(cur realm, address address) int64 {
86 previousRealm := cur.Previous()
87 caller := previousRealm.Address()
88 access.AssertIsEmission(caller)
89
90 lastGNSMintedTimestamp := LastMintedTimestamp()
91 currentTime := time.Now().Unix()
92
93 // Skip if already minted this timestamp or emission ended.
94 if lastGNSMintedTimestamp == currentTime || lastGNSMintedTimestamp >= GetEmissionEndTimestamp() {
95 return 0
96 }
97
98 amountToMint, err := calculateAmountToMint(getEmissionState(), lastGNSMintedTimestamp+1, currentTime)
99 if err != nil {
100 panic(err)
101 }
102
103 err = validEmissionAmount(amountToMint)
104 if err != nil {
105 panic(err)
106 }
107
108 setLastMintedTimestamp(currentTime)
109 setMintedEmissionAmount(safeAddInt64(MintedEmissionAmount(), amountToMint))
110 setLeftEmissionAmount(safeSubInt64(LeftEmissionAmount(), amountToMint))
111
112 err = privateLedger.Mint(address, amountToMint)
113 if err != nil {
114 panic(err.Error())
115 }
116
117 chain.Emit(
118 "MintGNS",
119 "prevAddr", caller.String(),
120 "prevRealm", previousRealm.PkgPath(),
121 "mintedBlockTime", formatInt(currentTime),
122 "mintedGNSAmount", formatInt(amountToMint),
123 "accumMintedGNSAmount", formatInt(MintedEmissionAmount()),
124 "accumLeftMintGNSAmount", formatInt(LeftEmissionAmount()),
125 )
126
127 return amountToMint
128}
129
130// Transfer transfers GNS tokens from caller to recipient.
131//
132// Parameters:
133// - to: recipient address
134// - amount: amount to transfer
135func Transfer(cur realm, to address, amount int64) {
136 checkErr(userTeller.Transfer(0, cur, to, amount))
137}
138
139// Approve allows spender to transfer GNS tokens from caller's account.
140//
141// Parameters:
142// - spender: address authorized to spend
143// - amount: maximum amount spender can transfer
144func Approve(cur realm, spender address, amount int64) {
145 checkErr(userTeller.Approve(0, cur, spender, amount))
146}
147
148// TransferFrom transfers GNS tokens on behalf of owner.
149//
150// Parameters:
151// - from: token owner address
152// - to: recipient address
153// - amount: amount to transfer
154func TransferFrom(cur realm, from, to address, amount int64) {
155 checkErr(userTeller.TransferFrom(0, cur, 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 }