Skip to content

Commit

Permalink
GSW-1145 feat: distribution calculation in emission (#249)
Browse files Browse the repository at this point in the history
* GSW-1145 feat: distribution calculation in emission
  • Loading branch information
r3v4s authored Jul 4, 2024
1 parent 87a30e8 commit 86430b7
Show file tree
Hide file tree
Showing 8 changed files with 364 additions and 1 deletion.
4 changes: 3 additions & 1 deletion _deploy/r/demo/gnoswap/consts_halving/consts.gno
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
const (
GNOSWAP_ADMIN std.Address = "g13f63ua8uhmuf9mgc0x8zfz04yrsaqh7j78vcgq" // GSA
FEE_COLLECTOR std.Address = "g18sp3hq6zqfxw88ffgz773gvaqgzjhxy62l9906" // FCL
DEV_OPS std.Address = "g1mjvd83nnjee3z2g7683er55me9f09688pd4mj9" // DevOps

INTERNAL_REWARD_ACCOUNT std.Address = "g1jms5fx2raq4qfkq3502mfh25g54nyl5qeuvz5y" // IRA for GNS
BLOCK_GENERATION_INTERVAL int64 = 5 // 5 seconds
Expand Down Expand Up @@ -91,5 +92,6 @@ const (

// ETCs
const (
ZERO_ADDRESS std.Address = ""
// REF: https://github.com/gnolang/gno/pull/2401#discussion_r1648064219
ZERO_ADDRESS std.Address = "g100000000000000000000000000000000dnmcnx"
)
10 changes: 10 additions & 0 deletions _deploy/r/demo/gns_halving/gns.gno
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,13 @@ func Mint(address pusers.AddressOrName) {
// println("height:", i, "minted:", amount)
}
}

// XXX: Remove this
// ONLY FOR EMISSION TESTING
func TestSetLastMintedHeight(height int64) {
caller := std.PrevRealm().Addr()
if caller != consts.EMISSION_ADDR {
panic("only emission contract can call TestSetLastMintedHeight")
}
lastMintedHeight = height
}
62 changes: 62 additions & 0 deletions emission/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package emission

import (
"std"
"testing"

"gno.land/r/demo/gns"

"gno.land/r/demo/gnoswap/consts"

pusers "gno.land/p/demo/users"
)

var (
gsa std.Address = consts.GNOSWAP_ADMIN
)

// Realms to mock frames
var (
gsaRealm = std.NewUserRealm(gsa)
govRealm = std.NewCodeRealm(consts.GOV_PATH)
)

func gnsBalance(addr std.Address) uint64 {
a2u := pusers.AddressOrName(addr)

return gns.BalanceOf(a2u)
}

func shouldEQ(t *testing.T, got, expected interface{}) {
if got != expected {
t.Errorf("got %v, expected %v", got, expected)
}
}

func shouldNEQ(t *testing.T, got, expected interface{}) {
if got == expected {
t.Errorf("got %v, didn't expected %v", got, expected)
}
}

func shouldPanic(t *testing.T, f func()) {
defer func() {
if r := recover(); r == nil {
t.Errorf("expected panic")
}
}()
f()
}

func shouldPanicWithMsg(t *testing.T, f func(), msg string) {
defer func() {
if r := recover(); r == nil {
t.Errorf("The code did not panic")
} else {
if r != msg {
t.Errorf("excepted panic(%v), got(%v)", msg, r)
}
}
}()
f()
}
69 changes: 69 additions & 0 deletions emission/__TEST_distribution_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package emission

import (
"std"
"testing"

"gno.land/r/demo/gns"
)

func TestEmitGns(t *testing.T) {
shouldEQ(t, gns.TotalSupply(), 100000000000000) // GSA has
shouldEQ(t, gnsBalance(emissionAddr), 0)

EmitGns() // 1 ~ 123 height

shouldEQ(t, gnsBalance(emissionAddr), 4387842345)
shouldEQ(t, gns.TotalSupply(), 100000000000000+4387842345)

shouldEQ(t, std.GetHeight(), 123)
}

func TestDistributeToTarget(t *testing.T) {
shouldEQ(t, gnsBalance(emissionAddr), 4387842345)
DistributeToTarget(gnsBalance(emissionAddr))
shouldEQ(t, gnsBalance(emissionAddr), 1) // 1 left
}

func TestDistributeToTargetAfter5Block(t *testing.T) {
std.TestSkipHeights(5)
EmitGns()
shouldEQ(t, gnsBalance(emissionAddr), 178367576)

DistributeToTarget(gnsBalance(emissionAddr))
shouldEQ(t, gnsBalance(emissionAddr), 1) // 1 left again
}

func TestChangeDistributionPctByAdmin(t *testing.T) {
std.TestSetRealm(gsaRealm)

shouldEQ(t, GetDistributionPct(LIQUIDITY_STAKING), 7500)
shouldEQ(t, GetDistributionPct(DEVOPS), 2000)

ChangeDistributionPct02(
1, 5000,
2, 4500,
)
shouldEQ(t, GetDistributionPct(LIQUIDITY_STAKING), 5000)
shouldEQ(t, GetDistributionPct(DEVOPS), 4500)

ChangeDistributionPct03(
1, 5000,
2, 4000,
3, 1000,
)
shouldEQ(t, GetDistributionPct(LIQUIDITY_STAKING), 5000)
shouldEQ(t, GetDistributionPct(DEVOPS), 4000)
shouldEQ(t, GetDistributionPct(COMMUNITY_POOL), 1000)

ChangeDistributionPct04(
1, 10000,
2, 0,
3, 0,
4, 0,
)
shouldEQ(t, GetDistributionPct(LIQUIDITY_STAKING), 10000)
shouldEQ(t, GetDistributionPct(DEVOPS), 0)
shouldEQ(t, GetDistributionPct(COMMUNITY_POOL), 0)
shouldEQ(t, GetDistributionPct(XGNS), 0)
}
41 changes: 41 additions & 0 deletions emission/__TEST_emission_test.gnoA
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package emission

import (
"std"
"testing"

"gno.land/r/demo/gns"
)

func TestEmitGns(t *testing.T) {
shouldEQ(t, gns.TotalSupply(), 100000000000000) // GSA has
shouldEQ(t, gnsBalance(emissionAddr), 0)

EmitGns() // 1 ~ 123 height

shouldEQ(t, gnsBalance(emissionAddr), 4387842345)
shouldEQ(t, gns.TotalSupply(), 100000000000000+4387842345)

shouldEQ(t, std.GetHeight(), 123)
}

func TestEmitGnsSameBlock(t *testing.T) {
// request mint again in same block => do not mint again
// it may happen because single block can have multiple txs & msgs
EmitGns()
shouldEQ(t, gns.TotalSupply(), 100000000000000+4387842345)
}

func TestEmitGnsAllAmount(t *testing.T) {
std.TestSkipHeights(75686400 - 1)
gns.TestSetLastMintedHeight(std.GetHeight())
std.TestSkipHeights(1)

EmitGns()
shouldEQ(t, gns.TotalSupply(), 100000000000000+4387842345+2229594)
// all emission duration has been passed

std.TestSkipHeights(1)
EmitGns() // since all emission has been done, no more minting
shouldEQ(t, gns.TotalSupply(), 100000000000000+4387842345+2229594)
}
154 changes: 154 additions & 0 deletions emission/distribution.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package emission

import (
"std"

"gno.land/r/demo/gnoswap/consts"
"gno.land/r/demo/gns"

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

// emissionTarget represents different targets for token emission.
type emissionTarget int

// distributionPctMap maps emission targets to their respective distribution percentages.
type distributionPctMap map[emissionTarget]uint64

const (
LIQUIDITY_STAKING emissionTarget = iota + 1
DEVOPS
COMMUNITY_POOL
XGNS
)

// distributionPct defines the distribution percentages.
var distributionPct distributionPctMap = distributionPctMap{
LIQUIDITY_STAKING: 7500, // 75%
DEVOPS: 2000, // 20%
COMMUNITY_POOL: 500, // 5%
XGNS: 0, // 0%
}

// DistributeToTarget distributes the specified amount to different targets based on their percentages.
func DistributeToTarget(amount uint64) {
totalSent := uint64(0)
for target, pct := range distributionPct {
distAmount := calculateAmount(amount, pct)
totalSent += distAmount

transferToTarget(target, distAmount)
}

// `amount-totalSent` can be left due to rounding
// it will be distributed next time
}

// GetDistributionPct returns the distribution percentage for the given target.
func GetDistributionPct(target emissionTarget) uint64 {
return distributionPct[target]
}

// ChangeDistributionPct01 changes the distribution percentage for the given single target.
func ChangeDistributionPct01(target01 emissionTarget, pct01 uint64) {
changeDistributionPct(target01, pct01)

checkSumDistributionPct()
}

// ChangeDistributionPct02 changes the distribution percentage for the given two targets.
func ChangeDistributionPct02(
target01 emissionTarget, pct01 uint64,
target02 emissionTarget, pct02 uint64,
) {
changeDistributionPct(target01, pct01)
changeDistributionPct(target02, pct02)

checkSumDistributionPct()
}

// ChangeDistributionPct03 changes the distribution percentage for the given three targets.
func ChangeDistributionPct03(
target01 emissionTarget, pct01 uint64,
target02 emissionTarget, pct02 uint64,
target03 emissionTarget, pct03 uint64,
) {
changeDistributionPct(target01, pct01)
changeDistributionPct(target02, pct02)
changeDistributionPct(target03, pct03)

checkSumDistributionPct()
}

// ChangeDistributionPct04 changes the distribution percentage for the given four targets.
func ChangeDistributionPct04(
target01 emissionTarget, pct01 uint64,
target02 emissionTarget, pct02 uint64,
target03 emissionTarget, pct03 uint64,
target04 emissionTarget, pct04 uint64,
) {
changeDistributionPct(target01, pct01)
changeDistributionPct(target02, pct02)
changeDistributionPct(target03, pct03)
changeDistributionPct(target04, pct04)

checkSumDistributionPct()
}

// calculateAmount calculates the amount based on the given percentage in basis points.
func calculateAmount(amount, bptPct uint64) uint64 {
return amount * bptPct / 10000
}

// transferToTarget transfers the specified amount to the given addresses.
func transferToTarget(target emissionTarget, amount uint64) {
switch target {
case LIQUIDITY_STAKING:
// transfer to staker contract
gns.Transfer(a2u(consts.STAKER_ADDR), amount)
case DEVOPS:
// transfer to devops
gns.Transfer(a2u(consts.DEV_OPS), amount)
case COMMUNITY_POOL:
// TBD, transfer to community pool
gns.Transfer(a2u(consts.ZERO_ADDRESS), amount)
case XGNS:
// TBD, transfer to xGNS
gns.Transfer(a2u(consts.ZERO_ADDRESS), amount)
default:
panic("invalid target")
}
}

// changeDistributionPct changes the distribution percentage for the given target.
func changeDistributionPct(target emissionTarget, pct uint64) {
// only admin or governance can change
caller := std.PrevRealm().Addr()
if caller != consts.GNOSWAP_ADMIN && caller != consts.GOV_ADDR {
panic("only admin or governance can change distribution percentages")
}

// cannot add new target
if target != LIQUIDITY_STAKING && target != DEVOPS && target != COMMUNITY_POOL && target != XGNS {
panic("invalid target")
}

// Maximum pct for a single target is 10000 basis points (100%)
if pct > 10000 {
panic("percentage too high")
}

distributionPct[target] = pct
}

// checkSumDistributionPct ensures the sum of all distribution percentages is 100%
func checkSumDistributionPct() {
sum := uint64(0)
for _, pct := range distributionPct {
sum += pct
}

if sum != 10000 {
panic(ufmt.Sprintf("sum of all pct should be 100%% (10000 bps), got %d\n", sum))
}
}
14 changes: 14 additions & 0 deletions emission/emission.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package emission

import (
"std"

"gno.land/r/demo/gnoswap/consts"
"gno.land/r/demo/gns"
)

var emissionAddr std.Address = consts.EMISSION_ADDR

func EmitGns() {
gns.Mint(a2u(emissionAddr))
}
11 changes: 11 additions & 0 deletions emission/utils.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package emission

import (
"std"

pusers "gno.land/p/demo/users"
)

func a2u(addr std.Address) pusers.AddressOrName {
return pusers.AddressOrName(addr)
}

0 comments on commit 86430b7

Please sign in to comment.