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