diff --git a/pkg/source/limitorder/constant.go b/pkg/source/limitorder/constant.go index 03b24848e..4bca12445 100644 --- a/pkg/source/limitorder/constant.go +++ b/pkg/source/limitorder/constant.go @@ -19,7 +19,7 @@ var ( Buy SwapSide = "BUY" Sell SwapSide = "SELL" - // BasGas is base gas to executor a tx for LO. + // BaseGas is base gas to executor a tx for LO. BaseGas = 90000 // GasPerOrderExecutor is gas for executing an order. diff --git a/pkg/source/limitorder/errors.go b/pkg/source/limitorder/errors.go index 505668024..65e1eefad 100644 --- a/pkg/source/limitorder/errors.go +++ b/pkg/source/limitorder/errors.go @@ -3,5 +3,6 @@ package limitorder import "errors" var ErrCannotFulfillAmountIn = errors.New("cannot fulfill amountIn") +var ErrCannotFulfillAmountOut = errors.New("cannot fulfill amountOut") var InvalidSwapInfo = errors.New("invalid swap info") var ErrSameSenderMaker = errors.New("swap recipient is the same as order receiver") diff --git a/pkg/source/limitorder/http_client_dto.go b/pkg/source/limitorder/http_client_dto.go index b43c67a79..167f5dee9 100644 --- a/pkg/source/limitorder/http_client_dto.go +++ b/pkg/source/limitorder/http_client_dto.go @@ -4,6 +4,8 @@ import ( "fmt" "math/big" "strconv" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" ) const ( @@ -207,3 +209,24 @@ func toOrder(ordersData []*orderData) ([]*order, error) { } return result, nil } + +func (o *order) RemainingAmount(limit pool.SwapLimit, filledMakingAmountByMaker map[string]*big.Int) (makingAmount, takingAmount *big.Int) { + if o.AvailableMakingAmount == nil { + makingAmount = new(big.Int).Sub(o.MakingAmount, o.FilledMakingAmount) + takingAmount = new(big.Int).Sub(o.TakingAmount, o.FilledTakingAmount) + } else { + makingAmount = o.AvailableMakingAmount + // the actual available balance might be less than `AvailableMakingAmount` + // for example if we have used another order for this same maker and makerAsset (but with different takerAsset) before + makerRemainingBalance := getMakerRemainingBalance(limit, filledMakingAmountByMaker, o.Maker, o.MakerAsset) + if makerRemainingBalance != nil && makingAmount.Cmp(makerRemainingBalance) > 0 { + makingAmount = makerRemainingBalance + } + takingAmount = new(big.Int).Div( + new(big.Int).Mul(makingAmount, o.TakingAmount), + o.MakingAmount, + ) // remainingTakingAmount = remainingMakingAmount * order.TakingAmount / order.MakingAmount + } + + return makingAmount, takingAmount +} diff --git a/pkg/source/limitorder/pool_simulator.go b/pkg/source/limitorder/pool_simulator.go index 0ecb6f131..e558ea1b3 100644 --- a/pkg/source/limitorder/pool_simulator.go +++ b/pkg/source/limitorder/pool_simulator.go @@ -48,7 +48,7 @@ func NewPoolSimulator(entityPool entity.Pool) (*PoolSimulator, error) { var contractAddress string var staticExtra StaticExtra if err := json.Unmarshal([]byte(entityPool.StaticExtra), &staticExtra); err != nil { - // this is optional for now, will changed to required later + // this is optional for now, will be changed to required later contractAddress = "" } else { contractAddress = staticExtra.ContractAddress @@ -86,7 +86,7 @@ func NewPoolSimulator(entityPool entity.Pool) (*PoolSimulator, error) { Info: pool.PoolInfo{ Address: strings.ToLower(entityPool.Address), ReserveUsd: entityPool.ReserveUsd, - SwapFee: constant.ZeroBI, + SwapFee: big.NewInt(0), Exchange: entityPool.Exchange, Type: entityPool.Type, Tokens: tokens, @@ -161,7 +161,7 @@ func (p *PoolSimulator) calcAmountOut( limit pool.SwapLimit, ) (*pool.CalcAmountOutResult, error) { swapSide := p.getSwapSide(tokenAmountIn.Token, tokenOut) - amountOut, swapInfo, feeAmount, err := p.calcAmountWithSwapInfo(swapSide, tokenAmountIn, limit) + amountOut, swapInfo, feeAmount, err := p.calcAmountOutWithSwapInfo(swapSide, tokenAmountIn, limit) if err != nil { return nil, err } @@ -172,7 +172,7 @@ func (p *PoolSimulator) calcAmountOut( Amount: amountOut, }, Fee: &pool.TokenAmount{ - Token: tokenAmountIn.Token, + Token: tokenOut, Amount: feeAmount, }, Gas: p.estimateGas(len(swapInfo.FilledOrders)), @@ -215,15 +215,14 @@ func getMakerRemainingBalance( } } -func (p *PoolSimulator) calcAmountWithSwapInfo(swapSide SwapSide, tokenAmountIn pool.TokenAmount, limit pool.SwapLimit) (*big.Int, SwapInfo, *big.Int, error) { - +func (p *PoolSimulator) calcAmountOutWithSwapInfo(swapSide SwapSide, tokenAmountIn pool.TokenAmount, limit pool.SwapLimit) (*big.Int, SwapInfo, *big.Int, error) { orderIDs := p.getOrderIDsBySwapSide(swapSide) if len(orderIDs) == 0 { return big.NewInt(0), SwapInfo{}, nil, nil } - totalAmountOutWei := constant.ZeroBI - totalAmountIn := tokenAmountIn.Amount + totalAmountOutWei := big.NewInt(0) + totalAmountIn := new(big.Int).Set(tokenAmountIn.Amount) swapInfo := SwapInfo{ FilledOrders: []*FilledOrderInfo{}, @@ -247,25 +246,13 @@ func (p *PoolSimulator) calcAmountWithSwapInfo(swapSide SwapSide, tokenAmountIn if !ok { return nil, swapInfo, nil, fmt.Errorf("order %d is not existed in pool", orderID) } - // rate should be the result of making amount/taking amount when dividing decimals per token. - // However, we can also use rate with making amount/taking amount (wei) to calculate the amount out instead of converting to measure per token. Because we will return amount out(wei) (we have to multip amountOut(taken out) with decimals) - rate := new(big.Float).Quo(new(big.Float).SetInt(order.MakingAmount), new(big.Float).SetInt(order.TakingAmount)) - var remainingMakingAmountWei, remainingTakingAmountWei *big.Int - if order.AvailableMakingAmount == nil { - remainingMakingAmountWei = new(big.Int).Sub(order.MakingAmount, order.FilledMakingAmount) - remainingTakingAmountWei = new(big.Int).Sub(order.TakingAmount, order.FilledTakingAmount) - } else { - remainingMakingAmountWei = order.AvailableMakingAmount - // the actual available balance might be less than `AvailableMakingAmount` - // for example if we has used another order for this same maker and makerAsset (but with different takerAsset) before - if makerRemainingBalance := getMakerRemainingBalance(limit, filledMakingAmountByMaker, order.Maker, order.MakerAsset); makerRemainingBalance != nil && remainingMakingAmountWei.Cmp(makerRemainingBalance) > 0 { - remainingMakingAmountWei = makerRemainingBalance - } - remainingTakingAmountWei = new(big.Int).Div(new(big.Int).Mul(remainingMakingAmountWei, order.TakingAmount), order.MakingAmount) - } + + // Get remaining making amount, taking amount + remainingMakingAmountWei, remainingTakingAmountWei := order.RemainingAmount(limit, filledMakingAmountByMaker) + totalMakingAmountWei = new(big.Int).Add(totalMakingAmountWei, remainingMakingAmountWei) // Order was filled out. - if remainingMakingAmountWei.Cmp(constant.ZeroBI) <= 0 { + if remainingMakingAmountWei.Sign() <= 0 { continue } @@ -274,19 +261,21 @@ func (p *PoolSimulator) calcAmountWithSwapInfo(swapSide SwapSide, tokenAmountIn // but for now it's not used, and we might get mixed up with makerAsset fee, so will ignore for now if remainingTakingAmountWei.Cmp(totalAmountInAfterFee) >= 0 { - amountOutWei := new(big.Float).Mul(new(big.Float).SetInt(totalAmountInAfterFee), rate) filledTakingAmountWei := totalAmountInAfterFee - filledMakingAmountWei, _ := amountOutWei.Int(nil) + filledMakingAmountWei := new(big.Int).Div( + new(big.Int).Mul(filledTakingAmountWei, order.MakingAmount), + order.TakingAmount, + ) // filledMakingAmountWei = filledTakingAmountWei * order.MakingAmount / order.TakingAmount // order too small - if filledMakingAmountWei.Cmp(constant.ZeroBI) <= 0 { + if filledMakingAmountWei.Sign() <= 0 { continue } - feeAmountWeiByOrder := p.calcMakerAsetFeeAmount(order, filledMakingAmountWei) - totalFeeAmountWei = new(big.Int).Add(totalFeeAmountWei, feeAmountWeiByOrder) + feeAmountWeiByOrder := p.calcMakerAssetFeeAmount(order, filledMakingAmountWei) + totalFeeAmountWei.Add(totalFeeAmountWei, feeAmountWeiByOrder) actualAmountOut := new(big.Int).Sub(filledMakingAmountWei, feeAmountWeiByOrder) - totalAmountOutWei = new(big.Int).Add(totalAmountOutWei, actualAmountOut) + totalAmountOutWei.Add(totalAmountOutWei, actualAmountOut) filledOrderInfo := newFilledOrderInfo(order, filledTakingAmountWei.String(), filledMakingAmountWei.String(), feeAmountWeiByOrder.String()) swapInfo.FilledOrders = append(swapInfo.FilledOrders, filledOrderInfo) isFulfillAmountIn = true @@ -296,40 +285,35 @@ func (p *PoolSimulator) calcAmountWithSwapInfo(swapSide SwapSide, tokenAmountIn // We will often meet edge cases that these orders can be fulfilled by a trading bot or taker on Aggregator. // From that, the estimated amount out and filled orders are not correct. So we need to add more orders when sending to SC to the executor. // In this case, we will some orders util total MakingAmount(remainMakingAmount)/estimated amountOut >= 1.3 (130%) - totalAmountOutWeiBigFloat := new(big.Float).SetInt64(totalAmountOutWei.Int64()) + totalAmountOutWeiBigFloat := new(big.Float).SetInt(totalAmountOutWei) + // threshold = totalAmountOutWei * FallbackPercentageOfTotalMakingAmount + threshold := new(big.Float).Mul(totalAmountOutWeiBigFloat, FallbackPercentageOfTotalMakingAmount) for j := i + 1; j < len(orderIDs); j++ { - if new(big.Float).SetInt(totalMakingAmountWei).Cmp(new(big.Float).Mul(totalAmountOutWeiBigFloat, FallbackPercentageOfTotalMakingAmount)) >= 0 { + if new(big.Float).SetInt(totalMakingAmountWei).Cmp(threshold) >= 0 { break } order, ok := p.ordersMapping[orderIDs[j]] if !ok { continue } - var remainingMakingAmountWei *big.Int - if order.AvailableMakingAmount == nil { - remainingMakingAmountWei = new(big.Int).Sub(order.MakingAmount, order.FilledMakingAmount) - } else { - remainingMakingAmountWei = order.AvailableMakingAmount - if makerRemainingBalance := getMakerRemainingBalance(limit, filledMakingAmountByMaker, order.Maker, order.MakerAsset); makerRemainingBalance != nil && remainingMakingAmountWei.Cmp(makerRemainingBalance) > 0 { - remainingMakingAmountWei = makerRemainingBalance - } - } - if remainingMakingAmountWei.Cmp(constant.ZeroBI) == 0 { + remainingMakingAmountWei, _ := order.RemainingAmount(limit, filledMakingAmountByMaker) + if remainingMakingAmountWei.Sign() == 0 { continue } + totalMakingAmountWei = new(big.Int).Add(totalMakingAmountWei, remainingMakingAmountWei) - filledOrderInfo := newFilledOrderInfo(order, "0", "0", "0") - filledOrderInfo.IsFallBack = true + filledOrderInfo := newFallbackOrderInfo(order) swapInfo.FilledOrders = append(swapInfo.FilledOrders, filledOrderInfo) } break } _, takerAssetFee := p.calcTakerAssetFeeAmountExactOut(order, remainingTakingAmountWei) - totalAmountIn = new(big.Int).Sub(new(big.Int).Sub(totalAmountIn, takerAssetFee), remainingTakingAmountWei) - feeAmountWeiByOrder := p.calcMakerAsetFeeAmount(order, remainingMakingAmountWei) + totalAmountIn.Sub(totalAmountIn, takerAssetFee) + totalAmountIn.Sub(totalAmountIn, remainingTakingAmountWei) + feeAmountWeiByOrder := p.calcMakerAssetFeeAmount(order, remainingMakingAmountWei) actualAmountOut := new(big.Int).Sub(remainingMakingAmountWei, feeAmountWeiByOrder) - totalAmountOutWei = new(big.Int).Add(totalAmountOutWei, actualAmountOut) - totalFeeAmountWei = new(big.Int).Add(totalFeeAmountWei, feeAmountWeiByOrder) + totalAmountOutWei.Add(totalAmountOutWei, actualAmountOut) + totalFeeAmountWei.Add(totalFeeAmountWei, feeAmountWeiByOrder) filledOrderInfo := newFilledOrderInfo(order, remainingTakingAmountWei.String(), remainingMakingAmountWei.String(), feeAmountWeiByOrder.String()) swapInfo.FilledOrders = append(swapInfo.FilledOrders, filledOrderInfo) addFilledMakingAmount(filledMakingAmountByMaker, order.Maker, remainingMakingAmountWei) @@ -341,12 +325,12 @@ func (p *PoolSimulator) calcAmountWithSwapInfo(swapSide SwapSide, tokenAmountIn } // feeAmount = (params.makingAmount * params.order.makerTokenFeePercent + BPS - 1) / BPS -func (p *PoolSimulator) calcMakerAsetFeeAmount(order *order, filledMakingAmount *big.Int) *big.Int { +func (p *PoolSimulator) calcMakerAssetFeeAmount(order *order, filledMakingAmount *big.Int) *big.Int { if order.IsTakerAssetFee { - return constant.ZeroBI + return big.NewInt(0) } if order.MakerTokenFeePercent == 0 { - return constant.ZeroBI + return big.NewInt(0) } amount := new(big.Int).Mul(filledMakingAmount, big.NewInt(int64(order.MakerTokenFeePercent))) return new(big.Int).Div(new(big.Int).Sub(new(big.Int).Add(amount, valueobject.BasisPoint), constant.One), valueobject.BasisPoint) @@ -355,12 +339,12 @@ func (p *PoolSimulator) calcMakerAsetFeeAmount(order *order, filledMakingAmount // given total takingAmount, calculate fee and takingAmountAfterFee func (p *PoolSimulator) calcTakerAssetFeeAmountExactIn(order *order, takingAmount *big.Int) (takingAmountAfterFee *big.Int, fee *big.Int) { if !order.IsTakerAssetFee { - return takingAmount, constant.ZeroBI + return new(big.Int).Set(takingAmount), big.NewInt(0) } feePct := order.MakerTokenFeePercent // reuse same field if feePct == 0 { - return takingAmount, constant.ZeroBI + return new(big.Int).Set(takingAmount), big.NewInt(0) } // fee = ceiling(takingAmountAfterFee * feePct / BasisPoint) @@ -381,12 +365,12 @@ func (p *PoolSimulator) calcTakerAssetFeeAmountExactIn(order *order, takingAmoun // given filled takingAmountAfterFee, calculate fee and total takingAmount func (p *PoolSimulator) calcTakerAssetFeeAmountExactOut(order *order, takingAmountAfterFee *big.Int) (takingAmount *big.Int, fee *big.Int) { if !order.IsTakerAssetFee { - return takingAmountAfterFee, constant.ZeroBI + return new(big.Int).Set(takingAmountAfterFee), big.NewInt(0) } feePct := order.MakerTokenFeePercent // reuse same field if feePct == 0 { - return takingAmountAfterFee, constant.ZeroBI + return new(big.Int).Set(takingAmountAfterFee), big.NewInt(0) } amount := new(big.Int).Mul(takingAmountAfterFee, big.NewInt(int64(feePct))) @@ -409,7 +393,6 @@ func (p *PoolSimulator) estimateGasForExecutor(numberOfFilledOrders int) int64 { func (p *PoolSimulator) estimateGasForRouter(numberOfFilledOrders int) int64 { return int64(numberOfFilledOrders) * int64(GasPerOrderRouter) - } func (p *PoolSimulator) getOrderIDsBySwapSide(swapSide SwapSide) []int64 { @@ -430,6 +413,12 @@ func (p *PoolSimulator) GetMetaInfo(_ string, _ string) interface{} { return p.contractAddress } +func newFallbackOrderInfo(order *order) *FilledOrderInfo { + orderInfo := newFilledOrderInfo(order, "0", "0", "0") + orderInfo.IsFallBack = true + return orderInfo +} + func newFilledOrderInfo(order *order, filledTakingAmount, filledMakingAmount string, feeAmount string) *FilledOrderInfo { feeConfig := "" if order.FeeConfig != nil { diff --git a/pkg/source/limitorder/pool_simulator_calc_amount_in.go b/pkg/source/limitorder/pool_simulator_calc_amount_in.go new file mode 100644 index 000000000..e8b942d61 --- /dev/null +++ b/pkg/source/limitorder/pool_simulator_calc_amount_in.go @@ -0,0 +1,173 @@ +package limitorder + +import ( + "fmt" + "math/big" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" + constant "github.com/KyberNetwork/kyberswap-dex-lib/pkg/util/bignumber" +) + +func (p *PoolSimulator) CalcAmountIn(param pool.CalcAmountInParams) (*pool.CalcAmountInResult, error) { + return p.calcAmountIn(param.TokenAmountOut, param.TokenIn, param.Limit) +} + +func (p *PoolSimulator) calcAmountIn( + tokenAmountOut pool.TokenAmount, + tokenIn string, + limit pool.SwapLimit, +) (*pool.CalcAmountInResult, error) { + swapSide := p.getSwapSide(tokenIn, tokenAmountOut.Token) + amountIn, swapInfo, feeAmount, err := p.calcAmountInWithSwapInfo(swapSide, tokenAmountOut, limit) + if err != nil { + return nil, err + } + return &pool.CalcAmountInResult{ + TokenAmountIn: &pool.TokenAmount{ + Token: tokenIn, + Amount: amountIn, + }, + Fee: &pool.TokenAmount{ + Token: tokenIn, + Amount: feeAmount, + }, + Gas: p.estimateGas(len(swapInfo.FilledOrders)), + SwapInfo: swapInfo, + }, nil +} + +func (p *PoolSimulator) calcAmountInWithSwapInfo(swapSide SwapSide, tokenAmountOut pool.TokenAmount, limit pool.SwapLimit) (*big.Int, SwapInfo, *big.Int, error) { + orderIDs := p.getOrderIDsBySwapSide(swapSide) + if len(orderIDs) == 0 { + return big.NewInt(0), SwapInfo{}, nil, nil + } + + totalAmountInWei := big.NewInt(0) + totalAmountOut := new(big.Int).Set(tokenAmountOut.Amount) + + swapInfo := SwapInfo{ + FilledOrders: make([]*FilledOrderInfo, 0, len(orderIDs)), + SwapSide: swapSide, + } + totalFilledTakingAmountWei := big.NewInt(0) + isFulfillAmountOut := false + totalFeeAmountWei := big.NewInt(0) + + // we need to update maker's remaining balance in 2 places: + // - in UpdateBalance: mainly to deal with case where maker has orders with same makerAsset but different takerAsset + // - when simulating filling each order here: we cannot do the same as in kyber-pmm (simulating first then check inventory limit at the end) + // because in LO we have multiple makers, and also because we still need to allow orders that have part of the balance available + // the problem is that in this func we cannot update the limit, + // so we'll use this map to track filled amount for each maker, then subtract from the original balance, to have the remaining balance available + filledMakingAmountByMaker := make(map[string]*big.Int, len(p.allMakersBalanceAllowance)) + + totalMakingAmountWei := new(big.Int) + for i, orderID := range orderIDs { + order, ok := p.ordersMapping[orderID] + if !ok { + return nil, SwapInfo{}, nil, fmt.Errorf("order %d is not existed in pool", orderID) + } + + // Get remaining making amount, taking amount + remainingMakingAmountWei, remainingTakingAmountWei := order.RemainingAmount(limit, filledMakingAmountByMaker) + + totalMakingAmountWei = new(big.Int).Add(totalMakingAmountWei, remainingMakingAmountWei) + // Order was filled out. + if remainingMakingAmountWei.Sign() <= 0 { + continue + } + + totalAmountOutBeforeFee, _ := p.calcMakerAssetAmountBeforeFee(order, totalAmountOut) + + if remainingMakingAmountWei.Cmp(totalAmountOutBeforeFee) >= 0 { + filledMakingAmountWei := totalAmountOutBeforeFee + filledTakingAmountWei := divCeil( + new(big.Int).Mul(totalAmountOutBeforeFee, order.TakingAmount), + order.MakingAmount, + ) // filledTakingAmountWei = ceil(takingAmount * totalAmountOutBeforeFee / makingAmount) + + // order too small + if filledTakingAmountWei.Sign() == 0 { + continue + } + + actualAmountIn, feeAmountWeiByOrder := p.calcTakerAssetFeeAmountExactOut(order, filledTakingAmountWei) + totalFeeAmountWei.Add(totalFeeAmountWei, feeAmountWeiByOrder) + totalAmountInWei.Add(totalAmountInWei, actualAmountIn) + filledOrderInfo := newFilledOrderInfo(order, filledTakingAmountWei.String(), filledMakingAmountWei.String(), feeAmountWeiByOrder.String()) + swapInfo.FilledOrders = append(swapInfo.FilledOrders, filledOrderInfo) + isFulfillAmountOut = true + addFilledMakingAmount(filledMakingAmountByMaker, order.Maker, filledMakingAmountWei) + totalFilledTakingAmountWei.Add(totalFilledTakingAmountWei, filledTakingAmountWei) + + // threshold = totalAmountOutBeforeFee * FallbackPercentageOfTotalMakingAmount + threshold := new(big.Float).SetInt(totalAmountOutBeforeFee) + threshold.Mul(threshold, FallbackPercentageOfTotalMakingAmount) + + for j := i + 1; j < len(orderIDs); j++ { + if new(big.Float).SetInt(totalMakingAmountWei).Cmp(threshold) >= 0 { + break + } + order, ok := p.ordersMapping[orderIDs[j]] + if !ok { + continue + } + + remainingMakingAmountWei, _ := order.RemainingAmount(limit, filledMakingAmountByMaker) + if remainingMakingAmountWei.Sign() == 0 { + continue + } + + totalMakingAmountWei = new(big.Int).Add(totalMakingAmountWei, remainingMakingAmountWei) + filledOrderInfo := newFallbackOrderInfo(order) + swapInfo.FilledOrders = append(swapInfo.FilledOrders, filledOrderInfo) + } + break + } + totalAmountOut.Sub(totalAmountOut, remainingMakingAmountWei) + _, takerAssetFee := p.calcTakerAssetFeeAmountExactOut(order, remainingTakingAmountWei) + actualAmountIn := new(big.Int).Add(remainingTakingAmountWei, takerAssetFee) + totalAmountInWei.Add(totalAmountInWei, actualAmountIn) + totalFeeAmountWei.Add(totalFeeAmountWei, takerAssetFee) + filledOrderInfo := newFilledOrderInfo(order, remainingTakingAmountWei.String(), remainingMakingAmountWei.String(), takerAssetFee.String()) + swapInfo.FilledOrders = append(swapInfo.FilledOrders, filledOrderInfo) + addFilledMakingAmount(filledMakingAmountByMaker, order.Maker, remainingMakingAmountWei) + totalFilledTakingAmountWei.Add(totalFilledTakingAmountWei, remainingTakingAmountWei) + } + if !isFulfillAmountOut { + return nil, SwapInfo{}, nil, ErrCannotFulfillAmountOut + } + swapInfo.AmountIn = totalFilledTakingAmountWei.String() + return totalAmountInWei, swapInfo, totalFeeAmountWei, nil +} + +// calcMakerAssetAmountBeforeFee calculates the maker asset amount before fee. +// input is the received amount after fee. +func (p *PoolSimulator) calcMakerAssetAmountBeforeFee(order *order, makingAmount *big.Int) (makingAmountBeforeFee *big.Int, fee *big.Int) { + if order.IsTakerAssetFee { + return new(big.Int).Set(makingAmount), big.NewInt(0) + } + + feePct := order.MakerTokenFeePercent + if feePct == 0 { + return new(big.Int).Set(makingAmount), big.NewInt(0) + } + + // makingAmountBeforeFee = makingAmount * BasisPoint / (BasisPoint - feePct) + makingAmountBeforeFee = divCeil( + new(big.Int).Mul(makingAmount, constant.BasisPoint), + new(big.Int).Sub(constant.BasisPoint, big.NewInt(int64(feePct))), + ) + + // fee = makingAmount - makingAmountBeforeFee + fee = new(big.Int).Sub(makingAmount, makingAmountBeforeFee) + + return makingAmountBeforeFee, fee +} + +func divCeil(a, b *big.Int) *big.Int { + // (a + b - 1) / b + a = new(big.Int).Add(a, b) + a.Sub(a, big.NewInt(1)) + return a.Div(a, b) +} diff --git a/pkg/source/limitorder/pool_simulator_calc_amount_in_test.go b/pkg/source/limitorder/pool_simulator_calc_amount_in_test.go new file mode 100644 index 000000000..b810506d2 --- /dev/null +++ b/pkg/source/limitorder/pool_simulator_calc_amount_in_test.go @@ -0,0 +1,510 @@ +package limitorder + +import ( + "math/big" + "strconv" + "testing" + + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/swaplimit" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/util/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + tokenUSDC = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174" + tokenUSDT = "0xc2132d05d31c914a87c6611c10748aeb04b58e8f" +) + +func TestPool_CalcAmountIn(t *testing.T) { + type args struct { + tokenIn string + tokenAmountOut pool.TokenAmount + } + tests := []struct { + name string + poolEntity entity.Pool + args args + want *pool.CalcAmountInResult + err error + }{ + { + name: "Should return correct CalcAmountInResult when swapSide is BUY(strings.ToLower(tokeIn) <= strings.ToLower(TokenOut))", + poolEntity: newExamplePool(t, nil, []*order{ + newExampleOrder(t, 1, tokenUSDT, big.NewInt(200), big.NewInt(0), tokenUSDC, big.NewInt(400), big.NewInt(0), 0, false), + newExampleOrder(t, 2, tokenUSDT, big.NewInt(300), big.NewInt(0), tokenUSDC, big.NewInt(300), big.NewInt(0), 0, true), + }), + args: args{ + tokenAmountOut: pool.TokenAmount{ + Token: tokenUSDT, + Amount: parseBigInt("500"), + AmountUsd: 0, + }, + tokenIn: tokenUSDC, + }, + want: &pool.CalcAmountInResult{ + TokenAmountIn: &pool.TokenAmount{ + Token: tokenUSDC, + Amount: parseBigInt("300"), + AmountUsd: 0, + }, + Fee: &pool.TokenAmount{ + Token: tokenUSDC, + Amount: big.NewInt(0), + AmountUsd: 0, + }, + Gas: 136616, + SwapInfo: SwapInfo{ + AmountIn: "300", + SwapSide: Buy, + FilledOrders: []*FilledOrderInfo{ + newExampleFilledOrderInfo( + t, + 1, + tokenUSDT, big.NewInt(200), big.NewInt(200), + tokenUSDC, big.NewInt(400), big.NewInt(400), + big.NewInt(0), 0, + ), + newExampleFilledOrderInfo( + t, + 2, + tokenUSDT, big.NewInt(300), big.NewInt(100), + tokenUSDC, big.NewInt(300), big.NewInt(100), + big.NewInt(0), 0, + ), + }, + }, + }, + err: nil, + }, + { + name: "Should return correct CalcAmountInResult when swapSide is SELL(strings.ToLower(tokeIn) > strings.ToLower(TokenOut))", + poolEntity: newExamplePool(t, []*order{ + newExampleOrder(t, 1, tokenUSDC, big.NewInt(992_000), big.NewInt(0), tokenUSDT, big.NewInt(1_000_000), big.NewInt(0), 0, false), + newExampleOrder(t, 2, tokenUSDC, big.NewInt(1_010_000), big.NewInt(0), tokenUSDT, big.NewInt(1_000_000), big.NewInt(0), 0, false), + }, nil), + args: args{ + tokenAmountOut: pool.TokenAmount{ + Token: tokenUSDC, + Amount: parseBigInt("1215841"), + AmountUsd: 0, + }, + tokenIn: tokenUSDT, + }, + want: &pool.CalcAmountInResult{ + TokenAmountIn: &pool.TokenAmount{ + Token: tokenUSDT, + Amount: parseBigInt("1210000"), + AmountUsd: 0, + }, + Fee: &pool.TokenAmount{ + Token: tokenUSDT, + Amount: big.NewInt(0), + AmountUsd: 0, + }, + Gas: 136616, + SwapInfo: SwapInfo{ + AmountIn: "1210000", + SwapSide: Sell, + FilledOrders: []*FilledOrderInfo{ + newExampleFilledOrderInfo( + t, + 1, + tokenUSDC, big.NewInt(992_000), big.NewInt(992_000), + tokenUSDT, big.NewInt(1_000_000), big.NewInt(1_000_000), + big.NewInt(0), 0, + ), + newExampleFilledOrderInfo( + t, + 2, + tokenUSDC, big.NewInt(1_010_000), big.NewInt(218_000), + tokenUSDT, big.NewInt(1_000_000), big.NewInt(215_841), + big.NewInt(0), 0, + ), + }, + }, + }, + err: nil, + }, + { + name: "Should return correct CalcAmountInResult when swapSide is BUY(strings.ToLower(tokeIn) <= strings.ToLower(TokenOut)) and orders has MakerTokenFeePercent", + poolEntity: newExamplePool(t, nil, []*order{ + newExampleOrder(t, 1383, tokenUSDT, big.NewInt(200), big.NewInt(0), tokenUSDC, big.NewInt(400), big.NewInt(0), 100, true), + newExampleOrder(t, 1382, tokenUSDT, big.NewInt(300), big.NewInt(0), tokenUSDC, big.NewInt(300), big.NewInt(0), 0, true), + }), + args: args{ + tokenAmountOut: pool.TokenAmount{ + Token: tokenUSDT, + Amount: parseBigInt("500"), + AmountUsd: 0, + }, + tokenIn: tokenUSDC, + }, + want: &pool.CalcAmountInResult{ + TokenAmountIn: &pool.TokenAmount{ + Token: tokenUSDC, + Amount: parseBigInt("302"), + AmountUsd: 0, + }, + Fee: &pool.TokenAmount{ + Token: tokenUSDC, + Amount: big.NewInt(2), + AmountUsd: 0, + }, + Gas: 136616, + SwapInfo: SwapInfo{ + AmountIn: "300", + SwapSide: Buy, + FilledOrders: []*FilledOrderInfo{ + newExampleFilledOrderInfo(t, + 1383, + tokenUSDT, big.NewInt(200), big.NewInt(200), + tokenUSDC, big.NewInt(400), big.NewInt(400), + big.NewInt(2), 100, + ), + newExampleFilledOrderInfo(t, + 1382, + tokenUSDT, big.NewInt(300), big.NewInt(100), + tokenUSDC, big.NewInt(300), big.NewInt(100), + big.NewInt(0), 0, + ), + }, + }, + }, + err: nil, + }, + { + name: "Should return correct CalcAmountInResult and list orders(include fallback orders) when swapSide is SELL(strings.ToLower(tokeIn) > strings.ToLower(TokenOut))", + poolEntity: newExamplePool(t, []*order{ + newExampleOrder(t, 1383, tokenUSDC, big.NewInt(700), big.NewInt(0), tokenUSDT, big.NewInt(1400), big.NewInt(0), 0, false), + newExampleOrder(t, 1382, tokenUSDC, big.NewInt(240), big.NewInt(0), tokenUSDT, big.NewInt(250), big.NewInt(0), 0, false), + newExampleOrder(t, 1385, tokenUSDC, big.NewInt(200), big.NewInt(0), tokenUSDT, big.NewInt(300), big.NewInt(0), 0, false), + newExampleOrder(t, 1389, tokenUSDC, big.NewInt(100), big.NewInt(0), tokenUSDT, big.NewInt(100), big.NewInt(0), 0, false), + }, nil), + args: args{ + tokenAmountOut: pool.TokenAmount{ + Token: tokenUSDC, + Amount: parseBigInt("1400"), + AmountUsd: 0, + }, + tokenIn: tokenUSDT, + }, + want: &pool.CalcAmountInResult{ + TokenAmountIn: &pool.TokenAmount{ + Token: tokenUSDT, + Amount: parseBigInt("700"), + AmountUsd: 0, + }, + Fee: &pool.TokenAmount{ + Token: tokenUSDT, + Amount: big.NewInt(0), + AmountUsd: 0, + }, + Gas: 159924, + SwapInfo: SwapInfo{ + AmountIn: "700", + SwapSide: Sell, + FilledOrders: []*FilledOrderInfo{ + newExampleFilledOrderInfo(t, + 1383, + tokenUSDC, big.NewInt(700), big.NewInt(700), + tokenUSDT, big.NewInt(1400), big.NewInt(1400), + big.NewInt(0), 0, + ), + newExampleFallBackOrderInfo(t, + 1382, + tokenUSDC, big.NewInt(240), + tokenUSDT, big.NewInt(250), + 0, + ), + newExampleFallBackOrderInfo(t, + 1385, + tokenUSDC, big.NewInt(200), + tokenUSDT, big.NewInt(300), + 0, + ), + }, + }, + }, + err: nil, + }, + { + name: "Should return correct error(ErrCannotFulfillAmountIn) when cannot fulfill amountIn buy the orders", + poolEntity: newExamplePool(t, []*order{ + newExampleOrder(t, 1383, tokenUSDC, big.NewInt(992_000), big.NewInt(0), tokenUSDT, big.NewInt(1_000_000), big.NewInt(0), 0, false), + newExampleOrder(t, 1382, tokenUSDC, big.NewInt(1_010_000), big.NewInt(0), tokenUSDT, big.NewInt(1_000_000), big.NewInt(0), 0, false), + }, nil), + args: args{ + tokenAmountOut: pool.TokenAmount{ + Token: tokenUSDC, + Amount: parseBigInt("121000000"), + AmountUsd: 0, + }, + tokenIn: tokenUSDT, + }, + want: nil, + err: ErrCannotFulfillAmountOut, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p, err := NewPoolSimulator(tt.poolEntity) + assert.Equal(t, nil, err) + got, err := testutil.MustConcurrentSafe[*pool.CalcAmountInResult](t, func() (any, error) { + limit := swaplimit.NewInventory("", p.CalculateLimit()) + return p.CalcAmountIn( + pool.CalcAmountInParams{ + TokenAmountOut: tt.args.tokenAmountOut, + TokenIn: tt.args.tokenIn, + Limit: limit, + }) + }) + assert.Equal(t, tt.err, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestPool_CalcAmountOut_CalcAmountIn(t *testing.T) { + type args struct { + tokenIn string + amountIn *big.Int + tokenOut string + amountOut *big.Int + } + + tests := []struct { + name string + poolEntity entity.Pool + args args + err error + }{ + { + name: "Should return correct CalcAmountInResult and CalcAmountOutResult when swapSide is BUY(strings.ToLower(tokeIn) <= strings.ToLower(TokenOut))", + poolEntity: newExamplePool(t, nil, []*order{ + newExampleOrder(t, 1, tokenUSDT, big.NewInt(200), big.NewInt(0), tokenUSDC, big.NewInt(400), big.NewInt(0), 0, false), + newExampleOrder(t, 2, tokenUSDT, big.NewInt(300), big.NewInt(0), tokenUSDC, big.NewInt(300), big.NewInt(0), 0, true), + }), + args: args{ + tokenIn: tokenUSDC, + amountIn: parseBigInt("300"), + tokenOut: tokenUSDT, + amountOut: parseBigInt("500"), + }, + err: nil, + }, + { + name: "Should return correct CalcAmountInResult and CalcAmountOutResult when swapSide is SELL(strings.ToLower(tokeIn) > strings.ToLower(TokenOut))", + poolEntity: newExamplePool(t, []*order{ + newExampleOrder(t, 1, tokenUSDC, big.NewInt(992_000), big.NewInt(0), tokenUSDT, big.NewInt(1_000_000), big.NewInt(0), 0, false), + newExampleOrder(t, 2, tokenUSDC, big.NewInt(1_010_000), big.NewInt(0), tokenUSDT, big.NewInt(1_000_000), big.NewInt(0), 0, false), + }, nil), + args: args{ + + tokenIn: tokenUSDT, + amountIn: parseBigInt("1210000"), + tokenOut: tokenUSDC, + amountOut: parseBigInt("1215841"), + }, + err: nil, + }, + { + name: "Should return correct CalcAmountInResult and CalcAmountOutResult when swapSide is BUY(strings.ToLower(tokeIn) <= strings.ToLower(TokenOut)) and orders has MakerTokenFeePercent", + poolEntity: newExamplePool(t, nil, []*order{ + newExampleOrder(t, 1383, tokenUSDT, big.NewInt(200), big.NewInt(0), tokenUSDC, big.NewInt(400), big.NewInt(0), 100, true), + newExampleOrder(t, 1382, tokenUSDT, big.NewInt(300), big.NewInt(0), tokenUSDC, big.NewInt(300), big.NewInt(0), 0, true), + }), + args: args{ + tokenIn: tokenUSDC, + amountIn: parseBigInt("302"), + tokenOut: tokenUSDT, + amountOut: parseBigInt("500"), + }, + err: nil, + }, + { + name: "Should return correct CalcAmountInResult and CalcAmountOutResult and list orders(include fallback orders) when swapSide is SELL(strings.ToLower(tokeIn) > strings.ToLower(TokenOut))", + poolEntity: newExamplePool(t, []*order{ + newExampleOrder(t, 1383, tokenUSDC, big.NewInt(700), big.NewInt(0), tokenUSDT, big.NewInt(1400), big.NewInt(0), 0, false), + newExampleOrder(t, 1382, tokenUSDC, big.NewInt(240), big.NewInt(0), tokenUSDT, big.NewInt(250), big.NewInt(0), 0, false), + newExampleOrder(t, 1385, tokenUSDC, big.NewInt(200), big.NewInt(0), tokenUSDT, big.NewInt(300), big.NewInt(0), 0, false), + newExampleOrder(t, 1389, tokenUSDC, big.NewInt(100), big.NewInt(0), tokenUSDT, big.NewInt(100), big.NewInt(0), 0, false), + }, nil), + args: args{ + tokenIn: tokenUSDT, + amountIn: parseBigInt("700"), + tokenOut: tokenUSDC, + amountOut: parseBigInt("1400"), + }, + err: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p, err := NewPoolSimulator(tt.poolEntity) + assert.Equal(t, nil, err) + calcAmountOutResult, err := testutil.MustConcurrentSafe[*pool.CalcAmountOutResult](t, func() (any, error) { + limit := swaplimit.NewInventory("", p.CalculateLimit()) + return p.CalcAmountOut( + pool.CalcAmountOutParams{ + TokenAmountIn: pool.TokenAmount{ + Token: tt.args.tokenIn, + Amount: tt.args.amountIn, + }, + TokenOut: tt.args.tokenOut, + Limit: limit, + }) + }) + require.NoError(t, err) + + assert.Equal(t, tt.args.amountOut, calcAmountOutResult.TokenAmountOut.Amount) + + calcAmountInResult, err := testutil.MustConcurrentSafe[*pool.CalcAmountInResult](t, func() (any, error) { + limit := swaplimit.NewInventory("", p.CalculateLimit()) + return p.CalcAmountIn( + pool.CalcAmountInParams{ + TokenAmountOut: pool.TokenAmount{ + Token: tt.args.tokenOut, + Amount: tt.args.amountOut, + }, + TokenIn: tt.args.tokenIn, + Limit: limit, + }) + }) + require.NoError(t, err) + + assert.Equal(t, tt.args.amountIn, calcAmountInResult.TokenAmountIn.Amount) + }) + } +} + +func newExamplePool(t *testing.T, sellOrders, buyOrders []*order) entity.Pool { + t.Helper() + + return entity.Pool{ + Address: "pool_limit_order_", + ReserveUsd: 1000000000, + AmplifiedTvl: 0, + SwapFee: 0, + Exchange: "kyberswap_limit-order", + Type: "limit-order", + Timestamp: 0, + Reserves: []string{"10000000000000000000", "10000000000000000000"}, + Tokens: []*entity.PoolToken{ + { + Address: tokenUSDT, + Name: "USDT", + Symbol: "USDT", + Decimals: 6, + Swappable: true, + }, + { + Address: tokenUSDC, + Name: "USDC", + Symbol: "USDC", + Decimals: 6, + Swappable: true, + }, + }, + Extra: marshalPoolExtra(&Extra{ + BuyOrders: buyOrders, + SellOrders: sellOrders, + }), + TotalSupply: "", + } +} + +func newExampleOrder( + t *testing.T, + id int64, + takerAsset string, takingAmount, filledTakingAmount *big.Int, + makerAsset string, makingAmount, filledMakingAmount *big.Int, + makerTokenFeePercent uint32, IsTakerAssetFee bool, +) *order { + t.Helper() + + return &order{ + ID: id, + ChainID: "5", + Salt: "185786982651412687203851465093295409688", + Signature: "signature" + strconv.Itoa(int(id)), + TakerAsset: takerAsset, + MakerAsset: makerAsset, + Receiver: "0xa246ec8bf7f2e54cc2f7bfdd869302ae4a08a590", + Maker: "0xa246ec8bf7f2e54cc2f7bfdd869302ae4a08a590", + AllowedSenders: "0x0000000000000000000000000000000000000000", + TakingAmount: takingAmount, + MakingAmount: makingAmount, + FeeConfig: parseBigInt("100"), + FeeRecipient: "0x0000000000000000000000000000000000000000", + FilledMakingAmount: filledMakingAmount, + FilledTakingAmount: filledTakingAmount, + MakerTokenFeePercent: makerTokenFeePercent, + MakerAssetData: "", + TakerAssetData: "", + GetMakerAmount: "f4a215c3000000000000000000000000000000000000000000000001d7d843dc3b4800000000000000000000000000000000000000000000000000000de0b6b3a7640000", + GetTakerAmount: "296637bf000000000000000000000000000000000000000000000001d7d843dc3b4800000000000000000000000000000000000000000000000000000de0b6b3a7640000", + Predicate: "961d5b1e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000002892e28b58ab329741f27fd1ea56dca0192a38840000000000000000000000002892e28b58ab329741f27fd1ea56dca0192a38840000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044cf6fc6e3000000000000000000000000a246ec8bf7f2e54cc2f7bfdd869302ae4a08a590000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002463592c2b0000000000000000000000000000000000000000000000000000000063c1169800000000000000000000000000000000000000000000000000000000", + Permit: "", + Interaction: "", + ExpiredAt: 0, + IsTakerAssetFee: IsTakerAssetFee, + } +} + +func newExampleFallBackOrderInfo( + t *testing.T, + orderID int64, + takerAsset string, takingAmount *big.Int, + makerAsset string, makingAmount *big.Int, + makerTokenFeePercent uint32, +) *FilledOrderInfo { + t.Helper() + o := newExampleFilledOrderInfo( + t, orderID, + takerAsset, takingAmount, big.NewInt(0), + makerAsset, makingAmount, big.NewInt(0), + big.NewInt(0), makerTokenFeePercent, + ) + o.IsFallBack = true + return o +} + +func newExampleFilledOrderInfo( + t *testing.T, + orderID int64, + takerAsset string, takingAmount, filledTakingAmount *big.Int, + makerAsset string, makingAmount, filledMakingAmount *big.Int, + feeAmount *big.Int, makerTokenFeePercent uint32, +) *FilledOrderInfo { + t.Helper() + + return &FilledOrderInfo{ + OrderID: orderID, + FilledTakingAmount: filledTakingAmount.String(), + FilledMakingAmount: filledMakingAmount.String(), + TakingAmount: takingAmount.String(), + MakingAmount: makingAmount.String(), + Salt: "185786982651412687203851465093295409688", + TakerAsset: takerAsset, + MakerAsset: makerAsset, + Maker: "0xa246ec8bf7f2e54cc2f7bfdd869302ae4a08a590", + Receiver: "0xa246ec8bf7f2e54cc2f7bfdd869302ae4a08a590", + AllowedSenders: "0x0000000000000000000000000000000000000000", + GetMakerAmount: "f4a215c3000000000000000000000000000000000000000000000001d7d843dc3b4800000000000000000000000000000000000000000000000000000de0b6b3a7640000", + GetTakerAmount: "296637bf000000000000000000000000000000000000000000000001d7d843dc3b4800000000000000000000000000000000000000000000000000000de0b6b3a7640000", + FeeConfig: "100", + FeeRecipient: "0x0000000000000000000000000000000000000000", + MakerTokenFeePercent: makerTokenFeePercent, + MakerAssetData: "", + TakerAssetData: "", + Predicate: "961d5b1e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000002892e28b58ab329741f27fd1ea56dca0192a38840000000000000000000000002892e28b58ab329741f27fd1ea56dca0192a38840000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044cf6fc6e3000000000000000000000000a246ec8bf7f2e54cc2f7bfdd869302ae4a08a590000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002463592c2b0000000000000000000000000000000000000000000000000000000063c1169800000000000000000000000000000000000000000000000000000000", + Permit: "", + Interaction: "", + Signature: "signature" + strconv.Itoa(int(orderID)), + FeeAmount: feeAmount.String(), + } +} diff --git a/pkg/source/limitorder/pool_simulator_test.go b/pkg/source/limitorder/pool_simulator_test.go index fdd0c5dcc..4b8853649 100644 --- a/pkg/source/limitorder/pool_simulator_test.go +++ b/pkg/source/limitorder/pool_simulator_test.go @@ -18,7 +18,6 @@ import ( ) func TestPool_CalcAmountOut(t *testing.T) { - type args struct { tokenAmountIn pool.TokenAmount tokenOut string @@ -131,7 +130,7 @@ func TestPool_CalcAmountOut(t *testing.T) { AmountUsd: 0, }, Fee: &pool.TokenAmount{ - Token: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + Token: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", Amount: big.NewInt(0), AmountUsd: 0, }, @@ -293,7 +292,7 @@ func TestPool_CalcAmountOut(t *testing.T) { AmountUsd: 0, }, Fee: &pool.TokenAmount{ - Token: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", + Token: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", Amount: big.NewInt(0), AmountUsd: 0, }, @@ -453,7 +452,7 @@ func TestPool_CalcAmountOut(t *testing.T) { AmountUsd: 0, }, Fee: &pool.TokenAmount{ - Token: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + Token: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", Amount: big.NewInt(4), AmountUsd: 0, }, @@ -663,7 +662,7 @@ func TestPool_CalcAmountOut(t *testing.T) { AmountUsd: 0, }, Fee: &pool.TokenAmount{ - Token: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", + Token: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", Amount: big.NewInt(0), AmountUsd: 0, },