From 01f0cd0ea5050206f5602a892b038d1bfdc801a9 Mon Sep 17 00:00:00 2001 From: 0xTopaz Date: Tue, 14 Jan 2025 21:34:12 +0900 Subject: [PATCH] fix: calculate to emission end block per year in gns --- .../r/gnoswap/common/address_and_username.gno | 6 +- .../common/address_and_username_test.gno | 4 +- _deploy/r/gnoswap/gns/_helper_test.gno | 2 +- _deploy/r/gnoswap/gns/errors.gno | 2 +- _deploy/r/gnoswap/gns/gns.gno | 138 ++++-- _deploy/r/gnoswap/gns/gns_test.gno | 109 +++++ _deploy/r/gnoswap/gns/halving.gno | 441 +++++++++++++----- _deploy/r/gnoswap/gns/halving_test.gno | 43 +- _deploy/r/gnoswap/gns/utils.gno | 32 +- 9 files changed, 597 insertions(+), 180 deletions(-) diff --git a/_deploy/r/gnoswap/common/address_and_username.gno b/_deploy/r/gnoswap/common/address_and_username.gno index 908f81712..bc8de54bb 100644 --- a/_deploy/r/gnoswap/common/address_and_username.gno +++ b/_deploy/r/gnoswap/common/address_and_username.gno @@ -10,7 +10,7 @@ import ( // AddrToUser converts a type from address to AddressOrName. // It panics if the address is invalid. func AddrToUser(addr std.Address) pusers.AddressOrName { - assertValidAddr(addr) + AssertValidAddr(addr) return pusers.AddressOrName(addr) } @@ -20,9 +20,9 @@ func UserToAddr(user pusers.AddressOrName) std.Address { return users.Resolve(user) } -// assertValidAddr checks if the given address is valid. +// AssertValidAddr checks if the given address is valid. // It panics with a detailed error message if the address is invalid. -func assertValidAddr(addr std.Address) { +func AssertValidAddr(addr std.Address) { if !addr.IsValid() { panic(newErrorWithDetail(errInvalidAddr, addr.String())) } diff --git a/_deploy/r/gnoswap/common/address_and_username_test.gno b/_deploy/r/gnoswap/common/address_and_username_test.gno index 9f62bd8ca..d988f047f 100644 --- a/_deploy/r/gnoswap/common/address_and_username_test.gno +++ b/_deploy/r/gnoswap/common/address_and_username_test.gno @@ -103,11 +103,11 @@ func TestAssertValidAddr(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.shouldPanic { uassert.PanicsWithMessage(t, tt.panicMsg, func() { - assertValidAddr(tt.addr) + AssertValidAddr(tt.addr) }) } else { uassert.NotPanics(t, func() { - assertValidAddr(tt.addr) + AssertValidAddr(tt.addr) }) } }) diff --git a/_deploy/r/gnoswap/gns/_helper_test.gno b/_deploy/r/gnoswap/gns/_helper_test.gno index 39e6c0142..a5c7a743f 100644 --- a/_deploy/r/gnoswap/gns/_helper_test.gno +++ b/_deploy/r/gnoswap/gns/_helper_test.gno @@ -38,6 +38,6 @@ func resetHalvingRelatedObject(t *testing.T) { startHeight = std.GetHeight() startTimestamp = time.Now().Unix() - initializeHalvingData() + emissionState = GetEmissionState() setEndTimestamp(startTimestamp + consts.TIMESTAMP_YEAR*HALVING_END_YEAR) } diff --git a/_deploy/r/gnoswap/gns/errors.gno b/_deploy/r/gnoswap/gns/errors.gno index 2f4480fd2..af5bb4e8c 100644 --- a/_deploy/r/gnoswap/gns/errors.gno +++ b/_deploy/r/gnoswap/gns/errors.gno @@ -7,7 +7,7 @@ import ( ) var ( - errNoPermission = errors.New("[GNOSWAP-GNS-001] caller has no permission") + errInvalidYear = errors.New("[GNOSWAP-GNS-001] invalid year") errTooManyEmission = errors.New("[GNOSWAP-GNS-002] too many emission reward") ) diff --git a/_deploy/r/gnoswap/gns/gns.gno b/_deploy/r/gnoswap/gns/gns.gno index 73725e6ff..9b75adb27 100644 --- a/_deploy/r/gnoswap/gns/gns.gno +++ b/_deploy/r/gnoswap/gns/gns.gno @@ -2,6 +2,7 @@ package gns import ( "std" + "strconv" "strings" "gno.land/p/demo/grc/grc20" @@ -12,6 +13,7 @@ import ( "gno.land/r/demo/grc20reg" "gno.land/r/demo/users" + "gno.land/r/gnoswap/v1/common" "gno.land/r/gnoswap/v1/consts" ) @@ -22,8 +24,7 @@ const ( ) var ( - owner *ownable.Ownable - + owner *ownable.Ownable Token *grc20.Token privateLedger *grc20.PrivateLedger UserTeller grc20.Teller @@ -37,7 +38,6 @@ var ( func init() { owner = ownable.NewWithAddress(consts.ADMIN) - Token, privateLedger = grc20.NewToken("Gnoswap", "GNS", 6) UserTeller = Token.CallerTeller() @@ -47,19 +47,68 @@ func init() { // 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 + setLeftEmissionAmount(MAX_EMISSION_AMOUNT) + setMintedEmissionAmount(uint64(0)) + setLastMintedHeight(std.GetHeight()) + burnAmount = uint64(0) +} + +func GetName() string { + return Token.GetName() +} + +func GetSymbol() string { + return Token.GetSymbol() +} + +func GetDecimals() uint { + return Token.GetDecimals() +} + +func TotalSupply() uint64 { + return Token.TotalSupply() +} + +func KnownAccounts() int { + return Token.KnownAccounts() +} + +func BalanceOfAddress(owner std.Address) uint64 { + common.AssertValidAddr(owner) + return Token.BalanceOf(owner) +} + +func AllowanceOfAddress(owner, spender std.Address) uint64 { + common.AssertValidAddr(owner) + common.AssertValidAddr(spender) + return Token.Allowance(owner, spender) +} + +func BalanceOf(owner pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + return UserTeller.BalanceOf(ownerAddr) +} + +func Allowance(owner, spender pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + return UserTeller.Allowance(ownerAddr, spenderAddr) +} - lastMintedHeight = std.GetHeight() +func SpendAllowance(owner, spender pusers.AddressOrName, amount uint64) { + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + checkErr(privateLedger.SpendAllowance(ownerAddr, spenderAddr, amount)) } func MintGns(address pusers.AddressOrName) uint64 { - lastMintedHeight := GetLastMintedHeight() + lastGNSMintedHeight := GetLastMintedHeight() currentHeight := std.GetHeight() // 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() { + if lastGNSMintedHeight == currentHeight || lastGNSMintedHeight >= GetEndHeight() { return 0 } @@ -67,8 +116,7 @@ func MintGns(address pusers.AddressOrName) uint64 { assertCallerIsEmission() // calculate gns amount to mint - amountToMint := calculateAmountToMint(lastMintedHeight+1, currentHeight) - println("============> GNS : MintGNS : [", lastMintedHeight+1, "] [", currentHeight, "] [", amountToMint, "]") + amountToMint := calculateAmountToMint(lastGNSMintedHeight+1, currentHeight) // update setLastMintedHeight(currentHeight) @@ -81,6 +129,17 @@ func MintGns(address pusers.AddressOrName) uint64 { panic(err.Error()) } + prevAddr, prevPkgPath := getPrev() + std.Emit( + "MintGNS", + "prevAddr", prevAddr, + "prevRealm", prevPkgPath, + "mintedBlockHeight", strconv.FormatInt(currentHeight, 10), + "mintedGNSAmount", strconv.FormatUint(amountToMint, 10), + "accumMintedGNSAmount", strconv.FormatUint(GetMintedEmissionAmount(), 10), + "accumLeftMintGNSAmount", strconv.FormatUint(GetLeftEmissionAmount(), 10), + ) + return amountToMint } @@ -90,21 +149,17 @@ func Burn(from pusers.AddressOrName, amount uint64) { checkErr(privateLedger.Burn(fromAddr, amount)) burnAmount += amount -} - -func TotalSupply() uint64 { - return UserTeller.TotalSupply() -} -func BalanceOf(owner pusers.AddressOrName) uint64 { - ownerAddr := users.Resolve(owner) - return UserTeller.BalanceOf(ownerAddr) -} - -func Allowance(owner, spender pusers.AddressOrName) uint64 { - ownerAddr := users.Resolve(owner) - spenderAddr := users.Resolve(spender) - return UserTeller.Allowance(ownerAddr, spenderAddr) + prevAddr, prevPkgPath := getPrev() + std.Emit( + "Burn", + "prevAddr", prevAddr, + "prevRealm", prevPkgPath, + "burnedBlockHeight", strconv.FormatInt(std.GetHeight(), 10), + "burnFrom", fromAddr.String(), + "burnedGNSAmount", strconv.FormatUint(amount, 10), + "accumBurnedGNSAmount", strconv.FormatUint(GetBurnAmount(), 10), + ) } func Transfer(to pusers.AddressOrName, amount uint64) { @@ -150,25 +205,35 @@ func checkErr(err error) { // 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 { - println("============> GNS : calculateAmountToMint : [", fromHeight, "] [", toHeight, "]") prevHeight := fromHeight currentHeight := toHeight - fromYear := GetHalvingYearByHeight(fromHeight) // if toHeight is greater than emission end height, set toHeight to emission end height - if toHeight > GetEndHeight() { - toHeight = GetEndHeight() + endH := GetEndHeight() + if toHeight > endH { + toHeight = endH + } + + if fromHeight > toHeight { + return 0 } + + fromYear := GetHalvingYearByHeight(fromHeight) toYear := GetHalvingYearByHeight(toHeight) totalAmountToMint := uint64(0) + curFrom := fromHeight + for year := fromYear; year <= toYear; year++ { yearEndHeight := GetHalvingYearEndBlock(year) mintUntilHeight := i64Min(yearEndHeight, toHeight) // how many blocks to calculate - blocks := uint64(mintUntilHeight-fromHeight) + 1 + blocks := uint64(mintUntilHeight - curFrom + 1) + if blocks <= 0 { + break + } // amount of gns to mint for each block for current year singleBlockAmount := GetAmountPerBlockPerHalvingYear(year) @@ -177,14 +242,21 @@ func calculateAmountToMint(fromHeight, toHeight int64) uint64 { yearAmountToMint := singleBlockAmount * blocks // if last block of halving year, handle left emission amount - if isLastBlockOfHalvingYear(mintUntilHeight) { - yearAmountToMint += handleLeftEmissionAmount(year, yearAmountToMint) + if mintUntilHeight >= yearEndHeight { + leftover := handleLeftEmissionAmount(year, yearAmountToMint) + yearAmountToMint += leftover } + totalAmountToMint += yearAmountToMint + setHalvingYearMintAmount(year, GetHalvingYearMintAmount(year)+yearAmountToMint) + setHalvingYearLeftAmount(year, GetHalvingYearLeftAmount(year)-yearAmountToMint) // update fromHeight for next year (if necessary) - fromHeight = mintUntilHeight + 1 + curFrom = mintUntilHeight + 1 + if curFrom > toHeight { + break + } } if prevHeight == currentHeight { @@ -209,7 +281,7 @@ func isLastBlockOfHalvingYear(height int64) bool { // 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 GetHalvingYearMaxAmount(year) - GetHalvingYearMintAmount(year) - amount + return GetHalvingYearLeftAmount(year) - amount } // skipIfSameHeight returns true if the current block height is the same as the last minted height. @@ -263,7 +335,7 @@ func setMintedEmissionAmount(amount uint64) { // 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 { + if (amount + GetMintedEmissionAmount()) > MAX_EMISSION_AMOUNT { panic(addDetailToError( errTooManyEmission, ufmt.Sprintf("amount: %d", amount), diff --git a/_deploy/r/gnoswap/gns/gns_test.gno b/_deploy/r/gnoswap/gns/gns_test.gno index 33ae2200c..1c72e2729 100644 --- a/_deploy/r/gnoswap/gns/gns_test.gno +++ b/_deploy/r/gnoswap/gns/gns_test.gno @@ -7,6 +7,7 @@ import ( "gno.land/p/demo/testutils" "gno.land/p/demo/uassert" + pusers "gno.land/p/demo/users" "gno.land/r/gnoswap/v1/consts" ) @@ -27,6 +28,114 @@ var ( bob = testutils.TestAddress("bob") ) +func TestGetName(t *testing.T) { + uassert.Equal(t, "Gnoswap", GetName()) +} + +func TestGetSymbol(t *testing.T) { + uassert.Equal(t, "GNS", GetSymbol()) +} + +func TestGetDecimals(t *testing.T) { + uassert.Equal(t, uint(6), GetDecimals()) +} + +func TestTotalSupply(t *testing.T) { + + uassert.Equal(t, INITIAL_MINT_AMOUNT, TotalSupply()) +} + +func TestKnownAccounts(t *testing.T) { + uassert.Equal(t, int(1), KnownAccounts()) +} + +func TestBalanceOfAddress(t *testing.T) { + t.Run( + "should return balance of address", + func(t *testing.T) { + uassert.Equal(t, INITIAL_MINT_AMOUNT, BalanceOfAddress(consts.ADMIN)) + }, + ) + t.Run( + "should panic if address is not valid", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "[GNOSWAP-COMMON-005] invalid address || 0xabcdefg", func() { + BalanceOfAddress("0xabcdefg") + }) + }, + ) +} + +func TestAllowanceOfAddress(t *testing.T) { + t.Run( + "should return allowance of address", + func(t *testing.T) { + uassert.Equal(t, uint64(0), AllowanceOfAddress(consts.ADMIN, alice)) + + std.TestSetOrigCaller(consts.ADMIN) + Approve(pusers.AddressOrName(alice), uint64(123)) + uassert.Equal(t, uint64(123), AllowanceOfAddress(consts.ADMIN, alice)) + }, + ) + t.Run( + "should panic if address is not valid", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "[GNOSWAP-COMMON-005] invalid address || 0xabcdefg", func() { + AllowanceOfAddress("0xabcdefg", alice) + }) + }, + ) + t.Run( + "should panic if spender is not valid", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "[GNOSWAP-COMMON-005] invalid address || 0xabcdefg", func() { + AllowanceOfAddress(consts.ADMIN, "0xabcdefg") + }) + }, + ) +} + +func TestBalanceOf(t *testing.T) { + uassert.Equal(t, INITIAL_MINT_AMOUNT, BalanceOf(a2u(consts.ADMIN))) +} + +func TestSpendAllowance(t *testing.T) { + t.Run( + "should spend allowance", + func(t *testing.T) { + std.TestSetOrigCaller(consts.ADMIN) + Approve(a2u(alice), uint64(123)) + + SpendAllowance(a2u(consts.ADMIN), a2u(alice), uint64(23)) + uassert.Equal(t, uint64(100), Allowance(a2u(consts.ADMIN), a2u(alice))) + }, + ) + t.Run( + "should panic if address is not valid", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "invalid address", func() { + SpendAllowance("0xabcdefg", a2u(alice), uint64(123)) + }) + }, + ) + t.Run( + "should panic if spender is not valid", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "invalid address", func() { + SpendAllowance(a2u(consts.ADMIN), "0xabcdefg", uint64(123)) + }) + }, + ) + t.Run( + "should panic if allowance is not enough", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "insufficient allowance", func() { + SpendAllowance(a2u(consts.ADMIN), a2u(alice), uint64(123)) + }) + }, + ) +} + func TestAssertTooManyEmission(t *testing.T) { tests := []struct { name string diff --git a/_deploy/r/gnoswap/gns/halving.gno b/_deploy/r/gnoswap/gns/halving.gno index 027465032..f634ae25f 100644 --- a/_deploy/r/gnoswap/gns/halving.gno +++ b/_deploy/r/gnoswap/gns/halving.gno @@ -6,13 +6,9 @@ import ( "time" "gno.land/p/demo/avl" - - "gno.land/p/demo/ufmt" - + "gno.land/p/demo/json" "gno.land/r/gnoswap/v1/common" "gno.land/r/gnoswap/v1/consts" - - "gno.land/p/demo/json" ) // init 12 years halving tier block @@ -28,8 +24,10 @@ import ( const ( HALVING_START_YEAR = int64(1) HALVING_END_YEAR = int64(12) +) - HALVING_AMOUNTS_PER_YEAR = [12]uint64{ +var ( + HALVING_AMOUNTS_PER_YEAR = [HALVING_END_YEAR]uint64{ 18_750_000_000_000 * 12, // Year 1: 225000000000000 18_750_000_000_000 * 12, // Year 2: 225000000000000 9_375_000_000_000 * 12, // Year 3: 112500000000000 @@ -50,176 +48,301 @@ var ( blockPerDay = consts.TIMESTAMP_DAY / consts.BLOCK_GENERATION_INTERVAL avgBlockTimeMs int64 = consts.SECOND_IN_MILLISECOND * consts.BLOCK_GENERATION_INTERVAL + perBlockMint = avl.NewTree() // height => uint64 ) -var ( - startHeight int64 - startTimestamp int64 - endTimestamp int64 -) +type HalvingData struct { + startBlockHeight []int64 + endBlockHeight []int64 + startTimestamp []int64 + maxAmount []uint64 + mintedAmount []uint64 + leftAmount []uint64 + accumAmount []uint64 + amountPerBlock []uint64 +} -var ( - perBlockMint = avl.NewTree() // height => uint64 -) +func (h *HalvingData) getStartBlockHeight(year int64) int64 { + if year == 0 { + return 0 + } + return h.startBlockHeight[year-1] +} -func GetCurrentEmission() uint64 { - emision := uint64(0) - perBlockMint.ReverseIterate("", common.EncodeUint(uint64(std.GetHeight())), func(key string, value interface{}) bool { - emision = value.(uint64) - return true - }) - return emision +func (h *HalvingData) setStartBlockHeight(year int64, height int64) { + assertValidYear(year) + h.startBlockHeight[year-1] = height } -func EmissionUpdates(startHeight uint64, endHeight uint64) ([]uint64, []uint64) { - heights := make([]uint64, 0) - updates := make([]uint64, 0) - println("EmissionUpdates : ", startHeight, ", ", endHeight) - perBlockMint.Iterate(common.EncodeUint(startHeight), common.EncodeUint(endHeight), func(key string, value interface{}) bool { - println("EmissionUpdates : ", key, ", ", value) - heights = append(heights, common.DecodeUint(key)) - updates = append(updates, value.(uint64)) - return false - }) +func (h *HalvingData) getEndBlockHeight(year int64) int64 { + if year == 0 { + return 0 + } + return h.endBlockHeight[year-1] +} - return heights, updates +func (h *HalvingData) setEndBlockHeight(year int64, height int64) { + assertValidYear(year) + h.endBlockHeight[year-1] = height } -func addPerBlockMintUpdate(height uint64, amount uint64) { - perBlockMint.Set(common.EncodeUint(height), amount) +func (h *HalvingData) getStartTimestamp(year int64) int64 { + if year == 0 { + return 0 + } + return h.startTimestamp[year-1] } -var halvingYearBlock = make(map[int64]int64) // year => block -// var halvingYearTimestamp = make(map[int64]int64) // year => timestamp -var ( - halvingYearStartBlock = make([]int64, HALVING_END_YEAR) // start block of each halving year - halvingYearEndBlock = make([]int64, HALVING_END_YEAR) // end block of each halving year - halvingYearTimestamp = make([]int64, HALVING_END_YEAR) // start timestamp of each halving year - - halvingYearMaxAmount = make([]uint64, HALVING_END_YEAR) // max amount per year can be minted - halvingYearMintAmount = make([]uint64, HALVING_END_YEAR) // actual minted amount per year - halvingYearAccuAmount = make([]uint64, HALVING_END_YEAR) // accumulated minted amount per year - amountPerBlockPerHalvingYear = make([]uint64, HALVING_END_YEAR) // amount per block per year to mint -) +func (h *HalvingData) setStartTimestamp(year int64, timestamp int64) { + assertValidYear(year) + h.startTimestamp[year-1] = timestamp +} -func init() { - startHeight = std.GetHeight() - startTimestamp = time.Now().Unix() +func (h *HalvingData) getMaxAmount(year int64) uint64 { + if year == 0 { + return 0 + } + return h.maxAmount[year-1] +} - // initialize halving data - initializeHalvingData() +func (h *HalvingData) setMaxAmount(year int64, amount uint64) { + assertValidYear(year) + h.maxAmount[year-1] = amount +} - // set end timestamp - // after 12 years, timestamp which gns emission will be ended - // it can not be changed - setEndTimestamp(startTimestamp + consts.TIMESTAMP_YEAR*HALVING_END_YEAR) +func (h *HalvingData) getMintedAmount(year int64) uint64 { + if year == 0 { + return 0 + } + return h.mintedAmount[year-1] } -// initializeHalvingData initializes the halving data -// it should be called only once, so we call this in init() -func initializeHalvingData() { +func (h *HalvingData) setMintedAmount(year int64, amount uint64) { + assertValidYear(year) + h.mintedAmount[year-1] = amount +} + +func (h *HalvingData) getLeftAmount(year int64) uint64 { + if year == 0 { + return 0 + } + return h.leftAmount[year-1] +} + +func (h *HalvingData) setLeftAmount(year int64, amount uint64) { + assertValidYear(year) + h.leftAmount[year-1] = amount +} + +func (h *HalvingData) getAccumAmount(year int64) uint64 { + if year == 0 { + return 0 + } + return h.accumAmount[year-1] +} + +func (h *HalvingData) setAccumAmount(year int64, amount uint64) { + assertValidYear(year) + h.accumAmount[year-1] = amount +} + +func (h *HalvingData) addAccumAmount(year int64, amount uint64) { + assertValidYear(year) + h.accumAmount[year-1] += amount +} + +func (h *HalvingData) getAmountPerBlock(year int64) uint64 { + if year == 0 { + return 0 + } + return h.amountPerBlock[year-1] +} + +func (h *HalvingData) setAmountPerBlock(year int64, amount uint64) { + assertValidYear(year) + h.amountPerBlock[year-1] = amount +} + +func (h *HalvingData) initialize(startHeight, startTimestamp int64) { for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { // set max emission amount per year // each year can not mint more than this amount currentYearMaxAmount := GetHalvingAmountsPerYear(year) - setHalvingYearMaxAmount(year, currentYearMaxAmount) + h.setMaxAmount(year, currentYearMaxAmount) if year == HALVING_START_YEAR { - setHalvingYearAccuAmount(year, currentYearMaxAmount) - setHalvingYearStartBlock(year, startHeight) - setHalvingYearEndBlock(year, startHeight+(blockPerYear*year)) + h.setAccumAmount(year, currentYearMaxAmount) + h.setStartBlockHeight(year, startHeight) + h.setEndBlockHeight(year, startHeight+(blockPerYear*year)-1) } else { // accumulate amount until current year, is the sum of current year max amount and accumulated amount until previous year - setHalvingYearAccuAmount(year, currentYearMaxAmount+GetHalvingYearAccuAmount(year-1)) + h.setAccumAmount(year, currentYearMaxAmount+h.getAccumAmount(year-1)) - // start block of current year, is the next block of previous year end block - setHalvingYearStartBlock(year, GetHalvingYearEndBlock(year-1)+1) + // start block of current year, is the next block of previous year of end block + h.setStartBlockHeight(year, h.getEndBlockHeight(year-1)+1) // end block of current year, is sum of start block and block per year - setHalvingYearEndBlock(year, GetHalvingYearStartBlock(year)+blockPerYear) + h.setEndBlockHeight(year, h.getStartBlockHeight(year)+blockPerYear-1) } - setHalvingYearTimestamp(year, startTimestamp+(consts.TIMESTAMP_YEAR*(year-1))) + h.setStartTimestamp(year, startTimestamp+(consts.TIMESTAMP_YEAR*(year-1))) amountPerDay := currentYearMaxAmount / consts.DAY_PER_YEAR amountPerBlock := amountPerDay / uint64(blockPerDay) - setAmountPerBlockPerHalvingYear(year, uint64(amountPerBlock)) + h.setAmountPerBlock(year, uint64(amountPerBlock)) + h.setMintedAmount(year, uint64(0)) + h.setLeftAmount(year, currentYearMaxAmount) + } +} + +type EmissionState struct { + startHeight int64 + startTimestamp int64 + endTimestamp int64 + halvingData HalvingData +} - setHalvingYearMintAmount(year, uint64(0)) +func GetEmissionState() *EmissionState { + if emissionState == nil { + emissionState = NewEmissionState() + emissionState.initializeHalvingData() + } + return emissionState +} + +func NewEmissionState() *EmissionState { + now := time.Now().Unix() + consts.BLOCK_GENERATION_INTERVAL + emissionEndTime := now + consts.TIMESTAMP_YEAR*HALVING_END_YEAR + + return &EmissionState{ + startHeight: std.GetHeight() + 1, + startTimestamp: now, + endTimestamp: emissionEndTime, + halvingData: HalvingData{ + startBlockHeight: make([]int64, HALVING_END_YEAR), + endBlockHeight: make([]int64, HALVING_END_YEAR), + startTimestamp: make([]int64, HALVING_END_YEAR), + maxAmount: make([]uint64, HALVING_END_YEAR), + mintedAmount: make([]uint64, HALVING_END_YEAR), + leftAmount: make([]uint64, HALVING_END_YEAR), + accumAmount: make([]uint64, HALVING_END_YEAR), + amountPerBlock: make([]uint64, HALVING_END_YEAR), + }, } } +func (e *EmissionState) getStartHeight() int64 { + return e.startHeight +} + +func (e *EmissionState) setStartHeight(height int64) { + e.startHeight = height +} + +func (e *EmissionState) getStartTimestamp() int64 { + return e.startTimestamp +} + +func (e *EmissionState) setStartTimestamp(timestamp int64) { + e.startTimestamp = timestamp +} + +func (e *EmissionState) getEndTimestamp() int64 { + return e.endTimestamp +} + +func (e *EmissionState) setEndTimestamp(timestamp int64) { + e.endTimestamp = timestamp +} + +func (e *EmissionState) getHalvingData() HalvingData { + return e.halvingData +} + +func (e *EmissionState) setHalvingData(data HalvingData) { + e.halvingData = data +} + +// initializeHalvingData initializes the halving data +// it should be called only once, so we call this in init() +func (e *EmissionState) initializeHalvingData() { + halvingData := e.getHalvingData() + halvingData.initialize(e.getStartHeight(), e.getStartTimestamp()) + e.setHalvingData(halvingData) +} + +var emissionState *EmissionState + +func init() { + emissionState = GetEmissionState() +} + func GetAvgBlockTimeInMs() int64 { return avgBlockTimeMs } // SetAvgBlockTimeInMsByAdmin sets the average block time in millisecond. func SetAvgBlockTimeInMsByAdmin(ms int64) { - caller := std.PrevRealm().Addr() - if err := common.AdminOnly(caller); err != nil { - panic(err) - } + assertCallerIsAdmin() + prevAvgBlockTimeInMs := GetAvgBlockTimeInMs() setAvgBlockTimeInMs(ms) - prevAddr, prevRealm := getPrev() + prevAddr, prevPkgPath := getPrev() std.Emit( "SetAvgBlockTimeInMsByAdmin", "prevAddr", prevAddr, - "prevRealm", prevRealm, - "ms", ufmt.Sprintf("%d", ms), + "prevRealm", prevPkgPath, + "prevAvgBlockTimeInMs", strconv.FormatInt(prevAvgBlockTimeInMs, 10), + "avgBlockTimeInMs", strconv.FormatInt(ms, 10), ) } // SetAvgBlockTimeInMs sets the average block time in millisecond. // Only governance contract can execute this function via proposal func SetAvgBlockTimeInMs(ms int64) { - caller := std.PrevRealm().Addr() - if err := common.GovernanceOnly(caller); err != nil { - panic(err) - } + assertCallerIsGovernance() + prevAvgBlockTimeInMs := GetAvgBlockTimeInMs() setAvgBlockTimeInMs(ms) - prevAddr, prevRealm := getPrev() + prevAddr, prevPkgPath := getPrev() std.Emit( "SetAvgBlockTimeInMs", "prevAddr", prevAddr, - "prevRealm", prevRealm, - "ms", ufmt.Sprintf("%d", ms), + "prevRealm", prevPkgPath, + "prevAvgBlockTimeInMs", strconv.FormatInt(prevAvgBlockTimeInMs, 10), + "avgBlockTimeInMs", strconv.FormatInt(ms, 10), ) } func setAvgBlockTimeInMs(ms int64) { - common.IsHalted() - - // update block per year - value1 := int64(consts.TIMESTAMP_YEAR * consts.SECOND_IN_MILLISECOND) - value2 := int64(value1 / ms) - blockPerYear = value2 + assertShouldNotBeHalted() now := time.Now().Unix() height := std.GetHeight() + // update block per year + yearByMilliSec := secToMs(consts.TIMESTAMP_YEAR) + blockPerYear = yearByMilliSec / ms + // get the halving year and end timestamp of current time - currentYear, endTimestamp := getHalvingYearAndEndTimestamp(now) + currentYear, currentYearEndTimestamp := getHalvingYearAndEndTimestamp(now) // how much time left for current halving year - timeLeft := endTimestamp - now - timeLeftMs := timeLeft * consts.SECOND_IN_MILLISECOND + timeLeft := currentYearEndTimestamp - now + timeLeftMs := secToMs(timeLeft) // how many block left for current halving year - blockLeft := (timeLeftMs / ms) + blockLeft := timeLeftMs / ms // how many reward left for current halving year minted := GetMintedEmissionAmount() amountLeft := GetHalvingYearAccuAmount(currentYear) - minted // how much reward should be minted per block for current halving year adjustedAmountPerBlock := amountLeft / uint64(blockLeft) - setAmountPerBlockPerHalvingYear(currentYear, adjustedAmountPerBlock) - // update it - amountPerBlockPerHalvingYear[currentYear] = adjustedAmountPerBlock + setAmountPerBlockPerHalvingYear(currentYear, adjustedAmountPerBlock) addPerBlockMintUpdate(uint64(std.GetHeight()), adjustedAmountPerBlock) for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { @@ -276,75 +399,92 @@ func GetHalvingYearByHeight(height int64) int64 { return 0 } -// getHalvingYearAndEndTimestamp returns the halving year and end timestamp of the given timestamp -// if the timestamp is not in any halving year, it returns 0, 0 -func getHalvingYearAndEndTimestamp(timestamp int64) (int64, int64) { - if timestamp > endTimestamp { // after 12 years - return 0, 0 - } - - timestamp -= startTimestamp - - year := timestamp / consts.TIMESTAMP_YEAR - year += 1 // since we subtract startTimestamp at line 215, we need to add 1 to get the correct year - - return year, startTimestamp + (consts.TIMESTAMP_YEAR * year) -} - func GetHalvingYearStartBlock(year int64) int64 { - return halvingYearStartBlock[year-1] + halvingData := GetEmissionState().getHalvingData() + return halvingData.getStartBlockHeight(year) } func setHalvingYearStartBlock(year int64, block int64) { - halvingYearStartBlock[year-1] = block + halvingData := GetEmissionState().getHalvingData() + halvingData.setStartBlockHeight(year, block) + GetEmissionState().setHalvingData(halvingData) } func GetHalvingYearEndBlock(year int64) int64 { - return halvingYearEndBlock[year-1] + halvingData := GetEmissionState().getHalvingData() + return halvingData.getEndBlockHeight(year) } func setHalvingYearEndBlock(year int64, block int64) { - halvingYearEndBlock[year-1] = block + halvingData := GetEmissionState().getHalvingData() + halvingData.setEndBlockHeight(year, block) + GetEmissionState().setHalvingData(halvingData) } func GetHalvingYearTimestamp(year int64) int64 { - return halvingYearTimestamp[year-1] + halvingData := GetEmissionState().getHalvingData() + return halvingData.getStartTimestamp(year) } func setHalvingYearTimestamp(year int64, timestamp int64) { - halvingYearTimestamp[year-1] = timestamp + halvingData := GetEmissionState().getHalvingData() + halvingData.setStartTimestamp(year, timestamp) + GetEmissionState().setHalvingData(halvingData) } func GetHalvingYearMaxAmount(year int64) uint64 { - return halvingYearMaxAmount[year-1] + halvingData := GetEmissionState().getHalvingData() + return halvingData.getMaxAmount(year) } func setHalvingYearMaxAmount(year int64, amount uint64) { - halvingYearMaxAmount[year-1] = amount + halvingData := GetEmissionState().getHalvingData() + halvingData.setMaxAmount(year, amount) + GetEmissionState().setHalvingData(halvingData) } func GetHalvingYearMintAmount(year int64) uint64 { - return halvingYearMintAmount[year-1] + halvingData := GetEmissionState().getHalvingData() + return halvingData.getMintedAmount(year) } func setHalvingYearMintAmount(year int64, amount uint64) { - halvingYearMintAmount[year-1] = amount + halvingData := GetEmissionState().getHalvingData() + halvingData.setMintedAmount(year, amount) + GetEmissionState().setHalvingData(halvingData) +} + +func GetHalvingYearLeftAmount(year int64) uint64 { + halvingData := GetEmissionState().getHalvingData() + return halvingData.getLeftAmount(year) +} + +func setHalvingYearLeftAmount(year int64, amount uint64) { + halvingData := GetEmissionState().getHalvingData() + halvingData.setLeftAmount(year, amount) + GetEmissionState().setHalvingData(halvingData) } func GetHalvingYearAccuAmount(year int64) uint64 { - return halvingYearAccuAmount[year-1] + halvingData := GetEmissionState().getHalvingData() + return halvingData.getAccumAmount(year) } func setHalvingYearAccuAmount(year int64, amount uint64) { - halvingYearAccuAmount[year-1] = amount + halvingData := GetEmissionState().getHalvingData() + halvingData.setAccumAmount(year, amount) + GetEmissionState().setHalvingData(halvingData) } func GetAmountPerBlockPerHalvingYear(year int64) uint64 { - return amountPerBlockPerHalvingYear[year-1] + halvingData := GetEmissionState().getHalvingData() + return halvingData.getAmountPerBlock(year) } func setAmountPerBlockPerHalvingYear(year int64, amount uint64) { - amountPerBlockPerHalvingYear[year-1] = amount + halvingData := GetEmissionState().getHalvingData() + halvingData.setAmountPerBlock(year, amount) + GetEmissionState().setHalvingData(halvingData) } func GetHalvingAmountsPerYear(year int64) uint64 { @@ -358,11 +498,11 @@ func GetEndHeight() int64 { } func GetEndTimestamp() int64 { - return endTimestamp + return GetEmissionState().getEndTimestamp() } func setEndTimestamp(timestamp int64) { - endTimestamp = timestamp + GetEmissionState().setEndTimestamp(timestamp) } func GetHalvingInfo() string { @@ -397,3 +537,50 @@ func GetHalvingInfo() string { func isEmissionEnded(height int64) bool { return height > GetEndHeight() } + +// getHalvingYearAndEndTimestamp returns the halving year and end timestamp of the given timestamp +// if the timestamp is not in any halving year, it returns 0, 0 +func getHalvingYearAndEndTimestamp(timestamp int64) (int64, int64) { + state := GetEmissionState() + + endTimestamp := state.getEndTimestamp() + startTimestamp := state.getStartTimestamp() + + if timestamp > endTimestamp { // after 12 years + return 0, 0 + } + + timestamp -= startTimestamp + + year := timestamp / consts.TIMESTAMP_YEAR + year += 1 // since we subtract startTimestamp at line 215, we need to add 1 to get the correct year + + return year, startTimestamp + (consts.TIMESTAMP_YEAR * year) +} + +func GetCurrentEmission() uint64 { + emission := uint64(0) + perBlockMint.ReverseIterate("", common.EncodeUint(uint64(std.GetHeight())), func(key string, value interface{}) bool { + emission = value.(uint64) + return true + }) + return emission +} + +func EmissionUpdates(startHeight uint64, endHeight uint64) ([]uint64, []uint64) { + heights := make([]uint64, 0) + updates := make([]uint64, 0) + println("EmissionUpdates : ", startHeight, ", ", endHeight) + perBlockMint.Iterate(common.EncodeUint(startHeight), common.EncodeUint(endHeight), func(key string, value interface{}) bool { + println("EmissionUpdates : ", key, ", ", value) + heights = append(heights, common.DecodeUint(key)) + updates = append(updates, value.(uint64)) + return false + }) + + return heights, updates +} + +func addPerBlockMintUpdate(height uint64, amount uint64) { + perBlockMint.Set(common.EncodeUint(height), amount) +} diff --git a/_deploy/r/gnoswap/gns/halving_test.gno b/_deploy/r/gnoswap/gns/halving_test.gno index 5010de73c..478fade0f 100644 --- a/_deploy/r/gnoswap/gns/halving_test.gno +++ b/_deploy/r/gnoswap/gns/halving_test.gno @@ -3,6 +3,7 @@ package gns import ( "std" "testing" + "time" "gno.land/p/demo/json" "gno.land/p/demo/uassert" @@ -13,21 +14,24 @@ import ( var ( govRealm = std.NewCodeRealm(consts.GOV_GOVERNANCE_PATH) adminRealm = std.NewUserRealm(consts.ADMIN) + + startHeight int64 = std.GetHeight() + startTimestamp int64 = time.Now().Unix() + consts.BLOCK_GENERATION_INTERVAL ) var FIRST_BLOCK_OF_YEAR = []int64{ - startHeight + (blockPerYear * 0) + 1, - startHeight + (blockPerYear * 1) + 2, - startHeight + (blockPerYear * 2) + 3, - startHeight + (blockPerYear * 3) + 4, - startHeight + (blockPerYear * 4) + 5, - startHeight + (blockPerYear * 5) + 6, - startHeight + (blockPerYear * 6) + 7, - startHeight + (blockPerYear * 7) + 8, - startHeight + (blockPerYear * 8) + 9, - startHeight + (blockPerYear * 9) + 10, - startHeight + (blockPerYear * 10) + 11, - startHeight + (blockPerYear * 11) + 12, + startHeight + (blockPerYear * 0), + startHeight + (blockPerYear * 1) + 1, + startHeight + (blockPerYear * 2) + 2, + startHeight + (blockPerYear * 3) + 3, + startHeight + (blockPerYear * 4) + 4, + startHeight + (blockPerYear * 5) + 5, + startHeight + (blockPerYear * 6) + 6, + startHeight + (blockPerYear * 7) + 7, + startHeight + (blockPerYear * 8) + 8, + startHeight + (blockPerYear * 9) + 9, + startHeight + (blockPerYear * 10) + 10, + startHeight + (blockPerYear * 11) + 11, } var FIRST_TIMESTAMP_OF_YEAR = []int64{ @@ -64,6 +68,21 @@ func TestGetAmountByHeight(t *testing.T) { for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { firstBlockOfYear := FIRST_BLOCK_OF_YEAR[year-1] uassert.Equal(t, GetAmountPerBlockPerHalvingYear(year), GetAmountByHeight(firstBlockOfYear)) + if year == HALVING_START_YEAR { + uassert.Equal(t, GetHalvingYearAccuAmount(year), GetHalvingYearMaxAmount(year)) + } else { + uassert.Equal(t, GetHalvingYearAccuAmount(year), GetHalvingYearAccuAmount(year-1)+GetHalvingYearMaxAmount(year)) + uassert.Equal(t, int64(1), GetHalvingYearStartBlock(year)-GetHalvingYearEndBlock(year-1)) + } + uassert.Equal(t, blockPerYear, (GetHalvingYearEndBlock(year) - GetHalvingYearStartBlock(year) + 1)) + + if year == HALVING_START_YEAR { + uassert.Equal(t, GetHalvingYearMaxAmount(year)+GetAmountPerBlockPerHalvingYear(year+1), calculateAmountToMint(GetHalvingYearStartBlock(year), GetHalvingYearStartBlock(year+1))) + } else if year == HALVING_END_YEAR { + uassert.Equal(t, GetHalvingYearMaxAmount(year)-GetAmountPerBlockPerHalvingYear(year), calculateAmountToMint(GetHalvingYearStartBlock(year)+1, GetHalvingYearEndBlock(year)+1)) + } else { + uassert.Equal(t, GetHalvingYearMaxAmount(year)-GetAmountPerBlockPerHalvingYear(year)+GetAmountPerBlockPerHalvingYear(year+1), calculateAmountToMint(GetHalvingYearStartBlock(year)+1, GetHalvingYearStartBlock(year+1))) + } } } diff --git a/_deploy/r/gnoswap/gns/utils.gno b/_deploy/r/gnoswap/gns/utils.gno index 6aa58902e..894a8d2c5 100644 --- a/_deploy/r/gnoswap/gns/utils.gno +++ b/_deploy/r/gnoswap/gns/utils.gno @@ -3,9 +3,11 @@ package gns import ( "std" + "gno.land/p/demo/ufmt" pusers "gno.land/p/demo/users" "gno.land/r/gnoswap/v1/common" + "gno.land/r/gnoswap/v1/consts" ) func getPrev() (string, string) { @@ -13,6 +15,10 @@ func getPrev() (string, string) { return prev.Addr().String(), prev.PkgPath() } +func getPrevAddr() std.Address { + return std.PrevRealm().Addr() +} + func a2u(addr std.Address) pusers.AddressOrName { return pusers.AddressOrName(addr) } @@ -22,15 +28,39 @@ func assertShouldNotBeHalted() { } func assertCallerIsEmission() { - caller := std.PrevRealm().Addr() + caller := getPrevAddr() if err := common.EmissionOnly(caller); err != nil { panic(err) } } +func assertCallerIsAdmin() { + caller := getPrevAddr() + if err := common.AdminOnly(caller); err != nil { + panic(err) + } +} + +func assertCallerIsGovernance() { + caller := getPrevAddr() + if err := common.GovernanceOnly(caller); err != nil { + panic(err) + } +} + +func assertValidYear(year int64) { + if year < HALVING_START_YEAR || year > HALVING_END_YEAR { + panic(addDetailToError(errInvalidYear, ufmt.Sprintf("year: %d", year))) + } +} + func i64Min(x, y int64) int64 { if x < y { return x } return y } + +func secToMs(sec int64) int64 { + return sec * consts.SECOND_IN_MILLISECOND +}