Skip to content

Commit

Permalink
debug/failed multi (#595)
Browse files Browse the repository at this point in the history
* debug: add stack trace for panic in CalcAmountOut

* fix: panic in woofi21 and dexalot and clipper

* fix: dexalot logic unexpectedly modifying price levels; refactor

* fix: curve stable-plain underflow when used as base pool (EX-2354)

also refactor to return error instead of panicking whenever possible

* fix: woofi-v21 UpdateBalance not copying MaxNotionalSwap and MaxGamma

* chore: clean up maxNotionalSwap and maxGamma checks
  • Loading branch information
NgoKimPhu authored Nov 18, 2024
1 parent 9f6b265 commit c318824
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 164 deletions.
4 changes: 4 additions & 0 deletions pkg/liquidity-source/clipper/pool_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var (
ErrInvalidTokenOut = errors.New("invalid token out")
ErrInvalidPair = errors.New("invalid pair")
ErrFMVCheckFailed = errors.New("FMV check failed")
ErrAmountOutNaN = errors.New("amountOut is NaN")

basisPoint float64 = 10000
)
Expand Down Expand Up @@ -151,6 +152,9 @@ func (p *PoolSimulator) CalcAmountOut(params pool.CalcAmountOutParams) (*pool.Ca

outY := qY - numerator/pY
outY *= math.Pow10(int(assetOut.Decimals))
if math.IsNaN(outY) {
return nil, ErrAmountOutNaN
}

amountOut, _ := big.NewFloat(outY).Int(nil)

Expand Down
3 changes: 3 additions & 0 deletions pkg/liquidity-source/curve/plain/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,9 @@ func (t *PoolSimulator) CalculateWithdrawOneCoinU256(
return err
}
dy.Sub(&xpReduced[i], &newYD)
if dy.Sign() <= 0 {
return ErrZero
}
dy.Div(number.SubUint64(dy, 1), &t.precisionMultipliers[i])
var dy0 = number.Div(number.Sub(&xp[i], &newY), &t.precisionMultipliers[i])
dyFee.Sub(dy0, dy)
Expand Down
9 changes: 8 additions & 1 deletion pkg/liquidity-source/curve/stable-ng/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,11 @@ func (t *PoolSimulator) GetDyByX(
}

// dy: uint256 = _xp[j] - y - 1 # -1 just in case there were some rounding errors
number.SafeSubZ(&xp[j], number.AddUint64(&y, 1), dy)
number.SafeSubZ(&xp[j], &y, dy)
if dy.Sign() <= 0 {
return ErrZero
}
dy.SubUint64(dy, 1)

// dy_fee: uint256 = unsafe_div(
// dy * self._dynamic_fee(
Expand Down Expand Up @@ -541,6 +545,9 @@ func (t *PoolSimulator) CalculateWithdrawOneCoinU256(tokenAmount *uint256.Int, i

// dy_0: uint256 = (xp[i] - new_y) * PRECISION / rates[i] # w/o fees
var dy0 = number.Div(number.SafeMul(number.SafeSub(&xp[i], &newY), Precision), &t.Extra.RateMultipliers[i])
if dy.Sign() <= 0 {
return ErrZero
}
// dy = unsafe_div((dy - 1) * PRECISION, rates[i]) # Withdraw less to account for rounding errors
dy.Div(number.SafeMul(number.SafeSub(dy, number.Number_1), Precision), &t.Extra.RateMultipliers[i])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ func (t *PoolSimulator) GetDy(
if err != nil {
return err
}
number.SafeSubZ(number.SafeSub(&xp[j], &y), number.Number_1, dy)
number.SafeSubZ(&xp[j], &y, dy)
if dy.Sign() <= 0 {
return ErrExchange0Coins
}
dy.SubUint64(dy, 1)
xp[j] = y
if j > 0 {
dy.Div(number.SafeMul(dy, Precision), &t.Extra.PriceScale[j-1])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ func (t *PoolSimulator) GetDy(
if err != nil {
return err
}
number.SafeSubZ(number.SafeSub(&xp[j], &y), number.Number_1, dy)
number.SafeSubZ(&xp[j], &y, dy)
if dy.Sign() <= 0 {
return ErrExchange0Coins
}
dy.SubUint64(dy, 1)
xp[j] = y
if j > 0 {
dy.Div(number.SafeMul(dy, Precision), &t.Extra.PriceScale[j-1])
Expand Down
75 changes: 33 additions & 42 deletions pkg/liquidity-source/dexalot/pool_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"math"
"math/big"
"slices"
"strings"

"github.com/KyberNetwork/logger"
Expand All @@ -19,7 +20,7 @@ var (
ErrEmptyPriceLevels = errors.New("empty price levels")
ErrAmountInIsLessThanLowestPriceLevel = errors.New("amountIn is less than lowest price level")
ErrAmountInIsGreaterThanHighestPriceLevel = errors.New("amountIn is greater than highest price level")
ErrNoSwapLimit = errors.New("swap limit is required for PMM pools")
ErrNoSwapLimit = errors.New("swap limit is required for dexalot pools")
)

type (
Expand Down Expand Up @@ -118,22 +119,24 @@ func (p *PoolSimulator) CalcAmountOut(params pool.CalcAmountOutParams) (*pool.Ca
if params.Limit == nil {
return nil, ErrNoSwapLimit
}
var limit = params.Limit

tokenIn, tokenOut, tokenInOriginal, tokenOutOriginal, levels := p.Token0, p.Token1, p.Token0Original, p.Token1Original, p.ZeroToOnePriceLevels
if params.TokenAmountIn.Token == p.Info.Tokens[1] {
tokenIn, tokenOut, tokenInOriginal, tokenOutOriginal, levels = p.Token1, p.Token0, p.Token1Original, p.Token0Original, p.OneToZeroPriceLevels
}
result, _, err := p.swap(params.TokenAmountIn.Amount, tokenIn, tokenOut, tokenInOriginal, tokenOutOriginal, levels)
if err != nil {
return nil, err
}

inventoryLimit := limit.GetLimit(tokenOut.Address)

inventoryLimit := params.Limit.GetLimit(tokenOut.Address)
if result.TokenAmountOut.Amount.Cmp(inventoryLimit) > 0 {
return nil, errors.New("not enough inventory")
}
return result, err
return result, nil
}

func (p *PoolSimulator) UpdateBalance(params pool.UpdateBalanceParams) {
// Ignore for now cause logic not exposed
tokenIn, tokenOut := p.Token0, p.Token1
if params.TokenAmountIn.Token == p.Token1.Address {
tokenIn, tokenOut = p.Token1, p.Token0
Expand Down Expand Up @@ -193,56 +196,44 @@ func (p *PoolSimulator) swap(amountIn *big.Int, baseToken, quoteToken entity.Poo
func getAmountOut(amountIn *big.Float, priceLevels []PriceLevel, amountOut *big.Float) error {
if len(priceLevels) == 0 {
return ErrEmptyPriceLevels
}
// Check lower bound
if amountIn.Cmp(priceLevels[0].Quote) < 0 {
} else if amountIn.Cmp(priceLevels[0].Quote) < 0 {
return ErrAmountInIsLessThanLowestPriceLevel
}

if amountIn.Cmp(priceLevels[len(priceLevels)-1].Quote) > 0 {
} else if amountIn.Cmp(priceLevels[len(priceLevels)-1].Quote) > 0 {
return ErrAmountInIsGreaterThanHighestPriceLevel
}
left := 0
right := len(priceLevels)
var qty *big.Float

for left < right {
mid := (left + right) / 2
qty = priceLevels[mid].Quote
if qty.Cmp(amountIn) <= 0 {
left = mid + 1
} else {
right = mid
}
}

levelIdx, _ := slices.BinarySearchFunc(priceLevels, amountIn, func(p PriceLevel, amtIn *big.Float) int {
return p.Quote.Cmp(amtIn)
}) // should always be found due to checks above
level := priceLevels[levelIdx]

var price *big.Float
if amountIn.Cmp(qty) == 0 {
price = priceLevels[left-1].Price // TODO: check with https://docs.dexalot.com/apiv2/SimpleSwap.html#_3b-request-batched-quotes-optional
} else if left == 0 {
price = big.NewFloat(0)
} else if left < len(priceLevels) {
price = priceLevels[left-1].Price.Add(
priceLevels[left-1].Price,
new(big.Float).Quo(
new(big.Float).Mul(
new(big.Float).Sub(priceLevels[left].Price, priceLevels[left-1].Price),
new(big.Float).Sub(amountIn, priceLevels[left-1].Quote),
if amountIn.Cmp(level.Quote) == 0 {
price = priceLevels[levelIdx].Price
} else {
prevLevel := priceLevels[levelIdx-1]
var num, tmp big.Float
price = num.Add(
prevLevel.Price,
num.Quo(
num.Mul(
num.Sub(level.Price, prevLevel.Price),
tmp.Sub(amountIn, prevLevel.Quote),
),
new(big.Float).Sub(priceLevels[left].Quote, priceLevels[left-1].Quote),
tmp.Sub(level.Quote, prevLevel.Quote),
),
)
}

amountOut.Mul(amountIn, price)
return nil
}

func (p *PoolSimulator) CalculateLimit() map[string]*big.Int {
var pmmInventory = make(map[string]*big.Int, len(p.GetTokens()))
tokens := p.GetTokens()
rsv := p.GetReserves()
tokens, rsv := p.GetTokens(), p.GetReserves()
inventory := make(map[string]*big.Int, len(tokens))
for i, tok := range tokens {
pmmInventory[tok] = big.NewInt(0).Set(rsv[i]) //clone here.
inventory[tok] = new(big.Int).Set(rsv[i])
}
return pmmInventory
return inventory
}
3 changes: 2 additions & 1 deletion pkg/liquidity-source/dexalot/pool_simulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"math/big"
"testing"

"github.com/stretchr/testify/assert"

"github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity"
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool"
"github.com/stretchr/testify/assert"
)

/*
Expand Down
Loading

0 comments on commit c318824

Please sign in to comment.