Skip to content

Commit

Permalink
Merge pull request #84 from gnoswap-labs/GSW-358-allow-creating-exter…
Browse files Browse the repository at this point in the history
…nal-incentive-using-same-reward-token

GSW-358 feat: use caller address when calculation incentive id
  • Loading branch information
notJoon committed Nov 8, 2023
2 parents bbd8bbf + 63dce52 commit 50e6ad8
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@ func TestUnstakeToken(t *testing.T) {
}

func TestEndExternalIncentive(t *testing.T) {
std.TestSetOrigCaller(lp01)
std.TestSetOrigCaller(ci01)
std.TestSkipHeights(9999999)
EndExternalIncentive("gno.land/r/bar:gno.land/r/foo:500", "gno.land/r/obl") // use same parameter as CreateExternalIncentive()
EndExternalIncentive(GetOrigCaller().String(), "gno.land/r/bar:gno.land/r/foo:500", "gno.land/r/obl") // use same parameter as CreateExternalIncentive()
std.TestSkipHeights(1)

shouldEQ(t, len(incentives), 0)
Expand Down
260 changes: 260 additions & 0 deletions staker/_TEST_staker_one_increase_external_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
package staker

import (
"std"
"testing"

"encoding/gjson"

"gno.land/p/demo/testutils"

g "gno.land/r/gov"
p "gno.land/r/pool"
pos "gno.land/r/position"

gnft "gno.land/r/gnft" // GNFT, Gnoswap NFT
gns "gno.land/r/gns" // GNS, Gnoswap Share
obl "gno.land/r/obl"

_ "gno.land/r/grc20_wrapper"
)

var (
pc01 = testutils.TestAddress("pc01") // Pool Creator
ci01 = testutils.TestAddress("ci01") // Create Incentive Caller
lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01
lp02 = testutils.TestAddress("lp02") // Liquidity Provider 02

ira = testutils.TestAddress("ira") // Internal Reward Account
)

var (
fooPath = "gno.land/r/foo"
barPath = "gno.land/r/bar"
)

func init() {
// init pool tiers
// tier 1
poolTiers["gno.land/r/bar:gno.land/r/foo:500"] = 1 // DEV

// tier 2
poolTiers["GNS/USDT_500"] = 2
poolTiers["ATOM/GNS_500"] = 2

// tier 3
poolTiers["ATOM/GNOT_500"] = 3
poolTiers["ATOM/USDT_500"] = 3
poolTiers["ATOM/WETH_500"] = 3
}

func TestPoolInitCreatePool(t *testing.T) {
std.TestSetOrigCaller(pc01)

p.InitManual()
std.TestSkipHeights(1)

p.CreatePool(fooPath, barPath, 500, 130621891405341611593710811006)
std.TestSkipHeights(1)
}

func TestPositionMint(t *testing.T) {
{
std.TestSetOrigCaller(lp01)
tPosTokenId, tPosLiquidity, tPosAmount0, tPosAmount1 := pos.Mint(
fooPath, // token0
barPath, // token1
uint16(500), // fee
int32(9000), // tickLower
int32(11000), // tickUpper
bigint(1000), // amount0Desired
bigint(1000), // amount1Desired
bigint(1), // amount0Min
bigint(1), // amount1Min
bigint(2345678901), // deadline
)
std.TestSkipHeights(1)

shouldEQ(t, tPosTokenId, 1)
shouldEQ(t, gnft.OwnerOf(tid(tPosTokenId)), GetOrigCaller()) // lp01

// approve nft to staker
std.TestSetPrevAddr(lp01)
gnft.Approve(a2u(GetOrigPkgAddr()), tid(tPosTokenId))
std.TestSkipHeights(1)
}

{
std.TestSetOrigCaller(lp02)
tPosTokenId, tPosLiquidity, tPosAmount0, tPosAmount1 := pos.Mint(
fooPath, // token0
barPath, // token1
uint16(500), // fee
int32(9100), // tickLower
int32(12000), // tickUpper
bigint(5000), // amount0Desired
bigint(5000), // amount1Desired
bigint(1), // amount0Min
bigint(1), // amount1Min
bigint(2345678901), // deadline
)
std.TestSkipHeights(1)

shouldEQ(t, tPosTokenId, 2)
shouldEQ(t, gnft.OwnerOf(tid(tPosTokenId)), GetOrigCaller()) // lp02

// approve nft to staker
std.TestSetPrevAddr(lp02)
gnft.Approve(a2u(GetOrigPkgAddr()), tid(tPosTokenId))
std.TestSkipHeights(1)
}
}

func TestCreateExternalIncentive(t *testing.T) {
std.TestSetOrigCaller(ci01)

CreateExternalIncentive(
"gno.land/r/bar:gno.land/r/foo:500", // targetPoolPath
"gno.land/r/obl", // rewardToken
10_000_000_000, // rewardAmount
GetTimestamp(), // startTimestamp
GetTimestamp()+TIMESTAMP_90DAYS, // endTimestamp
)
CreateExternalIncentive("gno.land/r/bar:gno.land/r/foo:500", "gno.land/r/obl", 10_000_000_000, GetTimestamp(), GetTimestamp()+TIMESTAMP_90DAYS)
std.TestSkipHeights(5)
}

func TestStakeToken(t *testing.T) {
{
std.TestSetOrigCaller(lp01)
StakeToken(1) // GNFT tokenId
std.TestSkipHeights(2)

shouldEQ(t, gnft.OwnerOf(tid(1)), GetOrigPkgAddr()) // staker
shouldEQ(t, len(deposits), 1)
}

{
std.TestSetOrigCaller(lp02)
StakeToken(2) // GNFT tokenId
std.TestSkipHeights(2)

shouldEQ(t, gnft.OwnerOf(tid(2)), GetOrigPkgAddr()) // staker
shouldEQ(t, len(deposits), 2)
}
}

func TestApiGetRewardsByAddress(t *testing.T) {
{
// lp01 reward check
gra := ApiGetRewardByAddress(lp01)
jsonStr := gjson.Parse(gra)
shouldEQ(t, jsonStr.Get("response.data.0.type").String(), "Internal")
shouldEQ(t, jsonStr.Get("response.data.0.token").String(), "GNS")
shouldEQ(t, jsonStr.Get("response.data.0.reward").Int(), 252)
shouldEQ(t, jsonStr.Get("response.data.1.type").String(), "External")
shouldEQ(t, jsonStr.Get("response.data.1.token").String(), "gno.land/r/obl")
shouldEQ(t, jsonStr.Get("response.data.1.reward").Int(), 648)
}

{
// lp02 reward check
gra := ApiGetRewardByAddress(lp02)
jsonStr := gjson.Parse(gra)
shouldEQ(t, jsonStr.Get("response.data.0.type").String(), "Internal")
shouldEQ(t, jsonStr.Get("response.data.0.token").String(), "GNS")
shouldEQ(t, jsonStr.Get("response.data.0.reward").Int(), 1397)
shouldEQ(t, jsonStr.Get("response.data.1.type").String(), "External")
shouldEQ(t, jsonStr.Get("response.data.1.token").String(), "gno.land/r/obl")
shouldEQ(t, jsonStr.Get("response.data.1.reward").Int(), 3595)
}
}

func TestUnstakeToken(t *testing.T) {
{
std.TestSetOrigCaller(lp01)
UnstakeToken(1) // GNFT tokenId
std.TestSkipHeights(1)

shouldEQ(t, gnft.OwnerOf(tid(1)), lp01)

// check reward
shouldEQ(t, gns.BalanceOf(a2u(lp01)), 252) // internal
shouldEQ(t, obl.BalanceOf(a2u(lp01)), 648) // external
}

{
std.TestSetOrigCaller(lp02)
UnstakeToken(2) // GNFT tokenId
std.TestSkipHeights(1)

shouldEQ(t, gnft.OwnerOf(tid(2)), lp02)

// check reward
shouldEQ(t, gns.BalanceOf(a2u(lp02)), 1650) // internal
shouldEQ(t, obl.BalanceOf(a2u(lp02)), 4243) // external
}
}

func TestEndExternalIncentive(t *testing.T) {
std.TestSetOrigCaller(ci01)
std.TestSkipHeights(9999999)
EndExternalIncentive(GetOrigCaller().String(), "gno.land/r/bar:gno.land/r/foo:500", "gno.land/r/obl") // use same parameter as CreateExternalIncentive()
std.TestSkipHeights(1)

shouldEQ(t, len(incentives), 0)
shouldEQ(t, len(poolIncentives["gno.land/r/bar:gno.land/r/foo:500"]), 0)
}

// GOV
func TestSubmitProposalParameterStakingReward(t *testing.T) {
// Init GOV Contract
g.Init()

id := SubmitProposalParameterStakingReward(
"staking reward change", // title
"change staking rewards", // summary
"", // metadata
0, // initialDeposit

10, // newStakingReward1
8, // newStakingReward2
6, // newStakingReward3
4, // newStakingReward4
)
shouldEQ(t, id, uint64(1))
}

/* HELPERS */
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, expected %v", got, expected)
}
}

