Skip to content

Commit 7b62f12

Browse files
onlyhyde0xTopaz
authored and
0xTopaz
committed
GSW-1839 refactor: integrated helper and test code (#432)
- integrated helper with nft helper - add test helper code - add test code for helper - change file filename
1 parent 168c7df commit 7b62f12

10 files changed

+625
-153
lines changed

position/_GET_no_receiver.gno.gno renamed to position/_GET_no_receiver.gno

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,5 @@ func PositionIsInRange(tokenId uint64) bool {
6868
}
6969

7070
func PositionGetPositionOwner(tokenId uint64) std.Address {
71-
return gnft.OwnerOf(tid(tokenId))
71+
return gnft.OwnerOf(tokenIdFrom(tokenId))
7272
}

position/_RPC_api.gno

+6-6
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func ApiGetPositions() string {
8080
_positionNode := json.ObjectNode("", map[string]*json.Node{
8181
"lpTokenId": json.NumberNode("lpTokenId", float64(position.LpTokenId)),
8282
"burned": json.BoolNode("burned", position.Burned),
83-
"owner": json.StringNode("owner", gnft.OwnerOf(tid(position.LpTokenId)).String()),
83+
"owner": json.StringNode("owner", gnft.OwnerOf(tokenIdFrom(position.LpTokenId)).String()),
8484
"operator": json.StringNode("operator", position.Operator),
8585
"poolKey": json.StringNode("poolKey", position.PoolKey),
8686
"tickLower": json.NumberNode("tickLower", float64(position.TickLower)),
@@ -140,7 +140,7 @@ func ApiGetPosition(lpTokenId uint64) string {
140140
_positionNode := json.ObjectNode("", map[string]*json.Node{
141141
"lpTokenId": json.NumberNode("lpTokenId", float64(position.LpTokenId)),
142142
"burned": json.BoolNode("burned", position.Burned),
143-
"owner": json.StringNode("owner", gnft.OwnerOf(tid(position.LpTokenId)).String()),
143+
"owner": json.StringNode("owner", gnft.OwnerOf(tokenIdFrom(position.LpTokenId)).String()),
144144
"operator": json.StringNode("operator", position.Operator),
145145
"poolKey": json.StringNode("poolKey", position.PoolKey),
146146
"tickLower": json.NumberNode("tickLower", float64(position.TickLower)),
@@ -203,7 +203,7 @@ func ApiGetPositionsByPoolPath(poolPath string) string {
203203
_positionNode := json.ObjectNode("", map[string]*json.Node{
204204
"lpTokenId": json.NumberNode("lpTokenId", float64(position.LpTokenId)),
205205
"burned": json.BoolNode("burned", position.Burned),
206-
"owner": json.StringNode("owner", gnft.OwnerOf(tid(position.LpTokenId)).String()),
206+
"owner": json.StringNode("owner", gnft.OwnerOf(tokenIdFrom(position.LpTokenId)).String()),
207207
"operator": json.StringNode("operator", position.Operator),
208208
"poolKey": json.StringNode("poolKey", position.PoolKey),
209209
"tickLower": json.NumberNode("tickLower", float64(position.TickLower)),
@@ -238,7 +238,7 @@ func ApiGetPositionsByAddress(address std.Address) string {
238238
rpcPositions := []RpcPosition{}
239239
for lpTokenId, position := range positions {
240240

241-
if !(position.operator == address || gnft.OwnerOf(tid(lpTokenId)) == address) {
241+
if !(position.operator == address || gnft.OwnerOf(tokenIdFrom(lpTokenId)) == address) {
242242
continue
243243
}
244244

@@ -266,7 +266,7 @@ func ApiGetPositionsByAddress(address std.Address) string {
266266
_positionNode := json.ObjectNode("", map[string]*json.Node{
267267
"lpTokenId": json.NumberNode("lpTokenId", float64(position.LpTokenId)),
268268
"burned": json.BoolNode("burned", position.Burned),
269-
"owner": json.StringNode("owner", gnft.OwnerOf(tid(position.LpTokenId)).String()),
269+
"owner": json.StringNode("owner", gnft.OwnerOf(tokenIdFrom(position.LpTokenId)).String()),
270270
"operator": json.StringNode("operator", position.Operator),
271271
"poolKey": json.StringNode("poolKey", position.PoolKey),
272272
"tickLower": json.NumberNode("tickLower", float64(position.TickLower)),
@@ -410,7 +410,7 @@ func rpcMakePosition(lpTokenId uint64) RpcPosition {
410410
return RpcPosition{
411411
LpTokenId: lpTokenId,
412412
Burned: burned,
413-
Owner: gnft.OwnerOf(tid(lpTokenId)).String(),
413+
Owner: gnft.OwnerOf(tokenIdFrom(lpTokenId)).String(),
414414
Operator: position.operator.String(),
415415
PoolKey: position.poolKey,
416416
TickLower: position.tickLower,

position/_helper_test.gno

+73-28
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"gno.land/r/gnoswap/v1/gnft"
1515
"gno.land/r/gnoswap/v1/gns"
1616
pl "gno.land/r/gnoswap/v1/pool"
17+
sr "gno.land/r/gnoswap/v1/staker"
1718
"gno.land/r/onbloc/bar"
1819
"gno.land/r/onbloc/baz"
1920
"gno.land/r/onbloc/foo"
@@ -37,6 +38,10 @@ const (
3738
fee3000 uint32 = 3000
3839
maxApprove uint64 = 18446744073709551615
3940
max_timeout int64 = 9999999999
41+
42+
TIER_1 uint64 = 1
43+
TIER_2 uint64 = 2
44+
TIER_3 uint64 = 3
4045
)
4146

4247
const (
@@ -165,6 +170,7 @@ func init() {
165170
var (
166171
admin = pusers.AddressOrName(consts.ADMIN)
167172
alice = pusers.AddressOrName(testutils.TestAddress("alice"))
173+
bob = pusers.AddressOrName(testutils.TestAddress("bob"))
168174
pool = pusers.AddressOrName(consts.POOL_ADDR)
169175
protocolFee = pusers.AddressOrName(consts.PROTOCOL_FEE_ADDR)
170176
adminRealm = std.NewUserRealm(users.Resolve(admin))
@@ -182,10 +188,7 @@ func InitialisePoolTest(t *testing.T) {
182188

183189
std.TestSetOrigCaller(users.Resolve(admin))
184190
TokenApprove(t, gnsPath, admin, pool, maxApprove)
185-
poolPath := pl.GetPoolPath(wugnotPath, gnsPath, fee3000)
186-
if !pl.DoesPoolPathExist(poolPath) {
187-
pl.CreatePool(wugnotPath, gnsPath, fee3000, "79228162514264337593543950336")
188-
}
191+
CreatePool(t, wugnotPath, gnsPath, fee3000, "79228162514264337593543950336", users.Resolve(admin))
189192

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

306+
func CreatePool(t *testing.T,
307+
token0 string,
308+
token1 string,
309+
fee uint32,
310+
sqrtPriceX96 string,
311+
caller std.Address) {
312+
t.Helper()
313+
314+
std.TestSetRealm(std.NewUserRealm(caller))
315+
poolPath := pl.GetPoolPath(token0, token1, fee)
316+
if !pl.DoesPoolPathExist(poolPath) {
317+
pl.CreatePool(token0, token1, fee, sqrtPriceX96)
318+
sr.SetPoolTierByAdmin(poolPath, TIER_1)
319+
}
320+
}
321+
303322
func MintPosition(t *testing.T,
304323
token0 string,
305324
token1 string,
@@ -332,6 +351,54 @@ func MintPosition(t *testing.T,
332351
caller)
333352
}
334353

354+
func MakeMintPositionWithoutFee(t *testing.T) (uint64, string, string, string) {
355+
t.Helper()
356+
357+
// make actual data to test resetting not only position's state but also pool's state
358+
std.TestSetRealm(adminRealm)
359+
360+
// set pool create fee to 0 for testing
361+
pl.SetPoolCreationFeeByAdmin(0)
362+
CreatePool(t, barPath, fooPath, fee500, common.TickMathGetSqrtRatioAtTick(0).ToString(), users.Resolve(admin))
363+
364+
TokenApprove(t, barPath, admin, pool, consts.UINT64_MAX)
365+
TokenApprove(t, fooPath, admin, pool, consts.UINT64_MAX)
366+
367+
// mint position
368+
return Mint(
369+
barPath,
370+
fooPath,
371+
fee500,
372+
-887270,
373+
887270,
374+
"50000",
375+
"50000",
376+
"0",
377+
"0",
378+
max_timeout,
379+
users.Resolve(admin),
380+
users.Resolve(admin),
381+
)
382+
}
383+
384+
func LPTokenApprove(t *testing.T, owner, operator pusers.AddressOrName, tokenId uint64) {
385+
t.Helper()
386+
std.TestSetRealm(std.NewUserRealm(users.Resolve(owner)))
387+
gnft.Approve(operator, tokenIdFrom(tokenId))
388+
}
389+
390+
func LPTokenStake(t *testing.T, owner pusers.AddressOrName, tokenId uint64) {
391+
t.Helper()
392+
std.TestSetRealm(std.NewUserRealm(users.Resolve(owner)))
393+
sr.StakeToken(tokenId)
394+
}
395+
396+
func LPTokenUnStake(t *testing.T, owner pusers.AddressOrName, tokenId uint64, unwrap bool) {
397+
t.Helper()
398+
std.TestSetRealm(std.NewUserRealm(users.Resolve(owner)))
399+
sr.UnstakeToken(tokenId, unwrap)
400+
}
401+
335402
func wugnotApprove(t *testing.T, owner, spender pusers.AddressOrName, amount uint64) {
336403
t.Helper()
337404
std.TestSetRealm(std.NewUserRealm(users.Resolve(owner)))
@@ -487,37 +554,15 @@ func burnAllNFT(t *testing.T) {
487554

488555
std.TestSetRealm(std.NewCodeRealm(consts.POSITION_PATH))
489556
for i := uint64(1); i <= gnft.TotalSupply(); i++ {
490-
gnft.Burn(tid(i))
557+
gnft.Burn(tokenIdFrom(i))
491558
}
492559
}
493560

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

498-
// set pool create fee to 0 for testing
499-
pl.SetPoolCreationFeeByAdmin(0)
500-
pl.CreatePool(barPath, fooPath, fee500, common.TickMathGetSqrtRatioAtTick(0).ToString())
501-
502-
// mint position
503-
bar.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX)
504-
foo.Approve(a2u(consts.POOL_ADDR), consts.UINT64_MAX)
505-
506-
tokenId, liquidity, amount0, amount1 := Mint(
507-
barPath,
508-
fooPath,
509-
fee500,
510-
-887270,
511-
887270,
512-
"50000",
513-
"50000",
514-
"0",
515-
"0",
516-
max_timeout,
517-
users.Resolve(admin),
518-
users.Resolve(admin),
519-
)
520-
565+
tokenId, liquidity, amount0, amount1 := MakeMintPositionWithoutFee(t)
521566
uassert.Equal(t, tokenId, uint64(1), "tokenId should be 1")
522567
uassert.Equal(t, liquidity, "50000", "liquidity should be 50000")
523568
uassert.Equal(t, amount0, "50000", "amount0 should be 50000")

position/errors.gno

+22-10
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,31 @@ import (
77
)
88

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

23+
// TODO:
24+
// addDetailToError -> newErrorWithDetail
2225
func addDetailToError(err error, detail string) string {
2326
finalErr := ufmt.Errorf("%s || %s", err.Error(), detail)
2427
return finalErr.Error()
2528
}
29+
30+
// newErrorWithDetail returns a new error with the given detail
31+
// e.g. newErrorWithDetail(err, "detail")
32+
//
33+
// input: err error, detail string
34+
// output: "err.Error() || detail"
35+
func newErrorWithDetail(err error, detail string) string {
36+
return ufmt.Errorf("%s || %s", err.Error(), detail).Error()
37+
}

position/helper.gno

+104-9
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
package position
22

33
import (
4+
"std"
45
"strconv"
56

67
"gno.land/p/demo/grc/grc721"
8+
"gno.land/p/demo/ufmt"
9+
"gno.land/r/gnoswap/v1/common"
10+
"gno.land/r/gnoswap/v1/consts"
11+
"gno.land/r/gnoswap/v1/gnft"
712
)
813

14+
// nextId is the next tokenId to be minted
915
func getNextId() uint64 {
1016
return nextId
1117
}
1218

13-
func tid(tokenId interface{}) grc721.TokenID {
19+
// tokenIdFrom converts tokenId to grc721.TokenID type
20+
// NOTE: input parameter tokenId can be string, int, uint64, or grc721.TokenID
21+
// if tokenId is nil or not supported, it will panic
22+
// if tokenId is not found, it will panic
23+
// input: tokenId interface{}
24+
// output: grc721.TokenID
25+
func tokenIdFrom(tokenId interface{}) grc721.TokenID {
1426
if tokenId == nil {
15-
panic(addDetailToError(
16-
errDataNotFound,
17-
"helper.gno__tid() || tokenId is nil",
18-
))
27+
panic(newErrorWithDetail(errInvalidInput, "tokenId is nil"))
1928
}
2029

2130
switch tokenId.(type) {
@@ -28,9 +37,95 @@ func tid(tokenId interface{}) grc721.TokenID {
2837
case grc721.TokenID:
2938
return tokenId.(grc721.TokenID)
3039
default:
31-
panic(addDetailToError(
32-
errInvalidInput,
33-
"helper.gno__tid() || unsupported tokenId type",
34-
))
40+
panic(newErrorWithDetail(errInvalidInput, "unsupported tokenId type"))
3541
}
3642
}
43+
44+
// exists checks whether tokenId exists
45+
// If tokenId doesn't exist, return false, otherwise return true
46+
// input: tokenId uint64
47+
// output: bool
48+
func exists(tokenId uint64) bool {
49+
return gnft.Exists(tokenIdFrom(tokenId))
50+
}
51+
52+
// isOwner checks whether the caller is the owner of the tokenId
53+
// If the caller is the owner of the tokenId, return true, otherwise return false
54+
// input: tokenId uint64, addr std.Address
55+
// output: bool
56+
func isOwner(tokenId uint64, addr std.Address) bool {
57+
owner := gnft.OwnerOf(tokenIdFrom(tokenId))
58+
if owner == addr {
59+
return true
60+
}
61+
return false
62+
}
63+
64+
// isOperator checks whether the caller is the approved operator of the tokenId
65+
// If the caller is the approved operator of the tokenId, return true, otherwise return false
66+
// input: tokenId uint64, addr std.Address
67+
// output: bool
68+
func isOperator(tokenId uint64, addr std.Address) bool {
69+
operator, ok := gnft.GetApproved(tokenIdFrom(tokenId))
70+
if ok && operator == addr {
71+
return true
72+
}
73+
return false
74+
}
75+
76+
// isStaked checks whether tokenId is staked
77+
// If tokenId is staked, owner of tokenId is staker contract
78+
// If tokenId is staked, return true, otherwise return false
79+
// input: tokenId grc721.TokenID
80+
// output: bool
81+
func isStaked(tokenId grc721.TokenID) bool {
82+
exist := gnft.Exists(tokenId)
83+
if exist {
84+
owner := gnft.OwnerOf(tokenId)
85+
if owner == consts.STAKER_ADDR {
86+
return true
87+
}
88+
}
89+
return false
90+
}
91+
92+
// isOwnerOrOperator checks whether the caller is the owner or approved operator of the tokenId
93+
// If the caller is the owner or approved operator of the tokenId, return true, otherwise return false
94+
// input: addr std.Address, tokenId uint64
95+
// output: bool
96+
func isOwnerOrOperator(addr std.Address, tokenId uint64) bool {
97+
assertOnlyValidAddress(addr)
98+
if !exists(tokenId) {
99+
return false
100+
}
101+
if isOwner(tokenId, addr) || isOperator(tokenId, addr) {
102+
return true
103+
}
104+
if isStaked(tokenIdFrom(tokenId)) {
105+
position, exist := positions[tokenId]
106+
if exist && addr == position.operator {
107+
return true
108+
}
109+
}
110+
return false
111+
}
112+
113+
// splitOf divides poolKey into pToken0, pToken1, and pFee
114+
// If poolKey is invalid, it will panic
115+
//
116+
// input: poolKey string
117+
// output:
118+
// - token0Path string
119+
// - token1Path string
120+
// - fee uint32
121+
func splitOf(poolKey string) (string, string, uint32) {
122+
res, err := common.Split(poolKey, ":", 3)
123+
if err != nil {
124+
panic(newErrorWithDetail(errInvalidInput, ufmt.Sprintf("invalid poolKey(%s)", poolKey)))
125+
}
126+
127+
pToken0, pToken1, pFeeStr := res[0], res[1], res[2]
128+
129+
pFee, _ := strconv.Atoi(pFeeStr)
130+
return pToken0, pToken1, uint32(pFee)
131+
}

0 commit comments

Comments
 (0)