Skip to content

Commit

Permalink
refactor: return remaining amount for consumers
Browse files Browse the repository at this point in the history
  • Loading branch information
piavgh committed Apr 2, 2024
1 parent e16853a commit d851d27
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 47 deletions.
72 changes: 72 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: 'Release'

permissions: write-all

on:
workflow_dispatch:
inputs:
version:
description: 'Release version'
type: string
required: true

jobs:
prepare:
runs-on: [ubuntu-22.04]
outputs:
version_tag: ${{ steps.version_tag.outputs.value }}
build_date: ${{ steps.build_date.outputs.value }}
steps:
- name: Check branch
if: ${{ !startsWith(github.ref, 'refs/heads/release') }}
run: |
echo "This workflow should be triggered with workflow_dispatch on release branch"
exit 1
- name: Format version tag
shell: bash
id: version_tag
env:
INPUT_TAG: ${{ github.event.inputs.version }}
run: |
TAG=${INPUT_TAG#v}
echo "::set-output name=value::v$TAG"
- name: Build date
shell: bash
id: build_date
run: echo "::set-output name=value::$(date +%FT%T%z)"

release:
needs:
- prepare
runs-on: [ ubuntu-22.04 ]
env:
VERSION_TAG: ${{ needs.prepare.outputs.version_tag }}
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0

- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: "1.21.x"

- name: Setup Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Create tag
run: |
git tag -d "$VERSION_TAG" 2> /dev/null || echo "Release tag '$VERSION_TAG' does NOT exist"
git tag --annotate --message "pancake-v3-sdk $VERSION_TAG" "$VERSION_TAG"
git push origin "refs/tags/$VERSION_TAG"
- name: Create release
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ env.VERSION_TAG }}
prerelease: false
name: "Pancake V3 SDK ${{ env.VERSION_TAG }}"
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ jobs:
test:
strategy:
matrix:
go-version: [1.20.x]
os: [ubuntu-latest, macos-latest, windows-latest]
go-version: [1.18.x]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
Expand All @@ -23,7 +23,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.20.x
go-version: 1.18.x
- name: Checkout code
uses: actions/checkout@v2
- uses: actions/cache@v2
Expand Down
131 changes: 99 additions & 32 deletions entities/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
)

var (
ErrFeeTooHigh = errors.New("Fee too high")
ErrInvalidSqrtRatioX96 = errors.New("Invalid sqrtRatioX96")
ErrTokenNotInvolved = errors.New("Token not involved in pool")
ErrFeeTooHigh = errors.New("fee too high")
ErrInvalidSqrtRatioX96 = errors.New("invalid sqrtRatioX96")
ErrTokenNotInvolved = errors.New("token not involved in pool")
ErrSqrtPriceLimitX96TooLow = errors.New("SqrtPriceLimitX96 too low")
ErrSqrtPriceLimitX96TooHigh = errors.New("SqrtPriceLimitX96 too high")
)
Expand Down Expand Up @@ -43,6 +43,29 @@ type Pool struct {
token1Price *entities.Price
}

type SwapResult struct {
amountCalculated *big.Int
sqrtRatioX96 *big.Int
liquidity *big.Int
remainingTargetAmount *big.Int
currentTick int
crossInitTickLoops int
}

type GetOutputAmountResult struct {
ReturnedAmount *entities.CurrencyAmount
RemainingAmountIn *entities.CurrencyAmount
NewPoolState *Pool
CrossInitTickLoops int
}

type GetInputAmountResult struct {
ReturnedAmount *entities.CurrencyAmount
RemainingAmountOut *entities.CurrencyAmount
NewPoolState *Pool
CrossInitTickLoops int
}

