From 7fc495b290fb3ae797b33702eee89a1c8f7e93be Mon Sep 17 00:00:00 2001 From: sameepsi Date: Fri, 21 Jul 2023 14:39:04 +0530 Subject: [PATCH 01/45] Config for QuickPerps on Polygon zkevm --- src/dex/gmx/config.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/dex/gmx/config.ts b/src/dex/gmx/config.ts index 5ae6f1a9d..83f115d6c 100644 --- a/src/dex/gmx/config.ts +++ b/src/dex/gmx/config.ts @@ -21,6 +21,16 @@ export const GMXConfig: DexConfigMap = { usdg: '0x45096e7aA921f27590f8F19e457794EB09678141', }, }, + QuickPerps: { + [Network.ZKEVM]: { + vault: '0x99B31498B0a1Dae01fc3433e3Cb60F095340935C', + reader: '0xf1CFB75854DE535475B88Bb6FBad317eea98c0F9', + priceFeed: '0x5b1F500134bdD7f4359F5B2adC65f839737290f4', + fastPriceFeed: '0x73903fEc691a80Ec47bc830bf3F0baD127A06e30', + fastPriceEvents: '0x08bC8ef0b71238055f9Ee6BBc90869D8d0DBdCCa', + usdg: '0x48aC594dd00c4aAcF40f83337fc6dA31F9F439A7', + }, + }, }; export const Adapters: { @@ -44,4 +54,12 @@ export const Adapters: { }, ], }, + [Network.ZKEVM]: { + [SwapSide.SELL]: [ + { + name: 'PolygonZkEvmAdapter01', + index: 2, + }, + ], + }, }; From c2e6b41333effaf7ca9f97213a0695131e1f707d Mon Sep 17 00:00:00 2001 From: sameepsi Date: Fri, 21 Jul 2023 14:54:09 +0530 Subject: [PATCH 02/45] Updated config for QuickPerps --- src/dex/gmx/gmx.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dex/gmx/gmx.ts b/src/dex/gmx/gmx.ts index 2372dbea8..71ea31083 100644 --- a/src/dex/gmx/gmx.ts +++ b/src/dex/gmx/gmx.ts @@ -1,4 +1,5 @@ import { Interface } from '@ethersproject/abi'; +import _ from 'lodash'; import { Token, Address, @@ -34,7 +35,7 @@ export class GMX extends SimpleExchange implements IDex { readonly isFeeOnTransferSupported = false; public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = - getDexKeysWithNetwork(GMXConfig); + getDexKeysWithNetwork(_.pick(GMXConfig, ['GMX', 'QuickPerps'])); public static erc20Interface = new Interface(ERC20ABI); From e47d805485d0ca35dd97e58d398fdffd6cf0d9d3 Mon Sep 17 00:00:00 2001 From: sameepsi Date: Fri, 21 Jul 2023 15:59:19 +0530 Subject: [PATCH 03/45] Added new impl for QuickPerps --- src/abi/quickPerps/fast-price-events.json | 70 + src/abi/quickPerps/fast-price-feed.json | 748 +++++++ src/abi/quickPerps/reader.json | 313 +++ src/abi/quickPerps/vault-price-feed.json | 477 ++++ src/abi/quickPerps/vault.json | 1953 +++++++++++++++++ src/dex/QuickPerps/config.ts | 31 + src/dex/QuickPerps/fast-price-feed.ts | 279 +++ src/dex/QuickPerps/pool.ts | 360 +++ src/dex/QuickPerps/quickPerps-e2e.test.ts | 166 ++ src/dex/QuickPerps/quickPerps-events.test.ts | 115 + .../QuickPerps/quickPerps-integration.test.ts | 117 + src/dex/QuickPerps/quickPerps.ts | 296 +++ src/dex/QuickPerps/types.ts | 94 + src/dex/QuickPerps/usdq.ts | 83 + src/dex/QuickPerps/vault-price-feed.ts | 384 ++++ src/dex/QuickPerps/vault-utils.ts | 53 + src/dex/QuickPerps/vault.ts | 290 +++ src/dex/gmx/config.ts | 18 - src/dex/gmx/gmx.ts | 3 +- 19 files changed, 5830 insertions(+), 20 deletions(-) create mode 100644 src/abi/quickPerps/fast-price-events.json create mode 100644 src/abi/quickPerps/fast-price-feed.json create mode 100644 src/abi/quickPerps/reader.json create mode 100644 src/abi/quickPerps/vault-price-feed.json create mode 100644 src/abi/quickPerps/vault.json create mode 100644 src/dex/QuickPerps/config.ts create mode 100644 src/dex/QuickPerps/fast-price-feed.ts create mode 100644 src/dex/QuickPerps/pool.ts create mode 100644 src/dex/QuickPerps/quickPerps-e2e.test.ts create mode 100644 src/dex/QuickPerps/quickPerps-events.test.ts create mode 100644 src/dex/QuickPerps/quickPerps-integration.test.ts create mode 100644 src/dex/QuickPerps/quickPerps.ts create mode 100644 src/dex/QuickPerps/types.ts create mode 100644 src/dex/QuickPerps/usdq.ts create mode 100644 src/dex/QuickPerps/vault-price-feed.ts create mode 100644 src/dex/QuickPerps/vault-utils.ts create mode 100644 src/dex/QuickPerps/vault.ts diff --git a/src/abi/quickPerps/fast-price-events.json b/src/abi/quickPerps/fast-price-events.json new file mode 100644 index 000000000..f9ff530fe --- /dev/null +++ b/src/abi/quickPerps/fast-price-events.json @@ -0,0 +1,70 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "priceFeed", + "type": "address" + } + ], + "name": "PriceUpdate", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_price", "type": "uint256" } + ], + "name": "emitPriceEvent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "gov", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "isPriceFeed", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_gov", "type": "address" } + ], + "name": "setGov", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_priceFeed", "type": "address" }, + { "internalType": "bool", "name": "_isPriceFeed", "type": "bool" } + ], + "name": "setIsPriceFeed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/quickPerps/fast-price-feed.json b/src/abi/quickPerps/fast-price-feed.json new file mode 100644 index 000000000..502cec0ed --- /dev/null +++ b/src/abi/quickPerps/fast-price-feed.json @@ -0,0 +1,748 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_priceDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxPriceUpdateDelay", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_minBlockInterval", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxDeviationBasisPoints", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_fastPriceEvents", + "type": "address" + }, + { "internalType": "address", "name": "_tokenManager", "type": "address" }, + { + "internalType": "address", + "name": "_positionRouter", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "DisableFastPrice", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "EnableFastPrice", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "refPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fastPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cumulativeRefDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cumulativeFastDelta", + "type": "uint256" + } + ], + "name": "MaxCumulativeDeltaDiffExceeded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "refPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fastPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cumulativeRefDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cumulativeFastDelta", + "type": "uint256" + } + ], + "name": "PriceData", + "type": "event" + }, + { + "inputs": [], + "name": "BASIS_POINTS_DIVISOR", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "BITMASK_32", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CUMULATIVE_DELTA_PRECISION", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_CUMULATIVE_FAST_DELTA", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_CUMULATIVE_REF_DELTA", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_PRICE_DURATION", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_REF_PRICE", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PRICE_PRECISION", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "disableFastPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "disableFastPriceVoteCount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "disableFastPriceVotes", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "enableFastPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "fastPriceEvents", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "favorFastPrice", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_refPrice", "type": "uint256" }, + { "internalType": "bool", "name": "_maximise", "type": "bool" } + ], + "name": "getPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "getPriceData", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gov", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_minAuthorizations", + "type": "uint256" + }, + { "internalType": "address[]", "name": "_signers", "type": "address[]" }, + { "internalType": "address[]", "name": "_updaters", "type": "address[]" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isInitialized", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "isSigner", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isSpreadEnabled", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "isUpdater", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastUpdatedAt", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastUpdatedBlock", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "maxCumulativeDeltaDiffs", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxDeviationBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxPriceUpdateDelay", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxTimeDeviation", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minAuthorizations", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minBlockInterval", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "positionRouter", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "priceData", + "outputs": [ + { "internalType": "uint160", "name": "refPrice", "type": "uint160" }, + { "internalType": "uint32", "name": "refTime", "type": "uint32" }, + { + "internalType": "uint32", + "name": "cumulativeRefDelta", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "cumulativeFastDelta", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "priceDataInterval", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "priceDuration", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "prices", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "_priceBitArray", + "type": "uint256[]" + }, + { "internalType": "uint256", "name": "_timestamp", "type": "uint256" } + ], + "name": "setCompactedPrices", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_fastPriceEvents", + "type": "address" + } + ], + "name": "setFastPriceEvents", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_gov", "type": "address" } + ], + "name": "setGov", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "_isSpreadEnabled", "type": "bool" } + ], + "name": "setIsSpreadEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_lastUpdatedAt", "type": "uint256" } + ], + "name": "setLastUpdatedAt", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address[]", "name": "_tokens", "type": "address[]" }, + { + "internalType": "uint256[]", + "name": "_maxCumulativeDeltaDiffs", + "type": "uint256[]" + } + ], + "name": "setMaxCumulativeDeltaDiffs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maxDeviationBasisPoints", + "type": "uint256" + } + ], + "name": "setMaxDeviationBasisPoints", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maxPriceUpdateDelay", + "type": "uint256" + } + ], + "name": "setMaxPriceUpdateDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maxTimeDeviation", + "type": "uint256" + } + ], + "name": "setMaxTimeDeviation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_minAuthorizations", + "type": "uint256" + } + ], + "name": "setMinAuthorizations", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_minBlockInterval", + "type": "uint256" + } + ], + "name": "setMinBlockInterval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_priceDataInterval", + "type": "uint256" + } + ], + "name": "setPriceDataInterval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_priceDuration", "type": "uint256" } + ], + "name": "setPriceDuration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address[]", "name": "_tokens", "type": "address[]" }, + { "internalType": "uint256[]", "name": "_prices", "type": "uint256[]" }, + { "internalType": "uint256", "name": "_timestamp", "type": "uint256" } + ], + "name": "setPrices", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_priceBits", "type": "uint256" }, + { "internalType": "uint256", "name": "_timestamp", "type": "uint256" } + ], + "name": "setPricesWithBits", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_priceBits", "type": "uint256" }, + { "internalType": "uint256", "name": "_timestamp", "type": "uint256" }, + { + "internalType": "uint256", + "name": "_endIndexForIncreasePositions", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_endIndexForDecreasePositions", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxIncreasePositions", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxDecreasePositions", + "type": "uint256" + } + ], + "name": "setPricesWithBitsAndExecute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { "internalType": "bool", "name": "_isActive", "type": "bool" } + ], + "name": "setSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_spreadBasisPointsIfChainError", + "type": "uint256" + } + ], + "name": "setSpreadBasisPointsIfChainError", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_spreadBasisPointsIfInactive", + "type": "uint256" + } + ], + "name": "setSpreadBasisPointsIfInactive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_tokenManager", "type": "address" } + ], + "name": "setTokenManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address[]", "name": "_tokens", "type": "address[]" }, + { + "internalType": "uint256[]", + "name": "_tokenPrecisions", + "type": "uint256[]" + } + ], + "name": "setTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { "internalType": "bool", "name": "_isActive", "type": "bool" } + ], + "name": "setUpdater", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vaultPriceFeed", + "type": "address" + } + ], + "name": "setVaultPriceFeed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "spreadBasisPointsIfChainError", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "spreadBasisPointsIfInactive", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenManager", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "tokenPrecisions", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "tokens", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultPriceFeed", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/quickPerps/reader.json b/src/abi/quickPerps/reader.json new file mode 100644 index 000000000..6e851476a --- /dev/null +++ b/src/abi/quickPerps/reader.json @@ -0,0 +1,313 @@ +[ + { + "inputs": [], + "name": "BASIS_POINTS_DIVISOR", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "POSITION_PROPS_LENGTH", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PRICE_PRECISION", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "USDQ_DECIMALS", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IVault", + "name": "_vault", + "type": "address" + }, + { "internalType": "address", "name": "_tokenIn", "type": "address" }, + { "internalType": "address", "name": "_tokenOut", "type": "address" }, + { "internalType": "uint256", "name": "_amountIn", "type": "uint256" } + ], + "name": "getAmountOut", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IVault", + "name": "_vault", + "type": "address" + }, + { "internalType": "address", "name": "_tokenIn", "type": "address" }, + { "internalType": "address", "name": "_tokenOut", "type": "address" }, + { "internalType": "uint256", "name": "_amountIn", "type": "uint256" } + ], + "name": "getFeeBasisPoints", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_vault", "type": "address" }, + { "internalType": "address[]", "name": "_tokens", "type": "address[]" } + ], + "name": "getFees", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_vault", "type": "address" }, + { "internalType": "address", "name": "_weth", "type": "address" }, + { "internalType": "uint256", "name": "_usdqAmount", "type": "uint256" }, + { "internalType": "address[]", "name": "_tokens", "type": "address[]" } + ], + "name": "getFullVaultTokenInfo", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_vault", "type": "address" }, + { "internalType": "address", "name": "_weth", "type": "address" }, + { "internalType": "address[]", "name": "_tokens", "type": "address[]" } + ], + "name": "getFundingRates", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IVault", + "name": "_vault", + "type": "address" + }, + { "internalType": "address", "name": "_tokenIn", "type": "address" }, + { "internalType": "address", "name": "_tokenOut", "type": "address" } + ], + "name": "getMaxAmountIn", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_vault", "type": "address" }, + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address[]", + "name": "_collateralTokens", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "_indexTokens", + "type": "address[]" + }, + { "internalType": "bool[]", "name": "_isLong", "type": "bool[]" } + ], + "name": "getPositions", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IVaultPriceFeed", + "name": "_priceFeed", + "type": "address" + }, + { "internalType": "address[]", "name": "_tokens", "type": "address[]" } + ], + "name": "getPrices", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address[]", + "name": "_yieldTrackers", + "type": "address[]" + } + ], + "name": "getStakingInfo", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { "internalType": "address[]", "name": "_tokens", "type": "address[]" } + ], + "name": "getTokenBalances", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { "internalType": "address[]", "name": "_tokens", "type": "address[]" } + ], + "name": "getTokenBalancesWithSupplies", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_token", + "type": "address" + }, + { + "internalType": "address[]", + "name": "_excludedAccounts", + "type": "address[]" + } + ], + "name": "getTokenSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_token", + "type": "address" + }, + { "internalType": "address[]", "name": "_accounts", "type": "address[]" } + ], + "name": "getTotalBalance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_yieldTokens", + "type": "address[]" + } + ], + "name": "getTotalStaked", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_vault", "type": "address" }, + { "internalType": "address", "name": "_weth", "type": "address" }, + { "internalType": "uint256", "name": "_usdqAmount", "type": "uint256" }, + { "internalType": "address[]", "name": "_tokens", "type": "address[]" } + ], + "name": "getVaultTokenInfo", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_vault", "type": "address" }, + { "internalType": "address", "name": "_weth", "type": "address" }, + { "internalType": "uint256", "name": "_usdqAmount", "type": "uint256" }, + { "internalType": "address[]", "name": "_tokens", "type": "address[]" } + ], + "name": "getVaultTokenInfoV2", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gov", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hasMaxGlobalShortSizes", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_hasMaxGlobalShortSizes", + "type": "bool" + } + ], + "name": "setConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_gov", "type": "address" } + ], + "name": "setGov", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/quickPerps/vault-price-feed.json b/src/abi/quickPerps/vault-price-feed.json new file mode 100644 index 000000000..d917d96cf --- /dev/null +++ b/src/abi/quickPerps/vault-price-feed.json @@ -0,0 +1,477 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "BASIS_POINTS_DIVISOR", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_ADJUSTMENT_BASIS_POINTS", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_ADJUSTMENT_INTERVAL", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_SPREAD_BASIS_POINTS", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ONE_USD", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PRICE_PRECISION", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "adjustmentBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bnb", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bnbBusd", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "btc", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "btcBnb", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "chainlinkFlags", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eth", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ethBnb", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "favorPrimaryPrice", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "getAmmPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "bool", "name": "_maximise", "type": "bool" }, + { "internalType": "uint256", "name": "_primaryPrice", "type": "uint256" } + ], + "name": "getAmmPriceV2", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "getLatestPrimaryPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_pair", "type": "address" }, + { "internalType": "bool", "name": "_divByReserve0", "type": "bool" } + ], + "name": "getPairPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "bool", "name": "_maximise", "type": "bool" }, + { "internalType": "bool", "name": "_includeAmmPrice", "type": "bool" }, + { "internalType": "bool", "name": "", "type": "bool" } + ], + "name": "getPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "bool", "name": "_maximise", "type": "bool" }, + { "internalType": "bool", "name": "_includeAmmPrice", "type": "bool" } + ], + "name": "getPriceV1", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "bool", "name": "_maximise", "type": "bool" }, + { "internalType": "bool", "name": "_includeAmmPrice", "type": "bool" } + ], + "name": "getPriceV2", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "bool", "name": "_maximise", "type": "bool" } + ], + "name": "getPrimaryPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { + "internalType": "uint256", + "name": "_referencePrice", + "type": "uint256" + }, + { "internalType": "bool", "name": "_maximise", "type": "bool" } + ], + "name": "getSecondaryPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gov", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "isAdjustmentAdditive", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isAmmEnabled", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isSecondaryPriceEnabled", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "lastAdjustmentTimings", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxStrictPriceDeviation", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "priceDecimals", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "priceFeeds", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "priceSampleSpace", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "secondaryPriceFeed", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "bool", "name": "_isAdditive", "type": "bool" }, + { "internalType": "uint256", "name": "_adjustmentBps", "type": "uint256" } + ], + "name": "setAdjustment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_chainlinkFlags", + "type": "address" + } + ], + "name": "setChainlinkFlags", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "_favorPrimaryPrice", "type": "bool" } + ], + "name": "setFavorPrimaryPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_gov", "type": "address" } + ], + "name": "setGov", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "_isEnabled", "type": "bool" } + ], + "name": "setIsAmmEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "_isEnabled", "type": "bool" } + ], + "name": "setIsSecondaryPriceEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maxStrictPriceDeviation", + "type": "uint256" + } + ], + "name": "setMaxStrictPriceDeviation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_bnbBusd", "type": "address" }, + { "internalType": "address", "name": "_ethBnb", "type": "address" }, + { "internalType": "address", "name": "_btcBnb", "type": "address" } + ], + "name": "setPairs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_priceSampleSpace", + "type": "uint256" + } + ], + "name": "setPriceSampleSpace", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_secondaryPriceFeed", + "type": "address" + } + ], + "name": "setSecondaryPriceFeed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { + "internalType": "uint256", + "name": "_spreadBasisPoints", + "type": "uint256" + } + ], + "name": "setSpreadBasisPoints", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_spreadThresholdBasisPoints", + "type": "uint256" + } + ], + "name": "setSpreadThresholdBasisPoints", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "address", "name": "_priceFeed", "type": "address" }, + { + "internalType": "uint256", + "name": "_priceDecimals", + "type": "uint256" + }, + { "internalType": "bool", "name": "_isStrictStable", "type": "bool" } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_btc", "type": "address" }, + { "internalType": "address", "name": "_eth", "type": "address" }, + { "internalType": "address", "name": "_bnb", "type": "address" } + ], + "name": "setTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "_useV2Pricing", "type": "bool" } + ], + "name": "setUseV2Pricing", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "spreadBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "spreadThresholdBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "strictStableTokens", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "useV2Pricing", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/quickPerps/vault.json b/src/abi/quickPerps/vault.json new file mode 100644 index 000000000..5b770bfd8 --- /dev/null +++ b/src/abi/quickPerps/vault.json @@ -0,0 +1,1953 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "usdqAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeBasisPoints", + "type": "uint256" + } + ], + "name": "BuyUSDQ", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "key", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "size", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "averagePrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "entryFundingRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserveAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "realisedPnl", + "type": "int256" + } + ], + "name": "ClosePosition", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeUsd", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeTokens", + "type": "uint256" + } + ], + "name": "CollectMarginFees", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeUsd", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeTokens", + "type": "uint256" + } + ], + "name": "CollectSwapFees", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DecreaseGuaranteedUsd", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DecreasePoolAmount", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "key", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "indexToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sizeDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isLong", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "DecreasePosition", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DecreaseReservedAmount", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DecreaseUsdqAmount", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DirectPoolDeposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "IncreaseGuaranteedUsd", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "IncreasePoolAmount", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "key", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "indexToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sizeDelta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isLong", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "IncreasePosition", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "IncreaseReservedAmount", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "IncreaseUsdqAmount", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "key", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "collateralToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "indexToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isLong", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "size", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserveAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "realisedPnl", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "markPrice", + "type": "uint256" + } + ], + "name": "LiquidatePosition", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "usdqAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeBasisPoints", + "type": "uint256" + } + ], + "name": "SellUSDQ", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOutAfterFees", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeBasisPoints", + "type": "uint256" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fundingRate", + "type": "uint256" + } + ], + "name": "UpdateFundingRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "key", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bool", + "name": "hasProfit", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "delta", + "type": "uint256" + } + ], + "name": "UpdatePnl", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "key", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "size", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateral", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "averagePrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "entryFundingRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserveAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "realisedPnl", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "markPrice", + "type": "uint256" + } + ], + "name": "UpdatePosition", + "type": "event" + }, + { + "inputs": [], + "name": "BASIS_POINTS_DIVISOR", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "FUNDING_RATE_PRECISION", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_FEE_BASIS_POINTS", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_FUNDING_RATE_FACTOR", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_LIQUIDATION_FEE_USD", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_FUNDING_RATE_INTERVAL", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_LEVERAGE", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PRICE_PRECISION", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "USDQ_DECIMALS", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_router", "type": "address" } + ], + "name": "addRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "address", "name": "_tokenDiv", "type": "address" }, + { "internalType": "address", "name": "_tokenMul", "type": "address" } + ], + "name": "adjustForDecimals", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "allWhitelistedTokens", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allWhitelistedTokensLength", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "approvedRouters", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "bufferAmounts", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "address", "name": "_receiver", "type": "address" } + ], + "name": "buyUSDQ", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "clearTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "cumulativeFundingRates", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { + "internalType": "uint256", + "name": "_collateralDelta", + "type": "uint256" + }, + { "internalType": "uint256", "name": "_sizeDelta", "type": "uint256" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" }, + { "internalType": "address", "name": "_receiver", "type": "address" } + ], + "name": "decreasePosition", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "directPoolDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "errorController", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "errors", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "feeReserves", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fundingInterval", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fundingRateFactor", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "uint256", "name": "_size", "type": "uint256" }, + { "internalType": "uint256", "name": "_averagePrice", "type": "uint256" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" }, + { + "internalType": "uint256", + "name": "_lastIncreasedTime", + "type": "uint256" + } + ], + "name": "getDelta", + "outputs": [ + { "internalType": "bool", "name": "", "type": "bool" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" } + ], + "name": "getEntryFundingRate", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_usdqDelta", "type": "uint256" }, + { + "internalType": "uint256", + "name": "_feeBasisPoints", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_taxBasisPoints", + "type": "uint256" + }, + { "internalType": "bool", "name": "_increment", "type": "bool" } + ], + "name": "getFeeBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" }, + { "internalType": "uint256", "name": "_size", "type": "uint256" }, + { + "internalType": "uint256", + "name": "_entryFundingRate", + "type": "uint256" + } + ], + "name": "getFundingFee", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "getGlobalShortDelta", + "outputs": [ + { "internalType": "bool", "name": "", "type": "bool" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "getMaxPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "getMinPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "uint256", "name": "_size", "type": "uint256" }, + { "internalType": "uint256", "name": "_averagePrice", "type": "uint256" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" }, + { "internalType": "uint256", "name": "_nextPrice", "type": "uint256" }, + { "internalType": "uint256", "name": "_sizeDelta", "type": "uint256" }, + { + "internalType": "uint256", + "name": "_lastIncreasedTime", + "type": "uint256" + } + ], + "name": "getNextAveragePrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "getNextFundingRate", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "uint256", "name": "_nextPrice", "type": "uint256" }, + { "internalType": "uint256", "name": "_sizeDelta", "type": "uint256" } + ], + "name": "getNextGlobalShortAveragePrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" } + ], + "name": "getPosition", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "bool", "name": "", "type": "bool" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" } + ], + "name": "getPositionDelta", + "outputs": [ + { "internalType": "bool", "name": "", "type": "bool" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" }, + { "internalType": "uint256", "name": "_sizeDelta", "type": "uint256" } + ], + "name": "getPositionFee", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" } + ], + "name": "getPositionKey", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" } + ], + "name": "getPositionLeverage", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_usdqAmount", "type": "uint256" } + ], + "name": "getRedemptionAmount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "getRedemptionCollateral", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "getRedemptionCollateralUsd", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "getTargetUsdqAmount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "getUtilisation", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "globalShortAveragePrices", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "globalShortSizes", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gov", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "guaranteedUsd", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hasDynamicFees", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "inManagerMode", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "inPrivateLiquidationMode", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "includeAmmPrice", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "uint256", "name": "_sizeDelta", "type": "uint256" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" } + ], + "name": "increasePosition", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_router", "type": "address" }, + { "internalType": "address", "name": "_usdq", "type": "address" }, + { "internalType": "address", "name": "_priceFeed", "type": "address" }, + { + "internalType": "uint256", + "name": "_liquidationFeeUsd", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_fundingRateFactor", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_stableFundingRateFactor", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isInitialized", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isLeverageEnabled", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "isLiquidator", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "isManager", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isSwapEnabled", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "lastFundingTimes", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" }, + { "internalType": "address", "name": "_feeReceiver", "type": "address" } + ], + "name": "liquidatePosition", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidationFeeUsd", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "marginFeeBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxGasPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "maxGlobalShortSizes", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLeverage", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "maxUsdqAmounts", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "minProfitBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minProfitTime", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "mintBurnFeeBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "poolAmounts", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "positions", + "outputs": [ + { "internalType": "uint256", "name": "size", "type": "uint256" }, + { "internalType": "uint256", "name": "collateral", "type": "uint256" }, + { "internalType": "uint256", "name": "averagePrice", "type": "uint256" }, + { + "internalType": "uint256", + "name": "entryFundingRate", + "type": "uint256" + }, + { "internalType": "uint256", "name": "reserveAmount", "type": "uint256" }, + { "internalType": "int256", "name": "realisedPnl", "type": "int256" }, + { + "internalType": "uint256", + "name": "lastIncreasedTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "priceFeed", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_router", "type": "address" } + ], + "name": "removeRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "reservedAmounts", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "router", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "address", "name": "_receiver", "type": "address" } + ], + "name": "sellUSDQ", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "setBufferAmount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_errorCode", "type": "uint256" }, + { "internalType": "string", "name": "_error", "type": "string" } + ], + "name": "setError", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_errorController", + "type": "address" + } + ], + "name": "setErrorController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_taxBasisPoints", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_stableTaxBasisPoints", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_mintBurnFeeBasisPoints", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_swapFeeBasisPoints", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_stableSwapFeeBasisPoints", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_marginFeeBasisPoints", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_liquidationFeeUsd", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_minProfitTime", + "type": "uint256" + }, + { "internalType": "bool", "name": "_hasDynamicFees", "type": "bool" } + ], + "name": "setFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_fundingInterval", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_fundingRateFactor", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_stableFundingRateFactor", + "type": "uint256" + } + ], + "name": "setFundingRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_gov", "type": "address" } + ], + "name": "setGov", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "_inManagerMode", "type": "bool" } + ], + "name": "setInManagerMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_inPrivateLiquidationMode", + "type": "bool" + } + ], + "name": "setInPrivateLiquidationMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "_isLeverageEnabled", "type": "bool" } + ], + "name": "setIsLeverageEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "_isSwapEnabled", "type": "bool" } + ], + "name": "setIsSwapEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_liquidator", "type": "address" }, + { "internalType": "bool", "name": "_isActive", "type": "bool" } + ], + "name": "setLiquidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_manager", "type": "address" }, + { "internalType": "bool", "name": "_isManager", "type": "bool" } + ], + "name": "setManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_maxGasPrice", "type": "uint256" } + ], + "name": "setMaxGasPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "setMaxGlobalShortSize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_maxLeverage", "type": "uint256" } + ], + "name": "setMaxLeverage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_priceFeed", "type": "address" } + ], + "name": "setPriceFeed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { + "internalType": "uint256", + "name": "_tokenDecimals", + "type": "uint256" + }, + { "internalType": "uint256", "name": "_tokenWeight", "type": "uint256" }, + { "internalType": "uint256", "name": "_minProfitBps", "type": "uint256" }, + { + "internalType": "uint256", + "name": "_maxUsdqAmount", + "type": "uint256" + }, + { "internalType": "bool", "name": "_isStable", "type": "bool" }, + { "internalType": "bool", "name": "_isShortable", "type": "bool" } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "setUsdqAmount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IVaultUtils", + "name": "_vaultUtils", + "type": "address" + } + ], + "name": "setVaultUtils", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "shortableTokens", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stableFundingRateFactor", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stableSwapFeeBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stableTaxBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "stableTokens", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_tokenIn", "type": "address" }, + { "internalType": "address", "name": "_tokenOut", "type": "address" }, + { "internalType": "address", "name": "_receiver", "type": "address" } + ], + "name": "swap", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "swapFeeBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "taxBasisPoints", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "tokenBalances", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "tokenDecimals", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_tokenAmount", "type": "uint256" } + ], + "name": "tokenToUsdMin", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "tokenWeights", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalTokenWeights", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" } + ], + "name": "updateCumulativeFundingRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_newVault", "type": "address" }, + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "upgradeVault", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_usdAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "_price", "type": "uint256" } + ], + "name": "usdToToken", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_usdAmount", "type": "uint256" } + ], + "name": "usdToTokenMax", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_usdAmount", "type": "uint256" } + ], + "name": "usdToTokenMin", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "usdq", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "usdqAmounts", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "useSwapPricing", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_account", "type": "address" }, + { + "internalType": "address", + "name": "_collateralToken", + "type": "address" + }, + { "internalType": "address", "name": "_indexToken", "type": "address" }, + { "internalType": "bool", "name": "_isLong", "type": "bool" }, + { "internalType": "bool", "name": "_raise", "type": "bool" } + ], + "name": "validateLiquidation", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultUtils", + "outputs": [ + { "internalType": "contract IVaultUtils", "name": "", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "whitelistedTokenCount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "whitelistedTokens", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "address", "name": "_receiver", "type": "address" } + ], + "name": "withdrawFees", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/dex/QuickPerps/config.ts b/src/dex/QuickPerps/config.ts new file mode 100644 index 000000000..1f44ef1bf --- /dev/null +++ b/src/dex/QuickPerps/config.ts @@ -0,0 +1,31 @@ +import { DexParams } from './types'; +import { DexConfigMap } from '../../types'; +import { Network, SwapSide } from '../../constants'; + +export const QuickPerpsConfig: DexConfigMap = { + QuickPerps: { + [Network.ZKEVM]: { + vault: '0x99B31498B0a1Dae01fc3433e3Cb60F095340935C', + reader: '0xf1CFB75854DE535475B88Bb6FBad317eea98c0F9', + priceFeed: '0x5b1F500134bdD7f4359F5B2adC65f839737290f4', + fastPriceFeed: '0x73903fEc691a80Ec47bc830bf3F0baD127A06e30', + fastPriceEvents: '0x08bC8ef0b71238055f9Ee6BBc90869D8d0DBdCCa', + usdq: '0x48aC594dd00c4aAcF40f83337fc6dA31F9F439A7', + }, + }, +}; + +export const Adapters: { + [chainId: number]: { + [side: string]: { name: string; index: number }[] | null; + }; +} = { + [Network.ZKEVM]: { + [SwapSide.SELL]: [ + { + name: 'PolygonZkEvmAdapter01', + index: 2, + }, + ], + }, +}; diff --git a/src/dex/QuickPerps/fast-price-feed.ts b/src/dex/QuickPerps/fast-price-feed.ts new file mode 100644 index 000000000..7140ddf70 --- /dev/null +++ b/src/dex/QuickPerps/fast-price-feed.ts @@ -0,0 +1,279 @@ +import _ from 'lodash'; +import { Interface } from '@ethersproject/abi'; +import { DeepReadonly } from 'ts-essentials'; +import { PartialEventSubscriber } from '../../composed-event-subscriber'; +import { + Address, + MultiCallInput, + MultiCallOutput, + Logger, + Log, + BlockHeader, +} from '../../types'; +import { FastPriceFeedConfig, FastPriceFeedState } from './types'; +import FastPriceFeedAbi from '../../abi/quickPerps/fast-price-feed.json'; +import FastPriceEventsAbi from '../../abi/quickPerps/fast-price-events.json'; +import { Lens } from '../../lens'; + +export class FastPriceFeed extends PartialEventSubscriber< + State, + FastPriceFeedState +> { + static readonly interface = new Interface(FastPriceFeedAbi); + static readonly fastPriceEventsInterface = new Interface(FastPriceEventsAbi); + + BASIS_POINTS_DIVISOR = 10000n; + protected priceDuration: number; + protected maxDeviationBasisPoints: bigint; + protected favorFastPrice: Record; + private spreadBasisPointsIfInactive: bigint; + private spreadBasisPointsIfChainError: bigint; + private maxPriceUpdateDelay: number; + + constructor( + private fastPriceFeedAddress: Address, + fastPriceEventsAddress: Address, + private tokenAddresses: Address[], + config: FastPriceFeedConfig, + lens: Lens, DeepReadonly>, + logger: Logger, + ) { + super([fastPriceEventsAddress], lens, logger); + this.priceDuration = config.priceDuration; + this.maxDeviationBasisPoints = config.maxDeviationBasisPoints; + this.favorFastPrice = config.favorFastPrice; + this.spreadBasisPointsIfInactive = config.spreadBasisPointsIfInactive; + this.spreadBasisPointsIfChainError = config.spreadBasisPointsIfChainError; + this.maxPriceUpdateDelay = config.maxPriceUpdateDelay; + } + + public processLog( + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ): DeepReadonly | null { + try { + const parsed = FastPriceFeed.fastPriceEventsInterface.parseLog(log); + switch (parsed.name) { + case 'PriceUpdate': { + const _state: FastPriceFeedState = _.cloneDeep(state); + _state.lastUpdatedAt = + typeof blockHeader.timestamp === 'string' + ? parseInt(blockHeader.timestamp) + : blockHeader.timestamp; + const tokenAddress = parsed.args.token.toLowerCase(); + if (tokenAddress in state.prices) + _state.prices[tokenAddress] = BigInt(parsed.args.price.toString()); + return _state; + } + default: + return null; + } + } catch (e) { + this.logger.error('Failed to parse log', e); + return null; + } + } + + getPrice( + _state: DeepReadonly, + _token: Address, + _refPrice: bigint, + _maximise: boolean, + ) { + const state = this.lens.get()(_state); + + const timestamp = Math.floor(Date.now() / 1000); + + if (timestamp > state.lastUpdatedAt + this.maxPriceUpdateDelay) { + if (_maximise) { + return ( + (_refPrice * + (this.BASIS_POINTS_DIVISOR + this.spreadBasisPointsIfChainError)) / + this.BASIS_POINTS_DIVISOR + ); + } + + return ( + (_refPrice * + (this.BASIS_POINTS_DIVISOR - this.spreadBasisPointsIfChainError)) / + this.BASIS_POINTS_DIVISOR + ); + } + + if (timestamp > state.lastUpdatedAt + this.priceDuration) { + if (_maximise) { + return ( + (_refPrice * + (this.BASIS_POINTS_DIVISOR + this.spreadBasisPointsIfInactive)) / + this.BASIS_POINTS_DIVISOR + ); + } + + return ( + (_refPrice * + (this.BASIS_POINTS_DIVISOR - this.spreadBasisPointsIfInactive)) / + this.BASIS_POINTS_DIVISOR + ); + } + + const fastPrice = state.prices[_token]; + if (fastPrice === 0n) return _refPrice; + + let diffBasisPoints = + _refPrice > fastPrice ? _refPrice - fastPrice : fastPrice - _refPrice; + diffBasisPoints = (diffBasisPoints * this.BASIS_POINTS_DIVISOR) / _refPrice; + + // create a spread between the _refPrice and the fastPrice if the maxDeviationBasisPoints is exceeded + // or if watchers have flagged an issue with the fast price + const hasSpread = + !this.favorFastPrice[_token] || + diffBasisPoints > this.maxDeviationBasisPoints; + + if (hasSpread) { + // return the higher of the two prices + if (_maximise) { + return _refPrice > fastPrice ? _refPrice : fastPrice; + } + + // return the lower of the two prices + return _refPrice < fastPrice ? _refPrice : fastPrice; + } + + return fastPrice; + } + + static getConfigMulticallInputs( + fastPriceFeedAddress: Address, + tokenAddresses: Address[], + ): MultiCallInput[] { + return [ + { + target: fastPriceFeedAddress, + callData: FastPriceFeed.interface.encodeFunctionData('priceDuration'), + }, + { + target: fastPriceFeedAddress, + callData: FastPriceFeed.interface.encodeFunctionData( + 'maxDeviationBasisPoints', + ), + }, + { + target: fastPriceFeedAddress, + callData: FastPriceFeed.interface.encodeFunctionData( + 'spreadBasisPointsIfInactive', + ), + }, + { + target: fastPriceFeedAddress, + callData: FastPriceFeed.interface.encodeFunctionData( + 'spreadBasisPointsIfChainError', + ), + }, + { + target: fastPriceFeedAddress, + callData: FastPriceFeed.interface.encodeFunctionData( + 'maxPriceUpdateDelay', + ), + }, + ...tokenAddresses.map(t => ({ + target: fastPriceFeedAddress, + callData: FastPriceFeed.interface.encodeFunctionData('favorFastPrice', [ + t, + ]), + })), + ]; + } + + static getConfig( + multicallOutputs: MultiCallOutput[], + tokenAddresses: Address[], + ): FastPriceFeedConfig { + return { + priceDuration: parseInt( + FastPriceFeed.interface + .decodeFunctionResult('priceDuration', multicallOutputs[0])[0] + .toString(), + ), + maxDeviationBasisPoints: BigInt( + FastPriceFeed.interface + .decodeFunctionResult( + 'maxDeviationBasisPoints', + multicallOutputs[1], + )[0] + .toString(), + ), + spreadBasisPointsIfInactive: BigInt( + FastPriceFeed.interface + .decodeFunctionResult( + 'spreadBasisPointsIfInactive', + multicallOutputs[2], + )[0] + .toString(), + ), + spreadBasisPointsIfChainError: BigInt( + FastPriceFeed.interface + .decodeFunctionResult( + 'spreadBasisPointsIfChainError', + multicallOutputs[3], + )[0] + .toString(), + ), + maxPriceUpdateDelay: parseInt( + FastPriceFeed.interface + .decodeFunctionResult('maxPriceUpdateDelay', multicallOutputs[4])[0] + .toString(), + ), + favorFastPrice: multicallOutputs + .slice(5) + .reduce>((acc, curr, i) => { + acc[tokenAddresses[i]] = FastPriceFeed.interface.decodeFunctionResult( + 'favorFastPrice', + curr, + )[0]; + return acc; + }, {}), + }; + } + + public getGenerateStateMultiCallInputs(): MultiCallInput[] { + const pricesEntries = this.tokenAddresses.map((t: Address) => ({ + target: this.fastPriceFeedAddress, + callData: FastPriceFeed.interface.encodeFunctionData('prices', [t]), + })); + return [ + ...pricesEntries, + { + target: this.fastPriceFeedAddress, + callData: FastPriceFeed.interface.encodeFunctionData('lastUpdatedAt'), + }, + ]; + } + + public generateState( + multicallOutputs: MultiCallOutput[], + blockNumber?: number | 'latest', + ): DeepReadonly { + let fastPriceFeedState: FastPriceFeedState = { + prices: {}, + lastUpdatedAt: 0, + }; + this.tokenAddresses.forEach( + (t: Address, i: number) => + (fastPriceFeedState.prices[t] = BigInt( + FastPriceFeed.interface + .decodeFunctionResult('prices', multicallOutputs[i])[0] + .toString(), + )), + ); + fastPriceFeedState.lastUpdatedAt = parseInt( + FastPriceFeed.interface + .decodeFunctionResult( + 'lastUpdatedAt', + multicallOutputs[this.tokenAddresses.length], + )[0] + .toString(), + ); + return fastPriceFeedState; + } +} diff --git a/src/dex/QuickPerps/pool.ts b/src/dex/QuickPerps/pool.ts new file mode 100644 index 000000000..20d2fbb92 --- /dev/null +++ b/src/dex/QuickPerps/pool.ts @@ -0,0 +1,360 @@ +import { DeepReadonly } from 'ts-essentials'; +import { Lens, lens } from '../../lens'; +import { Address, Log, Logger, MultiCallInput } from '../../types'; +import { ComposedEventSubscriber } from '../../composed-event-subscriber'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { PoolState, DexParams, PoolConfig } from './types'; +import { ChainLinkSubscriber } from '../../lib/chainlink'; +import { FastPriceFeed } from './fast-price-feed'; +import { VaultPriceFeed } from './vault-price-feed'; +import { Vault } from './vault'; +import { USDQ } from './usdq'; +import { Contract } from 'web3-eth-contract'; +import ReaderABI from '../../abi/quickPerps/reader.json'; + +const MAX_AMOUNT_IN_CACHE_TTL = 5 * 60; + +export class QuickPerpsEventPool extends ComposedEventSubscriber { + PRICE_PRECISION = 10n ** 30n; + USDQ_DECIMALS = 18; + BASIS_POINTS_DIVISOR = 10000n; + + vault: Vault; + reader: Contract; + + constructor( + parentName: string, + protected network: number, + protected dexHelper: IDexHelper, + logger: Logger, + config: PoolConfig, + ) { + const chainlinkMap = Object.entries(config.chainlink).reduce( + ( + acc: { [address: string]: ChainLinkSubscriber }, + [key, value], + ) => { + acc[key] = new ChainLinkSubscriber( + value.proxy, + value.aggregator, + lens>().primaryPrices[key], + dexHelper.getLogger(`${key} ChainLink for ${parentName}-${network}`), + ); + return acc; + }, + {}, + ); + const fastPriceFeed = new FastPriceFeed( + config.fastPriceFeed, + config.fastPriceEvents, + config.tokenAddresses, + config.fastPriceFeedConfig, + lens>().secondaryPrices, + dexHelper.getLogger(`${parentName}-${network} fastPriceFeed`), + ); + const vaultPriceFeed = new VaultPriceFeed( + config.vaultPriceFeedConfig, + chainlinkMap, + fastPriceFeed, + ); + const usdq = new USDQ( + config.usdqAddress, + lens>().usdq, + dexHelper.getLogger(`${parentName}-${network} USDQ`), + ); + const vault = new Vault( + config.vaultAddress, + config.tokenAddresses, + config.vaultConfig, + vaultPriceFeed, + usdq, + lens>().vault, + dexHelper.getLogger(`${parentName}-${network} vault`), + ); + super( + parentName, + 'pool', + dexHelper.getLogger(`${parentName}-${network}`), + dexHelper, + [...Object.values(chainlinkMap), fastPriceFeed, usdq, vault], + { + primaryPrices: {}, + secondaryPrices: { + lastUpdatedAt: 0, + prices: {}, + }, + vault: { + usdqAmounts: {}, + }, + usdq: { + totalSupply: 0n, + }, + }, + ); + this.vault = vault; + this.reader = new this.dexHelper.web3Provider.eth.Contract( + ReaderABI as any, + config.readerAddress, + ); + } + + async getStateOrGenerate(blockNumber: number): Promise> { + const evenState = this.getState(blockNumber); + if (evenState) return evenState; + const onChainState = await this.generateState(blockNumber); + this.setState(onChainState, blockNumber); + return onChainState; + } + + async getMaxAmountIn(_tokenIn: Address, _tokenOut: Address): Promise { + const cacheKey = `maxAmountIn_${_tokenIn}_${_tokenOut}`; + const maxAmountCached = await this.dexHelper.cache.get( + this.parentName, + this.network, + cacheKey, + ); + if (maxAmountCached) return BigInt(maxAmountCached); + const maxAmount: string = await this.reader.methods + .getMaxAmountIn(this.vault.vaultAddress, _tokenIn, _tokenOut) + .call(); + this.dexHelper.cache.setex( + this.parentName, + this.network, + cacheKey, + MAX_AMOUNT_IN_CACHE_TTL, + maxAmount, + ); + return BigInt(maxAmount); + } + + // Reference to the original implementation + // https://github.com/gmx-io/gmx-contracts/blob/master/contracts/peripherals/Reader.sol#L71 + async getAmountOut( + _tokenIn: Address, + _tokenOut: Address, + _amountsIn: bigint[], + blockNumber: number, + ): Promise { + const maxAmountIn = await this.getMaxAmountIn(_tokenIn, _tokenOut); + const state = await this.getStateOrGenerate(blockNumber); + const priceIn = this.vault.getMinPrice(state, _tokenIn); + const priceOut = this.vault.getMaxPrice(state, _tokenOut); + + const tokenInDecimals = this.vault.tokenDecimals[_tokenIn]; + const tokenOutDecimals = this.vault.tokenDecimals[_tokenOut]; + + const isStableSwap = + this.vault.stableTokens[_tokenIn] && this.vault.stableTokens[_tokenOut]; + const baseBps = isStableSwap + ? this.vault.stableSwapFeeBasisPoints + : this.vault.swapFeeBasisPoints; + const taxBps = isStableSwap + ? this.vault.stableTaxBasisPoints + : this.vault.taxBasisPoints; + const USDQUnit = BigInt(10 ** this.USDQ_DECIMALS); + const tokenInUnit = BigInt(10 ** tokenInDecimals); + const tokenOutUnit = BigInt(10 ** tokenOutDecimals); + + return _amountsIn.map(_amountIn => { + if (_amountIn > maxAmountIn) return 0n; + let feeBasisPoints; + { + let usdqAmount = (_amountIn * priceIn) / this.PRICE_PRECISION; + usdqAmount = (usdqAmount * USDQUnit) / tokenInUnit; + + const feesBasisPoints0 = this.vault.getFeeBasisPoints( + state, + _tokenIn, + usdqAmount, + baseBps, + taxBps, + true, + ); + const feesBasisPoints1 = this.vault.getFeeBasisPoints( + state, + _tokenOut, + usdqAmount, + baseBps, + taxBps, + false, + ); + // use the higher of the two fee basis points + feeBasisPoints = + feesBasisPoints0 > feesBasisPoints1 + ? feesBasisPoints0 + : feesBasisPoints1; + } + + let amountOut = (_amountIn * priceIn) / priceOut; + amountOut = (amountOut * tokenOutUnit) / tokenInUnit; + + const amountOutAfterFees = + (amountOut * (this.BASIS_POINTS_DIVISOR - feeBasisPoints)) / + this.BASIS_POINTS_DIVISOR; + return amountOutAfterFees; + }); + } + + static async getWhitelistedTokens( + vaultAddress: Address, + blockNumber: number | 'latest', + multiContract: Contract, + ) { + // get tokens count + const tokenCountResult = ( + await multiContract.methods + .aggregate([ + { + callData: Vault.interface.encodeFunctionData( + 'allWhitelistedTokensLength', + ), + target: vaultAddress, + }, + ]) + .call({}, blockNumber) + ).returnData; + const tokensCount = parseInt( + Vault.interface + .decodeFunctionResult('allWhitelistedTokensLength', tokenCountResult[0]) + .toString(), + ); + + // get tokens + const getTokensCalldata = new Array(tokensCount).fill(0).map((_, i) => { + return { + callData: Vault.interface.encodeFunctionData('allWhitelistedTokens', [ + i, + ]), + target: vaultAddress, + }; + }); + const tokensResult = ( + await multiContract.methods + .aggregate(getTokensCalldata) + .call({}, blockNumber) + ).returnData; + const tokens: Address[] = tokensResult.map((t: any) => + Vault.interface + .decodeFunctionResult('allWhitelistedTokens', t)[0] + .toLowerCase(), + ); + return tokens; + } + + static async getConfig( + dexParams: DexParams, + blockNumber: number | 'latest', + multiContract: Contract, + ): Promise { + const tokens = await this.getWhitelistedTokens( + dexParams.vault, + blockNumber, + multiContract, + ); + + // get price chainlink price feed + const getPriceFeedCalldata = tokens.map(t => { + return { + callData: VaultPriceFeed.interface.encodeFunctionData('priceFeeds', [ + t, + ]), + target: dexParams.priceFeed, + }; + }); + const priceFeedResult = ( + await multiContract.methods + .aggregate(getPriceFeedCalldata) + .call({}, blockNumber) + ).returnData; + const priceFeeds = priceFeedResult.map((p: any) => + VaultPriceFeed.interface + .decodeFunctionResult('priceFeeds', p)[0] + .toString() + .toLowerCase(), + ); + + // get config for all event listeners + let multicallSlices: [number, number][] = []; + let multiCallData: MultiCallInput[] = []; + let i = 0; + for (let priceFeed of priceFeeds) { + const chainlinkConfigCallData = + ChainLinkSubscriber.getReadAggregatorMultiCallInput(priceFeed); + multiCallData.push(chainlinkConfigCallData); + multicallSlices.push([i, i + 1]); + i += 1; + } + + const fastPriceFeedConfigCallData = FastPriceFeed.getConfigMulticallInputs( + dexParams.fastPriceFeed, + tokens, + ); + multiCallData.push(...fastPriceFeedConfigCallData); + multicallSlices.push([i, i + fastPriceFeedConfigCallData.length]); + i += fastPriceFeedConfigCallData.length; + + const vaultPriceFeedConfigCallData = + VaultPriceFeed.getConfigMulticallInputs(dexParams.priceFeed, tokens); + multiCallData.push(...vaultPriceFeedConfigCallData); + multicallSlices.push([i, i + vaultPriceFeedConfigCallData.length]); + i += vaultPriceFeedConfigCallData.length; + + const vaultConfigCallData = Vault.getConfigMulticallInputs( + dexParams.vault, + tokens, + ); + multiCallData.push(...vaultConfigCallData); + multicallSlices.push([i, i + vaultConfigCallData.length]); + i += vaultConfigCallData.length; + + const configResults = ( + await multiContract.methods.aggregate(multiCallData).call({}, blockNumber) + ).returnData; + + const chainlink: { + [address: string]: { proxy: Address; aggregator: Address }; + } = {}; + for (let token of tokens) { + const aggregator = ChainLinkSubscriber.readAggregator( + configResults.slice(...multicallSlices.shift()!)[0], + ); + chainlink[token] = { + proxy: priceFeeds.shift(), + aggregator, + }; + } + + const fastPriceFeedConfigResults = configResults.slice( + ...multicallSlices.shift()!, + ); + const fastPriceFeedConfig = FastPriceFeed.getConfig( + fastPriceFeedConfigResults, + tokens, + ); + + const vaultPriceFeedConfigResults = configResults.slice( + ...multicallSlices.shift()!, + ); + const vaultPriceFeedConfig = VaultPriceFeed.getConfig( + vaultPriceFeedConfigResults, + tokens, + ); + + const vaultConfigResults = configResults.slice(...multicallSlices.shift()!); + const vaultConfig = Vault.getConfig(vaultConfigResults, tokens); + + return { + vaultAddress: dexParams.vault, + readerAddress: dexParams.reader, + priceFeed: dexParams.priceFeed, + fastPriceFeed: dexParams.fastPriceFeed, + fastPriceEvents: dexParams.fastPriceEvents, + usdqAddress: dexParams.usdq, + tokenAddresses: tokens, + vaultConfig, + vaultPriceFeedConfig, + fastPriceFeedConfig, + chainlink, + }; + } +} diff --git a/src/dex/QuickPerps/quickPerps-e2e.test.ts b/src/dex/QuickPerps/quickPerps-e2e.test.ts new file mode 100644 index 000000000..2f84ff765 --- /dev/null +++ b/src/dex/QuickPerps/quickPerps-e2e.test.ts @@ -0,0 +1,166 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { testE2E } from '../../../tests/utils-e2e'; +import { + Tokens, + Holders, + NativeTokenSymbols, +} from '../../../tests/constants-e2e'; +import { Network, ContractMethod, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; + +describe('QuickPerps E2E', () => { + const dexKey = 'QuickPerps'; + + describe('QuickPerps AVALANCHE', () => { + const network = Network.AVALANCHE; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const tokenASymbol: string = 'WETHe'; + const tokenBSymbol: string = 'USDCe'; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const tokenAAmount: string = '1000000000000000000'; + const tokenBAmount: string = '2000000000'; + const nativeTokenAmount = '1000000000000000000'; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(nativeTokenSymbol + ' -> TOKEN', async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it('TOKEN -> ' + nativeTokenSymbol, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it('TOKEN -> TOKEN', async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }), + ); + }); + + describe('QuickPerps ARBITRUM', () => { + const network = Network.ARBITRUM; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'WETH'; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const tokenAAmount: string = '2000000000'; + const tokenBAmount: string = '1000000000000000000'; + const nativeTokenAmount = '1000000000000000000'; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(nativeTokenSymbol + ' -> TOKEN', async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it('TOKEN -> ' + nativeTokenSymbol, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it('TOKEN -> TOKEN', async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }), + ); + }); +}); diff --git a/src/dex/QuickPerps/quickPerps-events.test.ts b/src/dex/QuickPerps/quickPerps-events.test.ts new file mode 100644 index 000000000..2964f35f6 --- /dev/null +++ b/src/dex/QuickPerps/quickPerps-events.test.ts @@ -0,0 +1,115 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { QuickPerpsEventPool } from './pool'; +import { QuickPerpsConfig } from './config'; +import { Network } from '../../constants'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { testEventSubscriber } from '../../../tests/utils-events'; +import { PoolState } from './types'; + +jest.setTimeout(50 * 1000); +const dexKey = 'QuickPerps'; +const network = Network.AVALANCHE; +const params = QuickPerpsConfig[dexKey][network]; + +async function fetchPoolState( + quickPerpsPool: QuickPerpsEventPool, + blockNumber: number, +): Promise { + return quickPerpsPool.generateState(blockNumber); +} + +// timestamp can't be compared exactly as the event released +// doesn't have the timestamp. It is safe to consider the +// timestamp as the blockTime as the max deviation is bounded +// on the contract +const stateWithoutTimestamp = (state: PoolState) => ({ + ...state, + secondaryPrices: { + prices: state.secondaryPrices.prices, + // timestamp (this is removed) + }, +}); + +function compareState(state: PoolState, expectedState: PoolState) { + expect(stateWithoutTimestamp(state)).toEqual( + stateWithoutTimestamp(expectedState), + ); +} + +describe('QuickPerps Event', function () { + const blockNumbers: { [eventName: string]: number[] } = { + IncreaseUsdqAmount: [ + 19403150, 19403175, 19403183, 19403215, 19403232, 19403246, 19403344, + 19403484, 19403545, 19403553, 19403586, 19403662, 19403712, 19403721, + 19403757, 19403775, 19403782, 19403800, 19403807, 19403808, 19403826, + 19403844, 19403848, 19403852, 19403860, 19403863, 19403875, 19403877, + 19403885, 19403900, 19403904, 19403938, 19403963, 19403970, 19403973, + 19403978, 19403999, 19404000, 19404023, 19404026, 19404046, 19404056, + 19404060, 19404078, 19404083, 19404097, 19404149, 19404164, 19404178, + 19404182, 19404229, 19404243, 19404264, 19404272, 19404287, 19404347, + 19404378, 19404379, 19404389, 19404408, 19404463, 19404491, 19404560, + 19404625, 19404657, 19404687, 19404700, 19404714, 19404763, 19404889, + 19404892, 19404893, 19404894, 19404897, 19404904, 19404916, 19404917, + 19404927, 19404935, 19404946, 19404949, 19404951, 19404959, 19404973, + 19405017, 19405027, 19405034, + ], + DecreaseUsdqAmount: [ + 19403150, 19403175, 19403183, 19403215, 19403232, 19403246, 19403344, + 19403545, 19403553, 19403662, 19403712, 19403721, 19403757, 19403775, + 19403782, 19403800, 19403807, 19403808, 19403826, 19403844, 19403848, + 19403852, 19403860, 19403863, 19403875, 19403877, 19403885, 19403900, + 19403904, 19403938, 19403963, 19403970, 19403973, 19403978, 19403999, + 19404000, 19404023, 19404026, 19404046, 19404056, 19404060, 19404078, + 19404083, 19404097, 19404149, 19404164, 19404178, 19404182, 19404229, + 19404243, 19404264, 19404272, 19404287, 19404347, 19404378, 19404379, + 19404389, 19404408, 19404463, 19404491, 19404560, 19404625, 19404657, + 19404687, 19404700, 19404714, 19404763, 19404889, 19404892, 19404893, + 19404894, 19404897, 19404904, 19404916, 19404917, 19404927, 19404935, + 19404946, 19404949, 19404951, 19404959, 19404973, 19405017, 19405027, + 19405034, + ], + Transfer: [19403484, 19403586, 19405046, 19405100, 19405154, 19405318], + PriceUpdate: [ + 19403134, 19403135, 19403140, 19403141, 19403144, 19403148, 19403151, + 19403154, 19403163, 19403169, 19403170, 19403171, 19403178, 19403185, + 19403186, 19403202, + ], + }; + + describe('QuickPerpsEventPool', function () { + Object.keys(blockNumbers).forEach((event: string) => { + blockNumbers[event].forEach((blockNumber: number) => { + it(`Should return the correct state after the ${blockNumber}:${event}`, async function () { + const dexHelper = new DummyDexHelper(network); + const logger = dexHelper.getLogger(dexKey); + + const config = await QuickPerpsEventPool.getConfig( + params, + blockNumber, + dexHelper.multiContract, + ); + const quickPerpsPool = new QuickPerpsEventPool( + dexKey, + network, + dexHelper, + logger, + config, + ); + + await testEventSubscriber( + quickPerpsPool, + quickPerpsPool.addressesSubscribed, + (_blockNumber: number) => + fetchPoolState(quickPerpsPool, _blockNumber), + blockNumber, + `${dexKey}_${params.vault}`, + dexHelper.provider, + compareState, + ); + }); + }); + }); + }); +}); diff --git a/src/dex/QuickPerps/quickPerps-integration.test.ts b/src/dex/QuickPerps/quickPerps-integration.test.ts new file mode 100644 index 000000000..5c488326f --- /dev/null +++ b/src/dex/QuickPerps/quickPerps-integration.test.ts @@ -0,0 +1,117 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Interface } from '@ethersproject/abi'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { QuickPerps } from './quickPerps'; +import { QuickPerpsConfig } from './config'; +import { + checkPoolPrices, + checkPoolsLiquidity, + checkConstantPoolPrices, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import ReaderABI from '../../abi/quickPerps/reader.json'; + +const network = Network.AVALANCHE; +const TokenASymbol = 'USDCe'; +const TokenA = Tokens[network][TokenASymbol]; + +const TokenBSymbol = 'WAVAX'; +const TokenB = Tokens[network][TokenBSymbol]; + +const amounts = [ + 0n, + 1000000000n, + 2000000000n, + 3000000000n, + 4000000000n, + 5000000000n, +]; + +const dexKey = 'QuickPerps'; +const params = QuickPerpsConfig[dexKey][network]; +const readerInterface = new Interface(ReaderABI); +const readerAddress = '0x67b789D48c926006F5132BFCe4e976F0A7A63d5D'; + +describe('QuickPerps', function () { + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const dexHelper = new DummyDexHelper(network); + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const quickPerps = new QuickPerps(network, dexKey, dexHelper); + + await quickPerps.initializePricing(blocknumber); + + const pools = await quickPerps.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blocknumber, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await quickPerps.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + if (quickPerps.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + } + + // Do on chain pricing based on reader to compare + const readerCallData = amounts.map(a => ({ + target: readerAddress, + callData: readerInterface.encodeFunctionData('getAmountOut', [ + params.vault, + TokenA.address, + TokenB.address, + a.toString(), + ]), + })); + + const readerResult = ( + await dexHelper.multiContract.methods + .aggregate(readerCallData) + .call({}, blocknumber) + ).returnData; + const expectedPrices = readerResult.map((p: any) => + BigInt( + readerInterface.decodeFunctionResult('getAmountOut', p)[0].toString(), + ), + ); + + expect(poolPrices![0].prices).toEqual(expectedPrices); + }); + + it('getTopPoolsForToken', async function () { + const dexHelper = new DummyDexHelper(network); + const quickPerps = new QuickPerps(network, dexKey, dexHelper); + + await quickPerps.updatePoolState(); + const poolLiquidity = await quickPerps.getTopPoolsForToken( + TokenA.address, + 10, + ); + console.log( + `${TokenASymbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + if (!quickPerps.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity(poolLiquidity, TokenA.address, dexKey); + } + }); +}); diff --git a/src/dex/QuickPerps/quickPerps.ts b/src/dex/QuickPerps/quickPerps.ts new file mode 100644 index 000000000..f31ba1523 --- /dev/null +++ b/src/dex/QuickPerps/quickPerps.ts @@ -0,0 +1,296 @@ +import { Interface } from '@ethersproject/abi'; +import _ from 'lodash'; +import { + Token, + Address, + ExchangePrices, + PoolPrices, + AdapterExchangeParam, + SimpleExchangeParam, + PoolLiquidity, + Logger, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { getDexKeysWithNetwork, getBigIntPow } from '../../utils'; +import { IDex } from '../idex'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { QuickPerpsData, DexParams } from './types'; +import { QuickPerpsEventPool } from './pool'; +import { SimpleExchange } from '../simple-exchange'; +import { QuickPerpsConfig, Adapters } from './config'; +import { Vault } from './vault'; +import ERC20ABI from '../../abi/erc20.json'; + +const QuickPerpsGasCost = 300 * 1000; + +export class QuickPerps extends SimpleExchange implements IDex { + protected pool: QuickPerpsEventPool | null = null; + protected supportedTokensMap: { [address: string]: boolean } = {}; + // supportedTokens is only used by the pooltracker + protected supportedTokens: Token[] = []; + + readonly hasConstantPriceLargeAmounts = false; + readonly needWrapNative = true; + readonly isFeeOnTransferSupported = false; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(QuickPerpsConfig); + + public static erc20Interface = new Interface(ERC20ABI); + + vaultUSDBalance: number = 0; + + logger: Logger; + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + protected adapters = Adapters[network], + protected params: DexParams = QuickPerpsConfig[dexKey][network], + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey); + } + + // Initialize pricing is called once in the start of + // pricing service. It is intended to setup the integration + // for pricing requests. + async initializePricing(blockNumber: number) { + const config = await QuickPerpsEventPool.getConfig( + this.params, + blockNumber, + this.dexHelper.multiContract, + ); + config.tokenAddresses.forEach( + (token: Address) => (this.supportedTokensMap[token] = true), + ); + this.pool = new QuickPerpsEventPool( + this.dexKey, + this.network, + this.dexHelper, + this.logger, + config, + ); + await this.pool.initialize(blockNumber); + } + + // Returns the list of contract adapters (name and index) + // for a buy/sell. Return null if there are no adapters. + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return this.adapters[side]; + } + + // Returns list of pool identifiers that can be used + // for a given swap. poolIdentifiers must be unique + // across DEXes. + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + if (side === SwapSide.BUY || !this.pool) return []; + const srcAddress = this.dexHelper.config + .wrapETH(srcToken) + .address.toLowerCase(); + const destAddress = this.dexHelper.config + .wrapETH(destToken) + .address.toLowerCase(); + if ( + srcAddress !== destAddress && + this.supportedTokensMap[srcAddress] && + this.supportedTokensMap[destAddress] + ) { + return [`${this.dexKey}_${srcAddress}`, `${this.dexKey}_${destAddress}`]; + } + return []; + } + + // Returns pool prices for amounts. + // If limitPools is defined only pools in limitPools + // should be used. If limitPools is undefined then + // any pools can be used. + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + ): Promise> { + if (side === SwapSide.BUY || !this.pool) return null; + const srcAddress = this.dexHelper.config + .wrapETH(srcToken) + .address.toLowerCase(); + const destAddress = this.dexHelper.config + .wrapETH(destToken) + .address.toLowerCase(); + if ( + srcAddress === destAddress || + !( + this.supportedTokensMap[srcAddress] && + this.supportedTokensMap[destAddress] + ) + ) + return null; + const srcPoolIdentifier = `${this.dexKey}_${srcAddress}`; + const destPoolIdentifier = `${this.dexKey}_${destAddress}`; + const pools = [srcPoolIdentifier, destPoolIdentifier]; + if (limitPools && pools.some(p => !limitPools.includes(p))) return null; + + const unitVolume = getBigIntPow(srcToken.decimals); + const prices = await this.pool.getAmountOut( + srcAddress, + destAddress, + [unitVolume, ...amounts], + blockNumber, + ); + + if (!prices) return null; + + return [ + { + prices: prices.slice(1), + unit: prices[0], + gasCost: QuickPerpsGasCost, + exchange: this.dexKey, + data: {}, + poolAddresses: [this.params.vault], + }, + ]; + } + + // Returns estimated gas cost of calldata for this DEX in multiSwap + getCalldataGasCost( + poolPrices: PoolPrices, + ): number | number[] { + return CALLDATA_GAS_COST.DEX_NO_PAYLOAD; + } + + getAdapterParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: QuickPerpsData, + side: SwapSide, + ): AdapterExchangeParam { + return { + targetExchange: this.params.vault, + payload: '0x', + networkFee: '0', + }; + } + + getSimpleParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: QuickPerpsData, + side: SwapSide, + ): SimpleExchangeParam { + return { + callees: [srcToken, this.params.vault], + calldata: [ + QuickPerps.erc20Interface.encodeFunctionData('transfer', [ + this.params.vault, + srcAmount, + ]), + Vault.interface.encodeFunctionData('swap', [ + srcToken, + destToken, + this.augustusAddress, + ]), + ], + values: ['0', '0'], + networkFee: '0', + }; + } + + async updatePoolState(): Promise { + if (!this.supportedTokens.length) { + const tokenAddresses = await QuickPerpsEventPool.getWhitelistedTokens( + this.params.vault, + 'latest', + this.dexHelper.multiContract, + ); + + const decimalsCallData = + QuickPerps.erc20Interface.encodeFunctionData('decimals'); + const tokenBalanceMultiCall = tokenAddresses.map(t => ({ + target: t, + callData: decimalsCallData, + })); + const res = ( + await this.dexHelper.multiContract.methods + .aggregate(tokenBalanceMultiCall) + .call() + ).returnData; + + const tokenDecimals = res.map((r: any) => + parseInt( + QuickPerps.erc20Interface + .decodeFunctionResult('decimals', r)[0] + .toString(), + ), + ); + + this.supportedTokens = tokenAddresses.map((t, i) => ({ + address: t, + decimals: tokenDecimals[i], + })); + } + + const erc20BalanceCalldata = QuickPerps.erc20Interface.encodeFunctionData( + 'balanceOf', + [this.params.vault], + ); + const tokenBalanceMultiCall = this.supportedTokens.map(t => ({ + target: t.address, + callData: erc20BalanceCalldata, + })); + const res = ( + await this.dexHelper.multiContract.methods + .aggregate(tokenBalanceMultiCall) + .call() + ).returnData; + const tokenBalances = res.map((r: any) => + BigInt( + QuickPerps.erc20Interface + .decodeFunctionResult('balanceOf', r)[0] + .toString(), + ), + ); + const tokenBalancesUSD = await Promise.all( + this.supportedTokens.map((t, i) => + this.dexHelper.getTokenUSDPrice(t, tokenBalances[i]), + ), + ); + this.vaultUSDBalance = tokenBalancesUSD.reduce( + (sum: number, curr: number) => sum + curr, + ); + } + + // Returns list of top pools based on liquidity. Max + // limit number pools should be returned. + async getTopPoolsForToken( + _tokenAddress: Address, + limit: number, + ): Promise { + const tokenAddress = _tokenAddress.toLowerCase(); + if (!this.supportedTokens.some(t => t.address === tokenAddress)) return []; + return [ + { + exchange: this.dexKey, + address: this.params.vault, + connectorTokens: this.supportedTokens.filter( + t => t.address !== tokenAddress, + ), + liquidityUSD: this.vaultUSDBalance, + }, + ]; + } +} diff --git a/src/dex/QuickPerps/types.ts b/src/dex/QuickPerps/types.ts new file mode 100644 index 000000000..284933bb4 --- /dev/null +++ b/src/dex/QuickPerps/types.ts @@ -0,0 +1,94 @@ +import { Address } from '../../types'; +import { ChainLinkState } from '../../lib/chainlink'; + +export type PoolState = { + primaryPrices: { [poolAddress: string]: ChainLinkState }; + secondaryPrices: FastPriceFeedState; + vault: VaultState; + usdq: USDQState; +}; + +export type FastPriceFeedState = { + lastUpdatedAt: number; + prices: { [tokenAddress: string]: bigint }; +}; + +export type VaultState = { + usdqAmounts: { [tokenAddress: string]: bigint }; +}; + +export type USDQState = { + totalSupply: bigint; +}; + +export type QuickPerpsData = { + // TODO: QuickPerpsData is the dex data that is + // returned by the API that can be used for + // tx building. The data structure should be minimal. + // Complete me! +}; + +export type DexParams = { + vault: Address; + reader: Address; + priceFeed: Address; + fastPriceFeed: Address; + fastPriceEvents: Address; + // Last three param can be fetched on chain by calling + // vaultAddress.priceFeed() => priceFeed + // priceFeed.secondaryPriceFeed() => fastPriceFeed + // fastPriceFeed.fastPriceEvents() => fastPriceEvents + // It is added as constants to avoid unnecessary + // sequential onchain calls + usdq: Address; +}; + +export type FastPriceFeedConfig = { + priceDuration: number; + maxDeviationBasisPoints: bigint; + favorFastPrice: Record; + spreadBasisPointsIfInactive: bigint; + spreadBasisPointsIfChainError: bigint; + maxPriceUpdateDelay: number; +}; + +export type VaultPriceFeedConfig = { + isAmmEnabled: boolean; + isSecondaryPriceEnabled: boolean; + strictStableTokens: { [address: string]: boolean }; + spreadBasisPoints: { [address: string]: bigint }; + adjustmentBasisPoints: { [address: string]: bigint }; + isAdjustmentAdditive: { [address: string]: boolean }; + priceDecimals: { [address: string]: number }; + maxStrictPriceDeviation: bigint; + useV2Pricing: boolean; + priceSampleSpace: number; +}; + +export type VaultConfig = { + tokenDecimals: { [address: string]: number }; + stableTokens: { [address: string]: boolean }; + tokenWeights: { [address: string]: bigint }; + stableSwapFeeBasisPoints: bigint; + swapFeeBasisPoints: bigint; + stableTaxBasisPoints: bigint; + taxBasisPoints: bigint; + hasDynamicFees: bigint; + includeAmmPrice: boolean; + useSwapPricing: boolean; + totalTokenWeights: bigint; +}; + +export type PoolConfig = { + vaultAddress: Address; + readerAddress: Address; + priceFeed: Address; + fastPriceFeed: Address; + fastPriceEvents: Address; + usdqAddress: Address; + tokenAddresses: Address[]; + vaultConfig: VaultConfig; + vaultPriceFeedConfig: VaultPriceFeedConfig; + fastPriceFeedConfig: FastPriceFeedConfig; + chainlink: { [address: string]: { proxy: Address; aggregator: Address } }; +}; diff --git a/src/dex/QuickPerps/usdq.ts b/src/dex/QuickPerps/usdq.ts new file mode 100644 index 000000000..da09397ff --- /dev/null +++ b/src/dex/QuickPerps/usdq.ts @@ -0,0 +1,83 @@ +import _ from 'lodash'; +import { Interface } from '@ethersproject/abi'; +import { DeepReadonly } from 'ts-essentials'; +import { PartialEventSubscriber } from '../../composed-event-subscriber'; +import { + Address, + MultiCallInput, + MultiCallOutput, + Logger, + Log, + BlockHeader, +} from '../../types'; +import { USDQState } from './types'; +import ERC20ABI from '../../abi/erc20.json'; +import { Lens } from '../../lens'; +import { NULL_ADDRESS } from '../../constants'; + +export class USDQ extends PartialEventSubscriber { + static readonly interface = new Interface(ERC20ABI); + + constructor( + private usdqAddress: Address, + lens: Lens, DeepReadonly>, + logger: Logger, + ) { + super([usdqAddress], lens, logger); + } + + getTotalSupply(state: DeepReadonly) { + return this.lens.get()(state).totalSupply; + } + + public processLog( + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ): DeepReadonly | null { + try { + const parsed = USDQ.interface.parseLog(log); + const _state: USDQState = _.cloneDeep(state); + switch (parsed.name) { + case 'Transfer': { + const fromAddress = parsed.args.src; + const toAddress = parsed.args.dst; + if (fromAddress === NULL_ADDRESS) { + _state.totalSupply += BigInt(parsed.args.wad.toString()); + } else if (toAddress === NULL_ADDRESS) { + _state.totalSupply -= BigInt(parsed.args.wad.toString()); + } + return _state; + } + default: + return null; + } + } catch (e) { + this.logger.error('Failed to parse log', e); + return null; + } + } + + public getGenerateStateMultiCallInputs(): MultiCallInput[] { + return [ + { + target: this.usdqAddress, + callData: USDQ.interface.encodeFunctionData('totalSupply'), + }, + ]; + } + + public generateState( + multicallOutputs: MultiCallOutput[], + blockNumber?: number | 'latest', + ): DeepReadonly { + return { + totalSupply: BigInt( + USDQ.interface.decodeFunctionResult( + 'totalSupply', + multicallOutputs[0], + )[0], + ), + }; + } +} diff --git a/src/dex/QuickPerps/vault-price-feed.ts b/src/dex/QuickPerps/vault-price-feed.ts new file mode 100644 index 000000000..5071cb135 --- /dev/null +++ b/src/dex/QuickPerps/vault-price-feed.ts @@ -0,0 +1,384 @@ +import { Interface } from '@ethersproject/abi'; +import { Address, MultiCallInput, MultiCallOutput } from '../../types'; +import { PoolState, VaultPriceFeedConfig } from './types'; +import { FastPriceFeed } from './fast-price-feed'; +import VaultPriceFeedAbi from '../../abi/quickPerps/vault-price-feed.json'; +import { ChainLinkSubscriber } from '../../lib/chainlink'; +import { DeepReadonly } from 'ts-essentials'; + +export class VaultPriceFeed { + BASIS_POINTS_DIVISOR = 10000n; + PRICE_PRECISION = 10n ** 30n; + ONE_USD = this.PRICE_PRECISION; + + static interface = new Interface(VaultPriceFeedAbi); + + protected isAmmEnabled: boolean; + protected isSecondaryPriceEnabled: boolean; + protected strictStableTokens: { [address: string]: boolean }; + protected spreadBasisPoints: { [address: string]: bigint }; + protected adjustmentBasisPoints: { [address: string]: bigint }; + protected isAdjustmentAdditive: { [address: string]: boolean }; + protected priceDecimals: { [address: string]: number }; + protected maxStrictPriceDeviation: bigint; + protected useV2Pricing: boolean; + protected priceSampleSpace: number; + + constructor( + config: VaultPriceFeedConfig, + protected primaryPrices: { [token: string]: ChainLinkSubscriber }, + protected secondaryPrice: FastPriceFeed, + ) { + this.isAmmEnabled = config.isAmmEnabled; + this.isSecondaryPriceEnabled = config.isSecondaryPriceEnabled; + this.strictStableTokens = config.strictStableTokens; + this.spreadBasisPoints = config.spreadBasisPoints; + this.adjustmentBasisPoints = config.adjustmentBasisPoints; + this.isAdjustmentAdditive = config.isAdjustmentAdditive; + this.priceDecimals = config.priceDecimals; + this.maxStrictPriceDeviation = config.maxStrictPriceDeviation; + this.useV2Pricing = config.useV2Pricing; + this.priceSampleSpace = config.priceSampleSpace; + } + + getPrice( + state: DeepReadonly, + _token: Address, + _maximise: boolean, + _includeAmmPrice: boolean, + _useSwapPricing: boolean, + ): bigint { + let price = this.useV2Pricing + ? this.getPriceV2(state, _token, _maximise, _includeAmmPrice) + : this.getPriceV1(state, _token, _maximise, _includeAmmPrice); + + const adjustmentBps = this.adjustmentBasisPoints[_token]; + if (adjustmentBps > 0n) { + const isAdditive = this.isAdjustmentAdditive[_token]; + if (isAdditive) { + price = + (price * (this.BASIS_POINTS_DIVISOR + adjustmentBps)) / + this.BASIS_POINTS_DIVISOR; + } else { + price = + (price * (this.BASIS_POINTS_DIVISOR - adjustmentBps)) / + this.BASIS_POINTS_DIVISOR; + } + } + + return price; + } + + getPriceV2( + state: DeepReadonly, + _token: Address, + _maximise: boolean, + _includeAmmPrice: boolean, + ): bigint { + throw new Error( + 'getPriceV2 implementation is not complete, devs should disable the dex or complete the implementation', + ); + } + + getPriceV1( + state: DeepReadonly, + _token: Address, + _maximise: boolean, + _includeAmmPrice: boolean, + ): bigint { + let price = this.getPrimaryPrice(state, _token, _maximise); + + if (_includeAmmPrice && this.isAmmEnabled) { + const ammPrice = this.getAmmPrice(state, _token); + if (ammPrice > 0n) { + if (_maximise && ammPrice > price) { + price = ammPrice; + } + if (!_maximise && ammPrice < price) { + price = ammPrice; + } + } + } + + if (this.isSecondaryPriceEnabled) { + price = this.getSecondaryPrice(state, _token, price, _maximise); + } + + if (this.strictStableTokens[_token]) { + const delta = + price > this.ONE_USD ? price - this.ONE_USD : this.ONE_USD - price; + if (delta <= this.maxStrictPriceDeviation) { + return this.ONE_USD; + } + + // if _maximise and price is e.g. 1.02, return 1.02 + if (_maximise && price > this.ONE_USD) { + return price; + } + + // if !_maximise and price is e.g. 0.98, return 0.98 + if (!_maximise && price < this.ONE_USD) { + return price; + } + + return this.ONE_USD; + } + + const _spreadBasisPoints = this.spreadBasisPoints[_token]; + + if (_maximise) { + return ( + (price * (this.BASIS_POINTS_DIVISOR + _spreadBasisPoints)) / + this.BASIS_POINTS_DIVISOR + ); + } + + return ( + (price * (this.BASIS_POINTS_DIVISOR - _spreadBasisPoints)) / + this.BASIS_POINTS_DIVISOR + ); + } + + getAmmPrice(state: DeepReadonly, token: Address): bigint { + throw new Error( + 'getAmmPrice implementation is not complete, devs should disable the dex or complete the implementation', + ); + } + + getPrimaryPrice( + state: DeepReadonly, + _token: Address, + _maximise: boolean, + ): bigint { + // const priceFeedAddress = this.priceFeeds[_token]; + // require(priceFeedAddress != address(0), "VaultPriceFeed: invalid price feed"); + + // if (chainlinkFlags != address(0)) { + // bool isRaised = IChainlinkFlags(chainlinkFlags).getFlag(FLAG_ARBITRUM_SEQ_OFFLINE); + // if (isRaised) { + // // If flag is raised we shouldn't perform any critical operations + // revert("Chainlink feeds are not being updated"); + // } + // } + + // IPriceFeed priceFeed = IPriceFeed(priceFeedAddress); + let price = 0n; + // const roundId = priceFeed.latestRound; + + // for (let i = 0; i < this.priceSampleSpace; i++) { + // if (roundId <= i) { + // break; + // } + + // if (i == 0) { + // const _p = priceFeed.latestAnswer(); + // require(_p > 0, "VaultPriceFeed: invalid price"); + // p = uint256(_p); + // } else { + // (, int256 _p, , ,) = priceFeed.getRoundData(roundId - i); + // require(_p > 0, "VaultPriceFeed: invalid price"); + // p = uint256(_p); + // } + + // if (price == 0n) { + // price = p; + // continue; + // } + + // if (_maximise && p > price) { + // price = p; + // continue; + // } + + // if (!_maximise && p < price) { + // price = p; + // } + // } + if (this.priceSampleSpace > 1) { + throw new Error( + 'Chainlink price feed is not implemented for historical prices', + ); + } + price = this.primaryPrices[_token].getLatestRoundData(state); + + // require(price > 0n, "VaultPriceFeed: could not fetch price"); + if (price <= 0n) throw new Error('VaultPriceFeed: could not fetch price'); + // normalise price precision + const _priceDecimals = this.priceDecimals[_token]; + return (price * this.PRICE_PRECISION) / BigInt(10 ** _priceDecimals); + } + + getSecondaryPrice( + state: DeepReadonly, + _token: Address, + _referencePrice: bigint, + _maximise: boolean, + ): bigint { + return this.secondaryPrice.getPrice( + state, + _token, + _referencePrice, + _maximise, + ); + } + + static getConfigMulticallInputs( + vaultPriceFeedAddress: Address, + tokenAddresses: Address[], + ): MultiCallInput[] { + return [ + { + target: vaultPriceFeedAddress, + callData: VaultPriceFeed.interface.encodeFunctionData('isAmmEnabled'), + }, + { + target: vaultPriceFeedAddress, + callData: VaultPriceFeed.interface.encodeFunctionData( + 'isSecondaryPriceEnabled', + ), + }, + ...tokenAddresses.map(t => ({ + target: vaultPriceFeedAddress, + callData: VaultPriceFeed.interface.encodeFunctionData( + 'strictStableTokens', + [t], + ), + })), + ...tokenAddresses.map(t => ({ + target: vaultPriceFeedAddress, + callData: VaultPriceFeed.interface.encodeFunctionData( + 'spreadBasisPoints', + [t], + ), + })), + ...tokenAddresses.map(t => ({ + target: vaultPriceFeedAddress, + callData: VaultPriceFeed.interface.encodeFunctionData( + 'isAdjustmentAdditive', + [t], + ), + })), + ...tokenAddresses.map(t => ({ + target: vaultPriceFeedAddress, + callData: VaultPriceFeed.interface.encodeFunctionData( + 'adjustmentBasisPoints', + [t], + ), + })), + ...tokenAddresses.map(t => ({ + target: vaultPriceFeedAddress, + callData: VaultPriceFeed.interface.encodeFunctionData('priceDecimals', [ + t, + ]), + })), + { + target: vaultPriceFeedAddress, + callData: VaultPriceFeed.interface.encodeFunctionData( + 'maxStrictPriceDeviation', + ), + }, + { + target: vaultPriceFeedAddress, + callData: VaultPriceFeed.interface.encodeFunctionData('useV2Pricing'), + }, + { + target: vaultPriceFeedAddress, + callData: + VaultPriceFeed.interface.encodeFunctionData('priceSampleSpace'), + }, + ]; + } + + static getConfig( + multicallOutputs: MultiCallOutput[], + tokenAddress: Address[], + ): VaultPriceFeedConfig { + let i = 0; + return { + isAmmEnabled: VaultPriceFeed.interface.decodeFunctionResult( + 'isAmmEnabled', + multicallOutputs[i++], + )[0], + isSecondaryPriceEnabled: VaultPriceFeed.interface.decodeFunctionResult( + 'isSecondaryPriceEnabled', + multicallOutputs[i++], + )[0], + strictStableTokens: tokenAddress.reduce( + (acc: { [address: string]: boolean }, t: Address) => { + acc[t] = VaultPriceFeed.interface.decodeFunctionResult( + 'strictStableTokens', + multicallOutputs[i++], + )[0]; + return acc; + }, + {}, + ), + spreadBasisPoints: tokenAddress.reduce( + (acc: { [address: string]: bigint }, t: Address) => { + acc[t] = BigInt( + VaultPriceFeed.interface + .decodeFunctionResult( + 'spreadBasisPoints', + multicallOutputs[i++], + )[0] + .toString(), + ); + return acc; + }, + {}, + ), + isAdjustmentAdditive: tokenAddress.reduce( + (acc: { [address: string]: boolean }, t: Address) => { + acc[t] = VaultPriceFeed.interface.decodeFunctionResult( + 'isAdjustmentAdditive', + multicallOutputs[i++], + )[0]; + return acc; + }, + {}, + ), + adjustmentBasisPoints: tokenAddress.reduce( + (acc: { [address: string]: bigint }, t: Address) => { + acc[t] = BigInt( + VaultPriceFeed.interface + .decodeFunctionResult( + 'adjustmentBasisPoints', + multicallOutputs[i++], + )[0] + .toString(), + ); + return acc; + }, + {}, + ), + priceDecimals: tokenAddress.reduce( + (acc: { [address: string]: number }, t: Address) => { + acc[t] = parseInt( + VaultPriceFeed.interface + .decodeFunctionResult('priceDecimals', multicallOutputs[i++])[0] + .toString(), + ); + return acc; + }, + {}, + ), + maxStrictPriceDeviation: BigInt( + VaultPriceFeed.interface + .decodeFunctionResult( + 'maxStrictPriceDeviation', + multicallOutputs[i++], + )[0] + .toString(), + ), + useV2Pricing: VaultPriceFeed.interface.decodeFunctionResult( + 'useV2Pricing', + multicallOutputs[i++], + )[0], + priceSampleSpace: parseInt( + VaultPriceFeed.interface + .decodeFunctionResult('priceSampleSpace', multicallOutputs[i++])[0] + .toString(), + ), + }; + } +} diff --git a/src/dex/QuickPerps/vault-utils.ts b/src/dex/QuickPerps/vault-utils.ts new file mode 100644 index 000000000..1ff64466e --- /dev/null +++ b/src/dex/QuickPerps/vault-utils.ts @@ -0,0 +1,53 @@ +import { Vault } from './vault'; +import { Address } from '../../types'; +import { DeepReadonly } from 'ts-essentials'; + +export class VaultUtils { + constructor(protected vault: Vault) {} + + getFeeBasisPoints( + state: DeepReadonly, + _token: Address, + _usdqDelta: bigint, + _feeBasisPoints: bigint, + _taxBasisPoints: bigint, + _increment: boolean, + ) { + if (!this.vault.hasDynamicFees) { + return _feeBasisPoints; + } + + const initialAmount = this.vault.getUSDQAmount(state, _token); + let nextAmount = initialAmount + _usdqDelta; + if (!_increment) { + nextAmount = _usdqDelta > initialAmount ? 0n : initialAmount - _usdqDelta; + } + + const targetAmount = this.vault.getTargetUsdqAmount(state, _token); + if (targetAmount == 0n) { + return _feeBasisPoints; + } + + const initialDiff = + initialAmount > targetAmount + ? initialAmount - targetAmount + : targetAmount - initialAmount; + const nextDiff = + nextAmount > targetAmount + ? nextAmount - targetAmount + : targetAmount - nextAmount; + + // action improves relative asset balance + if (nextDiff < initialDiff) { + const rebateBps = (_taxBasisPoints * initialDiff) / targetAmount; + return rebateBps > _feeBasisPoints ? 0n : _feeBasisPoints - rebateBps; + } + + let averageDiff = (initialDiff + nextDiff) / 2n; + if (averageDiff > targetAmount) { + averageDiff = targetAmount; + } + const taxBps = (_taxBasisPoints * averageDiff) / targetAmount; + return _feeBasisPoints + taxBps; + } +} diff --git a/src/dex/QuickPerps/vault.ts b/src/dex/QuickPerps/vault.ts new file mode 100644 index 000000000..725d3f27f --- /dev/null +++ b/src/dex/QuickPerps/vault.ts @@ -0,0 +1,290 @@ +import _ from 'lodash'; +import { Interface } from '@ethersproject/abi'; +import { AsyncOrSync, DeepReadonly } from 'ts-essentials'; +import { PartialEventSubscriber } from '../../composed-event-subscriber'; +import { Lens } from '../../lens'; +import VaultABI from '../../abi/quickPerps/vault.json'; +import { VaultUtils } from './vault-utils'; +import { + VaultConfig, + VaultState, + FastPriceFeedConfig, + PoolState, +} from './types'; +import { VaultPriceFeed } from './vault-price-feed'; +import { USDQ } from './usdq'; +import { + MultiCallInput, + MultiCallOutput, + Address, + Logger, + Log, +} from '../../types'; +import { BlockHeader } from 'web3-eth'; + +export class Vault extends PartialEventSubscriber { + static readonly interface: Interface = new Interface(VaultABI); + + protected vaultUtils: VaultUtils; + + public tokenDecimals: { [address: string]: number }; + public stableTokens: { [address: string]: boolean }; + protected tokenWeights: { [address: string]: bigint }; + public stableSwapFeeBasisPoints: bigint; + public swapFeeBasisPoints: bigint; + public stableTaxBasisPoints: bigint; + public taxBasisPoints: bigint; + public hasDynamicFees: bigint; + protected includeAmmPrice: boolean; + protected useSwapPricing: boolean; + protected totalTokenWeights: bigint; + + constructor( + public readonly vaultAddress: Address, + protected tokenAddresses: Address[], + config: VaultConfig, + protected vaultPriceFeed: VaultPriceFeed, + protected usdq: USDQ, + lens: Lens, DeepReadonly>, + logger: Logger, + ) { + super([vaultAddress], lens, logger); + this.vaultUtils = new VaultUtils(this); + this.tokenDecimals = config.tokenDecimals; + this.stableTokens = config.stableTokens; + this.tokenWeights = config.tokenWeights; + this.stableSwapFeeBasisPoints = config.stableSwapFeeBasisPoints; + this.swapFeeBasisPoints = config.swapFeeBasisPoints; + this.stableTaxBasisPoints = config.stableTaxBasisPoints; + this.taxBasisPoints = config.taxBasisPoints; + this.hasDynamicFees = config.hasDynamicFees; + this.includeAmmPrice = config.includeAmmPrice; + this.useSwapPricing = config.useSwapPricing; + this.totalTokenWeights = config.totalTokenWeights; + } + + getMinPrice(state: DeepReadonly, _token: Address): bigint { + return this.vaultPriceFeed.getPrice( + state, + _token, + false, + this.includeAmmPrice, + this.useSwapPricing, + ); + } + + getMaxPrice(state: DeepReadonly, _token: Address): bigint { + return this.vaultPriceFeed.getPrice( + state, + _token, + true, + this.includeAmmPrice, + this.useSwapPricing, + ); + } + + getFeeBasisPoints( + state: DeepReadonly, + _token: Address, + _usdqDelta: bigint, + _feeBasisPoints: bigint, + _taxBasisPoints: bigint, + _increment: boolean, + ): bigint { + return this.vaultUtils.getFeeBasisPoints( + state, + _token, + _usdqDelta, + _feeBasisPoints, + _taxBasisPoints, + _increment, + ); + } + + getTargetUsdqAmount(state: DeepReadonly, _token: Address): bigint { + const supply = this.usdq.getTotalSupply(state); + if (supply == 0n) { + return 0n; + } + const weight = this.tokenWeights[_token]; + return (weight * supply) / this.totalTokenWeights; + } + + static getConfigMulticallInputs( + vaultAddress: Address, + tokenAddresses: Address[], + ): MultiCallInput[] { + return [ + ...tokenAddresses.map(t => ({ + target: vaultAddress, + callData: Vault.interface.encodeFunctionData('tokenDecimals', [t]), + })), + ...tokenAddresses.map(t => ({ + target: vaultAddress, + callData: Vault.interface.encodeFunctionData('stableTokens', [t]), + })), + ...tokenAddresses.map(t => ({ + target: vaultAddress, + callData: Vault.interface.encodeFunctionData('tokenWeights', [t]), + })), + ...[ + 'stableSwapFeeBasisPoints', + 'swapFeeBasisPoints', + 'stableTaxBasisPoints', + 'taxBasisPoints', + 'hasDynamicFees', + 'includeAmmPrice', + 'useSwapPricing', + 'totalTokenWeights', + ].map(fn => ({ + target: vaultAddress, + callData: Vault.interface.encodeFunctionData(fn), + })), + ]; + } + + static getConfig( + multicallOutputs: MultiCallOutput[], + tokenAddresses: Address[], + ): VaultConfig { + let i = 0; + return { + tokenDecimals: tokenAddresses.reduce( + (acc: { [address: string]: number }, t: Address) => { + acc[t] = parseInt( + Vault.interface + .decodeFunctionResult('tokenDecimals', multicallOutputs[i++])[0] + .toString(), + ); + return acc; + }, + {}, + ), + stableTokens: tokenAddresses.reduce( + (acc: { [address: string]: boolean }, t: Address) => { + acc[t] = Vault.interface.decodeFunctionResult( + 'stableTokens', + multicallOutputs[i++], + )[0]; + return acc; + }, + {}, + ), + tokenWeights: tokenAddresses.reduce( + (acc: { [address: string]: bigint }, t: Address) => { + acc[t] = BigInt( + Vault.interface + .decodeFunctionResult('tokenWeights', multicallOutputs[i++])[0] + .toString(), + ); + return acc; + }, + {}, + ), + stableSwapFeeBasisPoints: BigInt( + Vault.interface + .decodeFunctionResult( + 'stableSwapFeeBasisPoints', + multicallOutputs[i++], + )[0] + .toString(), + ), + swapFeeBasisPoints: BigInt( + Vault.interface + .decodeFunctionResult('swapFeeBasisPoints', multicallOutputs[i++])[0] + .toString(), + ), + stableTaxBasisPoints: BigInt( + Vault.interface + .decodeFunctionResult( + 'stableTaxBasisPoints', + multicallOutputs[i++], + )[0] + .toString(), + ), + taxBasisPoints: BigInt( + Vault.interface + .decodeFunctionResult('taxBasisPoints', multicallOutputs[i++])[0] + .toString(), + ), + hasDynamicFees: Vault.interface.decodeFunctionResult( + 'hasDynamicFees', + multicallOutputs[i++], + )[0], + includeAmmPrice: Vault.interface.decodeFunctionResult( + 'includeAmmPrice', + multicallOutputs[i++], + )[0], + useSwapPricing: Vault.interface.decodeFunctionResult( + 'useSwapPricing', + multicallOutputs[i++], + )[0], + totalTokenWeights: BigInt( + Vault.interface + .decodeFunctionResult('totalTokenWeights', multicallOutputs[i++])[0] + .toString(), + ), + }; + } + + public getGenerateStateMultiCallInputs(): MultiCallInput[] { + return this.tokenAddresses.map((t: Address) => ({ + target: this.vaultAddress, + callData: Vault.interface.encodeFunctionData('usdqAmounts', [t]), + })); + } + + public generateState( + multicallOutputs: MultiCallOutput[], + blockNumber?: number | 'latest', + ): DeepReadonly { + let vaultState: VaultState = { + usdqAmounts: {}, + }; + this.tokenAddresses.forEach( + (t: Address, i: number) => + (vaultState.usdqAmounts[t] = BigInt( + Vault.interface + .decodeFunctionResult('usdqAmounts', multicallOutputs[i])[0] + .toString(), + )), + ); + return vaultState; + } + + public getUSDQAmount(state: DeepReadonly, token: Address): bigint { + return this.lens.get()(state).usdqAmounts[token]; + } + + public processLog( + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ): AsyncOrSync { + try { + const parsed = Vault.interface.parseLog(log); + const _state: VaultState = _.cloneDeep(state); + switch (parsed.name) { + case 'IncreaseUsdqAmount': { + const tokenAddress = parsed.args.token.toLowerCase(); + const amount = BigInt(parsed.args.amount.toString()); + if (tokenAddress in state.usdqAmounts) + _state.usdqAmounts[tokenAddress] += amount; + return _state; + } + case 'DecreaseUsdqAmount': { + const tokenAddress = parsed.args.token.toLowerCase(); + const amount = BigInt(parsed.args.amount.toString()); + if (tokenAddress in state.usdqAmounts) + _state.usdqAmounts[tokenAddress] -= amount; + return _state; + } + default: + return null; + } + } catch (e) { + this.logger.error('Failed to parse log', e); + return null; + } + } +} diff --git a/src/dex/gmx/config.ts b/src/dex/gmx/config.ts index 83f115d6c..5ae6f1a9d 100644 --- a/src/dex/gmx/config.ts +++ b/src/dex/gmx/config.ts @@ -21,16 +21,6 @@ export const GMXConfig: DexConfigMap = { usdg: '0x45096e7aA921f27590f8F19e457794EB09678141', }, }, - QuickPerps: { - [Network.ZKEVM]: { - vault: '0x99B31498B0a1Dae01fc3433e3Cb60F095340935C', - reader: '0xf1CFB75854DE535475B88Bb6FBad317eea98c0F9', - priceFeed: '0x5b1F500134bdD7f4359F5B2adC65f839737290f4', - fastPriceFeed: '0x73903fEc691a80Ec47bc830bf3F0baD127A06e30', - fastPriceEvents: '0x08bC8ef0b71238055f9Ee6BBc90869D8d0DBdCCa', - usdg: '0x48aC594dd00c4aAcF40f83337fc6dA31F9F439A7', - }, - }, }; export const Adapters: { @@ -54,12 +44,4 @@ export const Adapters: { }, ], }, - [Network.ZKEVM]: { - [SwapSide.SELL]: [ - { - name: 'PolygonZkEvmAdapter01', - index: 2, - }, - ], - }, }; diff --git a/src/dex/gmx/gmx.ts b/src/dex/gmx/gmx.ts index 71ea31083..2372dbea8 100644 --- a/src/dex/gmx/gmx.ts +++ b/src/dex/gmx/gmx.ts @@ -1,5 +1,4 @@ import { Interface } from '@ethersproject/abi'; -import _ from 'lodash'; import { Token, Address, @@ -35,7 +34,7 @@ export class GMX extends SimpleExchange implements IDex { readonly isFeeOnTransferSupported = false; public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = - getDexKeysWithNetwork(_.pick(GMXConfig, ['GMX', 'QuickPerps'])); + getDexKeysWithNetwork(GMXConfig); public static erc20Interface = new Interface(ERC20ABI); From cf5c965abbab922ddf09dbf1466e6ef1d23e7541 Mon Sep 17 00:00:00 2001 From: Verisana Date: Thu, 24 Aug 2023 14:42:50 +0100 Subject: [PATCH 04/45] feat: implement force regenerate state if EP is not working for zkEVM --- src/dex/algebra/algebra.ts | 2 +- src/stateful-event-subscriber.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/dex/algebra/algebra.ts b/src/dex/algebra/algebra.ts index 0cbe60fd5..0d00e79c6 100644 --- a/src/dex/algebra/algebra.ts +++ b/src/dex/algebra/algebra.ts @@ -167,7 +167,7 @@ export class Algebra extends SimpleExchange implements IDex { /* reload state, on zkEVM this would most likely timeout during request life * but would allow to rely on staleState for couple of min for next requests */ - await pool.initialize(blockNumber); + await pool.initialize(blockNumber, { forceRegenerate: true }); } return pool; diff --git a/src/stateful-event-subscriber.ts b/src/stateful-event-subscriber.ts index 313cfa6fb..e76e47ffc 100644 --- a/src/stateful-event-subscriber.ts +++ b/src/stateful-event-subscriber.ts @@ -18,6 +18,7 @@ type StateCache = { export type InitializeStateOptions = { state?: DeepReadonly; initCallback?: (state: DeepReadonly) => void; + forceRegenerate?: boolean; }; export abstract class StatefulEventSubscriber @@ -89,6 +90,13 @@ export abstract class StatefulEventSubscriber let masterBn: undefined | number = undefined; if (options && options.state) { this.setState(options.state, blockNumber); + } else if (options && options.forceRegenerate) { + // ZkEVM forces to always regenerate state when it is old + this.logger.debug( + `${this.parentName}: ${this.name}: forced to regenerate state`, + ); + const state = await this.generateState(blockNumber); + this.setState(state, blockNumber); } else { if (this.dexHelper.config.isSlave && this.masterPoolNeeded) { let stateAsString = await this.dexHelper.cache.hget( From 6af2f7c12f45e7695324cb760f936200b0c9304f Mon Sep 17 00:00:00 2001 From: Verisana Date: Thu, 24 Aug 2023 14:48:17 +0100 Subject: [PATCH 05/45] 2.31.4-zkevm-revive.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0f431afad..2e09436b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.3", + "version": "2.31.4-zkevm-revive.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 3540849c08288b04c88827debbc24b511ac70141 Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 15:39:57 +0100 Subject: [PATCH 06/45] feat: implement manual state fetching for algebra pool --- src/abi/algebra/AlgebraFactory-v1_1.abi.json | 250 +++++++++++++++++++ src/config.ts | 30 +++ src/dex/algebra/algebra-integration.test.ts | 74 ++++++ src/dex/algebra/algebra-pool-v1_1.ts | 200 ++++++++++++++- src/dex/algebra/algebra.ts | 3 +- src/dex/algebra/types.ts | 5 +- src/dex/algebra/utils.ts | 85 ++++++- src/lib/decoders.ts | 12 + 8 files changed, 646 insertions(+), 13 deletions(-) create mode 100644 src/abi/algebra/AlgebraFactory-v1_1.abi.json diff --git a/src/abi/algebra/AlgebraFactory-v1_1.abi.json b/src/abi/algebra/AlgebraFactory-v1_1.abi.json new file mode 100644 index 000000000..c582f39e2 --- /dev/null +++ b/src/abi/algebra/AlgebraFactory-v1_1.abi.json @@ -0,0 +1,250 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_poolDeployer", "type": "address" }, + { "internalType": "address", "name": "_vaultAddress", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newFarmingAddress", + "type": "address" + } + ], + "name": "FarmingAddress", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "alpha1", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "alpha2", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "beta1", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "beta2", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "gamma1", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "gamma2", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "volumeBeta", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "volumeGamma", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "baseFee", + "type": "uint16" + } + ], + "name": "FeeConfiguration", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "Owner", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "Pool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newVaultAddress", + "type": "address" + } + ], + "name": "VaultAddress", + "type": "event" + }, + { + "inputs": [], + "name": "baseFeeConfiguration", + "outputs": [ + { "internalType": "uint16", "name": "alpha1", "type": "uint16" }, + { "internalType": "uint16", "name": "alpha2", "type": "uint16" }, + { "internalType": "uint32", "name": "beta1", "type": "uint32" }, + { "internalType": "uint32", "name": "beta2", "type": "uint32" }, + { "internalType": "uint16", "name": "gamma1", "type": "uint16" }, + { "internalType": "uint16", "name": "gamma2", "type": "uint16" }, + { "internalType": "uint32", "name": "volumeBeta", "type": "uint32" }, + { "internalType": "uint16", "name": "volumeGamma", "type": "uint16" }, + { "internalType": "uint16", "name": "baseFee", "type": "uint16" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "tokenA", "type": "address" }, + { "internalType": "address", "name": "tokenB", "type": "address" } + ], + "name": "createPool", + "outputs": [ + { "internalType": "address", "name": "pool", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "farmingAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "poolByPair", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "poolDeployer", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint16", "name": "alpha1", "type": "uint16" }, + { "internalType": "uint16", "name": "alpha2", "type": "uint16" }, + { "internalType": "uint32", "name": "beta1", "type": "uint32" }, + { "internalType": "uint32", "name": "beta2", "type": "uint32" }, + { "internalType": "uint16", "name": "gamma1", "type": "uint16" }, + { "internalType": "uint16", "name": "gamma2", "type": "uint16" }, + { "internalType": "uint32", "name": "volumeBeta", "type": "uint32" }, + { "internalType": "uint16", "name": "volumeGamma", "type": "uint16" }, + { "internalType": "uint16", "name": "baseFee", "type": "uint16" } + ], + "name": "setBaseFeeConfiguration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_farmingAddress", + "type": "address" + } + ], + "name": "setFarmingAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_owner", "type": "address" } + ], + "name": "setOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_vaultAddress", "type": "address" } + ], + "name": "setVaultAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vaultAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/config.ts b/src/config.ts index 45b466f84..399298705 100644 --- a/src/config.ts +++ b/src/config.ts @@ -317,6 +317,36 @@ const baseConfigs: { [network: number]: BaseConfig } = { rpcPollingBlocksBackToTriggerUpdate: 3, forceRpcFallbackDexs: [], }, + + [Network.ZKEVM]: { + network: Network.ZKEVM, + networkName: 'Polygon zkEVM', + isTestnet: false, + nativeTokenName: 'Ether', + nativeTokenSymbol: 'ETH', + wrappedNativeTokenAddress: '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', + hasEIP1559: true, + augustusAddress: '0xB83B554730d29cE4Cb55BB42206c3E2c03E4A40A', + augustusRFQAddress: '0x7Ee1F7fa4C0b2eDB0Fdd5944c14A07167700486E', + tokenTransferProxyAddress: '0xc8a21fcd5a100c3ecc037c97e2f9c53a8d3a02a1', + multicallV2Address: '0x6cA478C852DfA8941FC819fDf248606eA04780B6', + privateHttpProvider: process.env.HTTP_PROVIDER_1101, + adapterAddresses: { + PolygonZkEvmAdapter01: '0x760870be538e7b4E2110e2890473CC17ADB1fdC1', + PolygonZkEvmBuyAdapter: '0x61bFA60A992472869F8d7b4705609A6E0BCf383f', + }, + + rpcPollingMaxAllowedStateDelayInBlocks: 0, + rpcPollingBlocksBackToTriggerUpdate: 0, + hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', + hashFlowDisabledMMs: + process.env[`HASHFLOW_DISABLED_MMS_10`]?.split(',') || [], + uniswapV3EventLoggingSampleRate: 0, + rfqConfigs: {}, + forceRpcFallbackDexs: [], + // FIXME: Not set properly + uniswapV2ExchangeRouterAddress: '', + }, }; // Should not be used, except by internal test code diff --git a/src/dex/algebra/algebra-integration.test.ts b/src/dex/algebra/algebra-integration.test.ts index 54c674fb7..20b8ef98a 100644 --- a/src/dex/algebra/algebra-integration.test.ts +++ b/src/dex/algebra/algebra-integration.test.ts @@ -14,6 +14,8 @@ import { } from '../../../tests/utils'; import { Tokens } from '../../../tests/constants-e2e'; import { Address } from '@paraswap/core'; +import { AlgebraEventPoolV1_1 } from './algebra-pool-v1_1'; +import { DecodedStateMultiCallResultWithRelativeBitmapsV1_1 } from './types'; function getReaderCalldata( exchangeAddress: string, @@ -432,5 +434,77 @@ describe('Algebra', function () { ); } }); + + it('both generate state result match', async function () { + const pool = (await algebra.getPool( + tokens[srcTokenSymbol].address, + tokens[destTokenSymbol].address, + blockNumber, + )) as AlgebraEventPoolV1_1; + + const [balance0, balance1, stateMulticallFull] = + await pool.fetchPoolStateSingleStep(blockNumber); + + const stateMulticall = { + pool: stateMulticallFull.pool.toLowerCase(), + globalState: { + price: stateMulticallFull.globalState.price, + tick: stateMulticallFull.globalState.tick, + fee: stateMulticallFull.globalState.fee, + communityFeeToken0: stateMulticallFull.globalState.communityFeeToken0, + communityFeeToken1: stateMulticallFull.globalState.communityFeeToken1, + }, + liquidity: stateMulticallFull.liquidity, + tickSpacing: stateMulticallFull.tickSpacing, + maxLiquidityPerTick: stateMulticallFull.maxLiquidityPerTick, + tickBitmap: stateMulticallFull.tickBitmap.map(t => ({ + index: t.index, + value: t.value, + })), + ticks: stateMulticallFull.ticks.map(t => ({ + index: t.index, + value: { + liquidityNet: t.value.liquidityNet, + liquidityGross: t.value.liquidityGross, + secondsOutside: t.value.secondsOutside, + secondsPerLiquidityOutsideX128: + t.value.secondsPerLiquidityOutsideX128, + tickCumulativeOutside: t.value.tickCumulativeOutside, + initialized: t.value.initialized, + }, + })), + }; + + const stateMulticallWithBalance = [balance0, balance1, stateMulticall]; + const stateManually = await pool.fetchStateManually(blockNumber); + // @ts-ignore + delete stateManually[2]['blockTimestamp']; + stateManually[2].pool = stateManually[2].pool.toLowerCase(); + + expect(stateMulticallWithBalance).toStrictEqual(stateManually); + }); + }); + + describe('ZKEVM', () => { + const network = Network.ZKEVM; + const dexHelper = new DummyDexHelper(network); + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + algebra = new Algebra(network, dexKey, dexHelper); + if (algebra.initializePricing) { + await algebra.initializePricing(blockNumber); + } + }); + + it('both generate state result match', async function () { + const pool = (await algebra.getPool( + '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', + '0xc5015b9d9161dca7e18e32f6f25c4ad850731fd4', + blockNumber, + )) as AlgebraEventPoolV1_1; + + const stateManually = await pool.fetchStateManually(blockNumber); + }); }); }); diff --git a/src/dex/algebra/algebra-pool-v1_1.ts b/src/dex/algebra/algebra-pool-v1_1.ts index 2b9746de5..2548123be 100644 --- a/src/dex/algebra/algebra-pool-v1_1.ts +++ b/src/dex/algebra/algebra-pool-v1_1.ts @@ -2,29 +2,41 @@ import _ from 'lodash'; import { Interface } from '@ethersproject/abi'; import { DeepReadonly, assert } from 'ts-essentials'; import { Address, BlockHeader, Log, Logger } from '../../types'; -import { bigIntify, catchParseLogError, int16 } from '../../utils'; +import { bigIntify, catchParseLogError, int16, uint128 } from '../../utils'; import { InitializeStateOptions, StatefulEventSubscriber, } from '../../stateful-event-subscriber'; import { IDexHelper } from '../../dex-helper/idex-helper'; import { + DecodedGlobalStateV1_1, PoolStateV1_1, TickBitMapMappingsWithBigNumber, TickInfoMappingsWithBigNumber, + TickInfoWithBigNumber, } from './types'; -import { ethers } from 'ethers'; +import { BigNumber, ethers } from 'ethers'; import { Contract } from 'web3-eth-contract'; import AlgebraABI from '../../abi/algebra/AlgebraPool-v1_1.abi.json'; +import FactoryABI from '../../abi/algebra/AlgebraFactory-v1_1.abi.json'; import { DecodedStateMultiCallResultWithRelativeBitmapsV1_1 } from './types'; import { OUT_OF_RANGE_ERROR_POSTFIX, TICK_BITMAP_BUFFER, TICK_BITMAP_TO_USE, } from '../uniswap-v3/constants'; -import { uint256ToBigInt } from '../../lib/decoders'; +import { + addressDecode, + uint256ToBigInt, + uint128ToBigInt, + int24ToBigInt, +} from '../../lib/decoders'; import { MultiCallParams } from '../../lib/multi-wrapper'; -import { decodeStateMultiCallResultWithRelativeBitmapsV1_1 } from './utils'; +import { + decodeGlobalStateV1_1, + decodeStateMultiCallResultWithRelativeBitmapsV1_1, + decodeTicksV1_1, +} from './utils'; import { AlgebraMath } from './lib/AlgebraMath'; import { _reduceTickBitmap, @@ -53,6 +65,7 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber readonly token1: Address; public readonly poolIface = new Interface(AlgebraABI); + public readonly factoryIface = new Interface(FactoryABI); public initFailed = false; public initRetryAttemptCount = 0; @@ -69,6 +82,7 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber mapKey: string = '', readonly poolInitCodeHash: string, readonly poolDeployer: string, + private readonly forceManualStateGeneration: boolean = false, ) { super(parentName, `${token0}_${token1}`, dexHelper, logger, true, mapKey); this.token0 = token0.toLowerCase(); @@ -174,7 +188,7 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber return TICK_BITMAP_TO_USE + TICK_BITMAP_BUFFER; } - private async _fetchPoolStateSingleStep( + async fetchPoolStateSingleStep( blockNumber: number, ): Promise< [bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_1] @@ -330,13 +344,14 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber return [balance0, balance1, _state]; } + // FIXME: Here happens double conversion in types, but for prototyping and to check if this approach helps, it is nor very important async _fetchInitStateMultiStrategies( blockNumber: number, ): Promise< [bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_1] > { try { - return await this._fetchPoolStateSingleStep(blockNumber); + return await this.fetchPoolStateSingleStep(blockNumber); } catch (e) { if (e instanceof Error && e.message.includes('Pool does not exist')) throw e; @@ -347,9 +362,178 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber } } + async fetchStateManually( + blockNumber: number, + ): Promise< + [bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_1] + > { + // FIXME: If this approach works, need to add caching of multicalls + const [poolAddress] = await this.dexHelper.multiWrapper.aggregate([ + { + target: this.factoryAddress, + callData: this.factoryIface.encodeFunctionData('poolByPair', [ + this.token0, + this.token1, + ]), + decodeFunction: addressDecode, + }, + ]); + assert( + poolAddress === this.poolAddress, + `Pool address mismatch: ${poolAddress} != ${this.poolAddress}`, + ); + + const [ + balance0, + balance1, + liquidity, + tickSpacing, + maxLiquidityPerTick, + globalState, + ] = (await this.dexHelper.multiWrapper.aggregate< + bigint | number | DecodedGlobalStateV1_1 + >([ + { + target: this.token0, + callData: this.erc20Interface.encodeFunctionData('balanceOf', [ + this.poolAddress, + ]), + decodeFunction: uint256ToBigInt, + }, + { + target: this.token1, + callData: this.erc20Interface.encodeFunctionData('balanceOf', [ + this.poolAddress, + ]), + decodeFunction: uint256ToBigInt, + }, + { + target: poolAddress, + callData: this.poolIface.encodeFunctionData('liquidity', []), + decodeFunction: uint128ToBigInt, + }, + { + target: poolAddress, + callData: this.poolIface.encodeFunctionData('tickSpacing', []), + decodeFunction: int24ToBigInt, + }, + { + target: poolAddress, + callData: this.poolIface.encodeFunctionData('maxLiquidityPerTick', []), + decodeFunction: uint128ToBigInt, + }, + { + target: poolAddress, + callData: this.poolIface.encodeFunctionData('globalState', []), + decodeFunction: decodeGlobalStateV1_1, + }, + ])) as [bigint, bigint, bigint, bigint, bigint, DecodedGlobalStateV1_1]; + + const currentBitMapIndex = TickTable.position( + BigInt(BigInt(globalState.tick) / BigInt(tickSpacing)), + )[0]; + + const leftBitMapIndex = currentBitMapIndex - this.getBitmapRangeToRequest(); + const rightBitMapIndex = + currentBitMapIndex + this.getBitmapRangeToRequest(); + + const allTickBitMaps = await this.dexHelper.multiWrapper.aggregate( + _.range(Number(leftBitMapIndex), Number(rightBitMapIndex + 1n)).map( + index => { + return { + target: poolAddress, + callData: this.poolIface.encodeFunctionData('tickTable', [ + int16(BigInt(index)), + ]), + decodeFunction: uint256ToBigInt, + }; + }, + ), + ); + + const tickBitmap: TickBitMapMappingsWithBigNumber[] = []; + + let globalIndex = 0; + + for (let i = leftBitMapIndex; i <= rightBitMapIndex; i++) { + const index = int16(i); + const bitmap = allTickBitMaps[globalIndex]; + globalIndex++; + if (bitmap == 0n) continue; + tickBitmap.push({ index: Number(index), value: BigNumber.from(bitmap) }); + } + + const tickIndexes: bigint[] = []; + const ticksValues = await this.dexHelper.multiWrapper.aggregate( + tickBitmap + .map(tb => { + const allBits: MultiCallParams[] = []; + if (tb.value === BigNumber.from(0)) return allBits; + + _.range(0, 256).forEach(j => { + if ((tb.value.toBigInt() & (1n << BigInt(j))) > 0n) { + const populatedTick = + (BigInt.asIntN(16, BigInt(tb.index) << 8n) + BigInt(j)) * + tickSpacing; + + tickIndexes.push(populatedTick); + allBits.push({ + target: poolAddress, + callData: this.poolIface.encodeFunctionData('ticks', [ + populatedTick, + ]), + decodeFunction: decodeTicksV1_1, + }); + } + }); + return allBits; + }) + .flat(), + ); + assert( + tickIndexes.length === ticksValues.length, + `Tick indexes mismatch: ${tickIndexes.length} != ${ticksValues.length}`, + ); + + const ticks: TickInfoMappingsWithBigNumber[] = new Array( + tickIndexes.length, + ); + + tickIndexes.forEach((tickIndex, index) => { + ticks[index] = { + index: Number(tickIndex), + value: ticksValues[index], + }; + }); + + return [ + balance0, + balance1, + // FIXME: If we validate that this is working, remove redundant conversions + { + pool: poolAddress, + blockTimestamp: BigNumber.from(Date.now()), + globalState, + liquidity: BigNumber.from(liquidity), + tickSpacing: Number(tickSpacing), + maxLiquidityPerTick: BigNumber.from(maxLiquidityPerTick), + tickBitmap, + ticks, + }, + ]; + } + async generateState(blockNumber: number): Promise> { - const [balance0, balance1, _state] = - await this._fetchInitStateMultiStrategies(blockNumber); + let balance0 = 0n; + let balance1 = 0n; + let _state: DecodedStateMultiCallResultWithRelativeBitmapsV1_1; + if (this.forceManualStateGeneration) { + [balance0, balance1, _state] = await this.fetchStateManually(blockNumber); + } else { + [balance0, balance1, _state] = await this._fetchInitStateMultiStrategies( + blockNumber, + ); + } const tickBitmap = {}; const ticks = {}; diff --git a/src/dex/algebra/algebra.ts b/src/dex/algebra/algebra.ts index 0d00e79c6..3b978c80b 100644 --- a/src/dex/algebra/algebra.ts +++ b/src/dex/algebra/algebra.ts @@ -226,6 +226,7 @@ export class Algebra extends SimpleExchange implements IDex { this.cacheStateKey, this.config.initHash, this.config.deployer, + this.config.forceManualStateGenerate, ); try { @@ -258,7 +259,7 @@ export class Algebra extends SimpleExchange implements IDex { // on unkown error mark as failed and increase retryCount for retry init strategy // note: state would be null by default which allows to fallback this.logger.warn( - `${this.dexKey}: Can not generate pool state for srcAddress=${srcAddress}, destAddress=${destAddress}pool fallback to rpc and retry every ${this.config.initRetryFrequency} times, initRetryAttemptCount=${pool.initRetryAttemptCount}`, + `${this.dexKey}: Can not generate pool state for srcAddress=${srcAddress}, destAddress=${destAddress} pool fallback to rpc and retry every ${this.config.initRetryFrequency} times, initRetryAttemptCount=${pool.initRetryAttemptCount}`, e, ); pool.initFailed = true; diff --git a/src/dex/algebra/types.ts b/src/dex/algebra/types.ts index 98a8c1d33..891983100 100644 --- a/src/dex/algebra/types.ts +++ b/src/dex/algebra/types.ts @@ -2,7 +2,7 @@ import { BigNumber } from 'ethers'; import { Address, NumberAsString } from '../../types'; import { TickInfo } from '../uniswap-v3/types'; -type GlobalStateV1_1 = { +export type GlobalStateV1_1 = { price: bigint; // The square root of the current price in Q64.96 format tick: bigint; // The current tick fee: bigint; // The current fee in hundredths of a bip, i.e. 1e-6 @@ -72,6 +72,7 @@ export type DexParams = { initHash: string; version: 'v1.1' | 'v1.9'; forceRPC?: boolean; + forceManualStateGenerate?: boolean; }; export type IAlgebraPoolState = PoolStateV1_1 | PoolState_v1_9; @@ -95,7 +96,7 @@ export type TickInfoMappingsWithBigNumber = { value: TickInfoWithBigNumber; }; -type DecodedGlobalStateV1_1 = { +export type DecodedGlobalStateV1_1 = { price: BigNumber; tick: number; fee: number; diff --git a/src/dex/algebra/utils.ts b/src/dex/algebra/utils.ts index b0f7f73e5..05b5962c1 100644 --- a/src/dex/algebra/utils.ts +++ b/src/dex/algebra/utils.ts @@ -1,11 +1,12 @@ -import { BytesLike, ethers } from 'ethers'; +import { BigNumber, BytesLike, ethers } from 'ethers'; import { assert } from 'ts-essentials'; import { extractSuccessAndValue } from '../../lib/decoders'; import { MultiResult } from '../../lib/multi-wrapper'; import { - DecodedGlobalStateV1_9, + DecodedGlobalStateV1_1, DecodedStateMultiCallResultWithRelativeBitmapsV1_1, DecodedStateMultiCallResultWithRelativeBitmapsV1_9, + TickInfoWithBigNumber, } from './types'; export function decodeStateMultiCallResultWithRelativeBitmapsV1_1( @@ -134,3 +135,83 @@ export function decodeStateMultiCallResultWithRelativeBitmapsV1_9( // But I typed only the ones that are used later return decoded as DecodedStateMultiCallResultWithRelativeBitmapsV1_9; } + +export function decodeGlobalStateV1_1( + result: MultiResult | BytesLike, +): DecodedGlobalStateV1_1 { + const [isSuccess, toDecode] = extractSuccessAndValue(result); + + assert( + isSuccess && toDecode !== '0x', + `decodeGlobalStateV1_1 failed to get decodable result: ${result}`, + ); + + const results: DecodedGlobalStateV1_1 = { + price: BigNumber.from(0), + tick: 0, + fee: 0, + communityFeeToken0: 0, + communityFeeToken1: 0, + }; + + [ + results.price, + results.tick, + results.fee, + , + results.communityFeeToken0, + results.communityFeeToken1, + ] = ethers.utils.defaultAbiCoder.decode( + [`uint160`, `int24`, `uint16`, `uint16`, `uint8`, `uint8`, `bool`], + toDecode, + ); + // This conversion is not precise, because when we decode, we have more values + // But used later + return results; +} + +export function decodeTicksV1_1( + result: MultiResult | BytesLike, +): TickInfoWithBigNumber { + const [isSuccess, toDecode] = extractSuccessAndValue(result); + + assert( + isSuccess && toDecode !== '0x', + `decodeGlobalStateV1_1 failed to get decodable result: ${result}`, + ); + + const results: TickInfoWithBigNumber = { + liquidityNet: BigNumber.from(0), + liquidityGross: BigNumber.from(0), + secondsOutside: 0, + secondsPerLiquidityOutsideX128: BigNumber.from(0), + tickCumulativeOutside: BigNumber.from(0), + initialized: false, + }; + + [ + results.liquidityGross, + results.liquidityNet, + , + , + results.tickCumulativeOutside, + results.secondsPerLiquidityOutsideX128, + results.secondsOutside, + results.initialized, + ] = ethers.utils.defaultAbiCoder.decode( + [ + `uint128`, + `int128`, + `uint256`, + `uint256`, + `int256`, + `uint160`, + `uint32`, + `bool`, + ], + toDecode, + ); + // This conversion is not precise, because when we decode, we have more values + // But used later + return results; +} diff --git a/src/lib/decoders.ts b/src/lib/decoders.ts index ebafa3401..6c2453b0c 100644 --- a/src/lib/decoders.ts +++ b/src/lib/decoders.ts @@ -50,6 +50,18 @@ export const uint256ToBigInt = ( return generalDecoder(result, ['uint256'], 0n, value => value[0].toBigInt()); }; +export const uint128ToBigInt = ( + result: MultiResult | BytesLike, +): bigint => { + return generalDecoder(result, ['uint128'], 0n, value => value[0].toBigInt()); +}; + +export const int24ToBigInt = ( + result: MultiResult | BytesLike, +): bigint => { + return generalDecoder(result, ['int24'], 0n, value => BigInt(value[0])); +}; + export const uint256ArrayDecode = ( result: MultiResult | BytesLike, ): bigint => { From 9e58585dafbe6345a344ee7ac05d356a8ac4c99f Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 15:42:05 +0100 Subject: [PATCH 07/45] 2.31.4-zkevm-revive.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2e09436b7..b9b13bcc3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.0", + "version": "2.31.4-zkevm-revive.1", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From bc00ee233b4c07c19260a0c101191bb93d800011 Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 16:03:42 +0100 Subject: [PATCH 08/45] fix: set boolean flag to true --- src/dex/algebra/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dex/algebra/config.ts b/src/dex/algebra/config.ts index 9b6147a17..5809d2037 100644 --- a/src/dex/algebra/config.ts +++ b/src/dex/algebra/config.ts @@ -33,6 +33,7 @@ export const AlgebraConfig: DexConfigMap = { uniswapMulticall: '0x61530d6E1c7A47BBB3e48e8b8EdF7569DcFeE121', deployer: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', version: 'v1.1', + forceManualStateGenerate: true, }, }, ZyberSwapV3: { From 0082148a0592dedb755f4a496b1af558782fb5b2 Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 16:05:06 +0100 Subject: [PATCH 09/45] 2.31.4-zkevm-revive.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b9b13bcc3..b26d0e7a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.1", + "version": "2.31.4-zkevm-revive.2", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From a2dc773d7cc3691b28d111c4e891571f411929cd Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 16:12:51 +0100 Subject: [PATCH 10/45] fix: add new field to lower func --- src/dex/algebra/algebra.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dex/algebra/algebra.ts b/src/dex/algebra/algebra.ts index 3b978c80b..d824785e7 100644 --- a/src/dex/algebra/algebra.ts +++ b/src/dex/algebra/algebra.ts @@ -791,6 +791,7 @@ export class Algebra extends SimpleExchange implements IDex { subgraphURL: this.config.subgraphURL, version: this.config.version, forceRPC: this.config.forceRPC, + forceManualStateGenerate: this.config.forceManualStateGenerate, }; return newConfig; } From 300e0bdeb52109708f2ef217120e0357a399b4dd Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 16:13:24 +0100 Subject: [PATCH 11/45] 2.31.4-zkevm-revive.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b26d0e7a7..ace03f4e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.2", + "version": "2.31.4-zkevm-revive.3", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 75b45f806aa492cfa95476d4506716f4b60ea137 Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 16:19:06 +0100 Subject: [PATCH 12/45] fix: issue pool address case --- src/dex/algebra/algebra-pool-v1_1.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dex/algebra/algebra-pool-v1_1.ts b/src/dex/algebra/algebra-pool-v1_1.ts index 2548123be..4d36f0a14 100644 --- a/src/dex/algebra/algebra-pool-v1_1.ts +++ b/src/dex/algebra/algebra-pool-v1_1.ts @@ -380,7 +380,7 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber ]); assert( poolAddress === this.poolAddress, - `Pool address mismatch: ${poolAddress} != ${this.poolAddress}`, + `Pool address mismatch: ${poolAddress.toLowerCase()} != ${this.poolAddress.toLowerCase()}`, ); const [ From b8d981059332aeb6551fc743f0fad082aa04da48 Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 16:19:48 +0100 Subject: [PATCH 13/45] 2.31.4-zkevm-revive.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ace03f4e1..fd63b5bfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.3", + "version": "2.31.4-zkevm-revive.4", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From f5c31fa0d14e9234af32f0fc5fc49f935486e4a7 Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 16:24:00 +0100 Subject: [PATCH 14/45] fix: fix pool address mismatch --- src/dex/algebra/algebra-pool-v1_1.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dex/algebra/algebra-pool-v1_1.ts b/src/dex/algebra/algebra-pool-v1_1.ts index 4d36f0a14..cd11b8976 100644 --- a/src/dex/algebra/algebra-pool-v1_1.ts +++ b/src/dex/algebra/algebra-pool-v1_1.ts @@ -379,7 +379,7 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber }, ]); assert( - poolAddress === this.poolAddress, + poolAddress.toLowerCase() === this.poolAddress.toLowerCase(), `Pool address mismatch: ${poolAddress.toLowerCase()} != ${this.poolAddress.toLowerCase()}`, ); From 586d7811fb80f442205aa291a8cd9afcf8a2d9cc Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 16:24:39 +0100 Subject: [PATCH 15/45] 2.31.4-zkevm-revive.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fd63b5bfc..af7b7250f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.4", + "version": "2.31.4-zkevm-revive.5", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From b82d3a32e640737b45d8d7a4547dae4261053c65 Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 16:49:11 +0100 Subject: [PATCH 16/45] fix: split tick generating into 100 tick chunks --- src/dex/algebra/algebra-pool-v1_1.ts | 59 ++++++++++++++++------------ 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/src/dex/algebra/algebra-pool-v1_1.ts b/src/dex/algebra/algebra-pool-v1_1.ts index cd11b8976..0f9e3ccb1 100644 --- a/src/dex/algebra/algebra-pool-v1_1.ts +++ b/src/dex/algebra/algebra-pool-v1_1.ts @@ -464,32 +464,39 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber } const tickIndexes: bigint[] = []; - const ticksValues = await this.dexHelper.multiWrapper.aggregate( - tickBitmap - .map(tb => { - const allBits: MultiCallParams[] = []; - if (tb.value === BigNumber.from(0)) return allBits; - - _.range(0, 256).forEach(j => { - if ((tb.value.toBigInt() & (1n << BigInt(j))) > 0n) { - const populatedTick = - (BigInt.asIntN(16, BigInt(tb.index) << 8n) + BigInt(j)) * - tickSpacing; - - tickIndexes.push(populatedTick); - allBits.push({ - target: poolAddress, - callData: this.poolIface.encodeFunctionData('ticks', [ - populatedTick, - ]), - decodeFunction: decodeTicksV1_1, - }); - } - }); - return allBits; - }) - .flat(), - ); + + const tickRequests = tickBitmap + .map(tb => { + const allBits: MultiCallParams[] = []; + if (tb.value === BigNumber.from(0)) return allBits; + + _.range(0, 256).forEach(j => { + if ((tb.value.toBigInt() & (1n << BigInt(j))) > 0n) { + const populatedTick = + (BigInt.asIntN(16, BigInt(tb.index) << 8n) + BigInt(j)) * + tickSpacing; + + tickIndexes.push(populatedTick); + allBits.push({ + target: poolAddress, + callData: this.poolIface.encodeFunctionData('ticks', [ + populatedTick, + ]), + decodeFunction: decodeTicksV1_1, + }); + } + }); + return allBits; + }) + .flat(); + + const ticksValues = ( + await Promise.all( + _.chunk(tickRequests, 100).map(tickRequestChunk => + this.dexHelper.multiWrapper.aggregate(tickRequestChunk), + ), + ) + ).flat(); assert( tickIndexes.length === ticksValues.length, `Tick indexes mismatch: ${tickIndexes.length} != ${ticksValues.length}`, From 74b067c4506f0d5d27db54bcfa047cafa011d941 Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 16:49:50 +0100 Subject: [PATCH 17/45] 2.31.4-zkevm-revive.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af7b7250f..ea9060025 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.5", + "version": "2.31.4-zkevm-revive.6", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 3fa186ece102fdf2319886e40800f7457f1f5d8c Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 17:21:18 +0100 Subject: [PATCH 18/45] fix: sometimes stale is null regenerate in that case too --- src/dex/algebra/algebra.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dex/algebra/algebra.ts b/src/dex/algebra/algebra.ts index d824785e7..0178f2307 100644 --- a/src/dex/algebra/algebra.ts +++ b/src/dex/algebra/algebra.ts @@ -160,9 +160,10 @@ export class Algebra extends SimpleExchange implements IDex { if (this.network !== Network.ZKEVM) return pool; if ( - pool.getState(blockNumber) === null && - blockNumber - pool.getStateBlockNumber() > - MAX_STALE_STATE_BLOCK_AGE[this.network] + pool.getStaleState() === null || + (pool.getState(blockNumber) === null && + blockNumber - pool.getStateBlockNumber() > + MAX_STALE_STATE_BLOCK_AGE[this.network]) ) { /* reload state, on zkEVM this would most likely timeout during request life * but would allow to rely on staleState for couple of min for next requests From b21e737e65a1e0702e3abf5f93a4fe4fe10362fb Mon Sep 17 00:00:00 2001 From: Verisana Date: Fri, 25 Aug 2023 17:22:05 +0100 Subject: [PATCH 19/45] 2.31.4-zkevm-revive.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea9060025..32f20b96a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.6", + "version": "2.31.4-zkevm-revive.7", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From eec0c52c891ab43ed43386ca49d22c846283ff87 Mon Sep 17 00:00:00 2001 From: Verisana Date: Mon, 28 Aug 2023 12:44:54 +0100 Subject: [PATCH 20/45] fix: optimize manual state requests --- src/dex/algebra/algebra-integration.test.ts | 14 ++- src/dex/algebra/algebra-pool-v1_1.ts | 132 ++++++++++++-------- src/lib/decoders.ts | 13 +- 3 files changed, 101 insertions(+), 58 deletions(-) diff --git a/src/dex/algebra/algebra-integration.test.ts b/src/dex/algebra/algebra-integration.test.ts index 20b8ef98a..d345ef8a8 100644 --- a/src/dex/algebra/algebra-integration.test.ts +++ b/src/dex/algebra/algebra-integration.test.ts @@ -497,7 +497,7 @@ describe('Algebra', function () { } }); - it('both generate state result match', async function () { + it('generate state is working for problematic pool', async function () { const pool = (await algebra.getPool( '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', '0xc5015b9d9161dca7e18e32f6f25c4ad850731fd4', @@ -505,6 +505,18 @@ describe('Algebra', function () { )) as AlgebraEventPoolV1_1; const stateManually = await pool.fetchStateManually(blockNumber); + // We can not compare with usual way, because this pool can not be requested normally + expect(Array.isArray(stateManually)).toBeTruthy(); + }); + + it('recognize pool does not exist error', async function () { + const pool = (await algebra.getPool( + '0x8aaebb46e1742f4623e6e1621f909f01846ca5e2', + '0xf9ed88937b2d82707d0eabd8c3d9aa4870b714d3', + blockNumber, + )) as AlgebraEventPoolV1_1; + + expect(pool).toBeNull(); }); }); }); diff --git a/src/dex/algebra/algebra-pool-v1_1.ts b/src/dex/algebra/algebra-pool-v1_1.ts index 0f9e3ccb1..8690b1785 100644 --- a/src/dex/algebra/algebra-pool-v1_1.ts +++ b/src/dex/algebra/algebra-pool-v1_1.ts @@ -2,7 +2,7 @@ import _ from 'lodash'; import { Interface } from '@ethersproject/abi'; import { DeepReadonly, assert } from 'ts-essentials'; import { Address, BlockHeader, Log, Logger } from '../../types'; -import { bigIntify, catchParseLogError, int16, uint128 } from '../../utils'; +import { bigIntify, catchParseLogError, int16 } from '../../utils'; import { InitializeStateOptions, StatefulEventSubscriber, @@ -28,8 +28,8 @@ import { import { addressDecode, uint256ToBigInt, - uint128ToBigInt, - int24ToBigInt, + uint128ToBigNumber, + int24ToNumber, } from '../../lib/decoders'; import { MultiCallParams } from '../../lib/multi-wrapper'; import { @@ -43,9 +43,12 @@ import { _reduceTicks, } from '../uniswap-v3/contract-math/utils'; import { Constants } from './lib/Constants'; -import { Network } from '../../constants'; +import { Network, NULL_ADDRESS } from '../../constants'; import { TickTable } from './lib/TickTable'; +const BN_ZERO = BigNumber.from(0); +const MAX_BATCH_SIZE = 100; + export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber { handlers: { [event: string]: ( @@ -67,6 +70,10 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber public readonly poolIface = new Interface(AlgebraABI); public readonly factoryIface = new Interface(FactoryABI); + private readonly cachedStateMultiCalls: MultiCallParams< + string | bigint | BigNumber | number | DecodedGlobalStateV1_1 + >[]; + public initFailed = false; public initRetryAttemptCount = 0; @@ -98,6 +105,8 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber this.handlers['Flash'] = this.handleFlashEvent.bind(this); this.handlers['Collect'] = this.handleCollectEvent.bind(this); this.handlers['CommunityFee'] = this.handleCommunityFee.bind(this); + + this.cachedStateMultiCalls = this._getStateMulticall(); } get poolAddress() { @@ -344,7 +353,6 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber return [balance0, balance1, _state]; } - // FIXME: Here happens double conversion in types, but for prototyping and to check if this approach helps, it is nor very important async _fetchInitStateMultiStrategies( blockNumber: number, ): Promise< @@ -362,13 +370,10 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber } } - async fetchStateManually( - blockNumber: number, - ): Promise< - [bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_1] - > { - // FIXME: If this approach works, need to add caching of multicalls - const [poolAddress] = await this.dexHelper.multiWrapper.aggregate([ + private _getStateMulticall(): MultiCallParams< + string | bigint | BigNumber | number | DecodedGlobalStateV1_1 + >[] { + return [ { target: this.factoryAddress, callData: this.factoryIface.encodeFunctionData('poolByPair', [ @@ -377,22 +382,6 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber ]), decodeFunction: addressDecode, }, - ]); - assert( - poolAddress.toLowerCase() === this.poolAddress.toLowerCase(), - `Pool address mismatch: ${poolAddress.toLowerCase()} != ${this.poolAddress.toLowerCase()}`, - ); - - const [ - balance0, - balance1, - liquidity, - tickSpacing, - maxLiquidityPerTick, - globalState, - ] = (await this.dexHelper.multiWrapper.aggregate< - bigint | number | DecodedGlobalStateV1_1 - >([ { target: this.token0, callData: this.erc20Interface.encodeFunctionData('balanceOf', [ @@ -408,26 +397,68 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber decodeFunction: uint256ToBigInt, }, { - target: poolAddress, + target: this.poolAddress, callData: this.poolIface.encodeFunctionData('liquidity', []), - decodeFunction: uint128ToBigInt, + decodeFunction: uint128ToBigNumber, }, { - target: poolAddress, + target: this.poolAddress, callData: this.poolIface.encodeFunctionData('tickSpacing', []), - decodeFunction: int24ToBigInt, + decodeFunction: int24ToNumber, }, { - target: poolAddress, + target: this.poolAddress, callData: this.poolIface.encodeFunctionData('maxLiquidityPerTick', []), - decodeFunction: uint128ToBigInt, + decodeFunction: uint128ToBigNumber, }, { - target: poolAddress, + target: this.poolAddress, callData: this.poolIface.encodeFunctionData('globalState', []), decodeFunction: decodeGlobalStateV1_1, }, - ])) as [bigint, bigint, bigint, bigint, bigint, DecodedGlobalStateV1_1]; + ]; + } + + async fetchStateManually( + blockNumber: number, + ): Promise< + [bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_1] + > { + // Unfortunately I can not unite this call with the next one. For some reason even if pool does not exist + // call succeeds and makes decoding function to throw. Otherwise, I should rewrite decoders in different which + // require some time + const [poolAddress] = (await this.dexHelper.multiWrapper.aggregate< + string | bigint | BigNumber | number | DecodedGlobalStateV1_1 + >(this.cachedStateMultiCalls.slice(0, 1), blockNumber, MAX_BATCH_SIZE)) as [ + string, + ]; + + if (poolAddress === NULL_ADDRESS) { + throw new Error('Pool does not exist'); + } + + const [ + balance0, + balance1, + liquidity, + tickSpacing, + maxLiquidityPerTick, + globalState, + ] = (await this.dexHelper.multiWrapper.aggregate< + string | bigint | BigNumber | number | DecodedGlobalStateV1_1 + >(this.cachedStateMultiCalls.slice(1), blockNumber, MAX_BATCH_SIZE)) as [ + bigint, + bigint, + BigNumber, + number, + BigNumber, + DecodedGlobalStateV1_1, + ]; + + assert( + poolAddress.toLowerCase() === this.poolAddress.toLowerCase(), + `Pool address mismatch: ${poolAddress.toLowerCase()} != ${this.poolAddress.toLowerCase()}`, + ); const currentBitMapIndex = TickTable.position( BigInt(BigInt(globalState.tick) / BigInt(tickSpacing)), @@ -449,14 +480,15 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber }; }, ), + blockNumber, + MAX_BATCH_SIZE, ); const tickBitmap: TickBitMapMappingsWithBigNumber[] = []; let globalIndex = 0; - for (let i = leftBitMapIndex; i <= rightBitMapIndex; i++) { - const index = int16(i); + const index = Number(int16(i)); const bitmap = allTickBitMaps[globalIndex]; globalIndex++; if (bitmap == 0n) continue; @@ -468,13 +500,13 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber const tickRequests = tickBitmap .map(tb => { const allBits: MultiCallParams[] = []; - if (tb.value === BigNumber.from(0)) return allBits; + if (tb.value === BN_ZERO) return allBits; _.range(0, 256).forEach(j => { if ((tb.value.toBigInt() & (1n << BigInt(j))) > 0n) { const populatedTick = (BigInt.asIntN(16, BigInt(tb.index) << 8n) + BigInt(j)) * - tickSpacing; + BigInt(tickSpacing); tickIndexes.push(populatedTick); allBits.push({ @@ -490,13 +522,12 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber }) .flat(); - const ticksValues = ( - await Promise.all( - _.chunk(tickRequests, 100).map(tickRequestChunk => - this.dexHelper.multiWrapper.aggregate(tickRequestChunk), - ), - ) - ).flat(); + const ticksValues = await this.dexHelper.multiWrapper.aggregate( + tickRequests, + blockNumber, + MAX_BATCH_SIZE, + ); + assert( tickIndexes.length === ticksValues.length, `Tick indexes mismatch: ${tickIndexes.length} != ${ticksValues.length}`, @@ -516,14 +547,13 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber return [ balance0, balance1, - // FIXME: If we validate that this is working, remove redundant conversions { pool: poolAddress, blockTimestamp: BigNumber.from(Date.now()), globalState, - liquidity: BigNumber.from(liquidity), - tickSpacing: Number(tickSpacing), - maxLiquidityPerTick: BigNumber.from(maxLiquidityPerTick), + liquidity, + tickSpacing, + maxLiquidityPerTick, tickBitmap, ticks, }, diff --git a/src/lib/decoders.ts b/src/lib/decoders.ts index 6c2453b0c..fd48cd5c1 100644 --- a/src/lib/decoders.ts +++ b/src/lib/decoders.ts @@ -1,5 +1,6 @@ import { Result } from '@ethersproject/abi'; import BigNumber from 'bignumber.js'; +import { BigNumber as EthersBigNumber } from 'ethers'; import { BytesLike, defaultAbiCoder } from 'ethers/lib/utils'; import _, { parseInt } from 'lodash'; import { BN_0 } from '../bignumber-constants'; @@ -50,16 +51,16 @@ export const uint256ToBigInt = ( return generalDecoder(result, ['uint256'], 0n, value => value[0].toBigInt()); }; -export const uint128ToBigInt = ( +export const uint128ToBigNumber = ( result: MultiResult | BytesLike, -): bigint => { - return generalDecoder(result, ['uint128'], 0n, value => value[0].toBigInt()); +): EthersBigNumber => { + return generalDecoder(result, ['uint128'], 0n, value => value[0]); }; -export const int24ToBigInt = ( +export const int24ToNumber = ( result: MultiResult | BytesLike, -): bigint => { - return generalDecoder(result, ['int24'], 0n, value => BigInt(value[0])); +): number => { + return generalDecoder(result, ['int24'], 0n, value => value[0]); }; export const uint256ArrayDecode = ( From 103ea97dc69f7175c3794115f0a2b6005425499a Mon Sep 17 00:00:00 2001 From: Verisana Date: Mon, 28 Aug 2023 12:46:13 +0100 Subject: [PATCH 21/45] 2.31.4-zkevm-revive.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 32f20b96a..b1782b7e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.7", + "version": "2.31.4-zkevm-revive.8", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From acd0906717fde01fbd76c0cec1f9f0e1ea294a7b Mon Sep 17 00:00:00 2001 From: Verisana Date: Mon, 28 Aug 2023 13:40:33 +0100 Subject: [PATCH 22/45] fix: try search fo optimal batch size split --- src/dex/algebra/algebra-integration.test.ts | 14 +++++++- src/dex/algebra/algebra-pool-v1_1.ts | 40 ++++++++++++++++++--- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/dex/algebra/algebra-integration.test.ts b/src/dex/algebra/algebra-integration.test.ts index d345ef8a8..a244391d6 100644 --- a/src/dex/algebra/algebra-integration.test.ts +++ b/src/dex/algebra/algebra-integration.test.ts @@ -497,7 +497,7 @@ describe('Algebra', function () { } }); - it('generate state is working for problematic pool', async function () { + it('WETH/DAI generate state is working for problematic pool', async function () { const pool = (await algebra.getPool( '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', '0xc5015b9d9161dca7e18e32f6f25c4ad850731fd4', @@ -509,6 +509,18 @@ describe('Algebra', function () { expect(Array.isArray(stateManually)).toBeTruthy(); }); + it('WETH/MATIC generate state is working for problematic pool', async function () { + const pool = (await algebra.getPool( + '0x4f9a0e7fd2bf6067db6994cf12e4495df938e6e9', + '0xa2036f0538221a77a3937f1379699f44945018d0', + blockNumber, + )) as AlgebraEventPoolV1_1; + + const stateManually = await pool.fetchStateManually(blockNumber); + // We can not compare with usual way, because this pool can not be requested normally + expect(Array.isArray(stateManually)).toBeTruthy(); + }); + it('recognize pool does not exist error', async function () { const pool = (await algebra.getPool( '0x8aaebb46e1742f4623e6e1621f909f01846ca5e2', diff --git a/src/dex/algebra/algebra-pool-v1_1.ts b/src/dex/algebra/algebra-pool-v1_1.ts index 8690b1785..41b3737b6 100644 --- a/src/dex/algebra/algebra-pool-v1_1.ts +++ b/src/dex/algebra/algebra-pool-v1_1.ts @@ -48,6 +48,7 @@ import { TickTable } from './lib/TickTable'; const BN_ZERO = BigNumber.from(0); const MAX_BATCH_SIZE = 100; +const MAX_NUMBER_OF_BATCH_REQUEST_HALVING = 3; export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber { handlers: { @@ -74,6 +75,8 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber string | bigint | BigNumber | number | DecodedGlobalStateV1_1 >[]; + private optimalTickRequestBatchSize?: number; + public initFailed = false; public initRetryAttemptCount = 0; @@ -522,11 +525,38 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber }) .flat(); - const ticksValues = await this.dexHelper.multiWrapper.aggregate( - tickRequests, - blockNumber, - MAX_BATCH_SIZE, - ); + let ticksValues: TickInfoWithBigNumber[] = []; + if (this.optimalTickRequestBatchSize) { + ticksValues = await this.dexHelper.multiWrapper.aggregate( + tickRequests, + blockNumber, + this.optimalTickRequestBatchSize, + ); + // If we don't know what is optimal number of requests for this pool, we want to try it experimentally and save it + // Maybe later to consider distant caching + } else { + for (const i of _.range(0, MAX_NUMBER_OF_BATCH_REQUEST_HALVING)) { + const currentBatchSize = MAX_BATCH_SIZE / (+i + 1); + try { + // Some of the pools fails with 100 batch size, for them we want to try additionally with reduced batch size + ticksValues = await this.dexHelper.multiWrapper.aggregate( + tickRequests, + blockNumber, + currentBatchSize, + ); + this.optimalTickRequestBatchSize = currentBatchSize; + break; + } catch (e) { + if (+i + 1 === MAX_NUMBER_OF_BATCH_REQUEST_HALVING) { + this.logger.warn( + `Failed to fetch ticks for pool ${poolAddress} (${this.token0}_${this.token1}) with batch size ${currentBatchSize}`, + e, + ); + throw e; + } + } + } + } assert( tickIndexes.length === ticksValues.length, From d1edcb2faee36b5bfea8998fb4314259e37918b9 Mon Sep 17 00:00:00 2001 From: Verisana Date: Mon, 28 Aug 2023 13:41:49 +0100 Subject: [PATCH 23/45] 2.31.4-zkevm-revive.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b1782b7e6..4dbd325ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.8", + "version": "2.31.4-zkevm-revive.9", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 7bc84ff9f8dee0786fb4d00e419213dc970ef063 Mon Sep 17 00:00:00 2001 From: Verisana Date: Mon, 28 Aug 2023 18:07:41 +0100 Subject: [PATCH 24/45] test: add more tests for states --- src/dex/algebra/algebra-integration.test.ts | 24 +++++++++++++++++++++ src/dex/algebra/algebra.ts | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/dex/algebra/algebra-integration.test.ts b/src/dex/algebra/algebra-integration.test.ts index a244391d6..002b4292f 100644 --- a/src/dex/algebra/algebra-integration.test.ts +++ b/src/dex/algebra/algebra-integration.test.ts @@ -521,6 +521,30 @@ describe('Algebra', function () { expect(Array.isArray(stateManually)).toBeTruthy(); }); + it('stMATIC/WETH generate state is working for problematic pool', async function () { + const pool = (await algebra.getPool( + '0x83b874c1e09d316059d929da402dcb1a98e92082', + '0x4f9a0e7fd2bf6067db6994cf12e4495df938e6e9', + blockNumber, + )) as AlgebraEventPoolV1_1; + + const stateManually = await pool.fetchStateManually(blockNumber); + // We can not compare with usual way, because this pool can not be requested normally + expect(Array.isArray(stateManually)).toBeTruthy(); + }); + + it('WETH/USDC generate state is working for problematic pool', async function () { + const pool = (await algebra.getPool( + '0x4f9a0e7fd2bf6067db6994cf12e4495df938e6e9', + '0xa8ce8aee21bc2a48a5ef670afcc9274c7bbbc035', + blockNumber, + )) as AlgebraEventPoolV1_1; + + const stateManually = await pool.fetchStateManually(blockNumber); + // We can not compare with usual way, because this pool can not be requested normally + expect(Array.isArray(stateManually)).toBeTruthy(); + }); + it('recognize pool does not exist error', async function () { const pool = (await algebra.getPool( '0x8aaebb46e1742f4623e6e1621f909f01846ca5e2', diff --git a/src/dex/algebra/algebra.ts b/src/dex/algebra/algebra.ts index 0178f2307..3b1f55ae5 100644 --- a/src/dex/algebra/algebra.ts +++ b/src/dex/algebra/algebra.ts @@ -257,7 +257,7 @@ export class Algebra extends SimpleExchange implements IDex { e, ); } else { - // on unkown error mark as failed and increase retryCount for retry init strategy + // on unknown error mark as failed and increase retryCount for retry init strategy // note: state would be null by default which allows to fallback this.logger.warn( `${this.dexKey}: Can not generate pool state for srcAddress=${srcAddress}, destAddress=${destAddress} pool fallback to rpc and retry every ${this.config.initRetryFrequency} times, initRetryAttemptCount=${pool.initRetryAttemptCount}`, From 2ba99c0f1de03c0f880d77dca24d7e1f90bf8345 Mon Sep 17 00:00:00 2001 From: Verisana Date: Tue, 29 Aug 2023 11:01:11 +0100 Subject: [PATCH 25/45] style: rename files to dash --- src/dex/{QuickPerps => quick-perps}/config.ts | 0 src/dex/{QuickPerps => quick-perps}/fast-price-feed.ts | 0 src/dex/{QuickPerps => quick-perps}/pool.ts | 0 .../quick-perps-e2e.test.ts} | 0 .../quick-perps-events.test.ts} | 0 .../quick-perps-integration.test.ts} | 0 src/dex/{QuickPerps/quickPerps.ts => quick-perps/quick-perps.ts} | 0 src/dex/{QuickPerps => quick-perps}/types.ts | 0 src/dex/{QuickPerps => quick-perps}/usdq.ts | 0 src/dex/{QuickPerps => quick-perps}/vault-price-feed.ts | 0 src/dex/{QuickPerps => quick-perps}/vault-utils.ts | 0 src/dex/{QuickPerps => quick-perps}/vault.ts | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename src/dex/{QuickPerps => quick-perps}/config.ts (100%) rename src/dex/{QuickPerps => quick-perps}/fast-price-feed.ts (100%) rename src/dex/{QuickPerps => quick-perps}/pool.ts (100%) rename src/dex/{QuickPerps/quickPerps-e2e.test.ts => quick-perps/quick-perps-e2e.test.ts} (100%) rename src/dex/{QuickPerps/quickPerps-events.test.ts => quick-perps/quick-perps-events.test.ts} (100%) rename src/dex/{QuickPerps/quickPerps-integration.test.ts => quick-perps/quick-perps-integration.test.ts} (100%) rename src/dex/{QuickPerps/quickPerps.ts => quick-perps/quick-perps.ts} (100%) rename src/dex/{QuickPerps => quick-perps}/types.ts (100%) rename src/dex/{QuickPerps => quick-perps}/usdq.ts (100%) rename src/dex/{QuickPerps => quick-perps}/vault-price-feed.ts (100%) rename src/dex/{QuickPerps => quick-perps}/vault-utils.ts (100%) rename src/dex/{QuickPerps => quick-perps}/vault.ts (100%) diff --git a/src/dex/QuickPerps/config.ts b/src/dex/quick-perps/config.ts similarity index 100% rename from src/dex/QuickPerps/config.ts rename to src/dex/quick-perps/config.ts diff --git a/src/dex/QuickPerps/fast-price-feed.ts b/src/dex/quick-perps/fast-price-feed.ts similarity index 100% rename from src/dex/QuickPerps/fast-price-feed.ts rename to src/dex/quick-perps/fast-price-feed.ts diff --git a/src/dex/QuickPerps/pool.ts b/src/dex/quick-perps/pool.ts similarity index 100% rename from src/dex/QuickPerps/pool.ts rename to src/dex/quick-perps/pool.ts diff --git a/src/dex/QuickPerps/quickPerps-e2e.test.ts b/src/dex/quick-perps/quick-perps-e2e.test.ts similarity index 100% rename from src/dex/QuickPerps/quickPerps-e2e.test.ts rename to src/dex/quick-perps/quick-perps-e2e.test.ts diff --git a/src/dex/QuickPerps/quickPerps-events.test.ts b/src/dex/quick-perps/quick-perps-events.test.ts similarity index 100% rename from src/dex/QuickPerps/quickPerps-events.test.ts rename to src/dex/quick-perps/quick-perps-events.test.ts diff --git a/src/dex/QuickPerps/quickPerps-integration.test.ts b/src/dex/quick-perps/quick-perps-integration.test.ts similarity index 100% rename from src/dex/QuickPerps/quickPerps-integration.test.ts rename to src/dex/quick-perps/quick-perps-integration.test.ts diff --git a/src/dex/QuickPerps/quickPerps.ts b/src/dex/quick-perps/quick-perps.ts similarity index 100% rename from src/dex/QuickPerps/quickPerps.ts rename to src/dex/quick-perps/quick-perps.ts diff --git a/src/dex/QuickPerps/types.ts b/src/dex/quick-perps/types.ts similarity index 100% rename from src/dex/QuickPerps/types.ts rename to src/dex/quick-perps/types.ts diff --git a/src/dex/QuickPerps/usdq.ts b/src/dex/quick-perps/usdq.ts similarity index 100% rename from src/dex/QuickPerps/usdq.ts rename to src/dex/quick-perps/usdq.ts diff --git a/src/dex/QuickPerps/vault-price-feed.ts b/src/dex/quick-perps/vault-price-feed.ts similarity index 100% rename from src/dex/QuickPerps/vault-price-feed.ts rename to src/dex/quick-perps/vault-price-feed.ts diff --git a/src/dex/QuickPerps/vault-utils.ts b/src/dex/quick-perps/vault-utils.ts similarity index 100% rename from src/dex/QuickPerps/vault-utils.ts rename to src/dex/quick-perps/vault-utils.ts diff --git a/src/dex/QuickPerps/vault.ts b/src/dex/quick-perps/vault.ts similarity index 100% rename from src/dex/QuickPerps/vault.ts rename to src/dex/quick-perps/vault.ts From 7721832210c33d863c78975727868d946e502c57 Mon Sep 17 00:00:00 2001 From: Verisana Date: Tue, 29 Aug 2023 12:05:20 +0100 Subject: [PATCH 26/45] style: abi rename and update imports --- .../fast-price-events.json | 0 .../fast-price-feed.json | 0 .../{quickPerps => quick-perps}/reader.json | 0 .../vault-price-feed.json | 0 .../{quickPerps => quick-perps}/vault.json | 0 src/dex/quick-perps/fast-price-feed.ts | 4 +-- src/dex/quick-perps/pool.ts | 2 +- .../quick-perps-integration.test.ts | 29 +++++++++++-------- src/dex/quick-perps/vault-price-feed.ts | 2 +- src/dex/quick-perps/vault.ts | 2 +- 10 files changed, 22 insertions(+), 17 deletions(-) rename src/abi/{quickPerps => quick-perps}/fast-price-events.json (100%) rename src/abi/{quickPerps => quick-perps}/fast-price-feed.json (100%) rename src/abi/{quickPerps => quick-perps}/reader.json (100%) rename src/abi/{quickPerps => quick-perps}/vault-price-feed.json (100%) rename src/abi/{quickPerps => quick-perps}/vault.json (100%) diff --git a/src/abi/quickPerps/fast-price-events.json b/src/abi/quick-perps/fast-price-events.json similarity index 100% rename from src/abi/quickPerps/fast-price-events.json rename to src/abi/quick-perps/fast-price-events.json diff --git a/src/abi/quickPerps/fast-price-feed.json b/src/abi/quick-perps/fast-price-feed.json similarity index 100% rename from src/abi/quickPerps/fast-price-feed.json rename to src/abi/quick-perps/fast-price-feed.json diff --git a/src/abi/quickPerps/reader.json b/src/abi/quick-perps/reader.json similarity index 100% rename from src/abi/quickPerps/reader.json rename to src/abi/quick-perps/reader.json diff --git a/src/abi/quickPerps/vault-price-feed.json b/src/abi/quick-perps/vault-price-feed.json similarity index 100% rename from src/abi/quickPerps/vault-price-feed.json rename to src/abi/quick-perps/vault-price-feed.json diff --git a/src/abi/quickPerps/vault.json b/src/abi/quick-perps/vault.json similarity index 100% rename from src/abi/quickPerps/vault.json rename to src/abi/quick-perps/vault.json diff --git a/src/dex/quick-perps/fast-price-feed.ts b/src/dex/quick-perps/fast-price-feed.ts index 7140ddf70..8af150067 100644 --- a/src/dex/quick-perps/fast-price-feed.ts +++ b/src/dex/quick-perps/fast-price-feed.ts @@ -11,8 +11,8 @@ import { BlockHeader, } from '../../types'; import { FastPriceFeedConfig, FastPriceFeedState } from './types'; -import FastPriceFeedAbi from '../../abi/quickPerps/fast-price-feed.json'; -import FastPriceEventsAbi from '../../abi/quickPerps/fast-price-events.json'; +import FastPriceFeedAbi from '../../abi/quick-perps/fast-price-feed.json'; +import FastPriceEventsAbi from '../../abi/quick-perps/fast-price-events.json'; import { Lens } from '../../lens'; export class FastPriceFeed extends PartialEventSubscriber< diff --git a/src/dex/quick-perps/pool.ts b/src/dex/quick-perps/pool.ts index 20d2fbb92..da81d0f82 100644 --- a/src/dex/quick-perps/pool.ts +++ b/src/dex/quick-perps/pool.ts @@ -10,7 +10,7 @@ import { VaultPriceFeed } from './vault-price-feed'; import { Vault } from './vault'; import { USDQ } from './usdq'; import { Contract } from 'web3-eth-contract'; -import ReaderABI from '../../abi/quickPerps/reader.json'; +import ReaderABI from '../../abi/quick-perps/reader.json'; const MAX_AMOUNT_IN_CACHE_TTL = 5 * 60; diff --git a/src/dex/quick-perps/quick-perps-integration.test.ts b/src/dex/quick-perps/quick-perps-integration.test.ts index 5c488326f..28e243a78 100644 --- a/src/dex/quick-perps/quick-perps-integration.test.ts +++ b/src/dex/quick-perps/quick-perps-integration.test.ts @@ -1,11 +1,11 @@ +/* eslint-disable no-console */ import dotenv from 'dotenv'; dotenv.config(); import { Interface } from '@ethersproject/abi'; import { DummyDexHelper } from '../../dex-helper/index'; import { Network, SwapSide } from '../../constants'; -import { BI_POWS } from '../../bigint-constants'; -import { QuickPerps } from './quickPerps'; +import { QuickPerps } from './quick-perps'; import { QuickPerpsConfig } from './config'; import { checkPoolPrices, @@ -13,28 +13,33 @@ import { checkConstantPoolPrices, } from '../../../tests/utils'; import { Tokens } from '../../../tests/constants-e2e'; -import ReaderABI from '../../abi/quickPerps/reader.json'; +import ReaderABI from '../../abi/quick-perps/reader.json'; -const network = Network.AVALANCHE; -const TokenASymbol = 'USDCe'; +const network = Network.ZKEVM; +const TokenASymbol = 'WETH'; const TokenA = Tokens[network][TokenASymbol]; -const TokenBSymbol = 'WAVAX'; +const TokenBSymbol = 'MATIC'; const TokenB = Tokens[network][TokenBSymbol]; const amounts = [ 0n, - 1000000000n, - 2000000000n, - 3000000000n, - 4000000000n, - 5000000000n, + 100000000000000000n, + 200000000000000000n, + 300000000000000000n, + 400000000000000000n, + 500000000000000000n, + 600000000000000000n, + 700000000000000000n, + 800000000000000000n, + 900000000000000000n, + 1000000000000000000n, ]; const dexKey = 'QuickPerps'; const params = QuickPerpsConfig[dexKey][network]; const readerInterface = new Interface(ReaderABI); -const readerAddress = '0x67b789D48c926006F5132BFCe4e976F0A7A63d5D'; +const readerAddress = '0xf1CFB75854DE535475B88Bb6FBad317eea98c0F9'; describe('QuickPerps', function () { it('getPoolIdentifiers and getPricesVolume SELL', async function () { diff --git a/src/dex/quick-perps/vault-price-feed.ts b/src/dex/quick-perps/vault-price-feed.ts index 5071cb135..43a96e7e2 100644 --- a/src/dex/quick-perps/vault-price-feed.ts +++ b/src/dex/quick-perps/vault-price-feed.ts @@ -2,7 +2,7 @@ import { Interface } from '@ethersproject/abi'; import { Address, MultiCallInput, MultiCallOutput } from '../../types'; import { PoolState, VaultPriceFeedConfig } from './types'; import { FastPriceFeed } from './fast-price-feed'; -import VaultPriceFeedAbi from '../../abi/quickPerps/vault-price-feed.json'; +import VaultPriceFeedAbi from '../../abi/quick-perps/vault-price-feed.json'; import { ChainLinkSubscriber } from '../../lib/chainlink'; import { DeepReadonly } from 'ts-essentials'; diff --git a/src/dex/quick-perps/vault.ts b/src/dex/quick-perps/vault.ts index 725d3f27f..a5832c76c 100644 --- a/src/dex/quick-perps/vault.ts +++ b/src/dex/quick-perps/vault.ts @@ -3,7 +3,7 @@ import { Interface } from '@ethersproject/abi'; import { AsyncOrSync, DeepReadonly } from 'ts-essentials'; import { PartialEventSubscriber } from '../../composed-event-subscriber'; import { Lens } from '../../lens'; -import VaultABI from '../../abi/quickPerps/vault.json'; +import VaultABI from '../../abi/quick-perps/vault.json'; import { VaultUtils } from './vault-utils'; import { VaultConfig, From 240a431b99c043dcb3688f2753152eaf15913e52 Mon Sep 17 00:00:00 2001 From: Verisana Date: Tue, 29 Aug 2023 17:59:42 +0100 Subject: [PATCH 27/45] feat: update quick perps pricing is working --- src/abi/api3-proxy.json | 33 ++ src/abi/api3-server-v1.json | 463 ++++++++++++++++++ src/abi/quick-perps/fast-price-feed.json | 19 +- src/abi/quick-perps/vault-price-feed.json | 184 +------ src/dex/quick-perps/fast-price-feed.ts | 8 +- src/dex/quick-perps/pool.ts | 41 +- src/dex/quick-perps/quick-perps-e2e.test.ts | 87 +--- .../quick-perps/quick-perps-events.test.ts | 84 ++-- src/dex/quick-perps/quick-perps.ts | 3 - src/dex/quick-perps/types.ts | 11 +- src/dex/quick-perps/vault-price-feed.ts | 104 +--- src/lib/api3-feed.ts | 100 ++++ tests/constants-e2e.ts | 14 + 13 files changed, 723 insertions(+), 428 deletions(-) create mode 100644 src/abi/api3-proxy.json create mode 100644 src/abi/api3-server-v1.json create mode 100644 src/lib/api3-feed.ts diff --git a/src/abi/api3-proxy.json b/src/abi/api3-proxy.json new file mode 100644 index 000000000..a9b902bca --- /dev/null +++ b/src/abi/api3-proxy.json @@ -0,0 +1,33 @@ +[ + { + "inputs": [], + "name": "api3ServerV1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "read", + "outputs": [ + { + "internalType": "int224", + "name": "value", + "type": "int224" + }, + { + "internalType": "uint32", + "name": "timestamp", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/api3-server-v1.json b/src/abi/api3-server-v1.json new file mode 100644 index 000000000..c897541e0 --- /dev/null +++ b/src/abi/api3-server-v1.json @@ -0,0 +1,463 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_accessControlRegistry", + "type": "address" + }, + { + "internalType": "string", + "name": "_adminRoleDescription", + "type": "string" + }, + { "internalType": "address", "name": "_manager", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "dataFeedId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "dapiName", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "SetDapiName", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "beaconSetId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "int224", + "name": "value", + "type": "int224" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "timestamp", + "type": "uint32" + } + ], + "name": "UpdatedBeaconSetWithBeacons", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "beaconId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "int224", + "name": "value", + "type": "int224" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "timestamp", + "type": "uint32" + } + ], + "name": "UpdatedBeaconWithSignedData", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "beaconSetId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "updateId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "int224", + "name": "value", + "type": "int224" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "timestamp", + "type": "uint32" + } + ], + "name": "UpdatedOevProxyBeaconSetWithSignedData", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "beaconId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "updateId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "int224", + "name": "value", + "type": "int224" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "timestamp", + "type": "uint32" + } + ], + "name": "UpdatedOevProxyBeaconWithSignedData", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oevProxy", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "oevBeneficiary", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrew", + "type": "event" + }, + { + "inputs": [], + "name": "DAPI_NAME_SETTER_ROLE_DESCRIPTION", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlRegistry", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "adminRole", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "adminRoleDescription", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "containsBytecode", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "dapiNameHashToDataFeedId", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dapiNameSetterRole", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "dapiName", "type": "bytes32" } + ], + "name": "dapiNameToDataFeedId", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "dataFeedId", "type": "bytes32" } + ], + "name": "dataFeeds", + "outputs": [ + { "internalType": "int224", "name": "value", "type": "int224" }, + { "internalType": "uint32", "name": "timestamp", "type": "uint32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "getBalance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockBasefee", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockNumber", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockTimestamp", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getChainId", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "manager", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes[]", "name": "data", "type": "bytes[]" } + ], + "name": "multicall", + "outputs": [ + { "internalType": "bytes[]", "name": "returndata", "type": "bytes[]" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "oevProxyToBalance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "proxy", "type": "address" }, + { "internalType": "bytes32", "name": "dataFeedId", "type": "bytes32" } + ], + "name": "oevProxyToIdToDataFeed", + "outputs": [ + { "internalType": "int224", "name": "value", "type": "int224" }, + { "internalType": "uint32", "name": "timestamp", "type": "uint32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "dapiNameHash", "type": "bytes32" } + ], + "name": "readDataFeedWithDapiNameHash", + "outputs": [ + { "internalType": "int224", "name": "value", "type": "int224" }, + { "internalType": "uint32", "name": "timestamp", "type": "uint32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "dapiNameHash", "type": "bytes32" } + ], + "name": "readDataFeedWithDapiNameHashAsOevProxy", + "outputs": [ + { "internalType": "int224", "name": "value", "type": "int224" }, + { "internalType": "uint32", "name": "timestamp", "type": "uint32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "dataFeedId", "type": "bytes32" } + ], + "name": "readDataFeedWithId", + "outputs": [ + { "internalType": "int224", "name": "value", "type": "int224" }, + { "internalType": "uint32", "name": "timestamp", "type": "uint32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "dataFeedId", "type": "bytes32" } + ], + "name": "readDataFeedWithIdAsOevProxy", + "outputs": [ + { "internalType": "int224", "name": "value", "type": "int224" }, + { "internalType": "uint32", "name": "timestamp", "type": "uint32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "dapiName", "type": "bytes32" }, + { "internalType": "bytes32", "name": "dataFeedId", "type": "bytes32" } + ], + "name": "setDapiName", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes[]", "name": "data", "type": "bytes[]" } + ], + "name": "tryMulticall", + "outputs": [ + { "internalType": "bool[]", "name": "successes", "type": "bool[]" }, + { "internalType": "bytes[]", "name": "returndata", "type": "bytes[]" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32[]", "name": "beaconIds", "type": "bytes32[]" } + ], + "name": "updateBeaconSetWithBeacons", + "outputs": [ + { "internalType": "bytes32", "name": "beaconSetId", "type": "bytes32" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "airnode", "type": "address" }, + { "internalType": "bytes32", "name": "templateId", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "data", "type": "bytes" }, + { "internalType": "bytes", "name": "signature", "type": "bytes" } + ], + "name": "updateBeaconWithSignedData", + "outputs": [ + { "internalType": "bytes32", "name": "beaconId", "type": "bytes32" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "oevProxy", "type": "address" }, + { "internalType": "bytes32", "name": "dataFeedId", "type": "bytes32" }, + { "internalType": "bytes32", "name": "updateId", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "data", "type": "bytes" }, + { + "internalType": "bytes[]", + "name": "packedOevUpdateSignatures", + "type": "bytes[]" + } + ], + "name": "updateOevProxyDataFeedWithSignedData", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "oevProxy", "type": "address" } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/quick-perps/fast-price-feed.json b/src/abi/quick-perps/fast-price-feed.json index 502cec0ed..5cf8fccdf 100644 --- a/src/abi/quick-perps/fast-price-feed.json +++ b/src/abi/quick-perps/fast-price-feed.json @@ -26,12 +26,7 @@ "name": "_fastPriceEvents", "type": "address" }, - { "internalType": "address", "name": "_tokenManager", "type": "address" }, - { - "internalType": "address", - "name": "_positionRouter", - "type": "address" - } + { "internalType": "address", "name": "_tokenManager", "type": "address" } ], "stateMutability": "nonpayable", "type": "constructor" @@ -367,13 +362,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "positionRouter", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, { "inputs": [{ "internalType": "address", "name": "", "type": "address" }], "name": "priceData", @@ -593,6 +581,11 @@ }, { "inputs": [ + { + "internalType": "address", + "name": "_positionRouter", + "type": "address" + }, { "internalType": "uint256", "name": "_priceBits", "type": "uint256" }, { "internalType": "uint256", "name": "_timestamp", "type": "uint256" }, { diff --git a/src/abi/quick-perps/vault-price-feed.json b/src/abi/quick-perps/vault-price-feed.json index d917d96cf..42c932f08 100644 --- a/src/abi/quick-perps/vault-price-feed.json +++ b/src/abi/quick-perps/vault-price-feed.json @@ -51,50 +51,8 @@ }, { "inputs": [], - "name": "bnb", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "bnbBusd", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "btc", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "btcBnb", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "chainlinkFlags", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "eth", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "ethBnb", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "expireTimeForPriceFeed", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, @@ -105,26 +63,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" } - ], - "name": "getAmmPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "bool", "name": "_maximise", "type": "bool" }, - { "internalType": "uint256", "name": "_primaryPrice", "type": "uint256" } - ], - "name": "getAmmPriceV2", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { "internalType": "address", "name": "_token", "type": "address" } @@ -134,21 +72,11 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { "internalType": "address", "name": "_pair", "type": "address" }, - { "internalType": "bool", "name": "_divByReserve0", "type": "bool" } - ], - "name": "getPairPrice", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { "internalType": "address", "name": "_token", "type": "address" }, { "internalType": "bool", "name": "_maximise", "type": "bool" }, - { "internalType": "bool", "name": "_includeAmmPrice", "type": "bool" }, + { "internalType": "bool", "name": "", "type": "bool" }, { "internalType": "bool", "name": "", "type": "bool" } ], "name": "getPrice", @@ -159,8 +87,7 @@ { "inputs": [ { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "bool", "name": "_maximise", "type": "bool" }, - { "internalType": "bool", "name": "_includeAmmPrice", "type": "bool" } + { "internalType": "bool", "name": "_maximise", "type": "bool" } ], "name": "getPriceV1", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], @@ -170,18 +97,7 @@ { "inputs": [ { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "bool", "name": "_maximise", "type": "bool" }, - { "internalType": "bool", "name": "_includeAmmPrice", "type": "bool" } - ], - "name": "getPriceV2", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "bool", "name": "_maximise", "type": "bool" } + { "internalType": "bool", "name": "", "type": "bool" } ], "name": "getPrimaryPrice", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], @@ -217,13 +133,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "isAmmEnabled", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "isSecondaryPriceEnabled", @@ -254,18 +163,11 @@ }, { "inputs": [{ "internalType": "address", "name": "", "type": "address" }], - "name": "priceFeeds", + "name": "priceFeedProxies", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "priceSampleSpace", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "secondaryPriceFeed", @@ -287,12 +189,12 @@ { "inputs": [ { - "internalType": "address", - "name": "_chainlinkFlags", - "type": "address" + "internalType": "uint256", + "name": "_expireTimeForPriceFeed", + "type": "uint256" } ], - "name": "setChainlinkFlags", + "name": "setExpireTimeForPriceFeed", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -315,15 +217,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { "internalType": "bool", "name": "_isEnabled", "type": "bool" } - ], - "name": "setIsAmmEnabled", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { "internalType": "bool", "name": "_isEnabled", "type": "bool" } @@ -346,30 +239,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { "internalType": "address", "name": "_bnbBusd", "type": "address" }, - { "internalType": "address", "name": "_ethBnb", "type": "address" }, - { "internalType": "address", "name": "_btcBnb", "type": "address" } - ], - "name": "setPairs", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_priceSampleSpace", - "type": "uint256" - } - ], - "name": "setPriceSampleSpace", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -413,7 +282,11 @@ { "inputs": [ { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "address", "name": "_priceFeed", "type": "address" }, + { + "internalType": "address", + "name": "_priceFeedProxy", + "type": "address" + }, { "internalType": "uint256", "name": "_priceDecimals", @@ -426,26 +299,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { "internalType": "address", "name": "_btc", "type": "address" }, - { "internalType": "address", "name": "_eth", "type": "address" }, - { "internalType": "address", "name": "_bnb", "type": "address" } - ], - "name": "setTokens", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bool", "name": "_useV2Pricing", "type": "bool" } - ], - "name": "setUseV2Pricing", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [{ "internalType": "address", "name": "", "type": "address" }], "name": "spreadBasisPoints", @@ -466,12 +319,5 @@ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" - }, - { - "inputs": [], - "name": "useV2Pricing", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" } ] diff --git a/src/dex/quick-perps/fast-price-feed.ts b/src/dex/quick-perps/fast-price-feed.ts index 8af150067..0d554b47d 100644 --- a/src/dex/quick-perps/fast-price-feed.ts +++ b/src/dex/quick-perps/fast-price-feed.ts @@ -79,14 +79,14 @@ export class FastPriceFeed extends PartialEventSubscriber< _state: DeepReadonly, _token: Address, _refPrice: bigint, - _maximise: boolean, + _maximize: boolean, ) { const state = this.lens.get()(_state); const timestamp = Math.floor(Date.now() / 1000); if (timestamp > state.lastUpdatedAt + this.maxPriceUpdateDelay) { - if (_maximise) { + if (_maximize) { return ( (_refPrice * (this.BASIS_POINTS_DIVISOR + this.spreadBasisPointsIfChainError)) / @@ -102,7 +102,7 @@ export class FastPriceFeed extends PartialEventSubscriber< } if (timestamp > state.lastUpdatedAt + this.priceDuration) { - if (_maximise) { + if (_maximize) { return ( (_refPrice * (this.BASIS_POINTS_DIVISOR + this.spreadBasisPointsIfInactive)) / @@ -132,7 +132,7 @@ export class FastPriceFeed extends PartialEventSubscriber< if (hasSpread) { // return the higher of the two prices - if (_maximise) { + if (_maximize) { return _refPrice > fastPrice ? _refPrice : fastPrice; } diff --git a/src/dex/quick-perps/pool.ts b/src/dex/quick-perps/pool.ts index da81d0f82..7d641706e 100644 --- a/src/dex/quick-perps/pool.ts +++ b/src/dex/quick-perps/pool.ts @@ -1,10 +1,10 @@ import { DeepReadonly } from 'ts-essentials'; -import { Lens, lens } from '../../lens'; -import { Address, Log, Logger, MultiCallInput } from '../../types'; +import { lens } from '../../lens'; +import { Address, Logger, MultiCallInput } from '../../types'; import { ComposedEventSubscriber } from '../../composed-event-subscriber'; import { IDexHelper } from '../../dex-helper/idex-helper'; import { PoolState, DexParams, PoolConfig } from './types'; -import { ChainLinkSubscriber } from '../../lib/chainlink'; +import { Api3FeedSubscriber } from '../../lib/api3-feed'; import { FastPriceFeed } from './fast-price-feed'; import { VaultPriceFeed } from './vault-price-feed'; import { Vault } from './vault'; @@ -29,14 +29,14 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { logger: Logger, config: PoolConfig, ) { - const chainlinkMap = Object.entries(config.chainlink).reduce( + const api3ServerV1Map = Object.entries(config.api3ServerV1).reduce( ( - acc: { [address: string]: ChainLinkSubscriber }, + acc: { [address: string]: Api3FeedSubscriber }, [key, value], ) => { - acc[key] = new ChainLinkSubscriber( + acc[key] = new Api3FeedSubscriber( value.proxy, - value.aggregator, + value.api3ServerV1, lens>().primaryPrices[key], dexHelper.getLogger(`${key} ChainLink for ${parentName}-${network}`), ); @@ -54,7 +54,7 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { ); const vaultPriceFeed = new VaultPriceFeed( config.vaultPriceFeedConfig, - chainlinkMap, + api3ServerV1Map, fastPriceFeed, ); const usdq = new USDQ( @@ -76,7 +76,7 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { 'pool', dexHelper.getLogger(`${parentName}-${network}`), dexHelper, - [...Object.values(chainlinkMap), fastPriceFeed, usdq, vault], + [...Object.values(api3ServerV1Map), fastPriceFeed, usdq, vault], { primaryPrices: {}, secondaryPrices: { @@ -255,9 +255,10 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { // get price chainlink price feed const getPriceFeedCalldata = tokens.map(t => { return { - callData: VaultPriceFeed.interface.encodeFunctionData('priceFeeds', [ - t, - ]), + callData: VaultPriceFeed.interface.encodeFunctionData( + 'priceFeedProxies', + [t], + ), target: dexParams.priceFeed, }; }); @@ -268,7 +269,7 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { ).returnData; const priceFeeds = priceFeedResult.map((p: any) => VaultPriceFeed.interface - .decodeFunctionResult('priceFeeds', p)[0] + .decodeFunctionResult('priceFeedProxies', p)[0] .toString() .toLowerCase(), ); @@ -279,7 +280,7 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { let i = 0; for (let priceFeed of priceFeeds) { const chainlinkConfigCallData = - ChainLinkSubscriber.getReadAggregatorMultiCallInput(priceFeed); + Api3FeedSubscriber.getApi3ServerV1MultiCallInput(priceFeed); multiCallData.push(chainlinkConfigCallData); multicallSlices.push([i, i + 1]); i += 1; @@ -311,16 +312,16 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { await multiContract.methods.aggregate(multiCallData).call({}, blockNumber) ).returnData; - const chainlink: { - [address: string]: { proxy: Address; aggregator: Address }; + const api3ServerV1: { + [address: string]: { proxy: Address; api3ServerV1: Address }; } = {}; for (let token of tokens) { - const aggregator = ChainLinkSubscriber.readAggregator( + const serverV1Address = Api3FeedSubscriber.decodeApi3ServerV1Result( configResults.slice(...multicallSlices.shift()!)[0], ); - chainlink[token] = { + api3ServerV1[token] = { proxy: priceFeeds.shift(), - aggregator, + api3ServerV1: serverV1Address, }; } @@ -354,7 +355,7 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { vaultConfig, vaultPriceFeedConfig, fastPriceFeedConfig, - chainlink, + api3ServerV1, }; } } diff --git a/src/dex/quick-perps/quick-perps-e2e.test.ts b/src/dex/quick-perps/quick-perps-e2e.test.ts index 2f84ff765..f4a72acf8 100644 --- a/src/dex/quick-perps/quick-perps-e2e.test.ts +++ b/src/dex/quick-perps/quick-perps-e2e.test.ts @@ -14,8 +14,8 @@ import { generateConfig } from '../../config'; describe('QuickPerps E2E', () => { const dexKey = 'QuickPerps'; - describe('QuickPerps AVALANCHE', () => { - const network = Network.AVALANCHE; + describe('QuickPerps zkEVM', () => { + const network = Network.ZKEVM; const tokens = Tokens[network]; const holders = Holders[network]; const provider = new StaticJsonRpcProvider( @@ -23,87 +23,12 @@ describe('QuickPerps E2E', () => { network, ); - const tokenASymbol: string = 'WETHe'; - const tokenBSymbol: string = 'USDCe'; + const tokenASymbol: string = 'WETH'; + const tokenBSymbol: string = 'MATIC'; const nativeTokenSymbol = NativeTokenSymbols[network]; - const tokenAAmount: string = '1000000000000000000'; - const tokenBAmount: string = '2000000000'; - const nativeTokenAmount = '1000000000000000000'; - - const sideToContractMethods = new Map([ - [ - SwapSide.SELL, - [ - ContractMethod.simpleSwap, - ContractMethod.multiSwap, - ContractMethod.megaSwap, - ], - ], - ]); - - sideToContractMethods.forEach((contractMethods, side) => - contractMethods.forEach((contractMethod: ContractMethod) => { - describe(`${contractMethod}`, () => { - it(nativeTokenSymbol + ' -> TOKEN', async () => { - await testE2E( - tokens[nativeTokenSymbol], - tokens[tokenASymbol], - holders[nativeTokenSymbol], - side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); - }); - it('TOKEN -> ' + nativeTokenSymbol, async () => { - await testE2E( - tokens[tokenASymbol], - tokens[nativeTokenSymbol], - holders[tokenASymbol], - side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); - }); - it('TOKEN -> TOKEN', async () => { - await testE2E( - tokens[tokenASymbol], - tokens[tokenBSymbol], - holders[tokenASymbol], - side === SwapSide.SELL ? tokenAAmount : tokenBAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); - }); - }); - }), - ); - }); - - describe('QuickPerps ARBITRUM', () => { - const network = Network.ARBITRUM; - const tokens = Tokens[network]; - const holders = Holders[network]; - const provider = new StaticJsonRpcProvider( - generateConfig(network).privateHttpProvider, - network, - ); - - const tokenASymbol: string = 'USDC'; - const tokenBSymbol: string = 'WETH'; - const nativeTokenSymbol = NativeTokenSymbols[network]; - - const tokenAAmount: string = '2000000000'; - const tokenBAmount: string = '1000000000000000000'; + const tokenAAmount: string = '100000000000000000'; + const tokenBAmount: string = '10000000000000000000'; const nativeTokenAmount = '1000000000000000000'; const sideToContractMethods = new Map([ diff --git a/src/dex/quick-perps/quick-perps-events.test.ts b/src/dex/quick-perps/quick-perps-events.test.ts index 2964f35f6..020db0ab7 100644 --- a/src/dex/quick-perps/quick-perps-events.test.ts +++ b/src/dex/quick-perps/quick-perps-events.test.ts @@ -6,12 +6,14 @@ import { QuickPerpsConfig } from './config'; import { Network } from '../../constants'; import { DummyDexHelper } from '../../dex-helper/index'; import { testEventSubscriber } from '../../../tests/utils-events'; -import { PoolState } from './types'; +import { PoolConfig, PoolState } from './types'; jest.setTimeout(50 * 1000); const dexKey = 'QuickPerps'; -const network = Network.AVALANCHE; +const network = Network.ZKEVM; const params = QuickPerpsConfig[dexKey][network]; +const dexHelper = new DummyDexHelper(network); +const logger = dexHelper.getLogger(dexKey); async function fetchPoolState( quickPerpsPool: QuickPerpsEventPool, @@ -28,7 +30,6 @@ const stateWithoutTimestamp = (state: PoolState) => ({ ...state, secondaryPrices: { prices: state.secondaryPrices.prices, - // timestamp (this is removed) }, }); @@ -41,63 +42,48 @@ function compareState(state: PoolState, expectedState: PoolState) { describe('QuickPerps Event', function () { const blockNumbers: { [eventName: string]: number[] } = { IncreaseUsdqAmount: [ - 19403150, 19403175, 19403183, 19403215, 19403232, 19403246, 19403344, - 19403484, 19403545, 19403553, 19403586, 19403662, 19403712, 19403721, - 19403757, 19403775, 19403782, 19403800, 19403807, 19403808, 19403826, - 19403844, 19403848, 19403852, 19403860, 19403863, 19403875, 19403877, - 19403885, 19403900, 19403904, 19403938, 19403963, 19403970, 19403973, - 19403978, 19403999, 19404000, 19404023, 19404026, 19404046, 19404056, - 19404060, 19404078, 19404083, 19404097, 19404149, 19404164, 19404178, - 19404182, 19404229, 19404243, 19404264, 19404272, 19404287, 19404347, - 19404378, 19404379, 19404389, 19404408, 19404463, 19404491, 19404560, - 19404625, 19404657, 19404687, 19404700, 19404714, 19404763, 19404889, - 19404892, 19404893, 19404894, 19404897, 19404904, 19404916, 19404917, - 19404927, 19404935, 19404946, 19404949, 19404951, 19404959, 19404973, - 19405017, 19405027, 19405034, + 4960808, 4961034, 4961037, 4961046, 4961052, 4961055, 4961062, 4961153, + 4961167, 4961190, 4961194, 4961215, 4961220, 4961353, 4961472, 4961476, + 4961521, 4961628, 4961629, 4961648, 4961664, 4961683, 4961710, 4961716, + 4961848, 4961853, 4961863, 4961866, 4962156, 4962180, 4962200, 4962296, ], DecreaseUsdqAmount: [ - 19403150, 19403175, 19403183, 19403215, 19403232, 19403246, 19403344, - 19403545, 19403553, 19403662, 19403712, 19403721, 19403757, 19403775, - 19403782, 19403800, 19403807, 19403808, 19403826, 19403844, 19403848, - 19403852, 19403860, 19403863, 19403875, 19403877, 19403885, 19403900, - 19403904, 19403938, 19403963, 19403970, 19403973, 19403978, 19403999, - 19404000, 19404023, 19404026, 19404046, 19404056, 19404060, 19404078, - 19404083, 19404097, 19404149, 19404164, 19404178, 19404182, 19404229, - 19404243, 19404264, 19404272, 19404287, 19404347, 19404378, 19404379, - 19404389, 19404408, 19404463, 19404491, 19404560, 19404625, 19404657, - 19404687, 19404700, 19404714, 19404763, 19404889, 19404892, 19404893, - 19404894, 19404897, 19404904, 19404916, 19404917, 19404927, 19404935, - 19404946, 19404949, 19404951, 19404959, 19404973, 19405017, 19405027, - 19405034, + 4960808, 4960808, 4961034, 4961037, 4961046, 4961052, 4961055, 4961062, + 4961153, 4961167, 4961190, 4961194, 4961215, 4961220, 4961353, 4961472, + 4961476, 4961521, 4961628, 4961629, 4961648, 4961664, 4961683, 4961710, + 4961716, 4961848, 4961853, 4961863, 4961866, 4962156, 4962180, 4962200, + 4962296, ], - Transfer: [19403484, 19403586, 19405046, 19405100, 19405154, 19405318], + Transfer: [4958541, 4959994, 4959998, 4960452, 4960452], PriceUpdate: [ - 19403134, 19403135, 19403140, 19403141, 19403144, 19403148, 19403151, - 19403154, 19403163, 19403169, 19403170, 19403171, 19403178, 19403185, - 19403186, 19403202, + 4960534, 4960569, 4960584, 4960609, 4960637, 4960660, 4960694, 4960700, ], }; describe('QuickPerpsEventPool', function () { + let config: PoolConfig; + let quickPerpsPool: QuickPerpsEventPool; + let blockNumber: number; + beforeAll(async function () { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + + config = await QuickPerpsEventPool.getConfig( + params, + blockNumber, + dexHelper.multiContract, + ); + + quickPerpsPool = new QuickPerpsEventPool( + dexKey, + network, + dexHelper, + logger, + config, + ); + }); Object.keys(blockNumbers).forEach((event: string) => { blockNumbers[event].forEach((blockNumber: number) => { it(`Should return the correct state after the ${blockNumber}:${event}`, async function () { - const dexHelper = new DummyDexHelper(network); - const logger = dexHelper.getLogger(dexKey); - - const config = await QuickPerpsEventPool.getConfig( - params, - blockNumber, - dexHelper.multiContract, - ); - const quickPerpsPool = new QuickPerpsEventPool( - dexKey, - network, - dexHelper, - logger, - config, - ); - await testEventSubscriber( quickPerpsPool, quickPerpsPool.addressesSubscribed, diff --git a/src/dex/quick-perps/quick-perps.ts b/src/dex/quick-perps/quick-perps.ts index f31ba1523..a803741b4 100644 --- a/src/dex/quick-perps/quick-perps.ts +++ b/src/dex/quick-perps/quick-perps.ts @@ -54,9 +54,6 @@ export class QuickPerps extends SimpleExchange implements IDex { this.logger = dexHelper.getLogger(dexKey); } - // Initialize pricing is called once in the start of - // pricing service. It is intended to setup the integration - // for pricing requests. async initializePricing(blockNumber: number) { const config = await QuickPerpsEventPool.getConfig( this.params, diff --git a/src/dex/quick-perps/types.ts b/src/dex/quick-perps/types.ts index 284933bb4..3c52ee327 100644 --- a/src/dex/quick-perps/types.ts +++ b/src/dex/quick-perps/types.ts @@ -1,8 +1,8 @@ import { Address } from '../../types'; -import { ChainLinkState } from '../../lib/chainlink'; +import { Api3FeedSubscriberState } from '../../lib/api3-feed'; export type PoolState = { - primaryPrices: { [poolAddress: string]: ChainLinkState }; + primaryPrices: { [poolAddress: string]: Api3FeedSubscriberState }; secondaryPrices: FastPriceFeedState; vault: VaultState; usdq: USDQState; @@ -53,7 +53,6 @@ export type FastPriceFeedConfig = { }; export type VaultPriceFeedConfig = { - isAmmEnabled: boolean; isSecondaryPriceEnabled: boolean; strictStableTokens: { [address: string]: boolean }; spreadBasisPoints: { [address: string]: bigint }; @@ -61,8 +60,6 @@ export type VaultPriceFeedConfig = { isAdjustmentAdditive: { [address: string]: boolean }; priceDecimals: { [address: string]: number }; maxStrictPriceDeviation: bigint; - useV2Pricing: boolean; - priceSampleSpace: number; }; export type VaultConfig = { @@ -90,5 +87,7 @@ export type PoolConfig = { vaultConfig: VaultConfig; vaultPriceFeedConfig: VaultPriceFeedConfig; fastPriceFeedConfig: FastPriceFeedConfig; - chainlink: { [address: string]: { proxy: Address; aggregator: Address } }; + api3ServerV1: { + [address: string]: { proxy: Address; api3ServerV1: Address }; + }; }; diff --git a/src/dex/quick-perps/vault-price-feed.ts b/src/dex/quick-perps/vault-price-feed.ts index 43a96e7e2..eeff27b08 100644 --- a/src/dex/quick-perps/vault-price-feed.ts +++ b/src/dex/quick-perps/vault-price-feed.ts @@ -1,10 +1,10 @@ import { Interface } from '@ethersproject/abi'; import { Address, MultiCallInput, MultiCallOutput } from '../../types'; -import { PoolState, VaultPriceFeedConfig } from './types'; +import { VaultPriceFeedConfig } from './types'; import { FastPriceFeed } from './fast-price-feed'; import VaultPriceFeedAbi from '../../abi/quick-perps/vault-price-feed.json'; -import { ChainLinkSubscriber } from '../../lib/chainlink'; import { DeepReadonly } from 'ts-essentials'; +import { Api3FeedSubscriber } from '../../lib/api3-feed'; export class VaultPriceFeed { BASIS_POINTS_DIVISOR = 10000n; @@ -13,7 +13,6 @@ export class VaultPriceFeed { static interface = new Interface(VaultPriceFeedAbi); - protected isAmmEnabled: boolean; protected isSecondaryPriceEnabled: boolean; protected strictStableTokens: { [address: string]: boolean }; protected spreadBasisPoints: { [address: string]: bigint }; @@ -21,15 +20,12 @@ export class VaultPriceFeed { protected isAdjustmentAdditive: { [address: string]: boolean }; protected priceDecimals: { [address: string]: number }; protected maxStrictPriceDeviation: bigint; - protected useV2Pricing: boolean; - protected priceSampleSpace: number; constructor( config: VaultPriceFeedConfig, - protected primaryPrices: { [token: string]: ChainLinkSubscriber }, + protected primaryPrices: { [token: string]: Api3FeedSubscriber }, protected secondaryPrice: FastPriceFeed, ) { - this.isAmmEnabled = config.isAmmEnabled; this.isSecondaryPriceEnabled = config.isSecondaryPriceEnabled; this.strictStableTokens = config.strictStableTokens; this.spreadBasisPoints = config.spreadBasisPoints; @@ -37,20 +33,16 @@ export class VaultPriceFeed { this.isAdjustmentAdditive = config.isAdjustmentAdditive; this.priceDecimals = config.priceDecimals; this.maxStrictPriceDeviation = config.maxStrictPriceDeviation; - this.useV2Pricing = config.useV2Pricing; - this.priceSampleSpace = config.priceSampleSpace; } getPrice( state: DeepReadonly, _token: Address, - _maximise: boolean, + _maximize: boolean, _includeAmmPrice: boolean, _useSwapPricing: boolean, ): bigint { - let price = this.useV2Pricing - ? this.getPriceV2(state, _token, _maximise, _includeAmmPrice) - : this.getPriceV1(state, _token, _maximise, _includeAmmPrice); + let price = this.getPriceV1(state, _token, _maximize, _includeAmmPrice); const adjustmentBps = this.adjustmentBasisPoints[_token]; if (adjustmentBps > 0n) { @@ -69,39 +61,16 @@ export class VaultPriceFeed { return price; } - getPriceV2( - state: DeepReadonly, - _token: Address, - _maximise: boolean, - _includeAmmPrice: boolean, - ): bigint { - throw new Error( - 'getPriceV2 implementation is not complete, devs should disable the dex or complete the implementation', - ); - } - getPriceV1( state: DeepReadonly, _token: Address, - _maximise: boolean, + _maximize: boolean, _includeAmmPrice: boolean, ): bigint { - let price = this.getPrimaryPrice(state, _token, _maximise); - - if (_includeAmmPrice && this.isAmmEnabled) { - const ammPrice = this.getAmmPrice(state, _token); - if (ammPrice > 0n) { - if (_maximise && ammPrice > price) { - price = ammPrice; - } - if (!_maximise && ammPrice < price) { - price = ammPrice; - } - } - } + let price = this.getPrimaryPrice(state, _token, _maximize); if (this.isSecondaryPriceEnabled) { - price = this.getSecondaryPrice(state, _token, price, _maximise); + price = this.getSecondaryPrice(state, _token, price, _maximize); } if (this.strictStableTokens[_token]) { @@ -111,13 +80,13 @@ export class VaultPriceFeed { return this.ONE_USD; } - // if _maximise and price is e.g. 1.02, return 1.02 - if (_maximise && price > this.ONE_USD) { + // if _maximize and price is e.g. 1.02, return 1.02 + if (_maximize && price > this.ONE_USD) { return price; } - // if !_maximise and price is e.g. 0.98, return 0.98 - if (!_maximise && price < this.ONE_USD) { + // if !_maximize and price is e.g. 0.98, return 0.98 + if (!_maximize && price < this.ONE_USD) { return price; } @@ -126,7 +95,7 @@ export class VaultPriceFeed { const _spreadBasisPoints = this.spreadBasisPoints[_token]; - if (_maximise) { + if (_maximize) { return ( (price * (this.BASIS_POINTS_DIVISOR + _spreadBasisPoints)) / this.BASIS_POINTS_DIVISOR @@ -141,14 +110,14 @@ export class VaultPriceFeed { getAmmPrice(state: DeepReadonly, token: Address): bigint { throw new Error( - 'getAmmPrice implementation is not complete, devs should disable the dex or complete the implementation', + 'getAmmPrice implementation is not complete, developers should disable the dex or complete the implementation', ); } getPrimaryPrice( state: DeepReadonly, _token: Address, - _maximise: boolean, + _maximize: boolean, ): bigint { // const priceFeedAddress = this.priceFeeds[_token]; // require(priceFeedAddress != address(0), "VaultPriceFeed: invalid price feed"); @@ -185,25 +154,20 @@ export class VaultPriceFeed { // continue; // } - // if (_maximise && p > price) { + // if (_maximize && p > price) { // price = p; // continue; // } - // if (!_maximise && p < price) { + // if (!_maximize && p < price) { // price = p; // } // } - if (this.priceSampleSpace > 1) { - throw new Error( - 'Chainlink price feed is not implemented for historical prices', - ); - } - price = this.primaryPrices[_token].getLatestRoundData(state); + price = this.primaryPrices[_token].getLatestData(state); // require(price > 0n, "VaultPriceFeed: could not fetch price"); if (price <= 0n) throw new Error('VaultPriceFeed: could not fetch price'); - // normalise price precision + // normalize price precision const _priceDecimals = this.priceDecimals[_token]; return (price * this.PRICE_PRECISION) / BigInt(10 ** _priceDecimals); } @@ -212,13 +176,13 @@ export class VaultPriceFeed { state: DeepReadonly, _token: Address, _referencePrice: bigint, - _maximise: boolean, + _maximize: boolean, ): bigint { return this.secondaryPrice.getPrice( state, _token, _referencePrice, - _maximise, + _maximize, ); } @@ -227,10 +191,6 @@ export class VaultPriceFeed { tokenAddresses: Address[], ): MultiCallInput[] { return [ - { - target: vaultPriceFeedAddress, - callData: VaultPriceFeed.interface.encodeFunctionData('isAmmEnabled'), - }, { target: vaultPriceFeedAddress, callData: VaultPriceFeed.interface.encodeFunctionData( @@ -277,15 +237,6 @@ export class VaultPriceFeed { 'maxStrictPriceDeviation', ), }, - { - target: vaultPriceFeedAddress, - callData: VaultPriceFeed.interface.encodeFunctionData('useV2Pricing'), - }, - { - target: vaultPriceFeedAddress, - callData: - VaultPriceFeed.interface.encodeFunctionData('priceSampleSpace'), - }, ]; } @@ -295,10 +246,6 @@ export class VaultPriceFeed { ): VaultPriceFeedConfig { let i = 0; return { - isAmmEnabled: VaultPriceFeed.interface.decodeFunctionResult( - 'isAmmEnabled', - multicallOutputs[i++], - )[0], isSecondaryPriceEnabled: VaultPriceFeed.interface.decodeFunctionResult( 'isSecondaryPriceEnabled', multicallOutputs[i++], @@ -370,15 +317,6 @@ export class VaultPriceFeed { )[0] .toString(), ), - useV2Pricing: VaultPriceFeed.interface.decodeFunctionResult( - 'useV2Pricing', - multicallOutputs[i++], - )[0], - priceSampleSpace: parseInt( - VaultPriceFeed.interface - .decodeFunctionResult('priceSampleSpace', multicallOutputs[i++])[0] - .toString(), - ), }; } } diff --git a/src/lib/api3-feed.ts b/src/lib/api3-feed.ts new file mode 100644 index 000000000..103965360 --- /dev/null +++ b/src/lib/api3-feed.ts @@ -0,0 +1,100 @@ +import { DeepReadonly } from 'ts-essentials'; +import { PartialEventSubscriber } from '../composed-event-subscriber'; +import { + Address, + BlockHeader, + Log, + Logger, + MultiCallInput, + MultiCallOutput, +} from '../types'; +import { Lens } from '../lens'; +import { Interface } from '@ethersproject/abi'; +import ProxyABI from '../abi/api3-proxy.json'; +import Api3ServerV1ABI from '../abi/api3-server-v1.json'; + +export type Api3FeedSubscriberState = { + value: bigint; + timestamp: bigint; +}; + +export class Api3FeedSubscriber extends PartialEventSubscriber< + State, + Api3FeedSubscriberState +> { + static readonly proxyInterface = new Interface(ProxyABI); + static readonly api3ServerV1Iface = new Interface(Api3ServerV1ABI); + static readonly ANSWER_UPDATED_SIGNED_DATA = + Api3FeedSubscriber.api3ServerV1Iface.getEventTopic( + 'UpdatedBeaconWithSignedData', + ); + + constructor( + private proxy: Address, + api3Server: Address, + lens: Lens, DeepReadonly>, + logger: Logger, + ) { + super([api3Server], lens, logger); + } + + static getApi3ServerV1MultiCallInput(proxy: Address): MultiCallInput { + return { + target: proxy, + callData: + Api3FeedSubscriber.proxyInterface.encodeFunctionData('api3ServerV1'), + }; + } + + static decodeApi3ServerV1Result(multicallOutput: MultiCallOutput): Address { + return Api3FeedSubscriber.proxyInterface.decodeFunctionResult( + 'api3ServerV1', + multicallOutput, + )[0]; + } + + public processLog( + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ): DeepReadonly | null { + if (log.topics[0] !== Api3FeedSubscriber.ANSWER_UPDATED_SIGNED_DATA) + return null; // Ignore other events + const decoded = Api3FeedSubscriber.api3ServerV1Iface.decodeEventLog( + 'UpdatedBeaconWithSignedData', + log.data, + log.topics, + ); + return { + value: BigInt(decoded.value.toString()), + timestamp: BigInt(decoded.timestamp.toString()), + }; + } + + public getGenerateStateMultiCallInputs(): MultiCallInput[] { + return [ + { + target: this.proxy, + callData: Api3FeedSubscriber.proxyInterface.encodeFunctionData('read'), + }, + ]; + } + + public generateState( + multicallOutputs: MultiCallOutput[], + blockNumber?: number | 'latest', + ): DeepReadonly { + const decoded = Api3FeedSubscriber.proxyInterface.decodeFunctionResult( + 'read', + multicallOutputs[0], + ); + return { + value: BigInt(decoded.value.toString()), + timestamp: BigInt(decoded.timestamp.toString()), + }; + } + + public getLatestData(state: DeepReadonly): bigint { + return this.lens.get()(state).value; + } +} diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index 85df1682c..7f02a8f2f 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -857,6 +857,16 @@ export const Tokens: { decimals: 8, }, }, + [Network.ZKEVM]: { + WETH: { + address: '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', + decimals: 18, + }, + MATIC: { + address: '0xa2036f0538221a77a3937f1379699f44945018d0', + decimals: 18, + }, + }, }; export const Holders: { @@ -1068,6 +1078,10 @@ export const Holders: { rETH: '0x4c2e69e58b14de9afedfb94319519ce34e087283', WBTC: '0xb9c8f0d3254007ee4b98970b94544e473cd610ec', }, + [Network.ZKEVM]: { + WETH: '0xc44ad482f24fd750caeba387d2726d8653f8c4bb', + MATIC: '0x8f2a1450c040b3c19efe9676165d8f30d8280019', + }, }; export const SmartTokens = Object.keys(Tokens).reduce((acc, _network) => { From 5931d6099291a0cc3dedc6ddce7f89dd4bb5e785 Mon Sep 17 00:00:00 2001 From: Verisana Date: Wed, 30 Aug 2023 09:56:41 +0100 Subject: [PATCH 28/45] 2.31.4-zkevm-revive.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4dbd325ce..2576a08ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.9", + "version": "2.31.4-zkevm-revive.10", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From cb2e9a8536995b04fd783b148332f8d301e529c6 Mon Sep 17 00:00:00 2001 From: Verisana Date: Wed, 30 Aug 2023 11:21:56 +0100 Subject: [PATCH 29/45] fix: initialize quickPerps --- src/dex/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dex/index.ts b/src/dex/index.ts index 11302d4c3..9713509cd 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -71,6 +71,7 @@ import { SpiritSwapV3 } from './quickswap/spiritswap-v3'; import { TraderJoeV21 } from './trader-joe-v2.1'; import { PancakeswapV3 } from './pancakeswap-v3/pancakeswap-v3'; import { Algebra } from './algebra/algebra'; +import { QuickPerps } from './quick-perps/quick-perps'; const LegacyDexes = [ CurveV2, @@ -139,6 +140,7 @@ const Dexes = [ MaverickV1, Camelot, SwaapV2, + QuickPerps, ]; export type LegacyDexConstructor = new (dexHelper: IDexHelper) => IDexTxBuilder< From d342e6fe8259d1a3c58867e954aab5b484d23878 Mon Sep 17 00:00:00 2001 From: Verisana Date: Wed, 30 Aug 2023 11:23:29 +0100 Subject: [PATCH 30/45] 2.31.4-zkevm-revive.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2576a08ba..f0e0fd5a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.10", + "version": "2.31.4-zkevm-revive.11", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From c8e7dc8f1c8e6d3381275273feb29ec55415838a Mon Sep 17 00:00:00 2001 From: Verisana Date: Wed, 30 Aug 2023 11:52:02 +0100 Subject: [PATCH 31/45] feat: update adapter addresses for polygon zkevm --- src/config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.ts b/src/config.ts index 399298705..bac6becaf 100644 --- a/src/config.ts +++ b/src/config.ts @@ -332,8 +332,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { multicallV2Address: '0x6cA478C852DfA8941FC819fDf248606eA04780B6', privateHttpProvider: process.env.HTTP_PROVIDER_1101, adapterAddresses: { - PolygonZkEvmAdapter01: '0x760870be538e7b4E2110e2890473CC17ADB1fdC1', - PolygonZkEvmBuyAdapter: '0x61bFA60A992472869F8d7b4705609A6E0BCf383f', + PolygonZkEvmAdapter01: '0xd63B7691dD98fa89A2ea5e1604700489c585aa7B', + PolygonZkEvmBuyAdapter: '0xe2137168CdA486a2555E16c597905854C84F9127', }, rpcPollingMaxAllowedStateDelayInBlocks: 0, From 5f1d708cac51aca35098cc8d36c8ac9464b9182f Mon Sep 17 00:00:00 2001 From: Verisana Date: Wed, 30 Aug 2023 12:58:10 +0100 Subject: [PATCH 32/45] fix: move quickswap instance initializer inside test function --- .../quick-perps/quick-perps-events.test.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/dex/quick-perps/quick-perps-events.test.ts b/src/dex/quick-perps/quick-perps-events.test.ts index 020db0ab7..f3d4999e2 100644 --- a/src/dex/quick-perps/quick-perps-events.test.ts +++ b/src/dex/quick-perps/quick-perps-events.test.ts @@ -42,10 +42,11 @@ function compareState(state: PoolState, expectedState: PoolState) { describe('QuickPerps Event', function () { const blockNumbers: { [eventName: string]: number[] } = { IncreaseUsdqAmount: [ - 4960808, 4961034, 4961037, 4961046, 4961052, 4961055, 4961062, 4961153, - 4961167, 4961190, 4961194, 4961215, 4961220, 4961353, 4961472, 4961476, - 4961521, 4961628, 4961629, 4961648, 4961664, 4961683, 4961710, 4961716, - 4961848, 4961853, 4961863, 4961866, 4962156, 4962180, 4962200, 4962296, + 4960808, 4960808, 4961034, 4961037, 4961046, 4961052, 4961055, 4961062, + 4961153, 4961167, 4961190, 4961194, 4961215, 4961220, 4961353, 4961472, + 4961476, 4961521, 4961628, 4961629, 4961648, 4961664, 4961683, 4961710, + 4961716, 4961848, 4961853, 4961863, 4961866, 4962156, 4962180, 4962200, + 4962296, ], DecreaseUsdqAmount: [ 4960808, 4960808, 4961034, 4961037, 4961046, 4961052, 4961055, 4961062, @@ -72,18 +73,17 @@ describe('QuickPerps Event', function () { blockNumber, dexHelper.multiContract, ); - - quickPerpsPool = new QuickPerpsEventPool( - dexKey, - network, - dexHelper, - logger, - config, - ); }); Object.keys(blockNumbers).forEach((event: string) => { blockNumbers[event].forEach((blockNumber: number) => { it(`Should return the correct state after the ${blockNumber}:${event}`, async function () { + quickPerpsPool = new QuickPerpsEventPool( + dexKey, + network, + dexHelper, + logger, + config, + ); await testEventSubscriber( quickPerpsPool, quickPerpsPool.addressesSubscribed, From 63a0ef95f54cf1699c902bf7b29ff339d28eb56e Mon Sep 17 00:00:00 2001 From: Verisana Date: Wed, 30 Aug 2023 13:43:22 +0100 Subject: [PATCH 33/45] test: create EstimateGas simulator for zkEVM --- src/dex/quick-perps/quick-perps-e2e.test.ts | 38 ++++------------ tests/constants-e2e.ts | 5 +++ tests/tenderly-simulation.ts | 48 ++++++++++++++++++++- tests/utils-e2e.ts | 28 +++++++----- 4 files changed, 77 insertions(+), 42 deletions(-) diff --git a/src/dex/quick-perps/quick-perps-e2e.test.ts b/src/dex/quick-perps/quick-perps-e2e.test.ts index f4a72acf8..43bb516a8 100644 --- a/src/dex/quick-perps/quick-perps-e2e.test.ts +++ b/src/dex/quick-perps/quick-perps-e2e.test.ts @@ -23,13 +23,11 @@ describe('QuickPerps E2E', () => { network, ); - const tokenASymbol: string = 'WETH'; + const tokenASymbol: string = 'ETH'; const tokenBSymbol: string = 'MATIC'; - const nativeTokenSymbol = NativeTokenSymbols[network]; const tokenAAmount: string = '100000000000000000'; const tokenBAmount: string = '10000000000000000000'; - const nativeTokenAmount = '1000000000000000000'; const sideToContractMethods = new Map([ [ @@ -45,33 +43,7 @@ describe('QuickPerps E2E', () => { sideToContractMethods.forEach((contractMethods, side) => contractMethods.forEach((contractMethod: ContractMethod) => { describe(`${contractMethod}`, () => { - it(nativeTokenSymbol + ' -> TOKEN', async () => { - await testE2E( - tokens[nativeTokenSymbol], - tokens[tokenASymbol], - holders[nativeTokenSymbol], - side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); - }); - it('TOKEN -> ' + nativeTokenSymbol, async () => { - await testE2E( - tokens[tokenASymbol], - tokens[nativeTokenSymbol], - holders[tokenASymbol], - side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); - }); - it('TOKEN -> TOKEN', async () => { + it('ETH -> MATIC', async () => { await testE2E( tokens[tokenASymbol], tokens[tokenBSymbol], @@ -82,6 +54,12 @@ describe('QuickPerps E2E', () => { contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + undefined, + true, ); }); }); diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index 7f02a8f2f..e86553f32 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -858,6 +858,10 @@ export const Tokens: { }, }, [Network.ZKEVM]: { + ETH: { + address: ETHER_ADDRESS, + decimals: 18, + }, WETH: { address: '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', decimals: 18, @@ -1079,6 +1083,7 @@ export const Holders: { WBTC: '0xb9c8f0d3254007ee4b98970b94544e473cd610ec', }, [Network.ZKEVM]: { + ETH: '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', WETH: '0xc44ad482f24fd750caeba387d2726d8653f8c4bb', MATIC: '0x8f2a1450c040b3c19efe9676165d8f30d8280019', }, diff --git a/tests/tenderly-simulation.ts b/tests/tenderly-simulation.ts index f24bbda98..1d845b5ac 100644 --- a/tests/tenderly-simulation.ts +++ b/tests/tenderly-simulation.ts @@ -1,5 +1,7 @@ +/* eslint-disable no-console */ import axios from 'axios'; import { Address } from '@paraswap/core'; +import { Provider } from '@ethersproject/providers'; import { TxObject } from '../src/types'; import { StateOverrides, StateSimulateApiOverride } from './smart-tokens'; @@ -9,7 +11,50 @@ const TENDERLY_PROJECT = process.env.TENDERLY_PROJECT; const TENDERLY_FORK_ID = process.env.TENDERLY_FORK_ID; const TENDERLY_FORK_LAST_TX_ID = process.env.TENDERLY_FORK_LAST_TX_ID; -export class TenderlySimulation { +export type SimulationResult = { + success: boolean; + gasUsed?: string; + url?: string; + transaction?: any; +}; + +export interface TransactionSimulator { + forkId: string; + setup(): Promise; + + simulate( + params: TxObject, + stateOverrides?: StateOverrides, + ): Promise; +} + +export class EstimateGasSimulation implements TransactionSimulator { + forkId: string = '0'; + + constructor(private provider: Provider) {} + + async setup() {} + + async simulate( + params: TxObject, + _: StateOverrides, + ): Promise { + try { + const result = await this.provider.estimateGas(params); + return { + success: true, + gasUsed: result.toNumber().toString(), + }; + } catch (e) { + console.error(`Estimate gas simulation failed:`, e); + return { + success: false, + }; + } + } +} + +export class TenderlySimulation implements TransactionSimulator { lastTx: string = ''; forkId: string = ''; maxGasLimit = 80000000; @@ -118,7 +163,6 @@ export class TenderlySimulation { console.error(`TenderlySimulation_simulate:`, e); return { success: false, - tenderlyUrl: '', }; } } diff --git a/tests/utils-e2e.ts b/tests/utils-e2e.ts index 12143c4dc..ca5e068df 100644 --- a/tests/utils-e2e.ts +++ b/tests/utils-e2e.ts @@ -5,7 +5,11 @@ import { IParaSwapSDK, LocalParaswapSDK, } from '../src/implementations/local-paraswap-sdk'; -import { TenderlySimulation } from './tenderly-simulation'; +import { + EstimateGasSimulation, + TenderlySimulation, + TransactionSimulator, +} from './tenderly-simulation'; import { SwapSide, ETHER_ADDRESS, @@ -24,7 +28,7 @@ import { import Erc20ABI from '../src/abi/erc20.json'; import AugustusABI from '../src/abi/augustus.json'; import { generateConfig } from '../src/config'; -import { DummyLimitOrderProvider } from '../src/dex-helper'; +import { DummyDexHelper, DummyLimitOrderProvider } from '../src/dex-helper'; import { constructSimpleSDK, SimpleFetchSDK } from '@paraswap/sdk'; import axios from 'axios'; import { SmartToken, StateOverrides } from './smart-tokens'; @@ -268,16 +272,20 @@ export async function testE2E( // Specified in BPS: part of 10000 slippage?: number, sleepMs?: number, + replaceTenderlyWithEstimateGas?: boolean, ) { const amount = BigInt(_amount); - const ts = new TenderlySimulation(network); + + const ts: TransactionSimulator = replaceTenderlyWithEstimateGas + ? new EstimateGasSimulation(new DummyDexHelper(network).provider) + : new TenderlySimulation(network); await ts.setup(); if (srcToken.address.toLowerCase() !== ETHER_ADDRESS.toLowerCase()) { const allowanceTx = await ts.simulate( allowTokenTransferProxyParams(srcToken.address, senderAddress, network), ); - if (!allowanceTx.success) console.log(allowanceTx.tenderlyUrl); + if (!allowanceTx.success) console.log(allowanceTx.url); expect(allowanceTx!.success).toEqual(true); } @@ -309,7 +317,7 @@ export async function testE2E( expect(deployTx.success).toEqual(true); const contractAddress = - deployTx.transaction.transaction_info.contract_address; + deployTx.transaction?.transaction_info.contract_address; console.log( formatDeployMessage( 'adapter', @@ -384,15 +392,15 @@ export async function testE2E( const swapTx = await ts.simulate(swapParams); // Only log gas estimate if testing against API - if (useAPI) + if (useAPI) { + const gasUsed = swapTx.gasUsed || '0'; console.log( `Gas Estimate API: ${priceRoute.gasCost}, Simulated: ${ swapTx!.gasUsed - }, Difference: ${ - parseInt(priceRoute.gasCost) - parseInt(swapTx!.gasUsed) - }`, + }, Difference: ${parseInt(priceRoute.gasCost) - parseInt(gasUsed)}`, ); - console.log(`Tenderly URL: ${swapTx!.tenderlyUrl}`); + } + console.log(`Tenderly URL: ${swapTx!.url}`); expect(swapTx!.success).toEqual(true); } finally { if (paraswap.releaseResources) { From fea02932491ea059c630a85cf930b821e4dfacf0 Mon Sep 17 00:00:00 2001 From: Verisana Date: Wed, 30 Aug 2023 15:06:40 +0100 Subject: [PATCH 34/45] fix: add logger when generateState fails --- src/composed-event-subscriber.ts | 27 ++++++++++++++++----------- src/dex/quick-perps/pool.ts | 5 +++-- src/dex/quick-perps/quick-perps.ts | 1 - 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/composed-event-subscriber.ts b/src/composed-event-subscriber.ts index 0a7d15d28..fb18ed88d 100644 --- a/src/composed-event-subscriber.ts +++ b/src/composed-event-subscriber.ts @@ -110,17 +110,22 @@ export abstract class ComposedEventSubscriber< .call({}, blockNumber) ).returnData; } - const stateParts = await Promise.all( - this.parts.map((p, i) => - p.generateState( - returnData.slice(...this.multiCallSlices[i]), - blockNumber, + try { + const stateParts = await Promise.all( + this.parts.map((p, i) => + p.generateState( + returnData.slice(...this.multiCallSlices[i]), + blockNumber, + ), ), - ), - ); - return this.parts.reduce( - (acc, p, i) => p.lens.set(stateParts[i])(acc), - this.blankState, - ); + ); + return this.parts.reduce( + (acc, p, i) => p.lens.set(stateParts[i])(acc), + this.blankState, + ); + } catch (e) { + this.logger.error(`Error generating state: ${e}`); + throw e; + } } } diff --git a/src/dex/quick-perps/pool.ts b/src/dex/quick-perps/pool.ts index 7d641706e..c6ddf02c0 100644 --- a/src/dex/quick-perps/pool.ts +++ b/src/dex/quick-perps/pool.ts @@ -26,7 +26,6 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { parentName: string, protected network: number, protected dexHelper: IDexHelper, - logger: Logger, config: PoolConfig, ) { const api3ServerV1Map = Object.entries(config.api3ServerV1).reduce( @@ -38,7 +37,9 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { value.proxy, value.api3ServerV1, lens>().primaryPrices[key], - dexHelper.getLogger(`${key} ChainLink for ${parentName}-${network}`), + dexHelper.getLogger( + `${key} Api3FeedSubscriber for ${parentName}-${network}`, + ), ); return acc; }, diff --git a/src/dex/quick-perps/quick-perps.ts b/src/dex/quick-perps/quick-perps.ts index a803741b4..ac0e4e0c3 100644 --- a/src/dex/quick-perps/quick-perps.ts +++ b/src/dex/quick-perps/quick-perps.ts @@ -67,7 +67,6 @@ export class QuickPerps extends SimpleExchange implements IDex { this.dexKey, this.network, this.dexHelper, - this.logger, config, ); await this.pool.initialize(blockNumber); From 026bddad899ba29b2d3ab7ecb308d5b69cc89a63 Mon Sep 17 00:00:00 2001 From: Verisana Date: Wed, 30 Aug 2023 15:08:42 +0100 Subject: [PATCH 35/45] 2.31.4-zkevm-revive.12 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f0e0fd5a5..ebf0924fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.11", + "version": "2.31.4-zkevm-revive.12", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From e8e24ea665c6a2d1cb9357ba2479c3a26b61a886 Mon Sep 17 00:00:00 2001 From: Verisana Date: Wed, 30 Aug 2023 18:14:49 +0100 Subject: [PATCH 36/45] feat: WIP adding dapiNameHash to beaconId --- src/abi/api3-proxy.json | 13 +++ src/dex/quick-perps/pool.ts | 27 +++++-- src/dex/quick-perps/quick-perps-e2e.test.ts | 6 +- .../quick-perps-integration.test.ts | 79 +++++++++++++------ src/dex/quick-perps/types.ts | 6 +- src/lib/api3-feed.ts | 32 ++++++++ tests/constants-e2e.ts | 10 +++ 7 files changed, 136 insertions(+), 37 deletions(-) diff --git a/src/abi/api3-proxy.json b/src/abi/api3-proxy.json index a9b902bca..c4bb9ab9d 100644 --- a/src/abi/api3-proxy.json +++ b/src/abi/api3-proxy.json @@ -12,6 +12,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "dapiNameHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "read", diff --git a/src/dex/quick-perps/pool.ts b/src/dex/quick-perps/pool.ts index c6ddf02c0..6c90b8baa 100644 --- a/src/dex/quick-perps/pool.ts +++ b/src/dex/quick-perps/pool.ts @@ -280,11 +280,13 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { let multiCallData: MultiCallInput[] = []; let i = 0; for (let priceFeed of priceFeeds) { - const chainlinkConfigCallData = + const api3ServerAddressCallData = Api3FeedSubscriber.getApi3ServerV1MultiCallInput(priceFeed); - multiCallData.push(chainlinkConfigCallData); - multicallSlices.push([i, i + 1]); - i += 1; + const dapiNameHashCallData = + Api3FeedSubscriber.getApi3ServerV1MultiCallInput(priceFeed); + multiCallData.push(...[api3ServerAddressCallData, dapiNameHashCallData]); + multicallSlices.push([i, i + 2]); + i += 2; } const fastPriceFeedConfigCallData = FastPriceFeed.getConfigMulticallInputs( @@ -314,15 +316,24 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { ).returnData; const api3ServerV1: { - [address: string]: { proxy: Address; api3ServerV1: Address }; + [address: string]: { + proxy: Address; + api3ServerV1: Address; + dapiNameHash: string; + }; } = {}; for (let token of tokens) { - const serverV1Address = Api3FeedSubscriber.decodeApi3ServerV1Result( - configResults.slice(...multicallSlices.shift()!)[0], + const [api3ServerAddressRes, dapiHashNameRes] = configResults.slice( + ...multicallSlices.shift()!, ); + const serverV1Address = + Api3FeedSubscriber.decodeApi3ServerV1Result(api3ServerAddressRes); + const dapiNameHash = + Api3FeedSubscriber.decodeApi3ServerV1Result(dapiHashNameRes); api3ServerV1[token] = { proxy: priceFeeds.shift(), api3ServerV1: serverV1Address, + dapiNameHash, }; } @@ -345,6 +356,8 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { const vaultConfigResults = configResults.slice(...multicallSlices.shift()!); const vaultConfig = Vault.getConfig(vaultConfigResults, tokens); + const beeconIdMulticall = + return { vaultAddress: dexParams.vault, readerAddress: dexParams.reader, diff --git a/src/dex/quick-perps/quick-perps-e2e.test.ts b/src/dex/quick-perps/quick-perps-e2e.test.ts index 43bb516a8..6d42bdf0d 100644 --- a/src/dex/quick-perps/quick-perps-e2e.test.ts +++ b/src/dex/quick-perps/quick-perps-e2e.test.ts @@ -2,11 +2,7 @@ import dotenv from 'dotenv'; dotenv.config(); import { testE2E } from '../../../tests/utils-e2e'; -import { - Tokens, - Holders, - NativeTokenSymbols, -} from '../../../tests/constants-e2e'; +import { Tokens, Holders } from '../../../tests/constants-e2e'; import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; diff --git a/src/dex/quick-perps/quick-perps-integration.test.ts b/src/dex/quick-perps/quick-perps-integration.test.ts index 28e243a78..e45db84c5 100644 --- a/src/dex/quick-perps/quick-perps-integration.test.ts +++ b/src/dex/quick-perps/quick-perps-integration.test.ts @@ -16,32 +16,19 @@ import { Tokens } from '../../../tests/constants-e2e'; import ReaderABI from '../../abi/quick-perps/reader.json'; const network = Network.ZKEVM; -const TokenASymbol = 'WETH'; -const TokenA = Tokens[network][TokenASymbol]; - -const TokenBSymbol = 'MATIC'; -const TokenB = Tokens[network][TokenBSymbol]; - -const amounts = [ - 0n, - 100000000000000000n, - 200000000000000000n, - 300000000000000000n, - 400000000000000000n, - 500000000000000000n, - 600000000000000000n, - 700000000000000000n, - 800000000000000000n, - 900000000000000000n, - 1000000000000000000n, -]; - const dexKey = 'QuickPerps'; const params = QuickPerpsConfig[dexKey][network]; const readerInterface = new Interface(ReaderABI); const readerAddress = '0xf1CFB75854DE535475B88Bb6FBad317eea98c0F9'; -describe('QuickPerps', function () { +function testsForCase( + tokenASymbol: string, + tokenBSymbol: string, + amounts: bigint[], +) { + const TokenA = Tokens[network][tokenASymbol]; + const TokenB = Tokens[network][tokenBSymbol]; + it('getPoolIdentifiers and getPricesVolume SELL', async function () { const dexHelper = new DummyDexHelper(network); const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); @@ -55,7 +42,7 @@ describe('QuickPerps', function () { SwapSide.SELL, blocknumber, ); - console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools); + console.log(`${tokenASymbol} <> ${tokenBSymbol} Pool Identifiers: `, pools); expect(pools.length).toBeGreaterThan(0); @@ -67,7 +54,7 @@ describe('QuickPerps', function () { blocknumber, pools, ); - console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices); + console.log(`${tokenASymbol} <> ${tokenBSymbol} Pool Prices: `, poolPrices); expect(poolPrices).not.toBeNull(); if (quickPerps.hasConstantPriceLargeAmounts) { @@ -111,7 +98,7 @@ describe('QuickPerps', function () { 10, ); console.log( - `${TokenASymbol} Top Pools:`, + `${tokenASymbol} Top Pools:`, JSON.stringify(poolLiquidity, null, 2), ); @@ -119,4 +106,48 @@ describe('QuickPerps', function () { checkPoolsLiquidity(poolLiquidity, TokenA.address, dexKey); } }); +} + +describe('QuickPerps', function () { + describe('WETH -> MATIC', function () { + const TokenASymbol = 'WETH'; + const TokenBSymbol = 'MATIC'; + + const amounts = [ + 0n, + 100000000000000000n, + 200000000000000000n, + 300000000000000000n, + 400000000000000000n, + 500000000000000000n, + 600000000000000000n, + 700000000000000000n, + 800000000000000000n, + 900000000000000000n, + 1000000000000000000n, + ]; + + testsForCase(TokenASymbol, TokenBSymbol, amounts); + }); + + describe('WBTC -> USDC', function () { + const TokenASymbol = 'WBTC'; + const TokenBSymbol = 'USDC'; + + const amounts = [ + 0n, + 100000000n, + 200000000n, + 300000000n, + 400000000n, + 500000000n, + 600000000n, + 700000000n, + 800000000n, + 900000000n, + 1000000000n, + ]; + + testsForCase(TokenASymbol, TokenBSymbol, amounts); + }); }); diff --git a/src/dex/quick-perps/types.ts b/src/dex/quick-perps/types.ts index 3c52ee327..6bf52114a 100644 --- a/src/dex/quick-perps/types.ts +++ b/src/dex/quick-perps/types.ts @@ -88,6 +88,10 @@ export type PoolConfig = { vaultPriceFeedConfig: VaultPriceFeedConfig; fastPriceFeedConfig: FastPriceFeedConfig; api3ServerV1: { - [address: string]: { proxy: Address; api3ServerV1: Address }; + [address: string]: { + proxy: Address; + api3ServerV1: Address; + beaconId: string; + }; }; }; diff --git a/src/lib/api3-feed.ts b/src/lib/api3-feed.ts index 103965360..44fb76826 100644 --- a/src/lib/api3-feed.ts +++ b/src/lib/api3-feed.ts @@ -32,6 +32,7 @@ export class Api3FeedSubscriber extends PartialEventSubscriber< constructor( private proxy: Address, api3Server: Address, + private beaconId: string, lens: Lens, DeepReadonly>, logger: Logger, ) { @@ -46,6 +47,30 @@ export class Api3FeedSubscriber extends PartialEventSubscriber< }; } + static getDapiNameHash(proxy: Address): MultiCallInput { + return { + target: proxy, + callData: + Api3FeedSubscriber.proxyInterface.encodeFunctionData('dapiNameHash'), + }; + } + + static getFeedIdFromDapiNameHash(api3Server: string, dapiNameHash: string) { + return { + target: api3Server, + callData: Api3FeedSubscriber.api3ServerV1Iface.encodeFunctionData( + 'dapiNameHashToDataFeedId', + ), + }; + } + + static decodeFeedIdFromDapiNameHash(multicallOutput: MultiCallOutput) { + return Api3FeedSubscriber.proxyInterface.decodeFunctionResult( + 'dapiNameHashToDataFeedId', + multicallOutput, + )[0]; + } + static decodeApi3ServerV1Result(multicallOutput: MultiCallOutput): Address { return Api3FeedSubscriber.proxyInterface.decodeFunctionResult( 'api3ServerV1', @@ -53,6 +78,13 @@ export class Api3FeedSubscriber extends PartialEventSubscriber< )[0]; } + static decodeDapiNameHash(multicallOutput: MultiCallOutput): Address { + return Api3FeedSubscriber.proxyInterface.decodeFunctionResult( + 'dapiNameHash', + multicallOutput, + )[0]; + } + public processLog( state: DeepReadonly, log: Readonly, diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index 2deaae8ac..6f0bddf55 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -870,6 +870,14 @@ export const Tokens: { address: '0xa2036f0538221a77a3937f1379699f44945018d0', decimals: 18, }, + WBTC: { + address: '0xea034fb02eb1808c2cc3adbc15f447b93cbe08e1', + decimals: 8, + }, + USDC: { + address: '0xa8ce8aee21bc2a48a5ef670afcc9274c7bbbc035', + decimals: 6, + }, }, }; @@ -1086,6 +1094,8 @@ export const Holders: { ETH: '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', WETH: '0xc44ad482f24fd750caeba387d2726d8653f8c4bb', MATIC: '0x8f2a1450c040b3c19efe9676165d8f30d8280019', + WBTC: '0x99b31498b0a1dae01fc3433e3cb60f095340935c', + USDC: '0x99b31498b0a1dae01fc3433e3cb60f095340935c', }, }; From 0a818755d38ec1d29ffad40728470ae1306fb6c9 Mon Sep 17 00:00:00 2001 From: Verisana Date: Thu, 31 Aug 2023 11:26:10 +0100 Subject: [PATCH 37/45] fix: use dataFeedId to filter irrelevant events --- src/abi/api3-proxy.json | 2 +- src/dex/quick-perps/pool.ts | 17 ++++----- src/dex/quick-perps/types.ts | 2 +- src/lib/api3-feed.ts | 72 ++++++++++++++++++++---------------- 4 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/abi/api3-proxy.json b/src/abi/api3-proxy.json index c4bb9ab9d..41ce3c43e 100644 --- a/src/abi/api3-proxy.json +++ b/src/abi/api3-proxy.json @@ -14,7 +14,7 @@ }, { "inputs": [], - "name": "dapiNameHash", + "name": "dataFeedId", "outputs": [ { "internalType": "bytes32", diff --git a/src/dex/quick-perps/pool.ts b/src/dex/quick-perps/pool.ts index 6c90b8baa..8b922b236 100644 --- a/src/dex/quick-perps/pool.ts +++ b/src/dex/quick-perps/pool.ts @@ -36,6 +36,7 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { acc[key] = new Api3FeedSubscriber( value.proxy, value.api3ServerV1, + value.dataFeedId, lens>().primaryPrices[key], dexHelper.getLogger( `${key} Api3FeedSubscriber for ${parentName}-${network}`, @@ -282,9 +283,8 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { for (let priceFeed of priceFeeds) { const api3ServerAddressCallData = Api3FeedSubscriber.getApi3ServerV1MultiCallInput(priceFeed); - const dapiNameHashCallData = - Api3FeedSubscriber.getApi3ServerV1MultiCallInput(priceFeed); - multiCallData.push(...[api3ServerAddressCallData, dapiNameHashCallData]); + const dataFeedIdCallData = Api3FeedSubscriber.getDataFeedId(priceFeed); + multiCallData.push(...[api3ServerAddressCallData, dataFeedIdCallData]); multicallSlices.push([i, i + 2]); i += 2; } @@ -319,21 +319,20 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { [address: string]: { proxy: Address; api3ServerV1: Address; - dapiNameHash: string; + dataFeedId: string; }; } = {}; for (let token of tokens) { - const [api3ServerAddressRes, dapiHashNameRes] = configResults.slice( + const [api3ServerAddressRes, dataFeedIdRes] = configResults.slice( ...multicallSlices.shift()!, ); const serverV1Address = Api3FeedSubscriber.decodeApi3ServerV1Result(api3ServerAddressRes); - const dapiNameHash = - Api3FeedSubscriber.decodeApi3ServerV1Result(dapiHashNameRes); + const dataFeedId = Api3FeedSubscriber.decodeDataFeedId(dataFeedIdRes); api3ServerV1[token] = { proxy: priceFeeds.shift(), api3ServerV1: serverV1Address, - dapiNameHash, + dataFeedId, }; } @@ -356,8 +355,6 @@ export class QuickPerpsEventPool extends ComposedEventSubscriber { const vaultConfigResults = configResults.slice(...multicallSlices.shift()!); const vaultConfig = Vault.getConfig(vaultConfigResults, tokens); - const beeconIdMulticall = - return { vaultAddress: dexParams.vault, readerAddress: dexParams.reader, diff --git a/src/dex/quick-perps/types.ts b/src/dex/quick-perps/types.ts index 6bf52114a..fc0326e10 100644 --- a/src/dex/quick-perps/types.ts +++ b/src/dex/quick-perps/types.ts @@ -91,7 +91,7 @@ export type PoolConfig = { [address: string]: { proxy: Address; api3ServerV1: Address; - beaconId: string; + dataFeedId: string; }; }; }; diff --git a/src/lib/api3-feed.ts b/src/lib/api3-feed.ts index 44fb76826..588204372 100644 --- a/src/lib/api3-feed.ts +++ b/src/lib/api3-feed.ts @@ -28,11 +28,15 @@ export class Api3FeedSubscriber extends PartialEventSubscriber< Api3FeedSubscriber.api3ServerV1Iface.getEventTopic( 'UpdatedBeaconWithSignedData', ); + static readonly ANSWER_UPDATED_BEACON_SET_DATA = + Api3FeedSubscriber.api3ServerV1Iface.getEventTopic( + 'UpdatedBeaconSetWithBeacons', + ); constructor( private proxy: Address, api3Server: Address, - private beaconId: string, + private dataFeedId: string, lens: Lens, DeepReadonly>, logger: Logger, ) { @@ -47,26 +51,17 @@ export class Api3FeedSubscriber extends PartialEventSubscriber< }; } - static getDapiNameHash(proxy: Address): MultiCallInput { + static getDataFeedId(proxy: Address): MultiCallInput { return { target: proxy, callData: - Api3FeedSubscriber.proxyInterface.encodeFunctionData('dapiNameHash'), - }; - } - - static getFeedIdFromDapiNameHash(api3Server: string, dapiNameHash: string) { - return { - target: api3Server, - callData: Api3FeedSubscriber.api3ServerV1Iface.encodeFunctionData( - 'dapiNameHashToDataFeedId', - ), + Api3FeedSubscriber.proxyInterface.encodeFunctionData('dataFeedId'), }; } - static decodeFeedIdFromDapiNameHash(multicallOutput: MultiCallOutput) { + static decodeDataFeedId(multicallOutput: MultiCallOutput) { return Api3FeedSubscriber.proxyInterface.decodeFunctionResult( - 'dapiNameHashToDataFeedId', + 'dataFeedId', multicallOutput, )[0]; } @@ -78,29 +73,42 @@ export class Api3FeedSubscriber extends PartialEventSubscriber< )[0]; } - static decodeDapiNameHash(multicallOutput: MultiCallOutput): Address { - return Api3FeedSubscriber.proxyInterface.decodeFunctionResult( - 'dapiNameHash', - multicallOutput, - )[0]; - } - public processLog( state: DeepReadonly, log: Readonly, blockHeader: Readonly, ): DeepReadonly | null { - if (log.topics[0] !== Api3FeedSubscriber.ANSWER_UPDATED_SIGNED_DATA) - return null; // Ignore other events - const decoded = Api3FeedSubscriber.api3ServerV1Iface.decodeEventLog( - 'UpdatedBeaconWithSignedData', - log.data, - log.topics, - ); - return { - value: BigInt(decoded.value.toString()), - timestamp: BigInt(decoded.timestamp.toString()), - }; + if (log.topics[0] === Api3FeedSubscriber.ANSWER_UPDATED_SIGNED_DATA) { + const decoded = Api3FeedSubscriber.api3ServerV1Iface.decodeEventLog( + 'UpdatedBeaconWithSignedData', + log.data, + log.topics, + ); + + if (decoded.beaconId !== this.dataFeedId) return null; + + return { + value: BigInt(decoded.value.toString()), + timestamp: BigInt(decoded.timestamp.toString()), + }; + } else if ( + log.topics[0] === Api3FeedSubscriber.ANSWER_UPDATED_BEACON_SET_DATA + ) { + const decoded = Api3FeedSubscriber.api3ServerV1Iface.decodeEventLog( + 'UpdatedBeaconSetWithBeacons', + log.data, + log.topics, + ); + + if (decoded.beaconSetId !== this.dataFeedId) return null; + + return { + value: BigInt(decoded.value.toString()), + timestamp: BigInt(decoded.timestamp.toString()), + }; + } else { + return null; + } } public getGenerateStateMultiCallInputs(): MultiCallInput[] { From fc607fb9762971fc38117ca1be84770c9111c8ca Mon Sep 17 00:00:00 2001 From: Verisana Date: Thu, 31 Aug 2023 11:27:53 +0100 Subject: [PATCH 38/45] 2.31.4-zkevm-revive.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ebf0924fd..f84d00f82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.12", + "version": "2.31.4-zkevm-revive.13", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From a08c913830949f585b9ec24d48fd3436ddd78d8d Mon Sep 17 00:00:00 2001 From: Mohamed Hassen Mami Date: Wed, 6 Sep 2023 10:57:50 +0200 Subject: [PATCH 39/45] 2.34.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a6cce989f..ba58b19a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.33.3", + "version": "2.34.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 138db04fb19fd45c6c8ecceb1af4c334503f6655 Mon Sep 17 00:00:00 2001 From: Mohamed Hassen Mami Date: Wed, 6 Sep 2023 13:17:22 +0200 Subject: [PATCH 40/45] advance dex-lib version post merge --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f84d00f82..ba58b19a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.31.4-zkevm-revive.13", + "version": "2.34.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From e5fc062ae71c8dae1ccd472817c300465c3b4c5c Mon Sep 17 00:00:00 2001 From: Mohamed Hassen Mami Date: Wed, 6 Sep 2023 13:19:45 +0200 Subject: [PATCH 41/45] 2.35.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba58b19a6..9c051de3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.34.0", + "version": "2.35.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 08125bc537f1c56e785d32f96760149a09c05d04 Mon Sep 17 00:00:00 2001 From: Mohamed Hassen Mami Date: Wed, 6 Sep 2023 16:53:25 +0200 Subject: [PATCH 42/45] remove capping of price in algebra --- src/dex/algebra/algebra.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/dex/algebra/algebra.ts b/src/dex/algebra/algebra.ts index 3b1f55ae5..5ea37f7b4 100644 --- a/src/dex/algebra/algebra.ts +++ b/src/dex/algebra/algebra.ts @@ -817,20 +817,7 @@ export class Algebra extends SimpleExchange implements IDex { return null; } - // TODO buy - let lastNonZeroOutput = 0n; - let lastNonZeroTickCountsOutputs = 0; - for (let i = 0; i < outputsResult.outputs.length; i++) { - // local pricing algo may output 0s at the tail for some out of range amounts, prefer to propagating last amount to appease top algo - if (outputsResult.outputs[i] > 0n) { - lastNonZeroOutput = outputsResult.outputs[i]; - lastNonZeroTickCountsOutputs = outputsResult.tickCounts[i]; - } else { - outputsResult.outputs[i] = lastNonZeroOutput; - outputsResult.tickCounts[i] = lastNonZeroTickCountsOutputs; - } - if (outputsResult.outputs[i] > destTokenBalance) { outputsResult.outputs[i] = 0n; outputsResult.tickCounts[i] = 0; From 325ea57ff78a3155c22d4725b5ef5d1450e99cc3 Mon Sep 17 00:00:00 2001 From: Mohamed Hassen Mami Date: Wed, 6 Sep 2023 16:55:08 +0200 Subject: [PATCH 43/45] 2.35.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c051de3a..391dce2b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.35.0", + "version": "2.35.1", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From e6e5a357f7809d14f1822305fc54f2ab176084b5 Mon Sep 17 00:00:00 2001 From: Mohamed Hassen Mami Date: Fri, 8 Sep 2023 21:51:12 +0200 Subject: [PATCH 44/45] override non exisiting pool by dex - cmaelotV3 --- src/dex/algebra/algebra.ts | 7 +++++-- src/dex/algebra/config.ts | 1 + src/dex/algebra/types.ts | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/dex/algebra/algebra.ts b/src/dex/algebra/algebra.ts index 5ea37f7b4..30f16f34f 100644 --- a/src/dex/algebra/algebra.ts +++ b/src/dex/algebra/algebra.ts @@ -126,10 +126,13 @@ export class Algebra extends SimpleExchange implements IDex { } async initializePricing(blockNumber: number) { + const cleanNonExistingPoolTTLMs = + this.config.cleanExistingPoolTTLMs || + ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS; + if (!this.dexHelper.config.isSlave) { const cleanExpiredNotExistingPoolsKeys = async () => { - const maxTimestamp = - Date.now() - ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS; + const maxTimestamp = Date.now() - cleanNonExistingPoolTTLMs; await this.dexHelper.cache.zremrangebyscore( this.notExistingPoolSetKey, 0, diff --git a/src/dex/algebra/config.ts b/src/dex/algebra/config.ts index 5809d2037..daa761097 100644 --- a/src/dex/algebra/config.ts +++ b/src/dex/algebra/config.ts @@ -83,6 +83,7 @@ export const AlgebraConfig: DexConfigMap = { uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', deployer: '0x6dd3fb9653b10e806650f107c3b5a0a6ff974f65', version: 'v1.9', + cleanExistingPoolTTLMs: 20 * 60 * 1000, }, }, }; diff --git a/src/dex/algebra/types.ts b/src/dex/algebra/types.ts index 891983100..27ca978e1 100644 --- a/src/dex/algebra/types.ts +++ b/src/dex/algebra/types.ts @@ -73,6 +73,7 @@ export type DexParams = { version: 'v1.1' | 'v1.9'; forceRPC?: boolean; forceManualStateGenerate?: boolean; + cleanExistingPoolTTLMs?: number; }; export type IAlgebraPoolState = PoolStateV1_1 | PoolState_v1_9; From 16c16b35e7ae0342b132ac50fadbe834c5ff0050 Mon Sep 17 00:00:00 2001 From: Mohamed Hassen Mami Date: Fri, 8 Sep 2023 21:53:05 +0200 Subject: [PATCH 45/45] 2.35.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 391dce2b8..55739ce6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.35.1", + "version": "2.35.2", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib",