Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GSW-1845 refactor: gns (halving related) #439

Merged
merged 17 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading