Skip to content

Commit

Permalink
GSW-357 feat: change external incentive distribution period
Browse files Browse the repository at this point in the history
+ apply grc20 call by register interface
  • Loading branch information
r3v4s committed Nov 8, 2023
1 parent 7a87614 commit 775380d
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 52 deletions.
6 changes: 4 additions & 2 deletions staker/consts.gno
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const (
Q96 bigint = 79228162514264337593543950336 // 2 ** 96
Q128 bigint = 340282366920938463463374607431768211456 // 2 ** 128

TIMESTAMP_1DAY int64 = 86400
TIMESTAMP_30DAYS int64 = 2592000
TIMESTAMP_1DAY uint64 = 86400
TIMESTAMP_90DAYS uint64 = 7776000
TIMESTAMP_180DAYS uint64 = 15552000
TIMESTAMP_360DAYS uint64 = 31104000
)
10 changes: 5 additions & 5 deletions staker/reward_math.gno
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ func rewardMathComputeExternalRewardAmount(tokenId uint64, deposit Deposit, ince
monthlyReward := uint64(0)

switch {
case incentiveDuration == TIMESTAMP_30DAYS:
monthlyReward = incentive.rewardAmount
case incentiveDuration > TIMESTAMP_30DAYS:
case incentiveDuration == TIMESTAMP_90DAYS:
monthlyReward = incentive.rewardAmount / 3
case incentiveDuration > TIMESTAMP_90DAYS:
// 1 second reward == total reward amount / reward duration
monthlyReward = incentive.rewardAmount / uint64(incentiveDuration) * uint64(TIMESTAMP_30DAYS)
monthlyReward = incentive.rewardAmount / uint64(incentiveDuration)
default:
panic(ufmt.Sprintf("[STAKER] reward_math.gno || incentiveDuration(%d) at least 30 days", incentiveDuration))
panic(ufmt.Sprintf("[STAKER] reward_math.gno || incentiveDuration(%d) should be at least 90 days", incentiveDuration))
}

// calculate reward amount per block
Expand Down
58 changes: 30 additions & 28 deletions staker/staker.gno
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import (
"gno.land/p/demo/ufmt"

gnft "gno.land/r/gnft" // GNFT, Gnoswap NFT
gns "gno.land/r/gns" // GNS, INTERNAL Reward Token
obl "gno.land/r/obl" // OBL, EXTERNAL Reward Token
gns "gno.land/r/gns" // INTERAL REWARD FIXED

s "gno.land/r/position"
)
Expand All @@ -27,7 +26,7 @@ var (
func init() {
// init pool tiers
// tier 1
poolTiers["BAR/FOO_500"] = 1 // DEV
poolTiers["gno.land/r/bar:gno.land/r/foo:500"] = 1 // DEV

// tier 2
poolTiers["GNS/USDT_500"] = 2
Expand All @@ -41,14 +40,19 @@ func init() {

func CreateExternalIncentive(
targetPoolPath string,
// rewardToken *grc20.AdminToken, // FIXED TO OBL for now
rewardToken string,
rewardToken string, // token path should be registered
rewardAmount uint64,
startTimestamp int64,
endTimestamp int64,
) {
require(GetTimestamp() <= startTimestamp, ufmt.Sprintf("[STAKER] staker.gno__CreateExternalIncentive() || startTimestamp must be in the future__GetTimestamp(%d) <= startTimestamp(%d)", GetTimestamp(), startTimestamp))
require(endTimestamp-startTimestamp >= TIMESTAMP_30DAYS, ufmt.Sprintf("[STAKER] staker.gno__CreateExternalIncentive() || endTimestamp must be at least 30 days after startTimestamp__endTimestamp-startTimestamp(%d) >= TIMESTAMP_30DAYS(%d)", endTimestamp-startTimestamp, TIMESTAMP_30DAYS))

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)

Expand All @@ -59,15 +63,14 @@ func CreateExternalIncentive(
}
}

from := a2u(GetOrigCaller())
fromBalanceBefore := obl.BalanceOf(from)
require(fromBalanceBefore >= rewardAmount, ufmt.Sprintf("[STAKER] staker.gno__CreateExternalIncentive() || not enough OBL(%s) to create incentive(%s)", fromBalanceBefore, rewardAmount))
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())

to := a2u(GetOrigPkgAddr()) // staker contract
poolRewardBalanceBefore := obl.BalanceOf(to)
transferFromByRegisterCall(rewardToken, GetOrigCaller(), GetOrigPkgAddr(), rewardAmount)

obl.TransferFrom(from, to, rewardAmount)
poolRewardBalanceAfter := obl.BalanceOf(to)
poolRewardBalanceAfter := balanceOfByRegisterCall(rewardToken, GetOrigPkgAddr())
require(poolRewardBalanceAfter-poolRewardBalanceBefore == rewardAmount, ufmt.Sprintf("[STAKER] staker.gno__CreateExternalIncentive() || pool reward balance not updated correctly"))

incentives[incentiveId] = Incentive{
Expand All @@ -87,20 +90,20 @@ func StakeToken(
) {
// check whether tokenId already staked or not
_, exist := deposits[tokenId]
require(!exist, ufmt.Sprintf("[STAKER] staker.gno__StakeToken() || tokenId(%s) already staked", tokenId))
require(!exist, ufmt.Sprintf("[STAKER] staker.gno__StakeToken() || tokenId(%d) already staked", tokenId))

// check tokenId owner
require(
gnft.OwnerOf(tid(tokenId)) == GetOrigCaller(),
ufmt.Sprintf(
"[STAKER] staker.gno__StakeToken() || only owner can stake their token__gnft.OwnerOf(tid(tokenId(%s)))(%s) == GetOrigCaller()(%s)",
"[STAKER] staker.gno__StakeToken() || only owner can stake their token__gnft.OwnerOf(tid(tokenId(%d)))(%s) == GetOrigCaller()(%s)",
tokenId, gnft.OwnerOf(tid(tokenId)), GetOrigCaller(),
),
)

// check tokenId has liquidity or not
liquidity := s.PositionGetPositionLiquidity(tokenId)
require(liquidity > 0, ufmt.Sprintf("[STAKER] staker.gno__StakeToken() || tokenId(%s) has no liquidity", tokenId))
require(liquidity > 0, ufmt.Sprintf("[STAKER] staker.gno__StakeToken() || tokenId(%d) has no liquidity", tokenId))

// check pool path from tokenid
poolKey := s.PositionGetPositionPoolKey(tokenId)
Expand All @@ -120,22 +123,21 @@ func UnstakeToken(
tokenId uint64, // GNFT TokenID
) {
deposit, exist := deposits[tokenId]
require(exist, ufmt.Sprintf("[STAKER] staker.gno__UnstakeToken() || tokenId(%s) not staked", tokenId))
require(exist, ufmt.Sprintf("[STAKER] staker.gno__UnstakeToken() || tokenId(%d) not staked", tokenId))

// address who executed StakeToken() can call UnstakeToken()
require(PrevRealmAddr() == deposit.owner, ufmt.Sprintf("[STAKER] staker.gno__UnstakeToken() || only owner(%s) can unstake their token(%s), PrevRealmAddr()(%s)", deposit.owner, tokenId, PrevRealmAddr()))
require(PrevRealmAddr() == deposit.owner, ufmt.Sprintf("[STAKER] staker.gno__UnstakeToken() || only owner(%s) can unstake their token(%d), PrevRealmAddr()(%s)", deposit.owner, tokenId, PrevRealmAddr()))

// poolPath to unstake lp token
poolPath := s.PositionGetPositionPoolKey(tokenId)

// get all external reward list for this pool
for _, incentiveId := range poolIncentives[poolPath] {
incentive := incentives[incentiveId]
externalReward := rewardMathComputeExternalRewardAmount(tokenId, deposit, incentive) // OBL reward
externalReward := rewardMathComputeExternalRewardAmount(tokenId, deposit, incentive) // external reward

// r3v4_xxx: get external token then call transfer
// r3v4_xxx: handle native coin(gnot) reward
obl.Transfer(a2u(deposit.owner), uint64(externalReward))
transferByRegisterCall(incentive.rewardToken, deposit.owner, uint64(externalReward))

incentive.rewardAmount -= externalReward
incentives[incentiveId] = incentive
Expand All @@ -157,18 +159,18 @@ func UnstakeToken(
func EndExternalIncentive(targetPoolPath, rewardToken string) {
incentiveId := incentiveIdCompute(targetPoolPath, rewardToken)
incentive, exist := incentives[incentiveId]
require(exist, ufmt.Sprintf("[STAKER] staker.gno__EndIncentive() || cannot end non existent incentive(%s)", incentiveId))
require(GetTimestamp() >= incentive.endTimestamp, ufmt.Sprintf("[STAKER] staker.gno__EndIncentive() || cannot end incentive before endTimestamp(%s), current(%s)", incentive.endTimestamp, GetTimestamp()))
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__EndIncentive() || only refundee can end incentive")
// require(incentive.refundee == std.GetOrigCaller(), "[STAKER] staker.gno__EndExternalIncentive() || only refundee can end incentive")

refund := incentive.rewardAmount

poolOBL := obl.BalanceOf(a2u(GetOrigPkgAddr()))
require(poolOBL >= refund, ufmt.Sprintf("[STAKER] staker.gno__EndIncentive() || not enough OBL(%s) to refund(%s)", poolOBL, refund))
poolExternalReward := balanceOfByRegisterCall(incentive.rewardToken, GetOrigPkgAddr())
require(poolExternalReward >= refund, ufmt.Sprintf("[STAKER] staker.gno__EndExternalIncentive() || not enough poolExternalReward(%d) to refund(%d)", poolExternalReward, refund))

obl.Transfer(a2u(incentive.refundee), uint64(refund))
transferByRegisterCall(incentive.rewardToken, incentive.refundee, uint64(refund))

delete(incentives, incentiveId)
for i, v := range poolIncentives[targetPoolPath] {
Expand All @@ -180,7 +182,7 @@ func EndExternalIncentive(targetPoolPath, rewardToken string) {

func transferDeposit(tokenId uint64, to std.Address) {
owner := gnft.OwnerOf(tid(tokenId))
require(owner == GetOrigCaller(), ufmt.Sprintf("[STAKER] staker.gno__transferDeposit() || only owner(%s) can transfer tokenId(%s), GetOrigCaller()(%s)", owner, tokenId, PrevRealmAddr()))
require(owner == GetOrigCaller(), ufmt.Sprintf("[STAKER] staker.gno__transferDeposit() || only owner(%s) can transfer tokenId(%d), GetOrigCaller()(%s)", owner, tokenId, PrevRealmAddr()))

deposits[tokenId].owner = owner

Expand Down
121 changes: 121 additions & 0 deletions staker/staker_register.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package staker

import (
"std"

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

const APPROVED_CALLER = "g12l9splsyngcgefrwa52x5a7scc29e9v086m6p4" // gsa

var registered = []GRC20Pair{}

type GRC20Interface interface {
Transfer() func(to users.AddressOrName, amount uint64)
TransferFrom() func(from, to users.AddressOrName, amount uint64)
BalanceOf() func(owner users.AddressOrName) uint64
Approve() func(spender users.AddressOrName, amount uint64)
}

type GRC20Pair struct {
pkgPath string
igrc20 GRC20Interface
}

func findGRC20(pkgPath string) (int, bool) {
for i, pair := range registered {
if pair.pkgPath == pkgPath {
return i, true
}
}

return -1, false
}

func appendGRC20Interface(pkgPath string, igrc20 GRC20Interface) {
registered = append(registered, GRC20Pair{pkgPath: pkgPath, igrc20: igrc20})
}

func removeGRC20Interface(pkgPath string) {
i, found := findGRC20(pkgPath)
if !found {
return
}

registered = append(registered[:i], registered[i+1:]...)
}

func RegisterGRC20Interface(pkgPath string, igrc20 GRC20Interface) {
// only admin can register
// r3v4_xxx: below logic can't be used in test case
// r3v4_xxx: however must be used in production

// caller := std.GetOrigCaller()
// if caller != APPROVED_CALLER {
// panic("unauthorized address to register")
// }

_, found := findGRC20(pkgPath)
if !found {
appendGRC20Interface(pkgPath, igrc20)
}
}

func UnregisterGRC20Interface(pkgPath string) {
// do not allow realm to unregister
std.AssertOriginCall()

// only admin can unregister
caller := std.GetOrigCaller()
if caller != APPROVED_CALLER {
panic("unauthorized address to unregister")
}

_, found := findGRC20(pkgPath)
if found {
removeGRC20Interface(pkgPath)
}
}

func transferByRegisterCall(pkgPath string, to std.Address, amount uint64) bool {
i, found := findGRC20(pkgPath)
if !found {
return false
}

registered[i].igrc20.Transfer()(users.AddressOrName(to), amount)

return true
}

func transferFromByRegisterCall(pkgPath string, from, to std.Address, amount uint64) bool {
i, found := findGRC20(pkgPath)
if !found {
return false
}

registered[i].igrc20.TransferFrom()(users.AddressOrName(from), users.AddressOrName(to), amount)

return true
}

func balanceOfByRegisterCall(pkgPath string, owner std.Address) uint64 {
i, found := findGRC20(pkgPath)
if !found {
return 0
}

balance := registered[i].igrc20.BalanceOf()(users.AddressOrName(owner))
return balance
}

func approveByRegisterCall(pkgPath string, spender std.Address, amount uint64) bool {
i, found := findGRC20(pkgPath)
if !found {
return false
}

registered[i].igrc20.Approve()(users.AddressOrName(spender), amount)

return true
}
Loading

0 comments on commit 775380d

Please sign in to comment.