Skip to content

Commit

Permalink
GSW-1845 refactor: gns.gno (#436)
Browse files Browse the repository at this point in the history
* GSW-1845 fix: remove deprecated require in gno.mod

* GSW-1845 refactor: separate math logic from mint()

* refactor: change function name to aviod confusion
- from `Mint` to `MintGns`

* test: reset gns token object

* chore

* feat: check functino to whether emission ended or not

* test: detail unit test

* test: mint amount test based on height skipping

* feat: condition helper

* refactor: better readability

* fix: tc

* Update _deploy/r/gnoswap/gns/gns.gno

Co-authored-by: Dongwon <[email protected]>

* Update _deploy/r/gnoswap/gns/gns.gno

Co-authored-by: Lee ByeongJun <[email protected]>

* chore: categorize getter/setter

* feat: use min64 to make caluclate logic more straightforward

* test: check burn method(burnt amount) effection on mintedAmount, leftEmissionAmount

* chore: simplifiy skipIfSameHeight

* fix: typo

* chore: comments

---------

Co-authored-by: Dongwon <[email protected]>
Co-authored-by: Lee ByeongJun <[email protected]>
  • Loading branch information
3 people authored Dec 19, 2024
1 parent 1e9c270 commit 38d013a
Show file tree
Hide file tree
Showing 10 changed files with 627 additions and 222 deletions.
20 changes: 20 additions & 0 deletions _deploy/r/gnoswap/gns/_helper_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package gns

import (
"testing"

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

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

func testResetGnsTokenObject(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)
}
12 changes: 0 additions & 12 deletions _deploy/r/gnoswap/gns/gno.mod
Original file line number Diff line number Diff line change
@@ -1,13 +1 @@
module gno.land/r/gnoswap/v1/gns

require (
gno.land/p/demo/grc/grc20 v0.0.0-latest
gno.land/p/demo/json v0.0.0-latest
gno.land/p/demo/ownable v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/p/demo/users v0.0.0-latest
gno.land/r/demo/users v0.0.0-latest
gno.land/r/demo/grc20reg v0.0.0-latest
gno.land/r/gnoswap/v1/common v0.0.0-latest
gno.land/r/gnoswap/v1/consts v0.0.0-latest
)
209 changes: 130 additions & 79 deletions _deploy/r/gnoswap/gns/gns.gno
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,81 @@ import (
"gno.land/r/demo/grc20reg"
"gno.land/r/demo/users"

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

const MAXIMUM_SUPPLY = uint64(1_000_000_000_000_000) // 1B
const (
MAXIMUM_SUPPLY = uint64(1_000_000_000_000_000)
INITIAL_MINT_AMOUNT = uint64(100_000_000_000_000)
MAX_EMISSION_AMOUNT = uint64(900_000_000_000_000) // MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT
)

var (
lastMintedHeight = std.GetHeight()
)

var (
lastMintedHeight int64
amountToEmission uint64
// 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)
)

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

func init() {
privateLedger.Mint(owner.Owner(), 100_000_000_000_000) // 100_000_000 GNS ≈ 0.1B
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
}

amountToEmission = MAXIMUM_SUPPLY - uint64(100_000_000_000_000)
func MintGns(address pusers.AddressOrName) uint64 {
lastMintedHeight := GetLastMintedHeight()
currentHeight := std.GetHeight()

lastMintedHeight = std.GetHeight()
// skip minting process if gns for current block is already minted
if skipIfSameHeight(lastMintedHeight, currentHeight) {
return 0
}

assertShouldNotBeHalted()
assertCallerIsEmission()

// calculate gns amount to mint, and the mint to the target address
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)

return amountToMint
}

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

func TotalSupply() uint64 {
return UserTeller.TotalSupply()
}

func TotalMinted() uint64 {
return UserTeller.TotalSupply() - uint64(100_000_000_000_000)
}

func BalanceOf(owner pusers.AddressOrName) uint64 {
ownerAddr := users.Resolve(owner)
return UserTeller.BalanceOf(ownerAddr)
Expand Down Expand Up @@ -93,93 +131,106 @@ func Render(path string) string {
}
}

// Mint mints GNS to the address.
// Only emission contract can call Mint.
func Mint(address pusers.AddressOrName) uint64 {
common.IsHalted()

caller := std.PrevRealm().Addr()
if caller != consts.EMISSION_ADDR {
panic(addDetailToError(
errNoPermission,
ufmt.Sprintf("only emission contract(%s) can call Mint, called from %s", consts.EMISSION_ADDR, caller.String()),
))
func checkErr(err error) {
if err != nil {
panic(err.Error())
}
}

// if not yet initialized, mint 0 amount
if initialized == false {
return 0
}
// helper functions

// calculate gns emission amount for every block, and send by single call
// for this case, we assume that inside of block range gnoswap state hasn't changed.
nowHeight := std.GetHeight()
amountToMint := uint64(0)
// 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 lastMintedHeight >= nowHeight {
if isEmissionEnded(fromYear) || isEmissionEnded(toYear) {
return 0
}

// If from, to block is at same halving year, no need iterate
fromYear := GetHalvingYearByHeight(lastMintedHeight + 1)
toYear := GetHalvingYearByHeight(nowHeight)

if fromYear == toYear {
numBlock := uint64(nowHeight - lastMintedHeight)
singleBlockAmount := GetAmountByHeight(nowHeight)
totalBlockAmount := singleBlockAmount * numBlock

amountToMint += totalBlockAmount
amountToMint = checkAndHandleIfLastBlockOfHalvingYear(nowHeight, amountToMint)

halvingYearMintAmount[fromYear] += totalBlockAmount
} else {
for i := lastMintedHeight + 1; i <= nowHeight; i++ {
amount := GetAmountByHeight(i)
amount = checkAndHandleIfLastBlockOfHalvingYear(i, amount)
year := GetHalvingYearByHeight(i)
halvingYearMintAmount[year] += amount
amountToMint += amount
totalAmountToMint := uint64(0)

for i := fromYear; i <= toYear; i++ {
yearEndHeight := GetHalvingYearBlock(i)
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)

// 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)
}
}
totalAmountToMint += yearAmountToMint
SetHalvingYearMintAmount(i, GetHalvingYearMintAmount(i)+yearAmountToMint)

err := privateLedger.Mint(users.Resolve(address), amountToMint)
if err != nil {
panic(err.Error())
// update fromHeight for next year (if necessary)
fromHeight = mintUntilHeight + 1
}

lastMintedHeight = nowHeight

return amountToMint
return totalAmountToMint
}

func Burn(from pusers.AddressOrName, amount uint64) {
owner.AssertCallerIsOwner()
fromAddr := users.Resolve(from)
checkErr(privateLedger.Burn(fromAddr, amount))
// 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)

return height == lastBlock
}

func checkAndHandleIfLastBlockOfHalvingYear(height int64, amount uint64) uint64 {
year := GetHalvingYearByHeight(height)
lastBlock := halvingYearBlock[year]
if height == lastBlock {
leftForThisYear := halvingYearAmount[year] - halvingYearMintAmount[year]
amount = leftForThisYear
return amount
}
// 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 amount
// skipIfSameHeight returns true if the current block height is the same as the last minted height.
// This prevents multiple gns minting inside the same block.
func skipIfSameHeight(lastMintedHeight, currentHeight int64) bool {
return lastMintedHeight == currentHeight
}

func checkErr(err error) {
if err != nil {
panic(err.Error())
// 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
}

return true
}

// TODO:
// 1. when emission contract mint gns reward, last executed height should be get from gns contract.
// mint function of gns contract and mintGns function of emission contract should be synchronized.
// Getter
func GetLastMintedHeight() int64 {
return lastMintedHeight
}

func GetLeftEmissionAmount() uint64 {
return leftEmissionAmount
}

func GetMintedEmissionAmount() uint64 {
return mintedEmissionAmount
}

// Setter
func setLastMintedHeight(height int64) {
lastMintedHeight = height
}

func setLeftEmissionAmount(amount uint64) {
leftEmissionAmount = amount
}

func setMintedEmissionAmount(amount uint64) {
mintedEmissionAmount = amount
}
Loading

0 comments on commit 38d013a

Please sign in to comment.