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-1145 feat: distribution calculation in emission #249

Merged
merged 6 commits into from
Jul 4, 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
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
jungju marked this conversation as resolved.
Show resolved Hide resolved

// 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{
jungju marked this conversation as resolved.
Show resolved Hide resolved
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) {
jungju marked this conversation as resolved.
Show resolved Hide resolved
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
}

jungju marked this conversation as resolved.
Show resolved Hide resolved
// 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))
jungju marked this conversation as resolved.
Show resolved Hide resolved
}
}
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)
}