Skip to content

Commit

Permalink
GSW-1845 refactor: gns, calculate amount to mint
Browse files Browse the repository at this point in the history
  • Loading branch information
r3v4s committed Dec 11, 2024
1 parent 78dfa75 commit 36c3802
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 230 deletions.
60 changes: 60 additions & 0 deletions _deploy/r/gnoswap/gns/calculate_amount.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package gns

// 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)

amountToMint := uint64(0)

for i := fromYear; i <= toYear; i++ {
yearEndHeight := halvingYearBlock[i]
mintUntilHeight := yearEndHeight

if toHeight < mintUntilHeight {
mintUntilHeight = toHeight
}

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

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

// total amount of gns to mint for current year
amountToMint += singleBlockAmount * numBlock

// if last block of halving year, handle left emission amount
if isLastBlockOfHalvingYear(mintUntilHeight) {
amountToMint += handleLeftEmissionAmount(i, amountToMint)
}

// update halving year mint amount
halvingYearMintAmount[i] += amountToMint

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

return amountToMint
}

// isLastBlockOfHalvingYear returns true if the current block is the last block of a halving year.
func isLastBlockOfHalvingYear(height int64) bool {
year := GetHalvingYearByHeight(height)
lastBlock := halvingYearBlock[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 halvingYearAmount[year] - halvingYearMintAmount[year] - amount
}

func setLeftEmissionAmount(amount uint64) {
leftEmissionAmount = amount
}
97 changes: 97 additions & 0 deletions _deploy/r/gnoswap/gns/calculate_amount_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package gns

import (
"fmt"
"testing"

"gno.land/p/demo/uassert"
)

func TestCalculateAmountToMint(t *testing.T) {
// back up previous data
prevHalvingYearAmount := halvingYearMintAmount

t.Run("1 block for same year 01", func(t *testing.T) {
amount := calculateAmountToMint(10, 10)
uassert.Equal(t, amountPerBlockPerHalvingYear[1], amount)
})

t.Run("entire block for year 01 + 1 block for year 02", func(t *testing.T) {
amount := calculateAmountToMint(halvingYearBlock[1], halvingYearBlock[1]+2)
uassert.Equal(t, halvingYearAmount[1]+amountPerBlockPerHalvingYear[2], amount)
})

// restore previous data
halvingYearMintAmount = prevHalvingYearAmount
}

func TestIsLastBlockOfHalvingYear(t *testing.T) {
tests := make([]struct {
name string
height int64
want bool
}, 0, 24)

for i := int64(1); i <= 12; i++ {
tests = append(tests, struct {
name string
height int64
want bool
}{
name: fmt.Sprintf("last block of halving year %d", i),
height: halvingYearBlock[i],
want: true,
})

tests = append(tests, struct {
name string
height int64
want bool
}{
name: fmt.Sprintf("not last block of halving year %d", i),
height: halvingYearBlock[i] - 1,
want: false,
})
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
uassert.Equal(t, tt.want, isLastBlockOfHalvingYear(tt.height))
})
}
}

func TestHandleLeftEmissionAmount(t *testing.T) {
tests := make([]struct {
name string
year int64
amount uint64
want uint64
}, 0, 24)

for i := int64(1); i <= 12; i++ {
tests = append(tests, struct {
name string
year int64
amount uint64
want uint64
}{
name: fmt.Sprintf("handle left emission amount for year %d, non minted", i),
year: i,
amount: 0,
want: halvingYearAmount[i],
})

tests = append(tests, struct {
name string
year int64
amount uint64
want uint64
}{
name: fmt.Sprintf("handle left emission amount for year %d, minted", i),
year: i,
amount: uint64(123456),
want: halvingYearAmount[i] - uint64(123456),
})
}
}
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
)
156 changes: 68 additions & 88 deletions _deploy/r/gnoswap/gns/gns.gno
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,78 @@ import (
"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 = MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT
)

var (
lastMintedHeight = std.GetHeight()
)

var (
lastMintedHeight int64
amountToEmission uint64
leftEmissionAmount = MAX_EMISSION_AMOUNT
)

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, "")
}

amountToEmission = MAXIMUM_SUPPLY - uint64(100_000_000_000_000)
// 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
}

lastMintedHeight = std.GetHeight()
func Mint(address pusers.AddressOrName) uint64 {
common.IsHalted()

caller := std.PrevRealm().Addr()
if err := common.EmissionOnly(caller); err != nil {
panic(err)
}

lastMintedHeight := GetLastMintedHeight()
currentHeight := std.GetHeight()

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

// 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)
setLeftEmissionAmount(leftEmissionAmount - 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 +132,34 @@ 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()),
))
}

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

// 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)

if lastMintedHeight >= nowHeight {
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
}
}

err := privateLedger.Mint(users.Resolve(address), amountToMint)
func checkErr(err error) {
if err != nil {
panic(err.Error())
}
}

lastMintedHeight = nowHeight

return amountToMint
func isInitialized() bool {
return initialized
}

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

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
}
func setLastMintedHeight(height int64) {
lastMintedHeight = height
}

return amount
func GetLeftEmissionAmount() uint64 {
return leftEmissionAmount
}

func checkErr(err error) {
if err != nil {
panic(err.Error())
// 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 {
if lastMintedHeight == currentHeight {
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.
func GetLastMintedHeight() int64 {
return lastMintedHeight
return false
}
Loading

0 comments on commit 36c3802

Please sign in to comment.