func GetAddress(tokenA, tokenB *entities.Token, fee constants.FeeAmount, initCodeHashManualOverride string) (common.Address, error) {
return utils.ComputePoolAddress(constants.FactoryAddress, tokenA, tokenB, fee, initCodeHashManualOverride)
}
Expand Down Expand Up @@ -149,26 +172,40 @@ func (p *Pool) ChainID() uint {
* @param sqrtPriceLimitX96 The Q64.96 sqrt price limit
* @returns The output amount and the pool with updated state
*/
func (p *Pool) GetOutputAmount(inputAmount *entities.CurrencyAmount, sqrtPriceLimitX96 *big.Int) (*entities.CurrencyAmount, *Pool, error) {
func (p *Pool) GetOutputAmount(inputAmount *entities.CurrencyAmount, sqrtPriceLimitX96 *big.Int) (*GetOutputAmountResult, error) {
if !(inputAmount.Currency.IsToken() && p.InvolvesToken(inputAmount.Currency.Wrapped())) {
return nil, nil, ErrTokenNotInvolved
return nil, ErrTokenNotInvolved
}
zeroForOne := inputAmount.Currency.Equal(p.Token0)
outputAmount, sqrtRatioX96, liquidity, tickCurrent, err := p.swap(zeroForOne, inputAmount.Quotient(), sqrtPriceLimitX96)
swapResult, err := p.swap(zeroForOne, inputAmount.Quotient(), sqrtPriceLimitX96)
if err != nil {
return nil, nil, err
return nil, err
}
var outputToken *entities.Token
if zeroForOne {
outputToken = p.Token1
} else {
outputToken = p.Token0
}
pool, err := NewPool(p.Token0, p.Token1, p.Fee, sqrtRatioX96, liquidity, tickCurrent, p.TickDataProvider)
pool, err := NewPool(
p.Token0,
p.Token1,
p.Fee,
swapResult.sqrtRatioX96,
swapResult.liquidity,
swapResult.currentTick,
p.TickDataProvider,
)
if err != nil {
return nil, nil, err
return nil, err
}
return entities.FromRawAmount(outputToken, new(big.Int).Mul(outputAmount, constants.NegativeOne)), pool, nil

return &GetOutputAmountResult{
ReturnedAmount: entities.FromRawAmount(outputToken, new(big.Int).Mul(swapResult.amountCalculated, constants.NegativeOne)),
RemainingAmountIn: entities.FromRawAmount(inputAmount.Currency, swapResult.remainingTargetAmount),
NewPoolState: pool,
CrossInitTickLoops: swapResult.crossInitTickLoops,
}, nil
}

/**
Expand All @@ -177,39 +214,55 @@ func (p *Pool) GetOutputAmount(inputAmount *entities.CurrencyAmount, sqrtPriceLi
* @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this value after the swap. If one for zero, the price cannot be greater than this value after the swap
* @returns The input amount and the pool with updated state
*/
func (p *Pool) GetInputAmount(outputAmount *entities.CurrencyAmount, sqrtPriceLimitX96 *big.Int) (*entities.CurrencyAmount, *Pool, error) {
func (p *Pool) GetInputAmount(outputAmount *entities.CurrencyAmount, sqrtPriceLimitX96 *big.Int) (*GetInputAmountResult, error) {
if !(outputAmount.Currency.IsToken() && p.InvolvesToken(outputAmount.Currency.Wrapped())) {
return nil, nil, ErrTokenNotInvolved
return nil, ErrTokenNotInvolved
}
zeroForOne := outputAmount.Currency.Equal(p.Token1)
inputAmount, sqrtRatioX96, liquidity, tickCurrent, err := p.swap(zeroForOne, new(big.Int).Mul(outputAmount.Quotient(), constants.NegativeOne), sqrtPriceLimitX96)
swapResult, err := p.swap(zeroForOne, new(big.Int).Mul(outputAmount.Quotient(), constants.NegativeOne), sqrtPriceLimitX96)
if err != nil {
return nil, nil, err
return nil, err
}
var inputToken *entities.Token
if zeroForOne {
inputToken = p.Token0
} else {
inputToken = p.Token1
}
pool, err := NewPool(p.Token0, p.Token1, p.Fee, sqrtRatioX96, liquidity, tickCurrent, p.TickDataProvider)
pool, err := NewPool(
p.Token0,
p.Token1,
p.Fee,
swapResult.sqrtRatioX96,
swapResult.liquidity,
swapResult.currentTick,
p.TickDataProvider,
)
if err != nil {
return nil, nil, err
return nil, err
}
return entities.FromRawAmount(inputToken, inputAmount), pool, nil

return &GetInputAmountResult{
ReturnedAmount: entities.FromRawAmount(inputToken, swapResult.amountCalculated),
RemainingAmountOut: entities.FromRawAmount(outputAmount.Currency, swapResult.remainingTargetAmount),
NewPoolState: pool,
CrossInitTickLoops: swapResult.crossInitTickLoops,
}, nil
}

/**
* Executes a swap
* @param zeroForOne Whether the amount in is token0 or token1
* @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
* @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this value after the swap. If one for zero, the price cannot be greater than this value after the swap
* @returns amountCalculated
* @returns sqrtRatioX96
* @returns liquidity
* @returns tickCurrent
* @returns swapResult.amountCalculated
* @returns swapResult.sqrtRatioX96
* @returns swapResult.liquidity
* @returns swapResult.tickCurrent
*/
func (p *Pool) swap(zeroForOne bool, amountSpecified, sqrtPriceLimitX96 *big.Int) (amountCalCulated *big.Int, sqrtRatioX96 *big.Int, liquidity *big.Int, tickCurrent int, err error) {
func (p *Pool) swap(zeroForOne bool, amountSpecified, sqrtPriceLimitX96 *big.Int) (*SwapResult, error) {
var err error

if sqrtPriceLimitX96 == nil {
if zeroForOne {
sqrtPriceLimitX96 = new(big.Int).Add(utils.MinSqrtRatio, constants.One)
Expand All @@ -220,17 +273,17 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified, sqrtPriceLimitX96 *big.Int

if zeroForOne {
if sqrtPriceLimitX96.Cmp(utils.MinSqrtRatio) < 0 {
return nil, nil, nil, 0, ErrSqrtPriceLimitX96TooLow
return nil, ErrSqrtPriceLimitX96TooLow
}
if sqrtPriceLimitX96.Cmp(p.SqrtRatioX96) >= 0 {
return nil, nil, nil, 0, ErrSqrtPriceLimitX96TooHigh
return nil, ErrSqrtPriceLimitX96TooHigh
}
} else {
if sqrtPriceLimitX96.Cmp(utils.MaxSqrtRatio) > 0 {
return nil, nil, nil, 0, ErrSqrtPriceLimitX96TooHigh
return nil, ErrSqrtPriceLimitX96TooHigh
}
if sqrtPriceLimitX96.Cmp(p.SqrtRatioX96) <= 0 {
return nil, nil, nil, 0, ErrSqrtPriceLimitX96TooLow
return nil, ErrSqrtPriceLimitX96TooLow
}
}

Expand All @@ -252,6 +305,10 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified, sqrtPriceLimitX96 *big.Int
liquidity: p.Liquidity,
}

// crossInitTickLoops is the number of loops that cross an initialized tick.
// We only count when tick passes an initialized tick, since gas only significant in this case.
crossInitTickLoops := 0

// start swap while loop
for state.amountSpecifiedRemaining.Cmp(constants.Zero) != 0 && state.sqrtPriceX96.Cmp(sqrtPriceLimitX96) != 0 {
var step StepComputations
Expand All @@ -262,7 +319,7 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified, sqrtPriceLimitX96 *big.Int
// tickBitmap.nextInitializedTickWithinOneWord
step.tickNext, step.initialized, err = p.TickDataProvider.NextInitializedTickIndex(state.tick, zeroForOne)
if err != nil {
return nil, nil, nil, 0, err
return nil, err
}

if step.tickNext < utils.MinTick {
Expand All @@ -273,7 +330,7 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified, sqrtPriceLimitX96 *big.Int

step.sqrtPriceNextX96, err = utils.GetSqrtRatioAtTick(step.tickNext)
if err != nil {
return nil, nil, nil, 0, err
return nil, err
}
var targetValue *big.Int
if zeroForOne {
Expand All @@ -292,7 +349,7 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified, sqrtPriceLimitX96 *big.Int

state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount, err = utils.ComputeSwapStep(state.sqrtPriceX96, targetValue, state.liquidity, state.amountSpecifiedRemaining, p.Fee)
if err != nil {
return nil, nil, nil, 0, err
return nil, err
}

if exactInput {
Expand All @@ -309,7 +366,7 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified, sqrtPriceLimitX96 *big.Int
if step.initialized {
tick, err := p.TickDataProvider.GetTick(step.tickNext)
if err != nil {
return nil, nil, nil, 0, err
return nil, err
}

liquidityNet := tick.LiquidityNet
Expand All @@ -319,6 +376,8 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified, sqrtPriceLimitX96 *big.Int
liquidityNet = new(big.Int).Mul(liquidityNet, constants.NegativeOne)
}
state.liquidity = utils.AddDelta(state.liquidity, liquidityNet)

crossInitTickLoops++
}
if zeroForOne {
state.tick = step.tickNext - 1
Expand All @@ -329,11 +388,19 @@ func (p *Pool) swap(zeroForOne bool, amountSpecified, sqrtPriceLimitX96 *big.Int
// recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
state.tick, err = utils.GetTickAtSqrtRatio(state.sqrtPriceX96)
if err != nil {
return nil, nil, nil, 0, err
return nil, err
}
}
}
return state.amountCalculated, state.sqrtPriceX96, state.liquidity, state.tick, nil

return &SwapResult{
amountCalculated: state.amountCalculated,
sqrtRatioX96: state.sqrtPriceX96,
liquidity: state.liquidity,
currentTick: state.tick,
remainingTargetAmount: state.amountSpecifiedRemaining,
crossInitTickLoops: crossInitTickLoops,
}, nil
}

func (p *Pool) tickSpacing() int {
Expand Down
Loading

0 comments on commit d851d27

Please sign in to comment.