Skip to content

Commit

Permalink
GSW-1839 refactor: integrated helper and test code
Browse files Browse the repository at this point in the history
- integrated helper with nft helper
- add test helper code
- add test code for helper
- change file filename
  • Loading branch information
onlyhyde committed Dec 10, 2024
1 parent c86dab4 commit 57781bd
Show file tree
Hide file tree
Showing 10 changed files with 625 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,5 @@ func PositionIsInRange(tokenId uint64) bool {
}

func PositionGetPositionOwner(tokenId uint64) std.Address {
return gnft.OwnerOf(tid(tokenId))
return gnft.OwnerOf(tokenIdFrom(tokenId))
}
12 changes: 6 additions & 6 deletions position/_RPC_api.gno
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func ApiGetPositions() string {
_positionNode := json.ObjectNode("", map[string]*json.Node{
"lpTokenId": json.NumberNode("lpTokenId", float64(position.LpTokenId)),
"burned": json.BoolNode("burned", position.Burned),
"owner": json.StringNode("owner", gnft.OwnerOf(tid(position.LpTokenId)).String()),
"owner": json.StringNode("owner", gnft.OwnerOf(tokenIdFrom(position.LpTokenId)).String()),
"operator": json.StringNode("operator", position.Operator),
"poolKey": json.StringNode("poolKey", position.PoolKey),
"tickLower": json.NumberNode("tickLower", float64(position.TickLower)),
Expand Down Expand Up @@ -140,7 +140,7 @@ func ApiGetPosition(lpTokenId uint64) string {
_positionNode := json.ObjectNode("", map[string]*json.Node{
"lpTokenId": json.NumberNode("lpTokenId", float64(position.LpTokenId)),
"burned": json.BoolNode("burned", position.Burned),
"owner": json.StringNode("owner", gnft.OwnerOf(tid(position.LpTokenId)).String()),
"owner": json.StringNode("owner", gnft.OwnerOf(tokenIdFrom(position.LpTokenId)).String()),
"operator": json.StringNode("operator", position.Operator),
"poolKey": json.StringNode("poolKey", position.PoolKey),
"tickLower": json.NumberNode("tickLower", float64(position.TickLower)),
Expand Down Expand Up @@ -203,7 +203,7 @@ func ApiGetPositionsByPoolPath(poolPath string) string {
_positionNode := json.ObjectNode("", map[string]*json.Node{
"lpTokenId": json.NumberNode("lpTokenId", float64(position.LpTokenId)),
"burned": json.BoolNode("burned", position.Burned),
"owner": json.StringNode("owner", gnft.OwnerOf(tid(position.LpTokenId)).String()),
"owner": json.StringNode("owner", gnft.OwnerOf(tokenIdFrom(position.LpTokenId)).String()),
"operator": json.StringNode("operator", position.Operator),
"poolKey": json.StringNode("poolKey", position.PoolKey),
"tickLower": json.NumberNode("tickLower", float64(position.TickLower)),
Expand Down Expand Up @@ -238,7 +238,7 @@ func ApiGetPositionsByAddress(address std.Address) string {
rpcPositions := []RpcPosition{}
for lpTokenId, position := range positions {

if !(position.operator == address || gnft.OwnerOf(tid(lpTokenId)) == address) {
if !(position.operator == address || gnft.OwnerOf(tokenIdFrom(lpTokenId)) == address) {
continue
}

Expand Down Expand Up @@ -266,7 +266,7 @@ func ApiGetPositionsByAddress(address std.Address) string {
_positionNode := json.ObjectNode("", map[string]*json.Node{
"lpTokenId": json.NumberNode("lpTokenId", float64(position.LpTokenId)),
"burned": json.BoolNode("burned", position.Burned),
"owner": json.StringNode("owner", gnft.OwnerOf(tid(position.LpTokenId)).String()),
"owner": json.StringNode("owner", gnft.OwnerOf(tokenIdFrom(position.LpTokenId)).String()),
"operator": json.StringNode("operator", position.Operator),
"poolKey": json.StringNode("poolKey", position.PoolKey),
"tickLower": json.NumberNode("tickLower", float64(position.TickLower)),
Expand Down Expand Up @@ -410,7 +410,7 @@ func rpcMakePosition(lpTokenId uint64) RpcPosition {
return RpcPosition{
LpTokenId: lpTokenId,
Burned: burned,
Owner: gnft.OwnerOf(tid(lpTokenId)).String(),
Owner: gnft.OwnerOf(tokenIdFrom(lpTokenId)).String(),
Operator: position.operator.String(),
PoolKey: position.poolKey,
TickLower: position.tickLower,
Expand Down
101 changes: 73 additions & 28 deletions position/_helper_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"gno.land/r/gnoswap/v1/gnft"
"gno.land/r/gnoswap/v1/gns"
pl "gno.land/r/gnoswap/v1/pool"
sr "gno.land/r/gnoswap/v1/staker"
"gno.land/r/onbloc/bar"
"gno.land/r/onbloc/baz"
"gno.land/r/onbloc/foo"
Expand All @@ -37,6 +38,10 @@ const (
fee3000 uint32 = 3000
maxApprove uint64 = 18446744073709551615
max_timeout int64 = 9999999999

TIER_1 uint64 = 1
TIER_2 uint64 = 2
TIER_3 uint64 = 3
)

const (
Expand Down Expand Up @@ -165,6 +170,7 @@ func init() {
var (
admin = pusers.AddressOrName(consts.ADMIN)
alice = pusers.AddressOrName(testutils.TestAddress("alice"))
bob = pusers.AddressOrName(testutils.TestAddress("bob"))
pool = pusers.AddressOrName(consts.POOL_ADDR)
protocolFee = pusers.AddressOrName(consts.PROTOCOL_FEE_ADDR)
adminRealm = std.NewUserRealm(users.Resolve(admin))
Expand All @@ -182,10 +188,7 @@ func InitialisePoolTest(t *testing.T) {

std.TestSetOrigCaller(users.Resolve(admin))
TokenApprove(t, gnsPath, admin, pool, maxApprove)
poolPath := pl.GetPoolPath(wugnotPath, gnsPath, fee3000)
if !pl.DoesPoolPathExist(poolPath) {
pl.CreatePool(wugnotPath, gnsPath, fee3000, "79228162514264337593543950336")
}
CreatePool(t, wugnotPath, gnsPath, fee3000, "79228162514264337593543950336", users.Resolve(admin))

//2. create position
std.TestSetOrigCaller(users.Resolve(alice))
Expand Down Expand Up @@ -300,6 +303,22 @@ func TokenApprove(t *testing.T, tokenPath string, owner, spender pusers.AddressO
}
}

func CreatePool(t *testing.T,
token0 string,
token1 string,
fee uint32,
sqrtPriceX96 string,
caller std.Address) {
t.Helper()

std.TestSetRealm(std.NewUserRealm(caller))
poolPath := pl.GetPoolPath(token0, token1, fee)
if !pl.DoesPoolPathExist(poolPath) {
pl.CreatePool(token0, token1, fee, sqrtPriceX96)
sr.SetPoolTierByAdmin(poolPath, TIER_1)
}
}

func MintPosition(t *testing.T,
token0 string,
token1 string,
Expand Down Expand Up @@ -332,6 +351,54 @@ func MintPosition(t *testing.T,
caller)
}

func MakeMintPositionWithoutFee(t *testing.T) (uint64, string, string, string) {
t.Helper()

// make actual data to test resetting not only position's state but also pool's state
std.TestSetRealm(adminRealm)

// set pool create fee to 0 for testing
pl.SetPoolCreationFeeByAdmin(0)
CreatePool(t, barPath, fooPath, fee500, common.TickMathGetSqrtRatioAtTick(0).ToString(), users.Resolve(admin))

TokenApprove(t, barPath, admin, pool, consts.UINT64_MAX)
TokenApprove(t, fooPath, admin, pool, consts.UINT64_MAX)

// mint position
return Mint(
barPath,
fooPath,
fee500,
-887270,
887270,
"50000",
"50000",
"0",
"0",
max_timeout,
users.Resolve(admin),
users.Resolve(admin),
)
}

func LPTokenApprove(t *testing.T, owner, operator pusers.AddressOrName, tokenId uint64) {
t.Helper()
std.TestSetRealm(std.NewUserRealm(users.Resolve(owner)))
gnft.Approve(operator, tokenIdFrom(tokenId))
}

func LPTokenStake(t *testing.T, owner pusers.AddressOrName, tokenId uint64) {
t.Helper()
std.TestSetRealm(std.NewUserRealm(users.Resolve(owner)))
sr.StakeToken(tokenId)
}

func LPTokenUnStake(t *testing.T, owner pusers.AddressOrName, tokenId uint64, unwrap bool) {
t.Helper()
std.TestSetRealm(std.NewUserRealm(users.Resolve(owner)))
sr.UnstakeToken(tokenId, unwrap)
}

func wugnotApprove(t *testing.T, owner, spender pusers.AddressOrName, amount uint64) {
t.Helper()
std.TestSetRealm(std.NewUserRealm(users.Resolve(owner)))
Expand Down Expand Up @@ -487,37 +554,15 @@ func burnAllNFT(t *testing.T) {

std.TestSetRealm(std.NewCodeRealm(consts.POSITION_PATH))
for i := uint64(1); i <= gnft.TotalSupply(); i++ {
gnft.Burn(tid(i))
gnft.Burn(tokenIdFrom(i))
}
}

func TestBeforeResetObject(t *testing.T) {
// make actual data to test resetting not only position's state but also pool's state
std.TestSetRealm(adminRealm)

// set pool create fee to 0 for testing
pl.SetPoolCreationFeeByAdmin(0)
pl.CreatePool(barPath, fooPath, fee500, common.TickMathGetSqrtRatioAtTick(0).ToString())

// mint position
bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX)
foo.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX)

tokenId, liquidity, amount0, amount1 := Mint(
barPath,
fooPath,
fee500,
-887270,
887270,
"50000",
"50000",
"0",
"0",
max_timeout,
users.Resolve(admin),
users.Resolve(admin),
)

tokenId, liquidity, amount0, amount1 := MakeMintPositionWithoutFee(t)
uassert.Equal(t, tokenId, uint64(1), "tokenId should be 1")
uassert.Equal(t, liquidity, "50000", "liquidity should be 50000")
uassert.Equal(t, amount0, "50000", "amount0 should be 50000")
Expand Down
32 changes: 22 additions & 10 deletions position/errors.gno
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,31 @@ import (
)

var (
errNoPermission = errors.New("[GNOSWAP-POSITION-001] caller has no permission")
errSlippage = errors.New("[GNOSWAP-POSITION-002] slippage failed")
errWrapUnwrap = errors.New("[GNOSWAP-POSITION-003] wrap, unwrap failed")
errOutOfRange = errors.New("[GNOSWAP-POSITION-004] out of range for numeric value")
errInvalidInput = errors.New("[GNOSWAP-POSITION-005] invalid input data")
errDataNotFound = errors.New("[GNOSWAP-POSITION-006] requested data not found")
errExpired = errors.New("[GNOSWAP-POSITION-007] transaction expired")
errWugnotMinimum = errors.New("[GNOSWAP-POSITION-008] can not wrap less than minimum amount")
errNotClear = errors.New("[GNOSWAP-POSITION-009] position is not clear")
errZeroLiquidity = errors.New("[GNOSWAP-POSITION-010] zero liquidity")
errNoPermission = errors.New("[GNOSWAP-POSITION-001] caller has no permission")
errSlippage = errors.New("[GNOSWAP-POSITION-002] slippage failed")
errWrapUnwrap = errors.New("[GNOSWAP-POSITION-003] wrap, unwrap failed")
errOutOfRange = errors.New("[GNOSWAP-POSITION-004] out of range for numeric value")
errInvalidInput = errors.New("[GNOSWAP-POSITION-005] invalid input data")
errDataNotFound = errors.New("[GNOSWAP-POSITION-006] requested data not found")
errExpired = errors.New("[GNOSWAP-POSITION-007] transaction expired")
errWugnotMinimum = errors.New("[GNOSWAP-POSITION-008] can not wrap less than minimum amount")
errNotClear = errors.New("[GNOSWAP-POSITION-009] position is not clear")
errZeroLiquidity = errors.New("[GNOSWAP-POSITION-010] zero liquidity")
errInvalidAddress = errors.New("[GNOSWAP-POSITION-011] invalid address")
)

// TODO:
// addDetailToError -> newErrorWithDetail
func addDetailToError(err error, detail string) string {
finalErr := ufmt.Errorf("%s || %s", err.Error(), detail)
return finalErr.Error()
}

// newErrorWithDetail returns a new error with the given detail
// e.g. newErrorWithDetail(err, "detail")
//
// input: err error, detail string
// output: "err.Error() || detail"
func newErrorWithDetail(err error, detail string) string {
return ufmt.Errorf("%s || %s", err.Error(), detail).Error()
}
113 changes: 104 additions & 9 deletions position/helper.gno
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
package position

import (
"std"
"strconv"

"gno.land/p/demo/grc/grc721"
"gno.land/p/demo/ufmt"
"gno.land/r/gnoswap/v1/common"
"gno.land/r/gnoswap/v1/consts"
"gno.land/r/gnoswap/v1/gnft"
)

// nextId is the next tokenId to be minted
func getNextId() uint64 {
return nextId
}

func tid(tokenId interface{}) grc721.TokenID {
// tokenIdFrom converts tokenId to grc721.TokenID type
// NOTE: input parameter tokenId can be string, int, uint64, or grc721.TokenID
// if tokenId is nil or not supported, it will panic
// if tokenId is not found, it will panic
// input: tokenId interface{}
// output: grc721.TokenID
func tokenIdFrom(tokenId interface{}) grc721.TokenID {
if tokenId == nil {
panic(addDetailToError(
errDataNotFound,
"helper.gno__tid() || tokenId is nil",
))
panic(newErrorWithDetail(errInvalidInput, "tokenId is nil"))
}

switch tokenId.(type) {
Expand All @@ -28,9 +37,95 @@ func tid(tokenId interface{}) grc721.TokenID {
case grc721.TokenID:
return tokenId.(grc721.TokenID)
default:
panic(addDetailToError(
errInvalidInput,
"helper.gno__tid() || unsupported tokenId type",
))
panic(newErrorWithDetail(errInvalidInput, "unsupported tokenId type"))
}
}

// exists checks whether tokenId exists
// If tokenId doesn't exist, return false, otherwise return true
// input: tokenId uint64
// output: bool
func exists(tokenId uint64) bool {
return gnft.Exists(tokenIdFrom(tokenId))
}

// isOwner checks whether the caller is the owner of the tokenId
// If the caller is the owner of the tokenId, return true, otherwise return false
// input: tokenId uint64, addr std.Address
// output: bool
func isOwner(tokenId uint64, addr std.Address) bool {
owner := gnft.OwnerOf(tokenIdFrom(tokenId))
if owner == addr {
return true
}
return false
}

// isOperator checks whether the caller is the approved operator of the tokenId
// If the caller is the approved operator of the tokenId, return true, otherwise return false
// input: tokenId uint64, addr std.Address
// output: bool
func isOperator(tokenId uint64, addr std.Address) bool {
operator, ok := gnft.GetApproved(tokenIdFrom(tokenId))
if ok && operator == addr {
return true
}
return false
}

// isStaked checks whether tokenId is staked
// If tokenId is staked, owner of tokenId is staker contract
// If tokenId is staked, return true, otherwise return false
// input: tokenId grc721.TokenID
// output: bool
func isStaked(tokenId grc721.TokenID) bool {
exist := gnft.Exists(tokenId)
if exist {
owner := gnft.OwnerOf(tokenId)
if owner == consts.STAKER_ADDR {
return true
}
}
return false
}

// isOwnerOrOperator checks whether the caller is the owner or approved operator of the tokenId
// If the caller is the owner or approved operator of the tokenId, return true, otherwise return false
// input: addr std.Address, tokenId uint64
// output: bool
func isOwnerOrOperator(addr std.Address, tokenId uint64) bool {
assertOnlyValidAddress(addr)
if !exists(tokenId) {
return false
}
if isOwner(tokenId, addr) || isOperator(tokenId, addr) {
return true
}
if isStaked(tokenIdFrom(tokenId)) {
position, exist := positions[tokenId]
if exist && addr == position.operator {
return true
}
}
return false
}

// splitOf divides poolKey into pToken0, pToken1, and pFee
// If poolKey is invalid, it will panic
//
// input: poolKey string
// output:
// - token0Path string
// - token1Path string
// - fee uint32
func splitOf(poolKey string) (string, string, uint32) {
res, err := common.Split(poolKey, ":", 3)
if err != nil {
panic(newErrorWithDetail(errInvalidInput, ufmt.Sprintf("invalid poolKey(%s)", poolKey)))
}

pToken0, pToken1, pFeeStr := res[0], res[1], res[2]

pFee, _ := strconv.Atoi(pFeeStr)
return pToken0, pToken1, uint32(pFee)
}
Loading

0 comments on commit 57781bd

Please sign in to comment.