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-1839 refactor: integrated helper and test code #432

Merged
merged 1 commit into from
Dec 16, 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
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"))
r3v4s marked this conversation as resolved.
Show resolved Hide resolved
}
}

// 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
Loading