From bb663b1cad1b3d7b6534f22172d8f7f0a854c84d Mon Sep 17 00:00:00 2001 From: bergben Date: Mon, 18 Nov 2024 03:52:00 +0100 Subject: [PATCH] Fluid Dex: add limits handling (#594) * feat(fluid-dex): add logic for limits handling * feat(fluid-dex): clean up console logs * refactor(fluid-dex): cleanup * feat(fluid-dex): improve bignumber constants, clone limits * feat(fluid-dex): optimize constants * refactor(fluid-dex): optimize new big ints * feat(fluid-dex): update limits in UpdateBalance --------- Signed-off-by: bergben Signed-off-by: SunSpirit <48086732+sunspirit99@users.noreply.github.com> Co-authored-by: SunSpirit <48086732+sunspirit99@users.noreply.github.com> Co-authored-by: Phu Ngo <12547020+NgoKimPhu@users.noreply.github.com> --- .../dex-t1/abis/dexReservesResolver.json | 1149 +++++++++++------ pkg/liquidity-source/fluid/dex-t1/constant.go | 6 + .../fluid/dex-t1/pool_list_updater.go | 14 +- .../fluid/dex-t1/pool_list_updater_test.go | 2 +- .../fluid/dex-t1/pool_simulator.go | 490 +++++-- .../fluid/dex-t1/pool_simulator_test.go | 176 ++- .../fluid/dex-t1/pool_tracker.go | 41 +- .../fluid/dex-t1/pool_tracker_test.go | 9 +- pkg/liquidity-source/fluid/dex-t1/types.go | 18 +- 9 files changed, 1378 insertions(+), 527 deletions(-) diff --git a/pkg/liquidity-source/fluid/dex-t1/abis/dexReservesResolver.json b/pkg/liquidity-source/fluid/dex-t1/abis/dexReservesResolver.json index 34ef26c70..9e5c92896 100644 --- a/pkg/liquidity-source/fluid/dex-t1/abis/dexReservesResolver.json +++ b/pkg/liquidity-source/fluid/dex-t1/abis/dexReservesResolver.json @@ -1,14 +1,11 @@ [ { "inputs": [ + { "internalType": "address", "name": "factory_", "type": "address" }, + { "internalType": "address", "name": "liquidity_", "type": "address" }, { "internalType": "address", - "name": "factory_", - "type": "address" - }, - { - "internalType": "address", - "name": "liquidity_", + "name": "liquidityResolver_", "type": "address" } ], @@ -42,69 +39,42 @@ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "LIQUIDITY_RESOLVER", + "outputs": [ { - "internalType": "address", - "name": "dex_", + "internalType": "contract IFluidLiquidityResolver", + "name": "", "type": "address" - }, - { - "internalType": "bool", - "name": "swap0to1_", - "type": "bool" - }, - { - "internalType": "uint256", - "name": "amountIn_", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amountOutMin_", - "type": "uint256" } ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "dex_", "type": "address" }, + { "internalType": "bool", "name": "swap0to1_", "type": "bool" }, + { "internalType": "uint256", "name": "amountIn_", "type": "uint256" }, + { "internalType": "uint256", "name": "amountOutMin_", "type": "uint256" } + ], "name": "estimateSwapIn", "outputs": [ - { - "internalType": "uint256", - "name": "amountOut_", - "type": "uint256" - } + { "internalType": "uint256", "name": "amountOut_", "type": "uint256" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ - { - "internalType": "address", - "name": "dex_", - "type": "address" - }, - { - "internalType": "bool", - "name": "swap0to1_", - "type": "bool" - }, - { - "internalType": "uint256", - "name": "amountOut_", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amountInMax_", - "type": "uint256" - } + { "internalType": "address", "name": "dex_", "type": "address" }, + { "internalType": "bool", "name": "swap0to1_", "type": "bool" }, + { "internalType": "uint256", "name": "amountOut_", "type": "uint256" }, + { "internalType": "uint256", "name": "amountInMax_", "type": "uint256" } ], "name": "estimateSwapOut", "outputs": [ - { - "internalType": "uint256", - "name": "amountIn_", - "type": "uint256" - } + { "internalType": "uint256", "name": "amountIn_", "type": "uint256" } ], "stateMutability": "payable", "type": "function" @@ -113,11 +83,7 @@ "inputs": [], "name": "getAllPoolAddresses", "outputs": [ - { - "internalType": "address[]", - "name": "pools_", - "type": "address[]" - } + { "internalType": "address[]", "name": "pools_", "type": "address[]" } ], "stateMutability": "view", "type": "function" @@ -128,26 +94,10 @@ "outputs": [ { "components": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "token0", - "type": "address" - }, - { - "internalType": "address", - "name": "token1", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - } + { "internalType": "address", "name": "pool", "type": "address" }, + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint256", "name": "fee", "type": "uint256" } ], "internalType": "struct Structs.Pool[]", "name": "pools_", @@ -163,26 +113,10 @@ "outputs": [ { "components": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "token0", - "type": "address" - }, - { - "internalType": "address", - "name": "token1", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, + { "internalType": "address", "name": "pool", "type": "address" }, + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint256", "name": "fee", "type": "uint256" }, { "components": [ { @@ -248,14 +182,99 @@ "type": "tuple" }, { - "internalType": "uint256", - "name": "balanceToken0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceToken1", - "type": "uint256" + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" } ], "internalType": "struct Structs.PoolWithReserves[]", @@ -272,26 +291,10 @@ "outputs": [ { "components": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "token0", - "type": "address" - }, - { - "internalType": "address", - "name": "token1", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, + { "internalType": "address", "name": "pool", "type": "address" }, + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint256", "name": "fee", "type": "uint256" }, { "components": [ { @@ -357,14 +360,99 @@ "type": "tuple" }, { - "internalType": "uint256", - "name": "balanceToken0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceToken1", - "type": "uint256" + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" } ], "internalType": "struct Structs.PoolWithReserves[]", @@ -377,11 +465,7 @@ }, { "inputs": [ - { - "internalType": "address", - "name": "dex_", - "type": "address" - } + { "internalType": "address", "name": "dex_", "type": "address" } ], "name": "getDexCollateralReserves", "outputs": [ @@ -418,11 +502,7 @@ }, { "inputs": [ - { - "internalType": "address", - "name": "dex_", - "type": "address" - } + { "internalType": "address", "name": "dex_", "type": "address" } ], "name": "getDexCollateralReservesAdjusted", "outputs": [ @@ -459,11 +539,7 @@ }, { "inputs": [ - { - "internalType": "address", - "name": "dex_", - "type": "address" - } + { "internalType": "address", "name": "dex_", "type": "address" } ], "name": "getDexDebtReserves", "outputs": [ @@ -510,11 +586,7 @@ }, { "inputs": [ - { - "internalType": "address", - "name": "dex_", - "type": "address" - } + { "internalType": "address", "name": "dex_", "type": "address" } ], "name": "getDexDebtReservesAdjusted", "outputs": [ @@ -561,12 +633,113 @@ }, { "inputs": [ + { "internalType": "address", "name": "dex_", "type": "address" } + ], + "name": "getDexLimits", + "outputs": [ { - "internalType": "address", - "name": "dex_", - "type": "address" + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits_", + "type": "tuple" } ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "dex_", "type": "address" } + ], "name": "getDexPricesAndExchangePrices", "outputs": [ { @@ -627,36 +800,16 @@ }, { "inputs": [ - { - "internalType": "uint256", - "name": "poolId_", - "type": "uint256" - } + { "internalType": "uint256", "name": "poolId_", "type": "uint256" } ], "name": "getPool", "outputs": [ { "components": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "token0", - "type": "address" - }, - { - "internalType": "address", - "name": "token1", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - } + { "internalType": "address", "name": "pool", "type": "address" }, + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint256", "name": "fee", "type": "uint256" } ], "internalType": "struct Structs.Pool", "name": "pool_", @@ -668,62 +821,30 @@ }, { "inputs": [ - { - "internalType": "uint256", - "name": "poolId_", - "type": "uint256" - } + { "internalType": "uint256", "name": "poolId_", "type": "uint256" } ], "name": "getPoolAddress", "outputs": [ - { - "internalType": "address", - "name": "pool_", - "type": "address" - } + { "internalType": "address", "name": "pool_", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { - "internalType": "address", - "name": "pool_", - "type": "address" - } + { "internalType": "address", "name": "pool_", "type": "address" } ], "name": "getPoolConstantsView", "outputs": [ { "components": [ - { - "internalType": "uint256", - "name": "dexId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "liquidity", - "type": "address" - }, - { - "internalType": "address", - "name": "factory", - "type": "address" - }, + { "internalType": "uint256", "name": "dexId", "type": "uint256" }, + { "internalType": "address", "name": "liquidity", "type": "address" }, + { "internalType": "address", "name": "factory", "type": "address" }, { "components": [ - { - "internalType": "address", - "name": "shift", - "type": "address" - }, - { - "internalType": "address", - "name": "admin", - "type": "address" - }, + { "internalType": "address", "name": "shift", "type": "address" }, + { "internalType": "address", "name": "admin", "type": "address" }, { "internalType": "address", "name": "colOperations", @@ -749,16 +870,8 @@ "name": "deployerContract", "type": "address" }, - { - "internalType": "address", - "name": "token0", - "type": "address" - }, - { - "internalType": "address", - "name": "token1", - "type": "address" - }, + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, { "internalType": "bytes32", "name": "supplyToken0Slot", @@ -805,11 +918,7 @@ }, { "inputs": [ - { - "internalType": "address", - "name": "pool_", - "type": "address" - } + { "internalType": "address", "name": "pool_", "type": "address" } ], "name": "getPoolConstantsView2", "outputs": [ @@ -846,55 +955,27 @@ }, { "inputs": [ - { - "internalType": "address", - "name": "pool_", - "type": "address" - } + { "internalType": "address", "name": "pool_", "type": "address" } ], "name": "getPoolFee", "outputs": [ - { - "internalType": "uint256", - "name": "fee_", - "type": "uint256" - } + { "internalType": "uint256", "name": "fee_", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { - "internalType": "address", - "name": "pool_", - "type": "address" - } + { "internalType": "address", "name": "pool_", "type": "address" } ], "name": "getPoolReserves", "outputs": [ { "components": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "token0", - "type": "address" - }, - { - "internalType": "address", - "name": "token1", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, + { "internalType": "address", "name": "pool", "type": "address" }, + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint256", "name": "fee", "type": "uint256" }, { "components": [ { @@ -960,14 +1041,99 @@ "type": "tuple" }, { - "internalType": "uint256", - "name": "balanceToken0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceToken1", - "type": "uint256" + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" } ], "internalType": "struct Structs.PoolWithReserves", @@ -980,36 +1146,16 @@ }, { "inputs": [ - { - "internalType": "address", - "name": "pool_", - "type": "address" - } + { "internalType": "address", "name": "pool_", "type": "address" } ], "name": "getPoolReservesAdjusted", "outputs": [ { "components": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "token0", - "type": "address" - }, - { - "internalType": "address", - "name": "token1", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, + { "internalType": "address", "name": "pool", "type": "address" }, + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint256", "name": "fee", "type": "uint256" }, { "components": [ { @@ -1075,14 +1221,99 @@ "type": "tuple" }, { - "internalType": "uint256", - "name": "balanceToken0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceToken1", - "type": "uint256" + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" } ], "internalType": "struct Structs.PoolWithReserves", @@ -1095,60 +1326,28 @@ }, { "inputs": [ - { - "internalType": "address", - "name": "pool_", - "type": "address" - } + { "internalType": "address", "name": "pool_", "type": "address" } ], "name": "getPoolTokens", "outputs": [ - { - "internalType": "address", - "name": "token0_", - "type": "address" - }, - { - "internalType": "address", - "name": "token1_", - "type": "address" - } + { "internalType": "address", "name": "token0_", "type": "address" }, + { "internalType": "address", "name": "token1_", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { - "internalType": "address[]", - "name": "pools_", - "type": "address[]" - } + { "internalType": "address[]", "name": "pools_", "type": "address[]" } ], "name": "getPoolsReserves", "outputs": [ { "components": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "token0", - "type": "address" - }, - { - "internalType": "address", - "name": "token1", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, + { "internalType": "address", "name": "pool", "type": "address" }, + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint256", "name": "fee", "type": "uint256" }, { "components": [ { @@ -1214,14 +1413,99 @@ "type": "tuple" }, { - "internalType": "uint256", - "name": "balanceToken0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceToken1", - "type": "uint256" + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" } ], "internalType": "struct Structs.PoolWithReserves[]", @@ -1234,36 +1518,16 @@ }, { "inputs": [ - { - "internalType": "address[]", - "name": "pools_", - "type": "address[]" - } + { "internalType": "address[]", "name": "pools_", "type": "address[]" } ], "name": "getPoolsReservesAdjusted", "outputs": [ { "components": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "token0", - "type": "address" - }, - { - "internalType": "address", - "name": "token1", - "type": "address" - }, - { - "internalType": "uint256", - "name": "fee", - "type": "uint256" - }, + { "internalType": "address", "name": "pool", "type": "address" }, + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint256", "name": "fee", "type": "uint256" }, { "components": [ { @@ -1329,14 +1593,99 @@ "type": "tuple" }, { - "internalType": "uint256", - "name": "balanceToken0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balanceToken1", - "type": "uint256" + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" } ], "internalType": "struct Structs.PoolWithReserves[]", @@ -1350,14 +1699,8 @@ { "inputs": [], "name": "getTotalPools", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" } -] \ No newline at end of file +] diff --git a/pkg/liquidity-source/fluid/dex-t1/constant.go b/pkg/liquidity-source/fluid/dex-t1/constant.go index 2503bf81f..bddf63ae5 100644 --- a/pkg/liquidity-source/fluid/dex-t1/constant.go +++ b/pkg/liquidity-source/fluid/dex-t1/constant.go @@ -20,11 +20,17 @@ const ( const ( String1e18 = "1000000000000000000" + String1e27 = "1000000000000000000000000000" DexAmountsDecimals = 12 FeePercentPrecision int64 = 1e4 Fee100PercentPrecision int64 = 1e6 + + MaxPriceDiff int64 = 5 // 5% ) var bI1e18, _ = new(big.Int).SetString(String1e18, 10) // 1e18 +var bI1e27, _ = new(big.Int).SetString(String1e27, 10) // 1e27 +var bI10 = new(big.Int).SetInt64(10) +var bI100 = new(big.Int).SetInt64(100) diff --git a/pkg/liquidity-source/fluid/dex-t1/pool_list_updater.go b/pkg/liquidity-source/fluid/dex-t1/pool_list_updater.go index e13ac35dd..946a5e22d 100644 --- a/pkg/liquidity-source/fluid/dex-t1/pool_list_updater.go +++ b/pkg/liquidity-source/fluid/dex-t1/pool_list_updater.go @@ -2,7 +2,6 @@ package dexT1 import ( "context" - "math/big" "strings" "github.com/KyberNetwork/ethrpc" @@ -85,6 +84,7 @@ func (u *PoolsListUpdater) GetNewPools(ctx context.Context, metadataBytes []byte extra := PoolExtra{ CollateralReserves: curPool.CollateralReserves, DebtReserves: curPool.DebtReserves, + DexLimits: curPool.Limits, } extraBytes, err := json.Marshal(extra) @@ -98,8 +98,16 @@ func (u *PoolsListUpdater) GetNewPools(ctx context.Context, metadataBytes []byte Exchange: string(valueobject.ExchangeFluidDexT1), Type: DexType, Reserves: entity.PoolReserves{ - new(big.Int).Add(curPool.CollateralReserves.Token0RealReserves, curPool.DebtReserves.Token0RealReserves).String(), - new(big.Int).Add(curPool.CollateralReserves.Token1RealReserves, curPool.DebtReserves.Token1RealReserves).String(), + getMaxReserves( + curPool.Limits.WithdrawableToken0, + curPool.Limits.BorrowableToken0, + curPool.CollateralReserves.Token0RealReserves, + curPool.DebtReserves.Token0RealReserves).String(), + getMaxReserves( + curPool.Limits.WithdrawableToken1, + curPool.Limits.BorrowableToken1, + curPool.CollateralReserves.Token1RealReserves, + curPool.DebtReserves.Token1RealReserves).String(), }, Tokens: []*entity.PoolToken{ { diff --git a/pkg/liquidity-source/fluid/dex-t1/pool_list_updater_test.go b/pkg/liquidity-source/fluid/dex-t1/pool_list_updater_test.go index 708221552..d51b1474b 100644 --- a/pkg/liquidity-source/fluid/dex-t1/pool_list_updater_test.go +++ b/pkg/liquidity-source/fluid/dex-t1/pool_list_updater_test.go @@ -29,7 +29,7 @@ func TestPoolListUpdater(t *testing.T) { err error config = Config{ - DexReservesResolver: "0xE8a07a32489BD9d5a00f01A55749Cf5cB854Fd13", + DexReservesResolver: "0x45f4ad57e300da55c33dea579a40fcee000d7b94", } ) diff --git a/pkg/liquidity-source/fluid/dex-t1/pool_simulator.go b/pkg/liquidity-source/fluid/dex-t1/pool_simulator.go index 7abfcaef9..1bd1741aa 100644 --- a/pkg/liquidity-source/fluid/dex-t1/pool_simulator.go +++ b/pkg/liquidity-source/fluid/dex-t1/pool_simulator.go @@ -3,6 +3,7 @@ package dexT1 import ( "errors" "math/big" + "time" "github.com/goccy/go-json" "github.com/samber/lo" @@ -18,6 +19,11 @@ var ( ErrInsufficientReserve = errors.New("insufficient reserve: tokenOut amount exceeds reserve") ErrSwapAndArbitragePaused = errors.New("51043") + + ErrInsufficientWithdrawable = errors.New("insufficient reserve: tokenOut amount exceeds withdrawable limit") + ErrInsufficientBorrowable = errors.New("insufficient reserve: tokenOut amount exceeds borrowable limit") + + ErrInsufficientMaxPrice = errors.New("insufficient reserve: tokenOut amount exceeds max price limit") ) type PoolSimulator struct { @@ -26,10 +32,12 @@ type PoolSimulator struct { CollateralReserves CollateralReserves DebtReserves DebtReserves + DexLimits DexLimits Token0Decimals uint8 Token1Decimals uint8 + SyncTimestamp int64 IsSwapAndArbitragePaused bool } @@ -68,10 +76,12 @@ func NewPoolSimulator(entityPool entity.Pool) (*PoolSimulator, error) { }}, CollateralReserves: extra.CollateralReserves, DebtReserves: extra.DebtReserves, + DexLimits: extra.DexLimits, Token0Decimals: entityPool.Tokens[0].Decimals, Token1Decimals: entityPool.Tokens[1].Decimals, StaticExtra: staticExtra, IsSwapAndArbitragePaused: extra.IsSwapAndArbitragePaused, + SyncTimestamp: entityPool.Timestamp, }, nil } @@ -115,8 +125,33 @@ func (s *PoolSimulator) CalcAmountOut(param poolpkg.CalcAmountOutParams) (*poolp Token1ImaginaryReserves: new(big.Int).Set(s.DebtReserves.Token1ImaginaryReserves), } - _, tokenAmountOut, err := swapIn(swap0To1, amountInAfterFee, collateralReserves, debtReserves, - int64(tokenInDecimals), int64(tokenOutDecimals)) + dexLimits := DexLimits{ + BorrowableToken0: TokenLimit{ + Available: new(big.Int).Set(s.DexLimits.BorrowableToken0.Available), + ExpandsTo: new(big.Int).Set(s.DexLimits.BorrowableToken0.ExpandsTo), + ExpandDuration: new(big.Int).Set(s.DexLimits.BorrowableToken0.ExpandDuration), + }, + BorrowableToken1: TokenLimit{ + Available: new(big.Int).Set(s.DexLimits.BorrowableToken1.Available), + ExpandsTo: new(big.Int).Set(s.DexLimits.BorrowableToken1.ExpandsTo), + ExpandDuration: new(big.Int).Set(s.DexLimits.BorrowableToken1.ExpandDuration), + }, + WithdrawableToken0: TokenLimit{ + Available: new(big.Int).Set(s.DexLimits.WithdrawableToken0.Available), + ExpandsTo: new(big.Int).Set(s.DexLimits.WithdrawableToken0.ExpandsTo), + ExpandDuration: new(big.Int).Set(s.DexLimits.WithdrawableToken0.ExpandDuration), + }, + WithdrawableToken1: TokenLimit{ + Available: new(big.Int).Set(s.DexLimits.WithdrawableToken1.Available), + ExpandsTo: new(big.Int).Set(s.DexLimits.WithdrawableToken1.ExpandsTo), + ExpandDuration: new(big.Int).Set(s.DexLimits.WithdrawableToken1.ExpandDuration), + }, + } + + syncTimestamp := s.SyncTimestamp + + tokenAmountOut, err := swapIn(swap0To1, amountInAfterFee, collateralReserves, debtReserves, + int64(tokenInDecimals), int64(tokenOutDecimals), dexLimits, syncTimestamp) if err != nil { return nil, err } @@ -139,6 +174,7 @@ func (s *PoolSimulator) CalcAmountOut(param poolpkg.CalcAmountOutParams) (*poolp HasNative: s.HasNative, NewCollateralReserves: collateralReserves, NewDebtReserves: debtReserves, + NewDexLimits: dexLimits, }, }, nil } @@ -187,8 +223,33 @@ func (s *PoolSimulator) CalcAmountIn(param poolpkg.CalcAmountInParams) (*poolpkg Token1ImaginaryReserves: new(big.Int).Set(s.DebtReserves.Token1ImaginaryReserves), } - tokenAmountIn, _, err := swapOut(swap0To1, param.TokenAmountOut.Amount, collateralReserves, debtReserves, - int64(tokenInDecimals), int64(tokenOutDecimals)) + dexLimits := DexLimits{ + BorrowableToken0: TokenLimit{ + Available: new(big.Int).Set(s.DexLimits.BorrowableToken0.Available), + ExpandsTo: new(big.Int).Set(s.DexLimits.BorrowableToken0.ExpandsTo), + ExpandDuration: new(big.Int).Set(s.DexLimits.BorrowableToken0.ExpandDuration), + }, + BorrowableToken1: TokenLimit{ + Available: new(big.Int).Set(s.DexLimits.BorrowableToken1.Available), + ExpandsTo: new(big.Int).Set(s.DexLimits.BorrowableToken1.ExpandsTo), + ExpandDuration: new(big.Int).Set(s.DexLimits.BorrowableToken1.ExpandDuration), + }, + WithdrawableToken0: TokenLimit{ + Available: new(big.Int).Set(s.DexLimits.WithdrawableToken0.Available), + ExpandsTo: new(big.Int).Set(s.DexLimits.WithdrawableToken0.ExpandsTo), + ExpandDuration: new(big.Int).Set(s.DexLimits.WithdrawableToken0.ExpandDuration), + }, + WithdrawableToken1: TokenLimit{ + Available: new(big.Int).Set(s.DexLimits.WithdrawableToken1.Available), + ExpandsTo: new(big.Int).Set(s.DexLimits.WithdrawableToken1.ExpandsTo), + ExpandDuration: new(big.Int).Set(s.DexLimits.WithdrawableToken1.ExpandDuration), + }, + } + + syncTimestamp := s.SyncTimestamp + + tokenAmountIn, err := swapOut(swap0To1, param.TokenAmountOut.Amount, collateralReserves, debtReserves, + int64(tokenInDecimals), int64(tokenOutDecimals), dexLimits, syncTimestamp) if err != nil { return nil, err } @@ -207,6 +268,7 @@ func (s *PoolSimulator) CalcAmountIn(param poolpkg.CalcAmountInParams) (*poolpkg HasNative: s.HasNative, NewCollateralReserves: collateralReserves, NewDebtReserves: debtReserves, + NewDexLimits: dexLimits, }, }, nil } @@ -228,6 +290,10 @@ func (t *PoolSimulator) UpdateBalance(params poolpkg.UpdateBalanceParams) { if swapInfo, ok := params.SwapInfo.(SwapInfo); ok { t.CollateralReserves = swapInfo.NewCollateralReserves t.DebtReserves = swapInfo.NewDebtReserves + // Note: limits are updated, but are likely off for the input token until newly fetched. + // Erring on the cautious side with too tight limits to avoid potential reverts. + // See Comment Ref #4327563287 + t.DexLimits = swapInfo.NewDexLimits } } @@ -336,16 +402,33 @@ func swapRoutingOut(t *big.Int, x *big.Int, y *big.Int, x2 *big.Int, y2 *big.Int * @param {number} debtReserves.token1RealReserves - Real reserves of token1 in the debt pool. * @param {number} debtReserves.token0ImaginaryReserves - Imaginary reserves of token0 in the debt pool. * @param {number} debtReserves.token1ImaginaryReserves - Imaginary reserves of token1 in the debt pool. - * @returns {Object} An object containing the input amount and the calculated output amount. - * @returns {number} amountIn - The input amount. + * @param {number} outDecimals - The number of decimals for the output token. + * @param {Object} currentLimits - current borrowable & withdrawable of the pool. in token decimals. + * @param {Object} currentLimits.borrowableToken0 - token0 borrow limit + * @param {number} currentLimits.borrowableToken0.available - token0 instant borrowable available + * @param {number} currentLimits.borrowableToken0.expandsTo - token0 maximum amount the available borrow amount expands to + * @param {number} currentLimits.borrowableToken0.expandDuration - duration for token0 available to grow to expandsTo + * @param {Object} currentLimits.borrowableToken1 - token1 borrow limit + * @param {number} currentLimits.borrowableToken1.available - token1 instant borrowable available + * @param {number} currentLimits.borrowableToken1.expandsTo - token1 maximum amount the available borrow amount expands to + * @param {number} currentLimits.borrowableToken1.expandDuration - duration for token1 available to grow to expandsTo + * @param {Object} currentLimits.withdrawableToken0 - token0 withdraw limit + * @param {number} currentLimits.withdrawableToken0.available - token0 instant withdrawable available + * @param {number} currentLimits.withdrawableToken0.expandsTo - token0 maximum amount the available withdraw amount expands to + * @param {number} currentLimits.withdrawableToken0.expandDuration - duration for token0 available to grow to expandsTo + * @param {Object} currentLimits.withdrawableToken1 - token1 withdraw limit + * @param {number} currentLimits.withdrawableToken1.available - token1 instant withdrawable available + * @param {number} currentLimits.withdrawableToken1.expandsTo - token1 maximum amount the available withdraw amount expands to + * @param {number} currentLimits.withdrawableToken1.expandDuration - duration for token1 available to grow to expandsTo + * @param {number} syncTime - timestamp in seconds when the limits were synced * @returns {number} amountOut - The calculated output amount. * @returns {error} - An error object if the operation fails. */ -func swapInAdjusted(swap0To1 bool, amountToSwap *big.Int, colReserves CollateralReserves, - debtReserves DebtReserves) (*big.Int, *big.Int, error) { +func swapInAdjusted(swap0To1 bool, amountToSwap *big.Int, colReserves CollateralReserves, debtReserves DebtReserves, outDecimals int64, currentLimits DexLimits, syncTime int64) (*big.Int, error) { var ( colIReserveIn, colIReserveOut, debtIReserveIn, debtIReserveOut *big.Int colReserveOut, debtReserveOut *big.Int + borrowable, withdrawable *big.Int ) if swap0To1 { @@ -355,6 +438,8 @@ func swapInAdjusted(swap0To1 bool, amountToSwap *big.Int, colReserves Collateral debtReserveOut = debtReserves.Token1RealReserves debtIReserveIn = debtReserves.Token0ImaginaryReserves debtIReserveOut = debtReserves.Token1ImaginaryReserves + borrowable = getExpandedLimit(syncTime, currentLimits.BorrowableToken1) + withdrawable = getExpandedLimit(syncTime, currentLimits.WithdrawableToken1) } else { colReserveOut = colReserves.Token0RealReserves colIReserveIn = colReserves.Token1ImaginaryReserves @@ -362,6 +447,20 @@ func swapInAdjusted(swap0To1 bool, amountToSwap *big.Int, colReserves Collateral debtReserveOut = debtReserves.Token0RealReserves debtIReserveIn = debtReserves.Token1ImaginaryReserves debtIReserveOut = debtReserves.Token0ImaginaryReserves + borrowable = getExpandedLimit(syncTime, currentLimits.BorrowableToken0) + withdrawable = getExpandedLimit(syncTime, currentLimits.WithdrawableToken0) + } + + // bring borrowable and withdrawable from token decimals to 1e12 decimals, same as amounts + var factor *big.Int + if DexAmountsDecimals > outDecimals { + factor = new(big.Int).Exp(bI10, big.NewInt(DexAmountsDecimals-outDecimals), nil) + borrowable = new(big.Int).Mul(borrowable, factor) + withdrawable = new(big.Int).Mul(withdrawable, factor) + } else { + factor = new(big.Int).Exp(bI10, big.NewInt(outDecimals-DexAmountsDecimals), nil) + borrowable = new(big.Int).Div(borrowable, factor) + withdrawable = new(big.Int).Div(withdrawable, factor) } // Check if all reserves of collateral pool are greater than 0 @@ -384,47 +483,98 @@ func swapInAdjusted(swap0To1 bool, amountToSwap *big.Int, colReserves Collateral } else if colPoolEnabled { a = new(big.Int).Add(amountToSwap, big.NewInt(1)) // Route from collateral pool } else { - return nil, nil, errors.New("no pools are enabled") + return nil, errors.New("no pools are enabled") } - var amountOutCollateral, amountOutDebt *big.Int = bignumber.ZeroBI, bignumber.ZeroBI + amountInCollateral := big.NewInt(0) + amountOutCollateral := big.NewInt(0) + amountInDebt := big.NewInt(0) + amountOutDebt := big.NewInt(0) + + triggerUpdateDebtReserves := false + triggerUpdateColReserves := false + if a.Cmp(bignumber.ZeroBI) <= 0 { // Entire trade routes through debt pool + amountInDebt = amountToSwap amountOutDebt = getAmountOut(amountToSwap, debtIReserveIn, debtIReserveOut) - if amountOutDebt.Cmp(debtReserveOut) > 0 { - return nil, nil, errors.New("insufficient liquidity") - } - updateDebtReserves(swap0To1, amountToSwap, amountOutDebt, debtReserves) + triggerUpdateDebtReserves = true } else if a.Cmp(amountToSwap) >= 0 { // Entire trade routes through collateral pool + amountInCollateral = amountToSwap amountOutCollateral = getAmountOut(amountToSwap, colIReserveIn, colIReserveOut) - if amountOutCollateral.Cmp(colReserveOut) > 0 { - return nil, nil, errors.New("insufficient liquidity") - } - updateCollateralReserves(swap0To1, amountToSwap, amountOutCollateral, colReserves) + triggerUpdateColReserves = true } else { // Trade routes through both pools - var ( - amountInCollateral = a - amountInDebt = new(big.Int).Sub(amountToSwap, a) - ) - - amountOutCollateral = getAmountOut(amountInCollateral, colIReserveIn, colIReserveOut) - if amountOutCollateral.Cmp(colReserveOut) > 0 { - return nil, nil, errors.New("insufficient liquidity") - } + amountInDebt.Sub(amountToSwap, a) + amountInCollateral = a + amountOutCollateral = getAmountOut(a, colIReserveIn, colIReserveOut) + amountInDebt.Sub(amountToSwap, a) amountOutDebt = getAmountOut(amountInDebt, debtIReserveIn, debtIReserveOut) - if amountOutDebt.Cmp(debtReserveOut) > 0 { - return nil, nil, errors.New("insufficient liquidity") + + triggerUpdateDebtReserves = true + triggerUpdateColReserves = true + } + + if amountOutDebt.Cmp(debtReserveOut) > 0 { + return nil, errors.New(ErrInsufficientReserve.Error()) + } + + if amountOutCollateral.Cmp(colReserveOut) > 0 { + return nil, errors.New(ErrInsufficientReserve.Error()) + } + + if amountOutDebt.Cmp(borrowable) > 0 { + return nil, errors.New(ErrInsufficientBorrowable.Error()) + } + + if amountOutCollateral.Cmp(withdrawable) > 0 { + return nil, errors.New(ErrInsufficientWithdrawable.Error()) + } + + oldPrice := big.NewInt(0) + newPrice := big.NewInt(0) + priceDiff := big.NewInt(0) + maxPriceDiff := big.NewInt(0) + // from whatever pool higher amount of swap is routing we are taking that as final price, does not matter much because both pools final price should be same + if amountInCollateral.Cmp(amountInDebt) > 0 { + // new pool price from col pool + if swap0To1 { + oldPrice.Div(oldPrice.Mul(colIReserveOut, bI1e27), colIReserveIn) + newPrice.Div(newPrice.Mul(new(big.Int).Sub(colIReserveOut, amountOutCollateral), bI1e27), new(big.Int).Add(colIReserveIn, amountInCollateral)) + } else { + oldPrice.Div(oldPrice.Mul(colIReserveIn, bI1e27), colIReserveOut) + newPrice.Div(newPrice.Mul(new(big.Int).Add(colIReserveIn, amountInCollateral), bI1e27), new(big.Int).Sub(colIReserveOut, amountOutCollateral)) + } + } else { + // new pool price from debt pool + if swap0To1 { + oldPrice.Div(oldPrice.Mul(debtIReserveOut, bI1e27), debtIReserveIn) + newPrice.Div(newPrice.Mul(new(big.Int).Sub(debtIReserveOut, amountOutDebt), bI1e27), new(big.Int).Add(debtIReserveIn, amountInDebt)) + } else { + oldPrice.Div(oldPrice.Mul(debtIReserveIn, bI1e27), debtIReserveOut) + newPrice.Div(newPrice.Mul(new(big.Int).Add(debtIReserveIn, amountInDebt), bI1e27), new(big.Int).Sub(debtIReserveOut, amountOutDebt)) } + } + priceDiff.Abs(priceDiff.Sub(oldPrice, newPrice)) + maxPriceDiff.Div(maxPriceDiff.Mul(oldPrice, big.NewInt(MaxPriceDiff)), bI100) + if priceDiff.Cmp(maxPriceDiff) > 0 { + // if price diff is > 5% then swap would revert. + return nil, errors.New(ErrInsufficientMaxPrice.Error()) + } - updateBothReserves(swap0To1, amountInCollateral, amountOutCollateral, amountInDebt, amountOutDebt, colReserves, debtReserves) + if triggerUpdateColReserves && triggerUpdateDebtReserves { + updateBothReservesAndLimits(swap0To1, amountInCollateral, amountOutCollateral, amountInDebt, amountOutDebt, colReserves, debtReserves, currentLimits) + } else if triggerUpdateColReserves { + updateCollateralReservesAndLimits(swap0To1, amountToSwap, amountOutCollateral, colReserves, currentLimits) + } else if triggerUpdateDebtReserves { + updateDebtReservesAndLimits(swap0To1, amountToSwap, amountOutDebt, debtReserves, currentLimits) } - return amountToSwap, new(big.Int).Add(amountOutCollateral, amountOutDebt), nil + return new(big.Int).Add(amountOutCollateral, amountOutDebt), nil } /** @@ -443,8 +593,25 @@ func swapInAdjusted(swap0To1 bool, amountToSwap *big.Int, colReserves Collateral * @param {number} debtReserves.token1ImaginaryReserves - Imaginary reserves of token1 in the debt pool. * @param {number} inDecimals - The number of decimals for the input token. * @param {number} outDecimals - The number of decimals for the output token. - * @returns {number} amountIn - The input amount. - * @returns {number} amountOut - The calculated output amount scaled to token decimals + * @param {Object} currentLimits - current borrowable & withdrawable of the pool. in token decimals. + * @param {Object} currentLimits.borrowableToken0 - token0 borrow limit + * @param {number} currentLimits.borrowableToken0.available - token0 instant borrowable available + * @param {number} currentLimits.borrowableToken0.expandsTo - token0 maximum amount the available borrow amount expands to + * @param {number} currentLimits.borrowableToken0.expandDuration - duration for token0 available to grow to expandsTo + * @param {Object} currentLimits.borrowableToken1 - token1 borrow limit + * @param {number} currentLimits.borrowableToken1.available - token1 instant borrowable available + * @param {number} currentLimits.borrowableToken1.expandsTo - token1 maximum amount the available borrow amount expands to + * @param {number} currentLimits.borrowableToken1.expandDuration - duration for token1 available to grow to expandsTo + * @param {Object} currentLimits.withdrawableToken0 - token0 withdraw limit + * @param {number} currentLimits.withdrawableToken0.available - token0 instant withdrawable available + * @param {number} currentLimits.withdrawableToken0.expandsTo - token0 maximum amount the available withdraw amount expands to + * @param {number} currentLimits.withdrawableToken0.expandDuration - duration for token0 available to grow to expandsTo + * @param {Object} currentLimits.withdrawableToken1 - token1 withdraw limit + * @param {number} currentLimits.withdrawableToken1.available - token1 instant withdrawable available + * @param {number} currentLimits.withdrawableToken1.expandsTo - token1 maximum amount the available withdraw amount expands to + * @param {number} currentLimits.withdrawableToken1.expandDuration - duration for token1 available to grow to expandsTo + * @param {number} syncTime - timestamp in seconds when the limits were synced + * @returns {number} amountOut - The calculated output amount. * @returns {error} - An error object if the operation fails. */ func swapIn( @@ -454,32 +621,30 @@ func swapIn( debtReserves DebtReserves, inDecimals int64, outDecimals int64, -) (*big.Int, *big.Int, error) { + currentLimits DexLimits, + syncTime int64, +) (*big.Int, error) { var amountInAdjusted *big.Int if inDecimals > DexAmountsDecimals { - amountInAdjusted = new(big.Int).Div(amountIn, - new(big.Int).Exp(big.NewInt(10), big.NewInt(inDecimals-DexAmountsDecimals), nil)) + amountInAdjusted = new(big.Int).Div(amountIn, new(big.Int).Exp(bI10, big.NewInt(inDecimals-DexAmountsDecimals), nil)) } else { - amountInAdjusted = new(big.Int).Mul(amountIn, - new(big.Int).Exp(big.NewInt(10), big.NewInt(DexAmountsDecimals-inDecimals), nil)) + amountInAdjusted = new(big.Int).Mul(amountIn, new(big.Int).Exp(bI10, big.NewInt(DexAmountsDecimals-inDecimals), nil)) } - _, amountOut, err := swapInAdjusted(swap0To1, amountInAdjusted, colReserves, debtReserves) + amountOut, err := swapInAdjusted(swap0To1, amountInAdjusted, colReserves, debtReserves, outDecimals, currentLimits, syncTime) if err != nil { - return nil, nil, err + return nil, err } if outDecimals > DexAmountsDecimals { - amountOut = new(big.Int).Mul(amountOut, - new(big.Int).Exp(big.NewInt(10), big.NewInt(outDecimals-DexAmountsDecimals), nil)) + amountOut = new(big.Int).Mul(amountOut, new(big.Int).Exp(bI10, big.NewInt(outDecimals-DexAmountsDecimals), nil)) } else { - amountOut = new(big.Int).Div(amountOut, - new(big.Int).Exp(big.NewInt(10), big.NewInt(DexAmountsDecimals-outDecimals), nil)) + amountOut = new(big.Int).Div(amountOut, new(big.Int).Exp(bI10, big.NewInt(DexAmountsDecimals-outDecimals), nil)) } - return amountIn, amountOut, nil + return amountOut, nil } /** @@ -496,15 +661,41 @@ func swapIn( * @param {number} debtReserves.token1RealReserves - Real reserves of token1 in the debt pool. * @param {number} debtReserves.token0ImaginaryReserves - Imaginary reserves of token0 in the debt pool. * @param {number} debtReserves.token1ImaginaryReserves - Imaginary reserves of token1 in the debt pool. + * @param {number} outDecimals - The number of decimals for the output token. + * @param {Object} currentLimits - current borrowable & withdrawable of the pool. in token decimals. + * @param {Object} currentLimits.borrowableToken0 - token0 borrow limit + * @param {number} currentLimits.borrowableToken0.available - token0 instant borrowable available + * @param {number} currentLimits.borrowableToken0.expandsTo - token0 maximum amount the available borrow amount expands to + * @param {number} currentLimits.borrowableToken0.expandDuration - duration for token0 available to grow to expandsTo + * @param {Object} currentLimits.borrowableToken1 - token1 borrow limit + * @param {number} currentLimits.borrowableToken1.available - token1 instant borrowable available + * @param {number} currentLimits.borrowableToken1.expandsTo - token1 maximum amount the available borrow amount expands to + * @param {number} currentLimits.borrowableToken1.expandDuration - duration for token1 available to grow to expandsTo + * @param {Object} currentLimits.withdrawableToken0 - token0 withdraw limit + * @param {number} currentLimits.withdrawableToken0.available - token0 instant withdrawable available + * @param {number} currentLimits.withdrawableToken0.expandsTo - token0 maximum amount the available withdraw amount expands to + * @param {number} currentLimits.withdrawableToken0.expandDuration - duration for token0 available to grow to expandsTo + * @param {Object} currentLimits.withdrawableToken1 - token1 withdraw limit + * @param {number} currentLimits.withdrawableToken1.available - token1 instant withdrawable available + * @param {number} currentLimits.withdrawableToken1.expandsTo - token1 maximum amount the available withdraw amount expands to + * @param {number} currentLimits.withdrawableToken1.expandDuration - duration for token1 available to grow to expandsTo + * @param {number} syncTime - timestamp in seconds when the limits were synced * @returns {number} amountIn - The calculated input amount required for the swap. - * @returns {number} amountOut - The specified output amount of the swap. * @returns {error} - An error object if the operation fails. */ -func swapOutAdjusted(swap0To1 bool, amountOut *big.Int, colReserves CollateralReserves, - debtReserves DebtReserves) (*big.Int, *big.Int, error) { +func swapOutAdjusted( + swap0To1 bool, + amountOut *big.Int, + colReserves CollateralReserves, + debtReserves DebtReserves, + outDecimals int64, + currentLimits DexLimits, + syncTime int64, +) (*big.Int, error) { var ( colIReserveIn, colIReserveOut, debtIReserveIn, debtIReserveOut *big.Int colReserveOut, debtReserveOut *big.Int + borrowable, withdrawable *big.Int ) if swap0To1 { @@ -514,6 +705,8 @@ func swapOutAdjusted(swap0To1 bool, amountOut *big.Int, colReserves CollateralRe debtReserveOut = debtReserves.Token1RealReserves debtIReserveIn = debtReserves.Token0ImaginaryReserves debtIReserveOut = debtReserves.Token1ImaginaryReserves + borrowable = getExpandedLimit(syncTime, currentLimits.BorrowableToken1) + withdrawable = getExpandedLimit(syncTime, currentLimits.WithdrawableToken1) } else { colReserveOut = colReserves.Token0RealReserves colIReserveIn = colReserves.Token1ImaginaryReserves @@ -521,6 +714,20 @@ func swapOutAdjusted(swap0To1 bool, amountOut *big.Int, colReserves CollateralRe debtReserveOut = debtReserves.Token0RealReserves debtIReserveIn = debtReserves.Token1ImaginaryReserves debtIReserveOut = debtReserves.Token0ImaginaryReserves + borrowable = getExpandedLimit(syncTime, currentLimits.BorrowableToken0) + withdrawable = getExpandedLimit(syncTime, currentLimits.WithdrawableToken0) + } + + // bring borrowable and withdrawable from token decimals to 1e12 decimals, same as amounts + var factor *big.Int + if DexAmountsDecimals > outDecimals { + factor = new(big.Int).Exp(bI10, big.NewInt(DexAmountsDecimals-outDecimals), nil) + borrowable = new(big.Int).Mul(borrowable, factor) + withdrawable = new(big.Int).Mul(withdrawable, factor) + } else { + factor = new(big.Int).Exp(bI10, big.NewInt(outDecimals-DexAmountsDecimals), nil) + borrowable = new(big.Int).Div(borrowable, factor) + withdrawable = new(big.Int).Div(withdrawable, factor) } // Check if all reserves of collateral pool are greater than 0 @@ -543,45 +750,99 @@ func swapOutAdjusted(swap0To1 bool, amountOut *big.Int, colReserves CollateralRe } else if colPoolEnabled { a = new(big.Int).Add(amountOut, big.NewInt(1)) // Route from collateral pool } else { - return nil, nil, errors.New("no pools are enabled") + return nil, errors.New("no pools are enabled") } - var amountInCollateral, amountInDebt *big.Int = bignumber.ZeroBI, bignumber.ZeroBI + amountInCollateral := big.NewInt(0) + amountOutCollateral := big.NewInt(0) + amountInDebt := big.NewInt(0) + amountOutDebt := big.NewInt(0) + + triggerUpdateDebtReserves := false + triggerUpdateColReserves := false + if a.Cmp(bignumber.ZeroBI) <= 0 { // Entire trade routes through debt pool + amountOutDebt = amountOut amountInDebt = getAmountIn(amountOut, debtIReserveIn, debtIReserveOut) if amountOut.Cmp(debtReserveOut) > 0 { - return nil, nil, errors.New("insufficient liquidity") + return nil, errors.New(ErrInsufficientReserve.Error()) } - updateDebtReserves(swap0To1, amountInDebt, amountOut, debtReserves) + triggerUpdateDebtReserves = true } else if a.Cmp(amountOut) >= 0 { // Entire trade routes through collateral pool + amountOutCollateral = amountOut amountInCollateral = getAmountIn(amountOut, colIReserveIn, colIReserveOut) if amountOut.Cmp(colReserveOut) > 0 { - return nil, nil, errors.New("insufficient liquidity") + return nil, errors.New(ErrInsufficientReserve.Error()) } - updateCollateralReserves(swap0To1, amountInCollateral, amountOut, colReserves) + triggerUpdateColReserves = true } else { // Trade routes through both pools - var ( - amountOutCollateral = a - amountOutDebt = new(big.Int).Sub(amountOut, a) - ) - - amountInCollateral = getAmountIn(amountOutCollateral, colIReserveIn, colIReserveOut) + amountOutCollateral = a + amountInCollateral = getAmountIn(a, colIReserveIn, colIReserveOut) + amountOutDebt.Sub(amountOut, a) amountInDebt = getAmountIn(amountOutDebt, debtIReserveIn, debtIReserveOut) - if new(big.Int).Sub(amountOut, a).Cmp(debtReserveOut) > 0 || a.Cmp(debtReserveOut) > 0 { - return nil, nil, errors.New("insufficient liquidity") + if amountOutDebt.Cmp(debtReserveOut) > 0 || amountOutCollateral.Cmp(colReserveOut) > 0 { + return nil, errors.New(ErrInsufficientReserve.Error()) } - updateBothReserves(swap0To1, amountInCollateral, amountOutCollateral, amountInDebt, amountOutDebt, colReserves, debtReserves) + triggerUpdateDebtReserves = true + triggerUpdateColReserves = true + } + + if amountOutDebt.Cmp(borrowable) > 0 { + return nil, errors.New(ErrInsufficientBorrowable.Error()) + } + + if amountOutCollateral.Cmp(withdrawable) > 0 { + return nil, errors.New(ErrInsufficientWithdrawable.Error()) } - return new(big.Int).Add(amountInCollateral, amountInDebt), amountOut, nil + oldPrice := big.NewInt(0) + newPrice := big.NewInt(0) + priceDiff := big.NewInt(0) + maxPriceDiff := big.NewInt(0) + // from whatever pool higher amount of swap is routing we are taking that as final price, does not matter much because both pools final price should be same + if amountOutCollateral.Cmp(amountOutDebt) > 0 { + // new pool price from col pool + if swap0To1 { + oldPrice.Div(oldPrice.Mul(colIReserveOut, bI1e27), colIReserveIn) + newPrice.Div(newPrice.Mul(new(big.Int).Sub(colIReserveOut, amountOutCollateral), bI1e27), new(big.Int).Add(colIReserveIn, amountInCollateral)) + } else { + oldPrice.Div(oldPrice.Mul(colIReserveIn, bI1e27), colIReserveOut) + newPrice.Div(newPrice.Mul(new(big.Int).Add(colIReserveIn, amountInCollateral), bI1e27), new(big.Int).Sub(colIReserveOut, amountOutCollateral)) + } + } else { + // new pool price from debt pool + if swap0To1 { + oldPrice.Div(oldPrice.Mul(debtIReserveOut, bI1e27), debtIReserveIn) + newPrice.Div(newPrice.Mul(new(big.Int).Sub(debtIReserveOut, amountOutDebt), bI1e27), new(big.Int).Add(debtIReserveIn, amountInDebt)) + } else { + oldPrice.Div(oldPrice.Mul(debtIReserveIn, bI1e27), debtIReserveOut) + newPrice.Div(newPrice.Mul(new(big.Int).Add(debtIReserveIn, amountInDebt), bI1e27), new(big.Int).Sub(debtIReserveOut, amountOutDebt)) + } + } + priceDiff.Abs(priceDiff.Sub(oldPrice, newPrice)) + maxPriceDiff.Div(maxPriceDiff.Mul(oldPrice, big.NewInt(MaxPriceDiff)), bI100) + if priceDiff.Cmp(maxPriceDiff) > 0 { + // if price diff is > 5% then swap would revert. + return nil, errors.New(ErrInsufficientMaxPrice.Error()) + } + + if triggerUpdateColReserves && triggerUpdateDebtReserves { + updateBothReservesAndLimits(swap0To1, amountInCollateral, amountOutCollateral, amountInDebt, amountOutDebt, colReserves, debtReserves, currentLimits) + } else if triggerUpdateColReserves { + updateCollateralReservesAndLimits(swap0To1, amountInCollateral, amountOutCollateral, colReserves, currentLimits) + } else if triggerUpdateDebtReserves { + updateDebtReservesAndLimits(swap0To1, amountInDebt, amountOutDebt, debtReserves, currentLimits) + } + + return new(big.Int).Add(amountInCollateral, amountInDebt), nil } /** @@ -600,8 +861,25 @@ func swapOutAdjusted(swap0To1 bool, amountOut *big.Int, colReserves CollateralRe * @param {number} debtReserves.token1ImaginaryReserves - Imaginary reserves of token1 in the debt pool. * @param {number} inDecimals - The number of decimals for the input token. * @param {number} outDecimals - The number of decimals for the output token. - * @returns {number} amountIn - The calculated input amount required for the swap scaled to token decimals. - * @returns {number} amountOut - The specified output amount of the swap. + * @param {Object} currentLimits - current borrowable & withdrawable of the pool. in token decimals. + * @param {Object} currentLimits.borrowableToken0 - token0 borrow limit + * @param {number} currentLimits.borrowableToken0.available - token0 instant borrowable available + * @param {number} currentLimits.borrowableToken0.expandsTo - token0 maximum amount the available borrow amount expands to + * @param {number} currentLimits.borrowableToken0.expandDuration - duration for token0 available to grow to expandsTo + * @param {Object} currentLimits.borrowableToken1 - token1 borrow limit + * @param {number} currentLimits.borrowableToken1.available - token1 instant borrowable available + * @param {number} currentLimits.borrowableToken1.expandsTo - token1 maximum amount the available borrow amount expands to + * @param {number} currentLimits.borrowableToken1.expandDuration - duration for token1 available to grow to expandsTo + * @param {Object} currentLimits.withdrawableToken0 - token0 withdraw limit + * @param {number} currentLimits.withdrawableToken0.available - token0 instant withdrawable available + * @param {number} currentLimits.withdrawableToken0.expandsTo - token0 maximum amount the available withdraw amount expands to + * @param {number} currentLimits.withdrawableToken0.expandDuration - duration for token0 available to grow to expandsTo + * @param {Object} currentLimits.withdrawableToken1 - token1 withdraw limit + * @param {number} currentLimits.withdrawableToken1.available - token1 instant withdrawable available + * @param {number} currentLimits.withdrawableToken1.expandsTo - token1 maximum amount the available withdraw amount expands to + * @param {number} currentLimits.withdrawableToken1.expandDuration - duration for token1 available to grow to expandsTo + * @param {number} syncTime - timestamp in seconds when the limits were synced + * @returns {number} amountIn - The calculated input amount required for the swap. * @returns {error} - An error object if the operation fails. */ func swapOut( @@ -611,64 +889,110 @@ func swapOut( debtReserves DebtReserves, inDecimals int64, outDecimals int64, -) (*big.Int, *big.Int, error) { + currentLimits DexLimits, + syncTime int64, +) (*big.Int, error) { var amountOutAdjusted *big.Int if outDecimals > DexAmountsDecimals { amountOutAdjusted = new(big.Int).Div(amountOut, - new(big.Int).Exp(big.NewInt(10), big.NewInt(outDecimals-DexAmountsDecimals), nil)) + new(big.Int).Exp(bI10, big.NewInt(outDecimals-DexAmountsDecimals), nil)) } else { amountOutAdjusted = new(big.Int).Mul(amountOut, - new(big.Int).Exp(big.NewInt(10), big.NewInt(DexAmountsDecimals-outDecimals), nil)) + new(big.Int).Exp(bI10, big.NewInt(DexAmountsDecimals-outDecimals), nil)) } - amountIn, _, err := swapOutAdjusted(swap0To1, amountOutAdjusted, colReserves, debtReserves) + amountIn, err := swapOutAdjusted(swap0To1, amountOutAdjusted, colReserves, debtReserves, outDecimals, currentLimits, syncTime) if err != nil { - return nil, nil, err + return nil, err } if inDecimals > DexAmountsDecimals { amountIn = new(big.Int).Mul(amountIn, - new(big.Int).Exp(big.NewInt(10), big.NewInt(inDecimals-DexAmountsDecimals), nil)) + new(big.Int).Exp(bI10, big.NewInt(inDecimals-DexAmountsDecimals), nil)) } else { amountIn = new(big.Int).Div(amountIn, - new(big.Int).Exp(big.NewInt(10), big.NewInt(DexAmountsDecimals-inDecimals), nil)) + new(big.Int).Exp(bI10, big.NewInt(DexAmountsDecimals-inDecimals), nil)) + } + + return amountIn, nil +} + +// Calculates the currently available swappable amount for a token limit considering expansion since last syncTime. +func getExpandedLimit(syncTime int64, limit TokenLimit) *big.Int { + currentTime := time.Now().Unix() // get current time in seconds + elapsedTime := currentTime - syncTime + + expandedAmount := big.NewInt(0).Set(limit.Available) + + if elapsedTime < 10 { + // if almost no time has elapsed, return available amount + return expandedAmount + } + + if elapsedTime >= limit.ExpandDuration.Int64() { + // if duration has passed, return max amount + expandedAmount = limit.ExpandsTo + return expandedAmount } - return amountIn, amountOut, nil + expandedAmount = new(big.Int).Sub(limit.ExpandsTo, limit.Available) + expandedAmount.Mul(expandedAmount, big.NewInt(elapsedTime)) + expandedAmount.Div(expandedAmount, limit.ExpandDuration) + expandedAmount.Add(expandedAmount, limit.Available) + + return expandedAmount } -func updateDebtReserves(swap0To1 bool, amountIn, amountOut *big.Int, debtReserves DebtReserves) { +func updateDebtReservesAndLimits(swap0To1 bool, amountIn, amountOut *big.Int, debtReserves DebtReserves, limits DexLimits) { if swap0To1 { debtReserves.Token0RealReserves.Add(debtReserves.Token0RealReserves, amountIn) debtReserves.Token0ImaginaryReserves.Add(debtReserves.Token0ImaginaryReserves, amountIn) debtReserves.Token1RealReserves.Sub(debtReserves.Token1RealReserves, amountOut) debtReserves.Token1ImaginaryReserves.Sub(debtReserves.Token1ImaginaryReserves, amountOut) + + // Comment Ref #4327563287 + // if expandTo for borrowable and withdrawable match, that means they are a hard limit like liquidity layer balance + // or utilization limit. In that case, the available swap amount should increase by `amountIn` but it's not guaranteed + // because the actual borrow limit / withdrawal limit could be the limiting factor now, which could be even + // only +1 bigger. So not updating in amount to avoid any revert. The same applies on all other similar cases in the code + // below. Note a swap would anyway trigger an event, so the proper limits will be fetched shortly after the swap. + limits.BorrowableToken1.Available.Sub(limits.BorrowableToken1.Available, amountOut) + limits.BorrowableToken1.ExpandsTo.Sub(limits.BorrowableToken1.ExpandsTo, amountOut) } else { debtReserves.Token0RealReserves.Sub(debtReserves.Token0RealReserves, amountOut) debtReserves.Token0ImaginaryReserves.Sub(debtReserves.Token0ImaginaryReserves, amountOut) debtReserves.Token1RealReserves.Add(debtReserves.Token1RealReserves, amountIn) debtReserves.Token1ImaginaryReserves.Add(debtReserves.Token1ImaginaryReserves, amountIn) + + limits.BorrowableToken0.Available.Sub(limits.BorrowableToken0.Available, amountOut) + limits.BorrowableToken0.ExpandsTo.Sub(limits.BorrowableToken0.ExpandsTo, amountOut) } } -func updateCollateralReserves(swap0To1 bool, amountIn, amountOut *big.Int, colReserves CollateralReserves) { +func updateCollateralReservesAndLimits(swap0To1 bool, amountIn, amountOut *big.Int, colReserves CollateralReserves, limits DexLimits) { if swap0To1 { colReserves.Token0RealReserves.Add(colReserves.Token0RealReserves, amountIn) colReserves.Token0ImaginaryReserves.Add(colReserves.Token0ImaginaryReserves, amountIn) colReserves.Token1RealReserves.Sub(colReserves.Token1RealReserves, amountOut) colReserves.Token1ImaginaryReserves.Sub(colReserves.Token1ImaginaryReserves, amountOut) + + limits.WithdrawableToken1.Available.Sub(limits.WithdrawableToken1.Available, amountOut) + limits.WithdrawableToken1.ExpandsTo.Sub(limits.WithdrawableToken1.ExpandsTo, amountOut) } else { colReserves.Token0RealReserves.Sub(colReserves.Token0RealReserves, amountOut) colReserves.Token0ImaginaryReserves.Sub(colReserves.Token0ImaginaryReserves, amountOut) colReserves.Token1RealReserves.Add(colReserves.Token1RealReserves, amountIn) colReserves.Token1ImaginaryReserves.Add(colReserves.Token1ImaginaryReserves, amountIn) + + limits.WithdrawableToken0.Available.Sub(limits.WithdrawableToken0.Available, amountOut) + limits.WithdrawableToken0.ExpandsTo.Sub(limits.WithdrawableToken0.ExpandsTo, amountOut) } } -func updateBothReserves(swap0To1 bool, amountInCollateral, amountOutCollateral, amountInDebt, amountOutDebt *big.Int, - colReserves CollateralReserves, debtReserves DebtReserves) { +func updateBothReservesAndLimits(swap0To1 bool, amountInCollateral, amountOutCollateral, amountInDebt, amountOutDebt *big.Int, + colReserves CollateralReserves, debtReserves DebtReserves, limits DexLimits) { if swap0To1 { colReserves.Token1RealReserves.Sub(colReserves.Token1RealReserves, amountOutCollateral) colReserves.Token1ImaginaryReserves.Sub(colReserves.Token1ImaginaryReserves, amountOutCollateral) @@ -679,6 +1003,11 @@ func updateBothReserves(swap0To1 bool, amountInCollateral, amountOutCollateral, debtReserves.Token1ImaginaryReserves.Sub(debtReserves.Token1ImaginaryReserves, amountOutDebt) debtReserves.Token0RealReserves.Add(debtReserves.Token0RealReserves, amountInDebt) debtReserves.Token0ImaginaryReserves.Add(debtReserves.Token0ImaginaryReserves, amountInDebt) + + limits.BorrowableToken1.Available.Sub(limits.BorrowableToken1.Available, amountOutDebt) + limits.BorrowableToken1.ExpandsTo.Sub(limits.BorrowableToken1.ExpandsTo, amountOutDebt) + limits.WithdrawableToken1.Available.Sub(limits.WithdrawableToken1.Available, amountOutCollateral) + limits.WithdrawableToken1.ExpandsTo.Sub(limits.WithdrawableToken1.ExpandsTo, amountOutCollateral) } else { colReserves.Token1RealReserves.Add(colReserves.Token1RealReserves, amountInCollateral) colReserves.Token1ImaginaryReserves.Add(colReserves.Token1ImaginaryReserves, amountInCollateral) @@ -689,5 +1018,10 @@ func updateBothReserves(swap0To1 bool, amountInCollateral, amountOutCollateral, debtReserves.Token1ImaginaryReserves.Add(debtReserves.Token1ImaginaryReserves, amountInDebt) debtReserves.Token0RealReserves.Sub(debtReserves.Token0RealReserves, amountOutDebt) debtReserves.Token0ImaginaryReserves.Sub(debtReserves.Token0ImaginaryReserves, amountOutDebt) + + limits.BorrowableToken0.Available.Sub(limits.BorrowableToken0.Available, amountOutDebt) + limits.BorrowableToken0.ExpandsTo.Sub(limits.BorrowableToken0.ExpandsTo, amountOutDebt) + limits.WithdrawableToken0.Available.Sub(limits.WithdrawableToken0.Available, amountOutCollateral) + limits.WithdrawableToken0.ExpandsTo.Sub(limits.WithdrawableToken0.ExpandsTo, amountOutCollateral) } } diff --git a/pkg/liquidity-source/fluid/dex-t1/pool_simulator_test.go b/pkg/liquidity-source/fluid/dex-t1/pool_simulator_test.go index a1874fb3f..860a60aa5 100644 --- a/pkg/liquidity-source/fluid/dex-t1/pool_simulator_test.go +++ b/pkg/liquidity-source/fluid/dex-t1/pool_simulator_test.go @@ -3,6 +3,7 @@ package dexT1 import ( "math/big" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -47,6 +48,8 @@ func TestPoolSimulator_CalcAmountOut(t *testing.T) { Token0ImaginaryReserves: bignumber.NewBig("62511862774117387"), Token1ImaginaryReserves: bignumber.NewBig("73766803277429176"), }, + DexLimits: limitsWide, + SyncTimestamp: time.Now().Unix() - 10, Token0Decimals: 18, Token1Decimals: 18, }, @@ -91,6 +94,8 @@ func TestPoolSimulator_CalcAmountOut(t *testing.T) { Token0ImaginaryReserves: bignumber.NewBig("62511862774117387"), Token1ImaginaryReserves: bignumber.NewBig("73766803277429176"), }, + DexLimits: limitsWide, + SyncTimestamp: time.Now().Unix() - 10, Token0Decimals: 18, Token1Decimals: 18, }, @@ -134,6 +139,8 @@ func TestPoolSimulator_CalcAmountOut(t *testing.T) { Token0ImaginaryReserves: bignumber.NewBig("62511862774117387"), Token1ImaginaryReserves: bignumber.NewBig("73766803277429176"), }, + DexLimits: limitsWide, + SyncTimestamp: time.Now().Unix() - 10, Token0Decimals: 18, Token1Decimals: 18, }, @@ -178,6 +185,8 @@ func TestPoolSimulator_CalcAmountOut(t *testing.T) { Token0ImaginaryReserves: bignumber.NewBig("62511862774117387"), Token1ImaginaryReserves: bignumber.NewBig("73766803277429176"), }, + DexLimits: limitsWide, + SyncTimestamp: time.Now().Unix() - 10, Token0Decimals: 18, Token1Decimals: 18, }, @@ -248,6 +257,8 @@ func TestPoolSimulator_CalcAmountIn(t *testing.T) { Token0ImaginaryReserves: bignumber.NewBig("62511862774117387"), Token1ImaginaryReserves: bignumber.NewBig("73766803277429176"), }, + DexLimits: limitsWide, + SyncTimestamp: time.Now().Unix() - 10, Token0Decimals: 18, Token1Decimals: 18, }, @@ -289,6 +300,8 @@ func TestPoolSimulator_CalcAmountIn(t *testing.T) { Token0ImaginaryReserves: bignumber.NewBig("62511862774117387"), Token1ImaginaryReserves: bignumber.NewBig("73766803277429176"), }, + DexLimits: limitsWide, + SyncTimestamp: time.Now().Unix() - 10, Token0Decimals: 18, Token1Decimals: 18, }, @@ -330,6 +343,8 @@ func TestPoolSimulator_CalcAmountIn(t *testing.T) { Token0ImaginaryReserves: bignumber.NewBig("62511862774117387"), Token1ImaginaryReserves: bignumber.NewBig("73766803277429176"), }, + DexLimits: limitsWide, + SyncTimestamp: time.Now().Unix() - 10, Token0Decimals: 18, Token1Decimals: 18, }, @@ -371,6 +386,8 @@ func TestPoolSimulator_CalcAmountIn(t *testing.T) { Token0ImaginaryReserves: bignumber.NewBig("62511862774117387"), Token1ImaginaryReserves: bignumber.NewBig("73766803277429176"), }, + DexLimits: limitsWide, + SyncTimestamp: time.Now().Unix() - 10, Token0Decimals: 18, Token1Decimals: 18, }, @@ -404,6 +421,54 @@ func TestPoolSimulator_CalcAmountIn(t *testing.T) { } } +var limitExpandTight, _ = new(big.Int).SetString("711907234052361388866", 10) +var limitsTight = DexLimits{ + WithdrawableToken0: TokenLimit{ + Available: big.NewInt(456740438880263), + ExpandsTo: big.NewInt(0).Set(limitExpandTight), + ExpandDuration: big.NewInt(600), + }, + WithdrawableToken1: TokenLimit{ + Available: big.NewInt(825179383432029), + ExpandsTo: big.NewInt(0).Set(limitExpandTight), + ExpandDuration: big.NewInt(600), + }, + BorrowableToken0: TokenLimit{ + Available: big.NewInt(941825058374170), + ExpandsTo: big.NewInt(0).Set(limitExpandTight), + ExpandDuration: big.NewInt(600), + }, + BorrowableToken1: TokenLimit{ + Available: big.NewInt(941825058374170), + ExpandsTo: big.NewInt(0).Set(limitExpandTight), + ExpandDuration: big.NewInt(600), + }, +} + +var limitWide, _ = new(big.Int).SetString("34242332879776515083099999", 10) +var limitsWide = DexLimits{ + WithdrawableToken0: TokenLimit{ + Available: big.NewInt(0).Set(limitWide), + ExpandsTo: big.NewInt(0).Set(limitWide), + ExpandDuration: bignumber.ZeroBI, + }, + WithdrawableToken1: TokenLimit{ + Available: big.NewInt(0).Set(limitWide), + ExpandsTo: big.NewInt(0).Set(limitWide), + ExpandDuration: big.NewInt(22), + }, + BorrowableToken0: TokenLimit{ + Available: big.NewInt(0).Set(limitWide), + ExpandsTo: big.NewInt(0).Set(limitWide), + ExpandDuration: bignumber.ZeroBI, + }, + BorrowableToken1: TokenLimit{ + Available: big.NewInt(0).Set(limitWide), + ExpandsTo: big.NewInt(0).Set(limitWide), + ExpandDuration: big.NewInt(308), + }, +} + func NewColReservesOne() CollateralReserves { return CollateralReserves{ Token0RealReserves: big.NewInt(20000000006000000), @@ -440,33 +505,56 @@ func NewDebtReservesOne() DebtReserves { } } -func assertSwapInResult(t *testing.T, expected bool, amountIn *big.Int, colReserves CollateralReserves, debtReserves DebtReserves, expectedAmountIn string, expectedAmountOut string) { - inAmt, outAmt, _ := swapInAdjusted(expected, amountIn, colReserves, debtReserves) +func assertSwapInResult(t *testing.T, swap0To1 bool, amountIn *big.Int, colReserves CollateralReserves, debtReserves DebtReserves, expectedAmountIn string, expectedAmountOut string, outDecimals int64, limits DexLimits, syncTime int64) { + outAmt, _ := swapInAdjusted(swap0To1, amountIn, colReserves, debtReserves, outDecimals, limits, syncTime) - require.Equal(t, expectedAmountIn, inAmt.String()) + require.Equal(t, expectedAmountIn, amountIn.String()) require.Equal(t, expectedAmountOut, outAmt.String()) } -func assertSwapOutResult(t *testing.T, expected bool, amountOut *big.Int, colReserves CollateralReserves, debtReserves DebtReserves, expectedAmountIn string, expectedAmountOut string) { - inAmt, outAmt, _ := swapOutAdjusted(expected, amountOut, colReserves, debtReserves) +func assertSwapOutResult(t *testing.T, swap0To1 bool, amountOut *big.Int, colReserves CollateralReserves, debtReserves DebtReserves, expectedAmountIn string, expectedAmountOut string, outDecimals int64, limits DexLimits, syncTime int64) { + inAmt, _ := swapOutAdjusted(swap0To1, amountOut, colReserves, debtReserves, outDecimals, limits, syncTime) require.Equal(t, expectedAmountIn, inAmt.String()) - require.Equal(t, expectedAmountOut, outAmt.String()) + require.Equal(t, expectedAmountOut, amountOut.String()) } func TestPoolSimulator_SwapIn(t *testing.T) { t.Run("TestPoolSimulator_SwapIn", func(t *testing.T) { - assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1000000000000000", "998262697204710") - assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1000000000000000", "994619847016724") - assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1000000000000000", "997440731289905") - assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1000000000000000", "998262697752553") - assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1000000000000000", "994619847560607") - assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1000000000000000", "997440731837532") + assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1000000000000000", "998262697204710", 18, limitsWide, time.Now().Unix()-10) + assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1000000000000000", "994619847016724", 18, limitsWide, time.Now().Unix()-10) + assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1000000000000000", "997440731289905", 18, limitsWide, time.Now().Unix()-10) + assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1000000000000000", "998262697752553", 18, limitsWide, time.Now().Unix()-10) + assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1000000000000000", "994619847560607", 18, limitsWide, time.Now().Unix()-10) + assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1000000000000000", "997440731837532", 18, limitsWide, time.Now().Unix()-10) }) } + +func TestPoolSimulator_SwapInLimits(t *testing.T) { + t.Run("TestPoolSimulator_SwapInLimits", func(t *testing.T) { + // when limits hit + outAmt, err := swapInAdjusted(true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), 18, limitsTight, time.Now().Unix()-10) + require.Nil(t, outAmt) + require.EqualError(t, err, ErrInsufficientBorrowable.Error()) + + // when expanded + outAmt, _ = swapInAdjusted(true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), 18, limitsTight, time.Now().Unix()-6000) + require.Equal(t, "998262697204710", outAmt.String()) + + // when price diff hit + outAmt, err = swapInAdjusted(true, big.NewInt(3e16), NewColReservesOne(), NewDebtReservesOne(), 18, limitsWide, time.Now().Unix()-10) + require.Nil(t, outAmt) + require.EqualError(t, err, ErrInsufficientMaxPrice.Error()) + + // when reserves limt is hit + outAmt, err = swapInAdjusted(true, big.NewInt(5e16), NewColReservesOne(), NewDebtReservesOne(), 18, limitsWide, time.Now().Unix()-10) + require.Nil(t, outAmt) + require.EqualError(t, err, ErrInsufficientReserve.Error()) + }) +} + func TestPoolSimulator_swapInAdjusted(t *testing.T) { t.Run("TestPoolSimulator_SwapInCompareEstimateIn", func(t *testing.T) { - expectedAmountIn := "1000000000000000000" expectedAmountOut := "1180035404724000000" colReserves := CollateralReserves{ @@ -485,57 +573,79 @@ func TestPoolSimulator_swapInAdjusted(t *testing.T) { } amountIn := big.NewInt(1e12) - inAmt, outAmt, _ := swapInAdjusted(true, amountIn, colReserves, debtReserves) + outAmt, _ := swapInAdjusted(true, amountIn, colReserves, debtReserves, 18, limitsWide, time.Now().Unix()-10) - require.Equal(t, expectedAmountIn, big.NewInt(0).Mul(inAmt, big.NewInt(1e6)).String()) require.Equal(t, expectedAmountOut, big.NewInt(0).Mul(outAmt, big.NewInt(1e6)).String()) }) } func TestPoolSimulator_SwapOut(t *testing.T) { t.Run("TestPoolSimulator_SwapOut", func(t *testing.T) { - assertSwapOutResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1001743360284199", "1000000000000000") - assertSwapOutResult(t, true, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1005438674786548", "1000000000000000") - assertSwapOutResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1002572435818386", "1000000000000000") - assertSwapOutResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1001743359733488", "1000000000000000") - assertSwapOutResult(t, false, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1005438674233767", "1000000000000000") - assertSwapOutResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1002572435266527", "1000000000000000") + assertSwapOutResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1001743360284199", "1000000000000000", 18, limitsWide, time.Now().Unix()-10) + assertSwapOutResult(t, true, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1005438674786548", "1000000000000000", 18, limitsWide, time.Now().Unix()-10) + assertSwapOutResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1002572435818386", "1000000000000000", 18, limitsWide, time.Now().Unix()-10) + assertSwapOutResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1001743359733488", "1000000000000000", 18, limitsWide, time.Now().Unix()-10) + assertSwapOutResult(t, false, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1005438674233767", "1000000000000000", 18, limitsWide, time.Now().Unix()-10) + assertSwapOutResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1002572435266527", "1000000000000000", 18, limitsWide, time.Now().Unix()-10) + }) +} + +func TestPoolSimulator_SwapOutLimits(t *testing.T) { + t.Run("TestPoolSimulator_SwapInLimits", func(t *testing.T) { + // when limits hit + outAmt, err := swapOutAdjusted(true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), 18, limitsTight, time.Now().Unix()-10) + require.Nil(t, outAmt) + require.EqualError(t, err, ErrInsufficientBorrowable.Error()) + + // when expanded + outAmt, _ = swapOutAdjusted(true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), 18, limitsTight, time.Now().Unix()-6000) + require.Equal(t, "1001743360284199", outAmt.String()) + + // when price diff hit + outAmt, err = swapOutAdjusted(true, big.NewInt(2e16), NewColReservesOne(), NewDebtReservesOne(), 18, limitsWide, time.Now().Unix()-10) + require.Nil(t, outAmt) + require.EqualError(t, err, ErrInsufficientMaxPrice.Error()) + + // when reserves limt is hit + outAmt, err = swapOutAdjusted(true, big.NewInt(3e16), NewColReservesOne(), NewDebtReservesOne(), 18, limitsWide, time.Now().Unix()-10) + require.Nil(t, outAmt) + require.EqualError(t, err, ErrInsufficientReserve.Error()) }) } func TestPoolSimulator_SwapInOut(t *testing.T) { t.Run("TestPoolSimulator_SwapInOut", func(t *testing.T) { - assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1000000000000000", "998262697204710") + assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1000000000000000", "998262697204710", 18, limitsWide, time.Now().Unix()-10) - assertSwapOutResult(t, true, big.NewInt(998262697204710), NewColReservesOne(), NewDebtReservesOne(), "999999999999998", "998262697204710") + assertSwapOutResult(t, true, big.NewInt(998262697204710), NewColReservesOne(), NewDebtReservesOne(), "999999999999998", "998262697204710", 18, limitsWide, time.Now().Unix()-10) - assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1000000000000000", "998262697752553") + assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesOne(), "1000000000000000", "998262697752553", 18, limitsWide, time.Now().Unix()-10) - assertSwapOutResult(t, false, big.NewInt(998262697752553), NewColReservesOne(), NewDebtReservesOne(), "999999999999998", "998262697752553") + assertSwapOutResult(t, false, big.NewInt(998262697752553), NewColReservesOne(), NewDebtReservesOne(), "999999999999998", "998262697752553", 18, limitsWide, time.Now().Unix()-10) }) } func TestPoolSimulator_SwapInOutDebtEmpty(t *testing.T) { t.Run("TestPoolSimulator_SwapInOutDebtEmpty", func(t *testing.T) { - assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1000000000000000", "994619847016724") + assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1000000000000000", "994619847016724", 18, limitsWide, time.Now().Unix()-10) - assertSwapOutResult(t, true, big.NewInt(994619847016724), NewColReservesEmpty(), NewDebtReservesOne(), "999999999999999", "994619847016724") + assertSwapOutResult(t, true, big.NewInt(994619847016724), NewColReservesEmpty(), NewDebtReservesOne(), "999999999999999", "994619847016724", 18, limitsWide, time.Now().Unix()-10) - assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1000000000000000", "994619847560607") + assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesEmpty(), NewDebtReservesOne(), "1000000000000000", "994619847560607", 18, limitsWide, time.Now().Unix()-10) - assertSwapOutResult(t, false, big.NewInt(994619847560607), NewColReservesEmpty(), NewDebtReservesOne(), "999999999999999", "994619847560607") + assertSwapOutResult(t, false, big.NewInt(994619847560607), NewColReservesEmpty(), NewDebtReservesOne(), "999999999999999", "994619847560607", 18, limitsWide, time.Now().Unix()-10) }) } func TestPoolSimulator_SwapInOutColEmpty(t *testing.T) { t.Run("TestPoolSimulator_SwapInOutColEmpty", func(t *testing.T) { - assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1000000000000000", "997440731289905") + assertSwapInResult(t, true, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1000000000000000", "997440731289905", 18, limitsWide, time.Now().Unix()-10) - assertSwapOutResult(t, true, big.NewInt(997440731289905), NewColReservesOne(), NewDebtReservesEmpty(), "999999999999999", "997440731289905") + assertSwapOutResult(t, true, big.NewInt(997440731289905), NewColReservesOne(), NewDebtReservesEmpty(), "999999999999999", "997440731289905", 18, limitsWide, time.Now().Unix()-10) - assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1000000000000000", "997440731837532") + assertSwapInResult(t, false, big.NewInt(1e15), NewColReservesOne(), NewDebtReservesEmpty(), "1000000000000000", "997440731837532", 18, limitsWide, time.Now().Unix()-10) - assertSwapOutResult(t, false, big.NewInt(997440731837532), NewColReservesOne(), NewDebtReservesEmpty(), "999999999999999", "997440731837532") + assertSwapOutResult(t, false, big.NewInt(997440731837532), NewColReservesOne(), NewDebtReservesEmpty(), "999999999999999", "997440731837532", 18, limitsWide, time.Now().Unix()-10) }) } diff --git a/pkg/liquidity-source/fluid/dex-t1/pool_tracker.go b/pkg/liquidity-source/fluid/dex-t1/pool_tracker.go index 6accbe6c9..aa3d3ae9e 100644 --- a/pkg/liquidity-source/fluid/dex-t1/pool_tracker.go +++ b/pkg/liquidity-source/fluid/dex-t1/pool_tracker.go @@ -2,6 +2,7 @@ package dexT1 import ( "context" + "math/big" "time" "github.com/KyberNetwork/ethrpc" @@ -59,6 +60,7 @@ func (t *PoolTracker) getNewPoolState( CollateralReserves: collateralReserves, DebtReserves: debtReserves, IsSwapAndArbitragePaused: isSwapAndArbitragePaused, + DexLimits: poolReserves.Limits, } extraBytes, err := json.Marshal(extra) @@ -71,8 +73,18 @@ func (t *PoolTracker) getNewPoolState( p.Extra = string(extraBytes) p.BlockNumber = blockNumber p.Timestamp = time.Now().Unix() - - p.Reserves = entity.PoolReserves{poolReserves.BalanceToken0.String(), poolReserves.BalanceToken1.String()} + p.Reserves = entity.PoolReserves{ + getMaxReserves( + poolReserves.Limits.WithdrawableToken0, + poolReserves.Limits.BorrowableToken0, + poolReserves.CollateralReserves.Token0RealReserves, + poolReserves.DebtReserves.Token0RealReserves).String(), + getMaxReserves( + poolReserves.Limits.WithdrawableToken1, + poolReserves.Limits.BorrowableToken1, + poolReserves.CollateralReserves.Token1RealReserves, + poolReserves.DebtReserves.Token1RealReserves).String(), + } return p, nil } @@ -119,3 +131,28 @@ func (t *PoolTracker) getPoolReserves( return pool, isSwapAndArbitragePaused, resp.BlockNumber.Uint64(), nil } + +func getMaxReserves( + withdrawableLimit TokenLimit, + borrowableLimit TokenLimit, + realColReserves *big.Int, + realDebtReserves *big.Int, +) *big.Int { + // max available reserves: the smaller possible value between real reserves and the expandTo limits + // the expandTo limits include liquidity layer balances, utilization limits, withdrawable and borrowable limits + + // if expandTo for borrowable and withdrawable match, that means they are a hard limit like liquidity layer balance + // or utilization limit. In that case expandTo can not be summed up. Otherwise it's the case of expanding withdrawal + // and borrow limits, for which we must sum up the max available reserve amount. + maxLimitReserves := new(big.Int).Add(borrowableLimit.ExpandsTo, withdrawableLimit.ExpandsTo) + if borrowableLimit.ExpandsTo.Cmp(withdrawableLimit.ExpandsTo) == 0 { + maxLimitReserves.Set(borrowableLimit.ExpandsTo) + } + + maxRealReserves := new(big.Int).Add(realColReserves, realDebtReserves) + + if maxRealReserves.Cmp(maxLimitReserves) < 0 { + return maxRealReserves + } + return maxLimitReserves +} diff --git a/pkg/liquidity-source/fluid/dex-t1/pool_tracker_test.go b/pkg/liquidity-source/fluid/dex-t1/pool_tracker_test.go index 41bf6e75a..566e64f07 100644 --- a/pkg/liquidity-source/fluid/dex-t1/pool_tracker_test.go +++ b/pkg/liquidity-source/fluid/dex-t1/pool_tracker_test.go @@ -7,14 +7,13 @@ import ( "testing" "github.com/KyberNetwork/ethrpc" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" + "github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject" "github.com/KyberNetwork/logger" "github.com/ethereum/go-ethereum/common" "github.com/goccy/go-json" "github.com/stretchr/testify/require" - - "github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity" - "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool" - "github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject" ) func TestPoolTracker(t *testing.T) { @@ -26,7 +25,7 @@ func TestPoolTracker(t *testing.T) { var ( config = Config{ - DexReservesResolver: "0x87807F35b81cFdF762cEF717059B992920081298", + DexReservesResolver: "0x45f4ad57e300da55c33dea579a40fcee000d7b94", } ) diff --git a/pkg/liquidity-source/fluid/dex-t1/types.go b/pkg/liquidity-source/fluid/dex-t1/types.go index 3a352d0ff..64d9a82be 100644 --- a/pkg/liquidity-source/fluid/dex-t1/types.go +++ b/pkg/liquidity-source/fluid/dex-t1/types.go @@ -14,6 +14,7 @@ type PoolExtra struct { CollateralReserves CollateralReserves DebtReserves DebtReserves IsSwapAndArbitragePaused bool + DexLimits DexLimits } type CollateralReserves struct { @@ -32,6 +33,19 @@ type DebtReserves struct { Token1ImaginaryReserves *big.Int `json:"token1ImaginaryReserves"` } +type TokenLimit struct { + Available *big.Int `json:"available"` // maximum available swap amount + ExpandsTo *big.Int `json:"expandsTo"` // maximum amount the available swap amount expands to + ExpandDuration *big.Int `json:"expandDuration"` // duration for `available` to grow to `expandsTo` +} + +type DexLimits struct { + WithdrawableToken0 TokenLimit `json:"withdrawableToken0"` + WithdrawableToken1 TokenLimit `json:"withdrawableToken1"` + BorrowableToken0 TokenLimit `json:"borrowableToken0"` + BorrowableToken1 TokenLimit `json:"borrowableToken1"` +} + type PoolWithReserves struct { PoolAddress common.Address `json:"poolAddress"` Token0Address common.Address `json:"token0Address"` @@ -39,8 +53,7 @@ type PoolWithReserves struct { Fee *big.Int `json:"fee"` CollateralReserves CollateralReserves `json:"collateralReserves"` DebtReserves DebtReserves `json:"debtReserves"` - BalanceToken0 *big.Int `json:"balanceToken0"` - BalanceToken1 *big.Int `json:"balanceToken1"` + Limits DexLimits `json:"limits"` } type Gas struct { @@ -56,4 +69,5 @@ type SwapInfo struct { HasNative bool `json:"hasNative"` NewCollateralReserves CollateralReserves `json:"-"` NewDebtReserves DebtReserves `json:"-"` + NewDexLimits DexLimits `json:"-"` }