func shouldGT(t *testing.T, l, r interface{}) {
if !(l < r) {
t.Errorf("expected %v < %v", l, r)
}
}

func shouldLT(t *testing.T, l, r interface{}) {
if !(l > r) {
t.Errorf("expected %v > %v", l, r)
}
}

func shouldPanic(t *testing.T, f func()) {
defer func() {
if r := recover(); r == nil {
t.Errorf("expected panic")
}
}()
f()
}
4 changes: 2 additions & 2 deletions staker/incentive_id.gno
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"gno.land/p/demo/ufmt"
)

func incentiveIdCompute(targetPoolPath, rewardToken string) string {
key := ufmt.Sprintf("%s_%s", targetPoolPath, rewardToken)
func incentiveIdCompute(caller, targetPoolPath, rewardToken string) string {
key := ufmt.Sprintf("%s:%s:%s", caller, targetPoolPath, rewardToken)

encoded := base64.StdEncoding.EncodeToString([]byte(key))
return encoded
Expand Down
27 changes: 13 additions & 14 deletions staker/staker.gno
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,25 @@ func CreateExternalIncentive(

externalDuration := uint64(endTimestamp - startTimestamp)
if !(externalDuration == TIMESTAMP_90DAYS || externalDuration == TIMESTAMP_180DAYS || externalDuration == TIMESTAMP_360DAYS) {
println("externalDuration:", externalDuration)
println("TIMESTAMP_90DAYS:", TIMESTAMP_90DAYS)
panic(ufmt.Sprintf("[STAKER] staker.gno__CreateExternalIncentive() || externalDuration(%d) must be 90, 180, 360 days)", externalDuration))
}

incentiveId := incentiveIdCompute(targetPoolPath, rewardToken)
fromBalanceBefore := balanceOfByRegisterCall(rewardToken, GetOrigCaller())
require(fromBalanceBefore >= rewardAmount, ufmt.Sprintf("[STAKER] staker.gno__CreateExternalIncentive() || not enough rewardAmount(%d) to create incentive(%d)", fromBalanceBefore, rewardAmount))

poolRewardBalanceBefore := balanceOfByRegisterCall(rewardToken, GetOrigPkgAddr())

incentiveId := incentiveIdCompute(GetOrigCaller().String(), targetPoolPath, rewardToken)

// check whether incentive already exists or not
// if same incentiveId exists => increase rewardTokenAmount
for _, v := range poolIncentives[targetPoolPath] {
if v == incentiveId {
panic(ufmt.Sprintf("[STAKER] staker.gno__CreateExternalIncentive() || incentive(%s) already exists", incentiveId))
transferFromByRegisterCall(rewardToken, GetOrigCaller(), GetOrigPkgAddr(), rewardAmount)
incentives[v].rewardAmount += rewardAmount
return
}
}

fromBalanceBefore := balanceOfByRegisterCall(rewardToken, GetOrigCaller())
require(fromBalanceBefore >= rewardAmount, ufmt.Sprintf("[STAKER] staker.gno__CreateExternalIncentive() || not enough rewardAmount(%d) to create incentive(%d)", fromBalanceBefore, rewardAmount))

poolRewardBalanceBefore := balanceOfByRegisterCall(rewardToken, GetOrigPkgAddr())

transferFromByRegisterCall(rewardToken, GetOrigCaller(), GetOrigPkgAddr(), rewardAmount)

poolRewardBalanceAfter := balanceOfByRegisterCall(rewardToken, GetOrigPkgAddr())
Expand Down Expand Up @@ -156,14 +156,13 @@ func UnstakeToken(
gnft.TransferFrom(a2u(GetOrigPkgAddr()), a2u(deposit.owner), tid(tokenId))
}

func EndExternalIncentive(targetPoolPath, rewardToken string) {
incentiveId := incentiveIdCompute(targetPoolPath, rewardToken)
func EndExternalIncentive(refundee, targetPoolPath, rewardToken string) {
incentiveId := incentiveIdCompute(refundee, targetPoolPath, rewardToken)
incentive, exist := incentives[incentiveId]
require(exist, ufmt.Sprintf("[STAKER] staker.gno__EndExternalIncentive() || cannot end non existent incentive(%s)", incentiveId))
require(GetTimestamp() >= incentive.endTimestamp, ufmt.Sprintf("[STAKER] staker.gno__EndExternalIncentive() || cannot end incentive before endTimestamp(%d), current(%d)", incentive.endTimestamp, GetTimestamp()))

// r3v4_xxx: who can end incentive ??
// require(incentive.refundee == std.GetOrigCaller(), "[STAKER] staker.gno__EndExternalIncentive() || only refundee can end incentive")
require(incentive.refundee == std.GetOrigCaller(), "[STAKER] staker.gno__EndExternalIncentive() || only refundee can end incentive")

refund := incentive.rewardAmount

Expand Down

0 comments on commit 50e6ad8

Please sign in to comment.