package gns import ( "chain" "chain/runtime" "strings" "time" "gno.land/p/demo/tokens/grc20" "gno.land/p/nt/ufmt" "gno.land/r/demo/defi/grc20reg" "gno.land/r/gnoswap/access" prabc "gno.land/p/gnoswap/rbac" _ "gno.land/r/gnoswap/rbac" ) const ( MAXIMUM_SUPPLY = int64(1_000_000_000_000_000) INITIAL_MINT_AMOUNT = int64(100_000_000_000_000) MAX_EMISSION_AMOUNT = int64(900_000_000_000_000) // MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT ) var ( token, privateLedger = grc20.NewToken("Gnoswap", "GNS", 6) UserTeller = token.CallerTeller() leftEmissionAmount int64 // amount of GNS can be minted for emission mintedEmissionAmount int64 // amount of GNS that has been minted for emission lastMintedTimestamp int64 // last block time that gns was minted for emission ) func init() { adminAddr := access.MustGetAddress(prabc.ROLE_ADMIN.String()) err := privateLedger.Mint(adminAddr, INITIAL_MINT_AMOUNT) if err != nil { panic(err.Error()) } grc20reg.Register(cross, token, "") // Initial amount set to 900_000_000_000_000 (MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT). // leftEmissionAmount will decrease as tokens are minted. setLeftEmissionAmount(MAX_EMISSION_AMOUNT) setMintedEmissionAmount(0) setLastMintedTimestamp(0) } // Name returns the name of the GNS token. func Name() string { return token.GetName() } // Symbol returns the symbol of the GNS token. func Symbol() string { return token.GetSymbol() } // Decimals returns the number of decimal places for GNS token. func Decimals() int { return token.GetDecimals() } // TotalSupply returns the total supply of GNS tokens in circulation. func TotalSupply() int64 { return token.TotalSupply() } // KnownAccounts returns the number of addresses that have held GNS. func KnownAccounts() int { return token.KnownAccounts() } // BalanceOf returns the GNS balance of a specific address. func BalanceOf(owner address) int64 { return token.BalanceOf(owner) } // Allowance returns the amount of GNS that a spender is allowed to transfer from an owner. func Allowance(owner, spender address) int64 { return token.Allowance(owner, spender) } // MintGns mints new GNS tokens according to the emission schedule. // // Parameters: // - address: recipient address for minted tokens // // Returns amount minted. // Only callable by emission contract. // // Note: Halt check is performed by the caller (emission.MintAndDistributeGns) // to allow graceful handling. This function assumes caller has already verified // halt status before invoking. func MintGns(cur realm, address address) int64 { previousRealm := runtime.PreviousRealm() caller := previousRealm.Address() access.AssertIsEmission(caller) lastGNSMintedTimestamp := LastMintedTimestamp() currentTime := time.Now().Unix() // Skip if already minted this timestamp or emission ended. if lastGNSMintedTimestamp == currentTime || lastGNSMintedTimestamp >= GetEmissionEndTimestamp() { return 0 } amountToMint, err := calculateAmountToMint(getEmissionState(), lastGNSMintedTimestamp+1, currentTime) if err != nil { panic(err) } err = validEmissionAmount(amountToMint) if err != nil { panic(err) } setLastMintedTimestamp(currentTime) setMintedEmissionAmount(safeAddInt64(MintedEmissionAmount(), amountToMint)) setLeftEmissionAmount(safeSubInt64(LeftEmissionAmount(), amountToMint)) err = privateLedger.Mint(address, amountToMint) if err != nil { panic(err.Error()) } chain.Emit( "MintGNS", "prevAddr", caller.String(), "prevRealm", previousRealm.PkgPath(), "mintedBlockTime", formatInt(currentTime), "mintedGNSAmount", formatInt(amountToMint), "accumMintedGNSAmount", formatInt(MintedEmissionAmount()), "accumLeftMintGNSAmount", formatInt(LeftEmissionAmount()), ) return amountToMint } // Transfer transfers GNS tokens from caller to recipient. // // Parameters: // - to: recipient address // - amount: amount to transfer func Transfer(cur realm, to address, amount int64) { userTeller := token.CallerTeller() checkErr(userTeller.Transfer(to, amount)) } // Approve allows spender to transfer GNS tokens from caller's account. // // Parameters: // - spender: address authorized to spend // - amount: maximum amount spender can transfer func Approve(cur realm, spender address, amount int64) { userTeller := token.CallerTeller() checkErr(userTeller.Approve(spender, amount)) } // TransferFrom transfers GNS tokens on behalf of owner. // // Parameters: // - from: token owner address // - to: recipient address // - amount: amount to transfer func TransferFrom(cur realm, from, to address, amount int64) { userTeller := token.CallerTeller() checkErr(userTeller.TransferFrom(from, to, amount)) } // Render returns token information for web interface. func Render(path string) string { parts := strings.Split(path, "/") c := len(parts) switch { case path == "": return token.RenderHome() case c == 2 && parts[0] == "balance": owner := address(parts[1]) balance := token.BalanceOf(owner) return ufmt.Sprintf("%d\n", balance) default: return "404\n" } } // checkErr panics if error is not nil. func checkErr(err error) { if err != nil { panic(err.Error()) } } // calculateAmountToMint calculates and allocates GNS tokens to mint for given timestamp range. // This function has side effects: it updates the accumulated and remaining amounts // for each halving year in the emission state. func calculateAmountToMint(state *EmissionState, fromTimestamp, toTimestamp int64) (int64, error) { // Cache state to avoid repeated lookups endTimestamp := state.getEndTimestamp() if toTimestamp > endTimestamp { toTimestamp = endTimestamp } if fromTimestamp > toTimestamp { return 0, nil } startTimestamp := state.getStartTimestamp() if fromTimestamp < startTimestamp { fromTimestamp = startTimestamp } if toTimestamp < startTimestamp { return 0, nil } fromYear := state.getCurrentYear(fromTimestamp) toYear := state.getCurrentYear(toTimestamp) if fromYear == 0 || toYear == 0 { return 0, nil } totalAmountToMint := int64(0) for year := fromYear; year <= toYear; year++ { yearEndTimestamp := state.getHalvingYearEndTimestamp(year) currentToTimestamp := i64Min(toTimestamp, yearEndTimestamp) seconds := currentToTimestamp - fromTimestamp + 1 if seconds <= 0 { break } amountPerSecond := state.getHalvingYearAmountPerSecond(year) yearAmountToMint := safeMulInt64(amountPerSecond, seconds) if currentToTimestamp >= yearEndTimestamp { leftover := safeSubInt64(state.getHalvingYearLeftAmount(year), yearAmountToMint) yearAmountToMint = safeAddInt64(yearAmountToMint, leftover) } totalAmountToMint = safeAddInt64(totalAmountToMint, yearAmountToMint) err := state.addHalvingYearAccumulatedAmount(year, yearAmountToMint) if err != nil { return 0, err } err = state.subHalvingYearLeftAmount(year, yearAmountToMint) if err != nil { return 0, err } fromTimestamp = currentToTimestamp + 1 if fromTimestamp > toTimestamp { break } } return totalAmountToMint, nil } // LastMintedTimestamp returns the timestamp of the last GNS emission mint. func LastMintedTimestamp() int64 { return lastMintedTimestamp } // LeftEmissionAmount returns the remaining GNS tokens available for emission. func LeftEmissionAmount() int64 { return leftEmissionAmount } // MintedEmissionAmount returns the total GNS tokens minted through emission, // excluding the initial mint amount. func MintedEmissionAmount() int64 { return mintedEmissionAmount } // setLastMintedTimestamp sets the timestamp of the last emission mint. func setLastMintedTimestamp(timestamp int64) { lastMintedTimestamp = timestamp } // setLeftEmissionAmount sets the remaining emission amount. func setLeftEmissionAmount(amount int64) { leftEmissionAmount = amount } // setMintedEmissionAmount sets the total minted emission amount. func setMintedEmissionAmount(amount int64) { mintedEmissionAmount = amount }