diff --git a/staker/staker_test.gno b/staker/_TEST_staker_one_external_test.gnoa similarity index 97% rename from staker/staker_test.gno rename to staker/_TEST_staker_one_external_test.gnoa index dc1970a2..39c31634 100644 --- a/staker/staker_test.gno +++ b/staker/_TEST_staker_one_external_test.gnoa @@ -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) diff --git a/staker/_TEST_staker_one_increase_external_test.gno b/staker/_TEST_staker_one_increase_external_test.gno new file mode 100644 index 00000000..7458eefb --- /dev/null +++ b/staker/_TEST_staker_one_increase_external_test.gno @@ -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() +} diff --git a/staker/incentive_id.gno b/staker/incentive_id.gno index 7bca6842..fb2b0fb2 100644 --- a/staker/incentive_id.gno +++ b/staker/incentive_id.gno @@ -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 diff --git a/staker/staker.gno b/staker/staker.gno index 5a794923..8e610c78 100644 --- a/staker/staker.gno +++ b/staker/staker.gno @@ -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()) @@ -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