Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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 }