Skip to content

Commit

Permalink
refactor: pool mint
Browse files Browse the repository at this point in the history
  • Loading branch information
onlyhyde committed Dec 17, 2024
1 parent 90e4ff0 commit f12d970
Show file tree
Hide file tree
Showing 15 changed files with 504 additions and 105 deletions.
10 changes: 5 additions & 5 deletions pool/getter.gno
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,23 @@ func PoolGetLiquidity(poolPath string) string {

// position
func PoolGetPositionLiquidity(poolPath, key string) string {
return mustGetPool(poolPath).GetPositionLiquidity(key).ToString()
return mustGetPool(poolPath).PositionLiquidity(key).ToString()
}

func PoolGetPositionFeeGrowthInside0LastX128(poolPath, key string) string {
return mustGetPool(poolPath).GetPositionFeeGrowthInside0LastX128(key).ToString()
return mustGetPool(poolPath).PositionFeeGrowthInside0LastX128(key).ToString()
}

func PoolGetPositionFeeGrowthInside1LastX128(poolPath, key string) string {
return mustGetPool(poolPath).GetPositionFeeGrowthInside1LastX128(key).ToString()
return mustGetPool(poolPath).PositionFeeGrowthInside1LastX128(key).ToString()
}

func PoolGetPositionTokensOwed0(poolPath, key string) string {
return mustGetPool(poolPath).GetPositionTokensOwed0(key).ToString()
return mustGetPool(poolPath).PositionTokensOwed0(key).ToString()
}

func PoolGetPositionTokensOwed1(poolPath, key string) string {
return mustGetPool(poolPath).GetPositionTokensOwed1(key).ToString()
return mustGetPool(poolPath).PositionTokensOwed1(key).ToString()
}

// tick
Expand Down
42 changes: 30 additions & 12 deletions pool/pool.gno
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,32 @@ import (
u256 "gno.land/p/gnoswap/uint256"
)

// Mint creates a new position and mints liquidity tokens.
// Returns minted amount0, amount1 in string
// Mint adds liquidity to a pool by minting a new position.
//
// This function mints a liquidity position within the specified tick range in a pool.
// It verifies caller permissions, validates inputs, and updates the pool's state. Additionally,
// it transfers the required amounts of token0 and token1 from the caller to the pool.
//
// Parameters:
// - token0Path: string, the path or identifier for token 0.
// - token1Path: string, the path or identifier for token 1.
// - fee: uint32, the fee tier of the pool.
// - recipient: std.Address, the address to receive the newly created position.
// - tickLower: int32, the lower tick boundary of the position.
// - tickUpper: int32, the upper tick boundary of the position.
// - liquidityAmount: string, the amount of liquidity to add, provided as a decimal string.
// - positionCaller: std.Address, the address of the entity calling the function (e.g., the position owner).
//
// Returns:
// - string: The amount of token 0 transferred to the pool as a string.
// - string: The amount of token 1 transferred to the pool as a string.
//
// Panic Conditions:
// - The system is halted (`common.IsHalted()`).
// - Caller lacks permission to mint a position when `common.GetLimitCaller()` is enforced.
// - The provided `liquidityAmount` is zero.
// - Any failure during token transfers or position modifications.
//
// ref: https://docs.gnoswap.io/contracts/pool/pool.gno#mint
func Mint(
token0Path string,
Expand All @@ -25,15 +49,9 @@ func Mint(
liquidityAmount string,
positionCaller std.Address,
) (string, string) {
common.IsHalted()
assertOnlyNotHalted()
if common.GetLimitCaller() {
caller := std.PrevRealm().Addr()
if err := common.PositionOnly(caller); err != nil {
panic(addDetailToError(
errNoPermission,
ufmt.Sprintf("only position(%s) can call pool mint(), called from %s", consts.POSITION_ADDR, caller.String()),
))
}
assertOnlyPositionContract()
}

liquidity := u256.MustFromDecimal(liquidityAmount)
Expand Down Expand Up @@ -93,7 +111,7 @@ func Burn(
position.tokensOwed1 = new(u256.Uint).Add(position.tokensOwed1, amount1)
}

positionKey := positionGetKey(caller, tickLower, tickUpper)
positionKey := getPositionKey(caller, tickLower, tickUpper)
pool.positions[positionKey] = position

// actual token transfer happens in Collect()
Expand Down Expand Up @@ -127,7 +145,7 @@ func Collect(

pool := GetPool(token0Path, token1Path, fee)

positionKey := positionGetKey(std.PrevRealm().Addr(), tickLower, tickUpper)
positionKey := getPositionKey(std.PrevRealm().Addr(), tickLower, tickUpper)
position, exist := pool.positions[positionKey]
if !exist {
panic(addDetailToError(
Expand Down
7 changes: 1 addition & 6 deletions pool/pool_test.gno
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
package pool

import (
"std"
"testing"

"gno.land/p/demo/testutils"
"gno.land/p/demo/uassert"

i256 "gno.land/p/gnoswap/int256"
u256 "gno.land/p/gnoswap/uint256"

"gno.land/r/gnoswap/v1/consts"
Expand Down Expand Up @@ -100,7 +96,7 @@ func TestBurn(t *testing.T) {
}

// setup position for this test
posKey := positionGetKey(mockCaller, tt.tickLower, tt.tickUpper)
posKey := getPositionKey(mockCaller, tt.tickLower, tt.tickUpper)
mockPool.positions[posKey] = mockPosition

if tt.expectPanic {
Expand Down Expand Up @@ -139,4 +135,3 @@ func TestBurn(t *testing.T) {
})
}
}

123 changes: 79 additions & 44 deletions pool/position.gno
Original file line number Diff line number Diff line change
Expand Up @@ -15,49 +15,52 @@ var (
Q128 = u256.MustFromDecimal(consts.Q128)
)

// positionGetKey generates a unique key for a position based on the owner's address and the tick range.
func positionGetKey(
// getPositionKey generates a unique, encoded key for a liquidity position.
//
// This function creates a unique key for identifying a liquidity position in a pool. The key is based
// on the position's owner address, lower tick, and upper tick values. The generated key is then encoded
// as a base64 string to ensure compatibility and uniqueness.
//
// Parameters:
// - owner: std.Address, the address of the position's owner.
// - tickLower: int32, the lower tick boundary for the position.
// - tickUpper: int32, the upper tick boundary for the position.
//
// Returns:
// - string: A base64-encoded string representing the unique position key.
//
// Workflow:
// 1. Validates that the `owner` address is valid using `assertOnlyValidAddress`.
// 2. Ensures `tickLower` is less than `tickUpper` using `assertTickLowerLessThanUpper`.
// 3. Constructs the position key as a formatted string:
// "<owner>__<tickLower>__<tickUpper>"
// 4. Encodes the generated position key into a base64 string for safety and uniqueness.
// 5. Returns the encoded position key.
//
// Example:
//
// owner := std.Address("0x123456789")
// positionKey := getPositionKey(owner, 100, 200)
// fmt.Println("Position Key:", positionKey)
// // Output: base64-encoded string representing "0x123456789__100__200"
//
// Notes:
// - The base64 encoding ensures that the position key can be safely used as an identifier
// across different systems or data stores.
// - The function will panic if:
// - The `owner` address is invalid.
// - `tickLower` is greater than or equal to `tickUpper`.
func getPositionKey(
owner std.Address,
tickLower int32,
tickUpper int32,
) string {
if !owner.IsValid() {
panic(addDetailToError(
errInvalidAddress,
ufmt.Sprintf("position.gno__positionGetKey() || invalid owner address %s", owner.String()),
))
}

if tickLower > tickUpper {
panic(addDetailToError(
errInvalidTickRange,
ufmt.Sprintf("position.gno__positionGetKey() || tickLower(%d) is greater than tickUpper(%d)", tickLower, tickUpper),
))
}
assertOnlyValidAddress(owner)
assertTickLowerLessThanUpper(tickLower, tickUpper)

positionKey := ufmt.Sprintf("%s__%d__%d", owner.String(), tickLower, tickUpper)

encoded := base64.StdEncoding.EncodeToString([]byte(positionKey))
return encoded
}

// positionUpdateWithKey updates a position in the pool and returns the updated position.
func (pool *Pool) positionUpdateWithKey(
positionKey string,
liquidityDelta *i256.Int,
feeGrowthInside0X128 *u256.Uint,
feeGrowthInside1X128 *u256.Uint,
) PositionInfo {
// if pointer is nil, set to zero for calculation
liquidityDelta = liquidityDelta.NilToZero()
feeGrowthInside0X128 = feeGrowthInside0X128.NilToZero()
feeGrowthInside1X128 = feeGrowthInside1X128.NilToZero()

positionToUpdate := pool.positions[positionKey]
positionAfterUpdate := positionUpdate(positionToUpdate, liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128)
pool.positions[positionKey] = positionAfterUpdate

return positionAfterUpdate
encodedPositionKey := base64.StdEncoding.EncodeToString([]byte(positionKey))
return encodedPositionKey
}

// positionUpdate calculates and returns an updated PositionInfo.
Expand All @@ -67,7 +70,7 @@ func positionUpdate(
feeGrowthInside0X128 *u256.Uint,
feeGrowthInside1X128 *u256.Uint,
) PositionInfo {
position.init()
position.valueOrZero()

var liquidityNext *u256.Uint
if liquidityDelta.IsZero() {
Expand Down Expand Up @@ -105,25 +108,48 @@ func positionUpdate(
return position
}

// receiver getters
// positionUpdateWithKey updates a position in the pool and returns the updated position.
func (p *Pool) positionUpdateWithKey(
positionKey string,
liquidityDelta *i256.Int,
feeGrowthInside0X128 *u256.Uint,
feeGrowthInside1X128 *u256.Uint,
) PositionInfo {
// if pointer is nil, set to zero for calculation
liquidityDelta = liquidityDelta.NilToZero()
feeGrowthInside0X128 = feeGrowthInside0X128.NilToZero()
feeGrowthInside1X128 = feeGrowthInside1X128.NilToZero()

positionToUpdate := p.GetPosition(positionKey)
positionAfterUpdate := positionUpdate(positionToUpdate, liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128)

p.positions[positionKey] = positionAfterUpdate

return positionAfterUpdate
}

func (p *Pool) GetPositionLiquidity(key string) *u256.Uint {
// PositionLiquidity returns the liquidity of a position.
func (p *Pool) PositionLiquidity(key string) *u256.Uint {
return p.mustGetPosition(key).liquidity
}

func (p *Pool) GetPositionFeeGrowthInside0LastX128(key string) *u256.Uint {
// PositionFeeGrowthInside0LastX128 returns the fee growth of token0 inside a position.
func (p *Pool) PositionFeeGrowthInside0LastX128(key string) *u256.Uint {
return p.mustGetPosition(key).feeGrowthInside0LastX128
}

func (p *Pool) GetPositionFeeGrowthInside1LastX128(key string) *u256.Uint {
// PositionFeeGrowthInside1LastX128 returns the fee growth of token1 inside a position.
func (p *Pool) PositionFeeGrowthInside1LastX128(key string) *u256.Uint {
return p.mustGetPosition(key).feeGrowthInside1LastX128
}

func (p *Pool) GetPositionTokensOwed0(key string) *u256.Uint {
// PositionTokensOwed0 returns the amount of token0 owed by a position.
func (p *Pool) PositionTokensOwed0(key string) *u256.Uint {
return p.mustGetPosition(key).tokensOwed0
}

func (p *Pool) GetPositionTokensOwed1(key string) *u256.Uint {
// PositionTokensOwed1 returns the amount of token1 owed by a position.
func (p *Pool) PositionTokensOwed1(key string) *u256.Uint {
return p.mustGetPosition(key).tokensOwed1
}

Expand All @@ -135,6 +161,15 @@ func (p *Pool) mustGetPosition(key string) PositionInfo {
ufmt.Sprintf("position(%s) does not exist", key),
))
}
return position
}

func (p *Pool) GetPosition(key string) PositionInfo {
position, exist := p.positions[key]
if !exist {
newPosition := PositionInfo{}
newPosition.valueOrZero()
return newPosition
}
return position
}
6 changes: 3 additions & 3 deletions pool/position_modify.gno
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ func checkTicks(tickLower, tickUpper int32) {

func assertTickLowerLessThanUpper(tickLower, tickUpper int32) {
if tickLower >= tickUpper {
panic(ufmt.Sprintf(
"%v. tickLower(%d) >= tickUpper(%d)",
errTickLowerGtTickUpper, tickLower, tickUpper,
panic(addDetailToError(
errInvalidTickRange,
ufmt.Sprintf("tickLower(%d), tickUpper(%d)", tickLower, tickUpper),
))
}
}
Expand Down
21 changes: 13 additions & 8 deletions pool/position_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,23 @@ func TestPositionGetKey(t *testing.T) {
panicMsg string
expectedKey string
}{
{invalidAddr, 100, 200, true, `[GNOSWAP-POOL-023] invalid address || position.gno__positionGetKey() || invalid owner address invalidAddr`, ""}, // invalid address
{validAddr, 200, 100, true, `[GNOSWAP-POOL-024] tickLower is greater than tickUpper || position.gno__positionGetKey() || tickLower(200) is greater than tickUpper(100)`, ""}, // tickLower > tickUpper
{validAddr, -100, -200, true, `[GNOSWAP-POOL-024] tickLower is greater than tickUpper || position.gno__positionGetKey() || tickLower(-100) is greater than tickUpper(-200)`, ""}, // tickLower > tickUpper
{validAddr, 100, 100, false, "", "ZzF3ZXNrYzZ0eWc5anhndWpsdGEwNDdoNmx0YTA0N2g2bGRqbHVkdV9fMTAwX18xMDA="}, // tickLower == tickUpper
{validAddr, 100, 200, false, "", "ZzF3ZXNrYzZ0eWc5anhndWpsdGEwNDdoNmx0YTA0N2g2bGRqbHVkdV9fMTAwX18yMDA="}, // tickLower < tickUpper
{invalidAddr, 100, 200, true, `[GNOSWAP-POOL-023] invalid address || (invalidAddr)`, ""}, // invalid address
{validAddr, 200, 100, true, `[GNOSWAP-POOL-024] tickLower is greater than tickUpper || tickLower(200), tickUpper(100)`, ""}, // tickLower > tickUpper
{validAddr, -100, -200, true, `[GNOSWAP-POOL-024] tickLower is greater than tickUpper || tickLower(-100), tickUpper(-200)`, ""}, // tickLower > tickUpper
{validAddr, 100, 100, true, "[GNOSWAP-POOL-024] tickLower is greater than tickUpper || tickLower(100), tickUpper(100)", ""}, // tickLower == tickUpper
{validAddr, 100, 200, false, "", "ZzF3ZXNrYzZ0eWc5anhndWpsdGEwNDdoNmx0YTA0N2g2bGRqbHVkdV9fMTAwX18yMDA="}, // tickLower < tickUpper
}

for _, tc := range tests {
defer func() {
if r := recover(); r != nil {
uassert.Equal(t, tc.panicMsg, r.(string))
}
}()
if tc.shouldPanic {
uassert.PanicsWithMessage(t, tc.panicMsg, func() { positionGetKey(tc.owner, tc.tickLower, tc.tickUpper) })
uassert.PanicsWithMessage(t, tc.panicMsg, func() { getPositionKey(tc.owner, tc.tickLower, tc.tickUpper) })
} else {
key := positionGetKey(tc.owner, tc.tickLower, tc.tickUpper)
key := getPositionKey(tc.owner, tc.tickLower, tc.tickUpper)
uassert.Equal(t, tc.expectedKey, key)
}
}
Expand All @@ -55,7 +60,7 @@ func TestPositionUpdateWithKey(t *testing.T) {
)
dummyPool = newPool(poolParams)

positionKey = positionGetKey(
positionKey = getPositionKey(
testutils.TestAddress("dummyAddr"),
100,
200,
Expand Down
Loading

0 comments on commit f12d970

Please sign in to comment.