Skip to content

Commit

Permalink
GSW-1845 refactor: gns (halving related) (#439)
Browse files Browse the repository at this point in the history
* GSW-1845 refactor: use array if possible rather than avl
* GSW-1845 feat: skip minting gns block
- if emission is ended
* GSW-1845 refactor: if block range for emission includes emission end height, mint until end height
* GSW-1845 refactor: assert too many emission amount
* GSW-1845 refactor: calculate height for next halving when avg time changes
* fix: default interval time to 2 sec

---------

Co-authored-by: 0xTopaz <[email protected]>
  • Loading branch information
r3v4s and onlyhyde authored Dec 27, 2024
1 parent 3433e61 commit a055c68
Show file tree
Hide file tree
Showing 14 changed files with 764 additions and 519 deletions.
2 changes: 1 addition & 1 deletion _deploy/r/gnoswap/consts/consts.gno
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const (

TOKEN_REGISTER_NAMESPACE string = "gno.land/r/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5"

BLOCK_GENERATION_INTERVAL int64 = 1 // seconds
BLOCK_GENERATION_INTERVAL int64 = 2 // seconds
)

// WRAP & UNWRAP
Expand Down
27 changes: 25 additions & 2 deletions _deploy/r/gnoswap/gns/_helper_test.gno
Original file line number Diff line number Diff line change
@@ -1,20 +1,43 @@
package gns

import (
"std"
"testing"
"time"

"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/ownable"

"gno.land/r/gnoswap/v1/consts"
)

func testResetGnsTokenObject(t *testing.T) {
func resetObject(t *testing.T) {
t.Helper()

resetGnsTokenObject(t)
resetHalvingRelatedObject(t)

height := std.GetHeight()
lastMintedHeight = height
startHeight = height
startTimestamp = time.Now().Unix()
}

func resetGnsTokenObject(t *testing.T) {
t.Helper()

Token, privateLedger = grc20.NewToken("Gnoswap", "GNS", 6)
UserTeller = Token.CallerTeller()
owner = ownable.NewWithAddress(consts.ADMIN)

privateLedger.Mint(owner.Owner(), INITIAL_MINT_AMOUNT)
}

func resetHalvingRelatedObject(t *testing.T) {
t.Helper()

startHeight = std.GetHeight()
startTimestamp = time.Now().Unix()

initializeHalvingData()
setEndTimestamp(startTimestamp + consts.TIMESTAMP_YEAR*HALVING_END_YEAR)
}
3 changes: 2 additions & 1 deletion _deploy/r/gnoswap/gns/errors.gno
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import (
)

var (
errNoPermission = errors.New("[GNOSWAP-GNS-001] caller has no permission")
errNoPermission = errors.New("[GNOSWAP-GNS-001] caller has no permission")
errTooManyEmission = errors.New("[GNOSWAP-GNS-002] too many emission reward")
)

func addDetailToError(err error, detail string) string {
Expand Down
113 changes: 69 additions & 44 deletions _deploy/r/gnoswap/gns/gns.gno
Original file line number Diff line number Diff line change
Expand Up @@ -22,65 +22,73 @@ const (
)

var (
lastMintedHeight = std.GetHeight()
)
owner *ownable.Ownable

var (
// Initial amount set to 900_000_000_000_000 (MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT).
// leftEmissionAmount will decrease as tokens are minted.
leftEmissionAmount = MAX_EMISSION_AMOUNT
mintedEmissionAmount = uint64(0)
)
Token *grc20.Token
privateLedger *grc20.PrivateLedger
UserTeller grc20.Teller

var (
Token, privateLedger = grc20.NewToken("Gnoswap", "GNS", 6)
UserTeller = Token.CallerTeller()
owner = ownable.NewWithAddress(consts.ADMIN)
leftEmissionAmount uint64
mintedEmissionAmount uint64
lastMintedHeight int64

burnAmount uint64
)

func init() {
owner = ownable.NewWithAddress(consts.ADMIN)

Token, privateLedger = grc20.NewToken("Gnoswap", "GNS", 6)
UserTeller = Token.CallerTeller()

privateLedger.Mint(owner.Owner(), INITIAL_MINT_AMOUNT)
getter := func() *grc20.Token { return Token }
grc20reg.Register(getter, "")
}

// MintedEmissionAmount returns the amount of GNS that has been minted by the emission contract.
// It does not include initial minted amount.
func MintedEmissionAmount() uint64 {
return TotalSupply() - INITIAL_MINT_AMOUNT
// Initial amount set to 900_000_000_000_000 (MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT).
// leftEmissionAmount will decrease as tokens are minted.
leftEmissionAmount = MAX_EMISSION_AMOUNT

lastMintedHeight = std.GetHeight()
}

func MintGns(address pusers.AddressOrName) uint64 {
lastMintedHeight := GetLastMintedHeight()
currentHeight := std.GetHeight()

// skip minting process if gns for current block is already minted
if skipIfSameHeight(lastMintedHeight, currentHeight) {
// skip minting process if following conditions are met
// - if gns for current block is already minted
// - if last minted height is same or later than emission end height
if lastMintedHeight == currentHeight || lastMintedHeight >= GetEndHeight() {
return 0
}

assertShouldNotBeHalted()
assertCallerIsEmission()

// calculate gns amount to mint, and the mint to the target address
// calculate gns amount to mint
amountToMint := calculateAmountToMint(lastMintedHeight+1, currentHeight)
err := privateLedger.Mint(users.Resolve(address), amountToMint)
if err != nil {
panic(err.Error())
}

// update
setLastMintedHeight(currentHeight)
setMintedEmissionAmount(GetMintedEmissionAmount() + amountToMint)
setLeftEmissionAmount(GetLeftEmissionAmount() - amountToMint)

// mint calculated amount to address
err := privateLedger.Mint(users.Resolve(address), amountToMint)
if err != nil {
panic(err.Error())
}

return amountToMint
}

func Burn(from pusers.AddressOrName, amount uint64) {
owner.AssertCallerIsOwner()
fromAddr := users.Resolve(from)
checkErr(privateLedger.Burn(fromAddr, amount))

burnAmount += amount
}

func TotalSupply() uint64 {
Expand Down Expand Up @@ -137,60 +145,60 @@ func checkErr(err error) {
}
}

// helper functions

// calculateAmountToMint calculates the amount of gns to mint
// It calculates the amount of gns to mint for each halving year for block range.
// It also handles the left emission amount if the current block range includes halving year end block.
func calculateAmountToMint(fromHeight, toHeight int64) uint64 {
fromYear := GetHalvingYearByHeight(fromHeight)
toYear := GetHalvingYearByHeight(toHeight)

if isEmissionEnded(fromYear) || isEmissionEnded(toYear) {
return 0
// if toHeight is greater than emission end height, set toHeight to emission end height
if toHeight > GetEndHeight() {
toHeight = GetEndHeight()
}
toYear := GetHalvingYearByHeight(toHeight)

totalAmountToMint := uint64(0)

for i := fromYear; i <= toYear; i++ {
yearEndHeight := GetHalvingYearBlock(i)
for year := fromYear; year <= toYear; year++ {
yearEndHeight := GetHalvingYearEndBlock(year)
mintUntilHeight := i64Min(yearEndHeight, toHeight)

// how many blocks to calculate
blocks := uint64(mintUntilHeight-fromHeight) + 1

// amount of gns to mint for each block for current year
singleBlockAmount := GetAmountByHeight(yearEndHeight)
singleBlockAmount := GetAmountPerBlockPerHalvingYear(year)

// amount of gns to mint for current year
yearAmountToMint := singleBlockAmount * blocks

// if last block of halving year, handle left emission amount
if isLastBlockOfHalvingYear(mintUntilHeight) {
yearAmountToMint += handleLeftEmissionAmount(i, yearAmountToMint)
yearAmountToMint += handleLeftEmissionAmount(year, yearAmountToMint)
}
totalAmountToMint += yearAmountToMint
SetHalvingYearMintAmount(i, GetHalvingYearMintAmount(i)+yearAmountToMint)
setHalvingYearMintAmount(year, GetHalvingYearMintAmount(year)+yearAmountToMint)

// update fromHeight for next year (if necessary)
fromHeight = mintUntilHeight + 1
}

assertTooManyEmission(totalAmountToMint)
return totalAmountToMint
}

// isLastBlockOfHalvingYear returns true if the current block is the last block of a halving year.
func isLastBlockOfHalvingYear(height int64) bool {
year := GetHalvingYearByHeight(height)
lastBlock := GetHalvingYearBlock(year)
lastBlock := GetHalvingYearEndBlock(year)

return height == lastBlock
}

// handleLeftEmissionAmount handles the left emission amount for a halving year.
// It calculates the left emission amount by subtracting the halving year mint amount from the halving year amount.
func handleLeftEmissionAmount(year int64, amount uint64) uint64 {
return GetHalvingYearAmount(year) - GetHalvingYearMintAmount(year) - amount
return GetHalvingYearMaxAmount(year) - GetHalvingYearMintAmount(year) - amount
}

// skipIfSameHeight returns true if the current block height is the same as the last minted height.
Expand All @@ -199,30 +207,36 @@ func skipIfSameHeight(lastMintedHeight, currentHeight int64) bool {
return lastMintedHeight == currentHeight
}

// isEmissionEnded returns true if the emission is ended.
// It returns false if the emission is not ended.
func isEmissionEnded(year int64) bool {
if 1 <= year && year <= 12 {
return false
// skipIfEmissionEnded returns true if the emission has ended.
func skipIfEmissionEnded(height int64) bool {
if isEmissionEnded(height) {
return true
}

return true
return false
}

// Getter
// GetLastMintedHeight returns the last block height that gns was minted.
func GetLastMintedHeight() int64 {
return lastMintedHeight
}

// GetLeftEmissionAmount returns the amount of GNS can be minted.
func GetLeftEmissionAmount() uint64 {
return leftEmissionAmount
}

// GetBurnAmount returns the amount of GNS that has been burned.
func GetBurnAmount() uint64 {
return burnAmount
}

// GetMintedEmissionAmount returns the amount of GNS that has been minted by the emission contract.
// It does not include initial minted amount.
func GetMintedEmissionAmount() uint64 {
return mintedEmissionAmount
}

// Setter
func setLastMintedHeight(height int64) {
lastMintedHeight = height
}
Expand All @@ -234,3 +248,14 @@ func setLeftEmissionAmount(amount uint64) {
func setMintedEmissionAmount(amount uint64) {
mintedEmissionAmount = amount
}

// assertTooManyEmission asserts if the amount of gns to mint is too many.
// It checks if the amount of gns to mint is greater than the left emission amount or the total emission amount.
func assertTooManyEmission(amount uint64) {
if amount > GetLeftEmissionAmount() || (amount+GetMintedEmissionAmount()) > MAX_EMISSION_AMOUNT {
panic(addDetailToError(
errTooManyEmission,
ufmt.Sprintf("amount: %d", amount),
))
}
}
Loading

0 comments on commit a055c68

Please sign in to comment.