diff --git a/package.json b/package.json index d7547d5cd..605af44fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.28.11", + "version": "2.36.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", diff --git a/src/abi/RamsesV2Pool.abi.json b/src/abi/RamsesV2Pool.abi.json new file mode 100644 index 000000000..b69ed21f7 --- /dev/null +++ b/src/abi/RamsesV2Pool.abi.json @@ -0,0 +1,1554 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "Collect", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "CollectProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid1", + "type": "uint256" + } + ], + "name": "Flash", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextOld", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextNew", + "type": "uint16" + } + ], + "name": "IncreaseObservationCardinalityNext", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0New", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1New", + "type": "uint8" + } + ], + "name": "SetFeeProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount1", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Swap", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "period", + "type": "uint256" + } + ], + "name": "boostInfos", + "outputs": [ + { + "internalType": "uint128", + "name": "totalBoostAmount", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "totalVeRamAmount", + "type": "int128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "period", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "key", + "type": "bytes32" + } + ], + "name": "boostInfos", + "outputs": [ + { + "internalType": "uint128", + "name": "boostAmount", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "veRamAmount", + "type": "int128" + }, + { + "internalType": "int256", + "name": "secondsDebtX96", + "type": "int256" + }, + { + "internalType": "int256", + "name": "boostedSecondsDebtX96", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "boostedLiquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "veRamTokenId", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collectProtocol", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fee", + "outputs": [ + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal0X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal1X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "flash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + } + ], + "name": "increaseObservationCardinalityNext", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "address", + "name": "_nfpManager", + "type": "address" + }, + { + "internalType": "address", + "name": "_veRam", + "type": "address" + }, + { + "internalType": "address", + "name": "_voter", + "type": "address" + }, + { + "internalType": "address", + "name": "_token0", + "type": "address" + }, + { + "internalType": "address", + "name": "_token1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "_fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "_tickSpacing", + "type": "int24" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "lastPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLiquidityPerTick", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "veRamTokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "nfpManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "observations", + "outputs": [ + { + "internalType": "uint32", + "name": "blockTimestamp", + "type": "uint32" + }, + { + "internalType": "int56", + "name": "tickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityCumulativeX128", + "type": "uint160" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + }, + { + "internalType": "uint160", + "name": "secondsPerBoostedLiquidityPeriodX128", + "type": "uint160" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32[]", + "name": "secondsAgos", + "type": "uint32[]" + } + ], + "name": "observe", + "outputs": [ + { + "internalType": "int56[]", + "name": "tickCumulatives", + "type": "int56[]" + }, + { + "internalType": "uint160[]", + "name": "secondsPerLiquidityCumulativeX128s", + "type": "uint160[]" + }, + { + "internalType": "uint160[]", + "name": "secondsPerBoostedLiquidityPeriodX128s", + "type": "uint160[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "period", + "type": "uint32" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "periodCumulativesInside", + "outputs": [ + { + "internalType": "uint160", + "name": "secondsPerLiquidityInsideX128", + "type": "uint160" + }, + { + "internalType": "uint160", + "name": "secondsPerBoostedLiquidityInsideX128", + "type": "uint160" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "period", + "type": "uint256" + } + ], + "name": "periods", + "outputs": [ + { + "internalType": "uint32", + "name": "previousPeriod", + "type": "uint32" + }, + { + "internalType": "int24", + "name": "startTick", + "type": "int24" + }, + { + "internalType": "int24", + "name": "lastTick", + "type": "int24" + }, + { + "internalType": "uint160", + "name": "endSecondsPerLiquidityPeriodX128", + "type": "uint160" + }, + { + "internalType": "uint160", + "name": "endSecondsPerBoostedLiquidityPeriodX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "boostedInRange", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "period", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "positionPeriodDebt", + "outputs": [ + { + "internalType": "int256", + "name": "secondsDebtX96", + "type": "int256" + }, + { + "internalType": "int256", + "name": "boostedSecondsDebtX96", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "period", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "positionPeriodSecondsInRange", + "outputs": [ + { + "internalType": "uint256", + "name": "periodSecondsInsideX96", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "periodBoostedSecondsInsideX96", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "key", + "type": "bytes32" + } + ], + "name": "positions", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside0LastX128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1LastX128", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "tokensOwed0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "tokensOwed1", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "attachedVeRamId", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolFees", + "outputs": [ + { + "internalType": "uint128", + "name": "token0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "token1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "slots", + "type": "bytes32[]" + } + ], + "name": "readStorage", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "returnData", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "setFeeProtocol", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "slot0", + "outputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint16", + "name": "observationIndex", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinality", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "feeProtocol", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "unlocked", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "snapshotCumulativesInside", + "outputs": [ + { + "internalType": "int56", + "name": "tickCumulativeInside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityInsideX128", + "type": "uint160" + }, + { + "internalType": "uint160", + "name": "secondsPerBoostedLiquidityInsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsInside", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "int256", + "name": "amountSpecified", + "type": "int256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int16", + "name": "tick", + "type": "int16" + } + ], + "name": "tickBitmap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tickSpacing", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "ticks", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "liquidityNet", + "type": "int128" + }, + { + "internalType": "uint128", + "name": "boostedLiquidityGross", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "boostedLiquidityNet", + "type": "int128" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside1X128", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "tickCumulativeOutside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityOutsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsOutside", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "veRam", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "voter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/RamsesV2Quoter.abi.json b/src/abi/RamsesV2Quoter.abi.json new file mode 100644 index 000000000..ec532278d --- /dev/null +++ b/src/abi/RamsesV2Quoter.abi.json @@ -0,0 +1,274 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "WETH9", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "address", + "name": "_WETH9", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "name": "quoteExactInput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint160[]", + "name": "sqrtPriceX96AfterList", + "type": "uint160[]" + }, + { + "internalType": "uint32[]", + "name": "initializedTicksCrossedList", + "type": "uint32[]" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct IQuoterV2.QuoteExactInputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "quoteExactInputSingle", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96After", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "initializedTicksCrossed", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "name": "quoteExactOutput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint160[]", + "name": "sqrtPriceX96AfterList", + "type": "uint160[]" + }, + { + "internalType": "uint32[]", + "name": "initializedTicksCrossedList", + "type": "uint32[]" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct IQuoterV2.QuoteExactOutputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "quoteExactOutputSingle", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96After", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "initializedTicksCrossed", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "amount0Delta", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1Delta", + "type": "int256" + }, + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + } + ], + "name": "ramsesV2SwapCallback", + "outputs": [], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/RamsesV2StateMulticall.abi.json b/src/abi/RamsesV2StateMulticall.abi.json new file mode 100644 index 000000000..f83e859c2 --- /dev/null +++ b/src/abi/RamsesV2StateMulticall.abi.json @@ -0,0 +1,891 @@ +[ + { + "inputs": [ + { + "internalType": "contract IRamsesV2Factory", + "name": "factory", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int16", + "name": "tickBitmapStart", + "type": "int16" + }, + { + "internalType": "int16", + "name": "tickBitmapEnd", + "type": "int16" + } + ], + "name": "getAdditionalBitmapWithTicks", + "outputs": [ + { + "components": [ + { + "internalType": "int16", + "name": "index", + "type": "int16" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickBitMapMappings[]", + "name": "tickBitmap", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int24", + "name": "index", + "type": "int24" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "liquidityNet", + "type": "int128" + }, + { + "internalType": "uint128", + "name": "cleanUnusedSlot", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "cleanUnusedSlot2", + "type": "int128" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside1X128", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "tickCumulativeOutside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityOutsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsOutside", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickInfo", + "name": "value", + "type": "tuple" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickInfoMappings[]", + "name": "ticks", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IRamsesV2Factory", + "name": "factory", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int16", + "name": "tickBitmapStart", + "type": "int16" + }, + { + "internalType": "int16", + "name": "tickBitmapEnd", + "type": "int16" + } + ], + "name": "getAdditionalBitmapWithoutTicks", + "outputs": [ + { + "components": [ + { + "internalType": "int16", + "name": "index", + "type": "int16" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickBitMapMappings[]", + "name": "tickBitmap", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IRamsesV2Factory", + "name": "factory", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int16", + "name": "tickBitmapStart", + "type": "int16" + }, + { + "internalType": "int16", + "name": "tickBitmapEnd", + "type": "int16" + } + ], + "name": "getFullState", + "outputs": [ + { + "components": [ + { + "internalType": "contract IRamsesV2Pool", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockTimestamp", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint16", + "name": "observationIndex", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinality", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "feeProtocol", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "unlocked", + "type": "bool" + } + ], + "internalType": "struct IRamsesV2StateMulticall.Slot0", + "name": "slot0", + "type": "tuple" + }, + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "maxLiquidityPerTick", + "type": "uint128" + }, + { + "components": [ + { + "internalType": "uint32", + "name": "blockTimestamp", + "type": "uint32" + }, + { + "internalType": "int56", + "name": "tickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityCumulativeX128", + "type": "uint160" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + }, + { + "internalType": "uint160", + "name": "secondsPerBoostedLiquidityPeriodX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "boostedInRange", + "type": "uint32" + } + ], + "internalType": "struct IRamsesV2StateMulticall.Observation", + "name": "observation", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int16", + "name": "index", + "type": "int16" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickBitMapMappings[]", + "name": "tickBitmap", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int24", + "name": "index", + "type": "int24" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "liquidityNet", + "type": "int128" + }, + { + "internalType": "uint128", + "name": "cleanUnusedSlot", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "cleanUnusedSlot2", + "type": "int128" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside1X128", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "tickCumulativeOutside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityOutsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsOutside", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickInfo", + "name": "value", + "type": "tuple" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickInfoMappings[]", + "name": "ticks", + "type": "tuple[]" + } + ], + "internalType": "struct IRamsesV2StateMulticall.StateResult", + "name": "state", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IRamsesV2Factory", + "name": "factory", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int16", + "name": "leftBitmapAmount", + "type": "int16" + }, + { + "internalType": "int16", + "name": "rightBitmapAmount", + "type": "int16" + } + ], + "name": "getFullStateWithRelativeBitmaps", + "outputs": [ + { + "components": [ + { + "internalType": "contract IRamsesV2Pool", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockTimestamp", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint16", + "name": "observationIndex", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinality", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "feeProtocol", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "unlocked", + "type": "bool" + } + ], + "internalType": "struct IRamsesV2StateMulticall.Slot0", + "name": "slot0", + "type": "tuple" + }, + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "maxLiquidityPerTick", + "type": "uint128" + }, + { + "components": [ + { + "internalType": "uint32", + "name": "blockTimestamp", + "type": "uint32" + }, + { + "internalType": "int56", + "name": "tickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityCumulativeX128", + "type": "uint160" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + }, + { + "internalType": "uint160", + "name": "secondsPerBoostedLiquidityPeriodX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "boostedInRange", + "type": "uint32" + } + ], + "internalType": "struct IRamsesV2StateMulticall.Observation", + "name": "observation", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int16", + "name": "index", + "type": "int16" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickBitMapMappings[]", + "name": "tickBitmap", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int24", + "name": "index", + "type": "int24" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "liquidityNet", + "type": "int128" + }, + { + "internalType": "uint128", + "name": "cleanUnusedSlot", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "cleanUnusedSlot2", + "type": "int128" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside1X128", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "tickCumulativeOutside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityOutsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsOutside", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickInfo", + "name": "value", + "type": "tuple" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickInfoMappings[]", + "name": "ticks", + "type": "tuple[]" + } + ], + "internalType": "struct IRamsesV2StateMulticall.StateResult", + "name": "state", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IRamsesV2Factory", + "name": "factory", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int16", + "name": "tickBitmapStart", + "type": "int16" + }, + { + "internalType": "int16", + "name": "tickBitmapEnd", + "type": "int16" + } + ], + "name": "getFullStateWithoutTicks", + "outputs": [ + { + "components": [ + { + "internalType": "contract IRamsesV2Pool", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockTimestamp", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint16", + "name": "observationIndex", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinality", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "feeProtocol", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "unlocked", + "type": "bool" + } + ], + "internalType": "struct IRamsesV2StateMulticall.Slot0", + "name": "slot0", + "type": "tuple" + }, + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "maxLiquidityPerTick", + "type": "uint128" + }, + { + "components": [ + { + "internalType": "uint32", + "name": "blockTimestamp", + "type": "uint32" + }, + { + "internalType": "int56", + "name": "tickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityCumulativeX128", + "type": "uint160" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + }, + { + "internalType": "uint160", + "name": "secondsPerBoostedLiquidityPeriodX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "boostedInRange", + "type": "uint32" + } + ], + "internalType": "struct IRamsesV2StateMulticall.Observation", + "name": "observation", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int16", + "name": "index", + "type": "int16" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickBitMapMappings[]", + "name": "tickBitmap", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int24", + "name": "index", + "type": "int24" + }, + { + "components": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "liquidityNet", + "type": "int128" + }, + { + "internalType": "uint128", + "name": "cleanUnusedSlot", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "cleanUnusedSlot2", + "type": "int128" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside1X128", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "tickCumulativeOutside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityOutsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsOutside", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickInfo", + "name": "value", + "type": "tuple" + } + ], + "internalType": "struct IRamsesV2StateMulticall.TickInfoMappings[]", + "name": "ticks", + "type": "tuple[]" + } + ], + "internalType": "struct IRamsesV2StateMulticall.StateResult", + "name": "state", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/aerodrome/aerodrome-pool-factory.json b/src/abi/aerodrome/aerodrome-pool-factory.json new file mode 100644 index 000000000..56152f57d --- /dev/null +++ b/src/abi/aerodrome/aerodrome-pool-factory.json @@ -0,0 +1,586 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "FeeInvalid", + "type": "error" + }, + { + "inputs": [], + "name": "FeeTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPool", + "type": "error" + }, + { + "inputs": [], + "name": "NotFeeManager", + "type": "error" + }, + { + "inputs": [], + "name": "NotPauser", + "type": "error" + }, + { + "inputs": [], + "name": "NotVoter", + "type": "error" + }, + { + "inputs": [], + "name": "PoolAlreadyExists", + "type": "error" + }, + { + "inputs": [], + "name": "SameAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroFee", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "PoolCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "SetCustomFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "feeManager", + "type": "address" + } + ], + "name": "SetFeeManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "state", + "type": "bool" + } + ], + "name": "SetPauseState", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "pauser", + "type": "address" + } + ], + "name": "SetPauser", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "SetVoter", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_FEE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ZERO_FEE_INDICATOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allPools", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allPoolsLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + } + ], + "name": "createPool", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "createPool", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "customFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "bool", + "name": "_stable", + "type": "bool" + } + ], + "name": "getFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "getPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + } + ], + "name": "getPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "isPool", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pauser", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "setCustomFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_stable", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "_fee", + "type": "uint256" + } + ], + "name": "setFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_feeManager", + "type": "address" + } + ], + "name": "setFeeManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_state", + "type": "bool" + } + ], + "name": "setPauseState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_pauser", + "type": "address" + } + ], + "name": "setPauser", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_voter", + "type": "address" + } + ], + "name": "setVoter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stableFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "volatileFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "voter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] 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/abi/api3-proxy.json b/src/abi/api3-proxy.json new file mode 100644 index 000000000..41ce3c43e --- /dev/null +++ b/src/abi/api3-proxy.json @@ -0,0 +1,46 @@ +[ + { + "inputs": [], + "name": "api3ServerV1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "dataFeedId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "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/nomiswap-v2/nomiswap-v2-pool.json b/src/abi/nomiswap-v2/nomiswap-v2-pool.json new file mode 100644 index 000000000..98740664d --- /dev/null +++ b/src/abi/nomiswap-v2/nomiswap-v2-pool.json @@ -0,0 +1,773 @@ +[ + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint112", + "name": "reserve0", + "type": "uint112" + }, + { + "indexed": false, + "internalType": "uint112", + "name": "reserve1", + "type": "uint112" + } + ], + "name": "Sync", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "constant": true, + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "MINIMUM_LIQUIDITY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "devFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "kLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "price0CumulativeLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "price1CumulativeLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "swapFee", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getReserves", + "outputs": [ + { + "internalType": "uint112", + "name": "_reserve0", + "type": "uint112" + }, + { + "internalType": "uint112", + "name": "_reserve1", + "type": "uint112" + }, + { + "internalType": "uint32", + "name": "_blockTimestampLast", + "type": "uint32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "_token0", + "type": "address" + }, + { + "internalType": "address", + "name": "_token1", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint32", + "name": "_swapFee", + "type": "uint32" + } + ], + "name": "setSwapFee", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "_devFee", + "type": "uint256" + } + ], + "name": "setDevFee", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "skim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "sync", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/quick-perps/fast-price-events.json b/src/abi/quick-perps/fast-price-events.json new file mode 100644 index 000000000..f9ff530fe --- /dev/null +++ b/src/abi/quick-perps/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/quick-perps/fast-price-feed.json b/src/abi/quick-perps/fast-price-feed.json new file mode 100644 index 000000000..5cf8fccdf --- /dev/null +++ b/src/abi/quick-perps/fast-price-feed.json @@ -0,0 +1,741 @@ +[ + { + "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" } + ], + "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": [{ "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": "address", + "name": "_positionRouter", + "type": "address" + }, + { "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/quick-perps/reader.json b/src/abi/quick-perps/reader.json new file mode 100644 index 000000000..6e851476a --- /dev/null +++ b/src/abi/quick-perps/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/quick-perps/vault-price-feed.json b/src/abi/quick-perps/vault-price-feed.json new file mode 100644 index 000000000..42c932f08 --- /dev/null +++ b/src/abi/quick-perps/vault-price-feed.json @@ -0,0 +1,323 @@ +[ + { "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": "expireTimeForPriceFeed", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "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": "getLatestPrimaryPrice", + "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" }, + { "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" } + ], + "name": "getPriceV1", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "bool", "name": "", "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": "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": "priceFeedProxies", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "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": "uint256", + "name": "_expireTimeForPriceFeed", + "type": "uint256" + } + ], + "name": "setExpireTimeForPriceFeed", + "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": "setIsSecondaryPriceEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maxStrictPriceDeviation", + "type": "uint256" + } + ], + "name": "setMaxStrictPriceDeviation", + "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": "_priceFeedProxy", + "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": "", "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" + } +] diff --git a/src/abi/quick-perps/vault.json b/src/abi/quick-perps/vault.json new file mode 100644 index 000000000..5b770bfd8 --- /dev/null +++ b/src/abi/quick-perps/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/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/config.ts b/src/config.ts index f59133c26..5cd64635d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -51,9 +51,9 @@ const baseConfigs: { [network: number]: BaseConfig } = { adapterAddresses: { Adapter01: '0x9bE264469eF954c139Da4A45Cf76CbCC5e3A6A73', Adapter02: '0xFC2Ba6E830a04C25e207B8214b26d8C713F6881F', - Adapter03: '0x7c7f62e5ba00783f57b39df0530e32c195696a57', + Adapter03: '0xfb2a3de6c7B8c77b520E3da16021f3D8A4E93168', Adapter04: '0x30F6B9b6485ff0B67E881f5ac80D3F1c70A4B23d', - BuyAdapter: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + BuyAdapter: '0x613876f3dE2Ec633f8054fE7a561324c1a01d9cB', }, uniswapV2ExchangeRouterAddress: '0xF9234CB08edb93c0d4a4d4c70cC3FfD070e78e07', @@ -164,8 +164,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { process.env[`HASHFLOW_DISABLED_MMS_56`]?.split(',') || [], adapterAddresses: { BscAdapter01: '0xA31d9C571DF00e0F428B0bD24c34D103E8112222', - BscAdapter02: '0x1d2Fd92a1942A92a51198168eFCd626ed441CEC0', - BscBuyAdapter: '0x64C856fafE4C83a818514cBDfD661a3563a71B98', + BscAdapter02: '0x02f2c31ebDE63E871AD0E74c01E21c819292a59D', + BscBuyAdapter: '0x301c2813e3ceb43A448a12f21551EDBcdC37F157', }, rpcPollingMaxAllowedStateDelayInBlocks: 1, rpcPollingBlocksBackToTriggerUpdate: 1, @@ -192,8 +192,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { process.env[`HASHFLOW_DISABLED_MMS_137`]?.split(',') || [], adapterAddresses: { PolygonAdapter01: '0xE44769f42E1e9592f86B82f206407a8f7C84b4ed', - PolygonAdapter02: '0xa05d8C3F278fC7b20b39Ea7A3035E3aD8D808c78', - PolygonBuyAdapter: '0xB11bCA7B01b425afD0743A4D77B4f593883f94C0', + PolygonAdapter02: '0x654cD2Cf97D23059B3db4FaA38BB2b1F8351211d', + PolygonBuyAdapter: '0x4426a1F87Ee7e366542c58e29c02AFa2b5878b37', }, uniswapV2ExchangeRouterAddress: '0xf3938337F7294fEf84e9B2c6D548A93F956Cc281', @@ -223,8 +223,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { dexalotAuthToken: process.env.API_KEY_DEXALOT_AUTH_TOKEN || '', adapterAddresses: { AvalancheAdapter01: '0x745Ec73855CeC7249E5fF4c9DD81cc65b4D297a9', - AvalancheAdapter02: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', - AvalancheBuyAdapter: '0x7ebbDBB57d2ab59079423cf8337cf8805e225bB1', + AvalancheAdapter02: '0xFb8773AA4Fd02e54bbd352061D8Be1911FAa210a', + AvalancheBuyAdapter: '0x434C1Cca4842629230067674Dd54E21a14D9FD5D', }, uniswapV2ExchangeRouterAddress: '0x53e693c6C7FFC4446c53B205Cf513105Bf140D7b', @@ -278,9 +278,9 @@ const baseConfigs: { [network: number]: BaseConfig } = { hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_42161`]?.split(',') || [], adapterAddresses: { - ArbitrumAdapter01: '0x745Ec73855CeC7249E5fF4c9DD81cc65b4D297a9', - ArbitrumAdapter02: '0x3ad7f275E27AC579cA88e0b4765828242A9E8C49', - ArbitrumBuyAdapter: '0x6c33C7f6CBB4a428fe9ee31ca500a787c9f1525b', + ArbitrumAdapter01: '0xD8134ACfc9c71Ab51452b5bA23A31354F4739032', + ArbitrumAdapter02: '0xD1F70c98a78d48A93F0B4dDa49057469dc5aC126', + ArbitrumBuyAdapter: '0x434C1Cca4842629230067674Dd54E21a14D9FD5D', }, uniswapV2ExchangeRouterAddress: '0xB41dD984730dAf82f5C41489E21ac79D5e3B61bC', @@ -308,8 +308,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { process.env[`HASHFLOW_DISABLED_MMS_10`]?.split(',') || [], adapterAddresses: { - OptimismAdapter01: '0x4669D27A649f5451e0D44C20a2b246431F1B0572', - OptimismBuyAdapter: '0xb2634B3CBc1E401AB3C2743DB44d459C5c9aA662', + OptimismAdapter01: '0x3ad7f275E27AC579cA88e0b4765828242A9E8C49', + OptimismBuyAdapter: '0xfdDD975FE4c1af20c24A3Ad2b33e8609a62DDC73', }, uniswapV2ExchangeRouterAddress: '0xB41dD984730dAf82f5C41489E21ac79D5e3B61bC', @@ -319,6 +319,62 @@ 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: '0xd63B7691dD98fa89A2ea5e1604700489c585aa7B', + PolygonZkEvmBuyAdapter: '0xe2137168CdA486a2555E16c597905854C84F9127', + }, + + 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: '', + }, + [Network.BASE]: { + network: Network.BASE, + networkName: 'Base', + isTestnet: false, + nativeTokenName: 'Ether', + nativeTokenSymbol: 'ETH', + wrappedNativeTokenAddress: '0x4200000000000000000000000000000000000006', + hasEIP1559: false, + augustusAddress: '0x59C7C832e96D2568bea6db468C1aAdcbbDa08A52', + augustusRFQAddress: '0xa003dFBA51C9e1e56C67ae445b852bdEd7aC5EEd', + tokenTransferProxyAddress: '0x93aAAe79a53759cD164340E4C8766E4Db5331cD7', + multicallV2Address: '0xeDF6D2a16e8081F777eB623EeB4411466556aF3d', + privateHttpProvider: process.env.HTTP_PROVIDER_8453, + hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', + hashFlowDisabledMMs: [], + adapterAddresses: { + BaseAdapter01: '0x30F6B9b6485ff0B67E881f5ac80D3F1c70A4B23d', + BaseBuyAdapter: '0xB11bCA7B01b425afD0743A4D77B4f593883f94C0', + }, + uniswapV2ExchangeRouterAddress: + '0x75d199EfB540e47D27D52c62Da3E7daC2B9e834F', + uniswapV3EventLoggingSampleRate: 0, + rfqConfigs: {}, + rpcPollingMaxAllowedStateDelayInBlocks: 5, + rpcPollingBlocksBackToTriggerUpdate: 3, + forceRpcFallbackDexs: [], + }, }; // Should not be used, except by internal test code diff --git a/src/constants.ts b/src/constants.ts index 08c210b0a..3c0bf6b9a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -38,6 +38,7 @@ export enum Network { FANTOM = 250, ARBITRUM = 42161, OPTIMISM = 10, + BASE = 8453, } export const SUBGRAPH_TIMEOUT = 20 * 1000; diff --git a/src/dex-helper/dummy-dex-helper.ts b/src/dex-helper/dummy-dex-helper.ts index 69be935b8..b1db1cca0 100644 --- a/src/dex-helper/dummy-dex-helper.ts +++ b/src/dex-helper/dummy-dex-helper.ts @@ -41,6 +41,7 @@ class DummyCache implements ICache { } async rawget(key: string): Promise { + return this.storage[key] ? this.storage[key] : null; return null; } @@ -49,10 +50,12 @@ class DummyCache implements ICache { value: string, ttl: number, ): Promise { - return null; + this.storage[key] = value; + return 'OK'; } async rawdel(key: string): Promise { + delete this.storage[key]; return; } diff --git a/src/dex/algebra/algebra-integration.test.ts b/src/dex/algebra/algebra-integration.test.ts index 54c674fb7..002b4292f 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,125 @@ 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('WETH/DAI generate state is working for problematic pool', async function () { + const pool = (await algebra.getPool( + '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', + '0xc5015b9d9161dca7e18e32f6f25c4ad850731fd4', + 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/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('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', + '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 2b9746de5..41b3737b6 100644 --- a/src/dex/algebra/algebra-pool-v1_1.ts +++ b/src/dex/algebra/algebra-pool-v1_1.ts @@ -9,31 +9,47 @@ import { } 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, + uint128ToBigNumber, + int24ToNumber, +} 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, _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; +const MAX_NUMBER_OF_BATCH_REQUEST_HALVING = 3; + export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber { handlers: { [event: string]: ( @@ -53,6 +69,13 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber readonly token1: Address; public readonly poolIface = new Interface(AlgebraABI); + public readonly factoryIface = new Interface(FactoryABI); + + private readonly cachedStateMultiCalls: MultiCallParams< + string | bigint | BigNumber | number | DecodedGlobalStateV1_1 + >[]; + + private optimalTickRequestBatchSize?: number; public initFailed = false; public initRetryAttemptCount = 0; @@ -69,6 +92,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(); @@ -84,6 +108,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() { @@ -174,7 +200,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] @@ -336,7 +362,7 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber [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 +373,234 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber } } + private _getStateMulticall(): MultiCallParams< + string | bigint | BigNumber | number | DecodedGlobalStateV1_1 + >[] { + return [ + { + target: this.factoryAddress, + callData: this.factoryIface.encodeFunctionData('poolByPair', [ + this.token0, + this.token1, + ]), + decodeFunction: addressDecode, + }, + { + 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: this.poolAddress, + callData: this.poolIface.encodeFunctionData('liquidity', []), + decodeFunction: uint128ToBigNumber, + }, + { + target: this.poolAddress, + callData: this.poolIface.encodeFunctionData('tickSpacing', []), + decodeFunction: int24ToNumber, + }, + { + target: this.poolAddress, + callData: this.poolIface.encodeFunctionData('maxLiquidityPerTick', []), + decodeFunction: uint128ToBigNumber, + }, + { + target: this.poolAddress, + callData: this.poolIface.encodeFunctionData('globalState', []), + decodeFunction: decodeGlobalStateV1_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)), + )[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, + }; + }, + ), + blockNumber, + MAX_BATCH_SIZE, + ); + + const tickBitmap: TickBitMapMappingsWithBigNumber[] = []; + + let globalIndex = 0; + for (let i = leftBitMapIndex; i <= rightBitMapIndex; i++) { + const index = Number(int16(i)); + const bitmap = allTickBitMaps[globalIndex]; + globalIndex++; + if (bitmap == 0n) continue; + tickBitmap.push({ index: Number(index), value: BigNumber.from(bitmap) }); + } + + const tickIndexes: bigint[] = []; + + const tickRequests = tickBitmap + .map(tb => { + const allBits: MultiCallParams[] = []; + 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)) * + BigInt(tickSpacing); + + tickIndexes.push(populatedTick); + allBits.push({ + target: poolAddress, + callData: this.poolIface.encodeFunctionData('ticks', [ + populatedTick, + ]), + decodeFunction: decodeTicksV1_1, + }); + } + }); + return allBits; + }) + .flat(); + + 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, + `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, + { + pool: poolAddress, + blockTimestamp: BigNumber.from(Date.now()), + globalState, + liquidity, + tickSpacing, + 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 0cbe60fd5..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, @@ -160,14 +163,15 @@ 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 */ - await pool.initialize(blockNumber); + await pool.initialize(blockNumber, { forceRegenerate: true }); } return pool; @@ -226,6 +230,7 @@ export class Algebra extends SimpleExchange implements IDex { this.cacheStateKey, this.config.initHash, this.config.deployer, + this.config.forceManualStateGenerate, ); try { @@ -255,10 +260,10 @@ 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}`, + `${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; @@ -790,6 +795,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; } @@ -814,20 +820,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; diff --git a/src/dex/algebra/config.ts b/src/dex/algebra/config.ts index 9b6147a17..daa761097 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: { @@ -82,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 98a8c1d33..27ca978e1 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,8 @@ export type DexParams = { initHash: string; version: 'v1.1' | 'v1.9'; forceRPC?: boolean; + forceManualStateGenerate?: boolean; + cleanExistingPoolTTLMs?: number; }; export type IAlgebraPoolState = PoolStateV1_1 | PoolState_v1_9; @@ -95,7 +97,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/dex/balancer-v2/balancer-v2-e2e.test.ts b/src/dex/balancer-v2/balancer-v2-e2e.test.ts index 1aadf7669..5cc919399 100644 --- a/src/dex/balancer-v2/balancer-v2-e2e.test.ts +++ b/src/dex/balancer-v2/balancer-v2-e2e.test.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv'; dotenv.config(); import { testE2E } from '../../../tests/utils-e2e'; -import { Holders, Tokens } from '../../../tests/constants-e2e'; +import { Holders, NativeTokenSymbols, Tokens } from '../../../tests/constants-e2e'; import { ContractMethod, Network, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; @@ -10,6 +10,105 @@ import { generateConfig } from '../../config'; jest.setTimeout(50 * 1000); +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, + slippage?: number | undefined, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ], + ], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + }); + }); + }), + ); + }); +} + describe('BalancerV2 E2E', () => { describe('BalancerV2 MAINNET', () => { const dexKey = 'BalancerV2'; @@ -96,6 +195,18 @@ describe('BalancerV2 E2E', () => { buyAmount: '10000000000', }, ], + [ + { + name: 'USDC', + sellAmount: '111000000', + buyAmount: '111000000', + }, + { + name: 'wstETH', + sellAmount: '100000000000000000', + buyAmount: '100000000000000000', + }, + ], ]; sideToContractMethods.forEach((contractMethods, side) => @@ -1054,7 +1165,7 @@ describe('BalancerV2 E2E', () => { sellAmount: '200000000', buyAmount: '2000000000', }, - ] + ], ]; sideToContractMethods.forEach((contractMethods, side) => @@ -1137,7 +1248,7 @@ describe('BalancerV2 E2E', () => { sellAmount: '200000000', buyAmount: '2000000000000000', }, - ] + ], ]; sideToContractMethods.forEach((contractMethods, side) => @@ -1181,7 +1292,28 @@ describe('BalancerV2 E2E', () => { }), ); }); + }); + + describe('BalancerV2 Base', () => { + const dexKey = 'BalancerV2'; + const network = Network.BASE; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'GOLD'; + const tokenAAmount: string = '11110010'; + const tokenBAmount: string = '210000000000000000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); }); describe('BeetsFi OPTIMISM', () => { @@ -1436,5 +1568,4 @@ describe('BalancerV2 E2E', () => { }); }); }); - }); diff --git a/src/dex/balancer-v2/balancer-v2.ts b/src/dex/balancer-v2/balancer-v2.ts index f71274414..d6c34b3bf 100644 --- a/src/dex/balancer-v2/balancer-v2.ts +++ b/src/dex/balancer-v2/balancer-v2.ts @@ -72,6 +72,91 @@ import { } from './constants'; import { NumberAsString, OptimalSwapExchange } from '@paraswap/core'; +// If you disable some pool, don't forget to clear the cache, otherwise changes won't be applied immediately +const enabledPoolTypes = [ + // BalancerPoolTypes.MetaStable, // BOOSTED POOLS Disabled since vulnerability https://github.com/BalancerMaxis/multisig-ops/blob/main/BIPs/00notGov/2023-08-mitigation.md + BalancerPoolTypes.Stable, + BalancerPoolTypes.Weighted, + BalancerPoolTypes.LiquidityBootstrapping, + BalancerPoolTypes.Investment, + BalancerPoolTypes.StablePhantom, + BalancerPoolTypes.AaveLinear, + BalancerPoolTypes.ERC4626Linear, + BalancerPoolTypes.Linear, + BalancerPoolTypes.ComposableStable, + BalancerPoolTypes.BeefyLinear, + BalancerPoolTypes.GearboxLinear, + BalancerPoolTypes.MidasLinear, + BalancerPoolTypes.ReaperLinear, + BalancerPoolTypes.SiloLinear, + BalancerPoolTypes.TetuLinear, + BalancerPoolTypes.YearnLinear, +]; + +const disabledPoolIds = [ + // broken ? + '0xbd482ffb3e6e50dc1c437557c3bea2b68f3683ee0000000000000000000003c6', + + /* DISABLED POOLS SINCE VULNERABILITY https://github.com/BalancerMaxis/multisig-ops/blob/main/BIPs/00notGov/2023-08-mitigation.md*/ + /* START:2023-08-mitigation */ + //mainnet + '0xbf2ef8bdc2fc0f3203b3a01778e3ec5009aeef3300000000000000000000058d', + '0x99c88ad7dc566616548adde8ed3effa730eb6c3400000000000000000000049a', + '0x60683b05e9a39e3509d8fdb9c959f23170f8a0fa000000000000000000000489', + '0xa13a9247ea42d743238089903570127dda72fe4400000000000000000000035d', + '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe', + '0x25accb7943fd73dda5e23ba6329085a3c24bfb6a000200000000000000000387', + '0x50cf90b954958480b8df7958a9e965752f62712400000000000000000000046f', + '0x133d241f225750d2c92948e464a5a80111920331000000000000000000000476', + '0x8a6b25e33b12d1bb6929a8793961076bd1f9d3eb0002000000000000000003e8', + '0x959216bb492b2efa72b15b7aacea5b5c984c3cca000200000000000000000472', + '0x9b692f571b256140a39a34676bffa30634c586e100000000000000000000059d', + + // polygon + '0xb3d658d5b95bf04e2932370dd1ff976fe18dd66a000000000000000000000ace', + '0x48e6b98ef6329f8f0a30ebb8c7c960330d64808500000000000000000000075b', + '0xb54b2125b711cd183edd3dd09433439d5396165200000000000000000000075e', + + // arbitrum + '0xa8af146d79ac0bb981e4e0d8b788ec5711b1d5d000000000000000000000047b', + '0x077794c30afeccdf5ad2abc0588e8cee7197b71a000000000000000000000352', + '0x519cce718fcd11ac09194cff4517f12d263be067000000000000000000000382', + + // optimism + '0x23ca0306b21ea71552b148cf3c4db4fc85ae19290000000000000000000000ac', + '0x62cf35db540152e94936de63efc90d880d4e241b0000000000000000000000ef', + '0x098f32d98d0d64dba199fc1923d3bf4192e787190001000000000000000000d2', + '0x43da214fab3315aa6c02e0b8f2bfb7ef2e3c60a50000000000000000000000ae', + '0xb1c9ac57594e9b1ec0f3787d9f6744ef4cb0a02400000000000000000000006e', + '0xde45f101250f2ca1c0f8adfc172576d10c12072d00000000000000000000003f', + '0x05e7732bf9ae5592e6aa05afe8cd80f7ab0a7bea00020000000000000000005a', + '0x981fb05b738e981ac532a99e77170ecb4bc27aef00010000000000000000004b', + '0x6222ae1d2a9f6894da50aa25cb7b303497f9bebd000000000000000000000046', + '0x3c74c4ed512050eb843d89fb9dcd5ebb4668eb6d0002000000000000000000cc', + + // fantom + '0xc0064b291bd3d4ba0e44ccfc81bf8e7f7a579cd200000000000000000000042c', + '0x6e6dc948ce85c62125ff7a1e543d761a88f0a4cb000000000000000000000743', + '0x78ab08bf98f90f29a09c9b1d85b3b549369b03a3000100000000000000000354', + '0x302b8b64795b064cadc32f74993a6372498608070001000000000000000003e0', + '0x5ddb92a5340fd0ead3987d3661afcd6104c3b757000000000000000000000187', + '0xdfc65c1f15ad3507754ef0fd4ba67060c108db7e000000000000000000000406', + '0x6da14f5acd58dd5c8e486cfa1dc1c550f5c61c1c0000000000000000000003cf', + '0x592fa9f9d58065096f2b7838709c116957d7b5cf00020000000000000000043c', + '0xf47f4d59c863c02cbfa3eefe6771b9c9fbe7b97800000000000000000000072b', + '0xff2753aaba51c9f84689b9bd0a21b3cf380a1cff00000000000000000000072e', + '0x10441785a928040b456a179691141c48356eb3a50001000000000000000002fa', + '0x64b301e21d640f9bef90458b0987d81fb4cf1b9e00020000000000000000022e', + '0xba0e9aea8a7fa1daab4edf244191f2387a4e472b000100000000000000000737', + '0x1e2576344d49779bdbb71b1b76193d27e6f996b700020000000000000000032d', + '0xa10285f445bcb521f1d623300dc4998b02f11c8f00000000000000000000043b', + + // zkevm + '0x6f34a44fce1506352a171232163e7716dd073ade000200000000000000000015', + '0xe274c9deb6ed34cfe4130f8d0a8a948dea5bb28600000000000000000000000d', + /* END:2023-08-mitigation */ +]; + const fetchAllPools = `query ($count: Int) { pools: pools( first: $count @@ -81,7 +166,7 @@ const fetchAllPools = `query ($count: Int) { totalLiquidity_gt: ${MIN_USD_LIQUIDITY_TO_FETCH.toString()}, totalShares_not_in: ["0", "0.000000000001"], id_not_in: [ - "0xbd482ffb3e6e50dc1c437557c3bea2b68f3683ee0000000000000000000003c6" + ${disabledPoolIds.map(p => `"${p}"`).join(', ')} ], address_not_in: [ "0x0afbd58beca09545e4fb67772faf3858e610bcd0", @@ -93,9 +178,7 @@ const fetchAllPools = `query ($count: Int) { ], swapEnabled: true, poolType_in: [ - "MetaStable", "Stable", "Weighted", "LiquidityBootstrapping", "Investment", "StablePhantom", "AaveLinear", - "ERC4626Linear", "Linear", "ComposableStable", "BeefyLinear", "GearboxLinear", "MidasLinear", - "ReaperLinear", "SiloLinear", "TetuLinear", "YearnLinear" + ${enabledPoolTypes.map(p => `"${p}"`).join(', ')} ] } ) { @@ -826,8 +909,6 @@ export class BalancerV2 prices: resOut.prices, data: { poolId: pool.id, - tokenIn: _from.address.toLowerCase(), - tokenOut: _to.address.toLowerCase(), }, poolAddresses: [poolAddress], exchange: this.dexKey, diff --git a/src/dex/balancer-v2/config.ts b/src/dex/balancer-v2/config.ts index cbb0b3a77..86e7e2930 100644 --- a/src/dex/balancer-v2/config.ts +++ b/src/dex/balancer-v2/config.ts @@ -23,7 +23,12 @@ export const BalancerConfig: DexConfigMap = { subgraphURL: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-avalanche-v2', vaultAddress: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', - } + }, + [Network.BASE]: { + subgraphURL: + 'https://api.studio.thegraph.com/query/24660/balancer-base-v2/version/latest', + vaultAddress: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + }, }, BeetsFi: { [Network.FANTOM]: { @@ -71,5 +76,9 @@ export const Adapters: Record = { [Network.AVALANCHE]: { [SwapSide.SELL]: [{ name: 'AvalancheAdapter01', index: 8 }], [SwapSide.BUY]: [{ name: 'AvalancheBuyAdapter', index: 7 }], + }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 4 }], + [SwapSide.BUY]: [{ name: 'BaseBuyAdapter', index: 3 }], } }; diff --git a/src/dex/balancer-v2/optimizer.ts b/src/dex/balancer-v2/optimizer.ts index 201b7a19f..818c2f7f8 100644 --- a/src/dex/balancer-v2/optimizer.ts +++ b/src/dex/balancer-v2/optimizer.ts @@ -1,83 +1,70 @@ -import _ from 'lodash'; -import { UnoptimizedRate } from '../../types'; +import { UnoptimizedRate, OptimalSwapExchange } from '../../types'; +import { BalancerSwapV2 } from './types'; import { SwapSide } from '../../constants'; import { BalancerConfig } from './config'; -import { OptimalSwap } from '@paraswap/core'; + +const MAX_UINT256 = + '115792089237316195423570985008687907853269984665640564039457584007913129639935'; + +const AllBalancerV2Forks = Object.keys(BalancerConfig); export function balancerV2Merge(or: UnoptimizedRate): UnoptimizedRate { - const balancerForksList = Object.keys(BalancerConfig).map(b => - b.toLowerCase(), - ); - const fixSwap = (rawRate: OptimalSwap[], side: SwapSide): OptimalSwap[] => { - let lastExchange: false | OptimalSwap = false; - let optimizedRate = new Array(); - rawRate.forEach((s: OptimalSwap) => { - if ( - s.swapExchanges.length !== 1 || - !balancerForksList.includes(s.swapExchanges[0].exchange.toLowerCase()) - ) { - lastExchange = false; - optimizedRate.push(s); - } else if ( - lastExchange && - lastExchange.swapExchanges[0].exchange.toLowerCase() === - s.swapExchanges[0].exchange.toLowerCase() && - _.last( - lastExchange.swapExchanges[0].data.swaps, - )!.tokenOut.toLowerCase() === - s.swapExchanges[0].data.tokenIn.toLowerCase() - ) { - const [lastExchangeSwap] = lastExchange.swapExchanges; - const [currentSwap] = s.swapExchanges; - lastExchangeSwap.srcAmount = ( - BigInt(lastExchangeSwap.srcAmount) + BigInt(currentSwap.srcAmount) + const fixSwap = ( + rawSwap: OptimalSwapExchange[], + side: SwapSide, + ): OptimalSwapExchange[] => { + const newBalancers: { [key: string]: OptimalSwapExchange } = {}; + let optimizedSwap = new Array>(); + rawSwap.forEach((s: OptimalSwapExchange) => { + const exchangeKey = s.exchange.toLowerCase(); + if (AllBalancerV2Forks.some(d => d.toLowerCase() === exchangeKey)) { + if (!(exchangeKey in newBalancers)) { + newBalancers[exchangeKey] = { + exchange: s.exchange, + srcAmount: '0', + destAmount: '0', + percent: 0, + poolAddresses: [], + data: { + swaps: new Array(), + gasUSD: '0', + }, + }; + } + newBalancers[exchangeKey].srcAmount = ( + BigInt(newBalancers[exchangeKey].srcAmount) + BigInt(s.srcAmount) ).toString(); - lastExchangeSwap.destAmount = ( - BigInt(lastExchangeSwap.destAmount) + BigInt(currentSwap.destAmount) + newBalancers[exchangeKey].destAmount = ( + BigInt(newBalancers[exchangeKey].destAmount) + BigInt(s.destAmount) ).toString(); - lastExchangeSwap.percent += currentSwap.percent; - lastExchangeSwap.data.gasUSD = ( - parseFloat(lastExchangeSwap.data.gasUSD) + - parseFloat(currentSwap.data.gasUSD) + newBalancers[exchangeKey].percent += s.percent; + newBalancers[exchangeKey].data.exchangeProxy = s.data.exchangeProxy; + newBalancers[exchangeKey].data.gasUSD = ( + parseFloat(newBalancers[exchangeKey].data.gasUSD) + + parseFloat(s.data.gasUSD) ).toFixed(6); - lastExchangeSwap.data.swaps.push({ - poolId: currentSwap.data.poolId, - amount: - side === SwapSide.SELL - ? currentSwap.srcAmount - : currentSwap.destAmount, - tokenIn: currentSwap.data.tokenIn, - tokenOut: currentSwap.data.tokenOut, + newBalancers[exchangeKey].data.swaps.push({ + poolId: s.data.poolId, + amount: side === SwapSide.SELL ? s.srcAmount : s.destAmount, }); - lastExchangeSwap.poolAddresses!.push(currentSwap.poolAddresses![0]); + newBalancers[exchangeKey].poolAddresses!.push(s.poolAddresses![0]); } else { - lastExchange = _.cloneDeep(s); - lastExchange.swapExchanges[0].data = {}; - lastExchange.swapExchanges[0].data.gasUSD = - s.swapExchanges[0].data.gasUSD; - lastExchange.swapExchanges[0].data.swaps = [ - { - poolId: s.swapExchanges[0].data.poolId, - amount: - side === SwapSide.SELL - ? s.swapExchanges[0].srcAmount - : s.swapExchanges[0].destAmount, - tokenIn: s.swapExchanges[0].data.tokenIn, - tokenOut: s.swapExchanges[0].data.tokenOut, - }, - ]; - optimizedRate.push(lastExchange); + optimizedSwap.push(s); } }); - return optimizedRate; + optimizedSwap = optimizedSwap.concat(Object.values(newBalancers)); + return optimizedSwap; }; or.bestRoute = or.bestRoute.map(r => ({ ...r, - swaps: fixSwap(r.swaps, or.side), + swaps: r.swaps.map(s => ({ + ...s, + swapExchanges: fixSwap(s.swapExchanges, or.side), + })), })); return or; } diff --git a/src/dex/balancer-v2/types.ts b/src/dex/balancer-v2/types.ts index 33dd35bfe..f1ef3194c 100644 --- a/src/dex/balancer-v2/types.ts +++ b/src/dex/balancer-v2/types.ts @@ -82,8 +82,6 @@ export interface SubgraphPoolBase { export type BalancerSwapV2 = { poolId: string; amount: string; - tokenIn: string; - tokenOut: string; }; export type OptimizedBalancerV2Data = { @@ -141,8 +139,6 @@ export type BalancerV2DirectParam = [ export type BalancerV2Data = { poolId: string; - tokenIn: string; - tokenOut: string; }; export type DexParams = { diff --git a/src/dex/curve-v1-factory/config.ts b/src/dex/curve-v1-factory/config.ts index f485b873e..f458836ab 100644 --- a/src/dex/curve-v1-factory/config.ts +++ b/src/dex/curve-v1-factory/config.ts @@ -13,7 +13,10 @@ import { normalizeAddress } from '../../utils'; const CurveV1FactoryConfig: DexConfigMap = { CurveV1Factory: { [Network.MAINNET]: { - factoryAddress: '0xB9fC157394Af804a3578134A6585C0dc9cc990d4', + factoryAddresses: [ + '0xB9fC157394Af804a3578134A6585C0dc9cc990d4', + '0x4f8846ae9380b90d2e71d5e3d042dff3e7ebb40d', + ], stateUpdatePeriodMs: 5 * 1000, disabledPools: new Set([ '0x28B0Cf1baFB707F2c6826d10caf6DD901a6540C5', // It is rug pool token @@ -150,6 +153,11 @@ const CurveV1FactoryConfig: DexConfigMap = { name: ImplementationNames.FACTORY_PLAIN_4_OPTIMIZED, address: '0xad4753d045d3aed5c1a6606dfb6a7d7ad67c1ad7', }, + '0x67fe41A94e779CcFa22cff02cc2957DC9C0e4286': { + name: ImplementationNames.FACTORY_PLAIN_2_CRV_EMA, + address: '0x67fe41A94e779CcFa22cff02cc2957DC9C0e4286', + liquidityApiSlug: '/factory-crvusd', + }, }, customPools: { '0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2': { @@ -200,7 +208,7 @@ const CurveV1FactoryConfig: DexConfigMap = { }, }, [Network.POLYGON]: { - factoryAddress: '0x722272D36ef0Da72FF51c5A65Db7b870E2e8D4ee', + factoryAddresses: ['0x722272D36ef0Da72FF51c5A65Db7b870E2e8D4ee'], stateUpdatePeriodMs: 2 * 1000, disabledPools: new Set([ '0x666Dc3b4baBfd063FaF965BD020024AF0dC51B64', @@ -306,7 +314,7 @@ const CurveV1FactoryConfig: DexConfigMap = { }, }, [Network.FANTOM]: { - factoryAddress: '0x686d67265703D1f124c45E33d47d794c566889Ba', + factoryAddresses: ['0x686d67265703D1f124c45E33d47d794c566889Ba'], stateUpdatePeriodMs: 2 * 1000, disabledPools: new Set([]), disabledImplementations: new Set([]), @@ -421,7 +429,7 @@ const CurveV1FactoryConfig: DexConfigMap = { }, }, [Network.AVALANCHE]: { - factoryAddress: '0xb17b674D9c5CB2e441F8e196a2f048A81355d031', + factoryAddresses: ['0xb17b674D9c5CB2e441F8e196a2f048A81355d031'], stateUpdatePeriodMs: 2 * 1000, // FIX: This must be removed when we go for full CurveV1 event based support disabledPools: new Set(['0x16a7da911a4dd1d83f3ff066fe28f3c792c50d90']), @@ -519,7 +527,7 @@ const CurveV1FactoryConfig: DexConfigMap = { }, }, [Network.ARBITRUM]: { - factoryAddress: '0xb17b674D9c5CB2e441F8e196a2f048A81355d031', + factoryAddresses: ['0xb17b674D9c5CB2e441F8e196a2f048A81355d031'], stateUpdatePeriodMs: 2 * 1000, disabledPools: new Set([]), disabledImplementations: new Set([]), @@ -645,7 +653,7 @@ const CurveV1FactoryConfig: DexConfigMap = { }, }, [Network.OPTIMISM]: { - factoryAddress: '0x2db0E83599a91b508Ac268a6197b8B14F5e72840', + factoryAddresses: ['0x2db0E83599a91b508Ac268a6197b8B14F5e72840'], stateUpdatePeriodMs: 2 * 1000, disabledPools: new Set([]), disabledImplementations: new Set([]), @@ -869,9 +877,9 @@ const configAddressesNormalizer = ( // Unite everything into top level config const normalizedConfig: DexParams = { - factoryAddress: _config.factoryAddress - ? _config.factoryAddress.toLowerCase() - : _config.factoryAddress, + factoryAddresses: _config.factoryAddresses + ? _config.factoryAddresses.map(e => e.toLowerCase()) + : _config.factoryAddresses, stateUpdatePeriodMs: _config.stateUpdatePeriodMs, factoryPoolImplementations, customPools, diff --git a/src/dex/curve-v1-factory/curve-v1-factory-e2e.test.ts b/src/dex/curve-v1-factory/curve-v1-factory-e2e.test.ts index baf4d5527..2aa034745 100644 --- a/src/dex/curve-v1-factory/curve-v1-factory-e2e.test.ts +++ b/src/dex/curve-v1-factory/curve-v1-factory-e2e.test.ts @@ -111,6 +111,25 @@ describe('CurveV1Factory E2E', () => { tokenBAmount, ); }); + + describe('Mainnet crvUSD', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'crvUSD'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '10000000000000000000'; + const tokenBAmount: string = '10000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); describe('Mainnet ng pool', () => { const network = Network.MAINNET; diff --git a/src/dex/curve-v1-factory/curve-v1-factory-integration.test.ts b/src/dex/curve-v1-factory/curve-v1-factory-integration.test.ts index 55b6fcb20..23f81a439 100644 --- a/src/dex/curve-v1-factory/curve-v1-factory-integration.test.ts +++ b/src/dex/curve-v1-factory/curve-v1-factory-integration.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import dotenv from 'dotenv'; dotenv.config(); @@ -143,23 +144,6 @@ describe('CurveV1Factory', function () { const tokens = Tokens[network]; - const srcTokenSymbol = 'USDD'; - const destTokenSymbol = 'USDT'; - - const amountsForSell = [ - 0n, - 1n * BI_POWS[tokens[srcTokenSymbol].decimals], - 2n * BI_POWS[tokens[srcTokenSymbol].decimals], - 3n * BI_POWS[tokens[srcTokenSymbol].decimals], - 4n * BI_POWS[tokens[srcTokenSymbol].decimals], - 5n * BI_POWS[tokens[srcTokenSymbol].decimals], - 6n * BI_POWS[tokens[srcTokenSymbol].decimals], - 7n * BI_POWS[tokens[srcTokenSymbol].decimals], - 8n * BI_POWS[tokens[srcTokenSymbol].decimals], - 9n * BI_POWS[tokens[srcTokenSymbol].decimals], - 10n * BI_POWS[tokens[srcTokenSymbol].decimals], - ]; - beforeAll(async () => { blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); // @ts-expect-error for testing there is dummy blocknumber, but it is not @@ -178,39 +162,175 @@ describe('CurveV1Factory', function () { if (curveV1Factory) curveV1Factory.releaseResources(); }); - it('getPoolIdentifiers and getPricesVolume SELL', async function () { - await testPricingOnNetwork( - curveV1Factory, - network, - dexKey, - blockNumber, - srcTokenSymbol, - destTokenSymbol, - SwapSide.SELL, - amountsForSell, - ); + describe(`USDD-USDT`, () => { + const srcTokenSymbol = 'USDD'; + const destTokenSymbol = 'USDT'; + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + curveV1Factory, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newCurveV1Factory = new CurveV1Factory( + network, + dexKey, + dexHelper, + ); + if (newCurveV1Factory.updatePoolState) { + await newCurveV1Factory.updatePoolState(); + } + const poolLiquidity = await newCurveV1Factory.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newCurveV1Factory.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); }); - it('getTopPoolsForToken', async function () { - // We have to check without calling initializePricing, because - // pool-tracker is not calling that function - const newCurveV1Factory = new CurveV1Factory(network, dexKey, dexHelper); - if (newCurveV1Factory.updatePoolState) { - await newCurveV1Factory.updatePoolState(); - } - const poolLiquidity = await newCurveV1Factory.getTopPoolsForToken( - tokens[srcTokenSymbol].address, - 10, - ); - console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + describe(`crvUSD-GHO`, () => { + const srcTokenSymbol = 'crvUSD'; + const destTokenSymbol = 'GHO'; + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + curveV1Factory, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); - if (!newCurveV1Factory.hasConstantPriceLargeAmounts) { - checkPoolsLiquidity( - poolLiquidity, - Tokens[network][srcTokenSymbol].address, + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newCurveV1Factory = new CurveV1Factory( + network, dexKey, + dexHelper, ); - } + if (newCurveV1Factory.updatePoolState) { + await newCurveV1Factory.updatePoolState(); + } + const poolLiquidity = await newCurveV1Factory.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newCurveV1Factory.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); + + describe(`renBTC-wibBTC`, () => { + const srcTokenSymbol = 'renBTC'; + const destTokenSymbol = 'wibBTC'; + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + ]; + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + curveV1Factory, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newCurveV1Factory = new CurveV1Factory( + network, + dexKey, + dexHelper, + ); + if (newCurveV1Factory.updatePoolState) { + await newCurveV1Factory.updatePoolState(); + } + const poolLiquidity = await newCurveV1Factory.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newCurveV1Factory.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); }); }); describe('Polygon', () => { diff --git a/src/dex/curve-v1-factory/curve-v1-factory.ts b/src/dex/curve-v1-factory/curve-v1-factory.ts index e365c351b..8606bdce6 100644 --- a/src/dex/curve-v1-factory/curve-v1-factory.ts +++ b/src/dex/curve-v1-factory/curve-v1-factory.ts @@ -127,7 +127,7 @@ export class CurveV1Factory private coinsTypeTemplate: AbiItem = DefaultCoinsABI, ) { super(dexHelper, dexKey); - this.logger = dexHelper.getLogger(dexKey); + this.logger = dexHelper.getLogger(`${this.dexKey}-${this.network}`); this.ifaces = { exchangeRouter: new Interface(CurveABI), factory: new Interface(FactoryCurveV1ABI as JsonFragment[]), @@ -181,10 +181,13 @@ export class CurveV1Factory // This is only to start timer, each pool is initialized with updated state this.poolManager.initializePollingPools(); await this.fetchFactoryPools(blockNumber); + await this.poolManager.fetchLiquiditiesFromApi(); + await this.poolManager.updatePollingPoolsInBatch(); this.logger.info(`${this.dexKey}: successfully initialized`); } async initializeCustomPollingPools( + factoryAddresses: string[], blockNumber?: number, // We don't want to initialize state for PoolTracker. It doesn't make any sense initializeInitialState: boolean = true, @@ -193,131 +196,144 @@ export class CurveV1Factory return; } - const { factoryAddress } = this.config; - if (!factoryAddress) { - this.logger.warn( - `${this.dexKey}: No factory address specified for ${this.network}`, - ); - return; - } - await Promise.all( - Object.values(this.config.customPools).map(async customPool => { - const poolIdentifier = this.getPoolIdentifier( - customPool.address, - false, - ); - - const poolContextConstants = ImplementationConstants[customPool.name]; - const { - N_COINS: nCoins, - USE_LENDING: useLending, - isLending, - } = poolContextConstants; - - const coinsAndImplementations = - await this.dexHelper.multiWrapper.aggregate( - _.range(0, nCoins) - .map(i => ({ - target: customPool.address, - callData: this.abiCoder.encodeFunctionCall( - this._getCoinsABI(customPool.coinsInputType), - [i.toString()], - ), - decodeFunction: addressDecode, - })) - .concat([ - { - target: factoryAddress, - callData: this.ifaces.factory.encodeFunctionData( - 'get_implementation_address', - [customPool.address], - ), - decodeFunction: addressDecode, - }, - ]), + factoryAddresses.map(async factoryAddress => { + try { + await Promise.all( + Object.values(this.config.customPools).map(async customPool => { + const poolIdentifier = this.getPoolIdentifier( + customPool.address, + false, + ); + + const poolContextConstants = + ImplementationConstants[customPool.name]; + const { + N_COINS: nCoins, + USE_LENDING: useLending, + isLending, + } = poolContextConstants; + + const coinsAndImplementations = + await this.dexHelper.multiWrapper.aggregate( + _.range(0, nCoins) + .map(i => ({ + target: customPool.address, + callData: this.abiCoder.encodeFunctionCall( + this._getCoinsABI(customPool.coinsInputType), + [i.toString()], + ), + decodeFunction: addressDecode, + })) + .concat([ + { + target: factoryAddress, + callData: this.ifaces.factory.encodeFunctionData( + 'get_implementation_address', + [customPool.address], + ), + decodeFunction: addressDecode, + }, + ]), + ); + const COINS = coinsAndImplementations.slice(0, -1); + const implementationAddress = + coinsAndImplementations.slice(-1)[0]; + + const coins_decimals = ( + await this.dexHelper.multiWrapper.tryAggregate( + true, + COINS.map(c => ({ + target: c, + callData: this.ifaces.erc20.encodeFunctionData( + 'decimals', + [], + ), + decodeFunction: uint8ToNumber, + })), + ) + ).map(r => r.returnData); + + const poolConstants: PoolConstants = { + COINS, + coins_decimals, + rate_multipliers: this._calcRateMultipliers(coins_decimals), + lpTokenAddress: customPool.lpTokenAddress, + }; + + let newPool: PoolPollingBase; + if ( + Object.values( + CustomImplementationNames, + ).includes(customPool.name) + ) { + // We don't want custom pools to be used for pricing, unless explicitly specified + newPool = new CustomBasePoolForFactory( + this.logger, + this.dexKey, + this.dexHelper.config.data.network, + this.cacheStateKey, + customPool.name, + implementationAddress, + customPool.address, + this.config.stateUpdatePeriodMs, + poolIdentifier, + poolConstants, + poolContextConstants, + customPool.liquidityApiSlug, + customPool.lpTokenAddress, + isLending, + customPool.balancesInputType, + useLending, + customPool.useForPricing, + ); + } else { + // Use for pricing pools from factory + newPool = new CustomBasePoolForFactory( + this.logger, + this.dexKey, + this.dexHelper.config.data.network, + this.cacheStateKey, + customPool.name, + implementationAddress, + customPool.address, + this.config.stateUpdatePeriodMs, + poolIdentifier, + poolConstants, + poolContextConstants, + customPool.liquidityApiSlug, + customPool.lpTokenAddress, + isLending, + customPool.balancesInputType, + useLending, + true, + ); + } + + this.poolManager.initializeNewPoolForState( + poolIdentifier, + newPool, + ); + + if (initializeInitialState) { + await this.poolManager.initializeIndividualPollingPoolState( + poolIdentifier, + CustomBasePoolForFactory.IS_SRC_FEE_ON_TRANSFER_SUPPORTED, + blockNumber, + ); + } + }), ); - const COINS = coinsAndImplementations.slice(0, -1); - const implementationAddress = coinsAndImplementations.slice(-1)[0]; - - const coins_decimals = ( - await this.dexHelper.multiWrapper.tryAggregate( - true, - COINS.map(c => ({ - target: c, - callData: this.ifaces.erc20.encodeFunctionData('decimals', []), - decodeFunction: uint8ToNumber, - })), - ) - ).map(r => r.returnData); - - const poolConstants: PoolConstants = { - COINS, - coins_decimals, - rate_multipliers: this._calcRateMultipliers(coins_decimals), - lpTokenAddress: customPool.lpTokenAddress, - }; - - let newPool: PoolPollingBase; - if ( - Object.values( - CustomImplementationNames, - ).includes(customPool.name) - ) { - // We don't want custom pools to be used for pricing, unless explicitly specified - newPool = new CustomBasePoolForFactory( - this.logger, - this.dexKey, - this.dexHelper.config.data.network, - this.cacheStateKey, - customPool.name, - implementationAddress, - customPool.address, - this.config.stateUpdatePeriodMs, - poolIdentifier, - poolConstants, - poolContextConstants, - customPool.liquidityApiSlug, - customPool.lpTokenAddress, - isLending, - customPool.balancesInputType, - useLending, - customPool.useForPricing, - ); - } else { - // Use for pricing pools from factory - newPool = new CustomBasePoolForFactory( - this.logger, - this.dexKey, - this.dexHelper.config.data.network, - this.cacheStateKey, - customPool.name, - implementationAddress, - customPool.address, - this.config.stateUpdatePeriodMs, - poolIdentifier, - poolConstants, - poolContextConstants, - customPool.liquidityApiSlug, - customPool.lpTokenAddress, - isLending, - customPool.balancesInputType, - useLending, - true, - ); - } - - this.poolManager.initializeNewPoolForState(poolIdentifier, newPool); - - if (initializeInitialState) { - await this.poolManager.initializeIndividualPollingPoolState( - poolIdentifier, - CustomBasePoolForFactory.IS_SRC_FEE_ON_TRANSFER_SUPPORTED, - blockNumber, + } catch (e) { + this.logger.error( + `Error initializing custom polling pools for factory ${factoryAddress}: `, + e, ); + throw e; } }), ); + this.areCustomPoolsFetched = true; } @@ -326,283 +342,317 @@ export class CurveV1Factory // Variable initializeInitialState is only for poolTracker. We don't want to keep state updated with scheduler // We just want to initialize factory pools and send request to CurveAPI // Other values are not used - initializeInitialState: boolean = true, + initializeInitialState: boolean = false, ) { if (this.areFactoryPoolsFetched) { return; } + if ( + !this.config.factoryAddresses || + this.config.factoryAddresses.length == 0 + ) { + this.logger.warn(`No factory address specified in configs`); + return; + } + // There is no scenario when we need to call initialize custom pools without factory pools // So I put it here to not forget call, because custom pools must be initialised before factory pools // This function may be called multiple times, but will execute only once await this.initializeCustomPollingPools( + this.config.factoryAddresses, blockNumber, initializeInitialState, ); - const { factoryAddress } = this.config; - if (!factoryAddress) { - this.logger.warn( - `${this.dexKey}: No factory address specified for ${this.network}`, - ); - return; - } - - const poolCountResult = await this.dexHelper.multiWrapper!.tryAggregate( - true, - [ - { - target: factoryAddress, - callData: this.ifaces.factory.encodeFunctionData('pool_count'), - decodeFunction: uint256DecodeToNumber, - }, - // This is used later to request all available implementations. In particular meta implementations - { - target: factoryAddress, - callData: this.ifaces.factory.encodeFunctionData('base_pool_count'), - decodeFunction: uint256DecodeToNumber, - }, - ], - ); - - const poolCount = poolCountResult[0].returnData; - const basePoolCount = poolCountResult[1].returnData; - - const calldataGetPoolAddresses = _.range(0, poolCount).map(i => ({ - target: factoryAddress, - callData: this.ifaces.factory.encodeFunctionData('pool_list', [i]), - decodeFunction: addressDecode, - })); - - const calldataGetBasePoolAddresses = _.range(0, basePoolCount).map(i => ({ - target: factoryAddress, - callData: this.ifaces.factory.encodeFunctionData('base_pool_list', [i]), - decodeFunction: addressDecode, - })); - - const allPoolAddresses = ( - await this.dexHelper.multiWrapper.tryAggregate( - true, - calldataGetPoolAddresses.concat(calldataGetBasePoolAddresses), - ) - ).map(e => e.returnData); - - const poolAddresses = allPoolAddresses.slice(0, poolCount); - const basePoolAddresses = allPoolAddresses.slice(poolCount); - - const customPoolAddresses = Object.values(this.config.customPools).map( - customPool => customPool.address, - ); - basePoolAddresses.forEach(basePool => { - if ( - !customPoolAddresses.includes(basePool) && - !this.config.disabledPools.has(basePool) - ) { - this._reportForUnspecifiedCustomPool(basePool); - } - }); - - let callDataFromFactoryPools: MultiCallParams< - string[] | number[] | string - >[] = poolAddresses - .map(p => [ - { - target: factoryAddress, - callData: this.ifaces.factory.encodeFunctionData( - 'get_implementation_address', - [p], - ), - decodeFunction: addressDecode, - }, - { - target: factoryAddress, - callData: this.ifaces.factory.encodeFunctionData('get_coins', [p]), - decodeFunction: ( - result: MultiResult | BytesLike, - ): string[] => - generalDecoder( - result, - ['address[4]'], - new Array(4).fill(NULL_ADDRESS), - parsed => parsed[0].map((p: string) => p.toLowerCase()), - ), - }, - { - target: factoryAddress, - callData: this.ifaces.factory.encodeFunctionData('get_decimals', [p]), - decodeFunction: ( - result: MultiResult | BytesLike, - ): number[] => - generalDecoder( - result, - ['uint256[4]'], - [0, 0, 0, 0], - parsed => parsed[0].map((p: BigNumber) => Number(p.toString())), - ), - }, - ]) - .flat(); - - // This is divider between pools related results and implementations - const factoryResultsDivider = callDataFromFactoryPools.length; - - // Implementations must be requested from factory, but it accepts as arg basePool address - // for metaPools - callDataFromFactoryPools = callDataFromFactoryPools.concat( - ...basePoolAddresses.map(basePoolAddress => ({ - target: factoryAddress, - callData: this.ifaces.factory.encodeFunctionData( - 'metapool_implementations', - [basePoolAddress], - ), - decodeFunction: ( - result: MultiResult | BytesLike, - ): string[] => - generalDecoder( - result, - ['address[10]'], - new Array(10).fill(NULL_ADDRESS), - parsed => parsed[0].map((p: string) => p.toLowerCase()), - ), - })), - // To receive plain pool implementation address, you have to call plain_implementations - // with two variables: N_COINS and implementations_index - // N_COINS is between 2-4. Currently more than 4 coins is not supported - // as for implementation index, there are only 0-9 indexes - ..._.flattenDeep( - _.range(2, FACTORY_MAX_PLAIN_COINS + 1).map(coinNumber => - _.range(FACTORY_MAX_PLAIN_IMPLEMENTATIONS_FOR_COIN).map(implInd => ({ + await Promise.all( + this.config.factoryAddresses.map(async factoryAddress => { + try { + const poolCountResult = + await this.dexHelper.multiWrapper!.tryAggregate(true, [ + { + target: factoryAddress, + callData: this.ifaces.factory.encodeFunctionData('pool_count'), + decodeFunction: uint256DecodeToNumber, + }, + // This is used later to request all available implementations. In particular meta implementations + { + target: factoryAddress, + callData: + this.ifaces.factory.encodeFunctionData('base_pool_count'), + decodeFunction: uint256DecodeToNumber, + }, + ]); + + const poolCount = poolCountResult[0].returnData; + const basePoolCount = poolCountResult[1].returnData; + + const calldataGetPoolAddresses = _.range(0, poolCount).map(i => ({ target: factoryAddress, - callData: this.ifaces.factory.encodeFunctionData( - 'plain_implementations', - [coinNumber, implInd], - ), + callData: this.ifaces.factory.encodeFunctionData('pool_list', [i]), decodeFunction: addressDecode, - })), - ), - ), - ); + })); + + const calldataGetBasePoolAddresses = _.range(0, basePoolCount).map( + i => ({ + target: factoryAddress, + callData: this.ifaces.factory.encodeFunctionData( + 'base_pool_list', + [i], + ), + decodeFunction: addressDecode, + }), + ); - const allResultsFromFactory = ( - await this.dexHelper.multiWrapper.tryAggregate< - string[] | number[] | string - >(true, callDataFromFactoryPools) - ).map(r => r.returnData); + const allPoolAddresses = ( + await this.dexHelper.multiWrapper.tryAggregate( + true, + calldataGetPoolAddresses.concat(calldataGetBasePoolAddresses), + ) + ).map(e => e.returnData); + + const poolAddresses = allPoolAddresses.slice(0, poolCount); + const basePoolAddresses = allPoolAddresses.slice(poolCount); + + const customPoolAddresses = Object.values( + this.config.customPools, + ).map(customPool => customPool.address); + basePoolAddresses.forEach(basePool => { + if ( + !customPoolAddresses.includes(basePool) && + !this.config.disabledPools.has(basePool) + ) { + this._reportForUnspecifiedCustomPool(basePool); + } + }); - const resultsFromFactory = allResultsFromFactory.slice( - 0, - factoryResultsDivider, - ); + let callDataFromFactoryPools: MultiCallParams< + string[] | number[] | string + >[] = poolAddresses + .map(p => [ + { + target: factoryAddress, + callData: this.ifaces.factory.encodeFunctionData( + 'get_implementation_address', + [p], + ), + decodeFunction: addressDecode, + }, + { + target: factoryAddress, + callData: this.ifaces.factory.encodeFunctionData('get_coins', [ + p, + ]), + decodeFunction: ( + result: MultiResult | BytesLike, + ): string[] => + generalDecoder( + result, + ['address[4]'], + new Array(4).fill(NULL_ADDRESS), + parsed => parsed[0].map((p: string) => p.toLowerCase()), + ), + }, + { + target: factoryAddress, + callData: this.ifaces.factory.encodeFunctionData( + 'get_decimals', + [p], + ), + decodeFunction: ( + result: MultiResult | BytesLike, + ): number[] => + generalDecoder( + result, + ['uint256[4]'], + [0, 0, 0, 0], + parsed => + parsed[0].map((p: BigNumber) => Number(p.toString())), + ), + }, + ]) + .flat(); + + // This is divider between pools related results and implementations + const factoryResultsDivider = callDataFromFactoryPools.length; + + // Implementations must be requested from factory, but it accepts as arg basePool address + // for metaPools + callDataFromFactoryPools = callDataFromFactoryPools.concat( + ...basePoolAddresses.map(basePoolAddress => ({ + target: factoryAddress, + callData: this.ifaces.factory.encodeFunctionData( + 'metapool_implementations', + [basePoolAddress], + ), + decodeFunction: ( + result: MultiResult | BytesLike, + ): string[] => + generalDecoder( + result, + ['address[10]'], + new Array(10).fill(NULL_ADDRESS), + parsed => parsed[0].map((p: string) => p.toLowerCase()), + ), + })), + // To receive plain pool implementation address, you have to call plain_implementations + // with two variables: N_COINS and implementations_index + // N_COINS is between 2-4. Currently more than 4 coins is not supported + // as for implementation index, there are only 0-9 indexes + ..._.flattenDeep( + _.range(2, FACTORY_MAX_PLAIN_COINS + 1).map(coinNumber => + _.range(FACTORY_MAX_PLAIN_IMPLEMENTATIONS_FOR_COIN).map( + implInd => ({ + target: factoryAddress, + callData: this.ifaces.factory.encodeFunctionData( + 'plain_implementations', + [coinNumber, implInd], + ), + decodeFunction: addressDecode, + }), + ), + ), + ), + ); - const allAvailableImplementations = _.flattenDeep( - allResultsFromFactory.slice(factoryResultsDivider) as string[], - ).filter( - implementation => - implementation !== NULL_ADDRESS && - !this.config.disabledImplementations.has(implementation), - ); + const allResultsFromFactory = ( + await this.dexHelper.multiWrapper.tryAggregate< + string[] | number[] | string + >(true, callDataFromFactoryPools) + ).map(r => r.returnData); - allAvailableImplementations.forEach(implementation => { - const currentImplementation = - this.config.factoryPoolImplementations[implementation]; - if (currentImplementation === undefined) { - this._reportForUnspecifiedImplementation(implementation); - } - }); + const resultsFromFactory = allResultsFromFactory.slice( + 0, + factoryResultsDivider, + ); - const stateInitializePromises: Promise[] = []; - _.chunk(resultsFromFactory, 3).forEach((result, i) => { - if (this.config.disabledPools.has(poolAddresses[i])) { - this.logger.trace(`Filtering disabled pool ${poolAddresses[i]}`); - return; - } + const allAvailableImplementations = _.flattenDeep( + allResultsFromFactory.slice(factoryResultsDivider) as string[], + ).filter( + implementation => + implementation !== NULL_ADDRESS && + !this.config.disabledImplementations.has(implementation), + ); - let [implementationAddress, coins, coins_decimals] = result as [ - string, - string[], - number[], - ]; + allAvailableImplementations.forEach(implementation => { + const currentImplementation = + this.config.factoryPoolImplementations[implementation]; + if (currentImplementation === undefined) { + this._reportForUnspecifiedImplementation(implementation); + } + }); - implementationAddress = implementationAddress.toLowerCase(); - coins = coins.map(c => c.toLowerCase()).filter(c => c !== NULL_ADDRESS); - coins_decimals = coins_decimals.filter(cd => cd !== 0); + const stateInitializePromises: Promise[] = []; + _.chunk(resultsFromFactory, 3).forEach((result, i) => { + if (this.config.disabledPools.has(poolAddresses[i])) { + this.logger.trace(`Filtering disabled pool ${poolAddresses[i]}`); + return; + } + + let [implementationAddress, coins, coins_decimals] = result as [ + string, + string[], + number[], + ]; + + implementationAddress = implementationAddress.toLowerCase(); + coins = coins + .map(c => c.toLowerCase()) + .filter(c => c !== NULL_ADDRESS); + coins_decimals = coins_decimals.filter(cd => cd !== 0); + + const factoryImplementationFromConfig = + this.config.factoryPoolImplementations[implementationAddress]; + + if ( + factoryImplementationFromConfig === undefined && + !this.config.disabledImplementations.has(implementationAddress) + ) { + this._reportForUnspecifiedImplementation( + implementationAddress, + poolAddresses[i], + ); + return; + } + + const factoryImplementationConstants = + ImplementationConstants[factoryImplementationFromConfig.name]; + + let isMeta: boolean = false; + let basePoolStateFetcher: PoolPollingBase | undefined; + if (factoryImplementationFromConfig.basePoolAddress !== undefined) { + isMeta = true; + const basePoolIdentifier = this.getPoolIdentifier( + factoryImplementationFromConfig.basePoolAddress, + false, + ); + const basePool = this.poolManager.getPool( + basePoolIdentifier, + false, + ); + if (basePool === null) { + this.logger.error( + `${this.dexKey}_${this.dexHelper.config.data.network}: custom base pool ${basePoolIdentifier} was not initialized properly. ` + + `You must call initializeCustomPollingPools before fetching factory`, + ); + return; + } + basePoolStateFetcher = basePool; + } + + const poolConstants: PoolConstants = { + COINS: coins, + coins_decimals, + rate_multipliers: this._calcRateMultipliers(coins_decimals), + }; + + const poolIdentifier = this.getPoolIdentifier( + poolAddresses[i], + isMeta, + ); - const factoryImplementationFromConfig = - this.config.factoryPoolImplementations[implementationAddress]; + const newPool = new FactoryStateHandler( + this.logger, + this.dexKey, + this.dexHelper.config.data.network, + this.cacheStateKey, + factoryImplementationFromConfig.name, + implementationAddress.toLowerCase(), + poolAddresses[i], + this.config.stateUpdatePeriodMs, + factoryAddress, + poolIdentifier, + poolConstants, + factoryImplementationConstants, + factoryImplementationConstants.isFeeOnTransferSupported, + factoryImplementationFromConfig.liquidityApiSlug ?? '/factory', + basePoolStateFetcher, + factoryImplementationFromConfig.customGasCost, + factoryImplementationFromConfig.isStoreRateSupported, + ); - if ( - factoryImplementationFromConfig === undefined && - !this.config.disabledImplementations.has(implementationAddress) - ) { - this._reportForUnspecifiedImplementation( - implementationAddress, - poolAddresses[i], - ); - return; - } + this.poolManager.initializeNewPool(poolIdentifier, newPool); - const factoryImplementationConstants = - ImplementationConstants[factoryImplementationFromConfig.name]; + if (initializeInitialState) { + stateInitializePromises.push( + this.poolManager.initializeIndividualPollingPoolState( + poolIdentifier, + factoryImplementationConstants.isFeeOnTransferSupported, + ), + ); + } + }); - let isMeta: boolean = false; - let basePoolStateFetcher: PoolPollingBase | undefined; - if (factoryImplementationFromConfig.basePoolAddress !== undefined) { - isMeta = true; - const basePoolIdentifier = this.getPoolIdentifier( - factoryImplementationFromConfig.basePoolAddress, - false, - ); - const basePool = this.poolManager.getPool(basePoolIdentifier, false); - if (basePool === null) { + await Promise.all(stateInitializePromises); + } catch (e) { this.logger.error( - `${this.dexKey}_${this.dexHelper.config.data.network}: custom base pool ${basePoolIdentifier} was not initialized properly. ` + - `You must call initializeCustomPollingPools before fetching factory`, + `Error fetching factory pools for ${factoryAddress}: `, + e, ); - return; + throw e; } - basePoolStateFetcher = basePool; - } - - const poolConstants: PoolConstants = { - COINS: coins, - coins_decimals, - rate_multipliers: this._calcRateMultipliers(coins_decimals), - }; - - const poolIdentifier = this.getPoolIdentifier(poolAddresses[i], isMeta); - - const newPool = new FactoryStateHandler( - this.logger, - this.dexKey, - this.dexHelper.config.data.network, - this.cacheStateKey, - factoryImplementationFromConfig.name, - implementationAddress.toLowerCase(), - poolAddresses[i], - this.config.stateUpdatePeriodMs, - factoryAddress, - poolIdentifier, - poolConstants, - factoryImplementationConstants, - factoryImplementationConstants.isFeeOnTransferSupported, - basePoolStateFetcher, - factoryImplementationFromConfig.customGasCost, - factoryImplementationFromConfig.isStoreRateSupported, - ); - - this.poolManager.initializeNewPool(poolIdentifier, newPool); - - if (initializeInitialState) { - stateInitializePromises.push( - this.poolManager.initializeIndividualPollingPoolState( - poolIdentifier, - factoryImplementationConstants.isFeeOnTransferSupported, - ), - ); - } - }); - - await Promise.all(stateInitializePromises); + }), + ); this.areFactoryPoolsFetched = true; } @@ -722,57 +772,69 @@ export class CurveV1Factory ) : amountsWithUnit; - const results = pools.map( - (pool): PoolPrices | null => { - const state = pool.getState(); - - if (!state) { - return null; - } - - if (state.balances.every(b => b === 0n)) { - this.logger.trace( - `${this.dexKey} on ${this.dexHelper.config.data.network}: State balances equal to 0 in pool ${pool.address}`, + const results = await Promise.all( + pools.map( + async (pool): Promise | null> => { + let state = pool.getState(); + if (!state) { + await this.poolManager.updateManuallyPollingPools( + pool.baseStatePoolPolling + ? [pool.baseStatePoolPolling, pool] + : [pool], + ); + state = pool.getState(); + if (!state) { + return null; + } + } + + if (state.balances.every(b => b === 0n)) { + this.logger.trace( + `${this.dexKey} on ${this.dexHelper.config.data.network}: State balances equal to 0 in pool ${pool.address}`, + ); + return null; + } + + const poolData = pool.getPoolData( + srcTokenAddress, + destTokenAddress, ); - return null; - } - const poolData = pool.getPoolData(srcTokenAddress, destTokenAddress); - - if (poolData === null) { - this.logger.error( - `${pool.fullName}: one or both tokens can not be exchanged in pool ${pool.address}: ${srcTokenAddress} -> ${destTokenAddress}`, - ); - return null; - } - - let outputs: bigint[] = this.poolManager - .getPriceHandler(pool.implementationAddress) - .getOutputs( - state, - amountsWithUnitAndFee, - poolData.i, - poolData.j, - poolData.underlyingSwap, + if (poolData === null) { + this.logger.error( + `${pool.fullName}: one or both tokens can not be exchanged in pool ${pool.address}: ${srcTokenAddress} -> ${destTokenAddress}`, + ); + return null; + } + + let outputs: bigint[] = this.poolManager + .getPriceHandler(pool.implementationAddress) + .getOutputs( + state, + amountsWithUnitAndFee, + poolData.i, + poolData.j, + poolData.underlyingSwap, + ); + + outputs = applyTransferFee( + outputs, + side, + transferFees.destDexFee, + this.DEST_TOKEN_DEX_TRANSFERS, ); - outputs = applyTransferFee( - outputs, - side, - transferFees.destDexFee, - this.DEST_TOKEN_DEX_TRANSFERS, - ); - - return { - prices: [0n, ...outputs.slice(1)], - unit: outputs[0], - data: poolData, - exchange: this.dexKey, - poolIdentifier: pool.poolIdentifier, - gasCost: POOL_EXCHANGE_GAS_COST, - poolAddresses: [pool.address], - }; - }, + return { + prices: [0n, ...outputs.slice(1)], + unit: outputs[0], + data: poolData, + exchange: this.dexKey, + poolIdentifier: pool.poolIdentifier, + gasCost: POOL_EXCHANGE_GAS_COST, + poolAddresses: [pool.address], + }; + }, + ), ); return results.filter( diff --git a/src/dex/curve-v1-factory/curve-v1-pool-manager.ts b/src/dex/curve-v1-factory/curve-v1-pool-manager.ts index c240dbb09..45141def5 100644 --- a/src/dex/curve-v1-factory/curve-v1-pool-manager.ts +++ b/src/dex/curve-v1-factory/curve-v1-pool-manager.ts @@ -42,7 +42,10 @@ export class CurveV1FactoryPoolManager { // It should bo considered for optimizing private coinAddressesToPoolIdentifiers: Record = {}; - private allCurveLiquidityApiSlugs: Set = new Set(['/factory']); + private allCurveLiquidityApiSlugs: Set = new Set([ + '/factory', + '/factory-crvusd', + ]); private statePollingManager = StatePollingManager; private taskScheduler: TaskScheduler; @@ -91,7 +94,7 @@ export class CurveV1FactoryPoolManager { filteredPoolsByLiquidity.sort((a, b) => +a.isMetaPool - +b.isMetaPool), ); - this.statePollingManager.updatePoolsInBatch( + return this.statePollingManager.updatePoolsInBatch( this.logger, this.dexHelper, pools, @@ -102,6 +105,16 @@ export class CurveV1FactoryPoolManager { ); } + async updateManuallyPollingPools(pools: PoolPollingBase[]) { + return this.statePollingManager.updatePoolsInBatch( + this.logger, + this.dexHelper, + pools, + undefined, + undefined, + ); + } + async initializeIndividualPollingPoolState( identifier: string, isSrcFeeOnTransferTokenToBeExchanged: boolean, diff --git a/src/dex/curve-v1-factory/price-handlers/functions/_calc_withdraw_one_coin.ts b/src/dex/curve-v1-factory/price-handlers/functions/_calc_withdraw_one_coin.ts index 5c9b0954e..ff85c7bd5 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/_calc_withdraw_one_coin.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/_calc_withdraw_one_coin.ts @@ -284,6 +284,7 @@ const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: customArbitrum2CoinBtc, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: customArbitrum2CoinBtc, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: factoryPlain2EthEma2, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: customArbitrum2CoinBtc, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/_dynamic_fee.ts b/src/dex/curve-v1-factory/price-handlers/functions/_dynamic_fee.ts index 541a47343..b0ba16915 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/_dynamic_fee.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/_dynamic_fee.ts @@ -93,6 +93,7 @@ const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: notExist, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: notExist, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: notExist, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: notExist, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/_rates.ts b/src/dex/curve-v1-factory/price-handlers/functions/_rates.ts index b8ab211d1..f16d6b114 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/_rates.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/_rates.ts @@ -36,7 +36,7 @@ const customPlain3CoinSbtc: _rates = ( let rate = LENDING_PRECISION; // Used with no lending if (use_lending[i]) { const currentRate = state.exchangeRateCurrent[i]; - if (currentRate === undefined) { + if (!currentRate) { throw new Error( `${self.IMPLEMENTATION_NAME}: exchangeRateCurrent contains undefined value that supposed to be used: ${state.exchangeRateCurrent}`, ); @@ -108,6 +108,7 @@ const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: notExist, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: notExist, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: notExist, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: notExist, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/_xp.ts b/src/dex/curve-v1-factory/price-handlers/functions/_xp.ts index 2f815afdd..f12737784 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/_xp.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/_xp.ts @@ -106,6 +106,7 @@ const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: notExist, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: notExist, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: notExist, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: notExist, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/_xp_mem.ts b/src/dex/curve-v1-factory/price-handlers/functions/_xp_mem.ts index c93fe7a3b..94db894b6 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/_xp_mem.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/_xp_mem.ts @@ -95,6 +95,7 @@ const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: factoryPlain2Basic, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: factoryPlain2Basic, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: factoryPlain2Basic, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: factoryPlain2Basic, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/calc_token_amount.ts b/src/dex/curve-v1-factory/price-handlers/functions/calc_token_amount.ts index cb83a1dfe..7f5708faa 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/calc_token_amount.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/calc_token_amount.ts @@ -35,6 +35,35 @@ const customPlain3CoinThree: calc_token_amount = ( return (diff * token_amount) / D0; }; +const customAvalanche3CoinLending: calc_token_amount = ( + self: IPoolContext, + state: PoolState, + amounts: bigint[], + is_deposit: boolean, +) => { + const { N_COINS } = self.constants; + + const coin_balances = [...state.balances]; + const amp = state.A; + const D0 = self.get_D_precision(self, coin_balances, amp); + for (const i of _.range(N_COINS)) { + if (is_deposit) coin_balances[i] += amounts[i]; + else coin_balances[i] -= amounts[i]; + } + const D1 = self.get_D_precision(self, coin_balances, amp); + let diff = 0n; + if (is_deposit) diff = D1 - D0; + else diff = D0 - D1; + + if (state.totalSupply === undefined) { + throw new Error( + `${self.IMPLEMENTATION_NAME} customAvalanche3CoinLending: totalSupply is not provided`, + ); + } + + return (diff * state.totalSupply) / D0; +}; + const factoryPlain2Basic: calc_token_amount = ( self: IPoolContext, state: PoolState, @@ -67,33 +96,61 @@ const factoryPlain2Basic: calc_token_amount = ( return (diff * token_amount) / D0; }; -const customAvalanche3CoinLending: calc_token_amount = ( +const customPlain2CoinCrv: calc_token_amount = ( self: IPoolContext, state: PoolState, amounts: bigint[], is_deposit: boolean, ) => { - const { N_COINS } = self.constants; - - const coin_balances = [...state.balances]; + const { N_COINS, BI_N_COINS, FEE_DENOMINATOR } = self.constants; const amp = state.A; - const D0 = self.get_D_precision(self, coin_balances, amp); + const balances = [...state.balances]; + const D0 = self.get_D_mem(self, state, balances, amp); for (const i of _.range(N_COINS)) { - if (is_deposit) coin_balances[i] += amounts[i]; - else coin_balances[i] -= amounts[i]; + if (is_deposit) balances[i] += amounts[i]; + else balances[i] -= amounts[i]; } - const D1 = self.get_D_precision(self, coin_balances, amp); - let diff = 0n; - if (is_deposit) diff = D1 - D0; - else diff = D0 - D1; + const D1 = self.get_D_mem(self, state, balances, amp); if (state.totalSupply === undefined) { throw new Error( - `${self.IMPLEMENTATION_NAME} customAvalanche3CoinLending: totalSupply is not provided`, + `${self.IMPLEMENTATION_NAME} customPlain3CoinThree: totalSupply is not provided`, ); } - return (diff * state.totalSupply) / D0; + const total_supply = state.totalSupply; + let D2 = D1; + + if (total_supply > 0n) { + const base_fee = (state.fee * BI_N_COINS) / (4n * (BI_N_COINS - 1n)); + for (const i of _.range(N_COINS)) { + const ideal_balance = (D1 * state.balances[i]) / D0; + let difference = 0n; + const new_balance = balances[i]; + if (ideal_balance > new_balance) { + difference = ideal_balance - new_balance; + } else { + difference = new_balance - ideal_balance; + } + balances[i] -= (base_fee * difference) / FEE_DENOMINATOR; + } + const xp = self._xp_mem( + self, + [...state.constants.rate_multipliers], + balances, + ); + D2 = self.get_D_mem(self, state, xp, amp); + } else { + return D1; + } + + let diff = 0n; + if (is_deposit) { + diff = D2 - D0; + } else { + diff = D0 - D2; + } + return (diff * total_supply) / D0; }; const notImplemented: calc_token_amount = ( @@ -165,6 +222,7 @@ const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: customPlain3CoinThree, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: customPlain3CoinThree, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: customPlain3CoinThree, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: customPlain2CoinCrv, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/calc_withdraw_one_coin.ts b/src/dex/curve-v1-factory/price-handlers/functions/calc_withdraw_one_coin.ts index c6269ac23..4e6e06d66 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/calc_withdraw_one_coin.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/calc_withdraw_one_coin.ts @@ -82,6 +82,7 @@ export const implementations: Record< [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: customPlain3CoinThree, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: customPlain3CoinThree, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: customPlain3CoinThree, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: customPlain3CoinThree, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/constants.ts b/src/dex/curve-v1-factory/price-handlers/functions/constants.ts index e808ade22..a85483b84 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/constants.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/constants.ts @@ -573,6 +573,17 @@ const implementationConstants: Record< isFeeOnTransferSupported: true, isLending: false, + N_COINS: 2, + BI_N_COINS: 2n, + PRECISION: BI_POWS[18], + FEE_DENOMINATOR: BI_POWS[10], + A_PRECISION: 100n, + }, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: { + isWrapNative: false, + isFeeOnTransferSupported: false, + isLending: false, + N_COINS: 2, BI_N_COINS: 2n, PRECISION: BI_POWS[18], diff --git a/src/dex/curve-v1-factory/price-handlers/functions/get_D.ts b/src/dex/curve-v1-factory/price-handlers/functions/get_D.ts index c14500c6c..8ff128af1 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/get_D.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/get_D.ts @@ -321,6 +321,8 @@ const implementations: Record = { makeFuncCacheable(factoryPlain2Basic), [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: makeFuncCacheable(factoryPlain2Basic), + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: + makeFuncCacheable(factoryPlain2Basic), }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/get_D_mem.ts b/src/dex/curve-v1-factory/price-handlers/functions/get_D_mem.ts index 1178e2123..dc4d1d0fd 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/get_D_mem.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/get_D_mem.ts @@ -140,6 +140,7 @@ export const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: factoryPlain2Basic, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: factoryPlain2Basic, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: factoryPlain2EthEma2, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: factoryPlain2Basic, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/get_D_precisions.ts b/src/dex/curve-v1-factory/price-handlers/functions/get_D_precisions.ts index 178fc3232..b94501510 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/get_D_precisions.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/get_D_precisions.ts @@ -112,6 +112,7 @@ const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: notExist, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: notExist, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: notExist, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: notExist, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/get_dy.ts b/src/dex/curve-v1-factory/price-handlers/functions/get_dy.ts index bf69105bf..23339da18 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/get_dy.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/get_dy.ts @@ -266,6 +266,7 @@ const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: factoryPlain2Basic, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: factoryPlain2Basic, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: factoryPlain2EthEma2, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: factoryPlain2Basic, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/get_dy_underlying.ts b/src/dex/curve-v1-factory/price-handlers/functions/get_dy_underlying.ts index bd21d08e3..e4df36f5c 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/get_dy_underlying.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/get_dy_underlying.ts @@ -285,6 +285,7 @@ const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: notExist, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: notExist, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: notExist, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: notExist, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/get_y.ts b/src/dex/curve-v1-factory/price-handlers/functions/get_y.ts index 4fab6617e..62a323887 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/get_y.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/get_y.ts @@ -189,6 +189,7 @@ const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: customPlain2CoinFrax, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: customPlain2CoinFrax, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: customPlain2CoinFrax, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: customPlain2CoinFrax, }; export default implementations; diff --git a/src/dex/curve-v1-factory/price-handlers/functions/get_y_D.ts b/src/dex/curve-v1-factory/price-handlers/functions/get_y_D.ts index 7c713c3d1..65bbe1c8b 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/get_y_D.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/get_y_D.ts @@ -175,6 +175,7 @@ const implementations: Record = { [ImplementationNames.FACTORY_PLAIN_2_BASIC_EMA]: customPlain2CoinFrax, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA]: customPlain2CoinFrax, [ImplementationNames.FACTORY_PLAIN_2_ETH_EMA2]: customPlain2CoinFrax, + [ImplementationNames.FACTORY_PLAIN_2_CRV_EMA]: customPlain2CoinFrax, }; export default implementations; diff --git a/src/dex/curve-v1-factory/state-polling-pools/custom-pool-polling.ts b/src/dex/curve-v1-factory/state-polling-pools/custom-pool-polling.ts index 7a83192de..f2c3ea087 100644 --- a/src/dex/curve-v1-factory/state-polling-pools/custom-pool-polling.ts +++ b/src/dex/curve-v1-factory/state-polling-pools/custom-pool-polling.ts @@ -13,6 +13,7 @@ import { _require } from '../../../utils'; import { Address } from '@paraswap/core'; import { AbiItem } from 'web3-utils'; import { NULL_ADDRESS } from '../../../constants'; +import { assert } from 'ts-essentials'; type FunctionToCall = | 'A' @@ -227,16 +228,17 @@ export class CustomBasePoolForFactory extends PoolPollingBase { let exchangeRateCurrent: (bigint | undefined)[] | undefined; - let lastEndIndex = lastIndex + 1; + let lastEndIndex = lastIndex + this.poolConstants.COINS.length; if (this.useLending) { exchangeRateCurrent = new Array(this.useLending.length).fill(undefined); + let trueUseLendingCount = this.useLending.filter(el => el).length; const exchangeRateResults = multiOutputs.slice( lastEndIndex, // Filter false elements before checking length - lastEndIndex + this.useLending.filter(el => el).length, + lastEndIndex + trueUseLendingCount, ) as bigint[]; - lastEndIndex += this.useLending.length; + lastEndIndex += trueUseLendingCount; // We had array with booleans and I filtered of `false` and sent request. // So, now I must map that results to original indices. That is the reason of this complication const indicesToFill = this.useLending.reduce((acc, curr, i) => { @@ -255,9 +257,15 @@ export class CustomBasePoolForFactory extends PoolPollingBase { }, 'indicesToFill.length === exchangeRateResults.length', ); - indicesToFill.forEach((indexToFill, currentIndex) => { - exchangeRateResults[indexToFill] = exchangeRateResults[currentIndex]; + if (exchangeRateCurrent === undefined) { + throw new Error( + `${this.poolIdentifier}: exchangeRateCurrent is undefined`, + ); + } + const resultRate = exchangeRateResults[currentIndex]; + assert(resultRate, "resultRate can't be undefined"); + exchangeRateCurrent[indexToFill] = resultRate; }); } diff --git a/src/dex/curve-v1-factory/state-polling-pools/factory-pool-polling.ts b/src/dex/curve-v1-factory/state-polling-pools/factory-pool-polling.ts index 42f71e2cb..7f3c3811e 100644 --- a/src/dex/curve-v1-factory/state-polling-pools/factory-pool-polling.ts +++ b/src/dex/curve-v1-factory/state-polling-pools/factory-pool-polling.ts @@ -44,6 +44,7 @@ export class FactoryStateHandler extends PoolPollingBase { readonly poolConstants: PoolConstants, readonly poolContextConstants: PoolContextConstants, readonly isSrcFeeOnTransferSupported: boolean, + liquidityApiSlug: string, baseStatePoolPolling?: PoolPollingBase, customGasCost?: number, readonly isStoredRatesSupported: boolean = false, @@ -65,7 +66,7 @@ export class FactoryStateHandler extends PoolPollingBase { poolIdentifier, poolConstants, address, - '/factory', + liquidityApiSlug, false, baseStatePoolPolling, isSrcFeeOnTransferSupported, diff --git a/src/dex/curve-v1-factory/types.ts b/src/dex/curve-v1-factory/types.ts index f5d6832ce..bb2b889f6 100644 --- a/src/dex/curve-v1-factory/types.ts +++ b/src/dex/curve-v1-factory/types.ts @@ -104,6 +104,7 @@ export enum FactoryImplementationNames { FACTORY_PLAIN_2_ETH_EMA = 'factory_plain_2_eth_ema', FACTORY_PLAIN_2_ETH_EMA2 = 'factory_plain_2_eth_ema2', FACTORY_PLAIN_2_OPTIMIZED = 'factory_plain_2_optimized', + FACTORY_PLAIN_2_CRV_EMA = 'factory_plain_2_crv_ema', FACTORY_PLAIN_3_BALANCES = 'factory_plain_3_balances', FACTORY_PLAIN_3_BASIC = 'factory_plain_3_basic', @@ -154,6 +155,7 @@ export type FactoryPoolImplementations = { basePoolAddress?: Address; customGasCost?: number; isStoreRateSupported?: boolean; + liquidityApiSlug?: string; }; export type CustomPoolConfig = { @@ -174,7 +176,7 @@ export type CustomPoolConfig = { }; export type DexParams = { - factoryAddress: string | null; + factoryAddresses: string[] | null; stateUpdatePeriodMs: number; factoryPoolImplementations: Record; customPools: Record; diff --git a/src/dex/generic-rfq/example-api.test.ts b/src/dex/generic-rfq/example-api.test.ts index 9fff282b0..81809dba4 100644 --- a/src/dex/generic-rfq/example-api.test.ts +++ b/src/dex/generic-rfq/example-api.test.ts @@ -157,7 +157,7 @@ export const startTestServer = (account: ethers.Wallet) => { new BigNumber(_prices.asks![0][0]), ); } else { - const reversedPrices = _prices.bids!.map(price => + const reversedPrices = _prices.asks!.map(price => reversePrice([new BigNumber(price[0]), new BigNumber(price[1])]), ); value = new BigNumber(payload.makerAmount).times( diff --git a/src/dex/generic-rfq/generic-rfq.ts b/src/dex/generic-rfq/generic-rfq.ts index 389a9a4a3..c6c1584b1 100644 --- a/src/dex/generic-rfq/generic-rfq.ts +++ b/src/dex/generic-rfq/generic-rfq.ts @@ -6,6 +6,7 @@ import { PreprocessTransactionOptions, Config, PoolLiquidity, + Address, } from '../../types'; import { Network, SwapSide } from '../../constants'; import { IDexHelper } from '../../dex-helper'; @@ -61,17 +62,19 @@ export class GenericRFQ extends ParaSwapLimitOrders { return; } + getIdentifier(srcToken: Address, destToken: Address) { + // Keep only destination token in order to prevent taping into the same market maker liquidity during same swap (double spending) + return `${this.dexKey}_${destToken}`.toLowerCase(); + } + async getPoolIdentifiers( srcToken: Token, destToken: Token, side: SwapSide, blockNumber: number, ): Promise { - const _srcToken = this.dexHelper.config.wrapETH(srcToken); const _destToken = this.dexHelper.config.wrapETH(destToken); - return [ - `${this.dexKey}_${_srcToken.address}_${_destToken.address}`.toLowerCase(), - ]; + return [this.getIdentifier(srcToken.address, _destToken.address)]; } calcOutsFromAmounts( @@ -140,6 +143,10 @@ export class GenericRFQ extends ParaSwapLimitOrders { _destToken.address, ); + if (!limitPools?.includes(expectedIdentifier)) { + return null; + } + const rates = await this.rateFetcher.getOrderPrice( _srcToken, _destToken, @@ -203,6 +210,7 @@ export class GenericRFQ extends ParaSwapLimitOrders { : overOrder(optimalSwapExchange.destAmount, 1), side, options.txOrigin, + options.partner, ); const expiryAsBigInt = BigInt(order.order.expiry); diff --git a/src/dex/generic-rfq/rate-fetcher.ts b/src/dex/generic-rfq/rate-fetcher.ts index 64442d157..f5760c532 100644 --- a/src/dex/generic-rfq/rate-fetcher.ts +++ b/src/dex/generic-rfq/rate-fetcher.ts @@ -226,7 +226,7 @@ export class RateFetcher { private handleRatesResponse(resp: RatesResponse) { const pairs = this.pairs; - if(isEmpty(pairs)) return; + if (isEmpty(pairs)) return; Object.keys(resp.prices).forEach(pairName => { const pair = pairs[pairName]; @@ -239,7 +239,7 @@ export class RateFetcher { return; } - if(isEmpty(this.tokens)) return; + if (isEmpty(this.tokens)) return; const baseToken = this.tokens[pair.base]; const quoteToken = this.tokens[pair.quote]; @@ -376,6 +376,7 @@ export class RateFetcher { srcAmount: string, side: SwapSide, userAddress: Address, + partner?: string, ): Promise { const srcToken = this.dexHelper.config.wrapETH(_srcToken); const destToken = this.dexHelper.config.wrapETH(_destToken); @@ -390,6 +391,7 @@ export class RateFetcher { makerAmount: side === SwapSide.BUY ? srcAmount : undefined, takerAmount: side === SwapSide.SELL ? srcAmount : undefined, userAddress, + partner, }; try { diff --git a/src/dex/generic-rfq/types.ts b/src/dex/generic-rfq/types.ts index 1ded4dc7a..dc2d3fcf2 100644 --- a/src/dex/generic-rfq/types.ts +++ b/src/dex/generic-rfq/types.ts @@ -84,6 +84,7 @@ export type RFQPayload = { makerAmount?: string; takerAmount?: string; userAddress: Address; + partner?: string; }; export type AugustusOrderWithStringAndSignature = AugustusOrderWithString & { diff --git a/src/dex/hashflow/hashflow-e2e.test.ts b/src/dex/hashflow/hashflow-e2e.test.ts index 0ccd6797a..abd15126f 100644 --- a/src/dex/hashflow/hashflow-e2e.test.ts +++ b/src/dex/hashflow/hashflow-e2e.test.ts @@ -11,6 +11,9 @@ import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; +// Give time for rate fetcher to fill the cache +const sleepMs = 3000; + function testForNetwork( network: Network, dexKey: string, @@ -58,6 +61,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { @@ -71,6 +79,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); } else { @@ -85,6 +98,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { @@ -98,6 +116,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { @@ -111,6 +134,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { @@ -124,6 +152,11 @@ function testForNetwork( contractMethod, network, provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, ); }); } @@ -163,9 +196,9 @@ describe('Hashflow E2E', () => { const tokenASymbol: string = 'USDC'; const tokenBSymbol: string = 'DAI'; - const tokenAAmount: string = '100000000'; - const tokenBAmount: string = '100000000000000000000'; - const nativeTokenAmount = '1000000000000000000'; + const tokenAAmount: string = '1000000000'; + const tokenBAmount: string = '1000000000000000000000'; + const nativeTokenAmount = '100000000000000000000'; testForNetwork( network, diff --git a/src/dex/index.ts b/src/dex/index.ts index 539d58fb9..68ba40cc4 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -42,13 +42,17 @@ import { WooFiV2 } from './woo-fi-v2/woo-fi-v2'; import { ParaSwapLimitOrders } from './paraswap-limit-orders/paraswap-limit-orders'; import { AugustusRFQOrder } from './augustus-rfq'; import { Solidly } from './solidly/solidly'; +import { Ramses } from './solidly/forks-override/ramses'; import { Thena } from './solidly/forks-override/thena'; import { Chronos } from './solidly/forks-override/chronos'; import { Velodrome } from './solidly/forks-override/velodrome'; +import { VelodromeV2 } from './solidly/forks-override/velodromeV2'; +import { Aerodrome } from './solidly/forks-override/aerodrome'; import { SpiritSwapV2 } from './solidly/forks-override/spiritSwapV2'; import { Synthetix } from './synthetix/synthetix'; import { Cone } from './solidly/forks-override/cone'; import { SoliSnek } from './solidly/forks-override/solisnek'; +import { Equalizer } from './solidly/forks-override/equalizer'; import { BalancerV1 } from './balancer-v1/balancer-v1'; import { balancerV1Merge } from './balancer-v1/optimizer'; import { CurveV1 } from './curve-v1/curve-v1'; @@ -70,6 +74,8 @@ 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'; +import { NomiswapV2 } from './uniswap-v2/nomiswap-v2'; import { Dexalot } from './dexalot/dexalot'; const LegacyDexes = [ @@ -126,11 +132,15 @@ const Dexes = [ Solidly, SolidlyEthereum, SpiritSwapV2, + Ramses, Thena, Chronos, Velodrome, + VelodromeV2, + Aerodrome, Cone, SoliSnek, + Equalizer, Synthetix, CurveV1Factory, SwaapV1, @@ -139,6 +149,8 @@ const Dexes = [ MaverickV1, Camelot, SwaapV2, + QuickPerps, + NomiswapV2, ]; export type LegacyDexConstructor = new (dexHelper: IDexHelper) => IDexTxBuilder< diff --git a/src/dex/maverick-v1/config.ts b/src/dex/maverick-v1/config.ts index bd85811b8..6c4c2185f 100644 --- a/src/dex/maverick-v1/config.ts +++ b/src/dex/maverick-v1/config.ts @@ -14,6 +14,12 @@ export const MaverickV1Config: DexConfigMap = { routerAddress: '0x4a585e0f7c18e2c414221d6402652d5e0990e5f8', poolInspectorAddress: '0xaA5BF61a664109e959D69C38734d4EA7dF74e456', }, + [Network.BASE]: { + subgraphURL: + 'https://api.studio.thegraph.com/query/42519/maverick-base/version/latest', + routerAddress: '0x32AED3Bce901DA12ca8489788F3A99fCe1056e14', + poolInspectorAddress: '0x65A3AD03Be97619284bA7AA1E3Ca05638B9d6364', + }, }, }; @@ -22,4 +28,8 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'Adapter04', index: 2 }], [SwapSide.BUY]: [{ name: 'BuyAdapter', index: 8 }], }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 2 }], + [SwapSide.BUY]: [{ name: 'BaseBuyAdapter', index: 2 }], + }, }; diff --git a/src/dex/maverick-v1/maverick-v1-e2e.test.ts b/src/dex/maverick-v1/maverick-v1-e2e.test.ts index eebb6f57c..8f4c76a8e 100644 --- a/src/dex/maverick-v1/maverick-v1-e2e.test.ts +++ b/src/dex/maverick-v1/maverick-v1-e2e.test.ts @@ -11,10 +11,109 @@ import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, + slippage?: number | undefined, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ], + ], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + }); + }); + }), + ); + }); +} + describe('MaverickV1 E2E', () => { const dexKey = 'MaverickV1'; - describe('MaverickV1 MAINNET', () => { + describe('MAINNET', () => { const network = Network.MAINNET; const tokens = Tokens[network]; const holders = Holders[network]; @@ -121,4 +220,25 @@ describe('MaverickV1 E2E', () => { ); }); }); + + describe('BASE', () => { + const network = Network.BASE; + + const tokenASymbol: string = 'USDbC'; + const tokenBSymbol: string = 'MAV'; + + const tokenAAmount: string = '1111100000'; + const tokenBAmount: string = '1100000000000000000'; + const nativeTokenAmount = '100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); diff --git a/src/dex/maverick-v1/maverick-v1.ts b/src/dex/maverick-v1/maverick-v1.ts index 1dd608b32..b8d6b0779 100644 --- a/src/dex/maverick-v1/maverick-v1.ts +++ b/src/dex/maverick-v1/maverick-v1.ts @@ -78,6 +78,7 @@ export class MaverickV1 async setupEventPools(blockNumber: number) { const pools = await this.fetchAllSubgraphPools(); + await Promise.all( pools.map(async (pool: any) => { const eventPool = new MaverickV1EventPool( @@ -181,8 +182,17 @@ export class MaverickV1 getCalldataGasCost( poolPrices: PoolPrices, ): number | number[] { - // TODO: update if there is any payload in getAdapterParam - return CALLDATA_GAS_COST.DEX_NO_PAYLOAD; + const gasCost = CALLDATA_GAS_COST.DEX_NO_PAYLOAD; + + const arr = new Array(poolPrices.prices.length); + poolPrices.prices.forEach((p, index) => { + if (p == 0n) { + arr[index] = 0; + } else { + arr[index] = gasCost; + } + }); + return arr; } // Returns pool prices for amounts. diff --git a/src/dex/pancakeswap-v3/config.ts b/src/dex/pancakeswap-v3/config.ts index 43636ee06..00ae8a2ac 100644 --- a/src/dex/pancakeswap-v3/config.ts +++ b/src/dex/pancakeswap-v3/config.ts @@ -53,6 +53,21 @@ export const PancakeswapV3Config: DexConfigMap = { subgraphURL: 'https://api.studio.thegraph.com/query/45376/exchange-v3-arbitrum/version/latest', }, + [Network.BASE]: { + factory: '0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865', + deployer: '0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9', + quoter: '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997', + router: '0x1b81D678ffb9C0263b24A97847620C99d213eB14', + supportedFees: PANCAKE_SUPPORTED_FEES, + stateMulticall: '0xeBF40A40CA3D4310Bf53048F48e860656e1D7C81', + uniswapMulticall: '0x091e99cb1C49331a94dD62755D168E941AbD0693', + chunksCount: 10, + initRetryFrequency: 30, + initHash: + '0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2', + subgraphURL: + 'https://api.studio.thegraph.com/query/45376/exchange-v3-base/version/latest', + }, }, }; @@ -69,4 +84,8 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'ArbitrumAdapter01', index: 3 }], [SwapSide.BUY]: [{ name: 'ArbitrumBuyAdapter', index: 2 }], }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 1 }], + [SwapSide.BUY]: [{ name: 'BaseBuyAdapter', index: 1 }], + }, }; diff --git a/src/dex/pancakeswap-v3/pancakeswap-v3-e2e.test.ts b/src/dex/pancakeswap-v3/pancakeswap-v3-e2e.test.ts index 9be62cd7e..f3c61fdd3 100644 --- a/src/dex/pancakeswap-v3/pancakeswap-v3-e2e.test.ts +++ b/src/dex/pancakeswap-v3/pancakeswap-v3-e2e.test.ts @@ -11,6 +11,105 @@ import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, + slippage?: number | undefined, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ], + ], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + }); + }); + }), + ); + }); +} + describe('PancakeswapV3 E2E', () => { const dexKey = 'PancakeswapV3'; @@ -307,4 +406,25 @@ describe('PancakeswapV3 E2E', () => { }), ); }); + + describe('PancakeswapV3 Base', () => { + const network = Network.BASE; + + const tokenASymbol: string = 'USDbC'; + const tokenBSymbol: string = 'DAI'; + + const tokenAAmount: string = '11111000000'; + const tokenBAmount: string = '210000000000000000'; + const nativeTokenAmount = '110000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); diff --git a/src/dex/quick-perps/config.ts b/src/dex/quick-perps/config.ts new file mode 100644 index 000000000..1f44ef1bf --- /dev/null +++ b/src/dex/quick-perps/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/quick-perps/fast-price-feed.ts b/src/dex/quick-perps/fast-price-feed.ts new file mode 100644 index 000000000..0d554b47d --- /dev/null +++ b/src/dex/quick-perps/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/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< + 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, + _maximize: boolean, + ) { + const state = this.lens.get()(_state); + + const timestamp = Math.floor(Date.now() / 1000); + + if (timestamp > state.lastUpdatedAt + this.maxPriceUpdateDelay) { + if (_maximize) { + 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 (_maximize) { + 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 (_maximize) { + 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/quick-perps/pool.ts b/src/dex/quick-perps/pool.ts new file mode 100644 index 000000000..8b922b236 --- /dev/null +++ b/src/dex/quick-perps/pool.ts @@ -0,0 +1,372 @@ +import { DeepReadonly } from 'ts-essentials'; +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 { Api3FeedSubscriber } from '../../lib/api3-feed'; +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/quick-perps/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, + config: PoolConfig, + ) { + const api3ServerV1Map = Object.entries(config.api3ServerV1).reduce( + ( + acc: { [address: string]: Api3FeedSubscriber }, + [key, value], + ) => { + acc[key] = new Api3FeedSubscriber( + value.proxy, + value.api3ServerV1, + value.dataFeedId, + lens>().primaryPrices[key], + dexHelper.getLogger( + `${key} Api3FeedSubscriber 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, + api3ServerV1Map, + 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(api3ServerV1Map), 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( + 'priceFeedProxies', + [t], + ), + target: dexParams.priceFeed, + }; + }); + const priceFeedResult = ( + await multiContract.methods + .aggregate(getPriceFeedCalldata) + .call({}, blockNumber) + ).returnData; + const priceFeeds = priceFeedResult.map((p: any) => + VaultPriceFeed.interface + .decodeFunctionResult('priceFeedProxies', 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 api3ServerAddressCallData = + Api3FeedSubscriber.getApi3ServerV1MultiCallInput(priceFeed); + const dataFeedIdCallData = Api3FeedSubscriber.getDataFeedId(priceFeed); + multiCallData.push(...[api3ServerAddressCallData, dataFeedIdCallData]); + multicallSlices.push([i, i + 2]); + i += 2; + } + + 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 api3ServerV1: { + [address: string]: { + proxy: Address; + api3ServerV1: Address; + dataFeedId: string; + }; + } = {}; + for (let token of tokens) { + const [api3ServerAddressRes, dataFeedIdRes] = configResults.slice( + ...multicallSlices.shift()!, + ); + const serverV1Address = + Api3FeedSubscriber.decodeApi3ServerV1Result(api3ServerAddressRes); + const dataFeedId = Api3FeedSubscriber.decodeDataFeedId(dataFeedIdRes); + api3ServerV1[token] = { + proxy: priceFeeds.shift(), + api3ServerV1: serverV1Address, + dataFeedId, + }; + } + + 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, + api3ServerV1, + }; + } +} diff --git a/src/dex/quick-perps/quick-perps-e2e.test.ts b/src/dex/quick-perps/quick-perps-e2e.test.ts new file mode 100644 index 000000000..6d42bdf0d --- /dev/null +++ b/src/dex/quick-perps/quick-perps-e2e.test.ts @@ -0,0 +1,65 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { testE2E } from '../../../tests/utils-e2e'; +import { Tokens, Holders } 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 zkEVM', () => { + const network = Network.ZKEVM; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const tokenASymbol: string = 'ETH'; + const tokenBSymbol: string = 'MATIC'; + + const tokenAAmount: string = '100000000000000000'; + const tokenBAmount: string = '10000000000000000000'; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it('ETH -> MATIC', async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + undefined, + undefined, + true, + ); + }); + }); + }), + ); + }); +}); diff --git a/src/dex/quick-perps/quick-perps-events.test.ts b/src/dex/quick-perps/quick-perps-events.test.ts new file mode 100644 index 000000000..f3d4999e2 --- /dev/null +++ b/src/dex/quick-perps/quick-perps-events.test.ts @@ -0,0 +1,101 @@ +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 { PoolConfig, PoolState } from './types'; + +jest.setTimeout(50 * 1000); +const dexKey = 'QuickPerps'; +const network = Network.ZKEVM; +const params = QuickPerpsConfig[dexKey][network]; +const dexHelper = new DummyDexHelper(network); +const logger = dexHelper.getLogger(dexKey); + +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, + }, +}); + +function compareState(state: PoolState, expectedState: PoolState) { + expect(stateWithoutTimestamp(state)).toEqual( + stateWithoutTimestamp(expectedState), + ); +} + +describe('QuickPerps Event', function () { + const blockNumbers: { [eventName: string]: number[] } = { + IncreaseUsdqAmount: [ + 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, + 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: [4958541, 4959994, 4959998, 4960452, 4960452], + PriceUpdate: [ + 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, + ); + }); + 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, + (_blockNumber: number) => + fetchPoolState(quickPerpsPool, _blockNumber), + blockNumber, + `${dexKey}_${params.vault}`, + dexHelper.provider, + compareState, + ); + }); + }); + }); + }); +}); diff --git a/src/dex/quick-perps/quick-perps-integration.test.ts b/src/dex/quick-perps/quick-perps-integration.test.ts new file mode 100644 index 000000000..e45db84c5 --- /dev/null +++ b/src/dex/quick-perps/quick-perps-integration.test.ts @@ -0,0 +1,153 @@ +/* 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 { QuickPerps } from './quick-perps'; +import { QuickPerpsConfig } from './config'; +import { + checkPoolPrices, + checkPoolsLiquidity, + checkConstantPoolPrices, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import ReaderABI from '../../abi/quick-perps/reader.json'; + +const network = Network.ZKEVM; +const dexKey = 'QuickPerps'; +const params = QuickPerpsConfig[dexKey][network]; +const readerInterface = new Interface(ReaderABI); +const readerAddress = '0xf1CFB75854DE535475B88Bb6FBad317eea98c0F9'; + +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(); + 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); + } + }); +} + +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/quick-perps.ts b/src/dex/quick-perps/quick-perps.ts new file mode 100644 index 000000000..ac0e4e0c3 --- /dev/null +++ b/src/dex/quick-perps/quick-perps.ts @@ -0,0 +1,292 @@ +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); + } + + 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, + 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/quick-perps/types.ts b/src/dex/quick-perps/types.ts new file mode 100644 index 000000000..fc0326e10 --- /dev/null +++ b/src/dex/quick-perps/types.ts @@ -0,0 +1,97 @@ +import { Address } from '../../types'; +import { Api3FeedSubscriberState } from '../../lib/api3-feed'; + +export type PoolState = { + primaryPrices: { [poolAddress: string]: Api3FeedSubscriberState }; + 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 = { + isSecondaryPriceEnabled: boolean; + strictStableTokens: { [address: string]: boolean }; + spreadBasisPoints: { [address: string]: bigint }; + adjustmentBasisPoints: { [address: string]: bigint }; + isAdjustmentAdditive: { [address: string]: boolean }; + priceDecimals: { [address: string]: number }; + maxStrictPriceDeviation: bigint; +}; + +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; + api3ServerV1: { + [address: string]: { + proxy: Address; + api3ServerV1: Address; + dataFeedId: string; + }; + }; +}; diff --git a/src/dex/quick-perps/usdq.ts b/src/dex/quick-perps/usdq.ts new file mode 100644 index 000000000..da09397ff --- /dev/null +++ b/src/dex/quick-perps/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/quick-perps/vault-price-feed.ts b/src/dex/quick-perps/vault-price-feed.ts new file mode 100644 index 000000000..eeff27b08 --- /dev/null +++ b/src/dex/quick-perps/vault-price-feed.ts @@ -0,0 +1,322 @@ +import { Interface } from '@ethersproject/abi'; +import { Address, MultiCallInput, MultiCallOutput } from '../../types'; +import { VaultPriceFeedConfig } from './types'; +import { FastPriceFeed } from './fast-price-feed'; +import VaultPriceFeedAbi from '../../abi/quick-perps/vault-price-feed.json'; +import { DeepReadonly } from 'ts-essentials'; +import { Api3FeedSubscriber } from '../../lib/api3-feed'; + +export class VaultPriceFeed { + BASIS_POINTS_DIVISOR = 10000n; + PRICE_PRECISION = 10n ** 30n; + ONE_USD = this.PRICE_PRECISION; + + static interface = new Interface(VaultPriceFeedAbi); + + 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; + + constructor( + config: VaultPriceFeedConfig, + protected primaryPrices: { [token: string]: Api3FeedSubscriber }, + protected secondaryPrice: FastPriceFeed, + ) { + 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; + } + + getPrice( + state: DeepReadonly, + _token: Address, + _maximize: boolean, + _includeAmmPrice: boolean, + _useSwapPricing: boolean, + ): bigint { + let price = this.getPriceV1(state, _token, _maximize, _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; + } + + getPriceV1( + state: DeepReadonly, + _token: Address, + _maximize: boolean, + _includeAmmPrice: boolean, + ): bigint { + let price = this.getPrimaryPrice(state, _token, _maximize); + + if (this.isSecondaryPriceEnabled) { + price = this.getSecondaryPrice(state, _token, price, _maximize); + } + + 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 _maximize and price is e.g. 1.02, return 1.02 + if (_maximize && price > this.ONE_USD) { + return price; + } + + // if !_maximize and price is e.g. 0.98, return 0.98 + if (!_maximize && price < this.ONE_USD) { + return price; + } + + return this.ONE_USD; + } + + const _spreadBasisPoints = this.spreadBasisPoints[_token]; + + if (_maximize) { + 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, developers should disable the dex or complete the implementation', + ); + } + + getPrimaryPrice( + state: DeepReadonly, + _token: Address, + _maximize: 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 (_maximize && p > price) { + // price = p; + // continue; + // } + + // if (!_maximize && p < price) { + // price = p; + // } + // } + 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'); + // normalize price precision + const _priceDecimals = this.priceDecimals[_token]; + return (price * this.PRICE_PRECISION) / BigInt(10 ** _priceDecimals); + } + + getSecondaryPrice( + state: DeepReadonly, + _token: Address, + _referencePrice: bigint, + _maximize: boolean, + ): bigint { + return this.secondaryPrice.getPrice( + state, + _token, + _referencePrice, + _maximize, + ); + } + + static getConfigMulticallInputs( + vaultPriceFeedAddress: Address, + tokenAddresses: Address[], + ): MultiCallInput[] { + return [ + { + 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', + ), + }, + ]; + } + + static getConfig( + multicallOutputs: MultiCallOutput[], + tokenAddress: Address[], + ): VaultPriceFeedConfig { + let i = 0; + return { + 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(), + ), + }; + } +} diff --git a/src/dex/quick-perps/vault-utils.ts b/src/dex/quick-perps/vault-utils.ts new file mode 100644 index 000000000..1ff64466e --- /dev/null +++ b/src/dex/quick-perps/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/quick-perps/vault.ts b/src/dex/quick-perps/vault.ts new file mode 100644 index 000000000..a5832c76c --- /dev/null +++ b/src/dex/quick-perps/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/quick-perps/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/solidly/config.ts b/src/dex/solidly/config.ts index f8e364db3..c4dccd96b 100644 --- a/src/dex/solidly/config.ts +++ b/src/dex/solidly/config.ts @@ -72,6 +72,28 @@ export const SolidlyConfig: DexConfigMap = { feeCode: 2, }, }, + VelodromeV2: { + [Network.OPTIMISM]: { + // There is no subgraph for VelodromeV2 + factoryAddress: '0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a', + router: '0xa2f581b012E0f2dcCDe86fCbfb529f4aC5dD4983', + initCode: + '0x1a8f01f7eab324003d9388f229ea17991eee9c9d14586f429799f3656790eba0', + poolGasCost: 180 * 1000, + feeCode: 0, + }, + }, + Aerodrome: { + [Network.BASE]: { + // There is no subgraph for Aerodrome + factoryAddress: '0x420DD381b31aEf6683db6B902084cB0FFECe40Da', + router: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + initCode: + '0x1a8f01f7eab324003d9388f229ea17991eee9c9d14586f429799f3656790eba0', + poolGasCost: 180 * 1000, + feeCode: 0, + }, + }, Cone: { [Network.BSC]: { subgraphURL: 'https://api.thegraph.com/subgraphs/name/cone-exchange/cone', @@ -125,6 +147,37 @@ export const SolidlyConfig: DexConfigMap = { feeCode: 0, }, }, + Ramses: { + [Network.ARBITRUM]: { + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/ramsesexchange/api-subgraph', + factoryAddress: '0xAAA20D08e59F6561f242b08513D36266C5A29415', + router: '0xb2634B3CBc1E401AB3C2743DB44d459C5c9aA662', + initCode: + '0x1565b129f2d1790f12d45301b9b084335626f0c92410bc43130763b69971135d', + poolGasCost: 180 * 1000, + feeCode: 0, + }, + }, + Equalizer: { + [Network.FANTOM]: { + factoryAddress: '0xc6366EFD0AF1d09171fe0EBF32c7943BB310832a', + router: '0x93d2611EB8b85bE4FDEa9D94Ce9913D90072eC0f', + initCode: + '0x02ada2a0163cd4f7e0f0c9805f5230716a95b174140e4c84c14883de216cc6a3', + feeCode: 0, + poolGasCost: 180 * 1000, + }, + [Network.BASE]: { + factoryAddress: '0xed8db60acc29e14bc867a497d94ca6e3ceb5ec04', + router: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + initCode: + '0x7ba31a081e879b8e7f06d4e8bf5ee26b5c2680669c5701f4cdbdcde51727b275', + feeCode: 0, + feeFactor: 1e18, + poolGasCost: 180 * 1000, + }, + }, }; export const Adapters: Record = { @@ -132,7 +185,7 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'PolygonAdapter02', index: 3 }], // dystopia }, [Network.FANTOM]: { - [SwapSide.SELL]: [{ name: 'FantomAdapter01', index: 10 }], // solidly + spiritSwapV2 + [SwapSide.SELL]: [{ name: 'FantomAdapter01', index: 10 }], // solidly, spiritSwapV2, equalizer }, [Network.OPTIMISM]: { [SwapSide.SELL]: [{ name: 'OptimismAdapter01', index: 8 }], // velodrome @@ -147,6 +200,9 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'AvalancheAdapter02', index: 3 }], // solisnek }, [Network.ARBITRUM]: { - [SwapSide.SELL]: [{ name: 'ArbitrumAdapter02', index: 1 }], // chronos + [SwapSide.SELL]: [{ name: 'ArbitrumAdapter02', index: 1 }], // chronos, ramses }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 3 }], // aerodrome, equalizer + } }; diff --git a/src/dex/solidly/forks-override/aerodrome.ts b/src/dex/solidly/forks-override/aerodrome.ts new file mode 100644 index 000000000..ae09c0930 --- /dev/null +++ b/src/dex/solidly/forks-override/aerodrome.ts @@ -0,0 +1,57 @@ +import { VelodromeV2 } from './velodromeV2'; +import { Network, NULL_ADDRESS } from '../../../constants'; +import { getDexKeysWithNetwork } from '../../../utils'; +import _ from 'lodash'; +import { SolidlyConfig } from '../config'; +import { Token } from '../../../types'; +import { IDexHelper } from '../../../dex-helper'; +import AerodromeFactoryABI from '../../../abi/aerodrome/aerodrome-pool-factory.json'; + +export class Aerodrome extends VelodromeV2 { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyConfig, ['Aerodrome'])); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + ) { + super( + network, + dexKey, + dexHelper, + ); + + this.factory = new dexHelper.web3Provider.eth.Contract( + AerodromeFactoryABI as any, + SolidlyConfig[dexKey][network].factoryAddress, + ); + } + + async findSolidlyPair(from: Token, to: Token, stable: boolean) { + if (from.address.toLowerCase() === to.address.toLowerCase()) return null; + const [token0, token1] = + from.address.toLowerCase() < to.address.toLowerCase() + ? [from, to] + : [to, from]; + + const typePostfix = this.poolPostfix(stable); + const key = `${token0.address.toLowerCase()}-${token1.address.toLowerCase()}-${typePostfix}`; + let pair = this.pairs[key]; + if (pair) return pair; + + let exchange = await this.factory.methods + // Solidly has additional boolean parameter "StablePool" + // At first we look for uniswap-like volatile pool + .getPool(token0.address, token1.address, stable) + .call(); + + if (exchange === NULL_ADDRESS) { + pair = { token0, token1, stable }; + } else { + pair = { token0, token1, exchange, stable }; + } + this.pairs[key] = pair; + return pair; + } +} diff --git a/src/dex/solidly/forks-override/equalizer.ts b/src/dex/solidly/forks-override/equalizer.ts new file mode 100644 index 000000000..c72a61068 --- /dev/null +++ b/src/dex/solidly/forks-override/equalizer.ts @@ -0,0 +1,58 @@ +import { Solidly } from '../solidly'; +import { SolidlyPair } from '../types'; +import { Network } from '../../../constants'; +import { IDexHelper } from '../../../dex-helper'; +import { Interface } from '@ethersproject/abi'; +import { getDexKeysWithNetwork } from '../../../utils'; +import { SolidlyConfig } from '../config'; +import _ from 'lodash'; + +const EqualizerFactoryABI = [ + { + inputs: [{ internalType: 'address', name: '_pair', type: 'address' }], + name: 'getRealFee', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +]; + +const equalizerFactoryIface = new Interface(EqualizerFactoryABI); + +export class Equalizer extends Solidly { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyConfig, ['Equalizer'])); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + ) { + super( + network, + dexKey, + dexHelper, + true, // dynamic fees + ); + } + + protected getFeesMultiCallData(pair: SolidlyPair) { + const callEntry = { + target: this.factoryAddress, + callData: equalizerFactoryIface.encodeFunctionData('getRealFee', [ + pair.exchange, + ]), + }; + const callDecoder = (values: any[]) => + parseInt( + equalizerFactoryIface + .decodeFunctionResult('getRealFee', values)[0] + .toString(), + ); + + return { + callEntry, + callDecoder, + }; + } +} diff --git a/src/dex/solidly/forks-override/ramses.ts b/src/dex/solidly/forks-override/ramses.ts new file mode 100644 index 000000000..99c91e4dd --- /dev/null +++ b/src/dex/solidly/forks-override/ramses.ts @@ -0,0 +1,253 @@ +import { Network, SUBGRAPH_TIMEOUT } from '../../../constants'; +import { getDexKeysWithNetwork } from '../../../utils'; +import { SolidlyConfig } from '../config'; +import _ from 'lodash'; +import { Address, PoolLiquidity } from '../../../types'; +import BigNumber from 'bignumber.js'; +import { Solidly } from '../solidly'; +import { IDexHelper } from '../../../dex-helper'; +import { SolidlyPair } from '../types'; +import { Interface } from '@ethersproject/abi'; + +const RamsesFactoryABI = [ + { + inputs: [{ internalType: 'address', name: '', type: 'address' }], + name: 'pairFee', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +]; + +const ramsesFactoryIface = new Interface(RamsesFactoryABI); + +export type RamsesSubgraphPool = { + id: string; + isStable: boolean; + token0: string; + reserve0: string; + reserve1: string; + token1: string; +}; + +export type RamsesSubgraphToken = { + id: string; + decimals: string; +}; + +export class Ramses extends Solidly { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyConfig, ['Ramses'])); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + ) { + super( + network, + dexKey, + dexHelper, + true, // dynamic fees + ); + } + + protected getFeesMultiCallData(pair: SolidlyPair) { + const callEntry = { + target: this.factoryAddress, + callData: ramsesFactoryIface.encodeFunctionData('pairFee', [ + pair.exchange, + ]), + }; + const callDecoder = (values: any[]) => + parseInt( + ramsesFactoryIface + .decodeFunctionResult('pairFee', values)[0] + .toString(), + ); + + return { + callEntry, + callDecoder, + }; + } + + async getTopPoolsForToken( + tokenAddress: Address, + count: number, + ): Promise { + if (!this.subgraphURL) return []; + + const query = `query ($token: Bytes!, $count: Int) { + pools0: pairs(first: $count, orderBy: reserve0, orderDirection: desc, where: {token0: $token, reserve0_gt: 1, reserve1_gt: 1}) { + id + isStable + token0 + token1 + reserve0, + reserve1, + } + pools1: pairs(first: $count, orderBy: reserve1, orderDirection: desc, where: {token1: $token, reserve0_gt: 1, reserve1_gt: 1}) { + id + isStable + token0 + token1 + reserve0, + reserve1, + } + }`; + + const { data } = await this.dexHelper.httpRequest.post( + this.subgraphURL, + { + query, + variables: { token: tokenAddress.toLowerCase(), count }, + }, + SUBGRAPH_TIMEOUT, + ); + + if (!(data && data.pools0 && data.pools1)) + throw new Error("Couldn't fetch the pools from the subgraph"); + + const tokenIds = _.uniq( + [] + .concat(data.pools0, data.pools1) + .map((pool: RamsesSubgraphPool) => [pool.token0, pool.token1]) + .flat(), + ); + + const tokensQuery = ` + query ($tokenIds: [String!]) { + tokens(where: {id_in: $tokenIds}) { + id + decimals + } + }`; + + const { data: tokensData } = await this.dexHelper.httpRequest.post( + this.subgraphURL, + { + query: tokensQuery, + variables: { tokenIds }, + }, + SUBGRAPH_TIMEOUT, + ); + + const pools0 = await this.prepareSubgraphPools( + tokensData.tokens, + data.pools0, + ( + pool, + { address1, decimals1, liquidityUSDToken0, liquidityUSDToken1 }, + ) => ({ + exchange: this.dexKey, + stable: pool.isStable, + address: pool.id.toLowerCase(), + connectorTokens: [ + { + address: address1, + decimals: decimals1, + }, + ], + liquidityUSD: liquidityUSDToken0 + liquidityUSDToken1, + }), + ); + + const pools1 = await this.prepareSubgraphPools( + tokensData.tokens, + data.pools1, + ( + pool, + { address0, decimals0, liquidityUSDToken0, liquidityUSDToken1 }, + ) => ({ + exchange: this.dexKey, + stable: pool.isStable, + address: pool.id.toLowerCase(), + connectorTokens: [ + { + address: address0, + decimals: decimals0, + }, + ], + liquidityUSD: liquidityUSDToken0 + liquidityUSDToken1, + }), + ); + + return _.slice( + _.sortBy(_.concat(pools0, pools1), [pool => -1 * pool.liquidityUSD]), + 0, + count, + ); + } + + protected async prepareSubgraphPools( + tokens: RamsesSubgraphToken[], + pools: RamsesSubgraphPool[], + iterator: ( + pool: RamsesSubgraphPool, + { + address0, + address1, + decimals0, + decimals1, + reserve0, + reserve1, + liquidityUSDToken0, + liquidityUSDToken1, + }: { + address0: string; + address1: string; + decimals0: number; + decimals1: number; + reserve0: bigint; + reserve1: bigint; + liquidityUSDToken0: number; + liquidityUSDToken1: number; + }, + ) => PoolLiquidity, + ): Promise { + return Promise.all( + pools.map(async (pool: RamsesSubgraphPool) => { + const address0 = pool.token0.toLowerCase(); + const address1 = pool.token1.toLowerCase(); + + const decimals0 = parseInt( + tokens.find(t => t.id === address0)!.decimals, + ); + const decimals1 = parseInt( + tokens.find(t => t.id === address1)!.decimals, + ); + + const reserve0 = BigInt(new BigNumber(pool.reserve0).toFixed()); + const reserve1 = BigInt(new BigNumber(pool.reserve1).toFixed()); + + const liquidityUSDToken0 = await this.dexHelper.getTokenUSDPrice( + { + address: address0, + decimals: decimals0, + }, + reserve0, + ); + + const liquidityUSDToken1 = await this.dexHelper.getTokenUSDPrice( + { + address: address1, + decimals: decimals1, + }, + reserve1, + ); + + return iterator(pool, { + address0, + address1, + decimals0, + decimals1, + reserve0, + reserve1, + liquidityUSDToken0, + liquidityUSDToken1, + }); + }), + ); + } +} diff --git a/src/dex/solidly/forks-override/velodromeV2.ts b/src/dex/solidly/forks-override/velodromeV2.ts new file mode 100644 index 000000000..c0729832d --- /dev/null +++ b/src/dex/solidly/forks-override/velodromeV2.ts @@ -0,0 +1,63 @@ +import { Solidly } from '../solidly'; +import { Network } from '../../../constants'; +import { getDexKeysWithNetwork } from '../../../utils'; +import { SolidlyConfig } from '../config'; +import _ from 'lodash'; +import { Address, PoolLiquidity } from '../../../types'; +import { SolidlyPair } from '../types'; +import { Interface } from '@ethersproject/abi'; +import { IDexHelper } from '../../../dex-helper'; + +const VelodromeV2FactoryABI = [ + { + inputs: [ + { internalType: 'address', name: 'pool', type: 'address' }, + { internalType: 'bool', name: '_stable', type: 'bool' }, + ], + name: 'getFee', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +]; + +const velodromeV2FactoryIface = new Interface(VelodromeV2FactoryABI); + +export class VelodromeV2 extends Solidly { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyConfig, ['VelodromeV2'])); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + ) { + super( + network, + dexKey, + dexHelper, + true, // dynamic fees + ); + } + + protected getFeesMultiCallData(pair: SolidlyPair) { + const callEntry = { + target: this.factoryAddress, + callData: velodromeV2FactoryIface.encodeFunctionData('getFee', [ + pair.exchange, + pair.stable, + ]), + }; + const callDecoder = (values: any[]) => + parseInt( + velodromeV2FactoryIface + .decodeFunctionResult('getFee', values)[0] + .toString(), + ); + + return { + callEntry, + callDecoder, + }; + } +} diff --git a/src/dex/solidly/solidly-e2e.test.ts b/src/dex/solidly/solidly-e2e.test.ts index 338f3e1d3..79bfc833a 100644 --- a/src/dex/solidly/solidly-e2e.test.ts +++ b/src/dex/solidly/solidly-e2e.test.ts @@ -2,11 +2,95 @@ import dotenv from 'dotenv'; dotenv.config(); import { testE2E } from '../../../tests/utils-e2e'; -import { Tokens, Holders } from '../../../tests/constants-e2e'; +import { + Tokens, + Holders, + NativeTokenSymbols, +} from '../../../tests/constants-e2e'; import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + // TODO: Add any direct swap contractMethod name if it exists + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); +} + describe('Solidly E2E', () => { describe('Fantom', () => { const network = Network.FANTOM; @@ -309,6 +393,28 @@ describe('Solidly E2E', () => { }); }); }); + + describe('Equalizer', () => { + const dexKey = 'Equalizer'; + const network = Network.FANTOM; + + const tokenASymbol: string = 'FUSDT'; + const tokenBSymbol: string = 'USDC'; + + const tokenAAmount: string = '111110'; + const tokenBAmount: string = '100000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); describe('Mainnet', () => { @@ -689,6 +795,29 @@ describe('Solidly E2E', () => { }); }); }); + + describe('VelodromeV2', () => { + const dexKey = 'VelodromeV2'; + + const network = Network.OPTIMISM; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '111110000'; + const tokenBAmount: string = '1100000000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); describe('BSC', () => { @@ -1179,5 +1308,188 @@ describe('Solidly E2E', () => { }), ); }); + + describe('Ramses', () => { + const dexKey = 'Ramses'; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + const pairs: { name: string; sellAmount: string }[][] = [ + [ + { + name: 'ETH', + sellAmount: '10000000000000', + }, + { + name: 'USDCe', + sellAmount: '100000000', + }, + ], + [ + { + name: 'WETH', + sellAmount: '10000000000000', + }, + { + name: 'USDCe', + sellAmount: '100000000', + }, + ], + [ + { + name: 'USDT', + sellAmount: '100000000', + }, + { + name: 'USDCe', + sellAmount: '100000000', + }, + ], + [ + { + name: 'USDCe', + sellAmount: '500000', + }, + { + name: 'DAI', + sellAmount: '1000000000000000000', + }, + ], + ]; + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + pairs.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].name} -> ${pair[1].name}`, async () => { + await testE2E( + tokens[pair[0].name], + tokens[pair[1].name], + holders[pair[0].name], + pair[0].sellAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${pair[1].name} -> ${pair[0].name}`, async () => { + await testE2E( + tokens[pair[1].name], + tokens[pair[0].name], + holders[pair[1].name], + pair[1].sellAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }); + }), + ); + }); + }); + + describe('Base', () => { + const network = Network.BASE; + + describe('Aerodrome', () => { + const dexKey = 'Aerodrome'; + const tokenASymbol: string = 'USDbC'; + const tokenBSymbol: string = 'DAI'; + + const tokenAAmount: string = '1111100000'; + const tokenBAmount: string = '100000000000000000'; + const nativeTokenAmount = '100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('Equalizer', () => { + const dexKey = 'Equalizer'; + const network = Network.BASE; + + const tokenASymbol: string = 'USDbC'; + const tokenAAmount: string = '1111100000'; + const nativeTokenAmount = '110000000000000000'; + + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + // TODO: Add any direct swap contractMethod name if it exists + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); }); }); diff --git a/src/dex/solidly/solidly-integration.test.ts b/src/dex/solidly/solidly-integration.test.ts index 06bc8243a..73fce1d7d 100644 --- a/src/dex/solidly/solidly-integration.test.ts +++ b/src/dex/solidly/solidly-integration.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import dotenv from 'dotenv'; dotenv.config(); @@ -12,6 +13,10 @@ import solidlyPairABI from '../../abi/solidly/SolidlyPair.json'; import { SpiritSwapV2 } from './forks-override/spiritSwapV2'; import { Cone } from './forks-override/cone'; import { Chronos } from './forks-override/chronos'; +import { Ramses } from './forks-override/ramses'; +import * as util from 'util'; +import { VelodromeV2 } from './forks-override/velodromeV2'; +import { Equalizer } from './forks-override/equalizer'; const amounts18 = [0n, BI_POWS[18], 2000000000000000000n]; const amounts6 = [0n, BI_POWS[6], 2000000n]; @@ -43,7 +48,7 @@ function decodeReaderResult( const constructCheckOnChainPricing = (dexHelper: DummyDexHelper) => async ( - soldily: Solidly, + solidly: Solidly, funcName: string, blockNumber: number, prices: bigint[], @@ -60,7 +65,7 @@ const constructCheckOnChainPricing = funcName, tokenIn, ); - console.log('readerCallData', readerCallData); + const readerResult = ( await dexHelper.multiContract.methods .aggregate(readerCallData) @@ -70,6 +75,8 @@ const constructCheckOnChainPricing = decodeReaderResult(readerResult, readerIface, funcName), ); + console.log('ON-CHAIN PRICES: ', expectedPrices); + expect(prices.map(p => p.toString())).toEqual( expectedPrices.map(p => p.toString()), ); @@ -83,7 +90,7 @@ describe('Solidly integration tests', () => { describe('Solidly', function () { const dexKey = 'Solidly'; - const soldily = new Solidly(network, dexKey, dexHelper); + const solidly = new Solidly(network, dexKey, dexHelper); describe('UniswapV2 like pool', function () { const TokenASymbol = 'WFTM'; @@ -95,7 +102,7 @@ describe('Solidly integration tests', () => { it('getPoolIdentifiers and getPricesVolume', async function () { const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); - const pools = await soldily.getPoolIdentifiers( + const pools = await solidly.getPoolIdentifiers( tokenA, tokenB, SwapSide.SELL, @@ -108,7 +115,7 @@ describe('Solidly integration tests', () => { expect(pools.length).toBeGreaterThan(0); - const poolPrices = await soldily.getPricesVolume( + const poolPrices = await solidly.getPricesVolume( tokenA, tokenB, amounts, @@ -126,13 +133,13 @@ describe('Solidly integration tests', () => { // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( - soldily, + solidly, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -140,7 +147,7 @@ describe('Solidly integration tests', () => { }); it('getTopPoolsForToken', async function () { - const poolLiquidity = await soldily.getTopPoolsForToken( + const poolLiquidity = await solidly.getTopPoolsForToken( tokenA.address, 10, ); @@ -160,7 +167,7 @@ describe('Solidly integration tests', () => { it('getPoolIdentifiers and getPricesVolume', async function () { const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); - const pools = await soldily.getPoolIdentifiers( + const pools = await solidly.getPoolIdentifiers( tokenA, tokenB, SwapSide.SELL, @@ -173,7 +180,7 @@ describe('Solidly integration tests', () => { expect(pools.length).toBeGreaterThan(0); - const poolPrices = await soldily.getPricesVolume( + const poolPrices = await solidly.getPricesVolume( tokenA, tokenB, amounts, @@ -190,13 +197,13 @@ describe('Solidly integration tests', () => { checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( - soldily, + solidly, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -204,7 +211,7 @@ describe('Solidly integration tests', () => { }); it('getTopPoolsForToken', async function () { - const poolLiquidity = await soldily.getTopPoolsForToken( + const poolLiquidity = await solidly.getTopPoolsForToken( tokenA.address, 10, ); @@ -260,13 +267,13 @@ describe('Solidly integration tests', () => { // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( spiritSwapV2, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -324,13 +331,13 @@ describe('Solidly integration tests', () => { checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( spiritSwapV2, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -348,6 +355,176 @@ describe('Solidly integration tests', () => { }); }); }); + + describe('Equalizer', () => { + const dexKey = 'Equalizer'; + const equalizer = new Equalizer(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'WFTM'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'FUSDT'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await equalizer.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await equalizer.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + equalizer, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + + describe('Curve like stable pool', function () { + const TokenASymbol = 'FUSDT'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'USDC'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts6; // amounts6; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await equalizer.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await equalizer.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + equalizer, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + + describe('FTM -> EQUAL', () => { + const TokenASymbol = 'WFTM'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'EQUAL'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = [0n, 10000000n]; + + console.log('AMOUNTS: ', amounts); + it('getPoolIdentifiers and getPricesVolume', async function () { + // const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const blocknumber = 67666611; + const pools = await equalizer.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await equalizer.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + equalizer, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + }); }); describe('Polygon', () => { @@ -400,13 +577,13 @@ describe('Solidly integration tests', () => { // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( dystopia, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -464,13 +641,13 @@ describe('Solidly integration tests', () => { checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( dystopia, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -540,13 +717,13 @@ describe('Solidly integration tests', () => { // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( cone, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -604,13 +781,13 @@ describe('Solidly integration tests', () => { checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( cone, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -680,13 +857,13 @@ describe('Solidly integration tests', () => { // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( chronos, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -744,13 +921,13 @@ describe('Solidly integration tests', () => { checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( chronos, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -768,5 +945,327 @@ describe('Solidly integration tests', () => { }); }); }); + + describe('Ramses', function () { + const dexKey = 'Ramses'; + const ramses = new Ramses(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'USDCe'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'WETH'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await ramses.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + console.log('AMOUNTS: ', amounts); + + const poolPrices = await ramses.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + util.inspect(poolPrices, false, null, true), + ); + + expect(poolPrices).not.toBeNull(); + // checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + ramses, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await ramses.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); + + describe('Curve like stable pool', function () { + const TokenASymbol = 'USDT'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'USDCe'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts6; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await ramses.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await ramses.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + ramses, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await ramses.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); + }); + }); + + describe('Optimism', () => { + const network = Network.OPTIMISM; + const dexHelper = new DummyDexHelper(network); + const checkOnChainPricing = constructCheckOnChainPricing(dexHelper); + + describe('VelodromeV2', () => { + const dexKey = 'VelodromeV2'; + const velodromeV2 = new VelodromeV2(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'USDC'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'WETH'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await velodromeV2.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velodromeV2.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + velodromeV2, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + + describe('Curve like stable pool', function () { + const TokenASymbol = 'USDT'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'USDC'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts6; + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await velodromeV2.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velodromeV2.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + velodromeV2, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + }); + }); + + describe('Base', () => { + const network = Network.BASE; + const dexHelper = new DummyDexHelper(network); + const checkOnChainPricing = constructCheckOnChainPricing(dexHelper); + + describe('Equalizer', () => { + const dexKey = 'Equalizer'; + const equalizer = new Equalizer(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'USDbC'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'ETH'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await equalizer.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await equalizer.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + equalizer, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + }); }); }); diff --git a/src/dex/solidly/solidly.ts b/src/dex/solidly/solidly.ts index d5c12ccf2..13b5a0c75 100644 --- a/src/dex/solidly/solidly.ts +++ b/src/dex/solidly/solidly.ts @@ -66,12 +66,16 @@ export class Solidly extends UniswapV2 { getDexKeysWithNetwork( _.omit(SolidlyConfig, [ 'Velodrome', + 'VelodromeV2', 'SpiritSwapV2', 'Cone', 'SolidlyV2', 'Thena', 'SoliSnek', 'Chronos', + 'Ramses', + 'Equalizer', + 'Aerodrome', ]), ); @@ -520,6 +524,7 @@ export class Solidly extends UniswapV2 { const pair = await this.findSolidlyPair(from, to, stable); if (!(pair && pair.pool && pair.exchange)) return null; const pairState = pair.pool.getState(blockNumber); + if (!pairState) { this.logger.error( `Error_orderPairParams expected reserves, got none (maybe the pool doesn't exist) ${ diff --git a/src/dex/uniswap-v2/nomiswap-v2.ts b/src/dex/uniswap-v2/nomiswap-v2.ts new file mode 100644 index 000000000..ae9f9eb46 --- /dev/null +++ b/src/dex/uniswap-v2/nomiswap-v2.ts @@ -0,0 +1,63 @@ +import { Network } from '../../constants'; +import { getDexKeysWithNetwork } from '../../utils'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { UniswapV2, UniswapV2Pair } from '../uniswap-v2/uniswap-v2'; +import { Interface } from '@ethersproject/abi'; +import NomiswapPoolABI from '../../abi/nomiswap-v2/nomiswap-v2-pool.json'; +import { DexParams } from './types'; +import { DexConfigMap } from '../../types'; + +export const NomiswapV2Config: DexConfigMap = { + NomiswapV2: { + [Network.BSC]: { + factoryAddress: '0xd6715A8be3944ec72738F0BFDC739d48C3c29349', + initCode: + '0x83eb759f5ea0525124f03d4ac741bb4af0bb1c703d5f694bd42a8bd72e495a01', + poolGasCost: 120 * 1000, + feeCode: 0, // this is ignored as Nomiswap uses dynamic fees, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/nominex/nomiswap-exchange-subgraph', + }, + }, +}; + +export class NomiswapV2 extends UniswapV2 { + nomiswapPool: Interface; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(NomiswapV2Config); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + ) { + super( + network, + dexKey, + dexHelper, + true, + NomiswapV2Config[dexKey][network].factoryAddress, + NomiswapV2Config[dexKey][network].subgraphURL, + NomiswapV2Config[dexKey][network].initCode, + NomiswapV2Config[dexKey][network].feeCode, + NomiswapV2Config[dexKey][network].poolGasCost, + ); + this.nomiswapPool = new Interface(NomiswapPoolABI); + } + + protected getFeesMultiCallData(pair: UniswapV2Pair) { + const callEntry = { + target: pair.exchange!, + callData: this.nomiswapPool.encodeFunctionData('swapFee', []), + }; + const callDecoder = (values: any[]) => + parseInt( + this.nomiswapPool.decodeFunctionResult('swapFee', values)[0].toString(), + ) * 10; + return { + callEntry, + callDecoder, + }; + } +} diff --git a/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts b/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts index ff7a66013..674c4e957 100644 --- a/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts +++ b/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts @@ -2,11 +2,108 @@ import dotenv from 'dotenv'; dotenv.config(); import { testE2E } from '../../../tests/utils-e2e'; -import { Tokens, Holders } from '../../../tests/constants-e2e'; +import { + Tokens, + Holders, + NativeTokenSymbols, +} from '../../../tests/constants-e2e'; import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, + slippage?: number | undefined, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + }); + }); + }), + ); + }); +} + describe('UniswapV2 E2E BSC', () => { const network = Network.BSC; const tokens = Tokens[network]; @@ -1407,6 +1504,28 @@ describe('UniswapV2 E2E BSC', () => { }); }); + describe('NomiswapV2', () => { + const dexKey = 'NomiswapV2'; + const network = Network.BSC; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '1111100000'; + const tokenBAmount: string = '1000000000'; + const nativeTokenAmount = '11000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + describe(`Swapsicle`, () => { const dexKey = 'Swapsicle'; diff --git a/src/dex/uniswap-v3/config.ts b/src/dex/uniswap-v3/config.ts index c1c74165f..4a419d9b8 100644 --- a/src/dex/uniswap-v3/config.ts +++ b/src/dex/uniswap-v3/config.ts @@ -2,6 +2,9 @@ import { DexParams } from './types'; import { DexConfigMap, AdapterMappings } from '../../types'; import { Network, SwapSide } from '../../constants'; import { Address } from '../../types'; +import RamsesV2StateMulticallABI from '../../abi/RamsesV2StateMulticall.abi.json'; +import { AbiItem } from 'web3-utils'; +import { decodeStateMultiCallResultWithRelativeBitmaps } from './forks/ramses-v2/utils'; const SUPPORTED_FEES = [10000n, 3000n, 500n, 100n]; @@ -103,6 +106,158 @@ export const UniswapV3Config: DexConfigMap = { subgraphURL: 'https://api.thegraph.com/subgraphs/name/lynnshaoyu/uniswap-v3-avax', }, + [Network.BASE]: { + factory: '0x33128a8fC17869897dcE68Ed026d694621f6FDfD', + quoter: '0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a', + router: '0xaeE2b8d4A154e36f479dAeCe3FB3e6c3c03d396E', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x7160f736c52e1e78e92FD4eE4D73e21A7Cf4F950', + uniswapMulticall: '0x091e99cb1C49331a94dD62755D168E941AbD0693', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.studio.thegraph.com/query/48211/uniswap-v3-base/version/latest', + }, + }, + SushiSwapV3: { + [Network.MAINNET]: { + factory: '0xbACEB8eC6b9355Dfc0269C18bac9d6E2Bdc29C4F', + quoter: '0x64e8802FE490fa7cc61d3463958199161Bb608A7', + router: '0x00F23572b16c5e9e58e7b965DEF51Ff8Ff546E34', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x9c764D2e92dA68E4CDfD784B902283A095ff8b63', + uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-ethereum', + }, + [Network.POLYGON]: { + factory: '0x917933899c6a5f8e37f31e19f92cdbff7e8ff0e2', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0x34D41cE301257a4615D4F5AD260FA91D03925243', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x6Dc993Fe1e945A640576B4Dca81281d8e998DF71', + uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-polygon', + }, + [Network.BSC]: { + factory: '0x126555dd55a39328F69400d6aE4F782Bd4C34ABb', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x593F39A4Ba26A9c8ed2128ac95D109E8e403C485', + uniswapMulticall: '0x963Df249eD09c358A4819E39d9Cd5736c3087184', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-bsc', + }, + [Network.AVALANCHE]: { + factory: '0x3e603C14aF37EBdaD31709C4f848Fc6aD5BEc715', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0x24c90C7d8fb463722e304A71255341610Fa7589b', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x30F6B9b6485ff0B67E881f5ac80D3F1c70A4B23d', + uniswapMulticall: '0x8C0F842791F03C095b6c633759224FcC9ACe68ea', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-avalanche', + }, + [Network.FANTOM]: { + factory: '0x7770978eED668a3ba661d51a773d3a992Fc9DDCB', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x30F6B9b6485ff0B67E881f5ac80D3F1c70A4B23d', + uniswapMulticall: '0xB1395e098c0a847CC719Bcf1Fc8114421a9F8232', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-fantom', + }, + [Network.ARBITRUM]: { + factory: '0x1af415a1eba07a4986a52b6f2e7de7003d82231e', + quoter: '0x0524E833cCD057e4d7A296e3aaAb9f7675964Ce1', + router: '0xbDa4176fD98b47018aF673805d069b9dbd49373D', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0xaBB58098A7B5172A9b0B38a1925A522dbf0b4FC3', + uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-arbitrum', + }, + [Network.OPTIMISM]: { + factory: '0x9c6522117e2ed1fE5bdb72bb0eD5E3f2bdE7DBe0', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0xa05d8C3F278fC7b20b39Ea7A3035E3aD8D808c78', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x4FF0dEC5f9a763Aa1E5C2a962aa6f4eDFeE4f9eA', + uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-optimism', + }, + [Network.BASE]: { + factory: '0xc35DADB65012eC5796536bD9864eD8773aBc74C4', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0xCc0e85901f33D375FcdD9a888B05Df9616F68277', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x7160f736c52e1e78e92FD4eE4D73e21A7Cf4F950', + uniswapMulticall: '0x091e99cb1C49331a94dD62755D168E941AbD0693', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.studio.thegraph.com/query/32073/v3-base/v0.0.1', + }, + }, + ChronosV3: { + [Network.ARBITRUM]: { + factory: '0x4Db9D624F67E00dbF8ef7AE0e0e8eE54aF1dee49', + quoter: '0x6E7f0Ca45171a4440c0CDdF3A46A8dC5D4c2d4A0', + router: '0xE0aBdFD837D451640CF43cB1Ec4eE87976eFbb41', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x46b44eb4Cc3bEbB9f04C419f691aB85Ff885A4D6', + uniswapMulticall: '0xaBB58098A7B5172A9b0B38a1925A522dbf0b4FC3', + chunksCount: 10, + initRetryFrequency: 10, + initHash: + '0x09c178be473df44d1de6970978a4fdedce1ce52a23b2b979754547f6b43a19a5', + subgraphURL: + 'https://subgraph.chronos.exchange/subgraphs/name/chronos-v3', + }, + }, + RamsesV2: { + [Network.ARBITRUM]: { + factory: '0xAA2cd7477c451E703f3B9Ba5663334914763edF8', + deployer: '0xb3e423ab9cE6C03D98326A3A2a0D7D96b0829f22', + quoter: '0xAA20EFF7ad2F523590dE6c04918DaAE0904E3b20', + router: '0xAA23611badAFB62D37E7295A682D21960ac85A90', + supportedFees: [...SUPPORTED_FEES, 50n], + stateMulticall: '0x50EE4112Cab9c79812F23bE079aB3911395ACc8e', + stateMultiCallAbi: RamsesV2StateMulticallABI as AbiItem[], + uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', + chunksCount: 10, + initRetryFrequency: 10, + decodeStateMultiCallResultWithRelativeBitmaps, + initHash: + '0x1565b129f2d1790f12d45301b9b084335626f0c92410bc43130763b69971135d', + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/ramsesexchange/concentrated-liquidity-graph', + }, }, 'QuickSwapV3.1': { [Network.ZKEVM]: { @@ -154,4 +309,8 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'AvalancheAdapter02', index: 5 }], [SwapSide.BUY]: [{ name: 'AvalancheBuyAdapter', index: 6 }], }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 1 }], + [SwapSide.BUY]: [{ name: 'BaseBuyAdapter', index: 1 }], + }, }; diff --git a/src/dex/uniswap-v3/forks/ramses-v2/utils.ts b/src/dex/uniswap-v3/forks/ramses-v2/utils.ts new file mode 100644 index 000000000..9e47f08f2 --- /dev/null +++ b/src/dex/uniswap-v3/forks/ramses-v2/utils.ts @@ -0,0 +1,72 @@ +import { MultiResult } from '../../../../lib/multi-wrapper'; +import { BytesLike, ethers } from 'ethers'; +import { DecodedStateMultiCallResultWithRelativeBitmaps } from '../../types'; +import { extractSuccessAndValue } from '../../../../lib/decoders'; +import { assert } from 'ts-essentials'; + +export function decodeStateMultiCallResultWithRelativeBitmaps( + result: MultiResult | BytesLike, +): DecodedStateMultiCallResultWithRelativeBitmaps { + const [isSuccess, toDecode] = extractSuccessAndValue(result); + + assert( + isSuccess && toDecode !== '0x', + `decodeStateMultiCallResultWithRelativeBitmaps failed to get decodable result: ${result}`, + ); + + const decoded = ethers.utils.defaultAbiCoder.decode( + [ + // I don't want to pass here any interface, so I just use it in ethers format + ` + tuple( + address pool, + uint256 blockTimestamp, + tuple( + uint160 sqrtPriceX96, + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + uint16 observationCardinalityNext, + uint8 feeProtocol, + bool unlocked, + ) slot0, + uint128 liquidity, + int24 tickSpacing, + uint128 maxLiquidityPerTick, + tuple( + uint32 blockTimestamp, + int56 tickCumulative, + uint160 secondsPerLiquidityCumulativeX128, + bool initialized, + uint160 secondsPerBoostedLiquidityPeriodX128, + uint32 boostedInRange, + ) observation, + tuple( + int16 index, + uint256 value + )[] tickBitmap, + tuple( + int24 index, + tuple( + uint128 liquidityGross, + int128 liquidityNet, + uint128 cleanUnusedSlot, + uint128 cleanUnusedSlot2, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128, + int56 tickCumulativeOutside, + uint160 secondsPerLiquidityOutsideX128, + uint32 secondsOutside, + bool initialized, + ) value, + )[] ticks + ) + `, + ], + toDecode, + )[0]; + + // This conversion is not precise, because when we decode, we have more values + // But I typed only the ones that are used later + return decoded as DecodedStateMultiCallResultWithRelativeBitmaps; +} diff --git a/src/dex/uniswap-v3/types.ts b/src/dex/uniswap-v3/types.ts index c3fd7890c..b7c314500 100644 --- a/src/dex/uniswap-v3/types.ts +++ b/src/dex/uniswap-v3/types.ts @@ -1,6 +1,8 @@ -import { BigNumber } from 'ethers'; +import { BigNumber, BytesLike } from 'ethers'; import { NumberAsString } from '../../types'; import { Address } from '../../types'; +import { AbiItem } from 'web3-utils'; +import { MultiResult } from '../../lib/multi-wrapper'; export type OracleObservation = { blockTimestamp: bigint; @@ -60,6 +62,10 @@ export type UniswapV3Data = { isApproved?: boolean; }; +export type DecodeStateMultiCallFunc = ( + result: MultiResult | BytesLike, +) => DecodedStateMultiCallResultWithRelativeBitmaps; + export type DexParams = { router: Address; quoter: Address; @@ -72,6 +78,8 @@ export type DexParams = { deployer?: Address; subgraphURL: string; initHash: string; + stateMultiCallAbi?: AbiItem[]; + decodeStateMultiCallResultWithRelativeBitmaps?: DecodeStateMultiCallFunc; }; export type UniswapV3SimpleSwapSellParam = { diff --git a/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts b/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts index f417648c7..0bae0339a 100644 --- a/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts +++ b/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts @@ -11,403 +11,1109 @@ import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; -describe('UniswapV3 E2E', () => { - const dexKey = 'UniswapV3'; - - describe('UniswapV3 MAINNET', () => { - const network = Network.MAINNET; - const tokens = Tokens[network]; - const holders = Holders[network]; - const provider = new StaticJsonRpcProvider( - generateConfig(network).privateHttpProvider, - network, - ); +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, + slippage?: number | undefined, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; - it('BUY DAI -> USDC', async () => { - await testE2E( - tokens['DAI'], - tokens['USDC'], - holders['DAI'], - '100000000000', - SwapSide.BUY, - dexKey, + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ContractMethod.directUniV3Swap, + ], + ], + [ + SwapSide.BUY, + [ ContractMethod.simpleBuy, + ContractMethod.buy, + ContractMethod.directUniV3Buy, + ], + ], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + }); + }); + }), + ); + }); +} + +describe('UniswapV3 E2E', () => { + describe('UniswapV3', () => { + const dexKey = 'UniswapV3'; + + describe('UniswapV3 MAINNET', () => { + const network = Network.MAINNET; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, network, - provider, ); + + it('BUY DAI -> USDC', async () => { + await testE2E( + tokens['DAI'], + tokens['USDC'], + holders['DAI'], + '100000000000', + SwapSide.BUY, + dexKey, + ContractMethod.simpleBuy, + network, + provider, + ); + }); + it('SELL WETH -> SHIBA', async () => { + await testE2E( + tokens['WETH'], + tokens['SHIBA'], + holders['WETH'], + '1000000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.simpleSwap, + network, + provider, + ); + }); + + it('directSwap SELL WETH -> USDC', async () => { + await testE2E( + tokens['WETH'], + tokens['USDC'], + holders['WETH'], + '1000000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.directUniV3Swap, + network, + provider, + ); + }); }); - it('SELL WETH -> SHIBA', async () => { - await testE2E( - tokens['WETH'], - tokens['SHIBA'], - holders['WETH'], - '1000000000000000000', - SwapSide.SELL, - dexKey, - ContractMethod.simpleSwap, + + describe('UniswapV3 POLYGON', () => { + const network = Network.POLYGON; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, network, - provider, + ); + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'WETH'; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const tokenAAmount: string = '11000000'; + const tokenBAmount: string = '11000000000000000000'; + const nativeTokenAmount = '11000000000000000000'; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ContractMethod.directUniV3Swap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ContractMethod.directUniV3Buy, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${network} ${side} ${contractMethod} ${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }), ); }); - it('directSwap SELL WETH -> USDC', async () => { - await testE2E( - tokens['WETH'], - tokens['USDC'], - holders['WETH'], - '1000000000000000000', - SwapSide.SELL, - dexKey, - ContractMethod.directUniV3Swap, + describe('UniswapV3 BSC', () => { + const network = Network.BSC; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, network, - provider, + ); + + const tokenASymbol: string = 'BUSD'; + const tokenBSymbol: string = 'WBNB'; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const tokenAAmount: string = '100000000000000000000'; + const tokenBAmount: string = '1000000000000000000'; + const nativeTokenAmount = '1000000000000000000'; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${network} ${side} ${contractMethod} ${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }), ); }); - }); - describe('UniswapV3 POLYGON', () => { - const network = Network.POLYGON; - const tokens = Tokens[network]; - const holders = Holders[network]; - const provider = new StaticJsonRpcProvider( - generateConfig(network).privateHttpProvider, - network, - ); + describe('UniswapV3 Optimism', () => { + const network = Network.OPTIMISM; + 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 tokenASymbol: string = 'OP'; + const tokenBSymbol: string = 'ETH'; + const nativeTokenSymbol = NativeTokenSymbols[network]; - const tokenAAmount: string = '11000000'; - const tokenBAmount: string = '11000000000000000000'; - const nativeTokenAmount = '11000000000000000000'; + const tokenAAmount: string = '1000000000000000000'; + const tokenBAmount: string = '1000000000000000000'; + const nativeTokenAmount = '1000000000000000000'; - const sideToContractMethods = new Map([ - [ - SwapSide.SELL, + const sideToContractMethods = new Map([ [ - ContractMethod.simpleSwap, - ContractMethod.multiSwap, - ContractMethod.megaSwap, - ContractMethod.directUniV3Swap, + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], ], - ], - [ - SwapSide.BUY, + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${network} ${side} ${contractMethod} ${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }), + ); + }); + + describe('UniswapV3 Base', () => { + const network = Network.BASE; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const tokenASymbol: string = 'PRIME'; + const tokenBSymbol: string = 'WETH'; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const tokenAAmount: string = '1000000000000000000'; + const tokenBAmount: string = '1000000000000000000'; + const nativeTokenAmount = '1000000000000000000'; + + const sideToContractMethods = new Map([ [ - ContractMethod.simpleBuy, - ContractMethod.buy, - ContractMethod.directUniV3Buy, + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], ], - ], - ]); + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy + ], + ], + ]); - sideToContractMethods.forEach((contractMethods, side) => - contractMethods.forEach((contractMethod: ContractMethod) => { - describe(`${contractMethod}`, () => { - it(`${network} ${side} ${contractMethod} ${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { - await testE2E( - tokens[nativeTokenSymbol], - tokens[tokenASymbol], - holders[nativeTokenSymbol], - side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); - }); - it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { - await testE2E( - tokens[tokenASymbol], - tokens[nativeTokenSymbol], - holders[tokenASymbol], - side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${network} ${side} ${contractMethod} ${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenBSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[tokenBSymbol], + tokens[tokenASymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenBAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); }); - it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${tokenBSymbol}`, async () => { - await testE2E( - tokens[tokenASymbol], - tokens[tokenBSymbol], - holders[tokenASymbol], - side === SwapSide.SELL ? tokenAAmount : tokenBAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); + }), + ); + }); + + describe('UniswapV3 Avalanche', () => { + const network = Network.AVALANCHE; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = + [ + [ + { + name: 'AVAX', + sellAmount: '1000000000000000000', + buyAmount: '500000', + }, + { + name: 'USDT', + sellAmount: '1000000', + buyAmount: '10000000000000000000', + }, + ], + [ + { + name: 'AVAX', + sellAmount: '1000000000000000000', + buyAmount: '500000', + }, + { + name: 'USDC', + sellAmount: '1000000', + buyAmount: '1000000000000000000', + }, + ], + [ + { + name: 'WAVAX', + sellAmount: '1000000000000000000', + buyAmount: '500000', + }, + { + name: 'USDC', + sellAmount: '1000000', + buyAmount: '20000000000000000', + }, + ], + [ + { + name: 'WAVAX', + sellAmount: '1000000000000000000', + buyAmount: '10000000', + }, + { name: 'USDT', sellAmount: '1000000', buyAmount: '2000000000000' }, + ], + [ + { name: 'USDC', sellAmount: '1000000', buyAmount: '100000000' }, + { name: 'USDT', sellAmount: '100000000', buyAmount: '100000000' }, + ], + ]; + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + pairs.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].name} -> ${pair[1].name}`, async () => { + await testE2E( + tokens[pair[0].name], + tokens[pair[1].name], + holders[pair[0].name], + side === SwapSide.SELL + ? pair[0].sellAmount + : pair[0].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${pair[1].name} -> ${pair[0].name}`, async () => { + await testE2E( + tokens[pair[1].name], + tokens[pair[0].name], + holders[pair[1].name], + side === SwapSide.SELL + ? pair[1].sellAmount + : pair[1].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); }); - }); - }), - ); + }), + ); + }); }); - describe('UniswapV3 BSC', () => { - const network = Network.BSC; - const tokens = Tokens[network]; - const holders = Holders[network]; - const provider = new StaticJsonRpcProvider( - generateConfig(network).privateHttpProvider, - network, - ); + describe('RamsesV2', () => { + const dexKey = 'RamsesV2'; - const tokenASymbol: string = 'BUSD'; - const tokenBSymbol: string = 'WBNB'; - const nativeTokenSymbol = NativeTokenSymbols[network]; + describe('Arbitrum', () => { + const network = Network.ARBITRUM; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); - const tokenAAmount: string = '100000000000000000000'; - const tokenBAmount: string = '1000000000000000000'; - const nativeTokenAmount = '1000000000000000000'; + const tokenASymbol: string = 'USDCe'; + const tokenBSymbol: string = 'USDT'; + const nativeTokenSymbol = NativeTokenSymbols[network]; - const sideToContractMethods = new Map([ - [ - SwapSide.SELL, + const tokenAAmount: string = '1100000'; + const tokenBAmount: string = '1000000'; + const nativeTokenAmount = '1100000000000'; + + const sideToContractMethods = new Map([ [ - ContractMethod.simpleSwap, - ContractMethod.multiSwap, - ContractMethod.megaSwap, + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ContractMethod.directUniV3Swap, + ], ], - ], - [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], - ]); + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ContractMethod.directUniV3Buy, + ], + ], + ]); - sideToContractMethods.forEach((contractMethods, side) => - contractMethods.forEach((contractMethod: ContractMethod) => { - describe(`${contractMethod}`, () => { - it(`${network} ${side} ${contractMethod} ${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { - await testE2E( - tokens[nativeTokenSymbol], - tokens[tokenASymbol], - holders[nativeTokenSymbol], - side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); - }); - it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { - await testE2E( - tokens[tokenASymbol], - tokens[nativeTokenSymbol], - holders[tokenASymbol], - side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); - }); - it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${tokenBSymbol}`, async () => { - await testE2E( - tokens[tokenASymbol], - tokens[tokenBSymbol], - holders[tokenASymbol], - side === SwapSide.SELL ? tokenAAmount : tokenBAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${network} ${side} ${contractMethod} ${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); }); - }); - }), - ); + }), + ); + }); }); - describe('UniswapV3 Optimism', () => { - const network = Network.OPTIMISM; - const tokens = Tokens[network]; - const holders = Holders[network]; - const provider = new StaticJsonRpcProvider( - generateConfig(network).privateHttpProvider, - network, - ); + describe('ChronosV3', () => { + const dexKey = 'ChronosV3'; + describe('Arbitrum', () => { + const network = Network.ARBITRUM; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); - const tokenASymbol: string = 'OP'; - const tokenBSymbol: string = 'ETH'; - const nativeTokenSymbol = NativeTokenSymbols[network]; + const tokenASymbol: string = 'USDCe'; + const tokenBSymbol: string = 'USDT'; + const nativeTokenSymbol = NativeTokenSymbols[network]; - const tokenAAmount: string = '1000000000000000000'; - const tokenBAmount: string = '1000000000000000000'; - const nativeTokenAmount = '1000000000000000000'; + const tokenAAmount: string = '2000000'; + const tokenBAmount: string = '2000000'; + const nativeTokenAmount = '100000000000000000'; - const sideToContractMethods = new Map([ - [ - SwapSide.SELL, + const sideToContractMethods = new Map([ [ - ContractMethod.simpleSwap, - ContractMethod.multiSwap, - ContractMethod.megaSwap, + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ContractMethod.directUniV3Swap, + ], ], - ], - [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], - ]); + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ContractMethod.directUniV3Buy, + ], + ], + ]); - sideToContractMethods.forEach((contractMethods, side) => - contractMethods.forEach((contractMethod: ContractMethod) => { - describe(`${contractMethod}`, () => { - it(`${network} ${side} ${contractMethod} ${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { - await testE2E( - tokens[nativeTokenSymbol], - tokens[tokenASymbol], - holders[nativeTokenSymbol], - side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); - }); - it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { - await testE2E( - tokens[tokenASymbol], - tokens[nativeTokenSymbol], - holders[tokenASymbol], - side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); - }); - it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${tokenBSymbol}`, async () => { - await testE2E( - tokens[tokenASymbol], - tokens[tokenBSymbol], - holders[tokenASymbol], - side === SwapSide.SELL ? tokenAAmount : tokenBAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${network} ${side} ${contractMethod} ${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); }); - }); - }), - ); + }), + ); + }); }); - describe('UniswapV3 Avalanche', () => { - const network = Network.AVALANCHE; - const tokens = Tokens[network]; - const holders = Holders[network]; - const provider = new StaticJsonRpcProvider( - generateConfig(network).privateHttpProvider, - network, - ); + describe('SushiSwapV3 E2E', () => { + const dexKey = 'SushiSwapV3'; - const sideToContractMethods = new Map([ - [ - SwapSide.SELL, + describe('MAINNET', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '111110000'; + const tokenBAmount: string = '1100000000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('ARBITRUM', () => { + const network = Network.ARBITRUM; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDCe'; + + const tokenAAmount: string = '10000000'; + const tokenBAmount: string = '10000000'; + const nativeTokenAmount = '900000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('POLYGON', () => { + const network = Network.POLYGON; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '21111000'; + const tokenBAmount: string = '200000000'; + const nativeTokenAmount = '110000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('BSC', () => { + const network = Network.BSC; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const sideToContractMethods = new Map([ [ - ContractMethod.simpleSwap, - ContractMethod.multiSwap, - ContractMethod.megaSwap, + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ContractMethod.directUniV3Swap, + ], ], - ], - [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], - ]); + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ContractMethod.directUniV3Buy, + ], + ], + ]); - const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = [ - [ - { - name: 'AVAX', - sellAmount: '1000000000000000000', - buyAmount: '500000', - }, - { - name: 'USDT', - sellAmount: '1000000', - buyAmount: '10000000000000000000', - }, - ], - [ - { - name: 'AVAX', - sellAmount: '1000000000000000000', - buyAmount: '500000', - }, - { - name: 'USDC', - sellAmount: '1000000', - buyAmount: '1000000000000000000', - }, - ], - [ - { - name: 'WAVAX', - sellAmount: '1000000000000000000', - buyAmount: '500000', - }, - { name: 'USDC', sellAmount: '1000000', buyAmount: '20000000000000000' }, - ], - [ - { - name: 'WAVAX', - sellAmount: '1000000000000000000', - buyAmount: '10000000', - }, - { name: 'USDT', sellAmount: '1000000', buyAmount: '2000000000000' }, - ], - [ - { name: 'USDC', sellAmount: '1000000', buyAmount: '100000000' }, - { name: 'USDT', sellAmount: '100000000', buyAmount: '100000000' }, - ], - ]; + const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = + [ + [ + { + name: 'USDC', + sellAmount: '100000000000000000000', + buyAmount: '100000000000000000000', + }, + { + name: 'USDT', + sellAmount: '100000000000000000000', + buyAmount: '100000000000000000000', + }, + ], + [ + { + name: 'BNB', + sellAmount: '1000000000000000000', + buyAmount: '10000000000000000000', + }, + { + name: 'USDT', + sellAmount: '1000000000000000000000', + buyAmount: '20000000000000000', + }, + ], + ]; - sideToContractMethods.forEach((contractMethods, side) => - describe(`${side}`, () => { - contractMethods.forEach((contractMethod: ContractMethod) => { - pairs.forEach(pair => { - describe(`${contractMethod}`, () => { - it(`${pair[0].name} -> ${pair[1].name}`, async () => { - await testE2E( - tokens[pair[0].name], - tokens[pair[1].name], - holders[pair[0].name], - side === SwapSide.SELL - ? pair[0].sellAmount - : pair[0].buyAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + pairs.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].name} -> ${pair[1].name}`, async () => { + await testE2E( + tokens[pair[0].name], + tokens[pair[1].name], + holders[pair[0].name], + side === SwapSide.SELL + ? pair[0].sellAmount + : pair[0].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${pair[1].name} -> ${pair[0].name}`, async () => { + await testE2E( + tokens[pair[1].name], + tokens[pair[0].name], + holders[pair[1].name], + side === SwapSide.SELL + ? pair[1].sellAmount + : pair[1].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); }); - it(`${pair[1].name} -> ${pair[0].name}`, async () => { - await testE2E( - tokens[pair[1].name], - tokens[pair[0].name], - holders[pair[1].name], - side === SwapSide.SELL - ? pair[1].sellAmount - : pair[1].buyAmount, - side, - dexKey, - contractMethod, - network, - provider, - ); + }); + }); + }), + ); + }); + + describe('AVALANCHE', () => { + const network = Network.AVALANCHE; + + const tokenASymbol: string = 'USDT'; + const tokenBSymbol: string = 'USDC'; + + const tokenAAmount: string = '111110'; + const tokenBAmount: string = '100000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('FANTOM', () => { + const network = Network.FANTOM; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ContractMethod.directUniV3Swap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ContractMethod.directUniV3Buy, + ], + ], + ]); + + const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = + [ + [ + { + name: 'FTM', + sellAmount: '100000000000000000', + buyAmount: '100000000', + }, + { + name: 'USDC', + sellAmount: '100000000', + buyAmount: '100000000000000000', + }, + ], + [ + { + name: 'WFTM', + sellAmount: '100000000000000', + buyAmount: '1000000000000000', + }, + { + name: 'WETH', + sellAmount: '1000000000000000', + buyAmount: '100000000000000', + }, + ], + ]; + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + pairs.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].name} -> ${pair[1].name}`, async () => { + await testE2E( + tokens[pair[0].name], + tokens[pair[1].name], + holders[pair[0].name], + side === SwapSide.SELL + ? pair[0].sellAmount + : pair[0].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${pair[1].name} -> ${pair[0].name}`, async () => { + await testE2E( + tokens[pair[1].name], + tokens[pair[0].name], + holders[pair[1].name], + side === SwapSide.SELL + ? pair[1].sellAmount + : pair[1].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); }); }); }); - }); - }), - ); + }), + ); + }); + + describe('OPTIMISM', () => { + const network = Network.OPTIMISM; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '111110000'; + const tokenBAmount: string = '10000000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('BASE', () => { + const network = Network.BASE; + + const tokenASymbol: string = 'USDbC'; + const tokenBSymbol: string = 'DAI'; + + const tokenAAmount: string = '111110000'; + const tokenBAmount: string = '110000000000000000'; + const nativeTokenAmount = '1100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); }); diff --git a/src/dex/uniswap-v3/uniswap-v3-integration.test.ts b/src/dex/uniswap-v3/uniswap-v3-integration.test.ts index 35dee3c93..47367cda3 100644 --- a/src/dex/uniswap-v3/uniswap-v3-integration.test.ts +++ b/src/dex/uniswap-v3/uniswap-v3-integration.test.ts @@ -3,13 +3,13 @@ import dotenv from 'dotenv'; dotenv.config(); import { Interface, Result } from '@ethersproject/abi'; -import { DummyDexHelper } from '../../dex-helper/index'; +import { DummyDexHelper, IDexHelper } from '../../dex-helper/index'; import { Network, SwapSide } from '../../constants'; import { BI_POWS } from '../../bigint-constants'; import { UniswapV3 } from './uniswap-v3'; import { checkPoolPrices, checkPoolsLiquidity } from '../../../tests/utils'; import { Tokens } from '../../../tests/constants-e2e'; -import UniswapV3QuoterABI from '../../abi/uniswap-v3/UniswapV3Quoter.abi.json'; +import UniswapV3QuoterV2ABI from '../../abi/uniswap-v3/UniswapV3QuoterV2.abi.json'; import { Address } from '@paraswap/core'; const network = Network.POLYGON; @@ -28,10 +28,7 @@ const amounts = [ const amountsBuy = [0n, 1n * BI_POWS[18], 2n * BI_POWS[18], 3n * BI_POWS[18]]; -const dexHelper = new DummyDexHelper(network); -const dexKey = 'UniswapV3'; - -const quoterIface = new Interface(UniswapV3QuoterABI); +const quoterIface = new Interface(UniswapV3QuoterV2ABI); function getReaderCalldata( exchangeAddress: string, @@ -45,11 +42,7 @@ function getReaderCalldata( return amounts.map(amount => ({ target: exchangeAddress, callData: readerIface.encodeFunctionData(funcName, [ - tokenIn, - tokenOut, - fee, - amount, - 0n, + [tokenIn, tokenOut, amount.toString(), fee.toString(), 0], ]), })); } @@ -66,9 +59,11 @@ function decodeReaderResult( } async function checkOnChainPricing( + dexHelper: IDexHelper, uniswapV3: UniswapV3, funcName: string, blockNumber: number, + exchangeAddress: string, prices: bigint[], tokenIn: Address, tokenOut: Address, @@ -76,7 +71,7 @@ async function checkOnChainPricing( _amounts: bigint[], ) { // Quoter address - const exchangeAddress = '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6'; + // const exchangeAddress = '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6'; const readerIface = quoterIface; const sum = prices.reduce((acc, curr) => (acc += curr), 0n); @@ -117,6 +112,8 @@ async function checkOnChainPricing( decodeReaderResult(readerResult, readerIface, funcName), ); + console.log('EXPECTED PRICES: ', expectedPrices); + let firstZeroIndex = prices.slice(1).indexOf(0n); // we skipped first, so add +1 on result @@ -130,6 +127,9 @@ async function checkOnChainPricing( } describe('UniswapV3', function () { + const dexHelper = new DummyDexHelper(network); + const dexKey = 'UniswapV3'; + let blockNumber: number; let uniswapV3: UniswapV3; let uniswapV3Mainnet: UniswapV3; @@ -138,9 +138,9 @@ describe('UniswapV3', function () { blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); uniswapV3 = new UniswapV3(network, dexKey, dexHelper); uniswapV3Mainnet = new UniswapV3( - Network.MAINNET, + Network.ARBITRUM, dexKey, - new DummyDexHelper(Network.MAINNET), + new DummyDexHelper(Network.ARBITRUM), ); }); @@ -173,9 +173,11 @@ describe('UniswapV3', function () { poolPrices!.map(async price => { const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode; const res = await checkOnChainPricing( + dexHelper, uniswapV3, 'quoteExactInputSingle', blockNumber, + '0x61fFE014bA17989E743c5F6cB21bF9697530B21e', price.prices, TokenA.address, TokenB.address, @@ -219,9 +221,11 @@ describe('UniswapV3', function () { poolPrices!.map(async price => { const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode; const res = await checkOnChainPricing( + dexHelper, uniswapV3, 'quoteExactOutputSingle', blockNumber, + '0x61fFE014bA17989E743c5F6cB21bF9697530B21e', price.prices, TokenA.address, TokenB.address, @@ -329,9 +333,11 @@ describe('UniswapV3', function () { poolPrices!.map(async price => { const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode; const res = await checkOnChainPricing( + dexHelper, uniswapV3, 'quoteExactInputSingle', blockNumber, + '0x61fFE014bA17989E743c5F6cB21bF9697530B21e', price.prices, TokenA.address, TokenB.address, @@ -439,9 +445,11 @@ describe('UniswapV3', function () { poolPrices!.map(async price => { const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode; const res = await checkOnChainPricing( + dexHelper, uniswapV3, 'quoteExactOutputSingle', blockNumber, + '0x61fFE014bA17989E743c5F6cB21bF9697530B21e', price.prices, TokenA.address, TokenB.address, @@ -466,3 +474,631 @@ describe('UniswapV3', function () { } }); }); + +describe('RamsesV2', () => { + const dexKey = 'RamsesV2'; + let blockNumber: number; + let uniswapV3: UniswapV3; + let uniswapV3Mainnet: UniswapV3; + + const network = Network.ARBITRUM; + const dexHelper = new DummyDexHelper(network); + const TokenASymbol = 'USDCe'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'USDC'; + const TokenB = Tokens[network][TokenBSymbol]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + uniswapV3 = new UniswapV3(network, dexKey, dexHelper); + uniswapV3Mainnet = new UniswapV3(Network.ARBITRUM, dexKey, dexHelper); + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const amounts = [ + 0n, + 6000000n, + 12000000n, + 18000000n, + 24000000n, + 30000000n, + 36000000n, + 42000000n, + ]; + + const pools = await uniswapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blockNumber, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await uniswapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.SELL, + blockNumber, + pools, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + uniswapV3, + 'quoteExactInputSingle', + blockNumber, + '0xAA20EFF7ad2F523590dE6c04918DaAE0904E3b20', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + const amounts = [ + 0n, + 6000000n, + 12000000n, + 18000000n, + 24000000n, + 30000000n, + 36000000n, + 42000000n, + ]; + + const pools = await uniswapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await uniswapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + uniswapV3, + 'quoteExactOutputSingle', + blockNumber, + '0xAA20EFF7ad2F523590dE6c04918DaAE0904E3b20', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); +}); + +describe('ChronosV3', () => { + const dexKey = 'ChronosV3'; + let blockNumber: number; + let uniswapV3: UniswapV3; + let uniswapV3Mainnet: UniswapV3; + + const network = Network.ARBITRUM; + const dexHelper = new DummyDexHelper(network); + const TokenASymbol = 'USDCe'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'USDT'; + const TokenB = Tokens[network][TokenBSymbol]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + uniswapV3 = new UniswapV3(network, dexKey, dexHelper); + uniswapV3Mainnet = new UniswapV3(Network.ARBITRUM, dexKey, dexHelper); + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await uniswapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blockNumber, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await uniswapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.SELL, + blockNumber, + pools, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + uniswapV3, + 'quoteExactInputSingle', + blockNumber, + '0x6E7f0Ca45171a4440c0CDdF3A46A8dC5D4c2d4A0', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await uniswapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await uniswapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + uniswapV3, + 'quoteExactOutputSingle', + blockNumber, + '0x6E7f0Ca45171a4440c0CDdF3A46A8dC5D4c2d4A0', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it.skip('getTopPoolsForToken', async function () { + const poolLiquidity = await uniswapV3.getTopPoolsForToken( + TokenB.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, TokenB.address, dexKey); + }); +}); + +describe('SushiSwapV3', () => { + const dexKey = 'SushiSwapV3'; + + describe('Mainnet', () => { + let blockNumber: number; + let sushiSwapV3: UniswapV3; + let sushiSwapV3Mainnet: UniswapV3; + + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + const TokenASymbol = 'USDC'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'USDT'; + const TokenB = Tokens[network][TokenBSymbol]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + sushiSwapV3 = new UniswapV3(network, dexKey, dexHelper); + sushiSwapV3Mainnet = new UniswapV3(Network.MAINNET, dexKey, dexHelper); + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await sushiSwapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await sushiSwapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.SELL, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = sushiSwapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + sushiSwapV3, + 'quoteExactInputSingle', + blockNumber, + '0x64e8802FE490fa7cc61d3463958199161Bb608A7', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await sushiSwapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await sushiSwapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = sushiSwapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + sushiSwapV3, + 'quoteExactOutputSingle', + blockNumber, + '0x64e8802FE490fa7cc61d3463958199161Bb608A7', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await sushiSwapV3.getTopPoolsForToken( + TokenB.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, TokenB.address, dexKey); + }); + }); + + describe('Arbitrum', () => { + let blockNumber: number; + let sushiSwapV3: UniswapV3; + let sushiSwapV3Mainnet: UniswapV3; + + const network = Network.ARBITRUM; + const dexHelper = new DummyDexHelper(network); + const TokenASymbol = 'USDCe'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'USDT'; + const TokenB = Tokens[network][TokenBSymbol]; + + beforeEach(async () => { + // blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + blockNumber = 125789437; + sushiSwapV3 = new UniswapV3(network, dexKey, dexHelper); + sushiSwapV3Mainnet = new UniswapV3(Network.ARBITRUM, dexKey, dexHelper); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + const amounts = [0n, 100000000n, 200000000n]; + + const pools = await sushiSwapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await sushiSwapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = sushiSwapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + sushiSwapV3, + 'quoteExactOutputSingle', + blockNumber, + '0x0524E833cCD057e4d7A296e3aaAb9f7675964Ce1', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + }); + + describe('Base', () => { + let blockNumber: number; + let sushiSwapV3: UniswapV3; + let sushiSwapV3Mainnet: UniswapV3; + + const network = Network.BASE; + const dexHelper = new DummyDexHelper(network); + const TokenASymbol = 'USDbC'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'DAI'; + const TokenB = Tokens[network][TokenBSymbol]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + sushiSwapV3 = new UniswapV3(network, dexKey, dexHelper); + sushiSwapV3Mainnet = new UniswapV3(Network.MAINNET, dexKey, dexHelper); + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await sushiSwapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await sushiSwapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.SELL, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = sushiSwapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + sushiSwapV3, + 'quoteExactInputSingle', + blockNumber, + '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await sushiSwapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await sushiSwapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = sushiSwapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + sushiSwapV3, + 'quoteExactOutputSingle', + blockNumber, + '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await sushiSwapV3.getTopPoolsForToken( + TokenB.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, TokenB.address, dexKey); + }); + }); + +}); + diff --git a/src/dex/uniswap-v3/uniswap-v3-pool.ts b/src/dex/uniswap-v3/uniswap-v3-pool.ts index 82342ce24..54163da48 100644 --- a/src/dex/uniswap-v3/uniswap-v3-pool.ts +++ b/src/dex/uniswap-v3/uniswap-v3-pool.ts @@ -12,6 +12,7 @@ import { IDexHelper } from '../../dex-helper/idex-helper'; import { PoolState, DecodedStateMultiCallResultWithRelativeBitmaps, + DecodeStateMultiCallFunc, } from './types'; import UniswapV3PoolABI from '../../abi/uniswap-v3/UniswapV3Pool.abi.json'; import { bigIntify, catchParseLogError, isSampled } from '../../utils'; @@ -60,6 +61,9 @@ export class UniswapV3EventPool extends StatefulEventSubscriber { readonly dexHelper: IDexHelper, parentName: string, readonly stateMultiContract: Contract, + readonly decodeStateMultiCallResultWithRelativeBitmaps: + | DecodeStateMultiCallFunc + | undefined, readonly erc20Interface: Interface, protected readonly factoryAddress: Address, public readonly feeCode: bigint, @@ -226,9 +230,13 @@ export class UniswapV3EventPool extends StatefulEventSubscriber { this.getBitmapRangeToRequest(), ) .encodeABI(), - decodeFunction: decodeStateMultiCallResultWithRelativeBitmaps, + decodeFunction: + this.decodeStateMultiCallResultWithRelativeBitmaps !== undefined + ? this.decodeStateMultiCallResultWithRelativeBitmaps + : decodeStateMultiCallResultWithRelativeBitmaps, }, ]; + this._stateRequestCallData = callData; } return this._stateRequestCallData; diff --git a/src/dex/uniswap-v3/uniswap-v3.ts b/src/dex/uniswap-v3/uniswap-v3.ts index 270a3816d..b99a30ef4 100644 --- a/src/dex/uniswap-v3/uniswap-v3.ts +++ b/src/dex/uniswap-v3/uniswap-v3.ts @@ -91,7 +91,13 @@ export class UniswapV3 public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = getDexKeysWithNetwork( - _.pick(UniswapV3Config, ['UniswapV3', 'QuickSwapV3.1']), + _.pick(UniswapV3Config, [ + 'UniswapV3', + 'SushiSwapV3', + 'QuickSwapV3.1', + 'RamsesV2', + 'ChronosV3', + ]), ); logger: Logger; @@ -118,7 +124,9 @@ export class UniswapV3 this.config.uniswapMulticall, ); this.stateMultiContract = new this.dexHelper.web3Provider.eth.Contract( - UniswapV3StateMulticallABI as AbiItem[], + this.config.stateMultiCallAbi !== undefined + ? this.config.stateMultiCallAbi + : (UniswapV3StateMulticallABI as AbiItem[]), this.config.stateMulticall, ); @@ -239,6 +247,7 @@ export class UniswapV3 this.dexHelper, this.dexKey, this.stateMultiContract, + this.config.decodeStateMultiCallResultWithRelativeBitmaps, this.erc20Interface, this.config.factory, fee, @@ -1055,6 +1064,9 @@ export class UniswapV3 deployer: this.config.deployer?.toLowerCase(), initHash: this.config.initHash, subgraphURL: this.config.subgraphURL, + stateMultiCallAbi: this.config.stateMultiCallAbi, + decodeStateMultiCallResultWithRelativeBitmaps: + this.config.decodeStateMultiCallResultWithRelativeBitmaps, }; return newConfig; } diff --git a/src/dex/weth/config.ts b/src/dex/weth/config.ts index 3b528ab2d..95d779e20 100644 --- a/src/dex/weth/config.ts +++ b/src/dex/weth/config.ts @@ -25,6 +25,9 @@ export const WethConfig: DexConfigMap = { [Network.ZKEVM]: { poolGasCost: WethGasCost, }, + [Network.BASE]: { + poolGasCost: WethGasCost, + }, }, Wbnb: { [Network.BSC]: { diff --git a/src/dex/woo-fi-v2/config.ts b/src/dex/woo-fi-v2/config.ts index 3e353615b..3d020f99a 100644 --- a/src/dex/woo-fi-v2/config.ts +++ b/src/dex/woo-fi-v2/config.ts @@ -64,6 +64,16 @@ export const WooFiV2Config: DexConfigMap = { decimals: 6, }, }, + [Network.BASE]: { + wooPPV2Address: '0xb130a49065178465931d4f887056328CeA5D723f', + wooOracleV2Address: '0x2Fe5E5D341cFFa606a5d9DA1B6B646a381B0f7ec', + integrationHelperAddress: '0xC4E9B633685461E7B7A807D12a246C81f96F31B8', + // USDbC + quoteToken: { + address: '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA', + decimals: 6, + }, + }, }, }; @@ -84,4 +94,7 @@ export const Adapters: Record = { [Network.AVALANCHE]: { [SwapSide.SELL]: [{ name: 'AvalancheAdapter01', index: 12 }], }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 5 }], + }, }; diff --git a/src/dex/woo-fi-v2/woo-fi-v2-e2e.test.ts b/src/dex/woo-fi-v2/woo-fi-v2-e2e.test.ts index 915364c7d..4f0ba65d6 100644 --- a/src/dex/woo-fi-v2/woo-fi-v2-e2e.test.ts +++ b/src/dex/woo-fi-v2/woo-fi-v2-e2e.test.ts @@ -213,4 +213,68 @@ describe('WooFiV2 E2E', () => { tokenQuoteAmount, ); }); + + describe('Base', () => { + const network = Network.BASE; + + const baseATokenSymbol = 'USDbC'; + const baseBTokenSymbol = 'ETH'; + + const tokenBaseAAmount = '100000000'; + const tokenBaseBAmount = '1000000000000000000'; + + const tokens = Tokens[network]; + const holders = Holders[network]; + + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${baseATokenSymbol} -> ${baseBTokenSymbol}`, async () => { + await testE2E( + tokens[baseATokenSymbol], + tokens[baseBTokenSymbol], + holders[baseATokenSymbol], + tokenBaseAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${baseBTokenSymbol} -> ${baseATokenSymbol}`, async () => { + await testE2E( + tokens[baseBTokenSymbol], + tokens[baseATokenSymbol], + holders[baseBTokenSymbol], + tokenBaseBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); }); diff --git a/src/lib/api3-feed.ts b/src/lib/api3-feed.ts new file mode 100644 index 000000000..588204372 --- /dev/null +++ b/src/lib/api3-feed.ts @@ -0,0 +1,140 @@ +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', + ); + static readonly ANSWER_UPDATED_BEACON_SET_DATA = + Api3FeedSubscriber.api3ServerV1Iface.getEventTopic( + 'UpdatedBeaconSetWithBeacons', + ); + + constructor( + private proxy: Address, + api3Server: Address, + private dataFeedId: string, + lens: Lens, DeepReadonly>, + logger: Logger, + ) { + super([api3Server], lens, logger); + } + + static getApi3ServerV1MultiCallInput(proxy: Address): MultiCallInput { + return { + target: proxy, + callData: + Api3FeedSubscriber.proxyInterface.encodeFunctionData('api3ServerV1'), + }; + } + + static getDataFeedId(proxy: Address): MultiCallInput { + return { + target: proxy, + callData: + Api3FeedSubscriber.proxyInterface.encodeFunctionData('dataFeedId'), + }; + } + + static decodeDataFeedId(multicallOutput: MultiCallOutput) { + return Api3FeedSubscriber.proxyInterface.decodeFunctionResult( + 'dataFeedId', + multicallOutput, + )[0]; + } + + 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) { + 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[] { + 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/src/lib/decoders.ts b/src/lib/decoders.ts index ebafa3401..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,6 +51,18 @@ export const uint256ToBigInt = ( return generalDecoder(result, ['uint256'], 0n, value => value[0].toBigInt()); }; +export const uint128ToBigNumber = ( + result: MultiResult | BytesLike, +): EthersBigNumber => { + return generalDecoder(result, ['uint128'], 0n, value => value[0]); +}; + +export const int24ToNumber = ( + result: MultiResult | BytesLike, +): number => { + return generalDecoder(result, ['int24'], 0n, value => value[0]); +}; + export const uint256ArrayDecode = ( result: MultiResult | BytesLike, ): bigint => { 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( diff --git a/src/types.ts b/src/types.ts index 7ba68d56a..4e9133013 100644 --- a/src/types.ts +++ b/src/types.ts @@ -292,6 +292,7 @@ export type PreprocessTransactionOptions = { hmac?: string; mockRfqAndLO?: boolean; isDirectMethod?: boolean; + partner?: string; }; export type TransferFeeParams = { diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index cf0284848..f596301db 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -299,6 +299,18 @@ export const Tokens: { address: '0xc411db5f5eb3f7d552f9b8454b2d74097ccde6e3', decimals: 6, }, + GHO: { + address: '0x40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f', + decimals: 18, + }, + crvUSD: { + address: '0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E', + decimals: 18, + }, + wibBTC: { + address: '0x8751d4196027d4e6da63716fa7786b5174f04c15', + decimals: 18, + }, }, [Network.ROPSTEN]: { DAI: { @@ -448,6 +460,10 @@ export const Tokens: { address: '0x049d68029688eabf473097a2fc38ef61633a3c7a', decimals: 6, }, + EQUAL: { + address: '0x3fd3a0c85b70754efc07ac9ac0cbbdce664865a6', + decimals: 18, + }, POPS: { address: '0x9dE4b40bDcE50Ec6a1A668bF85997BbBD324069a', decimals: 18, @@ -845,6 +861,63 @@ export const Tokens: { decimals: 8, }, }, + [Network.ZKEVM]: { + ETH: { + address: ETHER_ADDRESS, + decimals: 18, + }, + WETH: { + address: '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', + decimals: 18, + }, + MATIC: { + address: '0xa2036f0538221a77a3937f1379699f44945018d0', + decimals: 18, + }, + WBTC: { + address: '0xea034fb02eb1808c2cc3adbc15f447b93cbe08e1', + decimals: 8, + }, + USDC: { + address: '0xa8ce8aee21bc2a48a5ef670afcc9274c7bbbc035', + decimals: 6, + }, + }, + [Network.BASE]: { + PRIME: { + address: '0xfA980cEd6895AC314E7dE34Ef1bFAE90a5AdD21b', + decimals: 18, + }, + WETH: { + address: '0x4200000000000000000000000000000000000006', + decimals: 18, + }, + MAV: { + address: '0x64b88c73A5DfA78D1713fE1b4c69a22d7E0faAa7', + decimals: 18, + }, + USDC: { + address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', + decimals: 6, + }, + USDbC: { + address: '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA', + decimals: 6, + }, + DAI: { + address: '0x50c5725949a6f0c72e6c4a641f24049a917db0cb', + decimals: 18, + }, + BAL: { + address: '0x4158734d47fc9692176b5085e0f52ee0da5d47f1', + decimals: 18, + }, + GOLD: { + address: '0xbeFD5C25A59ef2C1316c5A4944931171F30Cd3E4', + decimals: 18, + }, + ETH: { address: ETHER_ADDRESS, decimals: 18 }, + }, }; export const Holders: { @@ -908,6 +981,9 @@ export const Holders: { aEthWETH: '0x645C4c0c95C1Aa6EF25d12f4a25038cA9b0C6Cc7', dUSDC: '0x2FC2F705110A7F46Ce85F701d7217EF1018f01A3', PSP: '0xE5E5440a1CE69C5cf67BFFA74d185e57c31b43E5', + crvUSD: '0xA920De414eA4Ab66b97dA1bFE9e6EcA7d4219635', + GHO: '0x844Dc85EdD8492A56228D293cfEbb823EF3E10EC', + wibBTC: '0xFbdCA68601f835b27790D98bbb8eC7f05FDEaA9B', }, [Network.ROPSTEN]: { ETH: '0x43262A12d8610AA70C15DbaeAC321d51613c9071', @@ -948,7 +1024,7 @@ export const Holders: { DAI: '0x370f4b2dcf75c94d8d4450b493661a9c6170d0b5', FTM: '0x431e81E5dfB5A24541b5Ff8762bDEF3f32F96354', WFTM: '0x3e923747ca2675e096d812c3b24846ac39aed645', - USDC: '0xe48793b1533b351ae184e1c3119d0955dde7b330', + USDC: '0xf53feaeb035361c046e5669745695e450ebb4028', FUSDT: '0x9ade1c17d25246c405604344f89E8F23F8c1c632', POPS: '0x4b78b52e7de4d8b7d367297cb8a87c1875a9d591', aFanUSDT: '0x8EBc96fF91A30059E447bFC7C0a7394f8A5793E6', @@ -964,6 +1040,7 @@ export const Holders: { ETH: '0xf48883940b4056801de30f12b934dcea90133ee6', GUSDC: '0x894d774a293f8aa3d23d67815d4cadb5319c1094', GDAI: '0x0e2ed73f9c1409e2b36fe6c46e60d4557b7c2ac0', + EQUAL: '0x8b187ea19c93091a4d6b426b71871648182b5fac', }, [Network.BSC]: { DAI: '0xf68a4b64162906eff0ff6ae34e2bb1cd42fef62d', @@ -1020,7 +1097,7 @@ export const Holders: { USDCe: '0x62383739d68dd0f844103db8dfb05a7eded5bbe6', USDC: '0xa843392198862f98d17e3aa1421b08f2c2020cff', OHM: '0xebce5f29ff5ca9aa330ebdf7ec6b5f474bff271e', - USDT: '0x62383739d68dd0f844103db8dfb05a7eded5bbe6', + USDT: '0x5ff47d4ab75bcaff6807c81f1367abb53439883c', POPS: '0x4b78b52e7de4d8b7d367297cb8a87c1875a9d591', FRAX: '0x59bf0545fca0e5ad48e13da269facd2e8c886ba4', nUSD: '0x9dd329f5411466d9e0c488ff72519ca9fef0cb40', @@ -1053,6 +1130,24 @@ export const Holders: { rETH: '0x4c2e69e58b14de9afedfb94319519ce34e087283', WBTC: '0xb9c8f0d3254007ee4b98970b94544e473cd610ec', }, + [Network.ZKEVM]: { + ETH: '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', + WETH: '0xc44ad482f24fd750caeba387d2726d8653f8c4bb', + MATIC: '0x8f2a1450c040b3c19efe9676165d8f30d8280019', + WBTC: '0x99b31498b0a1dae01fc3433e3cb60f095340935c', + USDC: '0x99b31498b0a1dae01fc3433e3cb60f095340935c', + }, + [Network.BASE]: { + WETH: '0x4bb6b2efe7036020ba6f02a05602546c9f25bf28', + PRIME: '0x956bcc6b56c99db382d9d97a30ba5f1402144b3e', + ETH: '0xdd9176ea3e7559d6b68b537ef555d3e89403f742', + MAV: '0x7499785aa5d1bdf0a0ac862c1ef3698d3cba6568', + USDC: '0xaac391f166f33cdaefaa4afa6616a3bea66b694d', + USDbC: '0xc9d05a1c3c8e01dcb701d6185cdc21a5bb94becb', + DAI: '0x20f03e26968b179025f65c1f4afadfd3959c8d03', + BAL: '0x854b004700885a61107b458f11ecc169a019b764', + GOLD: '0x1374c25b3710758c326ee0c70ec48b595d5ccf8c', + }, }; export const SmartTokens = Object.keys(Tokens).reduce((acc, _network) => { @@ -1077,4 +1172,5 @@ export const NativeTokenSymbols: { [network: number]: string } = { [Network.FANTOM]: 'FTM', [Network.ARBITRUM]: 'ETH', [Network.OPTIMISM]: 'ETH', + [Network.BASE]: 'ETH', }; diff --git a/tests/tenderly-simulation.ts b/tests/tenderly-simulation.ts index f24bbda98..ddc53c786 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; @@ -104,13 +149,13 @@ export class TenderlySimulation { return { success: true, gasUsed: data.transaction.gas_used, - tenderlyUrl: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/fork/${this.forkId}/simulation/${lastTx}`, + url: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/fork/${this.forkId}/simulation/${lastTx}`, transaction: data.transaction, }; } else { return { success: false, - tenderlyUrl: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/fork/${this.forkId}/simulation/${lastTx}`, + url: `https://dashboard.tenderly.co/${TENDERLY_ACCOUNT_ID}/${TENDERLY_PROJECT}/fork/${this.forkId}/simulation/${lastTx}`, error: `Simulation failed: ${data.transaction.error_info.error_message} at ${data.transaction.error_info.address}`, }; } @@ -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..ece7ac264 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'; @@ -82,6 +86,7 @@ const MULTISIG: { [nid: number]: string } = { [Network.AVALANCHE]: '0x1e2ECA5e812D08D2A7F8664D69035163ff5BfEC2', [Network.OPTIMISM]: '0xf01121e808F782d7F34E857c27dA31AD1f151b39', [Network.ARBITRUM]: '0x90DfD8a6454CFE19be39EaB42ac93CD850c7f339', + [Network.BASE]: '0x6C674c8Df1aC663b822c4B6A56B4E5e889379AE0', }; class APIParaswapSDK implements IParaSwapSDK { @@ -268,16 +273,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); } @@ -290,6 +299,7 @@ export async function testE2E( ), ); expect(whitelistTx.success).toEqual(true); + console.log(`Successfully whitelisted ${deployedTestContractAddress}`); if (testContractType === 'router') { const setImplementationTx = await ts.simulate( @@ -309,7 +319,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 +394,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) { @@ -614,7 +624,7 @@ export async function newTestE2E({ parseInt(priceRoute.gasCost) - parseInt(swapTx!.gasUsed) }`, ); - console.log(`Tenderly URL: ${swapTx!.tenderlyUrl}`); + console.log(`Tenderly URL: ${swapTx!.url}`); expect(swapTx!.success).toEqual(true); } } finally {