diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..cc27ae949 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +HTTP_PROVIDER_1= +HTTP_PROVIDER_137= +HTTP_PROVIDER_43114= + +TENDERLY_TOKEN= +TENDERLY_ACCOUNT_ID= +TENDERLY_PROJECT= \ No newline at end of file diff --git a/package.json b/package.json index 4aa5e4e84..aafb6f781 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.28.9", + "version": "2.47.2", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", @@ -51,15 +51,17 @@ }, "dependencies": { "@0x/utils": "^4.5.2", + "@bgd-labs/aave-address-book": "2.21.1", + "@ethersproject/abi": "^5.7.0", "@hashflow/sdk": "1.2.4", - "@hashflow/taker-js": "0.0.2", - "@paraswap/core": "1.1.0", + "@hashflow/taker-js": "0.3.4", + "@paraswap/core": "2.0.0", "async": "^3.2.4", "axios": "0.26.0", "bignumber.js": "9.1.0", "cross-fetch": "^3.1.5", "es6-promise": "^4.2.8", - "ethers": "^5.6.5", + "ethers": "^5.7.2", "joi": "^17.7.0", "lens.ts": "^0.5.1", "lodash": "4.17.21", diff --git a/scripts/dex-integration.ts b/scripts/dex-integration.ts index e3487e690..ad04f3b19 100644 --- a/scripts/dex-integration.ts +++ b/scripts/dex-integration.ts @@ -116,7 +116,7 @@ function testIntegration(argv: IOptions) { process.env.NODE_ENV = 'test'; } - require('../node_modules/jest-cli/build/cli').run( + require('../node_modules/jest-cli/build').run( `src\/dex\/${dexNameParam}\/.+\.test\.ts`, ); } 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/algebra/SwapRouter.json b/src/abi/algebra/SwapRouter.json new file mode 100644 index 000000000..c41ec4a89 --- /dev/null +++ b/src/abi/algebra/SwapRouter.json @@ -0,0 +1,281 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_factory", "type": "address" }, + { "internalType": "address", "name": "_WNativeToken", "type": "address" }, + { "internalType": "address", "name": "_poolDeployer", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "WNativeToken", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "int256", "name": "amount0Delta", "type": "int256" }, + { "internalType": "int256", "name": "amount1Delta", "type": "int256" }, + { "internalType": "bytes", "name": "_data", "type": "bytes" } + ], + "name": "algebraSwapCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "bytes", "name": "path", "type": "bytes" }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, + { + "internalType": "uint256", + "name": "amountOutMinimum", + "type": "uint256" + } + ], + "internalType": "struct ISwapRouter.ExactInputParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactInput", + "outputs": [ + { "internalType": "uint256", "name": "amountOut", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "address", "name": "tokenOut", "type": "address" }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, + { + "internalType": "uint256", + "name": "amountOutMinimum", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "limitSqrtPrice", + "type": "uint160" + } + ], + "internalType": "struct ISwapRouter.ExactInputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactInputSingleSupportingFeeOnTransferTokens", + "outputs": [ + { "internalType": "uint256", "name": "amountOut", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "bytes", "name": "path", "type": "bytes" }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint256", "name": "amountOut", "type": "uint256" }, + { + "internalType": "uint256", + "name": "amountInMaximum", + "type": "uint256" + } + ], + "internalType": "struct ISwapRouter.ExactOutputParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactOutput", + "outputs": [ + { "internalType": "uint256", "name": "amountIn", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "address", "name": "tokenOut", "type": "address" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint256", "name": "amountOut", "type": "uint256" }, + { + "internalType": "uint256", + "name": "amountInMaximum", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "limitSqrtPrice", + "type": "uint160" + } + ], + "internalType": "struct ISwapRouter.ExactOutputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactOutputSingle", + "outputs": [ + { "internalType": "uint256", "name": "amountIn", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes[]", "name": "data", "type": "bytes[]" } + ], + "name": "multicall", + "outputs": [ + { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "poolDeployer", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "refundNativeToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "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": "selfPermit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "expiry", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "selfPermitAllowed", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "expiry", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "selfPermitAllowedIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "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": "selfPermitIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "amountMinimum", "type": "uint256" }, + { "internalType": "address", "name": "recipient", "type": "address" } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "amountMinimum", "type": "uint256" }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "feeBips", "type": "uint256" }, + { "internalType": "address", "name": "feeRecipient", "type": "address" } + ], + "name": "sweepTokenWithFee", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amountMinimum", "type": "uint256" }, + { "internalType": "address", "name": "recipient", "type": "address" } + ], + "name": "unwrapWNativeToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amountMinimum", "type": "uint256" }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "feeBips", "type": "uint256" }, + { "internalType": "address", "name": "feeRecipient", "type": "address" } + ], + "name": "unwrapWNativeTokenWithFee", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/src/abi/api3-proxy.json b/src/abi/api3-proxy.json new file mode 100644 index 000000000..ec343f133 --- /dev/null +++ b/src/abi/api3-proxy.json @@ -0,0 +1,53 @@ +[ + { + "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" + }, + { + "inputs": [], + "name": "dapiNameHash", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "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/dexalot/DexalotMainnetRFQ.json b/src/abi/dexalot/DexalotMainnetRFQ.json new file mode 100644 index 000000000..b6139cae1 --- /dev/null +++ b/src/abi/dexalot/DexalotMainnetRFQ.json @@ -0,0 +1,952 @@ +[ + { + "name": "AddressSet", + "type": "event", + "inputs": [ + { + "name": "name", + "type": "string", + "indexed": true, + "internalType": "string" + }, + { + "name": "actionName", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "newAddress", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "ExpiryUpdated", + "type": "event", + "inputs": [ + { + "name": "nonceAndMeta", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newExpiry", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "name": "Initialized", + "type": "event", + "inputs": [ + { + "name": "version", + "type": "uint8", + "indexed": false, + "internalType": "uint8" + } + ], + "anonymous": false + }, + { + "name": "Paused", + "type": "event", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "RebalancerWithdraw", + "type": "event", + "inputs": [ + { + "name": "asset", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "name": "RoleAdminChanged", + "type": "event", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "previousAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "newAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "name": "RoleGranted", + "type": "event", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "RoleRevoked", + "type": "event", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "RoleUpdated", + "type": "event", + "inputs": [ + { + "name": "name", + "type": "string", + "indexed": true, + "internalType": "string" + }, + { + "name": "actionName", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "updatedRole", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "updatedAddress", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "SlippageApplied", + "type": "event", + "inputs": [ + { + "name": "nonceAndMeta", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newMakerAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "name": "SlippageToleranceUpdated", + "type": "event", + "inputs": [ + { + "name": "newSlippageTolerance", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "name": "SwapExecuted", + "type": "event", + "inputs": [ + { + "name": "nonceAndMeta", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "maker", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "taker", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "makerAsset", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "takerAsset", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "makerAmountReceived", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "takerAmountReceived", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "name": "SwapSignerUpdated", + "type": "event", + "inputs": [ + { + "name": "newSwapSigner", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "Unpaused", + "type": "event", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "DEFAULT_ADMIN_ROLE", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "name": "REBALANCER_ADMIN_ROLE", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "name": "VERSION", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "name": "addAdmin", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "addRebalancer", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "batchClaimBalance", + "type": "function", + "inputs": [ + { + "name": "_assets", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "_amounts", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "claimBalance", + "type": "function", + "inputs": [ + { + "name": "_asset", + "type": "address", + "internalType": "address" + }, + { + "name": "_amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "getRoleAdmin", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "name": "getRoleMember", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "name": "getRoleMemberCount", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "name": "grantRole", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "hasRole", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "name": "initialize", + "type": "function", + "inputs": [ + { + "name": "_swapSigner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "isAdmin", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "name": "isRebalancer", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "name": "isValidSignature", + "type": "function", + "inputs": [ + { + "name": "_hash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "_signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "view" + }, + { + "name": "orderExpiryUpdated", + "type": "function", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "name": "orderMakerAmountUpdated", + "type": "function", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "name": "partialSwap", + "type": "function", + "inputs": [ + { + "name": "_order", + "type": "tuple", + "components": [ + { + "name": "nonceAndMeta", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "expiry", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "makerAsset", + "type": "address", + "internalType": "address" + }, + { + "name": "takerAsset", + "type": "address", + "internalType": "address" + }, + { + "name": "maker", + "type": "address", + "internalType": "address" + }, + { + "name": "taker", + "type": "address", + "internalType": "address" + }, + { + "name": "makerAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "takerAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "internalType": "struct MainnetRFQ.Order" + }, + { + "name": "_signature", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_takerAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "name": "pause", + "type": "function", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "paused", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "name": "removeAdmin", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "removeRebalancer", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "renounceRole", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "revokeRole", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "setSlippageTolerance", + "type": "function", + "inputs": [ + { + "name": "_newSlippageTolerance", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "setSwapSigner", + "type": "function", + "inputs": [ + { + "name": "_swapSigner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "simpleSwap", + "type": "function", + "inputs": [ + { + "name": "_order", + "type": "tuple", + "components": [ + { + "name": "nonceAndMeta", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "expiry", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "makerAsset", + "type": "address", + "internalType": "address" + }, + { + "name": "takerAsset", + "type": "address", + "internalType": "address" + }, + { + "name": "maker", + "type": "address", + "internalType": "address" + }, + { + "name": "taker", + "type": "address", + "internalType": "address" + }, + { + "name": "makerAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "takerAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "internalType": "struct MainnetRFQ.Order" + }, + { + "name": "_signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "name": "slippageTolerance", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "name": "supportsInterface", + "type": "function", + "inputs": [ + { + "name": "interfaceId", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "name": "swapSigner", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "name": "unpause", + "type": "function", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "updateOrderExpiry", + "type": "function", + "inputs": [ + { + "name": "_nonceAndMeta", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_newExpiry", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "updateOrderMakerAmount", + "type": "function", + "inputs": [ + { + "name": "_nonceAndMeta", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_newMakerAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_oldMakerAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + } +] diff --git a/src/abi/hashflow/HashflowRouter.abi.json b/src/abi/hashflow/HashflowRouter.abi.json index 3d364001c..5bff2950b 100644 --- a/src/abi/hashflow/HashflowRouter.abi.json +++ b/src/abi/hashflow/HashflowRouter.abi.json @@ -5,28 +5,53 @@ { "indexed": false, "internalType": "address", - "name": "governance", + "name": "guardian", "type": "address" - }, + } + ], + "name": "UpdateLimitOrderGuardian", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ { "indexed": false, "internalType": "address", - "name": "prevGovernance", + "name": "pool", "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "authorized", + "type": "bool" } ], - "name": "UpdateGovernance", + "name": "UpdatePoolAuthorizaton", "type": "event" }, { "anonymous": false, "inputs": [ { - "indexed": false, + "indexed": true, "internalType": "address", - "name": "migrationRouter", + "name": "pool", "type": "address" }, + { + "indexed": false, + "internalType": "uint16", + "name": "otherHashflowChainId", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "caller", + "type": "bytes32" + }, { "indexed": false, "internalType": "bool", @@ -34,18 +59,24 @@ "type": "bool" } ], - "name": "UpdateMigrationRouterStatus", + "name": "UpdateXChainCallerAuthorization", "type": "event" }, { "anonymous": false, "inputs": [ { - "indexed": false, + "indexed": true, "internalType": "address", "name": "pool", "type": "address" }, + { + "indexed": false, + "internalType": "address", + "name": "xChainMessenger", + "type": "address" + }, { "indexed": false, "internalType": "bool", @@ -53,26 +84,63 @@ "type": "bool" } ], - "name": "UpdatePoolAuthorizaton", + "name": "UpdateXChainMessengerAuthorization", "type": "event" }, { "anonymous": false, "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "callee", + "type": "address" + }, { "indexed": false, "internalType": "address", - "name": "xChainUa", + "name": "xChainMessenger", "type": "address" }, { "indexed": false, + "internalType": "bool", + "name": "authorized", + "type": "bool" + } + ], + "name": "UpdateXChainMessengerCallerAuthorization", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, "internalType": "address", - "name": "prevXChainUa", + "name": "pool", "type": "address" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "otherHashflowChainId", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "otherChainPool", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bool", + "name": "authorized", + "type": "bool" } ], - "name": "UpdateXChainUA", + "name": "UpdateXChainPoolAuthorization", "type": "event" }, { @@ -81,21 +149,395 @@ "internalType": "address", "name": "pool", "type": "address" + } + ], + "name": "authorizedPools", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dstContract", + "type": "address" + }, + { + "internalType": "uint16", + "name": "srcHashflowChainId", + "type": "uint16" }, + { + "internalType": "bytes32", + "name": "caller", + "type": "bytes32" + } + ], + "name": "authorizedXChainCallers", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", - "name": "token", + "name": "callee", + "type": "address" + }, + { + "internalType": "address", + "name": "messenger", + "type": "address" + } + ], + "name": "authorizedXChainMessengersByCallee", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "messenger", + "type": "address" + } + ], + "name": "authorizedXChainMessengersByPool", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "dstPool", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "srcHChainId", + "type": "uint16" + }, + { + "internalType": "bytes32", + "name": "srcPool", + "type": "bytes32" + } + ], + "name": "authorizedXChainPools", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint16", + "name": "srcHashflowChainId", + "type": "uint16" + }, + { + "internalType": "bytes32", + "name": "srcPool", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "dstPool", + "type": "address" + }, + { + "internalType": "address", + "name": "dstExternalAccount", + "type": "address" + }, + { + "internalType": "address", + "name": "dstTrader", + "type": "address" + }, + { + "internalType": "address", + "name": "quoteToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "quoteTokenAmount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "txid", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "srcCaller", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "dstContract", + "type": "address" + }, + { + "internalType": "bytes", + "name": "dstContractCalldata", + "type": "bytes" + } + ], + "internalType": "struct IHashflowRouter.XChainFillMessage", + "name": "fillMessage", + "type": "tuple" + } + ], + "name": "fillXChain", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "forceUnauthorizePool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", "type": "address" }, { - "internalType": "uint256", - "name": "amount", - "type": "uint256" + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "killswitchPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "externalAccount", + "type": "address" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "address", + "name": "baseToken", + "type": "address" + }, + { + "internalType": "address", + "name": "quoteToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "baseTokenAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "quoteTokenAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "quoteExpiry", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "txid", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "takerSignature", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "makerSignature", + "type": "bytes" + } + ], + "internalType": "struct IQuote.RFQMQuote", + "name": "quote", + "type": "tuple" + } + ], + "name": "tradeRFQM", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "externalAccount", + "type": "address" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "address", + "name": "baseToken", + "type": "address" + }, + { + "internalType": "address", + "name": "quoteToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "baseTokenAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "quoteTokenAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "quoteExpiry", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "txid", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "takerSignature", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "makerSignature", + "type": "bytes" + } + ], + "internalType": "struct IQuote.RFQMQuote", + "name": "quote", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "guardianSignature", + "type": "bytes" } ], - "name": "addLiquidityPrivatePool", + "name": "tradeRFQMLimitOrder", "outputs": [], - "stateMutability": "payable", + "stateMutability": "nonpayable", "type": "function" }, { @@ -109,124 +551,63 @@ }, { "internalType": "address", - "name": "token", + "name": "externalAccount", + "type": "address" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "address", + "name": "baseToken", + "type": "address" + }, + { + "internalType": "address", + "name": "quoteToken", "type": "address" }, { "internalType": "uint256", - "name": "amount", + "name": "baseTokenAmount", "type": "uint256" }, { "internalType": "uint256", - "name": "nonce", + "name": "quoteTokenAmount", "type": "uint256" }, + { + "internalType": "uint256", + "name": "quoteExpiry", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "txid", + "type": "bytes32" + }, { "internalType": "bytes", - "name": "signature", + "name": "takerSignature", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "makerSignature", "type": "bytes" } ], - "internalType": "struct IQuote.Deposit", - "name": "deposit", + "internalType": "struct IQuote.RFQMQuote", + "name": "quote", "type": "tuple" - } - ], - "name": "addLiquidityPublicPool", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - } - ], - "name": "isPoolAuthorized", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "bool", - "name": "enabled", - "type": "bool" - } - ], - "name": "killswitchPool", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "migrationRouter", - "type": "address" - } - ], - "name": "migratePoolAuthorization", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "burnAmount", - "type": "uint256" - } - ], - "name": "removeLiquidityPublicPool", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "address", - "name": "token", - "type": "address" }, { - "internalType": "uint256", - "name": "burnAmount", - "type": "uint256" + "internalType": "bytes", + "name": "guardianSignature", + "type": "bytes" }, { "internalType": "uint256", @@ -249,12 +630,12 @@ "type": "bytes32" }, { - "internalType": "bool", - "name": "approveMax", - "type": "bool" + "internalType": "uint256", + "name": "amountToApprove", + "type": "uint256" } ], - "name": "removeLiquidityPublicPoolWithPermit", + "name": "tradeRFQMLimitOrderWithPermit", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -278,11 +659,6 @@ "name": "trader", "type": "address" }, - { - "internalType": "address", - "name": "effectiveTrader", - "type": "address" - }, { "internalType": "address", "name": "baseToken", @@ -295,17 +671,12 @@ }, { "internalType": "uint256", - "name": "effectiveBaseTokenAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxBaseTokenAmount", + "name": "baseTokenAmount", "type": "uint256" }, { "internalType": "uint256", - "name": "maxQuoteTokenAmount", + "name": "quoteTokenAmount", "type": "uint256" }, { @@ -313,11 +684,6 @@ "name": "quoteExpiry", "type": "uint256" }, - { - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, { "internalType": "bytes32", "name": "txid", @@ -325,28 +691,48 @@ }, { "internalType": "bytes", - "name": "signature", + "name": "takerSignature", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "makerSignature", "type": "bytes" } ], - "internalType": "struct IQuote.RFQTQuote[]", - "name": "quotes", - "type": "tuple[]" + "internalType": "struct IQuote.RFQMQuote", + "name": "quote", + "type": "tuple" }, { - "internalType": "address", - "name": "baseToken", - "type": "address" + "internalType": "uint256", + "name": "deadline", + "type": "uint256" }, { - "internalType": "address", - "name": "quoteToken", - "type": "address" + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountToApprove", + "type": "uint256" } ], - "name": "tradeMultiHop", + "name": "tradeRFQMWithPermit", "outputs": [], - "stateMutability": "payable", + "stateMutability": "nonpayable", "type": "function" }, { @@ -368,6 +754,11 @@ "name": "trader", "type": "address" }, + { + "internalType": "address", + "name": "effectiveTrader", + "type": "address" + }, { "internalType": "address", "name": "baseToken", @@ -378,6 +769,11 @@ "name": "quoteToken", "type": "address" }, + { + "internalType": "uint256", + "name": "effectiveBaseTokenAmount", + "type": "uint256" + }, { "internalType": "uint256", "name": "baseTokenAmount", @@ -393,6 +789,11 @@ "name": "quoteExpiry", "type": "uint256" }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, { "internalType": "bytes32", "name": "txid", @@ -400,23 +801,18 @@ }, { "internalType": "bytes", - "name": "takerSignature", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "makerSignature", + "name": "signature", "type": "bytes" } ], - "internalType": "struct IQuote.RFQMQuote", + "internalType": "struct IQuote.RFQTQuote", "name": "quote", "type": "tuple" } ], - "name": "tradeRFQm", + "name": "tradeRFQT", "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "payable", "type": "function" }, { @@ -438,6 +834,11 @@ "name": "trader", "type": "address" }, + { + "internalType": "address", + "name": "effectiveTrader", + "type": "address" + }, { "internalType": "address", "name": "baseToken", @@ -448,6 +849,11 @@ "name": "quoteToken", "type": "address" }, + { + "internalType": "uint256", + "name": "effectiveBaseTokenAmount", + "type": "uint256" + }, { "internalType": "uint256", "name": "baseTokenAmount", @@ -464,22 +870,22 @@ "type": "uint256" }, { - "internalType": "bytes32", - "name": "txid", - "type": "bytes32" + "internalType": "uint256", + "name": "nonce", + "type": "uint256" }, { - "internalType": "bytes", - "name": "takerSignature", - "type": "bytes" + "internalType": "bytes32", + "name": "txid", + "type": "bytes32" }, { "internalType": "bytes", - "name": "makerSignature", + "name": "signature", "type": "bytes" } ], - "internalType": "struct IQuote.RFQMQuote", + "internalType": "struct IQuote.RFQTQuote", "name": "quote", "type": "tuple" }, @@ -504,12 +910,12 @@ "type": "bytes32" }, { - "internalType": "bool", - "name": "approveMax", - "type": "bool" + "internalType": "uint256", + "name": "amountToApprove", + "type": "uint256" } ], - "name": "tradeRFQmWithPermit", + "name": "tradeRFQTWithPermit", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -518,25 +924,45 @@ "inputs": [ { "components": [ + { + "internalType": "uint16", + "name": "srcChainId", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "dstChainId", + "type": "uint16" + }, { "internalType": "address", - "name": "pool", + "name": "srcPool", "type": "address" }, + { + "internalType": "bytes32", + "name": "dstPool", + "type": "bytes32" + }, { "internalType": "address", - "name": "externalAccount", + "name": "srcExternalAccount", "type": "address" }, + { + "internalType": "bytes32", + "name": "dstExternalAccount", + "type": "bytes32" + }, { "internalType": "address", "name": "trader", "type": "address" }, { - "internalType": "address", - "name": "effectiveTrader", - "type": "address" + "internalType": "bytes32", + "name": "dstTrader", + "type": "bytes32" }, { "internalType": "address", @@ -544,23 +970,18 @@ "type": "address" }, { - "internalType": "address", + "internalType": "bytes32", "name": "quoteToken", - "type": "address" - }, - { - "internalType": "uint256", - "name": "effectiveBaseTokenAmount", - "type": "uint256" + "type": "bytes32" }, { "internalType": "uint256", - "name": "maxBaseTokenAmount", + "name": "baseTokenAmount", "type": "uint256" }, { "internalType": "uint256", - "name": "maxQuoteTokenAmount", + "name": "quoteTokenAmount", "type": "uint256" }, { @@ -568,28 +989,43 @@ "name": "quoteExpiry", "type": "uint256" }, - { - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, { "internalType": "bytes32", "name": "txid", "type": "bytes32" }, + { + "internalType": "address", + "name": "xChainMessenger", + "type": "address" + }, { "internalType": "bytes", - "name": "signature", + "name": "takerSignature", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "makerSignature", "type": "bytes" } ], - "internalType": "struct IQuote.RFQTQuote", + "internalType": "struct IQuote.XChainRFQMQuote", "name": "quote", "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "dstContract", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "dstCalldata", + "type": "bytes" } ], - "name": "tradeSingleHop", + "name": "tradeXChainRFQM", "outputs": [], "stateMutability": "payable", "type": "function" @@ -633,15 +1069,20 @@ "name": "trader", "type": "address" }, + { + "internalType": "bytes32", + "name": "dstTrader", + "type": "bytes32" + }, { "internalType": "address", "name": "baseToken", "type": "address" }, { - "internalType": "address", + "internalType": "bytes32", "name": "quoteToken", - "type": "address" + "type": "bytes32" }, { "internalType": "uint256", @@ -658,33 +1099,68 @@ "name": "quoteExpiry", "type": "uint256" }, - { - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, { "internalType": "bytes32", "name": "txid", "type": "bytes32" }, + { + "internalType": "address", + "name": "xChainMessenger", + "type": "address" + }, { "internalType": "bytes", - "name": "signature", + "name": "takerSignature", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "makerSignature", "type": "bytes" } ], - "internalType": "struct IQuote.XChainRFQTQuote", + "internalType": "struct IQuote.XChainRFQMQuote", "name": "quote", "type": "tuple" }, { - "internalType": "enum IHashflowXChainUA.XChainMessageProtocol", - "name": "protocol", + "internalType": "bytes32", + "name": "dstContract", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "dstCalldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amountToApprove", + "type": "uint256" } ], - "name": "tradeXChain", + "name": "tradeXChainRFQMWithPermit", "outputs": [], "stateMutability": "payable", "type": "function" @@ -724,9 +1200,9 @@ "type": "bytes32" }, { - "internalType": "address", - "name": "trader", - "type": "address" + "internalType": "bytes32", + "name": "dstTrader", + "type": "bytes32" }, { "internalType": "address", @@ -734,9 +1210,14 @@ "type": "address" }, { - "internalType": "address", + "internalType": "bytes32", "name": "quoteToken", - "type": "address" + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "effectiveBaseTokenAmount", + "type": "uint256" }, { "internalType": "uint256", @@ -753,33 +1234,43 @@ "name": "quoteExpiry", "type": "uint256" }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, { "internalType": "bytes32", "name": "txid", "type": "bytes32" }, { - "internalType": "bytes", - "name": "takerSignature", - "type": "bytes" + "internalType": "address", + "name": "xChainMessenger", + "type": "address" }, { "internalType": "bytes", - "name": "makerSignature", + "name": "signature", "type": "bytes" } ], - "internalType": "struct IQuote.XChainRFQMQuote", + "internalType": "struct IQuote.XChainRFQTQuote", "name": "quote", "type": "tuple" }, { - "internalType": "enum IHashflowXChainUA.XChainMessageProtocol", - "name": "protocol", - "type": "uint8" + "internalType": "bytes32", + "name": "dstContract", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "dstCalldata", + "type": "bytes" } ], - "name": "tradeXChainRFQm", + "name": "tradeXChainRFQT", "outputs": [], "stateMutability": "payable", "type": "function" @@ -819,9 +1310,9 @@ "type": "bytes32" }, { - "internalType": "address", - "name": "trader", - "type": "address" + "internalType": "bytes32", + "name": "dstTrader", + "type": "bytes32" }, { "internalType": "address", @@ -829,9 +1320,14 @@ "type": "address" }, { - "internalType": "address", + "internalType": "bytes32", "name": "quoteToken", - "type": "address" + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "effectiveBaseTokenAmount", + "type": "uint256" }, { "internalType": "uint256", @@ -848,30 +1344,40 @@ "name": "quoteExpiry", "type": "uint256" }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, { "internalType": "bytes32", "name": "txid", "type": "bytes32" }, { - "internalType": "bytes", - "name": "takerSignature", - "type": "bytes" + "internalType": "address", + "name": "xChainMessenger", + "type": "address" }, { "internalType": "bytes", - "name": "makerSignature", + "name": "signature", "type": "bytes" } ], - "internalType": "struct IQuote.XChainRFQMQuote", + "internalType": "struct IQuote.XChainRFQTQuote", "name": "quote", "type": "tuple" }, { - "internalType": "enum IHashflowXChainUA.XChainMessageProtocol", - "name": "protocol", - "type": "uint8" + "internalType": "bytes32", + "name": "dstContract", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "dstCalldata", + "type": "bytes" }, { "internalType": "uint256", @@ -894,12 +1400,12 @@ "type": "bytes32" }, { - "internalType": "bool", - "name": "approveMax", - "type": "bool" + "internalType": "uint256", + "name": "amountToApprove", + "type": "uint256" } ], - "name": "tradeXChainRFQmWithPermit", + "name": "tradeXChainRFQTWithPermit", "outputs": [], "stateMutability": "payable", "type": "function" @@ -908,11 +1414,11 @@ "inputs": [ { "internalType": "address", - "name": "governance", + "name": "guardian", "type": "address" } ], - "name": "updateGovernance", + "name": "updateLimitOrderGuardian", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -921,16 +1427,39 @@ "inputs": [ { "internalType": "address", - "name": "router", + "name": "pool", "type": "address" }, { "internalType": "bool", - "name": "status", + "name": "authorized", + "type": "bool" + } + ], + "name": "updatePoolAuthorization", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "otherHashflowChainId", + "type": "uint16" + }, + { + "internalType": "bytes32", + "name": "caller", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "authorized", "type": "bool" } ], - "name": "updateMigrationRouterStatus", + "name": "updateXChainCallerAuthorization", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -939,7 +1468,7 @@ "inputs": [ { "internalType": "address", - "name": "pool", + "name": "xChainMessenger", "type": "address" }, { @@ -948,7 +1477,48 @@ "type": "bool" } ], - "name": "updatePoolAuthorization", + "name": "updateXChainMessengerAuthorization", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "xChainMessenger", + "type": "address" + }, + { + "internalType": "bool", + "name": "authorized", + "type": "bool" + } + ], + "name": "updateXChainMessengerCallerAuthorization", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "otherHashflowChainId", + "type": "uint16" + }, + { + "internalType": "bytes32", + "name": "otherPool", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "authorized", + "type": "bool" + } + ], + "name": "updateXChainPoolAuthorization", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -957,11 +1527,11 @@ "inputs": [ { "internalType": "address", - "name": "xChainUa", + "name": "token", "type": "address" } ], - "name": "updateXChainUa", + "name": "withdrawFunds", "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/pancakeswap-v3/PancakeswapV3Factory.abi.json b/src/abi/pancakeswap-v3/PancakeswapV3Factory.abi.json new file mode 100644 index 000000000..aaaddbdab --- /dev/null +++ b/src/abi/pancakeswap-v3/PancakeswapV3Factory.abi.json @@ -0,0 +1,300 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_poolDeployer", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + } + ], + "name": "FeeAmountEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelistRequested", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "FeeAmountExtraInfoUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "lmPoolDeployer", + "type": "address" + } + ], + "name": "SetLmPoolDeployer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "verified", + "type": "bool" + } + ], + "name": "WhiteListAdded", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "pool", "type": "address" }, + { "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": [ + { "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": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "int24", "name": "tickSpacing", "type": "int24" } + ], + "name": "enableFeeAmount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint24", "name": "", "type": "uint24" }], + "name": "feeAmountTickSpacing", + "outputs": [{ "internalType": "int24", "name": "", "type": "int24" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint24", "name": "", "type": "uint24" }], + "name": "feeAmountTickSpacingExtraInfo", + "outputs": [ + { "internalType": "bool", "name": "whitelistRequested", "type": "bool" }, + { "internalType": "bool", "name": "enabled", "type": "bool" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "uint24", "name": "", "type": "uint24" } + ], + "name": "getPool", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lmPoolDeployer", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "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": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "bool", "name": "whitelistRequested", "type": "bool" }, + { "internalType": "bool", "name": "enabled", "type": "bool" } + ], + "name": "setFeeAmountExtraInfo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "pool", "type": "address" }, + { "internalType": "uint32", "name": "feeProtocol0", "type": "uint32" }, + { "internalType": "uint32", "name": "feeProtocol1", "type": "uint32" } + ], + "name": "setFeeProtocol", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "pool", "type": "address" }, + { "internalType": "address", "name": "lmPool", "type": "address" } + ], + "name": "setLmPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_lmPoolDeployer", + "type": "address" + } + ], + "name": "setLmPoolDeployer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_owner", "type": "address" } + ], + "name": "setOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" }, + { "internalType": "bool", "name": "verified", "type": "bool" } + ], + "name": "setWhiteListAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/polygon-migration/PolygonMigration.abi.json b/src/abi/polygon-migration/PolygonMigration.abi.json new file mode 100644 index 000000000..56bace0fb --- /dev/null +++ b/src/abi/polygon-migration/PolygonMigration.abi.json @@ -0,0 +1,364 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "matic_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "InvalidAddress", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidAddressOrAlreadySet", + "type": "error" + }, + { + "inputs": [], + "name": "UnmigrationLocked", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Migrated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Unmigrated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "lock", + "type": "bool" + } + ], + "name": "UnmigrationLockUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "matic", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "migrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "polygon", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "polygon_", + "type": "address" + } + ], + "name": "setPolygonToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "unmigrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "unmigrateTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "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": "unmigrateWithPermit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unmigrationLocked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "unmigrationLocked_", + "type": "bool" + } + ], + "name": "updateUnmigrationLock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "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/abi/ramses-v2/RamsesV2Pool.abi.json b/src/abi/ramses-v2/RamsesV2Pool.abi.json new file mode 100644 index 000000000..b5516c8d7 --- /dev/null +++ b/src/abi/ramses-v2/RamsesV2Pool.abi.json @@ -0,0 +1,1599 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint24", + "name": "oldFee", + "type": "uint24" + }, + { + "indexed": false, + "internalType": "uint24", + "name": "newFee", + "type": "uint24" + } + ], + "name": "FeeAdjustment", + "type": "event" + }, + { + "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": "currentFee", + "outputs": [ + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "stateMutability": "view", + "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": [ + { + "internalType": "uint24", + "name": "_fee", + "type": "uint24" + } + ], + "name": "setFee", + "outputs": [], + "stateMutability": "nonpayable", + "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/smardex/all/smardex-router.json b/src/abi/smardex/all/smardex-router.json new file mode 100644 index 000000000..f4f5a4f3c --- /dev/null +++ b/src/abi/smardex/all/smardex-router.json @@ -0,0 +1,648 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_factory", "type": "address" }, + { "internalType": "address", "name": "_WETH", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "pair", + "type": "address" + } + ], + "name": "PairWhitelisted", + "type": "event" + }, + { + "inputs": [], + "name": "WETH", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "tokenA", "type": "address" }, + { "internalType": "address", "name": "tokenB", "type": "address" }, + { + "internalType": "uint256", + "name": "amountADesired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountBDesired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountAMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountBMin", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "fictiveReserveB", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "fictiveReserveAMin", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "fictiveReserveAMax", + "type": "uint128" + } + ], + "internalType": "struct ISmardexRouter.AddLiquidityParams", + "name": "_params", + "type": "tuple" + }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" } + ], + "name": "addLiquidity", + "outputs": [ + { "internalType": "uint256", "name": "amountA_", "type": "uint256" }, + { "internalType": "uint256", "name": "amountB_", "type": "uint256" }, + { "internalType": "uint256", "name": "liquidity_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "token", "type": "address" }, + { + "internalType": "uint256", + "name": "amountTokenDesired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountTokenMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountETHMin", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "fictiveReserveETH", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "fictiveReserveTokenMin", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "fictiveReserveTokenMax", + "type": "uint128" + } + ], + "internalType": "struct ISmardexRouter.AddLiquidityETHParams", + "name": "_params", + "type": "tuple" + }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" } + ], + "name": "addLiquidityETH", + "outputs": [ + { "internalType": "uint256", "name": "amountToken_", "type": "uint256" }, + { "internalType": "uint256", "name": "amountETH_", "type": "uint256" }, + { "internalType": "uint256", "name": "liquidity_", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_tokenA", "type": "address" }, + { "internalType": "address", "name": "_tokenB", "type": "address" } + ], + "name": "addPairToWhitelist", + "outputs": [ + { "internalType": "address", "name": "pair_", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "reserveIn", "type": "uint256" }, + { + "internalType": "uint256", + "name": "reserveOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fictiveReserveIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fictiveReserveOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "priceAverageIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "priceAverageOut", + "type": "uint256" + }, + { "internalType": "uint128", "name": "feesLP", "type": "uint128" }, + { "internalType": "uint128", "name": "feesPool", "type": "uint128" } + ], + "internalType": "struct SmardexLibrary.GetAmountParameters", + "name": "_param", + "type": "tuple" + } + ], + "name": "getAmountIn", + "outputs": [ + { "internalType": "uint256", "name": "amountIn_", "type": "uint256" }, + { "internalType": "uint256", "name": "newReserveIn_", "type": "uint256" }, + { + "internalType": "uint256", + "name": "newReserveOut_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newFictiveReserveIn_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newFictiveReserveOut_", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountOut", "type": "uint256" }, + { "internalType": "address", "name": "_tokenIn", "type": "address" }, + { "internalType": "address", "name": "_tokenOut", "type": "address" } + ], + "name": "getAmountInFromPair", + "outputs": [ + { "internalType": "uint256", "name": "amountIn_", "type": "uint256" }, + { "internalType": "uint256", "name": "newReserveIn_", "type": "uint256" }, + { + "internalType": "uint256", + "name": "newReserveOut_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newFictiveReserveIn_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newFictiveReserveOut_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "reserveIn", "type": "uint256" }, + { + "internalType": "uint256", + "name": "reserveOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fictiveReserveIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fictiveReserveOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "priceAverageIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "priceAverageOut", + "type": "uint256" + }, + { "internalType": "uint128", "name": "feesLP", "type": "uint128" }, + { "internalType": "uint128", "name": "feesPool", "type": "uint128" } + ], + "internalType": "struct SmardexLibrary.GetAmountParameters", + "name": "_param", + "type": "tuple" + } + ], + "name": "getAmountOut", + "outputs": [ + { "internalType": "uint256", "name": "amountOut_", "type": "uint256" }, + { "internalType": "uint256", "name": "newReserveIn_", "type": "uint256" }, + { + "internalType": "uint256", + "name": "newReserveOut_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newFictiveReserveIn_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newFictiveReserveOut_", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountIn", "type": "uint256" }, + { "internalType": "address", "name": "_tokenIn", "type": "address" }, + { "internalType": "address", "name": "_tokenOut", "type": "address" } + ], + "name": "getAmountOutFromPair", + "outputs": [ + { "internalType": "uint256", "name": "amountOut_", "type": "uint256" }, + { "internalType": "uint256", "name": "newReserveIn_", "type": "uint256" }, + { + "internalType": "uint256", + "name": "newReserveOut_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newFictiveReserveIn_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newFictiveReserveOut_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountA", "type": "uint256" }, + { "internalType": "uint256", "name": "_reserveA", "type": "uint256" }, + { "internalType": "uint256", "name": "_reserveB", "type": "uint256" } + ], + "name": "quote", + "outputs": [ + { "internalType": "uint256", "name": "amountB_", "type": "uint256" } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_tokenA", "type": "address" }, + { "internalType": "address", "name": "_tokenB", "type": "address" }, + { "internalType": "uint256", "name": "_liquidity", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountAMin", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountBMin", "type": "uint256" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" } + ], + "name": "removeLiquidity", + "outputs": [ + { "internalType": "uint256", "name": "amountA_", "type": "uint256" }, + { "internalType": "uint256", "name": "amountB_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_liquidity", "type": "uint256" }, + { + "internalType": "uint256", + "name": "_amountTokenMin", + "type": "uint256" + }, + { "internalType": "uint256", "name": "_amountETHMin", "type": "uint256" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" } + ], + "name": "removeLiquidityETH", + "outputs": [ + { "internalType": "uint256", "name": "amountToken_", "type": "uint256" }, + { "internalType": "uint256", "name": "amountETH_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_liquidity", "type": "uint256" }, + { + "internalType": "uint256", + "name": "_amountTokenMin", + "type": "uint256" + }, + { "internalType": "uint256", "name": "_amountETHMin", "type": "uint256" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" }, + { "internalType": "bool", "name": "_approveMax", "type": "bool" }, + { "internalType": "uint8", "name": "_v", "type": "uint8" }, + { "internalType": "bytes32", "name": "_r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "_s", "type": "bytes32" } + ], + "name": "removeLiquidityETHWithPermit", + "outputs": [ + { "internalType": "uint256", "name": "amountToken_", "type": "uint256" }, + { "internalType": "uint256", "name": "amountETH_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_tokenA", "type": "address" }, + { "internalType": "address", "name": "_tokenB", "type": "address" }, + { "internalType": "uint256", "name": "_liquidity", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountAMin", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountBMin", "type": "uint256" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" }, + { "internalType": "bool", "name": "_approveMax", "type": "bool" }, + { "internalType": "uint8", "name": "_v", "type": "uint8" }, + { "internalType": "bytes32", "name": "_r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "_s", "type": "bytes32" } + ], + "name": "removeLiquidityWithPermit", + "outputs": [ + { "internalType": "uint256", "name": "amountA_", "type": "uint256" }, + { "internalType": "uint256", "name": "amountB_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "token0", "type": "address" }, + { "internalType": "address", "name": "token1", "type": "address" }, + { "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1", "type": "uint256" }, + { "internalType": "address", "name": "payer", "type": "address" } + ], + "internalType": "struct ISmardexMintCallback.MintCallbackData", + "name": "_data", + "type": "tuple" + } + ], + "name": "smardexMintCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "int256", "name": "_amount0Delta", "type": "int256" }, + { "internalType": "int256", "name": "_amount1Delta", "type": "int256" }, + { "internalType": "bytes", "name": "_data", "type": "bytes" } + ], + "name": "smardexSwapCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountOut", "type": "uint256" }, + { "internalType": "address[]", "name": "_path", "type": "address[]" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" } + ], + "name": "swapETHForExactTokens", + "outputs": [ + { "internalType": "uint256", "name": "amountIn_", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountOutMin", "type": "uint256" }, + { "internalType": "address[]", "name": "_path", "type": "address[]" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" } + ], + "name": "swapExactETHForTokens", + "outputs": [ + { "internalType": "uint256", "name": "amountOut_", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountIn", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountOutMin", "type": "uint256" }, + { "internalType": "address[]", "name": "_path", "type": "address[]" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" } + ], + "name": "swapExactTokensForETH", + "outputs": [ + { "internalType": "uint256", "name": "amountOut_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountIn", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountOutMin", "type": "uint256" }, + { "internalType": "address[]", "name": "_path", "type": "address[]" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" }, + { "internalType": "bool", "name": "_approveMax", "type": "bool" }, + { "internalType": "uint8", "name": "_v", "type": "uint8" }, + { "internalType": "bytes32", "name": "_r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "_s", "type": "bytes32" } + ], + "name": "swapExactTokensForETHWithPermit", + "outputs": [ + { "internalType": "uint256", "name": "amountOut_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountIn", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountOutMin", "type": "uint256" }, + { "internalType": "address[]", "name": "_path", "type": "address[]" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" } + ], + "name": "swapExactTokensForTokens", + "outputs": [ + { "internalType": "uint256", "name": "amountOut_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountIn", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountOutMin", "type": "uint256" }, + { "internalType": "address[]", "name": "_path", "type": "address[]" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" }, + { "internalType": "bool", "name": "_approveMax", "type": "bool" }, + { "internalType": "uint8", "name": "_v", "type": "uint8" }, + { "internalType": "bytes32", "name": "_r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "_s", "type": "bytes32" } + ], + "name": "swapExactTokensForTokensWithPermit", + "outputs": [ + { "internalType": "uint256", "name": "amountOut_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountOut", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountInMax", "type": "uint256" }, + { "internalType": "address[]", "name": "_path", "type": "address[]" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" } + ], + "name": "swapTokensForExactETH", + "outputs": [ + { "internalType": "uint256", "name": "amountIn_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountOut", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountInMax", "type": "uint256" }, + { "internalType": "address[]", "name": "_path", "type": "address[]" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" }, + { "internalType": "bool", "name": "_approveMax", "type": "bool" }, + { "internalType": "uint8", "name": "_v", "type": "uint8" }, + { "internalType": "bytes32", "name": "_r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "_s", "type": "bytes32" } + ], + "name": "swapTokensForExactETHWithPermit", + "outputs": [ + { "internalType": "uint256", "name": "amountIn_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountOut", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountInMax", "type": "uint256" }, + { "internalType": "address[]", "name": "_path", "type": "address[]" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" } + ], + "name": "swapTokensForExactTokens", + "outputs": [ + { "internalType": "uint256", "name": "amountIn_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amountOut", "type": "uint256" }, + { "internalType": "uint256", "name": "_amountInMax", "type": "uint256" }, + { "internalType": "address[]", "name": "_path", "type": "address[]" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_deadline", "type": "uint256" }, + { "internalType": "bool", "name": "_approveMax", "type": "bool" }, + { "internalType": "uint8", "name": "_v", "type": "uint8" }, + { "internalType": "bytes32", "name": "_r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "_s", "type": "bytes32" } + ], + "name": "swapTokensForExactTokensWithPermit", + "outputs": [ + { "internalType": "uint256", "name": "amountIn_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/src/abi/smardex/layer-1/smardex-factory.json b/src/abi/smardex/layer-1/smardex-factory.json new file mode 100644 index 000000000..1691074d9 --- /dev/null +++ b/src/abi/smardex/layer-1/smardex-factory.json @@ -0,0 +1,242 @@ +[ + { "inputs": [], "name": "EmptyPair", "type": "error" }, + { "inputs": [], "name": "WhitelistNotClosed", "type": "error" }, + { "inputs": [], "name": "WhitelistNotOpen", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousFeeTo", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newFeeTo", + "type": "address" + } + ], + "name": "FeeToUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "feesLP", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "feesPool", + "type": "uint256" + } + ], + "name": "FeesChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "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": "pair", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalPair", + "type": "uint256" + } + ], + "name": "PairAdded", + "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": "pair", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalPair", + "type": "uint256" + } + ], + "name": "PairCreated", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "_pair", "type": "address" } + ], + "name": "addPair", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "allPairs", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allPairsLength", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "closeWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_tokenA", "type": "address" }, + { "internalType": "address", "name": "_tokenB", "type": "address" } + ], + "name": "createPair", + "outputs": [ + { "internalType": "address", "name": "pair_", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feeTo", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getDefaultFees", + "outputs": [ + { "internalType": "uint128", "name": "feesLP_", "type": "uint128" }, + { "internalType": "uint128", "name": "feesPool_", "type": "uint128" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "getPair", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_feeTo", "type": "address" } + ], + "name": "setFeeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint128", "name": "_feesLP", "type": "uint128" }, + { "internalType": "uint128", "name": "_feesPool", "type": "uint128" } + ], + "name": "setFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "whitelistOpen", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/smardex/layer-1/smardex-pool.json b/src/abi/smardex/layer-1/smardex-pool.json new file mode 100644 index 000000000..b37eec378 --- /dev/null +++ b/src/abi/smardex/layer-1/smardex-pool.json @@ -0,0 +1,506 @@ +[ + { "inputs": [], "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": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "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": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "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": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount1", + "type": "int256" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "reserve0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserve1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fictiveReserve0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fictiveReserve1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "priceAverage0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "priceAverage1", + "type": "uint256" + } + ], + "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" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "_to", "type": "address" }], + "name": "burn", + "outputs": [ + { "internalType": "uint256", "name": "amount0_", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getFees", + "outputs": [ + { "internalType": "uint256", "name": "fees0_", "type": "uint256" }, + { "internalType": "uint256", "name": "fees1_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getFictiveReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "fictiveReserve0_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fictiveReserve1_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPriceAverage", + "outputs": [ + { + "internalType": "uint256", + "name": "priceAverage0_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "priceAverage1_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "priceAverageLastTimestamp_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getReserves", + "outputs": [ + { "internalType": "uint256", "name": "reserve0_", "type": "uint256" }, + { "internalType": "uint256", "name": "reserve1_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_fictiveReserveIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_fictiveReserveOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_priceAverageLastTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_priceAverageIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_priceAverageOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_currentTimestamp", + "type": "uint256" + } + ], + "name": "getUpdatedPriceAverage", + "outputs": [ + { + "internalType": "uint256", + "name": "priceAverageIn_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "priceAverageOut_", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token0", "type": "address" }, + { "internalType": "address", "name": "_token1", "type": "address" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "_amount1", "type": "uint256" }, + { "internalType": "address", "name": "_payer", "type": "address" } + ], + "name": "mint", + "outputs": [ + { "internalType": "uint256", "name": "liquidity_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "bool", "name": "_zeroForOne", "type": "bool" }, + { + "internalType": "int256", + "name": "_amountSpecified", + "type": "int256" + }, + { "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": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "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": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/smardex/layer-2/smardex-factory.json b/src/abi/smardex/layer-2/smardex-factory.json new file mode 100644 index 000000000..40b08cef5 --- /dev/null +++ b/src/abi/smardex/layer-2/smardex-factory.json @@ -0,0 +1,185 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousFeeTo", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newFeeTo", + "type": "address" + } + ], + "name": "FeeToUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "feesLP", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "feesPool", + "type": "uint256" + } + ], + "name": "FeesChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "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": "pair", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalPair", + "type": "uint256" + } + ], + "name": "PairCreated", + "type": "event" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "allPairs", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allPairsLength", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_tokenA", "type": "address" }, + { "internalType": "address", "name": "_tokenB", "type": "address" } + ], + "name": "createPair", + "outputs": [ + { "internalType": "address", "name": "pair_", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feeTo", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getDefaultFees", + "outputs": [ + { "internalType": "uint128", "name": "feesLP_", "type": "uint128" }, + { "internalType": "uint128", "name": "feesPool_", "type": "uint128" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "getPair", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_feeTo", "type": "address" } + ], + "name": "setFeeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint128", "name": "_feesLP", "type": "uint128" }, + { "internalType": "uint128", "name": "_feesPool", "type": "uint128" } + ], + "name": "setFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/smardex/layer-2/smardex-pool.json b/src/abi/smardex/layer-2/smardex-pool.json new file mode 100644 index 000000000..22c5e2a13 --- /dev/null +++ b/src/abi/smardex/layer-2/smardex-pool.json @@ -0,0 +1,578 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { "inputs": [], "name": "InvalidShortString", "type": "error" }, + { + "inputs": [{ "internalType": "string", "name": "str", "type": "string" }], + "name": "StringTooLong", + "type": "error" + }, + { + "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": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "feesLP", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "feesPool", + "type": "uint256" + } + ], + "name": "FeesChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "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": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount1", + "type": "int256" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "reserve0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserve1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fictiveReserve0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fictiveReserve1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "priceAverage0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "priceAverage1", + "type": "uint256" + } + ], + "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" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "_to", "type": "address" }], + "name": "burn", + "outputs": [ + { "internalType": "uint256", "name": "amount0_", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { "internalType": "bytes1", "name": "fields", "type": "bytes1" }, + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "string", "name": "version", "type": "string" }, + { "internalType": "uint256", "name": "chainId", "type": "uint256" }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { "internalType": "bytes32", "name": "salt", "type": "bytes32" }, + { "internalType": "uint256[]", "name": "extensions", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getFeeToAmounts", + "outputs": [ + { "internalType": "uint256", "name": "fees0_", "type": "uint256" }, + { "internalType": "uint256", "name": "fees1_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getFictiveReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "fictiveReserve0_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fictiveReserve1_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPairFees", + "outputs": [ + { "internalType": "uint128", "name": "feesLP_", "type": "uint128" }, + { "internalType": "uint128", "name": "feesPool_", "type": "uint128" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPriceAverage", + "outputs": [ + { + "internalType": "uint256", + "name": "priceAverage0_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "priceAverage1_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "priceAverageLastTimestamp_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getReserves", + "outputs": [ + { "internalType": "uint256", "name": "reserve0_", "type": "uint256" }, + { "internalType": "uint256", "name": "reserve1_", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_fictiveReserveIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_fictiveReserveOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_priceAverageLastTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_priceAverageIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_priceAverageOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_currentTimestamp", + "type": "uint256" + } + ], + "name": "getUpdatedPriceAverage", + "outputs": [ + { + "internalType": "uint256", + "name": "priceAverageIn_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "priceAverageOut_", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token0", "type": "address" }, + { "internalType": "address", "name": "_token1", "type": "address" }, + { "internalType": "uint128", "name": "_feesLP", "type": "uint128" }, + { "internalType": "uint128", "name": "_feesPool", "type": "uint128" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "_amount1", "type": "uint256" }, + { "internalType": "address", "name": "_payer", "type": "address" } + ], + "name": "mint", + "outputs": [ + { "internalType": "uint256", "name": "liquidity_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint128", "name": "_feesLP", "type": "uint128" }, + { "internalType": "uint128", "name": "_feesPool", "type": "uint128" } + ], + "name": "setFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "bool", "name": "_zeroForOne", "type": "bool" }, + { + "internalType": "int256", + "name": "_amountSpecified", + "type": "int256" + }, + { "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": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "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": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/solidly-v3/SolidlyV3Pool.abi.json b/src/abi/solidly-v3/SolidlyV3Pool.abi.json new file mode 100644 index 000000000..cf00c0559 --- /dev/null +++ b/src/abi/solidly-v3/SolidlyV3Pool.abi.json @@ -0,0 +1,738 @@ +[ + { "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": "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": "uint24", + "name": "feeOld", + "type": "uint24" + }, + { + "indexed": false, + "internalType": "uint24", + "name": "feeNew", + "type": "uint24" + } + ], + "name": "SetFee", + "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": "int24", "name": "tickLower", "type": "int24" }, + { "internalType": "int24", "name": "tickUpper", "type": "int24" }, + { "internalType": "uint128", "name": "amount", "type": "uint128" }, + { "internalType": "uint256", "name": "amount0Min", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1Min", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "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": "address", "name": "recipient", "type": "address" }, + { "internalType": "int24", "name": "tickLower", "type": "int24" }, + { "internalType": "int24", "name": "tickUpper", "type": "int24" }, + { "internalType": "uint128", "name": "amountToBurn", "type": "uint128" }, + { + "internalType": "uint128", + "name": "amount0ToCollect", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1ToCollect", + "type": "uint128" + } + ], + "name": "burnAndCollect", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0FromBurn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1FromBurn", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "amount0Collected", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Collected", + "type": "uint128" + } + ], + "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": "amountToBurn", "type": "uint128" }, + { + "internalType": "uint256", + "name": "amount0FromBurnMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1FromBurnMin", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "amount0ToCollect", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1ToCollect", + "type": "uint128" + }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "name": "burnAndCollect", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0FromBurn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1FromBurn", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "amount0Collected", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Collected", + "type": "uint128" + } + ], + "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": "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": [ + { "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": "uint160", "name": "sqrtPriceX96", "type": "uint160" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "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": "uint256", "name": "amount0Min", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1Min", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "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": "int24", "name": "tickLower", "type": "int24" }, + { "internalType": "int24", "name": "tickUpper", "type": "int24" }, + { "internalType": "uint128", "name": "amount", "type": "uint128" } + ], + "name": "mint", + "outputs": [ + { "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "poolFees", + "outputs": [ + { "internalType": "uint128", "name": "token0", "type": "uint128" }, + { "internalType": "uint128", "name": "token1", "type": "uint128" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "positions", + "outputs": [ + { "internalType": "uint128", "name": "liquidity", "type": "uint128" }, + { "internalType": "uint128", "name": "tokensOwed0", "type": "uint128" }, + { "internalType": "uint128", "name": "tokensOwed1", "type": "uint128" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "zeroForOne", "type": "bool" }, + { "internalType": "int256", "name": "amountSpecified", "type": "int256" }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "name": "quoteSwap", + "outputs": [ + { "internalType": "int256", "name": "amount0", "type": "int256" }, + { "internalType": "int256", "name": "amount1", "type": "int256" }, + { + "internalType": "uint160", + "name": "sqrtPriceX96After", + "type": "uint160" + }, + { "internalType": "int24", "name": "tickAfter", "type": "int24" }, + { "internalType": "uint128", "name": "liquidityAfter", "type": "uint128" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint24", "name": "fee", "type": "uint24" }], + "name": "setFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "slot0", + "outputs": [ + { "internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160" }, + { "internalType": "int24", "name": "tick", "type": "int24" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "bool", "name": "unlocked", "type": "bool" } + ], + "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": "uint256", "name": "amountLimit", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "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": "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": "address", "name": "recipient", "type": "address" }, + { "internalType": "bool", "name": "zeroForOne", "type": "bool" }, + { "internalType": "int256", "name": "amountSpecified", "type": "int256" }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { "internalType": "uint256", "name": "amountLimit", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "name": "swap", + "outputs": [ + { "internalType": "int256", "name": "amount0", "type": "int256" }, + { "internalType": "int256", "name": "amount1", "type": "int256" } + ], + "stateMutability": "nonpayable", + "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" + } + ], + "name": "swap", + "outputs": [ + { "internalType": "int256", "name": "amount0", "type": "int256" }, + { "internalType": "int256", "name": "amount1", "type": "int256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "int16", "name": "", "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": "", "type": "int24" }], + "name": "ticks", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { "internalType": "int128", "name": "liquidityNet", "type": "int128" }, + { "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" + } +] diff --git a/src/abi/solidly-v3/SolidlyV3StateMulticall.abi.json b/src/abi/solidly-v3/SolidlyV3StateMulticall.abi.json new file mode 100644 index 000000000..15e78b740 --- /dev/null +++ b/src/abi/solidly-v3/SolidlyV3StateMulticall.abi.json @@ -0,0 +1,375 @@ +[ + { + "inputs": [ + { + "internalType": "contract IUniswapV3Factory", + "name": "factory", + "type": "address" + }, + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "address", "name": "tokenOut", "type": "address" }, + { "internalType": "int24", "name": "tickSpacing", "type": "int24" }, + { "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 IUniswapV3StateMulticall.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": "bool", "name": "initialized", "type": "bool" } + ], + "internalType": "struct IUniswapV3StateMulticall.TickInfo", + "name": "value", + "type": "tuple" + } + ], + "internalType": "struct IUniswapV3StateMulticall.TickInfoMappings[]", + "name": "ticks", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IUniswapV3Factory", + "name": "factory", + "type": "address" + }, + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "address", "name": "tokenOut", "type": "address" }, + { "internalType": "int24", "name": "tickSpacing", "type": "int24" }, + { "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 IUniswapV3StateMulticall.TickBitMapMappings[]", + "name": "tickBitmap", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IUniswapV3Factory", + "name": "factory", + "type": "address" + }, + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "address", "name": "tokenOut", "type": "address" }, + { "internalType": "int24", "name": "tickSpacing", "type": "int24" }, + { "internalType": "int16", "name": "tickBitmapStart", "type": "int16" }, + { "internalType": "int16", "name": "tickBitmapEnd", "type": "int16" } + ], + "name": "getFullState", + "outputs": [ + { + "components": [ + { + "internalType": "contract IUniswapV3Pool", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockTimestamp", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { "internalType": "int24", "name": "tick", "type": "int24" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "bool", "name": "unlocked", "type": "bool" } + ], + "internalType": "struct IUniswapV3StateMulticall.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": "int16", "name": "index", "type": "int16" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "internalType": "struct IUniswapV3StateMulticall.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": "bool", + "name": "initialized", + "type": "bool" + } + ], + "internalType": "struct IUniswapV3StateMulticall.TickInfo", + "name": "value", + "type": "tuple" + } + ], + "internalType": "struct IUniswapV3StateMulticall.TickInfoMappings[]", + "name": "ticks", + "type": "tuple[]" + } + ], + "internalType": "struct IUniswapV3StateMulticall.StateResult", + "name": "state", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IUniswapV3Factory", + "name": "factory", + "type": "address" + }, + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "address", "name": "tokenOut", "type": "address" }, + { "internalType": "int24", "name": "tickSpacing", "type": "int24" }, + { "internalType": "int16", "name": "leftBitmapAmount", "type": "int16" }, + { "internalType": "int16", "name": "rightBitmapAmount", "type": "int16" } + ], + "name": "getFullStateWithRelativeBitmaps", + "outputs": [ + { + "components": [ + { + "internalType": "contract IUniswapV3Pool", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockTimestamp", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { "internalType": "int24", "name": "tick", "type": "int24" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "bool", "name": "unlocked", "type": "bool" } + ], + "internalType": "struct IUniswapV3StateMulticall.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": "int16", "name": "index", "type": "int16" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "internalType": "struct IUniswapV3StateMulticall.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": "bool", + "name": "initialized", + "type": "bool" + } + ], + "internalType": "struct IUniswapV3StateMulticall.TickInfo", + "name": "value", + "type": "tuple" + } + ], + "internalType": "struct IUniswapV3StateMulticall.TickInfoMappings[]", + "name": "ticks", + "type": "tuple[]" + } + ], + "internalType": "struct IUniswapV3StateMulticall.StateResult", + "name": "state", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IUniswapV3Factory", + "name": "factory", + "type": "address" + }, + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "address", "name": "tokenOut", "type": "address" }, + { "internalType": "int24", "name": "tickSpacing", "type": "int24" }, + { "internalType": "int16", "name": "tickBitmapStart", "type": "int16" }, + { "internalType": "int16", "name": "tickBitmapEnd", "type": "int16" } + ], + "name": "getFullStateWithoutTicks", + "outputs": [ + { + "components": [ + { + "internalType": "contract IUniswapV3Pool", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockTimestamp", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { "internalType": "int24", "name": "tick", "type": "int24" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "bool", "name": "unlocked", "type": "bool" } + ], + "internalType": "struct IUniswapV3StateMulticall.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": "int16", "name": "index", "type": "int16" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "internalType": "struct IUniswapV3StateMulticall.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": "bool", + "name": "initialized", + "type": "bool" + } + ], + "internalType": "struct IUniswapV3StateMulticall.TickInfo", + "name": "value", + "type": "tuple" + } + ], + "internalType": "struct IUniswapV3StateMulticall.TickInfoMappings[]", + "name": "ticks", + "type": "tuple[]" + } + ], + "internalType": "struct IUniswapV3StateMulticall.StateResult", + "name": "state", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/swETH.json b/src/abi/swETH.json new file mode 100644 index 000000000..a8e5d31f0 --- /dev/null +++ b/src/abi/swETH.json @@ -0,0 +1,1086 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "AddressAlreadyInWhitelist", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "AddressMissingFromWhitelist", + "type": "error" + }, + { + "inputs": [], + "name": "BotMethodsPaused", + "type": "error" + }, + { + "inputs": [], + "name": "CannotBeZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "CannotReferSelf", + "type": "error" + }, + { + "inputs": [], + "name": "CannotRepriceWithZeroSwETHSupply", + "type": "error" + }, + { + "inputs": [], + "name": "CoreMethodsPaused", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidETHDeposit", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMethodCall", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPreRewardETHReserves", + "type": "error" + }, + { + "inputs": [], + "name": "NoActiveValidators", + "type": "error" + }, + { + "inputs": [], + "name": "NoTokensToWithdraw", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "remainingTime", + "type": "uint256" + } + ], + "name": "NotEnoughTimeElapsedForReprice", + "type": "error" + }, + { + "inputs": [], + "name": "NotInWhitelist", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "x", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "y", + "type": "uint256" + } + ], + "name": "PRBMath_MulDiv18_Overflow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "x", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "y", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "name": "PRBMath_MulDiv_Overflow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "repriceDiff", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximumRepriceDiff", + "type": "uint256" + } + ], + "name": "RepriceDifferenceTooLarge", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "repriceswETHDiff", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximumswETHRepriceDiff", + "type": "uint256" + } + ], + "name": "RepriceswETHDifferenceTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "RewardPercentageTotalOverflow", + "type": "error" + }, + { + "inputs": [], + "name": "WhitelistAlreadyDisabled", + "type": "error" + }, + { + "inputs": [], + "name": "WhitelistAlreadyEnabled", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "AddedToWhitelist", + "type": "event" + }, + { + "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": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "swETHMinted", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTotalETHDeposited", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "referral", + "type": "address" + } + ], + "name": "ETHDepositReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "swETHBurned", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethReturned", + "type": "uint256" + } + ], + "name": "ETHWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_oldMaximumRepriceDifferencePercentage", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_newMaximumRepriceDifferencePercentage", + "type": "uint256" + } + ], + "name": "MaximumRepriceDifferencePercentageUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_oldMaximumRepriceswETHDifferencePercentage", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_newMaximumRepriceswETHDifferencePercentage", + "type": "uint256" + } + ], + "name": "MaximumRepriceswETHDifferencePercentageUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_oldMinimumRepriceTime", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_newMinimumRepriceTime", + "type": "uint256" + } + ], + "name": "MinimumRepriceTimeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldPercentage", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newPercentage", + "type": "uint256" + } + ], + "name": "NodeOperatorRewardPercentageUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "RemovedFromWhitelist", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newEthReserves", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newSwETHToETHRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nodeOperatorRewards", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "swellTreasuryRewards", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalETHDeposited", + "type": "uint256" + } + ], + "name": "Reprice", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldPercentage", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newPercentage", + "type": "uint256" + } + ], + "name": "SwellTreasuryRewardPercentageUpdate", + "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" + }, + { + "anonymous": false, + "inputs": [], + "name": "WhitelistDisabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "WhitelistEnabled", + "type": "event" + }, + { + "stateMutability": "nonpayable", + "type": "fallback" + }, + { + "inputs": [], + "name": "AccessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "addToWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_addresses", + "type": "address[]" + } + ], + "name": "batchAddToWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_addresses", + "type": "address[]" + } + ], + "name": "batchRemoveFromWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "referral", + "type": "address" + } + ], + "name": "depositWithReferral", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "disableWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "enableWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "ethToSwETHRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IAccessControlManager", + "name": "_accessControlManager", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "lastRepriceETHReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastRepriceUNIX", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maximumRepriceDifferencePercentage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maximumRepriceswETHDifferencePercentage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minimumRepriceTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nodeOperatorRewardPercentage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "removeFromWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_preRewardETHReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_newETHRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_swETHTotalSupply", + "type": "uint256" + } + ], + "name": "reprice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maximumRepriceDifferencePercentage", + "type": "uint256" + } + ], + "name": "setMaximumRepriceDifferencePercentage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maximumRepriceswETHDifferencePercentage", + "type": "uint256" + } + ], + "name": "setMaximumRepriceswETHDifferencePercentage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_minimumRepriceTime", + "type": "uint256" + } + ], + "name": "setMinimumRepriceTime", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newNodeOperatorRewardPercentage", + "type": "uint256" + } + ], + "name": "setNodeOperatorRewardPercentage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newSwellTreasuryRewardPercentage", + "type": "uint256" + } + ], + "name": "setSwellTreasuryRewardPercentage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "swETHToETHRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "swellTreasuryRewardPercentage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalETHDeposited", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "whitelistEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "whitelistedAddresses", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_token", + "type": "address" + } + ], + "name": "withdrawERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/wombat/asset.json b/src/abi/wombat/asset.json new file mode 100644 index 000000000..588a16cbe --- /dev/null +++ b/src/abi/wombat/asset.json @@ -0,0 +1,743 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "underlyingToken_", + "type": "address" + }, + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + } + ], + "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": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "previousMaxSupply", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newMaxSupply", + "type": "uint256" + } + ], + "name": "SetMaxSupply", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousPoolAddr", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newPoolAddr", + "type": "address" + } + ], + "name": "SetPool", + "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" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "addCash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "addLiability", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cash", + "outputs": [ + { + "internalType": "uint120", + "name": "", + "type": "uint120" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRelativePrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liability", + "outputs": [ + { + "internalType": "uint120", + "name": "", + "type": "uint120" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "removeCash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "removeLiability", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "maxSupply_", + "type": "uint256" + } + ], + "name": "setMaxSupply", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool_", + "type": "address" + } + ], + "name": "setPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferUnderlyingToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "underlyingToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "underlyingTokenBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "underlyingTokenDecimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/wombat/bmw.json b/src/abi/wombat/bmw.json new file mode 100644 index 000000000..f225ba581 --- /dev/null +++ b/src/abi/wombat/bmw.json @@ -0,0 +1,1247 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "pid", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "lpToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IBoostedMultiRewarder", + "name": "boostedRewarder", + "type": "address" + } + ], + "name": "Add", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "pid", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "pid", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DepositFor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "pid", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "EmergencyWithdraw", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "name": "EmergencyWomWithdraw", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "pid", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Harvest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "pid", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "contract IBoostedMultiRewarder", + "name": "boostedRewarder", + "type": "address" + } + ], + "name": "SetBoostedRewarder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IBribeRewarderFactory", + "name": "bribeRewarderFactory", + "type": "address" + } + ], + "name": "SetBribeRewarderFactory", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IMasterWombatV3", + "name": "masterWormbat", + "type": "address" + } + ], + "name": "SetNewMasterWombat", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "pid", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "contract IMultiRewarder", + "name": "rewarder", + "type": "address" + } + ], + "name": "SetRewarder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "basePartition", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "boostedPartition", + "type": "uint256" + } + ], + "name": "UpdateEmissionPartition", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "oldVeWOM", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newVeWOM", + "type": "address" + } + ], + "name": "UpdateVeWOM", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "oldVoter", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newVoter", + "type": "address" + } + ], + "name": "UpdateVoter", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "oldWOM", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newWOM", + "type": "address" + } + ], + "name": "UpdateWOM", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "pid", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [], + "name": "ACC_TOKEN_PRECISION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REWARD_DURATION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TOTAL_PARTITION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_lpToken", + "type": "address" + }, + { + "internalType": "contract IBoostedMultiRewarder", + "name": "_boostedRewarder", + "type": "address" + } + ], + "name": "add", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "basePartition", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "boostedPartition", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "boostedRewarders", + "outputs": [ + { + "internalType": "contract IBoostedMultiRewarder", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bribeRewarderFactory", + "outputs": [ + { + "internalType": "contract IBribeRewarderFactory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + } + ], + "name": "calRewardPerUnit", + "outputs": [ + { + "internalType": "uint256", + "name": "accWomPerShare", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "accWomPerFactorShare", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "reward", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "additionalRewards", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "depositFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + } + ], + "name": "emergencyWithdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "emergencyWomWithdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "getAssetPid", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + } + ], + "name": "getSumOfFactors", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_wom", + "type": "address" + }, + { + "internalType": "contract IVeWom", + "name": "_veWom", + "type": "address" + }, + { + "internalType": "address", + "name": "_voter", + "type": "address" + }, + { + "internalType": "uint16", + "name": "_basePartition", + "type": "uint16" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_periodFinish", + "type": "uint256" + } + ], + "name": "lastTimeRewardApplicable", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "massUpdatePools", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "_pids", + "type": "uint256[]" + } + ], + "name": "migrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "_pids", + "type": "uint256[]" + } + ], + "name": "multiClaim", + "outputs": [ + { + "internalType": "uint256", + "name": "reward", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "uint256[][]", + "name": "additionalRewards", + "type": "uint256[][]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_lpToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "notifyRewardAmount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "pendingTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "pendingRewards", + "type": "uint256" + }, + { + "internalType": "contract IERC20[]", + "name": "bonusTokenAddresses", + "type": "address[]" + }, + { + "internalType": "string[]", + "name": "bonusTokenSymbols", + "type": "string[]" + }, + { + "internalType": "uint256[]", + "name": "pendingBonusRewards", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + } + ], + "name": "poolInfo", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "lpToken", + "type": "address" + }, + { + "internalType": "uint96", + "name": "allocPoint", + "type": "uint96" + }, + { + "internalType": "contract IMultiRewarder", + "name": "rewarder", + "type": "address" + }, + { + "internalType": "uint256", + "name": "sumOfFactors", + "type": "uint256" + }, + { + "internalType": "uint104", + "name": "accWomPerShare", + "type": "uint104" + }, + { + "internalType": "uint104", + "name": "accWomPerFactorShare", + "type": "uint104" + }, + { + "internalType": "uint40", + "name": "lastRewardTimestamp", + "type": "uint40" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "poolInfoV3", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "lpToken", + "type": "address" + }, + { + "internalType": "contract IMultiRewarder", + "name": "rewarder", + "type": "address" + }, + { + "internalType": "uint40", + "name": "periodFinish", + "type": "uint40" + }, + { + "internalType": "uint128", + "name": "sumOfFactors", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "rewardRate", + "type": "uint128" + }, + { + "internalType": "uint104", + "name": "accWomPerShare", + "type": "uint104" + }, + { + "internalType": "uint104", + "name": "accWomPerFactorShare", + "type": "uint104" + }, + { + "internalType": "uint40", + "name": "lastRewardTimestamp", + "type": "uint40" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "poolLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + } + ], + "name": "rewarderBonusTokenInfo", + "outputs": [ + { + "internalType": "contract IERC20[]", + "name": "bonusTokenAddresses", + "type": "address[]" + }, + { + "internalType": "string[]", + "name": "bonusTokenSymbols", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + }, + { + "internalType": "contract IBoostedMultiRewarder", + "name": "_boostedRewarder", + "type": "address" + } + ], + "name": "setBoostedRewarder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IBribeRewarderFactory", + "name": "_bribeRewarderFactory", + "type": "address" + } + ], + "name": "setBribeRewarderFactory", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IMasterWombatV3", + "name": "_newMasterWombat", + "type": "address" + } + ], + "name": "setNewMasterWombat", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + }, + { + "internalType": "contract IMultiRewarder", + "name": "_rewarder", + "type": "address" + } + ], + "name": "setRewarder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IVeWom", + "name": "_newVeWom", + "type": "address" + } + ], + "name": "setVeWom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newVoter", + "type": "address" + } + ], + "name": "setVoter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_newWom", + "type": "address" + } + ], + "name": "setWom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "_basePartition", + "type": "uint16" + } + ], + "name": "updateEmissionPartition", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_newVeWomBalance", + "type": "uint256" + } + ], + "name": "updateFactor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + } + ], + "name": "updatePool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "userInfo", + "outputs": [ + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "factor", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "rewardDebt", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "pendingWom", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "veWom", + "outputs": [ + { + "internalType": "contract IVeWom", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "voter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "reward", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "additionalRewards", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "wom", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/wombat/pool-v2.json b/src/abi/wombat/pool-v2.json new file mode 100644 index 000000000..c9859e9bf --- /dev/null +++ b/src/abi/wombat/pool-v2.json @@ -0,0 +1,1289 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "AssetAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "AssetRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "FillPool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "PausedAsset", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SetAmpFactor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "SetDev", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "lpDividendRatio", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "retentionRatio", + "type": "uint256" + } + ], + "name": "SetFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "SetFeeTo", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SetHaircutRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "SetMasterWombat", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SetMintFeeThreshold", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "toAmount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "TransferTipBucket", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "UnpausedAsset", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "addAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "addressOfAsset", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ampFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumLiquidity", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "shouldStake", + "type": "bool" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "dev", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "endCovRatio", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "exchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "xr", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeTo", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "fillPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "globalEquilCovRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "equilCovRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "invariantInUint", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "haircutRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "ampFactor_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "haircutRate_", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lpDividendRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "masterWombat", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "mintFee", + "outputs": [ + { + "internalType": "uint256", + "name": "feeCollected", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "mintFeeThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "pauseAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "int256", + "name": "toAmount", + "type": "int256" + } + ], + "name": "quoteAmountIn", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "haircut", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "quotePotentialDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reward", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "int256", + "name": "fromAmount", + "type": "int256" + } + ], + "name": "quotePotentialSwap", + "outputs": [ + { + "internalType": "uint256", + "name": "potentialOutcome", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "haircut", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "name": "quotePotentialWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "name": "quotePotentialWithdrawFromOtherAsset", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrewAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "removeAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "retentionRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "ampFactor_", + "type": "uint256" + } + ], + "name": "setAmpFactor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "startCovRatio_", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "endCovRatio_", + "type": "uint128" + } + ], + "name": "setCovRatioFeeParam", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dev_", + "type": "address" + } + ], + "name": "setDev", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "lpDividendRatio_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "retentionRatio_", + "type": "uint256" + } + ], + "name": "setFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "feeTo_", + "type": "address" + } + ], + "name": "setFeeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "haircutRate_", + "type": "uint256" + } + ], + "name": "setHaircutRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "masterWombat_", + "type": "address" + } + ], + "name": "setMasterWombat", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "mintFeeThreshold_", + "type": "uint256" + } + ], + "name": "setMintFeeThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "startCovRatio", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumToAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "uint256", + "name": "actualToAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "haircut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "tipBucketBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transferTipBucket", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "unpauseAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "withdrawFromOtherAsset", + "outputs": [ + { + "internalType": "uint256", + "name": "toAmount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "creditForTokensHaircut", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/wombat/pool-v3.json b/src/abi/wombat/pool-v3.json new file mode 100644 index 000000000..af288d33f --- /dev/null +++ b/src/abi/wombat/pool-v3.json @@ -0,0 +1,2013 @@ +[ + { + "inputs": [], + "name": "POOL__CREDIT_NOT_ENOUGH", + "type": "error" + }, + { + "inputs": [], + "name": "POOL__REACH_MAXIMUM_BURNED_CREDIT", + "type": "error" + }, + { + "inputs": [], + "name": "POOL__REACH_MAXIMUM_MINTED_CREDIT", + "type": "error" + }, + { + "inputs": [], + "name": "POOL__SWAP_CREDIT_FOR_TOKENS_DISABLED", + "type": "error" + }, + { + "inputs": [], + "name": "POOL__SWAP_TOKENS_FOR_CREDIT_DISABLED", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_AMOUNT_TOO_LOW", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_ASSET_ALREADY_EXIST", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_ASSET_ALREADY_PAUSED", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_ASSET_NOT_EXISTS", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_ASSET_NOT_PAUSED", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_CASH_NOT_ENOUGH", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_COV_RATIO_LIMIT_EXCEEDED", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_EXPIRED", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_FORBIDDEN", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_INVALID_VALUE", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_SAME_ADDRESS", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_ZERO_ADDRESS", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_ZERO_AMOUNT", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_ZERO_CREDIT_AMOUNT", + "type": "error" + }, + { + "inputs": [], + "name": "WOMBAT_ZERO_LIQUIDITY", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "AssetAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "AssetRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "FillPool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "creditAmount", + "type": "uint256" + } + ], + "name": "MintCredit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "PausedAsset", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SetAmpFactor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "SetDev", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "lpDividendRatio", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "retentionRatio", + "type": "uint256" + } + ], + "name": "SetFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "SetFeeTo", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SetHaircutRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "SetMasterWombat", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SetMintFeeThreshold", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SetWithdrawalHaircutRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "creditAmount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "toAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "toTokenFee", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "SwapCreditForTokens", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fromTokenFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "creditAmount", + "type": "uint256" + } + ], + "name": "SwapTokensForCredit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "toAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "toTokenFee", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "SwapV2", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "TransferTipBucket", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "UnpausedAsset", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [], + "name": "adaptor", + "outputs": [ + { + "internalType": "contract IAdaptor", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "addAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "addressOfAsset", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ampFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumToAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "completeSwapCreditForTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "actualToAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "toTokenFee", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "creditBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "creditForTokensHaircut", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumLiquidity", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "shouldStake", + "type": "bool" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "dev", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "endCovRatio", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "exchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "xr", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeTo", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "fillPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "globalEquilCovRatio", + "outputs": [ + { + "internalType": "int256", + "name": "equilCovRatio", + "type": "int256" + }, + { + "internalType": "int256", + "name": "invariant", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "globalEquilCovRatioWithCredit", + "outputs": [ + { + "internalType": "uint256", + "name": "equilCovRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "invariantInUint", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "haircutRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "ampFactor_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "haircutRate_", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lpDividendRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "masterWombat", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maximumInboundCredit", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maximumOutboundCredit", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "creditAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "mintCredit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "mintFee", + "outputs": [ + { + "internalType": "uint256", + "name": "feeCollected", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "mintFeeThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "pauseAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "int256", + "name": "toAmount", + "type": "int256" + } + ], + "name": "quoteAmountIn", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "haircut", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "quotePotentialDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "int256", + "name": "fromAmount", + "type": "int256" + } + ], + "name": "quotePotentialSwap", + "outputs": [ + { + "internalType": "uint256", + "name": "potentialOutcome", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "haircut", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "name": "quotePotentialWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "name": "quotePotentialWithdrawFromOtherAsset", + "outputs": [ + { + "internalType": "uint256", + "name": "finalAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrewAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fromCreditAmount", + "type": "uint256" + } + ], + "name": "quoteSwapCreditForTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "actualToAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "toTokenFee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + } + ], + "name": "quoteSwapTokensForCredit", + "outputs": [ + { + "internalType": "uint256", + "name": "creditAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fromTokenFee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "removeAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "retentionRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IAdaptor", + "name": "_adaptor", + "type": "address" + } + ], + "name": "setAdaptorAddr", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "ampFactor_", + "type": "uint256" + } + ], + "name": "setAmpFactor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "startCovRatio_", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "endCovRatio_", + "type": "uint128" + } + ], + "name": "setCovRatioFeeParam", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "_tokensForCreditHaircut", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "_creditForTokensHaircut", + "type": "uint128" + } + ], + "name": "setCrossChainHaircut", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dev_", + "type": "address" + } + ], + "name": "setDev", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "lpDividendRatio_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "retentionRatio_", + "type": "uint256" + } + ], + "name": "setFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "feeTo_", + "type": "address" + } + ], + "name": "setFeeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "haircutRate_", + "type": "uint256" + } + ], + "name": "setHaircutRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "masterWombat_", + "type": "address" + } + ], + "name": "setMasterWombat", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "_maximumInboundCredit", + "type": "uint128" + } + ], + "name": "setMaximumInboundCredit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint128", + "name": "_maximumOutboundCredit", + "type": "uint128" + } + ], + "name": "setMaximumOutboundCredit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "mintFeeThreshold_", + "type": "uint256" + } + ], + "name": "setMintFeeThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "setSwapCreditForTokensEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "enable", + "type": "bool" + } + ], + "name": "setSwapTokensForCreditEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "withdrawalHaircutRate_", + "type": "uint256" + } + ], + "name": "setWithdrawalHaircutRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "startCovRatio", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumToAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "uint256", + "name": "actualToAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "haircut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumToAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "swapCreditForTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "actualToAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "toTokenFee", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "toChain", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumToAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "receiverValue", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deliveryGasLimit", + "type": "uint256" + } + ], + "name": "swapCreditForTokensCrossChain", + "outputs": [ + { + "internalType": "uint256", + "name": "trackingId", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "swapCreditForTokensEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "swapTokensForCreditEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "toChain", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumCreditAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumToAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "receiverValue", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deliveryGasLimit", + "type": "uint256" + } + ], + "name": "swapTokensForTokensCrossChain", + "outputs": [ + { + "internalType": "uint256", + "name": "creditAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fromTokenFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sequence", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "tipBucketBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokensForCreditHaircut", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalCreditBurned", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalCreditMinted", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transferTipBucket", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "unpauseAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimumAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "withdrawFromOtherAsset", + "outputs": [ + { + "internalType": "uint256", + "name": "toAmount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalHaircutRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "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 bef159a5b..c62234fc9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -30,6 +30,9 @@ type BaseConfig = { hashFlowAuthToken?: string; hashFlowDisabledMMs: string[]; swaapV2AuthToken?: string; + dexalotAuthToken?: string; + smardexSubgraphAuthToken?: string; + forceRpcFallbackDexs: string[]; }; const baseConfigs: { [network: number]: BaseConfig } = { @@ -49,9 +52,11 @@ const baseConfigs: { [network: number]: BaseConfig } = { adapterAddresses: { Adapter01: '0x9bE264469eF954c139Da4A45Cf76CbCC5e3A6A73', Adapter02: '0xFC2Ba6E830a04C25e207B8214b26d8C713F6881F', - Adapter03: '0x7c7f62e5ba00783f57b39df0530e32c195696a57', - Adapter04: '0x30F6B9b6485ff0B67E881f5ac80D3F1c70A4B23d', - BuyAdapter: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + Adapter03: '0xBAEeb4540f59d30E567a5B563CC0c4587eDd9366', + Adapter04: '0x369A2FDb910d432f0a07381a5E3d27572c876713', + Adapter05: '0x3329dfa55A40B450952FBE0203167Ae6908E656d', + BuyAdapter: '0x84bEF12C9931cE12662cc9F2366b6a5029E4BD29', + BuyAdapter02: '0xe53d24CD81cC81bbf271AD7B02D0d67f851D727c', }, uniswapV2ExchangeRouterAddress: '0xF9234CB08edb93c0d4a4d4c70cC3FfD070e78e07', @@ -59,6 +64,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { rpcPollingBlocksBackToTriggerUpdate: 0, swaapV2AuthToken: process.env.API_KEY_SWAAP_V2_AUTH_TOKEN || '', hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', + smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_1`]?.split(',') || [], uniswapV3EventLoggingSampleRate: 0, @@ -115,6 +121,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { }, }, }, + forceRpcFallbackDexs: [], }, [Network.ROPSTEN]: { network: Network.ROPSTEN, @@ -142,6 +149,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { rfqConfigs: {}, rpcPollingMaxAllowedStateDelayInBlocks: 5, rpcPollingBlocksBackToTriggerUpdate: 3, + forceRpcFallbackDexs: [], }, [Network.BSC]: { network: Network.BSC, @@ -156,18 +164,20 @@ const baseConfigs: { [network: number]: BaseConfig } = { multicallV2Address: '0xC50F4c1E81c873B2204D7eFf7069Ffec6Fbe136D', privateHttpProvider: process.env.HTTP_PROVIDER_56, hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', + smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_56`]?.split(',') || [], adapterAddresses: { BscAdapter01: '0xA31d9C571DF00e0F428B0bD24c34D103E8112222', - BscAdapter02: '0x1d2Fd92a1942A92a51198168eFCd626ed441CEC0', - BscBuyAdapter: '0x64C856fafE4C83a818514cBDfD661a3563a71B98', + BscAdapter02: '0x9A92D2649C38415860FA59ba8B9a9960cd2839Db', + BscBuyAdapter: '0xd32C191e0febaa6Cc93A29Cb676474c72486E00b', }, rpcPollingMaxAllowedStateDelayInBlocks: 1, rpcPollingBlocksBackToTriggerUpdate: 1, uniswapV2ExchangeRouterAddress: '0x53e693c6C7FFC4446c53B205Cf513105Bf140D7b', rfqConfigs: {}, + forceRpcFallbackDexs: [], }, [Network.POLYGON]: { network: Network.POLYGON, @@ -183,12 +193,13 @@ const baseConfigs: { [network: number]: BaseConfig } = { multicallV2Address: '0x275617327c958bD06b5D6b871E7f491D76113dd8', privateHttpProvider: process.env.HTTP_PROVIDER_137, hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', + smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_137`]?.split(',') || [], adapterAddresses: { PolygonAdapter01: '0xE44769f42E1e9592f86B82f206407a8f7C84b4ed', - PolygonAdapter02: '0xa05d8C3F278fC7b20b39Ea7A3035E3aD8D808c78', - PolygonBuyAdapter: '0xB11bCA7B01b425afD0743A4D77B4f593883f94C0', + PolygonAdapter02: '0x84bEF12C9931cE12662cc9F2366b6a5029E4BD29', + PolygonBuyAdapter: '0xBAEeb4540f59d30E567a5B563CC0c4587eDd9366', }, uniswapV2ExchangeRouterAddress: '0xf3938337F7294fEf84e9B2c6D548A93F956Cc281', @@ -197,6 +208,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { rpcPollingMaxAllowedStateDelayInBlocks: 2, rpcPollingBlocksBackToTriggerUpdate: 1, swaapV2AuthToken: process.env.API_KEY_SWAAP_V2_AUTH_TOKEN || '', + forceRpcFallbackDexs: [], }, [Network.AVALANCHE]: { network: Network.AVALANCHE, @@ -212,18 +224,21 @@ const baseConfigs: { [network: number]: BaseConfig } = { multicallV2Address: '0xd7Fc8aD069f95B6e2835f4DEff03eF84241cF0E1', privateHttpProvider: process.env.HTTP_PROVIDER_43114, hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', + smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_43114`]?.split(',') || [], + dexalotAuthToken: process.env.API_KEY_DEXALOT_AUTH_TOKEN || '', adapterAddresses: { AvalancheAdapter01: '0x745Ec73855CeC7249E5fF4c9DD81cc65b4D297a9', - AvalancheAdapter02: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', - AvalancheBuyAdapter: '0x7ebbDBB57d2ab59079423cf8337cf8805e225bB1', + AvalancheAdapter02: '0xA10c9a84E72d9DfF424Fe2284B6460784bed407E', + AvalancheBuyAdapter: '0x654dE10890f8B2C5bF54E50Af169a7E93165C416', }, uniswapV2ExchangeRouterAddress: '0x53e693c6C7FFC4446c53B205Cf513105Bf140D7b', rfqConfigs: {}, rpcPollingMaxAllowedStateDelayInBlocks: 2, rpcPollingBlocksBackToTriggerUpdate: 1, + forceRpcFallbackDexs: [], }, [Network.FANTOM]: { network: Network.FANTOM, @@ -239,11 +254,12 @@ const baseConfigs: { [network: number]: BaseConfig } = { multicallV2Address: '0xdC6E2b14260F972ad4e5a31c68294Fba7E720701', privateHttpProvider: process.env.HTTP_PROVIDER_250, hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', + smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_250`]?.split(',') || [], adapterAddresses: { - FantomAdapter01: '0x434C1Cca4842629230067674Dd54E21a14D9FD5D', + FantomAdapter01: '0x654dE10890f8B2C5bF54E50Af169a7E93165C416', FantomBuyAdapter: '0xb2634B3CBc1E401AB3C2743DB44d459C5c9aA662', }, uniswapV2ExchangeRouterAddress: @@ -251,6 +267,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { rfqConfigs: {}, rpcPollingMaxAllowedStateDelayInBlocks: 2, rpcPollingBlocksBackToTriggerUpdate: 1, + forceRpcFallbackDexs: [], }, [Network.ARBITRUM]: { network: Network.ARBITRUM, @@ -266,12 +283,14 @@ const baseConfigs: { [network: number]: BaseConfig } = { multicallV2Address: '0x7eCfBaa8742fDf5756DAC92fbc8b90a19b8815bF', privateHttpProvider: process.env.HTTP_PROVIDER_42161, hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', + smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', + swaapV2AuthToken: process.env.API_KEY_SWAAP_V2_AUTH_TOKEN || '', hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_42161`]?.split(',') || [], adapterAddresses: { - ArbitrumAdapter01: '0x745Ec73855CeC7249E5fF4c9DD81cc65b4D297a9', - ArbitrumAdapter02: '0x3ad7f275E27AC579cA88e0b4765828242A9E8C49', - ArbitrumBuyAdapter: '0x6c33C7f6CBB4a428fe9ee31ca500a787c9f1525b', + ArbitrumAdapter01: '0x369A2FDb910d432f0a07381a5E3d27572c876713', + ArbitrumAdapter02: '0x58a5f0b73969800FAFf8556cD2187E3FCE71A6cb', + ArbitrumBuyAdapter: '0x4483Ae378897eB9FbF7f15Df98Bf07233ffFEe8b', }, uniswapV2ExchangeRouterAddress: '0xB41dD984730dAf82f5C41489E21ac79D5e3B61bC', @@ -279,6 +298,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { rfqConfigs: {}, rpcPollingMaxAllowedStateDelayInBlocks: 4, rpcPollingBlocksBackToTriggerUpdate: 3, + forceRpcFallbackDexs: [], }, [Network.OPTIMISM]: { network: Network.OPTIMISM, @@ -294,12 +314,12 @@ const baseConfigs: { [network: number]: BaseConfig } = { multicallV2Address: '0x2DC0E2aa608532Da689e89e237dF582B783E552C', privateHttpProvider: process.env.HTTP_PROVIDER_10, hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', + smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_10`]?.split(',') || [], - adapterAddresses: { - OptimismAdapter01: '0x4669D27A649f5451e0D44C20a2b246431F1B0572', - OptimismBuyAdapter: '0xb2634B3CBc1E401AB3C2743DB44d459C5c9aA662', + OptimismAdapter01: '0x5dcf544b0c9689fa67dcb713fd2656d217e25a59', + OptimismBuyAdapter: '0xA10c9a84E72d9DfF424Fe2284B6460784bed407E', }, uniswapV2ExchangeRouterAddress: '0xB41dD984730dAf82f5C41489E21ac79D5e3B61bC', @@ -307,6 +327,65 @@ const baseConfigs: { [network: number]: BaseConfig } = { rfqConfigs: {}, rpcPollingMaxAllowedStateDelayInBlocks: 5, 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 || '', + smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', + 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 || '', + smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', + hashFlowDisabledMMs: [], + adapterAddresses: { + BaseAdapter01: '0xA10c9a84E72d9DfF424Fe2284B6460784bed407E', + BaseBuyAdapter: '0xEECA9223063bD13e8ca77ed9e39a07f2BD1923E6', + }, + uniswapV2ExchangeRouterAddress: + '0x75d199EfB540e47D27D52c62Da3E7daC2B9e834F', + uniswapV3EventLoggingSampleRate: 0, + rfqConfigs: {}, + rpcPollingMaxAllowedStateDelayInBlocks: 5, + rpcPollingBlocksBackToTriggerUpdate: 3, + forceRpcFallbackDexs: [], }, }; @@ -348,8 +427,11 @@ export function generateConfig(network: number): Config { rpcPollingBlocksBackToTriggerUpdate: baseConfig.rpcPollingBlocksBackToTriggerUpdate, hashFlowAuthToken: baseConfig.hashFlowAuthToken, + smardexSubgraphAuthToken: baseConfig.smardexSubgraphAuthToken, swaapV2AuthToken: baseConfig.swaapV2AuthToken, + dexalotAuthToken: baseConfig.dexalotAuthToken, hashFlowDisabledMMs: baseConfig.hashFlowDisabledMMs, + forceRpcFallbackDexs: baseConfig.forceRpcFallbackDexs, }; } @@ -359,7 +441,7 @@ export class ConfigHelper { constructor( public isSlave: boolean, public data: Config, - private masterCachePrefix: string, + masterCachePrefix: string, ) { this.masterBlockNumberCacheKey = `${masterCachePrefix}_${data.network}_bn`.toLowerCase(); 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..287a76d8e 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; } @@ -112,6 +115,10 @@ class DummyCache implements ICache { return 0; } + async zrem(key: string, membersKeys: string[]): Promise { + return 0; + } + async zadd(key: string, bulkItemsToAdd: (number | string)[], option?: 'NX') { return 0; } diff --git a/src/dex-helper/icache.ts b/src/dex-helper/icache.ts index edaa1536d..4aa725ee6 100644 --- a/src/dex-helper/icache.ts +++ b/src/dex-helper/icache.ts @@ -46,6 +46,8 @@ export interface ICache { zremrangebyscore(key: string, min: number, max: number): Promise; + zrem(key: string, membersKeys: string[]): Promise; + zscore(setKey: string, key: string): Promise; sismember(setKey: string, key: string): Promise; diff --git a/src/dex/aave-v2/aave-v2.ts b/src/dex/aave-v2/aave-v2.ts index 43e591df9..a60480aef 100644 --- a/src/dex/aave-v2/aave-v2.ts +++ b/src/dex/aave-v2/aave-v2.ts @@ -23,21 +23,14 @@ import { IDex } from '../../dex/idex'; import { IDexHelper } from '../../dex-helper/idex-helper'; import { SimpleExchange } from '../simple-exchange'; -import { AaveV2Config, Adapters } from './config'; +import { + AaveV2Config, + Adapters, + WETH_GATEWAY, + aaveLendingPool, +} from './config'; import { isAaveV2Pair } from './tokens'; -const aaveLendingPool: { [network: string]: string } = { - [Network.MAINNET]: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', - [Network.POLYGON]: '0x8dff5e27ea6b7ac08ebfdf9eb090f32ee9a30fcf', - [Network.AVALANCHE]: '0x4F01AeD16D97E3aB5ab2B501154DC9bb0F1A5A2C', -}; - -const WETH_GATEWAY: any = { - [Network.MAINNET]: '0xDcD33426BA191383f1c9B431A342498fdac73488', - [Network.POLYGON]: '0xbEadf48d62aCC944a06EEaE0A9054A90E5A7dc97', - [Network.AVALANCHE]: '0x8a47F74d1eE0e2edEB4F3A7e64EF3bD8e11D27C8', -}; - const WETH_GATEWAY_ABI: any = { [Network.MAINNET]: WETH_GATEWAY_ABI_MAINNET, [Network.POLYGON]: WETH_GATEWAY_ABI_POLYGON, diff --git a/src/dex/aave-v2/config.ts b/src/dex/aave-v2/config.ts index 13265f716..6fef2160f 100644 --- a/src/dex/aave-v2/config.ts +++ b/src/dex/aave-v2/config.ts @@ -1,5 +1,22 @@ import { DexConfigMap } from '../../types'; import { Network, SwapSide } from '../../constants'; +import { + AaveV2Avalanche, + AaveV2Ethereum, + AaveV2Polygon, +} from '@bgd-labs/aave-address-book'; + +export const aaveLendingPool: { [network: string]: string } = { + [Network.MAINNET]: AaveV2Ethereum.POOL, + [Network.POLYGON]: AaveV2Polygon.POOL, + [Network.AVALANCHE]: AaveV2Avalanche.POOL, +}; + +export const WETH_GATEWAY: any = { + [Network.MAINNET]: AaveV2Ethereum.WETH_GATEWAY, + [Network.POLYGON]: AaveV2Polygon.WETH_GATEWAY, + [Network.AVALANCHE]: AaveV2Avalanche.WETH_GATEWAY, +}; export const AaveV2Config: DexConfigMap = { AaveV2: { diff --git a/src/dex/aave-v2/tokens-avalanche.json b/src/dex/aave-v2/tokens-avalanche.json deleted file mode 100644 index dcc55363b..000000000 --- a/src/dex/aave-v2/tokens-avalanche.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "aSymbol": "avWETH", - "aAddress": "0x53f7c5869a859f0aec3d334ee8b4cf01e3492f21", - "address": "0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab", - "decimals": 18 - }, - { - "aSymbol": "avDAI", - "aAddress": "0x47afa96cdc9fab46904a55a6ad4bf6660b53c38a", - "address": "0xd586e7f844cea2f87f50152665bcbc2c279d8d70", - "decimals": 18 - }, - { - "aSymbol": "avUSDT", - "aAddress": "0x532e6537fea298397212f09a61e03311686f548e", - "address": "0xc7198437980c041c805a1edcba50c1ce5db95118", - "decimals": 6 - }, - { - "aSymbol": "avUSDC", - "aAddress": "0x46a51127c3ce23fb7ab1de06226147f446e4a857", - "address": "0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664", - "decimals": 6 - }, - { - "aSymbol": "avAAVE", - "aAddress": "0xd45b7c061016102f9fa220502908f2c0f1add1d7", - "address": "0x63a72806098bd3d9520cc43356dd78afe5d386d9", - "decimals": 18 - }, - { - "aSymbol": "avWBTC", - "aAddress": "0x686bef2417b6dc32c50a3cbfbcc3bb60e1e9a15d", - "address": "0x50b7545627a5162f82a992c33b87adc75187b218", - "decimals": 8 - }, - { - "aSymbol": "avWAVAX", - "aAddress": "0xdfe521292ece2a4f44242efbcd66bc594ca9714b", - "address": "0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7", - "decimals": 18 - } -] diff --git a/src/dex/aave-v2/tokens-mainnet.json b/src/dex/aave-v2/tokens-mainnet.json deleted file mode 100644 index 6758530c5..000000000 --- a/src/dex/aave-v2/tokens-mainnet.json +++ /dev/null @@ -1,188 +0,0 @@ -[ - { - "aSymbol": "aUSDT", - "aAddress": "0x3ed3b47dd13ec9a98b44e6204a523e766b225811", - "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", - "decimals": 6 - }, - { - "aSymbol": "aWBTC", - "aAddress": "0x9ff58f4ffb29fa2266ab25e75e2a8b3503311656", - "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", - "decimals": 8 - }, - { - "aSymbol": "aWETH", - "aAddress": "0x030ba81f1c18d280636f32af80b9aad02cf0854e", - "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "decimals": 18 - }, - { - "aSymbol": "aYFI", - "aAddress": "0x5165d24277cd063f5ac44efd447b27025e888f37", - "address": "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e", - "decimals": 18 - }, - { - "aSymbol": "aZRX", - "aAddress": "0xdf7ff54aacacbff42dfe29dd6144a69b629f8c9e", - "address": "0xe41d2489571d322189246dafa5ebde1f4699f498", - "decimals": 18 - }, - { - "aSymbol": "aUNI", - "aAddress": "0xb9d7cb55f463405cdfbe4e90a6d2df01c2b92bf1", - "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", - "decimals": 18 - }, - { - "aSymbol": "aAAVE", - "aAddress": "0xffc97d72e13e01096502cb8eb52dee56f74dad7b", - "address": "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9", - "decimals": 18 - }, - { - "aSymbol": "aBAT", - "aAddress": "0x05ec93c0365baaeabf7aeffb0972ea7ecdd39cf1", - "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", - "decimals": 18 - }, - { - "aSymbol": "aBUSD", - "aAddress": "0xa361718326c15715591c299427c62086f69923d9", - "address": "0x4fabb145d64652a948d72533023f6e7a623c7c53", - "decimals": 18 - }, - { - "aSymbol": "aDAI", - "aAddress": "0x028171bca77440897b824ca71d1c56cac55b68a3", - "address": "0x6b175474e89094c44da98b954eedeac495271d0f", - "decimals": 18 - }, - { - "aSymbol": "aENJ", - "aAddress": "0xac6df26a590f08dcc95d5a4705ae8abbc88509ef", - "address": "0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c", - "decimals": 18 - }, - { - "aSymbol": "aKNC", - "aAddress": "0x39c6b3e42d6a679d7d776778fe880bc9487c2eda", - "address": "0xdd974d5c2e2928dea5f71b9825b8b646686bd200", - "decimals": 18 - }, - { - "aSymbol": "aLINK", - "aAddress": "0xa06bc25b5805d5f8d82847d191cb4af5a3e873e0", - "address": "0x514910771af9ca656af840dff83e8264ecf986ca", - "decimals": 18 - }, - { - "aSymbol": "aMANA", - "aAddress": "0xa685a61171bb30d4072b338c80cb7b2c865c873e", - "address": "0x0f5d2fb29fb7d3cfee444a200298f468908cc942", - "decimals": 18 - }, - { - "aSymbol": "aMKR", - "aAddress": "0xc713e5e149d5d0715dcd1c156a020976e7e56b88", - "address": "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", - "decimals": 18 - }, - { - "aSymbol": "aREN", - "aAddress": "0xcc12abe4ff81c9378d670de1b57f8e0dd228d77a", - "address": "0x408e41876cccdc0f92210600ef50372656052a38", - "decimals": 18 - }, - { - "aSymbol": "aSNX", - "aAddress": "0x35f6b052c598d933d69a4eec4d04c73a191fe6c2", - "address": "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f", - "decimals": 18 - }, - { - "aSymbol": "aSUSD", - "aAddress": "0x6c5024cd4f8a59110119c56f8933403a539555eb", - "address": "0x57ab1ec28d129707052df4df418d58a2d46d5f51", - "decimals": 18 - }, - { - "aSymbol": "aTUSD", - "aAddress": "0x101cc05f4a51c0319f570d5e146a8c625198e636", - "address": "0x0000000000085d4780b73119b644ae5ecd22b376", - "decimals": 18 - }, - { - "aSymbol": "aUSDC", - "aAddress": "0xbcca60bb61934080951369a648fb03df4f96263c", - "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "decimals": 6 - }, - { - "aSymbol": "aCRV", - "aAddress": "0x8dae6cb04688c62d939ed9b68d32bc62e49970b1", - "address": "0xd533a949740bb3306d119cc777fa900ba034cd52", - "decimals": 18 - }, - { - "aSymbol": "aGUSD", - "aAddress": "0xd37ee7e4f452c6638c96536e68090de8cbcdb583", - "address": "0x056fd409e1d7a124bd7017459dfea2f387b6d5cd", - "decimals": 2 - }, - { - "aSymbol": "aBAL", - "aAddress": "0x272f97b7a56a387ae942350bbc7df5700f8a4576", - "address": "0xba100000625a3754423978a60c9317c58a424e3d", - "decimals": 18 - }, - { - "aSymbol": "aXSUSHI", - "aAddress": "0xf256cc7847e919fac9b808cc216cac87ccf2f47a", - "address": "0x8798249c2e607446efb7ad49ec89dd1865ff4272", - "decimals": 18 - }, - { - "aSymbol": "aRENFIL", - "aAddress": "0x514cd6756ccbe28772d4cb81bc3156ba9d1744aa", - "address": "0xd5147bc8e386d91cc5dbe72099dac6c9b99276f5", - "decimals": 18 - }, - { - "aSymbol": "aRAI", - "aAddress": "0xc9bc48c72154ef3e5425641a3c747242112a46af", - "address": "0x03ab458634910aad20ef5f1c8ee96f1d6ac54919", - "decimals": 18 - }, - { - "aSymbol": "aAMPL", - "aAddress": "0x1e6bb68acec8fefbd87d192be09bb274170a0548", - "address": "0xd46ba6d942050d489dbd938a2c909a5d5039a161", - "decimals": 9 - }, - { - "aSymbol": "aUSDP", - "aAddress": "0x2e8f4bdbe3d47d7d7de490437aea9915d930f1a3", - "address": "0x8e870d67f660d95d5be530380d0ec0bd388289e1", - "decimals": 18 - }, - { - "aSymbol": "aDPI", - "aAddress": "0x6f634c6135d2ebd550000ac92f494f9cb8183dae", - "address": "0x1494ca1f11d487c2bbe4543e90080aeba4ba3c2b", - "decimals": 18 - }, - { - "aSymbol": "aFRAX", - "aAddress": "0xd4937682df3c8aef4fe912a96a74121c0829e664", - "address": "0x853d955acef822db058eb8505911ed77f175b99e", - "decimals": 18 - }, - { - "aSymbol": "aFEI", - "aAddress": "0x683923db55fead99a79fa01a27eec3cb19679cc3", - "address": "0x956f47f50a910163d8bf957cf5846d573e7f87ca", - "decimals": 18 - } -] diff --git a/src/dex/aave-v2/tokens-polygon.json b/src/dex/aave-v2/tokens-polygon.json deleted file mode 100644 index fd047ba77..000000000 --- a/src/dex/aave-v2/tokens-polygon.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "aSymbol": "amAAVE", - "aAddress": "0x1d2a0E5EC8E5bBDCA5CB219e649B565d8e5c3360", - "address": "0xd6df932a45c0f255f85145f286ea0b292b21c90b", - "decimals": 18 - }, - { - "aSymbol": "amDAI", - "aAddress": "0x27f8d03b3a2196956ed754badc28d73be8830a6e", - "address": "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063", - "decimals": 18 - }, - { - "aSymbol": "amUSDC", - "aAddress": "0x1a13f4ca1d028320a707d99520abfefca3998b7f", - "address": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "decimals": 6 - }, - { - "aSymbol": "amUSDT", - "aAddress": "0x60d55f02a771d515e077c9c2403a1ef324885cec", - "address": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", - "decimals": 6 - }, - { - "aSymbol": "amWBTC", - "aAddress": "0x5c2ed810328349100a66b82b78a1791b101c9d61", - "address": "0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6", - "decimals": 8 - }, - { - "aSymbol": "amWETH", - "aAddress": "0x28424507fefb6f7f8e9d3860f56504e4e5f5f390", - "address": "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619", - "decimals": 18 - }, - { - "aSymbol": "amWMATIC", - "aAddress": "0x8df3aad3a84da6b69a4da8aec3ea40d9091b2ac4", - "address": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270", - "decimals": 18 - } -] diff --git a/src/dex/aave-v2/tokens.ts b/src/dex/aave-v2/tokens.ts index 40c7e0ae8..095a3aac1 100644 --- a/src/dex/aave-v2/tokens.ts +++ b/src/dex/aave-v2/tokens.ts @@ -1,18 +1,33 @@ import { aToken, Token } from '../../types'; import { Network } from '../../constants'; +import { tokenlist } from '@bgd-labs/aave-address-book'; +import { aaveLendingPool } from './config'; -import tokensMainnet from './tokens-mainnet.json'; -import tokensPolygon from './tokens-polygon.json'; -import tokensAvalanche from './tokens-avalanche.json'; +function getTokensForPool(pool: string): aToken[] { + return tokenlist.tokens + .filter( + token => + token.extensions?.pool && + token.extensions?.pool.toLowerCase() === pool.toLowerCase() && + token.tags.includes('aTokenV2'), + ) + .map(token => ({ + aSymbol: token.symbol, + aAddress: token.address.toLowerCase(), + // the type is a bit imprecise, when tag is aTokenV2, underlying will exist + address: token.extensions!.underlying.toLowerCase(), + decimals: token.decimals, + })); +} export const Tokens: { [network: number]: { [symbol: string]: aToken } } = {}; const TokensByAddress: { [network: number]: { [address: string]: aToken } } = {}; const tokensByNetwork: { [network: number]: any } = { - [Network.MAINNET]: tokensMainnet, - [Network.POLYGON]: tokensPolygon, - [Network.AVALANCHE]: tokensAvalanche, + [Network.MAINNET]: getTokensForPool(aaveLendingPool[Network.MAINNET]), + [Network.POLYGON]: getTokensForPool(aaveLendingPool[Network.POLYGON]), + [Network.AVALANCHE]: getTokensForPool(aaveLendingPool[Network.AVALANCHE]), }; for (const [key, tokens] of Object.entries(tokensByNetwork)) { diff --git a/src/dex/aave-v3/config.ts b/src/dex/aave-v3/config.ts index d365c4f19..c0d058bff 100644 --- a/src/dex/aave-v3/config.ts +++ b/src/dex/aave-v3/config.ts @@ -1,6 +1,17 @@ import { DexConfigMap } from '../../types'; import { Network, SwapSide } from '../../constants'; import { DexParam } from './types'; +import { + AaveV3Arbitrum, + AaveV3Avalanche, + AaveV3BNB, + AaveV3Base, + AaveV3Ethereum, + AaveV3Fantom, + AaveV3Optimism, + AaveV3Polygon, + AaveV3PolygonZkEvm, +} from '@bgd-labs/aave-address-book'; // TODO: find vals for V3 export const Config: DexConfigMap = { @@ -8,38 +19,56 @@ export const Config: DexConfigMap = { [Network.FANTOM]: { ethGasCost: 246 * 100, lendingGasCost: 328 * 1000, - poolAddress: '0x794a61358D6845594F94dc1DB02A252b5b4814aD', - wethGatewayAddress: '0x17d013C19FE25cf4D911CE85eD5f40FE8880F46f', + poolAddress: AaveV3Fantom.POOL, + wethGatewayAddress: AaveV3Fantom.WETH_GATEWAY, }, [Network.POLYGON]: { ethGasCost: 246 * 100, lendingGasCost: 328 * 1000, - poolAddress: '0x794a61358D6845594F94dc1DB02A252b5b4814aD', - wethGatewayAddress: '0x9bdb5fcc80a49640c7872ac089cc0e00a98451b6', + poolAddress: AaveV3Polygon.POOL, + wethGatewayAddress: AaveV3Polygon.WETH_GATEWAY, }, [Network.AVALANCHE]: { ethGasCost: 246 * 100, lendingGasCost: 328 * 1000, - poolAddress: '0x794a61358D6845594F94dc1DB02A252b5b4814aD', - wethGatewayAddress: '0xa938d8536aEed1Bd48f548380394Ab30Aa11B00E', + poolAddress: AaveV3Avalanche.POOL, + wethGatewayAddress: AaveV3Avalanche.WETH_GATEWAY, }, [Network.ARBITRUM]: { ethGasCost: 246 * 100, lendingGasCost: 328 * 1000, - poolAddress: '0x794a61358D6845594F94dc1DB02A252b5b4814aD', - wethGatewayAddress: '0xC09e69E79106861dF5d289dA88349f10e2dc6b5C', + poolAddress: AaveV3Arbitrum.POOL, + wethGatewayAddress: AaveV3Arbitrum.WETH_GATEWAY, }, [Network.OPTIMISM]: { ethGasCost: 246 * 100, lendingGasCost: 328 * 1000, - poolAddress: '0x794a61358D6845594F94dc1DB02A252b5b4814aD', - wethGatewayAddress: '0x86b4D2636EC473AC4A5dD83Fc2BEDa98845249A7', + poolAddress: AaveV3Optimism.POOL, + wethGatewayAddress: AaveV3Optimism.WETH_GATEWAY, }, [Network.MAINNET]: { ethGasCost: 246 * 100, lendingGasCost: 328 * 1000, - poolAddress: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2', - wethGatewayAddress: '0xD322A49006FC828F9B5B37Ab215F99B4E5caB19C', + poolAddress: AaveV3Ethereum.POOL, + wethGatewayAddress: AaveV3Ethereum.WETH_GATEWAY, + }, + [Network.BASE]: { + ethGasCost: 246 * 100, + lendingGasCost: 328 * 1000, + poolAddress: AaveV3Base.POOL, + wethGatewayAddress: AaveV3Base.WETH_GATEWAY, + }, + [Network.BSC]: { + ethGasCost: 246 * 100, + lendingGasCost: 328 * 1000, + poolAddress: AaveV3BNB.POOL, + wethGatewayAddress: AaveV3BNB.WETH_GATEWAY, + }, + [Network.ZKEVM]: { + ethGasCost: 246 * 100, + lendingGasCost: 328 * 1000, + poolAddress: AaveV3PolygonZkEvm.POOL, + wethGatewayAddress: AaveV3PolygonZkEvm.WETH_GATEWAY, }, }, }; @@ -95,4 +124,28 @@ export const Adapters: { }, ], }, + [Network.BASE]: { + [SwapSide.SELL]: [ + { + name: 'BaseAdapter01', + index: 9, + }, + ], + }, + [Network.BSC]: { + [SwapSide.SELL]: [ + { + name: 'BscAdapter02', + index: 9, + }, + ], + }, + [Network.ZKEVM]: { + [SwapSide.SELL]: [ + { + name: 'PolygonZkEvmAdapter02', + index: 1, + }, + ], + }, }; diff --git a/src/dex/algebra/algebra-e2e.test.ts b/src/dex/algebra/algebra-e2e.test.ts index d945141d1..9aaa162b9 100644 --- a/src/dex/algebra/algebra-e2e.test.ts +++ b/src/dex/algebra/algebra-e2e.test.ts @@ -11,6 +11,7 @@ import { import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; +import { TransferFeeParams } from '../../types'; /* README @@ -59,6 +60,12 @@ function testForNetwork( tokenAAmount: string, tokenBAmount: string, nativeTokenAmount: string, + transferFees: TransferFeeParams = { + srcFee: 0, + destFee: 0, + srcDexFee: 0, + destDexFee: 0, + }, ) { const provider = new StaticJsonRpcProvider( generateConfig(network).privateHttpProvider, @@ -80,6 +87,7 @@ function testForNetwork( ], // TODO: If buy is not supported remove the buy contract methods [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + // [SwapSide.BUY, [ContractMethod.simpleBuy]], ]); describe(`${network}`, () => { @@ -87,43 +95,81 @@ function testForNetwork( describe(`${side}`, () => { contractMethods.forEach((contractMethod: ContractMethod) => { describe(`${contractMethod}`, () => { - it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + // if src token is tax token and BUY side, then should fail (skip) + if (!!transferFees?.srcDexFee && side === SwapSide.BUY) return; + + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { await testE2E( - tokens[nativeTokenSymbol], tokens[tokenASymbol], - holders[nativeTokenSymbol], - side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, side, dexKey, contractMethod, network, provider, + undefined, + undefined, + transferFees, ); }); - it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { await testE2E( + tokens[tokenBSymbol], tokens[tokenASymbol], + holders[tokenBSymbol], + side === SwapSide.SELL ? tokenBAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + // switch src and dest fee when tax token is dest token + { + ...transferFees, + srcDexFee: transferFees.destDexFee, + destDexFee: transferFees.srcDexFee, + }, + ); + }); + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( tokens[nativeTokenSymbol], - holders[tokenASymbol], - side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, side, dexKey, contractMethod, network, provider, + undefined, + undefined, + // switch src and dest fee when tax token is dest token + { + ...transferFees, + srcDexFee: transferFees.destDexFee, + destDexFee: transferFees.srcDexFee, + }, ); }); - it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { await testE2E( tokens[tokenASymbol], - tokens[tokenBSymbol], + tokens[nativeTokenSymbol], holders[tokenASymbol], - side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, side, dexKey, contractMethod, network, provider, + undefined, + undefined, + transferFees, ); }); }); @@ -198,16 +244,41 @@ describe('Algebra', () => { tokenAAmount, tokenBAmount, nativeTokenAmount, - ) + ); }); }); describe('CamelotV3', () => { const dexKey = 'CamelotV3'; + const network = Network.ARBITRUM; - describe('Arbitrum', () => { - const network = Network.ARBITRUM; - const tokenASymbol: string = 'USDC'; + describe('Arbitrum: Tax Tokens', () => { + const tokenASymbol: string = 'RDPX'; + const tokenBSymbol: string = 'WETH'; + + const tokenAAmount: string = '100000000000000000000'; + const tokenBAmount: string = '100000000000000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + { + srcFee: 0, + destFee: 0, + srcDexFee: 1000, + destDexFee: 0, + }, + ); + }); + + describe('Arbitrum: Non-Tax tokens', () => { + const tokenASymbol: string = 'USDCe'; const tokenBSymbol: string = 'USDT'; const tokenAAmount: string = '1000000000'; diff --git a/src/dex/algebra/algebra-factory.ts b/src/dex/algebra/algebra-factory.ts new file mode 100644 index 000000000..41de2a014 --- /dev/null +++ b/src/dex/algebra/algebra-factory.ts @@ -0,0 +1,70 @@ +import { Interface } from '@ethersproject/abi'; +import { DeepReadonly } from 'ts-essentials'; +import FactoryABI from '../../abi/algebra/AlgebraFactory-v1_1.abi.json'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { Address, Log, Logger } from '../../types'; +import { LogDescription } from 'ethers/lib/utils'; +import { FactoryState } from './types'; + +export type OnPoolCreatedCallback = ({ + token0, + token1, +}: { + token0: string; + token1: string; +}) => Promise; + +/* + * "Stateless" event subscriber in order to capture "PoolCreated" event on new pools created. + * State is present, but it's a placeholder to actually make the events reach handlers (if there's no previous state - `processBlockLogs` is not called) + */ +export class AlgebraFactory extends StatefulEventSubscriber { + handlers: { + [event: string]: (event: any) => Promise; + } = {}; + + logDecoder: (log: Log) => any; + + public readonly factoryIface = new Interface(FactoryABI); + + constructor( + readonly dexHelper: IDexHelper, + parentName: string, + protected readonly factoryAddress: Address, + logger: Logger, + protected readonly onPoolCreated: OnPoolCreatedCallback, + mapKey: string = '', + ) { + super(parentName, `${parentName} Factory`, dexHelper, logger, true, mapKey); + + this.addressesSubscribed = [factoryAddress]; + + this.logDecoder = (log: Log) => this.factoryIface.parseLog(log); + + this.handlers['Pool'] = this.handleNewPool.bind(this); + } + + generateState(): FactoryState { + return {}; + } + + protected async processLog( + _: DeepReadonly, + log: Readonly, + ): Promise { + const event = this.logDecoder(log); + if (event.name in this.handlers) { + await this.handlers[event.name](event); + } + + return {}; + } + + async handleNewPool(event: LogDescription) { + const token0 = event.args.token0.toLowerCase(); + const token1 = event.args.token1.toLowerCase(); + + await this.onPoolCreated({ token0, token1 }); + } +} diff --git a/src/dex/algebra/algebra-integration.test.ts b/src/dex/algebra/algebra-integration.test.ts index 97017d03a..002b4292f 100644 --- a/src/dex/algebra/algebra-integration.test.ts +++ b/src/dex/algebra/algebra-integration.test.ts @@ -14,21 +14,8 @@ import { } from '../../../tests/utils'; import { Tokens } from '../../../tests/constants-e2e'; import { Address } from '@paraswap/core'; - -/* - README - ====== - - This test script adds tests for Algebra general integration - with the DEX interface. The test cases below are example tests. - It is recommended to add tests which cover Algebra specific - logic. - - You can run this individual test script by running: - `npx jest src/dex//-integration.test.ts` - - (This comment should be removed from the final implementation) -*/ +import { AlgebraEventPoolV1_1 } from './algebra-pool-v1_1'; +import { DecodedStateMultiCallResultWithRelativeBitmapsV1_1 } from './types'; function getReaderCalldata( exchangeAddress: string, @@ -70,7 +57,7 @@ async function checkOnChainPricing( tokenOut: Address, amounts: bigint[], ) { - const exchangeAddress = '0xa15f0d7377b2a0c0c10db057f641bed21028fc89'; // Quoter address + const exchangeAddress = algebra.config.quoter; const readerIface = algebra.quoterIface; @@ -155,6 +142,199 @@ async function testPricingOnNetwork( ); } +describe('CamelotV3', function () { + const dexKey = 'CamelotV3'; + let blockNumber: number; + let algebra: Algebra; + + describe('Arbitrum', () => { + const network = Network.ARBITRUM; + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + + describe('GRAIL => USDCe', () => { + const srcTokenSymbol = 'GRAIL'; + const destTokenSymbol = 'USDCe'; + + const amountsForSell = [ + 0n, + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + 20n * BI_POWS[tokens[srcTokenSymbol].decimals], + 30n * BI_POWS[tokens[srcTokenSymbol].decimals], + 40n * BI_POWS[tokens[srcTokenSymbol].decimals], + 50n * BI_POWS[tokens[srcTokenSymbol].decimals], + 60n * BI_POWS[tokens[srcTokenSymbol].decimals], + 70n * BI_POWS[tokens[srcTokenSymbol].decimals], + 80n * BI_POWS[tokens[srcTokenSymbol].decimals], + 90n * BI_POWS[tokens[srcTokenSymbol].decimals], + 100n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + 2n * BI_POWS[tokens[destTokenSymbol].decimals], + 3n * BI_POWS[tokens[destTokenSymbol].decimals], + 4n * BI_POWS[tokens[destTokenSymbol].decimals], + 5n * BI_POWS[tokens[destTokenSymbol].decimals], + 6n * BI_POWS[tokens[destTokenSymbol].decimals], + 7n * BI_POWS[tokens[destTokenSymbol].decimals], + 8n * BI_POWS[tokens[destTokenSymbol].decimals], + 9n * BI_POWS[tokens[destTokenSymbol].decimals], + 10n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + algebra = new Algebra(network, dexKey, dexHelper); + if (algebra.initializePricing) { + await algebra.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + algebra, + network, + dexKey, + dexHelper, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + 'quoteExactInputSingle', + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + algebra, + network, + dexKey, + dexHelper, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + 'quoteExactOutputSingle', + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newAlgebra = new Algebra(network, dexKey, dexHelper); + const poolLiquidity = await newAlgebra.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newAlgebra.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); + + describe('USDCe => GRAIL', () => { + const srcTokenSymbol = 'USDCe'; + const destTokenSymbol = 'GRAIL'; + + const amountsForSell = [ + 0n, + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + 20n * BI_POWS[tokens[srcTokenSymbol].decimals], + 30n * BI_POWS[tokens[srcTokenSymbol].decimals], + 40n * BI_POWS[tokens[srcTokenSymbol].decimals], + 50n * BI_POWS[tokens[srcTokenSymbol].decimals], + 60n * BI_POWS[tokens[srcTokenSymbol].decimals], + 70n * BI_POWS[tokens[srcTokenSymbol].decimals], + 80n * BI_POWS[tokens[srcTokenSymbol].decimals], + 90n * BI_POWS[tokens[srcTokenSymbol].decimals], + 100n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + 2n * BI_POWS[tokens[destTokenSymbol].decimals], + 3n * BI_POWS[tokens[destTokenSymbol].decimals], + 4n * BI_POWS[tokens[destTokenSymbol].decimals], + 5n * BI_POWS[tokens[destTokenSymbol].decimals], + 6n * BI_POWS[tokens[destTokenSymbol].decimals], + 7n * BI_POWS[tokens[destTokenSymbol].decimals], + 8n * BI_POWS[tokens[destTokenSymbol].decimals], + 9n * BI_POWS[tokens[destTokenSymbol].decimals], + 10n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + algebra = new Algebra(network, dexKey, dexHelper); + if (algebra.initializePricing) { + await algebra.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + algebra, + network, + dexKey, + dexHelper, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + 'quoteExactInputSingle', + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + algebra, + network, + dexKey, + dexHelper, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + 'quoteExactOutputSingle', + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newAlgebra = new Algebra(network, dexKey, dexHelper); + const poolLiquidity = await newAlgebra.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newAlgebra.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); + }); +}); + describe('Algebra', function () { const dexKey = 'QuickSwapV3'; let blockNumber: number; @@ -254,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..92132c4a0 100644 --- a/src/dex/algebra/algebra-pool-v1_1.ts +++ b/src/dex/algebra/algebra-pool-v1_1.ts @@ -9,30 +9,48 @@ 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 } from '../uniswap-v3/constants'; import { - OUT_OF_RANGE_ERROR_POSTFIX, - TICK_BITMAP_BUFFER, - TICK_BITMAP_TO_USE, -} from '../uniswap-v3/constants'; -import { uint256ToBigInt } from '../../lib/decoders'; + 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'; +import { + TICK_BITMAP_BUFFER, + TICK_BITMAP_BUFFER_BY_CHAIN, + TICK_BITMAP_TO_USE, + TICK_BITMAP_TO_USE_BY_CHAIN, +} from './constants'; + +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: { @@ -53,6 +71,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 +94,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 +110,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() { @@ -171,10 +199,17 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber } getBitmapRangeToRequest() { - return TICK_BITMAP_TO_USE + TICK_BITMAP_BUFFER; + const networkId = this.dexHelper.config.data.network; + + const tickBitmapToUse = + TICK_BITMAP_TO_USE_BY_CHAIN[networkId] ?? TICK_BITMAP_TO_USE; + const tickBitmapBuffer = + TICK_BITMAP_BUFFER_BY_CHAIN[networkId] ?? TICK_BITMAP_BUFFER; + + return tickBitmapToUse + tickBitmapBuffer; } - private async _fetchPoolStateSingleStep( + async fetchPoolStateSingleStep( blockNumber: number, ): Promise< [bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_1] @@ -336,7 +371,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 +382,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 = {}; @@ -409,6 +669,7 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber const zeroForOne = amount0 > 0n; const [, , , , , communityFee] = AlgebraMath._calculateSwapAndLock( + this.dexHelper.config.data.network, pool, zeroForOne, newSqrtPriceX96, @@ -466,6 +727,7 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber pool.blockTimestamp = bigIntify(blockHeader.timestamp); AlgebraMath._updatePositionTicksAndFees( + this.dexHelper.config.data.network, pool, bottomTick, topTick, @@ -490,6 +752,7 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber pool.blockTimestamp = bigIntify(blockHeader.timestamp); AlgebraMath._updatePositionTicksAndFees( + this.dexHelper.config.data.network, pool, bottomTick, topTick, diff --git a/src/dex/algebra/algebra-pool-v1_9.ts b/src/dex/algebra/algebra-pool-v1_9.ts index f740680a9..308fef47b 100644 --- a/src/dex/algebra/algebra-pool-v1_9.ts +++ b/src/dex/algebra/algebra-pool-v1_9.ts @@ -17,11 +17,7 @@ import { import { ethers } from 'ethers'; import { Contract } from 'web3-eth-contract'; import AlgebraV1_9ABI from '../../abi/algebra/AlgebraPool-v1_9.abi.json'; -import { - OUT_OF_RANGE_ERROR_POSTFIX, - TICK_BITMAP_BUFFER, - TICK_BITMAP_TO_USE, -} from '../uniswap-v3/constants'; +import { OUT_OF_RANGE_ERROR_POSTFIX } from '../uniswap-v3/constants'; import { uint256ToBigInt } from '../../lib/decoders'; import { MultiCallParams } from '../../lib/multi-wrapper'; import { decodeStateMultiCallResultWithRelativeBitmapsV1_9 } from './utils'; @@ -30,9 +26,14 @@ import { _reduceTickBitmap, _reduceTicks, } from '../uniswap-v3/contract-math/utils'; -import { Constants } from './lib/Constants'; import { Network } from '../../constants'; import { TickTable } from './lib/TickTable'; +import { + TICK_BITMAP_BUFFER, + TICK_BITMAP_BUFFER_BY_CHAIN, + TICK_BITMAP_TO_USE, + TICK_BITMAP_TO_USE_BY_CHAIN, +} from './constants'; export class AlgebraEventPoolV1_9 extends StatefulEventSubscriber { handlers: { @@ -172,7 +173,14 @@ export class AlgebraEventPoolV1_9 extends StatefulEventSubscriber 0n; const [, , , , , communityFee] = AlgebraMath._calculateSwapAndLock( + this.dexHelper.config.data.network, pool, zeroForOne, newSqrtPriceX96, @@ -464,6 +473,7 @@ export class AlgebraEventPoolV1_9 extends StatefulEventSubscriber { - readonly isFeeOnTransferSupported: boolean = false; + private readonly factory: AlgebraFactory; + readonly isFeeOnTransferSupported: boolean = true; protected eventPools: Record = {}; + private newlyCreatedPoolKeys: Set = new Set(); + readonly hasConstantPriceLargeAmounts = false; readonly needWrapNative = true; @@ -81,14 +95,17 @@ export class Algebra extends SimpleExchange implements IDex { | typeof AlgebraEventPoolV1_1 | typeof AlgebraEventPoolV1_9; + readonly SRC_TOKEN_DEX_TRANSFERS = 1; + readonly DEST_TOKEN_DEX_TRANSFERS = 1; + constructor( protected network: Network, dexKey: string, protected dexHelper: IDexHelper, protected adapters = Adapters[network] || {}, - readonly routerIface = new Interface(UniswapV3RouterABI), // same abi as uniswapV3 + readonly routerIface = new Interface(SwapRouter), readonly quoterIface = new Interface(AlgebraQuoterABI), - protected config = AlgebraConfig[dexKey][network], + readonly config = AlgebraConfig[dexKey][network], ) { super(dexHelper, dexKey); this.logger = dexHelper.getLogger(dexKey + '-' + network); @@ -104,12 +121,24 @@ export class Algebra extends SimpleExchange implements IDex { this.dexHelper.web3Provider.eth.handleRevert = false; this.config = this._toLowerForAllConfigAddresses(); + // External configuration has priority over internal + this.config.forceRPC = dexHelper.config.data.forceRpcFallbackDexs.includes( + dexKey.toLowerCase(), + ); this.notExistingPoolSetKey = `${CACHE_PREFIX}_${network}_${dexKey}_not_existings_pool_set`.toLowerCase(); this.AlgebraPoolImplem = config.version === 'v1.1' ? AlgebraEventPoolV1_1 : AlgebraEventPoolV1_9; + + this.factory = new AlgebraFactory( + dexHelper, + dexKey, + this.config.factory, + this.logger, + this.onPoolCreatedDeleteFromNonExistingSet, + ); } getAdapters(side: SwapSide): { name: string; index: number }[] | null { @@ -122,23 +151,66 @@ export class Algebra extends SimpleExchange implements IDex { } async initializePricing(blockNumber: number) { - if (!this.dexHelper.config.isSlave) { - const cleanExpiredNotExistingPoolsKeys = async () => { - const maxTimestamp = - Date.now() - ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS; - await this.dexHelper.cache.zremrangebyscore( - this.notExistingPoolSetKey, - 0, - maxTimestamp, - ); - }; + // Init listening to new pools creation + await this.factory.initialize(blockNumber); + + //// COMMENTING DEPRECATED LOGIC: as we now invalidate pools on creation this is not needed anymore + // if (!this.dexHelper.config.isSlave) { + // const cleanExpiredNotExistingPoolsKeys = async () => { + // const maxTimestamp = + // Date.now() - ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS; + // await this.dexHelper.cache.zremrangebyscore( + // this.notExistingPoolSetKey, + // 0, + // maxTimestamp, + // ); + // }; + + // this.intervalTask = setInterval( + // cleanExpiredNotExistingPoolsKeys.bind(this), + // ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS, + // ); + // } + } + + /* + * When a non existing pool is queried, it's blacklisted for an arbitrary long period in order to prevent issuing too many rpc calls + * Once the pool is created, it gets immediately flagged + */ + onPoolCreatedDeleteFromNonExistingSet: OnPoolCreatedCallback = async ({ + token0, + token1, + }) => { + const logPrefix = '[onPoolCreatedDeleteFromNonExistingSet]'; + const [_token0, _token1] = this._sortTokens(token0, token1); + const poolKey = `${_token0}_${_token1}`.toLowerCase(); - this.intervalTask = setInterval( - cleanExpiredNotExistingPoolsKeys.bind(this), - ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS, + this.newlyCreatedPoolKeys.add(poolKey); + + // consider doing it only from master pool for less calls to distant cache + + // delete entry locally to let local instance discover the pool + delete this.eventPools[this.getPoolIdentifier(_token0, _token1)]; + + try { + this.logger.info( + `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}`, + ); + // delete pool record from set + const result = await this.dexHelper.cache.zrem( + this.notExistingPoolSetKey, + [poolKey], + ); + this.logger.info( + `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}; result: ${result}`, + ); + } catch (error) { + this.logger.error( + `${logPrefix} ERROR: failed to delete pool from set: set=${this.notExistingPoolSetKey}; key=${poolKey}`, + error, ); } - } + }; async getPool( srcAddress: Address, @@ -156,14 +228,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; @@ -222,6 +295,7 @@ export class Algebra extends SimpleExchange implements IDex { this.cacheStateKey, this.config.initHash, this.config.deployer, + this.config.forceManualStateGenerate, ); try { @@ -234,27 +308,43 @@ export class Algebra extends SimpleExchange implements IDex { pool!.initRetryAttemptCount = 0; }, }); + + if (this.newlyCreatedPoolKeys.has(key)) { + this.newlyCreatedPoolKeys.delete(key); + } } catch (e) { if (e instanceof Error && e.message.endsWith('Pool does not exist')) { - // no need to await we want the set to have the pool key but it's not blocking - this.dexHelper.cache.zadd( - this.notExistingPoolSetKey, - [Date.now(), key], - 'NX', - ); + /* + protection against 2 race conditions + 1/ if pool.initialize() promise rejects after the Pool creation event got treated + 2/ if the rpc node we hit on the http request is lagging behind the one we got event from (websocket) + */ + if (this.newlyCreatedPoolKeys.has(key)) { + this.logger.warn( + `[block=${blockNumber}][Pool=${key}] newly created pool failed to initialise, trying on next request`, + ); + } else { + this.logger.info( + `[block=${blockNumber}][Pool=${key}] pool failed to initialize so it's marked as non existing`, + e, + ); - // Pool does not exist for this pair, so we can set it to null - // to prevent more requests for this pool - pool = null; - this.logger.trace( - `${this.dexHelper}: Pool: srcAddress=${srcAddress}, destAddress=${destAddress} not found`, - e, - ); + // no need to await we want the set to have the pool key but it's not blocking + this.dexHelper.cache.zadd( + this.notExistingPoolSetKey, + [Date.now(), key], + 'NX', + ); + + // Pool does not exist for this pair, so we can set it to null + // to prevent more requests for this pool + pool = null; + } } 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}`, + `[block=${blockNumber}][Pool=${key}] 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; @@ -263,6 +353,10 @@ export class Algebra extends SimpleExchange implements IDex { if (pool !== null) { const allEventPools = Object.values(this.eventPools); + // if pool was created, delete pool record from non existing set + this.dexHelper.cache + .zrem(this.notExistingPoolSetKey, [key]) + .catch(() => {}); this.logger.info( `starting to listen to new non-null pool: ${key}. Already following ${allEventPools // Not that I like this reduce, but since it is done only on initialization, expect this to be ok @@ -328,10 +422,23 @@ export class Algebra extends SimpleExchange implements IDex { amounts: bigint[], side: SwapSide, pool: IAlgebraEventPool, + transferFees: TransferFeeParams = { + srcFee: 0, + destFee: 0, + srcDexFee: 0, + destDexFee: 0, + }, ): Promise | null> { - this.logger.warn( - `fallback to rpc for ${from.address}_${to.address}_${pool.name}_${pool.poolAddress} pool(s)`, - ); + if (!this.config.forceRPC) { + this.logger.warn( + `fallback to rpc for ${from.address}_${to.address}_${pool.name}_${pool.poolAddress} pool(s)`, + ); + } + + const _isSrcTokenTransferFeeToBeExchanged = + isSrcTokenTransferFeeToBeExchanged(transferFees); + const _isDestTokenTransferFeeToBeExchanged = + isDestTokenTransferFeeToBeExchanged(transferFees); const unitVolume = getBigIntPow( (side === SwapSide.SELL ? from : to).decimals, @@ -347,7 +454,16 @@ export class Algebra extends SimpleExchange implements IDex { ), ); - const calldata = _amounts.map(_amount => ({ + const _amountsWithFee = _isSrcTokenTransferFeeToBeExchanged + ? applyTransferFee( + _amounts, + side, + transferFees.srcDexFee, + this.SRC_TOKEN_DEX_TRANSFERS, + ) + : _amounts; + + const calldata = _amountsWithFee.map(_amount => ({ target: this.config.quoter, gasLimit: ALGEBRA_QUOTE_GASLIMIT, callData: @@ -388,12 +504,22 @@ export class Algebra extends SimpleExchange implements IDex { : Math.round(totalGasCost / totalSuccessFullSwaps); let i = 0; - const _rates = _amounts.map(() => decode(i++)); - const unit: bigint = _rates[0]; + const _rates = _amountsWithFee.map(() => decode(i++)); + + const _ratesWithFee = _isDestTokenTransferFeeToBeExchanged + ? applyTransferFee( + _rates, + side, + transferFees.destDexFee, + this.DEST_TOKEN_DEX_TRANSFERS, + ) + : _rates; + + const unit: bigint = _ratesWithFee[0]; const prices = interpolate( - _amounts.slice(1), - _rates.slice(1), + _amountsWithFee.slice(1), + _ratesWithFee.slice(1), amounts, side, ); @@ -403,6 +529,7 @@ export class Algebra extends SimpleExchange implements IDex { prices, unit, data: { + feeOnTransfer: _isSrcTokenTransferFeeToBeExchanged, path: [ { tokenIn: from.address, @@ -425,6 +552,12 @@ export class Algebra extends SimpleExchange implements IDex { side: SwapSide, blockNumber: number, limitPools?: string[], + transferFees: TransferFeeParams = { + srcFee: 0, + destFee: 0, + srcDexFee: 0, + destDexFee: 0, + }, ): Promise> { try { const _srcToken = this.dexHelper.config.wrapETH(srcToken); @@ -435,6 +568,11 @@ export class Algebra extends SimpleExchange implements IDex { _destToken, ); + const _isSrcTokenTransferFeeToBeExchanged = + isSrcTokenTransferFeeToBeExchanged(transferFees); + const _isDestTokenTransferFeeToBeExchanged = + isDestTokenTransferFeeToBeExchanged(transferFees); + if (_srcAddress === _destAddress) return null; if ( @@ -443,9 +581,15 @@ export class Algebra extends SimpleExchange implements IDex { return null; const pool = await this.getPool(_srcAddress, _destAddress, blockNumber); - if (!pool) return null; + if (_isSrcTokenTransferFeeToBeExchanged && side == SwapSide.BUY) { + this.logger.error( + `pool: ${pool.poolAddress} doesn't support buy for tax srcToken ${srcToken.address}`, + ); + return null; + } + if (this.config.forceRPC) { const rpcPrice = await this.getPricingFromRpc( _srcToken, @@ -453,6 +597,7 @@ export class Algebra extends SimpleExchange implements IDex { amounts, side, pool, + transferFees, ); return rpcPrice; @@ -487,6 +632,7 @@ export class Algebra extends SimpleExchange implements IDex { amounts, side, pool, + transferFees, ); return rpcPrice; @@ -518,16 +664,26 @@ export class Algebra extends SimpleExchange implements IDex { const balanceDestToken = _destAddress === pool.token0 ? state.balance0 : state.balance1; + const [unitAmountWithFee, ...amountsWithFee] = + _isSrcTokenTransferFeeToBeExchanged + ? applyTransferFee( + [unitAmount, ..._amounts], + side, + transferFees.srcDexFee, + this.SRC_TOKEN_DEX_TRANSFERS, + ) + : [unitAmount, ..._amounts]; + const unitResult = this._getOutputs( state, - [unitAmount], + [unitAmountWithFee], zeroForOne, side, balanceDestToken, ); const pricesResult = this._getOutputs( state, - _amounts, + amountsWithFee, zeroForOne, side, balanceDestToken, @@ -538,10 +694,20 @@ export class Algebra extends SimpleExchange implements IDex { return null; } - const prices = [0n, ...pricesResult.outputs]; + const [unitResultWithFee, ...pricesResultWithFee] = + _isDestTokenTransferFeeToBeExchanged + ? applyTransferFee( + [unitResult.outputs[0], ...pricesResult.outputs], + side, + transferFees.destDexFee, + this.DEST_TOKEN_DEX_TRANSFERS, + ) + : [unitResult.outputs[0], ...pricesResult.outputs]; + + const prices = [0n, ...pricesResultWithFee]; const gasCost = [ 0, - ...pricesResult.outputs.map((p, index) => { + ...pricesResultWithFee.map((p, index) => { if (p == 0n) { return 0; } else { @@ -556,9 +722,10 @@ export class Algebra extends SimpleExchange implements IDex { return [ { - unit: unitResult.outputs[0], + unit: unitResultWithFee, prices, data: { + feeOnTransfer: _isSrcTokenTransferFeeToBeExchanged, path: [ { tokenIn: _srcAddress, @@ -591,19 +758,20 @@ export class Algebra extends SimpleExchange implements IDex { data: AlgebraData, side: SwapSide, ): AdapterExchangeParam { - const { path: rawPath } = data; - const path = this._encodePath(rawPath, side); + let path = this._encodePath(data.path, side); const payload = this.abiCoder.encodeParameter( { ParentStruct: { path: 'bytes', deadline: 'uint256', + feeOnTransfer: 'bool', }, }, { path, deadline: getLocalDeadlineAsFriendlyPlaceholder(), // FIXME: more gas efficient to pass block.timestamp in adapter + feeOnTransfer: data.feeOnTransfer, }, ); @@ -647,30 +815,52 @@ export class Algebra extends SimpleExchange implements IDex { data: AlgebraData, side: SwapSide, ): Promise { - const swapFunction = - side === SwapSide.SELL - ? UniswapV3Functions.exactInput - : UniswapV3Functions.exactOutput; - - const path = this._encodePath(data.path, side); - const swapFunctionParams: UniswapV3SimpleSwapParams = - side === SwapSide.SELL - ? { - recipient: this.augustusAddress, - deadline: getLocalDeadlineAsFriendlyPlaceholder(), - amountIn: srcAmount, - amountOutMinimum: destAmount, - path, - } - : { - recipient: this.augustusAddress, - deadline: getLocalDeadlineAsFriendlyPlaceholder(), - amountOut: destAmount, - amountInMaximum: srcAmount, - path, - }; + let swapFunction; + let swapParams; + + if (data.feeOnTransfer) { + _require( + data.path.length !== 1, + `LOGIC ERROR: multihop is not supported for feeOnTransfer token, passed: ${data.path + .map(p => `${p?.tokenIn}->${p?.tokenOut}`) + .join(' ')}`, + ); + swapFunction = AlgebraFunctions.exactInputWithFeeToken; + swapParams = { + limitSqrtPrice: '0', + recipient: this.augustusAddress, + deadline: getLocalDeadlineAsFriendlyPlaceholder(), + amountIn: srcAmount, + amountOutMinimum: destAmount, + tokenIn: data.path[0].tokenIn, + tokenOut: data.path[0].tokenOut, + }; + } else { + swapFunction = + side === SwapSide.SELL + ? AlgebraFunctions.exactInput + : AlgebraFunctions.exactOutput; + const path = this._encodePath(data.path, side); + swapParams = + side === SwapSide.SELL + ? { + recipient: this.augustusAddress, + deadline: getLocalDeadlineAsFriendlyPlaceholder(), + amountIn: srcAmount, + amountOutMinimum: destAmount, + path, + } + : { + recipient: this.augustusAddress, + deadline: getLocalDeadlineAsFriendlyPlaceholder(), + amountOut: destAmount, + amountInMaximum: srcAmount, + path, + }; + } + const swapData = this.routerIface.encodeFunctionData(swapFunction, [ - swapFunctionParams, + swapParams, ]); return this.buildSimpleParamWithoutWETHConversion( @@ -786,6 +976,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; } @@ -799,6 +990,7 @@ export class Algebra extends SimpleExchange implements IDex { ): OutputResult | null { try { const outputsResult = AlgebraMath.queryOutputs( + this.network, state, amounts, zeroForOne, @@ -810,20 +1002,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 f8f942067..b8a64c3e0 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: { @@ -97,7 +98,7 @@ export const Adapters: Record = { [SwapSide.BUY]: [{ name: 'PolygonZkEvmBuyAdapter', index: 1 }], }, [Network.ARBITRUM]: { - [SwapSide.SELL]: [{ name: 'ArbitrumAdapter01', index: 3 }], + [SwapSide.SELL]: [{ name: 'ArbitrumAdapter02', index: 7 }], [SwapSide.BUY]: [{ name: 'ArbitrumBuyAdapter', index: 2 }], }, [Network.OPTIMISM]: { diff --git a/src/dex/algebra/constants.ts b/src/dex/algebra/constants.ts new file mode 100644 index 000000000..4f53890a7 --- /dev/null +++ b/src/dex/algebra/constants.ts @@ -0,0 +1,17 @@ +import { Network } from '../../constants'; + +/// THIS FILE CONTAINS OVERRIDES OF UniswapV3's constant file + +export const TICK_BITMAP_TO_USE = 400n; + +export const TICK_BITMAP_BUFFER = 800n; + +export const TICK_BITMAP_TO_USE_BY_CHAIN: Record = { + [Network.ZKEVM]: 10n, +}; + +export const TICK_BITMAP_BUFFER_BY_CHAIN: Record = { + [Network.ZKEVM]: 4n, +}; + +export const MAX_PRICING_COMPUTATION_STEPS_ALLOWED = 4096; diff --git a/src/dex/algebra/lib/AlgebraMath.ts b/src/dex/algebra/lib/AlgebraMath.ts index af9af67d1..ea9f06df7 100644 --- a/src/dex/algebra/lib/AlgebraMath.ts +++ b/src/dex/algebra/lib/AlgebraMath.ts @@ -16,12 +16,10 @@ import { PriceComputationState, _updatePriceComputationObjects, } from '../../uniswap-v3/contract-math/uniswap-v3-math'; -import { - MAX_PRICING_COMPUTATION_STEPS_ALLOWED, - OUT_OF_RANGE_ERROR_POSTFIX, -} from '../../uniswap-v3/constants'; +import { OUT_OF_RANGE_ERROR_POSTFIX } from '../../uniswap-v3/constants'; import { TickManager } from './TickManager'; import { TickTable } from './TickTable'; +import { MAX_PRICING_COMPUTATION_STEPS_ALLOWED } from '../constants'; type UpdatePositionCache = { price: bigint; @@ -56,6 +54,7 @@ const isPoolV1_9 = ( // % START OF COPY PASTA FROM UNISWAPV3 % function _priceComputationCycles( + networkId: number, poolState: DeepReadonly, ticksCopy: Record, state: PriceComputationState, @@ -114,6 +113,7 @@ function _priceComputationCycles( try { [step.tickNext, step.initialized] = TickTable.nextInitializedTickWithinOneWord( + networkId, poolState, state.tick, zeroForOne, @@ -230,6 +230,7 @@ function _priceComputationCycles( class AlgebraMathClass { queryOutputs( + networkId: number, poolState: DeepReadonly, amounts: bigint[], zeroForOne: boolean, @@ -311,6 +312,7 @@ class AlgebraMathClass { if (!isOutOfRange) { const [finalState, { latestFullCycleState, latestFullCycleCache }] = _priceComputationCycles( + networkId, poolState, ticksCopy, state, @@ -416,6 +418,7 @@ class AlgebraMathClass { } _updatePositionTicksAndFees( + networkId: number, state: PoolStateV1_1 | PoolState_v1_9, bottomTick: bigint, topTick: bigint, @@ -449,6 +452,7 @@ class AlgebraMathClass { ) { toggledBottom = true; TickTable.toggleTick( + networkId, state, bottomTick, state.areTicksCompressed ? state.tickSpacing : undefined, @@ -469,6 +473,7 @@ class AlgebraMathClass { ) { toggledTop = true; TickTable.toggleTick( + networkId, state, topTick, state.areTicksCompressed ? state.tickSpacing : undefined, @@ -509,6 +514,7 @@ class AlgebraMathClass { } _calculateSwapAndLock( + networkId: number, poolState: PoolStateV1_1 | PoolState_v1_9, zeroToOne: boolean, newSqrtPriceX96: bigint, @@ -579,6 +585,7 @@ class AlgebraMathClass { //equivalent of tickTable.nextTickInTheSameRow(currentTick, zeroToOne); [step.nextTick, step.initialized] = TickTable.nextInitializedTickWithinOneWord( + networkId, poolState, currentTick, zeroToOne, @@ -590,7 +597,7 @@ class AlgebraMathClass { // equivalent of PriceMovementMath.movePriceTowardsTarget const result = SwapMath.computeSwapStep( - poolState.globalState.price, + currentPrice, zeroToOne == step.nextTickPrice < newSqrtPriceX96 ? newSqrtPriceX96 : step.nextTickPrice, @@ -653,6 +660,13 @@ class AlgebraMathClass { } } + _require( + currentPrice === newSqrtPriceX96 && currentTick === newTick, + 'LOGIC ERROR: calculated (currentPrice,currentTick) and (newSqrtPriceX96, newTick) from event should always be equal at the end', + { currentPrice, newSqrtPriceX96, currentTick, newTick }, + 'currentPrice === newSqrtPriceX96 && currentTick === newTick', + ); + let [amount0, amount1] = zeroToOne == cache.exactInput // the amount to provide could be less then initially specified (e.g. reached limit) ? [cache.amountRequiredInitial - amountRequired, cache.amountCalculated] // the amount to get could be less then initially specified (e.g. reached limit) diff --git a/src/dex/algebra/lib/TickTable.ts b/src/dex/algebra/lib/TickTable.ts index c90dd50c9..da600e7df 100644 --- a/src/dex/algebra/lib/TickTable.ts +++ b/src/dex/algebra/lib/TickTable.ts @@ -1,15 +1,18 @@ import { IAlgebraPoolState } from '../types'; import { _require } from '../../../utils'; import { DeepReadonly } from 'ts-essentials'; +import { OUT_OF_RANGE_ERROR_POSTFIX } from '../../uniswap-v3/constants'; +import { TickMath } from '../../uniswap-v3/contract-math/TickMath'; +import { Yul } from './yul-helper'; import { - OUT_OF_RANGE_ERROR_POSTFIX, TICK_BITMAP_BUFFER, + TICK_BITMAP_BUFFER_BY_CHAIN, TICK_BITMAP_TO_USE, -} from '../../uniswap-v3/constants'; -import { TickMath } from '../../uniswap-v3/contract-math/TickMath'; -import { Yul } from './yul-helper'; + TICK_BITMAP_TO_USE_BY_CHAIN, +} from '../constants'; function isWordPosOut( + networkId: number, wordPos: bigint, startTickBitmap: bigint, // For pricing we use wider range to check price impact. If function called from event @@ -19,14 +22,19 @@ function isWordPosOut( let lowerTickBitmapLimit; let upperTickBitmapLimit; + const tickBitmapToUse = + TICK_BITMAP_TO_USE_BY_CHAIN[networkId] ?? TICK_BITMAP_TO_USE; + const tickBitmapBuffer = + TICK_BITMAP_BUFFER_BY_CHAIN[networkId] ?? TICK_BITMAP_BUFFER; + if (isPriceQuery) { lowerTickBitmapLimit = - startTickBitmap - (TICK_BITMAP_BUFFER + TICK_BITMAP_TO_USE); + startTickBitmap - (tickBitmapBuffer + tickBitmapToUse); upperTickBitmapLimit = - startTickBitmap + (TICK_BITMAP_BUFFER + TICK_BITMAP_TO_USE); + startTickBitmap + (tickBitmapBuffer + tickBitmapToUse); } else { - lowerTickBitmapLimit = startTickBitmap - TICK_BITMAP_BUFFER; - upperTickBitmapLimit = startTickBitmap + TICK_BITMAP_BUFFER; + lowerTickBitmapLimit = startTickBitmap - tickBitmapBuffer; + upperTickBitmapLimit = startTickBitmap + tickBitmapBuffer; } _require( @@ -43,11 +51,12 @@ export class TickTable { // wordPos BigInt.asIntN(16, tick >> 8n), // bitPos - BigInt.asUintN(8, tick & BigInt(0xff)), + BigInt.asUintN(8, tick & 0xffn), ]; } static toggleTick( + networkId: number, state: Pick, tick: bigint, tickSpacing?: bigint, @@ -56,11 +65,11 @@ export class TickTable { tick /= tickSpacing; } const [rowNumber, bitNumber] = TickTable.position(tick); - const mask = 1n << bitNumber; + const mask = BigInt.asUintN(256, 1n << bitNumber); // toggleTick is used only in _updatePosition which is always state changing event // Therefore it is never used in price query - isWordPosOut(rowNumber, state.startTickBitmap, false); + isWordPosOut(networkId, rowNumber, state.startTickBitmap, false); const stringWordPos = rowNumber.toString(); if (state.tickBitmap[stringWordPos] === undefined) { @@ -74,6 +83,7 @@ export class TickTable { } static nextInitializedTickWithinOneWord( + networkId: number, state: DeepReadonly< Pick >, @@ -83,20 +93,24 @@ export class TickTable { tickSpacing?: bigint, ): [bigint, boolean] { if (tickSpacing !== undefined) { - tick = Yul.sub( - Yul.sdiv(tick, tickSpacing), - Yul.and( - Yul.slt(tick, 0n), - Yul.not(Yul.iszero(Yul.smod(tick, tickSpacing))), + tick = BigInt.asIntN( + 24, + Yul.sub( + Yul.sdiv(tick, tickSpacing), + Yul.and( + Yul.slt(tick, 0n), + Yul.not(Yul.iszero(Yul.smod(tick, tickSpacing))), + ), ), ); } - const [rowNumber, bitNumber] = TickTable.position(tick); - isWordPosOut(rowNumber, state.startTickBitmap, isPriceQuery); - let tickBitmapValue = state.tickBitmap[rowNumber.toString()]; - tickBitmapValue = tickBitmapValue === undefined ? 0n : tickBitmapValue; if (lte) { - const _row = tickBitmapValue << (255n - bitNumber); + const [rowNumber, bitNumber] = TickTable.position(tick); + isWordPosOut(networkId, rowNumber, state.startTickBitmap, isPriceQuery); + let tickBitmapValue = state.tickBitmap[rowNumber.toString()]; + tickBitmapValue = tickBitmapValue === undefined ? 0n : tickBitmapValue; + + const _row = BigInt.asUintN(256, tickBitmapValue << (255n - bitNumber)); if (_row != 0n) { tick -= BigInt.asIntN(24, 255n - TickTable.getMostSignificantBit(_row)); return [TickTable.boundTick(tick, tickSpacing), true]; @@ -106,7 +120,13 @@ export class TickTable { } } else { tick += 1n; - const _row = tickBitmapValue >> bitNumber; + + const [rowNumber, bitNumber] = TickTable.position(tick); + isWordPosOut(networkId, rowNumber, state.startTickBitmap, isPriceQuery); + let tickBitmapValue = state.tickBitmap[rowNumber.toString()]; + tickBitmapValue = tickBitmapValue === undefined ? 0n : tickBitmapValue; + + const _row = BigInt.asUintN(256, tickBitmapValue >> bitNumber); if (_row !== 0n) { tick += BigInt.asIntN( 24, @@ -123,80 +143,71 @@ export class TickTable { static getSingleSignificantBit(word: bigint): bigint { let singleBitPos = 0n; singleBitPos = Yul.iszero( - word && - BigInt( - '0x5555555555555555555555555555555555555555555555555555555555555555', - ), + word & + 0x5555555555555555555555555555555555555555555555555555555555555555n, ); singleBitPos = - singleBitPos || - Yul.iszero( - word && - BigInt( - '0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff', - ), - ) << 7n; + singleBitPos | + (Yul.iszero( + word & + 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffffn, + ) << + 7n); singleBitPos = - singleBitPos || - Yul.iszero( - word && - BigInt( - '0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff', - ), - ) << 6n; + singleBitPos | + (Yul.iszero( + word & + 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffffn, + ) << + 6n); singleBitPos = - singleBitPos || - Yul.iszero( - word && - BigInt( - '0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff', - ), - ) << 5n; + singleBitPos | + (Yul.iszero( + word & + 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffffn, + ) << + 5n); singleBitPos = - singleBitPos || - Yul.iszero( - word && - BigInt( - '0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff', - ), - ) << 4n; + singleBitPos | + (Yul.iszero( + word & + 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffffn, + ) << + 4n); singleBitPos = - singleBitPos || - Yul.iszero( - word && - BigInt( - '0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff', - ), - ) << 3n; + singleBitPos | + (Yul.iszero( + word & + 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ffn, + ) << + 3n); singleBitPos = - singleBitPos || - Yul.iszero( - word && - BigInt( - '0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f', - ), - ) << 2n; + singleBitPos | + (Yul.iszero( + word & + 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0fn, + ) << + 2n); singleBitPos = - singleBitPos || - Yul.iszero( - word && - BigInt( - '0x3333333333333333333333333333333333333333333333333333333333333333', - ), - ) << 1n; + singleBitPos | + (Yul.iszero( + word & + 0x3333333333333333333333333333333333333333333333333333333333333333n, + ) << + 1n); return BigInt.asUintN(8, singleBitPos); } static getMostSignificantBit(word: bigint): bigint { - word = word || word >> 1n; - word = word || word >> 2n; - word = word || word >> 4n; - word = word || word >> 8n; - word = word || word >> 16n; - word = word || word >> 32n; - word = word || word >> 64n; - word = word || word >> 128n; + word = word | (word >> 1n); + word = word | (word >> 2n); + word = word | (word >> 4n); + word = word | (word >> 8n); + word = word | (word >> 16n); + word = word | (word >> 32n); + word = word | (word >> 64n); + word = word | (word >> 128n); word = word - (word >> 1n); return TickTable.getSingleSignificantBit(BigInt.asUintN(256, word)); } diff --git a/src/dex/algebra/scripts/validate-state.ts b/src/dex/algebra/scripts/validate-state.ts new file mode 100644 index 000000000..bfe7c036c --- /dev/null +++ b/src/dex/algebra/scripts/validate-state.ts @@ -0,0 +1,292 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Network } from '../../../constants'; +import { DummyDexHelper } from '../../../dex-helper'; +import { Algebra } from '../algebra'; +import { uint256ToBigInt } from '../../../lib/decoders'; +import { + DecodedStateMultiCallResultWithRelativeBitmapsV1_9, + PoolState_v1_9, +} from '../types'; +import { decodeStateMultiCallResultWithRelativeBitmapsV1_9 } from '../utils'; +import { MultiCallParams, MultiWrapper } from '../../../lib/multi-wrapper'; +import Web3 from 'web3'; +import multiABIV2 from '../../../abi/multi-v2.json'; +import ERC20ABI from '../../../abi/erc20.json'; +import { getLogger } from '../../../lib/log4js'; +import { Interface } from 'ethers/lib/utils'; +import { AbiItem } from 'web3-utils'; +import AlgebraStateMulticallABI from '../../../abi/algebra/AlgebraStateMulticall.abi.json'; +import { TICK_BITMAP_TO_USE, TICK_BITMAP_BUFFER } from '../constants'; +import { BlockHeader } from 'web3-eth'; +import { ethers } from 'ethers'; + +// public RPC, replace to RPC with archive node +const web3Provider = new Web3(''); + +// pool and tokens for specific broken tx - 0x9a296a13f7c5eb8ce838e15ecbe3888e8998a14803a1ec46838611e1ff118d6e +const factoryAddress = '0x1a3c9b1d2f0529d97f2afc5136cc23e58f1fd35b'; +const poolAddress = '0xb1026b8e7276e7ac75410f1fcbbe21796e8f7526'; +const srcToken = { + address: `0x82af49447d8a07e3bd95bd0d56f35241523fbab1`, + decimals: 18, +}; +const destToken = { + address: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', + decimals: 6, +}; +const blockHeaders: Record = {}; + +const multiContract = new web3Provider.eth.Contract( + multiABIV2 as any, + '0x7eCfBaa8742fDf5756DAC92fbc8b90a19b8815bF', +); +const stateMultiContract = new web3Provider.eth.Contract( + AlgebraStateMulticallABI as AbiItem[], + '0x2cB568442a102dF518b3D37CBD0d2884523C940B', +); + +const multiWrapper = new MultiWrapper(multiContract, getLogger(`Ticks`)); +const erc20Interface = new Interface(ERC20ABI); + +function getBitmapRangeToRequest() { + return TICK_BITMAP_TO_USE + TICK_BITMAP_BUFFER; +} + +async function _fetchPoolState_v1_9SingleStep( + blockNumber: number, +): Promise< + [bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_9] +> { + const callData: MultiCallParams< + bigint | DecodedStateMultiCallResultWithRelativeBitmapsV1_9 + >[] = [ + { + target: srcToken.address, + callData: erc20Interface.encodeFunctionData('balanceOf', [poolAddress]), + decodeFunction: uint256ToBigInt, + }, + { + target: destToken.address, + callData: erc20Interface.encodeFunctionData('balanceOf', [poolAddress]), + decodeFunction: uint256ToBigInt, + }, + { + target: stateMultiContract.options.address, + callData: stateMultiContract.methods + .getFullStateWithRelativeBitmaps( + factoryAddress, + srcToken.address, + destToken.address, + getBitmapRangeToRequest(), + getBitmapRangeToRequest(), + ) + .encodeABI(), + decodeFunction: decodeStateMultiCallResultWithRelativeBitmapsV1_9, + }, + ]; + + const [resBalance0, resBalance1, resState] = await multiWrapper.tryAggregate< + bigint | DecodedStateMultiCallResultWithRelativeBitmapsV1_9 + >(false, callData, blockNumber, multiWrapper.defaultBatchSize, false); + + const [balance0, balance1, _state] = [ + resBalance0.returnData, + resBalance1.returnData, + resState.returnData, + ] as [bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmapsV1_9]; + + return [balance0, balance1, _state]; +} + +function preprocessField(value: any): string { + if ( + typeof value === 'bigint' || + typeof value === 'number' || + ethers.BigNumber.isBigNumber(value) + ) { + return value.toString(); + } + return value; +} + +function compareAndLogDifferences( + obj1: T, + obj2: Y, + keyNames: Array, + checkEachKey = false, +) { + let isValid = true; + let keys = keyNames; + + if (checkEachKey) { + // find common keys + const keys1 = Object.keys(obj1); + const keys2 = new Set(Object.keys(obj2)); + keys = keys1.filter(key => keys2.has(key)) as Array; + } + + for (let fieldName of keys) { + const value1 = preprocessField(obj1[fieldName]); + const value2 = preprocessField(obj2[fieldName]); + + if (value1 !== value2) { + console.log( + `${fieldName.toString()} mismatch: actual: ${value1} vs pool: ${value2}`, + ); + isValid = false; + } + } + + return isValid; +} + +async function isPoolStateEqualToReal( + state: PoolState_v1_9, + blockNumber: number, +) { + const [balance0, balance1, contractState] = + await _fetchPoolState_v1_9SingleStep(blockNumber); + + const isValidBalances = compareAndLogDifferences( + state, + { balance0, balance1, ...contractState }, + ['balance0', 'balance1', 'liquidity', 'tickSpacing'], + ); + + const isValidGlobalState = compareAndLogDifferences( + state.globalState, + contractState.globalState, + // can check only for one of them, because they are directly co-related + ['price', 'tick'], + ); + + const isValidTickBitmap = compareAndLogDifferences( + state.tickBitmap, + contractState.tickBitmap, + [], + true, + ); + + let isValidTicks = true; + for (let tick of contractState.ticks) { + const stateTick = state?.ticks[tick.index]; + + const isValidTick = compareAndLogDifferences(stateTick, tick.value, [ + 'liquidityGross', + 'liquidityNet', + // next fields doesn't affect pricing, so skip checks + // 'initialized', + // 'secondsOutside', + // 'secondsPerLiquidityOutsideX128', + // 'tickCumulativeOutside' + ]); + + if (isValidTicks && !isValidTick) { + isValidTicks = false; + } + } + + return ( + isValidBalances && isValidGlobalState && isValidTicks && isValidTickBitmap + ); +} + +async function checkPoolStateForBlockRange( + startBlockNumber: number, + endBlockNumber: number, +): Promise { + const network = Network.ARBITRUM; + const dexKey = 'CamelotV3'; + const dexHelper = new DummyDexHelper(network); + + const algebra = new Algebra(network, dexKey, dexHelper); + const pool = await algebra.getPool( + srcToken.address, + destToken.address, + startBlockNumber, + ); + + const logsToDispatch = await dexHelper.provider.getLogs({ + fromBlock: startBlockNumber, + toBlock: endBlockNumber, + address: poolAddress, + }); + + console.log(logsToDispatch.length); + + // group logs by block number + const logsByBlockNumber: Record = {}; + for (let log of logsToDispatch) { + if (!logsByBlockNumber[log.blockNumber]) { + logsByBlockNumber[log.blockNumber] = []; + } + logsByBlockNumber[log.blockNumber].push(log); + } + + const sortedBlocks = Object.keys(logsByBlockNumber) + .map(Number) + .sort((a, b) => Number(a) - Number(b)); + + for (let blockNumber of sortedBlocks) { + if (!blockHeaders[blockNumber]) { + blockHeaders[blockNumber] = await dexHelper.web3Provider.eth.getBlock( + blockNumber, + ); + } + + await pool?.update(logsByBlockNumber[blockNumber], { + [blockNumber]: blockHeaders[blockNumber], + }); + } + + const state = pool?.getState(startBlockNumber) as PoolState_v1_9; + return isPoolStateEqualToReal(state, endBlockNumber); +} + +async function findBreakingBlock(startBlock: number, endBlock: number) { + let left = startBlock; + let right = endBlock; + + // If the state is valid at the start, then there's no breaking block in the range + if (await checkPoolStateForBlockRange(left, right)) { + return -1; // Indicates no breaking block found + } + + while (left <= right) { + const mid = left + Math.floor((right - left) / 2); + const isValid = await checkPoolStateForBlockRange(startBlock, mid); + + if (isValid) { + // If the state is valid up to mid, the issue must be in the second half + left = mid + 1; + } else { + // If the state is not valid up to mid, the issue is in the first half + // But we need to check if mid is the first occurrence of the issue + if ( + mid === startBlock || + (await checkPoolStateForBlockRange(startBlock, mid - 1)) + ) { + return mid; // Found the breaking block + } + right = mid - 1; + } + } + + return -1; // Should not reach here if there's a breaking block +} + +async function main() { + // use findBreakingBlock to find the block where the state is broken + // console.log(await findBreakingBlock(startBlockNumber, endBlockNumber)); + // previously broken block 150502863 + // console.log(await checkPoolStateForBlockRange(150502853, 150502873)); + // console.log(await checkPoolStateForBlockRange(152087800, 152287800)); + // console.log(await checkPoolStateForBlockRange(152100945, 152100947)); +} + +main() + .then(() => console.log('Done')) + .catch(e => console.error(e)); diff --git a/src/dex/algebra/types.ts b/src/dex/algebra/types.ts index 98a8c1d33..195a85e9a 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 @@ -38,10 +38,10 @@ type GlobalState_v1_9 = { export type PoolState_v1_9 = { pool: string; blockTimestamp: bigint; - tickSpacing: bigint; // is actually constant + tickSpacing: bigint; globalState: GlobalState_v1_9; // eq slot0 liquidity: bigint; - maxLiquidityPerTick: bigint; // is actually constant + maxLiquidityPerTick: bigint; tickBitmap: Record; // actually called tickTable in contract- ticks: Record; // although variable names are different in contracts but matches UniswapV3 TickInfo struct 1:1 isValid: boolean; @@ -51,14 +51,28 @@ export type PoolState_v1_9 = { areTicksCompressed: boolean; }; +export type FactoryState = Record; + export type AlgebraData = { path: { tokenIn: Address; tokenOut: Address; }[]; + feeOnTransfer: boolean; isApproved?: boolean; }; +export type AlgebraDataWithFee = { + tokenIn: Address; + tokenOut: Address; +}; + +export enum AlgebraFunctions { + exactInput = 'exactInput', + exactOutput = 'exactOutput', + exactInputWithFeeToken = 'exactInputSingleSupportingFeeOnTransferTokens', +} + export type DexParams = { router: Address; quoter: Address; @@ -72,6 +86,7 @@ export type DexParams = { initHash: string; version: 'v1.1' | 'v1.9'; forceRPC?: boolean; + forceManualStateGenerate?: boolean; }; export type IAlgebraPoolState = PoolStateV1_1 | PoolState_v1_9; @@ -95,7 +110,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-v1/balancer-v1.ts b/src/dex/balancer-v1/balancer-v1.ts index cfdc4d67b..1469735c9 100644 --- a/src/dex/balancer-v1/balancer-v1.ts +++ b/src/dex/balancer-v1/balancer-v1.ts @@ -28,17 +28,36 @@ import { } from './types'; import { SimpleExchange } from '../simple-exchange'; import { + MIN_USD_LIQUIDITY_TO_FETCH, BalancerV1Config, Adapters, - POOLS_FETCH_TIMEOUT, MAX_POOLS_FOR_PRICING, BALANCER_SWAP_GAS_COST, + MAX_POOL_CNT, } from './config'; import { BalancerV1EventPool } from './balancer-v1-pool'; import { generatePoolStates } from './utils'; import BalancerV1ExchangeProxyABI from '../../abi/BalancerV1ExchangeProxy.json'; import BalancerCustomMulticallABI from '../../abi/BalancerCustomMulticall.json'; +const fetchAllPoolsQuery = `query { + pools(first: ${MAX_POOL_CNT.toString()} + orderBy: liquidity + orderDirection: desc + where: {liquidity_gt: ${MIN_USD_LIQUIDITY_TO_FETCH.toString()}}) { + id + swapFee + totalWeight + tokensList + tokens { + address + balance + decimals + denormWeight + } + } + }`; + export class BalancerV1 extends SimpleExchange implements IDex @@ -82,10 +101,21 @@ export class BalancerV1 // for pricing requests. It is optional for a DEX to // implement this function async initializePricing(_blockNumber: number) { - this.poolsInfo = await this.dexHelper.httpRequest.get( - this.config.poolsURL, - POOLS_FETCH_TIMEOUT, + const { data } = await this.dexHelper.httpRequest.post<{ + data: { pools: PoolInfo[] }; + }>( + this.config.subgraphURL, + { query: fetchAllPoolsQuery }, + SUBGRAPH_TIMEOUT, ); + + if (!(data && data.pools)) + throw new Error( + `Error ${this.dexKey} Subgraph: couldn't fetch the pools from the subgraph`, + ); + + this.poolsInfo = data; + this.poolInfosByToken = {}; for (const poolInfo of this.poolsInfo.pools) { for (const tokenAddress of poolInfo.tokensList) { diff --git a/src/dex/balancer-v1/config.ts b/src/dex/balancer-v1/config.ts index a1ea4e29d..0ec496a67 100644 --- a/src/dex/balancer-v1/config.ts +++ b/src/dex/balancer-v1/config.ts @@ -2,7 +2,8 @@ import { DexParams } from './types'; import { DexConfigMap, AdapterMappings } from '../../types'; import { Network, SwapSide } from '../../constants'; -export const POOLS_FETCH_TIMEOUT = 10000; +export const MAX_POOL_CNT = 1000; +export const MIN_USD_LIQUIDITY_TO_FETCH = 100; export const BALANCES_MULTICALL_POOLS_LIMIT = 200; export const MAX_POOLS_FOR_PRICING = 5; export const BALANCER_SWAP_GAS_COST = 120 * 1000; diff --git a/src/dex/balancer-v2/balancer-v2-e2e.test.ts b/src/dex/balancer-v2/balancer-v2-e2e.test.ts index 1aadf7669..0938b3c25 100644 --- a/src/dex/balancer-v2/balancer-v2-e2e.test.ts +++ b/src/dex/balancer-v2/balancer-v2-e2e.test.ts @@ -2,7 +2,11 @@ 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 +14,99 @@ 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 +193,18 @@ describe('BalancerV2 E2E', () => { buyAmount: '10000000000', }, ], + [ + { + name: 'USDC', + sellAmount: '111000000', + buyAmount: '111000000', + }, + { + name: 'wstETH', + sellAmount: '100000000000000000', + buyAmount: '100000000000000000', + }, + ], ]; sideToContractMethods.forEach((contractMethods, side) => @@ -1054,7 +1163,7 @@ describe('BalancerV2 E2E', () => { sellAmount: '200000000', buyAmount: '2000000000', }, - ] + ], ]; sideToContractMethods.forEach((contractMethods, side) => @@ -1137,7 +1246,7 @@ describe('BalancerV2 E2E', () => { sellAmount: '200000000', buyAmount: '2000000000000000', }, - ] + ], ]; sideToContractMethods.forEach((contractMethods, side) => @@ -1181,7 +1290,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 +1566,4 @@ describe('BalancerV2 E2E', () => { }); }); }); - }); diff --git a/src/dex/balancer-v2/balancer-v2.ts b/src/dex/balancer-v2/balancer-v2.ts index f71274414..f71de9e44 100644 --- a/src/dex/balancer-v2/balancer-v2.ts +++ b/src/dex/balancer-v2/balancer-v2.ts @@ -72,6 +72,92 @@ 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', + '0xe7b1d394f3b40abeaa0b64a545dbcf89da1ecb3f00010000000000000000009a', + + // 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 +167,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 +179,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 +910,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..1f05ae1c1 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/camelot/camelot-pool-discovery.test.ts b/src/dex/camelot/camelot-pool-discovery.test.ts new file mode 100644 index 000000000..ee09eef11 --- /dev/null +++ b/src/dex/camelot/camelot-pool-discovery.test.ts @@ -0,0 +1,149 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Network, SwapSide } from '../../constants'; +import { DummyDexHelper } from '../../dex-helper'; +import { BlockHeader } from 'web3-eth'; +import { Camelot } from './camelot'; +import { CamelotConfig } from './config'; +import { Log } from 'web3-core'; + +const network = Network.ARBITRUM; +const dexKey = 'Camelot'; + +interface Pool { + startBlockNumber: number; + endBlockNumber: number; + address: string; + srcToken: { address: string; decimals: number }; + destToken: { address: string; decimals: number }; +} + +const factoryAddress = CamelotConfig[dexKey][network].factoryAddress; +const blockHeaders: Record = {}; + +const pools: Pool[] = [ + { + address: '0x19d51dc52e52407656b40b197b1bbe14294b955b', + startBlockNumber: 162099096, + endBlockNumber: 162099098, + srcToken: { + address: `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1`, + decimals: 18, + }, + destToken: { + address: '0x939727d85D99d0aC339bF1B76DfE30Ca27C19067', + decimals: 18, + }, + }, + { + address: '0x8b5c25d5f9be67dc6243e2fafcb36ec10ba54aa2', + startBlockNumber: 163124111, + endBlockNumber: 163124113, + srcToken: { + address: `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1`, + decimals: 18, + }, + destToken: { + address: '0x38CC4c71425e9aB6B0A6Ec4240121598efE08398', + decimals: 18, + }, + }, +]; + +function groupAndSortLogsByBlockNumber(logsToDispatch: any[]): { + logsByBlockNumber: Record; + sortedBlocks: number[]; +} { + const logsByBlockNumber: Record = {}; + for (let log of logsToDispatch) { + if (!logsByBlockNumber[log.blockNumber]) { + logsByBlockNumber[log.blockNumber] = []; + } + logsByBlockNumber[log.blockNumber].push(log); + } + + const sortedBlocks = Object.keys(logsByBlockNumber) + .map(Number) + .sort((a, b) => Number(a) - Number(b)); + + return { logsByBlockNumber, sortedBlocks }; +} + +async function testPoolDiscovery(pool: Pool) { + const { startBlockNumber, endBlockNumber, srcToken, destToken, address } = + pool; + describe(`${address}: from ${startBlockNumber} to ${endBlockNumber}`, () => { + const dexHelper = new DummyDexHelper(network); + const camelot = new Camelot(network, dexKey, dexHelper); + + let logsByBlockNumber: Record = {}; + let sortedBlocks: number[] = []; + + it('should have pair created event', async () => { + const logsToDispatch = await dexHelper.provider.getLogs({ + fromBlock: startBlockNumber, + toBlock: endBlockNumber, + address: factoryAddress, + }); + + expect(logsToDispatch.length).toBe(1); + + const groupedLogs = groupAndSortLogsByBlockNumber(logsToDispatch); + logsByBlockNumber = groupedLogs.logsByBlockNumber; + sortedBlocks = groupedLogs.sortedBlocks; + }); + + it('prices before pool discovery should be empty', async () => { + // override default block to force camelot.factory.call() use block before pool was created + camelot.factory.defaultBlock = startBlockNumber; + const pricesBeforePoolDiscovery = await camelot.getPricesVolume( + srcToken, + destToken, + [10000000000000000000n, 10000000000000000000n], + SwapSide.SELL, + startBlockNumber, + ); + + // prices before pool created should not be available + expect(pricesBeforePoolDiscovery).toBe(null); + }); + + it('prices after pool discovery should not be empty', async () => { + // @ts-ignore + // initialize factory state + await camelot.factoryInst?.initialize(startBlockNumber); + + for (let blockNumber of sortedBlocks) { + if (!blockHeaders[blockNumber]) { + blockHeaders[blockNumber] = await dexHelper.web3Provider.eth.getBlock( + blockNumber, + ); + } + + // override default block to force camelot.factory.call() use specific block + camelot.factory.defaultBlock = blockNumber; + + // @ts-ignore + // emit PairCreated event for factory + await camelot.factoryInst?.update(logsByBlockNumber[blockNumber], { + [blockNumber]: blockHeaders[blockNumber], + }); + } + + const pricesAfterPoolDiscovery = await camelot.getPricesVolume( + srcToken, + destToken, + [10000000000000000000n, 10000000000000000000n], + SwapSide.SELL, + endBlockNumber, + ); + + expect(pricesAfterPoolDiscovery).not.toBe(null); + }); + }); +} + +describe(`Test ${dexKey} pool discovery`, () => { + pools.forEach(pool => testPoolDiscovery(pool)); +}); diff --git a/src/dex/camelot/camelot.ts b/src/dex/camelot/camelot.ts index 4163b1297..e4b3a465b 100644 --- a/src/dex/camelot/camelot.ts +++ b/src/dex/camelot/camelot.ts @@ -44,6 +44,10 @@ import { SimpleExchange } from '../simple-exchange'; import { applyTransferFee } from '../../lib/token-transfer-fee'; import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; import { SolidlyData } from '../solidly/types'; +import { + OnPoolCreatedCallback, + UniswapV2Factory, +} from '../uniswap-v2/uniswap-v2-factory'; const DefaultCamelotPoolGasCost = 90 * 1000; @@ -171,6 +175,10 @@ export class Camelot readonly DEST_TOKEN_DEX_TRANSFERS = 1; logger: Logger; + private readonly factoryInst: UniswapV2Factory; + + private newlyCreatedPoolKeys: Set = new Set(); + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = getDexKeysWithNetwork(CamelotConfig); @@ -205,8 +213,59 @@ export class Camelot this.routerInterface = new Interface(ParaSwapABI); this.exchangeRouterInterface = new Interface(UniswapV2ExchangeRouterABI); + + this.factoryInst = new UniswapV2Factory( + dexHelper, + dexKey, + factoryAddress, + this.logger, + this.onPoolCreatedDeleteFromNonExistingSet, + ); + } + + async initializePricing(blockNumber: number) { + // Init listening to new pools creation + await this.factoryInst.initialize(blockNumber); + } + + private getPoolIdentifier(token0: string, token1: string) { + const [_token0, _token1] = + token0.toLowerCase() < token1.toLowerCase() + ? [token0, token1] + : [token1, token0]; + + const poolKey = `${this.dexKey}_${_token0}_${_token1}`.toLowerCase(); + + return poolKey; } + /* + * When a non existing pool is queried, it's blacklisted for an arbitrary long period in order to prevent issuing too many rpc calls + * Once the pool is created, it gets immediately flagged + */ + onPoolCreatedDeleteFromNonExistingSet: OnPoolCreatedCallback = async ({ + token0, + token1, + }) => { + const logPrefix = '[onPoolCreatedDeleteFromNonExistingSet]'; + + try { + const poolKey = this.getPoolIdentifier(token0, token1); + + this.newlyCreatedPoolKeys.add(poolKey); + + // delete entry locally to let local instance discover the pool + delete this.pairs[poolKey]; + + this.logger.info(`${logPrefix} discovered new pool ${poolKey}`); + } catch (e) { + this.logger.error( + `${logPrefix} LOGIC ERROR on ack new pool (token0=${token0},token1=${token1})`, + e, + ); + } + }; + getPoolStatesMultiCallData(pair: CamelotPair): { callEntries: any[]; callDecoder: (data: any[]) => { @@ -328,14 +387,17 @@ export class Camelot ? [from, to] : [to, from]; - const key = `${token0.address.toLowerCase()}-${token1.address.toLowerCase()}`; + const key = this.getPoolIdentifier(token0.address, token1.address); let pair = this.pairs[key]; if (pair) return pair; const exchange = await this.factory.methods .getPair(token0.address, token1.address) .call(); if (exchange === NULL_ADDRESS) { - pair = { token0, token1 }; + // if the pool has been newly created to not allow this op as we can run into race condition between pool discovery and concurrent pricing request touching this pool + if (!this.newlyCreatedPoolKeys.has(key)) { + pair = { token0, token1 }; + } } else { pair = { token0, token1, exchange }; } @@ -476,12 +538,9 @@ export class Camelot return []; } - const tokenAddress = [from.address.toLowerCase(), to.address.toLowerCase()] - .sort((a, b) => (a > b ? 1 : -1)) - .join('_'); + const poolKey = this.getPoolIdentifier(from.address, to.address); - const poolIdentifier = `${this.dexKey}_${tokenAddress}`; - return [poolIdentifier]; + return [poolKey]; } async getPricesVolume( @@ -507,14 +566,7 @@ export class Camelot return null; } - const tokenAddress = [ - from.address.toLowerCase(), - to.address.toLowerCase(), - ] - .sort((a, b) => (a > b ? 1 : -1)) - .join('_'); - - const poolIdentifier = `${this.dexKey}_${tokenAddress}`; + const poolIdentifier = this.getPoolIdentifier(from.address, to.address); if (limitPools && limitPools.every(p => p !== poolIdentifier)) return null; 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/curve-v1/config.ts b/src/dex/curve-v1/config.ts index 979f6ecde..45c599a36 100644 --- a/src/dex/curve-v1/config.ts +++ b/src/dex/curve-v1/config.ts @@ -939,44 +939,46 @@ export const CurveV1Config: DexConfigMap = { isMetapool: false, baseToken: '0x321162Cd933E2Be498Cd2267a90534A804051b11', }, - main_gDAI_gUSDC_gUSDT: { - underlying: [ - '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', // DAI - '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75', // USDC - '0x049d68029688eAbF473097a2fC38ef61633A3C7A', // fUSDT - ], - coins: [ - '0x07E6332dD090D287d3489245038daF987955DCFB', // gDAI - '0xe578C856933D8e1082740bf7661e379Aa2A30b26', // gUSDC - '0x940F41F0ec9ba1A34CF001cc03347ac092F5F6B5', // gUSDT - ], - address: '0x0fa949783947Bf6c1b171DB13AEACBB488845B3f', - name: 'main_gDAI_gUSDC_gUSDT', - type: 2, - version: 3, - isLending: true, - isMetapool: false, - baseToken: '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', - }, - main_iDAI_iUSDC_iFUSDT: { - underlying: [ - '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', // DAI - '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75', // USDC - '0x049d68029688eAbF473097a2fC38ef61633A3C7A', // fUSDT - ], - coins: [ - '0x04c762a5dF2Fa02FE868F25359E0C259fB811CfE', // iDAI - '0x328A7b4d538A2b3942653a9983fdA3C12c571141', // iUSDC - '0x70faC71debfD67394D1278D98A29dea79DC6E57A', // iFUSDT - ], - address: '0x4FC8D635c3cB1d0aa123859e2B2587d0FF2707b1', - name: 'main_iDAI_iUSDC_iFUSDT', - type: 2, - version: 3, - isLending: true, - isMetapool: false, - baseToken: '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', - }, + // Disabled pool + // main_gDAI_gUSDC_gUSDT: { + // underlying: [ + // '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', // DAI + // '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75', // USDC + // '0x049d68029688eAbF473097a2fC38ef61633A3C7A', // fUSDT + // ], + // coins: [ + // '0x07E6332dD090D287d3489245038daF987955DCFB', // gDAI + // '0xe578C856933D8e1082740bf7661e379Aa2A30b26', // gUSDC + // '0x940F41F0ec9ba1A34CF001cc03347ac092F5F6B5', // gUSDT + // ], + // address: '0x0fa949783947Bf6c1b171DB13AEACBB488845B3f', + // name: 'main_gDAI_gUSDC_gUSDT', + // type: 2, + // version: 3, + // isLending: true, + // isMetapool: false, + // baseToken: '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', + // }, + // Disabled pool + // main_iDAI_iUSDC_iFUSDT: { + // underlying: [ + // '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', // DAI + // '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75', // USDC + // '0x049d68029688eAbF473097a2fC38ef61633A3C7A', // fUSDT + // ], + // coins: [ + // '0x04c762a5dF2Fa02FE868F25359E0C259fB811CfE', // iDAI + // '0x328A7b4d538A2b3942653a9983fdA3C12c571141', // iUSDC + // '0x70faC71debfD67394D1278D98A29dea79DC6E57A', // iFUSDT + // ], + // address: '0x4FC8D635c3cB1d0aa123859e2B2587d0FF2707b1', + // name: 'main_iDAI_iUSDC_iFUSDT', + // type: 2, + // version: 3, + // isLending: true, + // isMetapool: false, + // baseToken: '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E', + // }, }, }, [Network.AVALANCHE]: { diff --git a/src/dex/dexalot/config.ts b/src/dex/dexalot/config.ts new file mode 100644 index 000000000..658792a20 --- /dev/null +++ b/src/dex/dexalot/config.ts @@ -0,0 +1,18 @@ +import { DexParams } from './types'; +import { DexConfigMap, AdapterMappings } from '../../types'; +import { Network, SwapSide } from '../../constants'; + +export const DexalotConfig: DexConfigMap = { + Dexalot: { + [Network.AVALANCHE]: { + mainnetRFQAddress: '0xEed3c159F3A96aB8d41c8B9cA49EE1e5071A7cdD', + }, + }, +}; + +export const Adapters: Record = { + [Network.AVALANCHE]: { + [SwapSide.SELL]: [{ name: 'AvalancheAdapter02', index: 6 }], + [SwapSide.BUY]: [{ name: 'AvalancheBuyAdapter', index: 8 }], + }, +}; diff --git a/src/dex/dexalot/constants.ts b/src/dex/dexalot/constants.ts new file mode 100644 index 000000000..4d1dc589f --- /dev/null +++ b/src/dex/dexalot/constants.ts @@ -0,0 +1,32 @@ +import BigNumber from 'bignumber.js'; + +export const DEXALOT_PRICES_CACHES_TTL_S = 3; + +export const DEXALOT_PAIRS_CACHES_TTL_S = 21 * 60; // 21 mins + +export const DEXALOT_TOKENS_CACHES_TTL_S = 21 * 60; // 21 mins + +export const DEXALOT_BLACKLIST_CACHES_TTL_S = 180 * 60; // 3 hours + +export const DEXALOT_API_PRICES_POLLING_INTERVAL_MS = 1000; + +export const DEXALOT_API_PAIRS_POLLING_INTERVAL_MS = 1000 * 60 * 10; // 10 mins + +export const DEXALOT_API_BLACKLIST_POLLING_INTERVAL_MS = 1000 * 60 * 60; // 1 hour + +export const DEXALOT_API_URL = 'https://api.dexalot.com'; + +export const DEXALOT_GAS_COST = 120_000; + +export const DEXALOT_RATE_LIMITED_TTL_S = 60 * 60; // 1 hour + +export const DEXALOT_RATELIMIT_CACHE_VALUE = 'limited'; + +export const DEXALOT_RESTRICT_TTL_S = 60 * 30; // 30 minutes + +export const DEXALOT_RESTRICTED_CACHE_KEY = 'restricted'; + +export const DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION = + new BigNumber('0.005'); + +export const DEXALOT_FIRM_QUOTE_TIMEOUT_MS = 2000; diff --git a/src/dex/dexalot/dexalot-e2e.test.ts b/src/dex/dexalot/dexalot-e2e.test.ts new file mode 100644 index 000000000..2fc23f588 --- /dev/null +++ b/src/dex/dexalot/dexalot-e2e.test.ts @@ -0,0 +1,214 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { testE2E } from '../../../tests/utils-e2e'; +import { + Tokens, + NativeTokenSymbols, + Holders, +} 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, + excludeNativeTokenTests: boolean = false, +) { + const config = generateConfig(network); + const provider = new StaticJsonRpcProvider( + config.privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + const sleepMs = 10000; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.megaSwap, + ContractMethod.multiSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + if (excludeNativeTokenTests) { + 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, + undefined, + sleepMs, + ); + }); + it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[tokenBSymbol], + tokens[tokenASymbol], + holders[tokenBSymbol], + side === SwapSide.SELL ? tokenBAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + } else { + 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, + undefined, + sleepMs, + ); + }); + 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, + undefined, + sleepMs, + ); + }); + 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, + undefined, + sleepMs, + ); + }); + it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[tokenBSymbol], + tokens[tokenASymbol], + holders[tokenBSymbol], + side === SwapSide.SELL ? tokenBAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + } + }); + }); + }), + ); + }); +} + +describe('Dexalot E2E', () => { + const dexKey = 'Dexalot'; + + describe('Avalanche', () => { + const network = Network.AVALANCHE; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '1000000'; + const tokenBAmount: string = '1000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('BTC.b -> USDC', () => { + const network = Network.AVALANCHE; + + const tokenASymbol: string = 'BTCb'; + const tokenBSymbol: string = 'USDC'; + + const tokenAAmount: string = '100'; + const tokenBAmount: string = '9000000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + true, + ); + }); +}); diff --git a/src/dex/dexalot/dexalot-integration.test.ts b/src/dex/dexalot/dexalot-integration.test.ts new file mode 100644 index 000000000..5c767a34f --- /dev/null +++ b/src/dex/dexalot/dexalot-integration.test.ts @@ -0,0 +1,188 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { Dexalot } from './dexalot'; +import { + checkPoolPrices, + checkPoolsLiquidity, + checkConstantPoolPrices, + sleep, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; + +async function testPricingOnNetwork( + dexalot: Dexalot, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], +) { + const networkTokens = Tokens[network]; + + const pools = await dexalot.getPoolIdentifiers( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await dexalot.getPricesVolume( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + if (dexalot.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey); + } +} + +describe('Dexalot', function () { + const dexKey = 'Dexalot'; + let blockNumber: number; + let dexalot: Dexalot; + + describe('Avalanche', () => { + const network = Network.AVALANCHE; + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + + const tokenASymbol = 'AVAX'; + const tokenBSymbol = 'USDC'; + + const amountsForTokenA = [ + 0n, + 1n * BI_POWS[tokens[tokenASymbol].decimals], + 2n * BI_POWS[tokens[tokenASymbol].decimals], + 3n * BI_POWS[tokens[tokenASymbol].decimals], + 4n * BI_POWS[tokens[tokenASymbol].decimals], + 5n * BI_POWS[tokens[tokenASymbol].decimals], + 6n * BI_POWS[tokens[tokenASymbol].decimals], + 7n * BI_POWS[tokens[tokenASymbol].decimals], + 8n * BI_POWS[tokens[tokenASymbol].decimals], + 9n * BI_POWS[tokens[tokenASymbol].decimals], + 10n * BI_POWS[tokens[tokenASymbol].decimals], + ]; + + const amountsForTokenB = [ + 0n, + 1n * BI_POWS[tokens[tokenBSymbol].decimals], + 2n * BI_POWS[tokens[tokenBSymbol].decimals], + 3n * BI_POWS[tokens[tokenBSymbol].decimals], + 4n * BI_POWS[tokens[tokenBSymbol].decimals], + 5n * BI_POWS[tokens[tokenBSymbol].decimals], + 6n * BI_POWS[tokens[tokenBSymbol].decimals], + 7n * BI_POWS[tokens[tokenBSymbol].decimals], + 8n * BI_POWS[tokens[tokenBSymbol].decimals], + 9n * BI_POWS[tokens[tokenBSymbol].decimals], + 10n * BI_POWS[tokens[tokenBSymbol].decimals], + ]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + dexalot = new Dexalot(network, dexKey, dexHelper); + await dexalot.initializePricing(blockNumber); + await sleep(5000); + }); + + afterEach(async () => { + dexalot.releaseResources(); + await sleep(5000); + }); + + it(`getPoolIdentifiers and getPricesVolume SELL ${tokenASymbol} ${tokenBSymbol}`, async function () { + await testPricingOnNetwork( + dexalot, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.SELL, + amountsForTokenA, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume BUY ${tokenASymbol} ${tokenBSymbol}`, async function () { + await testPricingOnNetwork( + dexalot, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.BUY, + amountsForTokenB, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume SELL ${tokenBSymbol} ${tokenASymbol}`, async function () { + await testPricingOnNetwork( + dexalot, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.SELL, + amountsForTokenB, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume BUY ${tokenBSymbol} ${tokenASymbol}`, async function () { + await testPricingOnNetwork( + dexalot, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.BUY, + amountsForTokenA, + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const dexalot = new Dexalot(network, dexKey, dexHelper); + const poolLiquidity = await dexalot.getTopPoolsForToken( + tokens[tokenASymbol].address, + 10, + ); + console.log(`${tokenASymbol} Top Pools:`, poolLiquidity); + + if (!dexalot.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][tokenASymbol].address, + dexKey, + ); + } + }); + }); +}); diff --git a/src/dex/dexalot/dexalot.ts b/src/dex/dexalot/dexalot.ts new file mode 100644 index 000000000..014831349 --- /dev/null +++ b/src/dex/dexalot/dexalot.ts @@ -0,0 +1,1053 @@ +import { + Token, + Address, + ExchangePrices, + PoolPrices, + AdapterExchangeParam, + SimpleExchangeParam, + PoolLiquidity, + Logger, + ExchangeTxInfo, + OptimalSwapExchange, + PreprocessTransactionOptions, + TransferFeeParams, +} from '../../types'; +import { + SwapSide, + Network, + ETHER_ADDRESS, + NULL_ADDRESS, +} from '../../constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { getDexKeysWithNetwork, isAxiosError } from '../../utils'; +import { IDex } from '../idex'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { + ClobSide, + DexalotData, + PairData, + PairDataMap, + PriceDataMap, + DexalotRfqError, + DexalotAPIParameters, + RFQResponse, + RFQResponseError, + TokenAddrDataMap, + TokenDataMap, +} from './types'; +import { + SlippageCheckError, + TooStrictSlippageCheckError, +} from '../generic-rfq/types'; +import { SimpleExchange } from '../simple-exchange'; +import { Adapters, DexalotConfig } from './config'; +import { RateFetcher } from './rate-fetcher'; +import mainnetRFQAbi from '../../abi/dexalot/DexalotMainnetRFQ.json'; +import { Interface } from 'ethers/lib/utils'; +import { assert } from 'ts-essentials'; +import { + DEXALOT_API_URL, + DEXALOT_API_PRICES_POLLING_INTERVAL_MS, + DEXALOT_PRICES_CACHES_TTL_S, + DEXALOT_GAS_COST, + DEXALOT_PAIRS_CACHES_TTL_S, + DEXALOT_API_PAIRS_POLLING_INTERVAL_MS, + DEXALOT_TOKENS_CACHES_TTL_S, + DEXALOT_API_BLACKLIST_POLLING_INTERVAL_MS, + DEXALOT_RATE_LIMITED_TTL_S, + DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION, + DEXALOT_RESTRICTED_CACHE_KEY, + DEXALOT_RESTRICT_TTL_S, + DEXALOT_RATELIMIT_CACHE_VALUE, + DEXALOT_BLACKLIST_CACHES_TTL_S, + DEXALOT_FIRM_QUOTE_TIMEOUT_MS, +} from './constants'; +import { BI_MAX_UINT256 } from '../../bigint-constants'; +import { ethers } from 'ethers'; +import BigNumber from 'bignumber.js'; +import { Method } from '../../dex-helper/irequest-wrapper'; + +export class Dexalot extends SimpleExchange implements IDex { + readonly isStatePollingDex = true; + readonly hasConstantPriceLargeAmounts = false; + readonly needWrapNative = false; + readonly isFeeOnTransferSupported = false; + private rateFetcher: RateFetcher; + + private dexalotAuthToken: string; + + private pricesCacheKey: string; + private pairsCacheKey: string; + private tokensAddrCacheKey: string; + private tokensCacheKey: string; + private blacklistCacheKey: string; + private tokensMap: TokenDataMap = {}; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(DexalotConfig); + + logger: Logger; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + protected adapters = Adapters[network] || {}, + readonly mainnetRFQAddress: string = DexalotConfig['Dexalot'][network] + .mainnetRFQAddress, + protected rfqInterface = new Interface(mainnetRFQAbi), + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey); + + const authToken = dexHelper.config.data.dexalotAuthToken; + assert( + authToken !== undefined, + 'Dexalot auth token is not specified with env variable', + ); + this.dexalotAuthToken = authToken; + + this.pricesCacheKey = 'prices'; + this.pairsCacheKey = 'pairs'; + this.tokensAddrCacheKey = 'tokens_addr'; + this.tokensCacheKey = 'tokens'; + this.blacklistCacheKey = 'blacklist'; + + this.rateFetcher = new RateFetcher( + this.dexHelper, + this.dexKey, + this.network, + this.logger, + { + rateConfig: { + pairsIntervalMs: DEXALOT_API_PAIRS_POLLING_INTERVAL_MS, + pricesIntervalMs: DEXALOT_API_PRICES_POLLING_INTERVAL_MS, + blacklistIntervalMs: DEXALOT_API_BLACKLIST_POLLING_INTERVAL_MS, + pairsReqParams: this.getAPIReqParams('api/rfq/pairs', 'GET'), + pricesReqParams: this.getAPIReqParams('api/rfq/prices', 'GET'), + blacklistReqParams: this.getAPIReqParams('api/rfq/blacklist', 'GET'), + pairsCacheKey: this.pairsCacheKey, + pairsCacheTTLSecs: DEXALOT_PAIRS_CACHES_TTL_S, + pricesCacheKey: this.pricesCacheKey, + pricesCacheTTLSecs: DEXALOT_PRICES_CACHES_TTL_S, + tokensAddrCacheKey: this.tokensAddrCacheKey, + tokensCacheKey: this.tokensCacheKey, + tokensCacheTTLSecs: DEXALOT_TOKENS_CACHES_TTL_S, + blacklistCacheKey: this.blacklistCacheKey, + blacklistCacheTTLSecs: DEXALOT_BLACKLIST_CACHES_TTL_S, + }, + }, + ); + } + + async initializePricing(blockNumber: number): Promise { + if (!this.dexHelper.config.isSlave) { + this.rateFetcher.start(); + } + + return; + } + + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return this.adapters[side] ? this.adapters[side] : null; + } + + getPairString(baseToken: Token, quoteToken: Token): string { + return `${baseToken.symbol}/${quoteToken.symbol}`.toLowerCase(); + } + + async getPairData( + srcToken: Token, + destToken: Token, + ): Promise { + const normalizedSrcToken = this.normalizeToken(srcToken); + const normalizedDestToken = this.normalizeToken(destToken); + if (normalizedSrcToken.address === normalizedDestToken.address) { + return null; + } + + const cachedTokens = (await this.getCachedTokens()) || {}; + if ( + !(normalizedSrcToken.address in cachedTokens) || + !(normalizedDestToken.address in cachedTokens) + ) { + return null; + } + normalizedSrcToken.symbol = cachedTokens[normalizedSrcToken.address].symbol; + normalizedDestToken.symbol = + cachedTokens[normalizedDestToken.address].symbol; + + const cachedPairs = (await this.getCachedPairs()) || {}; + + const potentialPairs = [ + { + identifier: this.getPairString(normalizedSrcToken, normalizedDestToken), + isSrcBase: true, + }, + { + identifier: this.getPairString(normalizedDestToken, normalizedSrcToken), + isSrcBase: false, + }, + ]; + + for (const pair of potentialPairs) { + if (pair.identifier in cachedPairs) { + const pairData = cachedPairs[pair.identifier]; + pairData.isSrcBase = pair.isSrcBase; + return pairData; + } + } + return null; + } + + getIdentifier(srcAddress: Address, destAddress: Address) { + const sortedAddresses = + srcAddress < destAddress + ? [srcAddress, destAddress] + : [destAddress, srcAddress]; + + return `${ + this.dexKey + }_${sortedAddresses[0].toLowerCase()}_${sortedAddresses[1].toLowerCase()}`; + } + + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + if (!srcToken || !destToken) { + return []; + } + const pairData = await this.getPairData(srcToken, destToken); + if (!pairData) { + return []; + } + + const tokensAddr = (await this.getCachedTokensAddr()) || {}; + + return [ + this.getIdentifier( + tokensAddr[pairData.base.toLowerCase()], + tokensAddr[pairData.quote.toLowerCase()], + ), + ]; + } + + async getCachedPairs(): Promise { + const cachedPairs = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.pairsCacheKey, + ); + + if (cachedPairs) { + return JSON.parse(cachedPairs) as PairDataMap; + } + + return null; + } + + async getCachedPrices(): Promise { + const cachedPrices = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.pricesCacheKey, + ); + + if (cachedPrices) { + return JSON.parse(cachedPrices) as PriceDataMap; + } + + return null; + } + + async getCachedTokensAddr(): Promise { + const cachedTokensAddr = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.tokensAddrCacheKey, + ); + + if (cachedTokensAddr) { + return JSON.parse(cachedTokensAddr) as TokenAddrDataMap; + } + + return null; + } + + async getCachedTokens(): Promise { + const cachedTokens = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.tokensCacheKey, + ); + + if (cachedTokens) { + return JSON.parse(cachedTokens) as TokenDataMap; + } + + return null; + } + + normalizeAddress(address: string): string { + return address.toLowerCase() === ETHER_ADDRESS + ? NULL_ADDRESS + : address.toLowerCase(); + } + + denormalizeAddress(address: string): string { + return address.toLowerCase() === NULL_ADDRESS + ? ETHER_ADDRESS + : address.toLowerCase(); + } + + // Dexalot protocol for native token expects 0x00000... instead of 0xeeeee... + normalizeToken(token: Token): Token { + return { + address: this.normalizeAddress(token.address), + decimals: token.decimals, + }; + } + + denormalizeToken(token: Token): Token { + return { + address: this.denormalizeAddress(token.address), + decimals: token.decimals, + }; + } + + calculateOrderPrice( + amounts: bigint[], + orderbook: string[][], + baseToken: Token, + quoteToken: Token, + isInputQuote: boolean, + ) { + let result = []; + + for (let i = 0; i < amounts.length; i++) { + let amt = amounts[i]; + if (amt === 0n) { + result.push(amt); + continue; + } + + let left = 0, + right = orderbook.length; + let qty = BigInt(0); + + while (left < right) { + let mid = Math.floor((left + right) / 2); + qty = BigInt( + ethers.utils + .parseUnits(orderbook[mid][1], quoteToken.decimals) + .toString(), + ); + if (isInputQuote) { + const price = BigInt( + ethers.utils + .parseUnits(orderbook[mid][0], baseToken.decimals) + .toString(), + ); + qty = + (qty * BigInt(10 ** (baseToken.decimals * 2))) / + (price * BigInt(10 ** quoteToken.decimals)); + } + if (qty <= amt) { + left = mid + 1; + } else { + right = mid; + } + } + + let price = BigInt(0), + amount = BigInt(0); + if (amounts[i] === qty) { + price = BigInt( + ethers.utils + .parseUnits(orderbook[left][0], baseToken.decimals) + .toString(), + ); + amount = amounts[i]; + } else if (left === 0) { + price = 0n; + } else if (left < orderbook.length) { + const lPrice = BigInt( + ethers.utils + .parseUnits(orderbook[left - 1][0], baseToken.decimals) + .toString(), + ); + const rPrice = BigInt( + ethers.utils + .parseUnits(orderbook[left][0], baseToken.decimals) + .toString(), + ); + let lQty = BigInt( + ethers.utils + .parseUnits(orderbook[left - 1][1], quoteToken.decimals) + .toString(), + ); + let rQty = BigInt( + ethers.utils + .parseUnits(orderbook[left][1], quoteToken.decimals) + .toString(), + ); + if (isInputQuote) { + lQty = + (lQty * BigInt(10 ** (baseToken.decimals * 2))) / + (lPrice * BigInt(10 ** quoteToken.decimals)); + rQty = + (rQty * BigInt(10 ** (baseToken.decimals * 2))) / + (rPrice * BigInt(10 ** quoteToken.decimals)); + } + price = lPrice + ((rPrice - lPrice) * (amt - lQty)) / (rQty - lQty); + amount = amounts[i]; + } + + if (isInputQuote) { + result.push( + (price * amount * BigInt(10 ** quoteToken.decimals)) / + BigInt(10 ** (baseToken.decimals * 2)), + ); + } else { + result.push( + price !== 0n // To avoid division by zero error + ? (amount * BigInt(10 ** (baseToken.decimals * 2))) / + (price * BigInt(10 ** quoteToken.decimals)) + : 0n, + ); + } + } + return result; + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + transferFees?: TransferFeeParams, + ): Promise> { + try { + const normalizedSrcToken = this.normalizeToken(srcToken); + const normalizedDestToken = this.normalizeToken(destToken); + + this.tokensMap = (await this.getCachedTokens()) || {}; + if (normalizedSrcToken.address === normalizedDestToken.address) { + return null; + } + + let pools = limitPools + ? limitPools.filter( + p => + p === + this.getIdentifier( + normalizedSrcToken.address, + normalizedDestToken.address, + ), + ) + : await this.getPoolIdentifiers(srcToken, destToken, side, blockNumber); + + pools = await Promise.all( + pools.map(async p => !(await this.isRestrictedPool(p))), + ).then(res => pools.filter((_v, i) => res[i])); + if (pools.length === 0) { + return null; + } + + const pairData = await this.getPairData( + normalizedSrcToken, + normalizedDestToken, + ); + if (!pairData) { + return null; + } + + const priceMap = await this.getCachedPrices(); + if (!priceMap) { + return null; + } + + const tokensAddr = (await this.getCachedTokensAddr()) || {}; + const pairKey = `${pairData.base}/${pairData.quote}`.toLowerCase(); + if ( + !(pairKey in priceMap) || + !pools.includes( + this.getIdentifier( + tokensAddr[pairData.base.toLowerCase()], + tokensAddr[pairData.quote.toLowerCase()], + ), + ) + ) { + return null; + } + + const priceData = priceMap[pairKey]; + const baseToken = pairData.isSrcBase + ? normalizedSrcToken + : normalizedDestToken; + const quoteToken = pairData.isSrcBase + ? normalizedDestToken + : normalizedSrcToken; + + // convert from swap to clob side + let orderbook = priceData.asks; + let clobSide = ClobSide.BID; + if (pairData.isSrcBase) { + orderbook = priceData.bids; + clobSide = ClobSide.ASK; + } + if (orderbook.length === 0) { + throw new Error(`Empty orderbook for ${pairKey}`); + } + + const isInputQuote = + (clobSide === ClobSide.ASK && side === SwapSide.SELL) || + (clobSide === ClobSide.BID && side === SwapSide.BUY); + + const prices = this.calculateOrderPrice( + amounts, + orderbook, + baseToken, + quoteToken, + isInputQuote, + ); + const outDecimals = + clobSide === ClobSide.BID ? baseToken.decimals : quoteToken.decimals; + const poolIdentifier = this.getIdentifier(pairData.base, pairData.quote); + + return [ + { + prices, + unit: BigInt(outDecimals), + data: {}, + poolIdentifier: poolIdentifier, + exchange: this.dexKey, + gasCost: DEXALOT_GAS_COST, + poolAddresses: [this.mainnetRFQAddress], + }, + ]; + } catch (e: unknown) { + this.logger.error( + `Error_getPricesVolume ${srcToken.address || srcToken.symbol}, ${ + destToken.address || destToken.symbol + }, ${side}:`, + e, + ); + return null; + } + } + + generateRFQError(errorStr: string, swapIdentifier: string) { + const message = `${this.dexKey}-${this.network}: Failed to fetch RFQ for ${swapIdentifier}. ${errorStr}`; + this.logger.warn(message); + throw new DexalotRfqError(message); + } + + async preProcessTransaction( + optimalSwapExchange: OptimalSwapExchange, + srcToken: Token, + destToken: Token, + side: SwapSide, + options: PreprocessTransactionOptions, + ): Promise<[OptimalSwapExchange, ExchangeTxInfo]> { + if (await this.isBlacklisted(options.txOrigin)) { + this.logger.warn( + `${this.dexKey}-${this.network}: blacklisted TX Origin address '${options.txOrigin}' trying to build a transaction. Bailing...`, + ); + throw new Error( + `${this.dexKey}-${ + this.network + }: user=${options.txOrigin.toLowerCase()} is blacklisted`, + ); + } + + if (BigInt(optimalSwapExchange.srcAmount) === 0n) { + throw new Error('getFirmRate failed with srcAmount === 0'); + } + + const normalizedSrcToken = this.normalizeToken(srcToken); + const normalizedDestToken = this.normalizeToken(destToken); + const swapIdentifier = `${this.getIdentifier( + normalizedSrcToken.address, + normalizedDestToken.address, + )}_${side}`; + + try { + const makerToken = normalizedDestToken; + const takerToken = normalizedSrcToken; + + const isSell = side === SwapSide.SELL; + const isBuy = side === SwapSide.BUY; + + const slippageBps = isSell + ? BigNumber(1) + .minus(options.slippageFactor) + .multipliedBy(10000) + .toFixed(0) + : options.slippageFactor.minus(1).multipliedBy(10000).toFixed(0); + const rfqParams = { + makerAsset: ethers.utils.getAddress(makerToken.address), + takerAsset: ethers.utils.getAddress(takerToken.address), + makerAmount: isBuy ? optimalSwapExchange.destAmount : undefined, + takerAmount: isSell ? optimalSwapExchange.srcAmount : undefined, + userAddress: options.txOrigin, + chainid: this.network, + executor: this.augustusAddress, + partner: options.partner, + slippage: slippageBps, + }; + + const rfq: RFQResponse = await this.dexHelper.httpRequest.post( + `${DEXALOT_API_URL}/api/rfq/firm`, + rfqParams, + DEXALOT_FIRM_QUOTE_TIMEOUT_MS, + { 'x-apikey': this.dexalotAuthToken }, + ); + if (!rfq) { + this.generateRFQError( + 'Missing quote data', + `RFQ ${swapIdentifier} ${JSON.stringify(rfq)}`, + ); + } else if (!rfq.signature) { + this.generateRFQError('Missing signature', swapIdentifier); + } + rfq.order.signature = rfq.signature; + + const { order } = rfq; + + assert( + order.makerAsset.toLowerCase() === makerToken.address, + `QuoteData makerAsset=${order.makerAsset} is different from Paraswap makerAsset=${makerToken.address}`, + ); + assert( + order.takerAsset.toLowerCase() === takerToken.address, + `QuoteData takerAsset=${order.takerAsset} is different from Paraswap takerAsset=${takerToken.address}`, + ); + if (isSell) { + assert( + order.takerAmount === optimalSwapExchange.srcAmount, + `QuoteData takerAmount=${order.takerAmount} is different from Paraswap srcAmount=${optimalSwapExchange.srcAmount}`, + ); + } else { + assert( + order.makerAmount === optimalSwapExchange.destAmount, + `QuoteData makerAmount=${order.makerAmount} is different from Paraswap destAmount=${optimalSwapExchange.destAmount}`, + ); + } + + const expiryAsBigInt = BigInt(order.expiry); + const minDeadline = expiryAsBigInt > 0 ? expiryAsBigInt : BI_MAX_UINT256; + + const slippageFactor = options.slippageFactor; + let isFailOnSlippage = false; + let slippageErrorMessage = ''; + + if (isSell) { + if ( + BigInt(order.makerAmount) < + BigInt( + new BigNumber(optimalSwapExchange.destAmount.toString()) + .times(slippageFactor) + .toFixed(0), + ) + ) { + isFailOnSlippage = true; + const message = `${this.dexKey}-${this.network}: too much slippage on quote ${side} quoteTokenAmount ${order.makerAmount} / destAmount ${optimalSwapExchange.destAmount} < ${slippageFactor}`; + slippageErrorMessage = message; + this.logger.warn(message); + } + } else { + if ( + BigInt(order.takerAmount) > + BigInt( + slippageFactor + .times(optimalSwapExchange.srcAmount.toString()) + .toFixed(0), + ) + ) { + isFailOnSlippage = true; + const message = `${this.dexKey}-${ + this.network + }: too much slippage on quote ${side} baseTokenAmount ${ + order.takerAmount + } / srcAmount ${ + optimalSwapExchange.srcAmount + } > ${slippageFactor.toFixed()}`; + slippageErrorMessage = message; + this.logger.warn(message); + } + } + + let isTooStrictSlippage = false; + if ( + isFailOnSlippage && + isSell && + new BigNumber(1) + .minus(slippageFactor) + .lt(DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION) + ) { + isTooStrictSlippage = true; + } else if ( + isFailOnSlippage && + isBuy && + slippageFactor + .minus(1) + .lt(DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION) + ) { + isTooStrictSlippage = true; + } + + if (isFailOnSlippage && isTooStrictSlippage) { + throw new TooStrictSlippageCheckError(slippageErrorMessage); + } else if (isFailOnSlippage && !isTooStrictSlippage) { + throw new SlippageCheckError(slippageErrorMessage); + } + + return [ + { + ...optimalSwapExchange, + data: { + quoteData: order, + }, + }, + { deadline: minDeadline }, + ]; + } catch (e) { + if (isAxiosError(e) && e.response && e.response.data) { + const errorData: RFQResponseError = e.response.data; + if (errorData.ReasonCode === 'FQ-009') { + this.logger.warn( + `${this.dexKey}-${this.network}: Encountered rate limited user=${options.txOrigin}. Adding to local rate limit cache`, + ); + await this.setRateLimited(options.txOrigin, errorData.RetryAfter); + } else { + await this.setBlacklist(options.txOrigin); + this.logger.error( + `${this.dexKey}-${this.network}: Failed to fetch RFQ for ${swapIdentifier}: ${errorData.Reason}`, + ); + } + } else { + if (e instanceof TooStrictSlippageCheckError) { + this.logger.warn( + `${this.dexKey}-${this.network}: failed to build transaction on side ${side} with too strict slippage. Skipping restriction`, + ); + } else { + const poolIdentifiers = await this.getPoolIdentifiers( + srcToken, + destToken, + side, + 0, + ); + this.logger.warn( + `${this.dexKey}-${this.network}: protocol is restricted for pools ${poolIdentifiers} due to swap: ${swapIdentifier}`, + ); + await Promise.all( + poolIdentifiers.map(async p => await this.restrictPool(p)), + ); + } + } + + throw e; + } + } + + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + return ( + CALLDATA_GAS_COST.DEX_OVERHEAD + + // addresses: makerAsset, takerAsset, maker, taker + CALLDATA_GAS_COST.ADDRESS * 4 + + // uint256: expiry + CALLDATA_GAS_COST.wordNonZeroBytes(16) + + // uint256: nonceAndMeta, makerAmount, takerAmount + CALLDATA_GAS_COST.AMOUNT * 3 + + // bytes: _signature (65 bytes) + CALLDATA_GAS_COST.FULL_WORD * 2 + + CALLDATA_GAS_COST.OFFSET_SMALL + ); + } + + getTokenFromAddress(address: Address): Token { + return this.tokensMap[this.normalizeAddress(address)]; + } + + getAdapterParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: DexalotData, + side: SwapSide, + ): AdapterExchangeParam { + const { quoteData } = data; + + assert( + quoteData !== undefined, + `${this.dexKey}-${this.network}: quoteData undefined`, + ); + + const params = [ + { + nonceAndMeta: quoteData.nonceAndMeta, + expiry: quoteData.expiry, + makerAsset: quoteData.makerAsset, + takerAsset: quoteData.takerAsset, + maker: quoteData.maker, + taker: quoteData.taker, + makerAmount: quoteData.makerAmount, + takerAmount: quoteData.takerAmount, + }, + quoteData.signature, + ]; + + const payload = this.abiCoder.encodeParameter( + { + ParentStruct: { + order: { + nonceAndMeta: 'uint256', + expiry: 'uint128', + makerAsset: 'address', + takerAsset: 'address', + maker: 'address', + taker: 'address', + makerAmount: 'uint256', + takerAmount: 'uint256', + }, + signature: 'bytes', + }, + }, + { + order: params[0], + signature: params[1], + }, + ); + + return { + targetExchange: this.mainnetRFQAddress, + payload, + networkFee: '0', + }; + } + + getRestrictedPoolKey(poolIdentifier: string): string { + return `${DEXALOT_RESTRICTED_CACHE_KEY}-${poolIdentifier}`; + } + + async restrictPool( + poolIdentifier: string, + ttl: number = DEXALOT_RESTRICT_TTL_S, + ): Promise { + await this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.getRestrictedPoolKey(poolIdentifier), + ttl, + 'true', + ); + return true; + } + + async isRestrictedPool(poolIdentifier: string): Promise { + const result = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.getRestrictedPoolKey(poolIdentifier), + ); + + return result === 'true'; + } + + async setBlacklist( + txOrigin: Address, + ttl: number = DEXALOT_BLACKLIST_CACHES_TTL_S, + ): Promise { + const cachedBlacklist = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.blacklistCacheKey, + ); + + let blacklist: string[] = []; + if (cachedBlacklist) { + blacklist = JSON.parse(cachedBlacklist); + } + + blacklist.push(txOrigin.toLowerCase()); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.blacklistCacheKey, + ttl, + JSON.stringify(blacklist), + ); + + return true; + } + + async isBlacklisted(txOrigin: Address): Promise { + const cachedBlacklist = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.blacklistCacheKey, + ); + + if (cachedBlacklist) { + const blacklist = JSON.parse(cachedBlacklist) as string[]; + return blacklist.includes(txOrigin.toLowerCase()); + } + + // To not show pricing for rate limited users + if (await this.isRateLimited(txOrigin)) { + return true; + } + + return false; + } + + getRateLimitedKey(address: Address) { + return `rate_limited_${address}`.toLowerCase(); + } + + async isRateLimited(txOrigin: Address): Promise { + const result = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.getRateLimitedKey(txOrigin), + ); + return result === DEXALOT_RATELIMIT_CACHE_VALUE; + } + + async setRateLimited(txOrigin: Address, ttl = DEXALOT_RATE_LIMITED_TTL_S) { + await this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.getRateLimitedKey(txOrigin), + ttl, + DEXALOT_RATELIMIT_CACHE_VALUE, + ); + return true; + } + + async getSimpleParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: DexalotData, + side: SwapSide, + ): Promise { + const { quoteData } = data; + + assert( + quoteData !== undefined, + `${this.dexKey}-${this.network}: quoteData undefined`, + ); + + const swapFunction = 'simpleSwap'; + const swapFunctionParams = [ + [ + quoteData.nonceAndMeta, + quoteData.expiry, + quoteData.makerAsset, + quoteData.takerAsset, + quoteData.maker, + quoteData.taker, + quoteData.makerAmount, + quoteData.takerAmount, + ], + quoteData.signature, + ]; + + const swapData = this.rfqInterface.encodeFunctionData( + swapFunction, + swapFunctionParams, + ); + + return this.buildSimpleParamWithoutWETHConversion( + srcToken, + srcAmount, + destToken, + destAmount, + swapData, + this.mainnetRFQAddress, + ); + } + + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + const normalizedTokenAddress = this.normalizeAddress(tokenAddress); + const pairs = (await this.getCachedPairs()) || {}; + this.tokensMap = (await this.getCachedTokens()) || {}; + const tokensAddr = (await this.getCachedTokensAddr()) || {}; + const token = this.getTokenFromAddress(normalizedTokenAddress); + if (!token) { + return []; + } + + const tokenSymbol = token.symbol?.toLowerCase() || ''; + + let pairsByLiquidity = []; + for (const pairName of Object.keys(pairs)) { + if (!pairName.includes(tokenSymbol)) { + continue; + } + + const tokensInPair = pairName.split('/'); + if (tokensInPair.length !== 2) { + continue; + } + + const [baseToken, quoteToken] = tokensInPair; + const addr = tokensAddr[baseToken.toLowerCase()]; + let outputToken = this.getTokenFromAddress(addr); + if (baseToken === tokenSymbol) { + const addr = tokensAddr[quoteToken.toLowerCase()]; + outputToken = this.getTokenFromAddress(addr); + } + + const denormalizedToken = this.denormalizeToken(outputToken); + + pairsByLiquidity.push({ + exchange: this.dexKey, + address: this.mainnetRFQAddress, + connectorTokens: [ + { + address: denormalizedToken.address, + decimals: denormalizedToken.decimals, + }, + ], + liquidityUSD: pairs[pairName].liquidityUSD, + }); + } + + pairsByLiquidity.sort( + (a: PoolLiquidity, b: PoolLiquidity) => b.liquidityUSD - a.liquidityUSD, + ); + + return pairsByLiquidity.slice(0, limit); + } + + getAPIReqParams(endpoint: string, method: Method): DexalotAPIParameters { + return { + url: `${DEXALOT_API_URL}/${endpoint}`, + headers: { 'x-apikey': this.dexalotAuthToken }, + params: { + chainid: this.network, + }, + method: method, + }; + } + + releaseResources(): void { + if (this.rateFetcher) { + this.rateFetcher.stop(); + } + } +} diff --git a/src/dex/dexalot/rate-fetcher.ts b/src/dex/dexalot/rate-fetcher.ts new file mode 100644 index 000000000..558ee598b --- /dev/null +++ b/src/dex/dexalot/rate-fetcher.ts @@ -0,0 +1,197 @@ +import { IDexHelper } from '../../dex-helper'; +import { Fetcher } from '../../lib/fetcher/fetcher'; +import { validateAndCast } from '../../lib/validators'; +import { Logger, Token } from '../../types'; +import { + DexalotRateFetcherConfig, + DexalotPairsResponse, + PairDataMap, + DexalotPricesResponse, + PriceDataMap, + DexalotBlacklistResponse, +} from './types'; +import { + pricesResponseValidator, + pairsResponseValidator, + blacklistResponseValidator, +} from './validators'; +import { Network } from '../../constants'; + +export class RateFetcher { + private pairsFetcher: Fetcher; + private pairsCacheKey: string; + private pairsCacheTTL: number; + + private rateFetcher: Fetcher; + private pricesCacheKey: string; + private pricesCacheTTL: number; + + private tokensAddrCacheKey: string; + private tokensCacheKey: string; + private tokensCacheTTL: number; + + private blacklistFetcher: Fetcher; + private blacklistCacheKey: string; + private blacklistCacheTTL: number; + + constructor( + private dexHelper: IDexHelper, + private dexKey: string, + private network: Network, + private logger: Logger, + config: DexalotRateFetcherConfig, + ) { + this.pairsCacheKey = config.rateConfig.pairsCacheKey; + this.pairsCacheTTL = config.rateConfig.pairsCacheTTLSecs; + this.pricesCacheKey = config.rateConfig.pricesCacheKey; + this.pricesCacheTTL = config.rateConfig.pricesCacheTTLSecs; + this.tokensAddrCacheKey = config.rateConfig.tokensAddrCacheKey; + this.tokensCacheKey = config.rateConfig.tokensCacheKey; + this.tokensCacheTTL = config.rateConfig.tokensCacheTTLSecs; + this.blacklistCacheKey = config.rateConfig.blacklistCacheKey; + this.blacklistCacheTTL = config.rateConfig.blacklistCacheTTLSecs; + + this.pairsFetcher = new Fetcher( + dexHelper.httpRequest, + { + info: { + requestOptions: config.rateConfig.pairsReqParams, + caster: (data: unknown) => { + return validateAndCast( + data, + pairsResponseValidator, + ); + }, + }, + handler: this.handlePairsResponse.bind(this), + }, + config.rateConfig.pairsIntervalMs, + logger, + ); + + this.rateFetcher = new Fetcher( + dexHelper.httpRequest, + { + info: { + requestOptions: config.rateConfig.pricesReqParams, + caster: (data: unknown) => { + return validateAndCast( + data, + pricesResponseValidator, + ); + }, + }, + handler: this.handleRatesResponse.bind(this), + }, + config.rateConfig.pricesIntervalMs, + logger, + ); + + this.blacklistFetcher = new Fetcher( + dexHelper.httpRequest, + { + info: { + requestOptions: config.rateConfig.blacklistReqParams, + caster: (data: unknown) => { + return validateAndCast( + data, + blacklistResponseValidator, + ); + }, + }, + handler: this.handleBlacklistResponse.bind(this), + }, + config.rateConfig.blacklistIntervalMs, + logger, + ); + } + + start() { + this.pairsFetcher.startPolling(); + this.rateFetcher.startPolling(); + this.blacklistFetcher.startPolling(); + } + + stop() { + this.pairsFetcher.stopPolling(); + this.rateFetcher.stopPolling(); + this.blacklistFetcher.stopPolling(); + } + + private handlePairsResponse(resp: DexalotPairsResponse): void { + const pairs = resp; + const dexPairs: PairDataMap = {}; + const tokenMap: { [address: string]: Token } = {}; + const tokenAddrMap: { [symbol: string]: string } = {}; + Object.keys(pairs).forEach(pair => { + dexPairs[pair.toLowerCase()] = pairs[pair]; + tokenAddrMap[pairs[pair].base.toLowerCase()] = + pairs[pair].baseAddress.toLowerCase(); + tokenAddrMap[pairs[pair].quote.toLowerCase()] = + pairs[pair].quoteAddress.toLowerCase(); + tokenMap[pairs[pair].baseAddress.toLowerCase()] = { + address: pairs[pair].baseAddress.toLowerCase(), + symbol: pairs[pair].base, + decimals: pairs[pair].baseDecimals, + }; + tokenMap[pairs[pair].quoteAddress.toLowerCase()] = { + address: pairs[pair].quoteAddress.toLowerCase(), + symbol: pairs[pair].quote, + decimals: pairs[pair].quoteDecimals, + }; + }); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.pairsCacheKey, + this.pairsCacheTTL, + JSON.stringify(dexPairs), + ); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.tokensCacheKey, + this.tokensCacheTTL, + JSON.stringify(tokenMap), + ); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.tokensAddrCacheKey, + this.tokensCacheTTL, + JSON.stringify(tokenAddrMap), + ); + } + + private handleRatesResponse(resp: DexalotPricesResponse): void { + const { prices } = resp; + const dexPrices: PriceDataMap = {}; + Object.keys(prices).forEach(pair => { + dexPrices[pair.toLowerCase()] = prices[pair]; + }); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.pricesCacheKey, + this.pricesCacheTTL, + JSON.stringify(dexPrices), + ); + } + + private async handleBlacklistResponse( + resp: DexalotBlacklistResponse, + ): Promise { + const { blacklist } = resp; + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.blacklistCacheKey, + this.blacklistCacheTTL, + JSON.stringify(blacklist.map(item => item.toLowerCase())), + ); + } +} diff --git a/src/dex/dexalot/types.ts b/src/dex/dexalot/types.ts new file mode 100644 index 000000000..41bc53257 --- /dev/null +++ b/src/dex/dexalot/types.ts @@ -0,0 +1,131 @@ +import { RequestHeaders } from '../../dex-helper'; +import { Token } from '../../types'; +import { Method } from '../../dex-helper/irequest-wrapper'; + +type RFQOrder = { + nonceAndMeta: string; + expiry: number; + makerAsset: string; + takerAsset: string; + maker: string; + taker: string; + makerAmount: string; + takerAmount: string; + signature?: string; +}; + +export type RFQResponse = { + order: RFQOrder; + signature: string; +}; + +export type RFQResponseError = { + Reason: string; + ReasonCode: string; + Success: boolean; + RetryAfter?: number; +}; + +export type DexalotData = { + quoteData?: RFQOrder; +}; + +export type DexParams = { + mainnetRFQAddress: string; +}; + +export enum ClobSide { + BID = 'BID', + ASK = 'ASK', +} + +export class DexalotRfqError extends Error {} + +export type PairData = { + base: string; + quote: string; + liquidityUSD: number; + isSrcBase?: boolean; +}; + +export type PairDataMap = { + [pair: string]: PairData; +}; + +export type PairDataResp = { + base: string; + quote: string; + liquidityUSD: number; + baseAddress: string; + quoteAddress: string; + baseDecimals: number; + quoteDecimals: number; +}; + +export type DexalotPairsResponse = { + [pair: string]: PairDataResp; +}; + +type PriceData = { + bids: string[][]; + asks: string[][]; +}; + +export type PriceDataMap = { + [pair: string]: PriceData; +}; + +export type DexalotPricesResponse = { + prices: PriceDataMap; +}; + +export type TokenAddrDataMap = { + [symbol: string]: string; +}; + +export type TokenDataMap = { + [address: string]: Token; +}; + +export type DexalotBlacklistResponse = { + blacklist: string[]; +}; + +export type DexalotRateFetcherConfig = { + rateConfig: { + pairsReqParams: { + url: string; + headers?: RequestHeaders; + params?: any; + }; + pricesReqParams: { + url: string; + headers?: RequestHeaders; + params?: any; + }; + blacklistReqParams: { + url: string; + headers?: RequestHeaders; + params?: any; + }; + pairsIntervalMs: number; + pricesIntervalMs: number; + blacklistIntervalMs: number; + pairsCacheKey: string; + pricesCacheKey: string; + tokensAddrCacheKey: string; + tokensCacheKey: string; + blacklistCacheKey: string; + blacklistCacheTTLSecs: number; + pairsCacheTTLSecs: number; + pricesCacheTTLSecs: number; + tokensCacheTTLSecs: number; + }; +}; + +export type DexalotAPIParameters = { + url: string; + headers?: RequestHeaders; + params?: any; + method?: Method; +}; diff --git a/src/dex/dexalot/validators.ts b/src/dex/dexalot/validators.ts new file mode 100644 index 000000000..89a2e11e1 --- /dev/null +++ b/src/dex/dexalot/validators.ts @@ -0,0 +1,46 @@ +import joi from 'joi'; + +const pairValidator = joi.object({ + base: joi.string().min(1).required(), + quote: joi.string().min(1).required(), + liquidityUSD: joi.number().min(0).required(), + baseAddress: joi.string().min(1).required(), + quoteAddress: joi.string().min(1).required(), + baseDecimals: joi.number().min(0).required(), + quoteDecimals: joi.number().min(0).required(), +}); + +export const pairsResponseValidator = joi + .object() + .pattern(joi.string(), pairValidator); + +const orderbookRecordValidator = joi + .array() + .items(joi.string().min(1)) + .length(2); + +const orderbookValidator = joi.object({ + bids: joi.array().items(orderbookRecordValidator).required(), + asks: joi.array().items(orderbookRecordValidator).required(), +}); + +export const pricesResponseValidator = joi.object({ + prices: joi.object().pattern(joi.string(), orderbookValidator), +}); + +const tokenValidator = joi.object({ + symbol: joi.string().min(1).required(), + name: joi.string().min(1).required(), + description: joi.string().min(1).required(), + address: joi.string().min(1).required(), + decimals: joi.number().min(0).required(), + type: joi.string().min(1).required(), +}); + +export const tokensResponseValidator = joi.object({ + tokens: joi.object().pattern(joi.string(), tokenValidator), +}); + +export const blacklistResponseValidator = joi.object({ + blacklist: joi.array().items(joi.string().min(1)).required(), +}); 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/fetch-mm-api.ts b/src/dex/generic-rfq/fetch-mm-api.ts index befc145db..4359a1b32 100644 --- a/src/dex/generic-rfq/fetch-mm-api.ts +++ b/src/dex/generic-rfq/fetch-mm-api.ts @@ -31,6 +31,10 @@ import { validateAndCast } from '../../lib/validators'; const network = 1; +const AugustusAddress = { + 1: '0xdef171fe48cf0115b1d80b88dc8eab59176fee57', +}; + const getEnv = (envName: string): string => { if (!process.env[envName]) { throw new Error(`Missing ${envName}`); @@ -269,6 +273,7 @@ const mainFirm = async () => { network, config.multicallV2Address, multiWrapper, + AugustusAddress[network], firmRateResp.order, ); diff --git a/src/dex/generic-rfq/generic-rfq.ts b/src/dex/generic-rfq/generic-rfq.ts index 389a9a4a3..2fcbb5771 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, @@ -202,7 +209,9 @@ export class GenericRFQ extends ParaSwapLimitOrders { ? overOrder(optimalSwapExchange.srcAmount, OVERORDER_BPS) : overOrder(optimalSwapExchange.destAmount, 1), side, + this.augustusAddress, 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..57ff5dc4d 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]; @@ -375,7 +375,9 @@ export class RateFetcher { _destToken: Token, srcAmount: string, side: SwapSide, + takerAddress: Address, userAddress: Address, + partner?: string, ): Promise { const srcToken = this.dexHelper.config.wrapETH(_srcToken); const destToken = this.dexHelper.config.wrapETH(_destToken); @@ -390,6 +392,8 @@ export class RateFetcher { makerAmount: side === SwapSide.BUY ? srcAmount : undefined, takerAmount: side === SwapSide.SELL ? srcAmount : undefined, userAddress, + takerAddress, + partner, }; try { @@ -423,6 +427,7 @@ export class RateFetcher { this.dexHelper.config.data.network, this.dexHelper.config.data.augustusRFQAddress, this.dexHelper.multiWrapper, + takerAddress, firmRateResp.order, this.verifierContract, ); diff --git a/src/dex/generic-rfq/types.ts b/src/dex/generic-rfq/types.ts index 1ded4dc7a..cd7b834b7 100644 --- a/src/dex/generic-rfq/types.ts +++ b/src/dex/generic-rfq/types.ts @@ -84,6 +84,8 @@ export type RFQPayload = { makerAmount?: string; takerAmount?: string; userAddress: Address; + takerAddress: Address; + partner?: string; }; export type AugustusOrderWithStringAndSignature = AugustusOrderWithString & { diff --git a/src/dex/generic-rfq/utils.ts b/src/dex/generic-rfq/utils.ts index 520097f10..56b4947ad 100644 --- a/src/dex/generic-rfq/utils.ts +++ b/src/dex/generic-rfq/utils.ts @@ -16,9 +16,15 @@ export const checkOrder = async ( network: Network, augustusRFQAddress: Address, multiWrapper: MultiWrapper, + takerAddress: Address, order: AugustusOrderWithStringAndSignature, verifierContract?: ERC1271Contract, ) => { + if (order.taker.toLowerCase() !== takerAddress.toLowerCase()) { + throw new Error( + `Taker mismatch, expected ${takerAddress} but signed order got ${order.taker}`, + ); + } const hash = calculateOrderHash(network, order, augustusRFQAddress); if (verifierContract) { diff --git a/src/dex/generic-rfq/validators.ts b/src/dex/generic-rfq/validators.ts index 551c457cc..74ce67a95 100644 --- a/src/dex/generic-rfq/validators.ts +++ b/src/dex/generic-rfq/validators.ts @@ -149,7 +149,7 @@ const stringStartWithHex0x = ( return value; }; -const mustBeAugustusSwapper = ( +const mustBeAllowedTaker = ( value: string, helpers: CustomHelpers, ): string | ErrorReport => { @@ -168,7 +168,7 @@ export const orderWithSignatureValidator = joi nonceAndMeta: joi.string().custom(stringPositiveBigIntValidator), expiry: joi.number().min(0), maker: addressSchema.required(), - taker: addressSchema.required().custom(mustBeAugustusSwapper), + taker: addressSchema.required().custom(mustBeAllowedTaker), makerAsset: addressSchema.required(), takerAsset: addressSchema.required(), makerAmount: joi diff --git a/src/dex/gmx/config.ts b/src/dex/gmx/config.ts index 5ae6f1a9d..5b1802ea3 100644 --- a/src/dex/gmx/config.ts +++ b/src/dex/gmx/config.ts @@ -15,12 +15,30 @@ export const GMXConfig: DexConfigMap = { [Network.ARBITRUM]: { vault: '0x489ee077994B6658eAfA855C308275EAd8097C4A', reader: '0x22199a49A999c351eF7927602CFB187ec3cae489', - priceFeed: '0xfe661cbf27da0656b7a1151a761ff194849c387a', + priceFeed: '0x2d68011bca022ed0e474264145f46cc4de96a002', fastPriceFeed: '0x8960d1b45a2d15d063b84b34dfb2fb2ca7535527', fastPriceEvents: '0x4530b7de1958270a2376be192a24175d795e1b07', usdg: '0x45096e7aA921f27590f8F19e457794EB09678141', }, }, + Morphex: { + [Network.FANTOM]: { + vault: '0x245cD6d33578de9aF75a3C0c636c726b1A8cbdAa', + reader: '0xcA47b9b612a152ece991F31d8D3547D73BaF2Ecc', + priceFeed: '0x7a451DE877CbB6551AACa671d0458B6f9dF1e29A', + fastPriceFeed: '0x7f54C35A38D89fcf5Fe516206E6628745ed38CC7', + fastPriceEvents: '0xDc7C389be5da32e326A261dC0126feCa7AE04d79', + usdg: '0xe135c7BFfda932b5B862Da442cF4CbC4d43DC3Ad', + }, + [Network.BSC]: { + vault: '0x46940Dc651bFe3F2CC3E04cf9dC5579B50Cf0765', + reader: '0x49A97680938B4F1f73816d1B70C3Ab801FAd124B', + priceFeed: '0x0144b19D1B9338fC7C286d6767bd9b29F0347f27', + fastPriceFeed: '0x55e6e6A968e485abEC1e1d957f408586e45a4f99', + fastPriceEvents: '0x491Df61db853761d42C4F38BeD220E9D807143dE', + usdg: '0x548f93779fBC992010C07467cBaf329DD5F059B7', + }, + }, }; export const Adapters: { @@ -44,4 +62,20 @@ export const Adapters: { }, ], }, + [Network.BSC]: { + [SwapSide.SELL]: [ + { + name: 'BscAdapter02', + index: 6, + }, + ], + }, + [Network.FANTOM]: { + [SwapSide.SELL]: [ + { + name: 'FantomAdapter01', + index: 12, + }, + ], + }, }; diff --git a/src/dex/gmx/gmx-e2e.test.ts b/src/dex/gmx/gmx-e2e.test.ts index f209019cb..4a6fe096d 100644 --- a/src/dex/gmx/gmx-e2e.test.ts +++ b/src/dex/gmx/gmx-e2e.test.ts @@ -164,3 +164,156 @@ describe('GMX E2E', () => { ); }); }); + +describe('Morphex E2E', () => { + const dexKey = 'Morphex'; + + describe('Fantom', () => { + const network = Network.FANTOM; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const tokenASymbol: string = 'axlUSDC'; + const tokenBSymbol: string = 'lzUSDC'; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const tokenAAmount: string = '500000000'; // 500 Axelar USDC + const tokenBAmount: string = '500000000'; // 500 Layer Zero USDC + const nativeTokenAmount = '100000000000000000000'; // 100 FTM + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, 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('BSC', () => { + const network = Network.BSC; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'XRP'; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const tokenAAmount: string = '100000'; + const tokenBAmount: string = '100000'; + const nativeTokenAmount = '1000000000000000000'; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${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, + ); + }); + }); + }), + ); + }); +}); diff --git a/src/dex/gmx/gmx-integration.test.ts b/src/dex/gmx/gmx-integration.test.ts index f8ef4f659..ef0e96e6c 100644 --- a/src/dex/gmx/gmx-integration.test.ts +++ b/src/dex/gmx/gmx-integration.test.ts @@ -15,13 +15,6 @@ import { import { Tokens } from '../../../tests/constants-e2e'; import ReaderABI from '../../abi/gmx/reader.json'; -const network = Network.AVALANCHE; -const TokenASymbol = 'USDCe'; -const TokenA = Tokens[network][TokenASymbol]; - -const TokenBSymbol = 'WAVAX'; -const TokenB = Tokens[network][TokenBSymbol]; - const amounts = [ 0n, 1000000000n, @@ -31,12 +24,116 @@ const amounts = [ 5000000000n, ]; -const dexKey = 'GMX'; -const params = GMXConfig[dexKey][network]; -const readerInterface = new Interface(ReaderABI); -const readerAddress = '0x67b789D48c926006F5132BFCe4e976F0A7A63d5D'; +describe('GMX Avalanche', function () { + const network = Network.AVALANCHE; + const TokenASymbol = 'USDCe'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'WAVAX'; + const TokenB = Tokens[network][TokenBSymbol]; + const dexKey = 'GMX'; + const params = GMXConfig[dexKey][network]; + const readerInterface = new Interface(ReaderABI); + const readerAddress = '0x67b789D48c926006F5132BFCe4e976F0A7A63d5D'; + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const dexHelper = new DummyDexHelper(network); + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const gmx = new GMX(network, dexKey, dexHelper); + + await gmx.initializePricing(blocknumber); + + const pools = await gmx.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blocknumber, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await gmx.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + if (gmx.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 gmx = new GMX(network, dexKey, dexHelper); + + await gmx.updatePoolState(); + const poolLiquidity = await gmx.getTopPoolsForToken(TokenA.address, 10); + console.log( + `${TokenASymbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + if (!gmx.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity(poolLiquidity, TokenA.address, dexKey); + } + }); +}); + +describe('Morphex BSC', function () { + const dexKey = 'Morphex'; + const network = Network.BSC; + const TokenASymbol = 'WBNB'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'XRP'; + const TokenB = Tokens[network][TokenBSymbol]; + + const amounts = [ + 0n, + 1000000000000000000n, + 2000000000000000000n, + 3000000000000000000n, + 4000000000000000000n, + 5000000000000000000n, + ]; + + const readerInterface = new Interface(ReaderABI); + const params = GMXConfig[dexKey][network]; + const readerAddress = params.reader; -describe('GMX', function () { it('getPoolIdentifiers and getPricesVolume SELL', async function () { const dexHelper = new DummyDexHelper(network); const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); diff --git a/src/dex/hashflow/config.ts b/src/dex/hashflow/config.ts index e2042d7d2..2a2e05fcd 100644 --- a/src/dex/hashflow/config.ts +++ b/src/dex/hashflow/config.ts @@ -1,26 +1,26 @@ -import { DexParams } from './types'; -import { DexConfigMap, AdapterMappings } from '../../types'; import { Network, SwapSide } from '../../constants'; +import { AdapterMappings, DexConfigMap } from '../../types'; +import { DexParams } from './types'; export const HashflowConfig: DexConfigMap = { Hashflow: { [Network.MAINNET]: { - routerAddress: '0xf6a94dfd0e6ea9ddfdffe4762ad4236576136613', + routerAddress: '0x55084eE0fEf03f14a305cd24286359A35D735151', }, [Network.POLYGON]: { - routerAddress: '0x72550597dc0b2e0bec24e116add353599eff2e35', + routerAddress: '0x55084eE0fEf03f14a305cd24286359A35D735151', }, [Network.BSC]: { - routerAddress: '0x0acffb0fb2cddd9bd35d03d359f3d899e32facc9', + routerAddress: '0x55084eE0fEf03f14a305cd24286359A35D735151', }, [Network.ARBITRUM]: { - routerAddress: '0x1f772fa3bc263160ea09bb16ce1a6b8fc0fab36a', + routerAddress: '0x55084eE0fEf03f14a305cd24286359A35D735151', }, [Network.AVALANCHE]: { - routerAddress: '0x64d2f9f44fe26c157d552ae7eaa613ca6587b59e', + routerAddress: '0x55084eE0fEf03f14a305cd24286359A35D735151', }, [Network.OPTIMISM]: { - routerAddress: '0xb3999f658c0391d94a37f7ff328f3fec942bcadc', + routerAddress: '0xCa310B1B942A30Ff4b40a5E1b69AB4607eC79Bc1', }, }, }; diff --git a/src/dex/hashflow/hashflow-e2e.test.ts b/src/dex/hashflow/hashflow-e2e.test.ts index 0ccd6797a..a4e001c59 100644 --- a/src/dex/hashflow/hashflow-e2e.test.ts +++ b/src/dex/hashflow/hashflow-e2e.test.ts @@ -1,15 +1,18 @@ import dotenv from 'dotenv'; dotenv.config(); -import { testE2E } from '../../../tests/utils-e2e'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { - Tokens, Holders, NativeTokenSymbols, + Tokens, } from '../../../tests/constants-e2e'; -import { Network, ContractMethod, SwapSide } from '../../constants'; -import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { testE2E } from '../../../tests/utils-e2e'; import { generateConfig } from '../../config'; +import { ContractMethod, Network, SwapSide } from '../../constants'; + +// Give time for rate fetcher to fill the cache +const sleepMs = 3000; function testForNetwork( network: Network, @@ -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/hashflow/hashflow-integration.test.ts b/src/dex/hashflow/hashflow-integration.test.ts index 3fb4982d7..24d079b09 100644 --- a/src/dex/hashflow/hashflow-integration.test.ts +++ b/src/dex/hashflow/hashflow-integration.test.ts @@ -2,16 +2,17 @@ import dotenv from 'dotenv'; dotenv.config(); -import { DummyDexHelper } from '../../dex-helper/index'; -import { Network, SwapSide } from '../../constants'; -import { BI_POWS } from '../../bigint-constants'; -import { Hashflow } from './hashflow'; +import { Tokens } from '../../../tests/constants-e2e'; import { + checkConstantPoolPrices, checkPoolPrices, checkPoolsLiquidity, - checkConstantPoolPrices, + sleep, } from '../../../tests/utils'; -import { Tokens } from '../../../tests/constants-e2e'; +import { BI_POWS } from '../../bigint-constants'; +import { Network, SwapSide } from '../../constants'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { Hashflow } from './hashflow'; async function testPricingOnNetwork( hashflow: Hashflow, @@ -104,6 +105,8 @@ describe('Hashflow', function () { beforeAll(async () => { blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); hashflow = new Hashflow(network, dexKey, dexHelper); + await hashflow.initializePricing(0); + await sleep(5000); }); it('getPoolIdentifiers and getPricesVolume SELL', async function () { diff --git a/src/dex/hashflow/hashflow.ts b/src/dex/hashflow/hashflow.ts index dc7dbd2ff..19e13ef8d 100644 --- a/src/dex/hashflow/hashflow.ts +++ b/src/dex/hashflow/hashflow.ts @@ -1,67 +1,67 @@ +import { ChainId, ZERO_ADDRESS } from '@hashflow/sdk'; +import { Chain, ChainType, HashflowApi } from '@hashflow/taker-js'; import { - Token, + MarketMakersResponse, + PriceLevelsResponse, + RfqResponse, +} from '@hashflow/taker-js/dist/types/rest'; +import BigNumber from 'bignumber.js'; +import { Interface } from 'ethers/lib/utils'; +import { assert } from 'ts-essentials'; +import routerAbi from '../../abi/hashflow/HashflowRouter.abi.json'; +import { BI_MAX_UINT256 } from '../../bigint-constants'; +import { BN_0, BN_1, getBigNumberPow } from '../../bignumber-constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { + CACHE_PREFIX, + ETHER_ADDRESS, + Network, + SwapSide, +} from '../../constants'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { IDex } from '../../dex/idex'; +import { + AdapterExchangeParam, Address, ExchangePrices, - PoolPrices, - AdapterExchangeParam, - SimpleExchangeParam, - PoolLiquidity, - Logger, ExchangeTxInfo, + Logger, OptimalSwapExchange, + PoolLiquidity, + PoolPrices, PreprocessTransactionOptions, + SimpleExchangeParam, + Token, } from '../../types'; -import { - SwapSide, - Network, - ETHER_ADDRESS, - CACHE_PREFIX, -} from '../../constants'; -import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; import { getDexKeysWithNetwork } from '../../utils'; -import { IDex } from '../../dex/idex'; -import { IDexHelper } from '../../dex-helper/idex-helper'; -import { - HashflowData, - PriceLevel, - RfqError, - RFQType, - SlippageCheckError, -} from './types'; +import { TooStrictSlippageCheckError } from '../generic-rfq/types'; import { SimpleExchange } from '../simple-exchange'; import { Adapters, HashflowConfig } from './config'; -import { HashflowApi } from '@hashflow/taker-js'; -import { RateFetcher } from './rate-fetcher'; -import routerAbi from '../../abi/hashflow/HashflowRouter.abi.json'; -import BigNumber from 'bignumber.js'; -import { BN_0, BN_1, getBigNumberPow } from '../../bignumber-constants'; -import { Interface } from 'ethers/lib/utils'; -import { ChainId, ZERO_ADDRESS } from '@hashflow/sdk'; -import { - MarketMakersResponse, - PriceLevelsResponse, - RfqResponse, -} from '@hashflow/taker-js/dist/types/rest'; -import { assert } from 'ts-essentials'; import { - HASHFLOW_BLACKLIST_TTL_S, - HASHFLOW_MM_RESTRICT_TTL_S, HASHFLOW_API_CLIENT_NAME, - HASHFLOW_API_URL, - HASHFLOW_API_PRICES_POLLING_INTERVAL_MS, HASHFLOW_API_MARKET_MAKERS_POLLING_INTERVAL_MS, - HASHFLOW_PRICES_CACHES_TTL_S, - HASHFLOW_MARKET_MAKERS_CACHES_TTL_S, + HASHFLOW_API_PRICES_POLLING_INTERVAL_MS, + HASHFLOW_API_URL, + HASHFLOW_BLACKLIST_TTL_S, HASHFLOW_GAS_COST, + HASHFLOW_MARKET_MAKERS_CACHES_TTL_S, HASHFLOW_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION, + HASHFLOW_MM_RESTRICT_TTL_S, + HASHFLOW_PRICES_CACHES_TTL_S, } from './constants'; -import { BI_MAX_UINT256 } from '../../bigint-constants'; -import { TooStrictSlippageCheckError } from '../generic-rfq/types'; +import { RateFetcher } from './rate-fetcher'; +import { + HashflowData, + PriceLevel, + RfqError, + SlippageCheckError, +} from './types'; export class Hashflow extends SimpleExchange implements IDex { readonly isStatePollingDex = true; readonly hasConstantPriceLargeAmounts = false; readonly needWrapNative = false; + readonly needsSequentialPreprocessing = true; readonly isFeeOnTransferSupported = false; private api: HashflowApi; private rateFetcher: RateFetcher; @@ -120,19 +120,21 @@ export class Hashflow extends SimpleExchange implements IDex { markerMakersIntervalMs: HASHFLOW_API_MARKET_MAKERS_POLLING_INTERVAL_MS, marketMakersReqParams: { - url: `${HASHFLOW_API_URL}/taker/v1/marketMakers`, + url: `${HASHFLOW_API_URL}/taker/v3/market-makers`, params: { - networkId: this.network, + baseChainId: this.network, source: HASHFLOW_API_CLIENT_NAME, + baseChainType: 'evm', }, headers: { Authorization: this.hashFlowAuthToken }, }, pricesReqParams: { - url: `${HASHFLOW_API_URL}/taker/v2/price-levels`, + url: `${HASHFLOW_API_URL}/taker/v3/price-levels`, params: { - networkId: this.network, + baseChainId: this.network, source: HASHFLOW_API_CLIENT_NAME, marketMakers: [], + baseChainType: 'evm', }, headers: { Authorization: this.hashFlowAuthToken }, }, @@ -320,8 +322,8 @@ export class Hashflow extends SimpleExchange implements IDex { priceLevels: PriceLevel[], ): { level: BigNumber; price: BigNumber }[] => priceLevels.map(l => ({ - level: new BigNumber(l.level), - price: new BigNumber(l.price), + level: new BigNumber(l.q), + price: new BigNumber(l.p), })); computeLevelsQuote( @@ -487,7 +489,7 @@ export class Hashflow extends SimpleExchange implements IDex { new BigNumber(a.toString()).dividedBy(divider), ); const firstLevelRaw = levels[0]; - const firstLevelAmountBN = new BigNumber(firstLevelRaw.level); + const firstLevelAmountBN = new BigNumber(firstLevelRaw.q); if (amountsRaw[amountsRaw.length - 1].lt(firstLevelAmountBN)) { return null; @@ -495,7 +497,7 @@ export class Hashflow extends SimpleExchange implements IDex { if (firstLevelAmountBN.gt(0)) { // Add zero level for price computation - levels.unshift({ level: '0', price: firstLevelRaw.price }); + levels.unshift({ q: '0', p: firstLevelRaw.p }); } const unitPrice = this.computePricesFromLevels( @@ -564,14 +566,15 @@ export class Hashflow extends SimpleExchange implements IDex { `${this.dexKey}-${this.network}: MM was not provided in data`, ); const chainId = this.network as ChainId; - + let chainType: ChainType = 'evm'; + const chain: Chain = { chainType, chainId }; const normalizedSrcToken = this.normalizeToken(srcToken); const normalizedDestToken = this.normalizeToken(destToken); let rfq: RfqResponse; try { rfq = await this.api.requestQuote({ - chainId, + baseChain: chain, baseToken: normalizedSrcToken.address, quoteToken: normalizedDestToken.address, ...(side === SwapSide.SELL @@ -593,7 +596,7 @@ export class Hashflow extends SimpleExchange implements IDex { )}: ${JSON.stringify(rfq)}`; this.logger.warn(message); throw new RfqError(message); - } else if (!rfq.quoteData) { + } else if (!rfq.quotes[0].quoteData) { const message = `${this.dexKey}-${ this.network }: Failed to fetch RFQ for ${this.getPairName( @@ -602,7 +605,7 @@ export class Hashflow extends SimpleExchange implements IDex { )}. Missing quote data`; this.logger.warn(message); throw new RfqError(message); - } else if (!rfq.signature) { + } else if (!rfq.quotes[0].signature) { const message = `${this.dexKey}-${ this.network }: Failed to fetch RFQ for ${this.getPairName( @@ -611,40 +614,22 @@ export class Hashflow extends SimpleExchange implements IDex { )}. Missing signature`; this.logger.warn(message); throw new RfqError(message); - } else if (!rfq.gasEstimate) { - const message = `${this.dexKey}-${ - this.network - }: Failed to fetch RFQ for ${this.getPairName( - normalizedSrcToken.address, - normalizedDestToken.address, - )}. No gas estimate.`; - this.logger.warn(message); - throw new RfqError(message); - } else if (rfq.quoteData.rfqType !== RFQType.RFQT) { - const message = `${this.dexKey}-${ - this.network - }: Failed to fetch RFQ for ${this.getPairName( - normalizedSrcToken.address, - normalizedDestToken.address, - )}. Invalid RFQ type.`; - this.logger.warn(message); - throw new RfqError(message); } assert( - rfq.quoteData.baseToken === normalizedSrcToken.address, - `QuoteData baseToken=${rfq.quoteData.baseToken} is different from srcToken=${normalizedSrcToken.address}`, + rfq.quotes[0].quoteData.baseToken === normalizedSrcToken.address, + `QuoteData baseToken=${rfq.quotes[0].quoteData.baseToken} is different from srcToken=${normalizedSrcToken.address}`, ); assert( - rfq.quoteData.quoteToken === normalizedDestToken.address, - `QuoteData baseToken=${rfq.quoteData.quoteToken} is different from srcToken=${normalizedDestToken.address}`, + rfq.quotes[0].quoteData.quoteToken === normalizedDestToken.address, + `QuoteData baseToken=${rfq.quotes[0].quoteData.quoteToken} is different from srcToken=${normalizedDestToken.address}`, ); - const expiryAsBigInt = BigInt(rfq.quoteData.quoteExpiry); + const expiryAsBigInt = BigInt(rfq.quotes[0].quoteData.quoteExpiry); const minDeadline = expiryAsBigInt > 0 ? expiryAsBigInt : BI_MAX_UINT256; - const baseTokenAmount = BigInt(rfq.quoteData.baseTokenAmount); - const quoteTokenAmount = BigInt(rfq.quoteData.quoteTokenAmount); + const baseTokenAmount = BigInt(rfq.quotes[0].quoteData.baseTokenAmount); + const quoteTokenAmount = BigInt(rfq.quotes[0].quoteData.quoteTokenAmount); const srcAmount = BigInt(optimalSwapExchange.srcAmount); const destAmount = BigInt(optimalSwapExchange.destAmount); @@ -720,9 +705,8 @@ export class Hashflow extends SimpleExchange implements IDex { ...optimalSwapExchange, data: { mm, - quoteData: rfq.quoteData, - signature: rfq.signature, - gasEstimate: rfq.gasEstimate, + quoteData: rfq.quotes[0].quoteData, + signature: rfq.quotes[0].signature, }, }, { deadline: minDeadline }, @@ -737,7 +721,7 @@ export class Hashflow extends SimpleExchange implements IDex { ); await this.setBlacklist(options.txOrigin); } else { - if(e instanceof TooStrictSlippageCheckError) { + if (e instanceof TooStrictSlippageCheckError) { this.logger.warn( `${this.dexKey}-${this.network}: Market Maker ${mm} failed to build transaction on side ${side} with too strict slippage. Skipping restriction`, ); @@ -817,7 +801,7 @@ export class Hashflow extends SimpleExchange implements IDex { { pool: quoteData.pool, quoteToken: quoteData.quoteToken, - externalAccount: quoteData.eoa ?? ZERO_ADDRESS, + externalAccount: quoteData.externalAccount ?? ZERO_ADDRESS, baseTokenAmount: quoteData.baseTokenAmount, quoteTokenAmount: quoteData.quoteTokenAmount, quoteExpiry: quoteData.quoteExpiry, @@ -878,10 +862,10 @@ export class Hashflow extends SimpleExchange implements IDex { ); // Encode here the transaction arguments - const swapData = this.routerInterface.encodeFunctionData('tradeSingleHop', [ + const swapData = this.routerInterface.encodeFunctionData('tradeRFQT', [ [ quoteData.pool, - quoteData.eoa ?? ZERO_ADDRESS, + quoteData.externalAccount ?? ZERO_ADDRESS, quoteData.trader, quoteData.effectiveTrader ?? quoteData.trader, quoteData.baseToken, @@ -923,7 +907,7 @@ export class Hashflow extends SimpleExchange implements IDex { levels: PriceLevel[], baseTokenPriceUsd: number, ): number => { - const maxLevel = new BigNumber(levels[levels.length - 1]?.level ?? '0'); + const maxLevel = new BigNumber(levels[levels.length - 1]?.q ?? '0'); return maxLevel.multipliedBy(baseTokenPriceUsd).toNumber(); }; diff --git a/src/dex/hashflow/rate-fetcher.ts b/src/dex/hashflow/rate-fetcher.ts index 29b5ba6a5..2df301a41 100644 --- a/src/dex/hashflow/rate-fetcher.ts +++ b/src/dex/hashflow/rate-fetcher.ts @@ -1,14 +1,14 @@ +import { Network } from '../../constants'; import { IDexHelper } from '../../dex-helper'; import { Fetcher, SkippingRequest } from '../../lib/fetcher/fetcher'; import { validateAndCast } from '../../lib/validators'; import { Logger } from '../../types'; import { + HashflowMarketMakersResponse, HashflowRateFetcherConfig, HashflowRatesResponse, - HashflowMarketMakersResponse, } from './types'; -import { pricesResponseValidator, marketMakersValidator } from './validators'; -import { Network } from '../../constants'; +import { marketMakersValidator, pricesResponseValidator } from './validators'; export class RateFetcher { private rateFetcher: Fetcher; @@ -30,7 +30,6 @@ export class RateFetcher { this.pricesCacheTTL = config.rateConfig.pricesCacheTTLSecs; this.marketMakersCacheKey = config.rateConfig.marketMakersCacheKey; this.marketMakersCacheTTL = config.rateConfig.marketMakersCacheTTLSecs; - this.marketMakersFetcher = new Fetcher( dexHelper.httpRequest, { @@ -69,10 +68,9 @@ export class RateFetcher { ); } - const prices = await dexHelper.httpRequest.request({ - ...options, - params: { ...options.params, marketMakers: filteredMarketMakers }, - }); + options.params.marketMakers = filteredMarketMakers; + + const prices = await dexHelper.httpRequest.request(options); return prices; }, diff --git a/src/dex/hashflow/types.ts b/src/dex/hashflow/types.ts index ff28311ad..562458465 100644 --- a/src/dex/hashflow/types.ts +++ b/src/dex/hashflow/types.ts @@ -1,11 +1,10 @@ -import { QuoteData } from '@hashflow/taker-js/dist/types/common'; +import { Chain, QuoteData } from '@hashflow/taker-js/dist/types/common'; import { RequestHeaders } from '../../dex-helper'; export type HashflowData = { mm: string; quoteData?: QuoteData; signature?: string; - gasEstimate?: number; }; export type DexParams = { @@ -13,8 +12,8 @@ export type DexParams = { }; export interface PriceLevel { - level: string; - price: string; + q: string; + p: string; } export class RfqError extends Error {} @@ -29,7 +28,6 @@ export class SlippageCheckError extends Error {} export type HashflowRatesLevel = { pair: Record; levels: Array>; - includesFees: boolean; }; export type HashflowMarketMakersResponse = { @@ -38,7 +36,8 @@ export type HashflowMarketMakersResponse = { export type HashflowRatesResponse = { status: string; - networkId: string; + baseChain: Chain; + quoteChain: Chain; levels: Record>; }; diff --git a/src/dex/hashflow/validators.ts b/src/dex/hashflow/validators.ts index 4e66c521c..d82e3d2d8 100644 --- a/src/dex/hashflow/validators.ts +++ b/src/dex/hashflow/validators.ts @@ -12,17 +12,24 @@ export const levelValidator = joi.array().items( }), levels: joi.array().items( joi.object({ - level: joi.string().min(1).required(), - price: joi.string().min(1).required(), + q: joi.string().min(1).required(), + p: joi.string().min(1).required(), }), ), - includesFees: joi.boolean().required(), }), ); export const pricesResponseValidator = joi.object({ status: joi.string().required(), - networkId: joi.number().required(), + // networkId: joi.number().required(), + baseChain: joi.object({ + chainType: joi.string().required(), + chainId: joi.number().required(), + }), + quoteChain: joi.object({ + chainType: joi.string().required(), + chainId: joi.number().required(), + }), levels: joi.object().pattern(joi.string().min(1), levelValidator), }); diff --git a/src/dex/idex.ts b/src/dex/idex.ts index a5e35cde4..ada44c557 100644 --- a/src/dex/idex.ts +++ b/src/dex/idex.ts @@ -20,6 +20,7 @@ import { IDexHelper } from '../dex-helper/idex-helper'; export interface IDexTxBuilder { needWrapNative: boolean; + needsSequentialPreprocessing?: boolean; // Returns the ETH fee required to swap // It is optional for a DEX to implement this @@ -130,9 +131,11 @@ export interface IDexPricing { blockNumber: number, // list of pool identifiers to use for pricing, if undefined use all pools limitPools?: string[], + // I don't like putting this as new params, but in order to not change interface // across all integrations, done it like this transferFees?: TransferFeeParams, + isFirstSwap?: boolean, ): Promise | null>; // Returns estimated gas cost for calldata for DEX when used in multiSwap. diff --git a/src/dex/index.ts b/src/dex/index.ts index f442e2edd..124f44459 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -1,3 +1,4 @@ +import _ from 'lodash'; import { UnoptimizedRate } from '../types'; import { CurveV2 } from './curve-v2'; import { IDexTxBuilder, DexContructor, IDex, IRouteOptimizer } from './idex'; @@ -5,6 +6,7 @@ import { Jarvis } from './jarvis'; import { JarvisV6 } from './jarvis-v6/jarvis-v6'; import { StablePool } from './stable-pool'; import { Weth } from './weth/weth'; +import { PolygonMigrator } from './polygon-migrator/polygon-migrator'; import { ZeroX } from './zerox'; import { UniswapV3 } from './uniswap-v3/uniswap-v3'; import { BalancerV2 } from './balancer-v2/balancer-v2'; @@ -42,13 +44,20 @@ 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 { SolidlyV3 } from './solidly-v3/solidly-v3'; +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 { Usdfi } from './solidly/forks-override/usdfi'; +import { Equalizer } from './solidly/forks-override/equalizer'; +import { Velocimeter } from './solidly/forks-override/velocimeter'; import { BalancerV1 } from './balancer-v1/balancer-v1'; import { balancerV1Merge } from './balancer-v1/optimizer'; import { CurveV1 } from './curve-v1/curve-v1'; @@ -71,6 +80,12 @@ import { TraderJoeV21 } from './trader-joe-v2.1'; import { PancakeswapV3 } from './pancakeswap-v3/pancakeswap-v3'; import { Algebra } from './algebra/algebra'; import { AngleStakedStable } from './angle-staked-stable/angle-staked-stable'; +import { QuickPerps } from './quick-perps/quick-perps'; +import { NomiswapV2 } from './uniswap-v2/nomiswap-v2'; +import { Dexalot } from './dexalot/dexalot'; +import { Smardex } from './smardex/smardex'; +import { Wombat } from './wombat/wombat'; +import { Swell } from './swell/swell'; const LegacyDexes = [ CurveV2, @@ -97,6 +112,7 @@ const LegacyDexes = [ ]; const Dexes = [ + Dexalot, CurveV1, CurveFork, Swerve, @@ -115,6 +131,7 @@ const Dexes = [ AaveV3, KyberDmm, Weth, + PolygonMigrator, MakerPsm, Nerve, Platypus, @@ -125,11 +142,17 @@ const Dexes = [ Solidly, SolidlyEthereum, SpiritSwapV2, + Ramses, Thena, Chronos, Velodrome, + VelodromeV2, + Aerodrome, Cone, SoliSnek, + Equalizer, + Velocimeter, + Usdfi, Synthetix, CurveV1Factory, SwaapV1, @@ -139,6 +162,12 @@ const Dexes = [ Camelot, SwaapV2, AngleStakedStable, + QuickPerps, + NomiswapV2, + SolidlyV3, + Smardex, + Wombat, + Swell, ]; export type LegacyDexConstructor = new (dexHelper: IDexHelper) => IDexTxBuilder< @@ -268,7 +297,7 @@ export class DexAdapterService { } getAllDexKeys() { - return this.dexKeys; + return _.uniq(this.dexKeys); } getDexByKey(key: string): IDex { @@ -305,4 +334,13 @@ export class DexAdapterService { ? this.sellAdapters[specialDexKey] : this.buyAdapters[specialDexKey]; } + + doesPreProcessingRequireSequentiality(dexKey: string): boolean { + try { + const dex = this.getDexByKey(dexKey); + return !!dex.needsSequentialPreprocessing; + } catch (e) { + return false; + } + } } 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..6840f7464 100644 --- a/src/dex/maverick-v1/maverick-v1-e2e.test.ts +++ b/src/dex/maverick-v1/maverick-v1-e2e.test.ts @@ -11,10 +11,103 @@ 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 +214,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..af1065c76 100644 --- a/src/dex/pancakeswap-v3/pancakeswap-v3-e2e.test.ts +++ b/src/dex/pancakeswap-v3/pancakeswap-v3-e2e.test.ts @@ -11,6 +11,99 @@ 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 +400,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/pancakeswap-v3/pancakeswap-v3-factory.ts b/src/dex/pancakeswap-v3/pancakeswap-v3-factory.ts new file mode 100644 index 000000000..5b60cb025 --- /dev/null +++ b/src/dex/pancakeswap-v3/pancakeswap-v3-factory.ts @@ -0,0 +1,73 @@ +import { Interface } from '@ethersproject/abi'; +import { DeepReadonly } from 'ts-essentials'; +import FactoryABI from '../../abi/pancakeswap-v3/PancakeswapV3Factory.abi.json'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { Address, Log, Logger } from '../../types'; +import { LogDescription } from 'ethers/lib/utils'; +import { FactoryState } from '../uniswap-v3/types'; + +export type OnPoolCreatedCallback = ({ + token0, + token1, + fee, +}: { + token0: string; + token1: string; + fee: bigint; +}) => Promise; + +/* + * "Stateless" event subscriber in order to capture "PoolCreated" event on new pools created. + * State is present, but it's a placeholder to actually make the events reach handlers (if there's no previous state - `processBlockLogs` is not called) + */ +export class PancakeswapV3Factory extends StatefulEventSubscriber { + handlers: { + [event: string]: (event: any) => Promise; + } = {}; + + logDecoder: (log: Log) => any; + + public readonly factoryIface = new Interface(FactoryABI); + + constructor( + readonly dexHelper: IDexHelper, + parentName: string, + protected readonly factoryAddress: Address, + logger: Logger, + protected readonly onPoolCreated: OnPoolCreatedCallback, + mapKey: string = '', + ) { + super(parentName, `${parentName} Factory`, dexHelper, logger, true, mapKey); + + this.addressesSubscribed = [factoryAddress]; + + this.logDecoder = (log: Log) => this.factoryIface.parseLog(log); + + this.handlers['PoolCreated'] = this.handleNewPool.bind(this); + } + + generateState(): FactoryState { + return {}; + } + + protected async processLog( + _: DeepReadonly, + log: Readonly, + ): Promise { + const event = this.logDecoder(log); + if (event.name in this.handlers) { + await this.handlers[event.name](event); + } + + return {}; + } + + async handleNewPool(event: LogDescription) { + const token0 = event.args.token0.toLowerCase(); + const token1 = event.args.token1.toLowerCase(); + const fee = event.args.fee; + + await this.onPoolCreated({ token0, token1, fee }); + } +} diff --git a/src/dex/pancakeswap-v3/pancakeswap-v3.ts b/src/dex/pancakeswap-v3/pancakeswap-v3.ts index 41d2de86e..185a8f17b 100644 --- a/src/dex/pancakeswap-v3/pancakeswap-v3.ts +++ b/src/dex/pancakeswap-v3/pancakeswap-v3.ts @@ -56,6 +56,10 @@ import { DEFAULT_ID_ERC20, DEFAULT_ID_ERC20_AS_STRING, } from '../../lib/tokens/types'; +import { + OnPoolCreatedCallback, + PancakeswapV3Factory, +} from './pancakeswap-v3-factory'; type PoolPairsInfo = { token0: Address; @@ -63,14 +67,15 @@ type PoolPairsInfo = { fee: string; }; -const PANCAKESWAPV3_CLEAN_NOT_EXISTING_POOL_TTL_MS = 60 * 60 * 24 * 1000; // 24 hours -const PANCAKESWAPV3_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS = 30 * 60 * 1000; // Once in 30 minutes +const PANCAKESWAPV3_CLEAN_NOT_EXISTING_POOL_TTL_MS = 3 * 24 * 60 * 60 * 1000; // 3 days +const PANCAKESWAPV3_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS = 24 * 60 * 60 * 1000; // Once in a day const PANCAKESWAPV3_QUOTE_GASLIMIT = 200_000; export class PancakeswapV3 extends SimpleExchange implements IDex { + private readonly factory: PancakeswapV3Factory; readonly isFeeOnTransferSupported: boolean = false; readonly eventPools: Record = {}; @@ -117,6 +122,14 @@ export class PancakeswapV3 this.notExistingPoolSetKey = `${CACHE_PREFIX}_${network}_${dexKey}_not_existings_pool_set`.toLowerCase(); + + this.factory = new PancakeswapV3Factory( + dexHelper, + dexKey, + this.config.factory, + this.logger, + this.onPoolCreatedDeleteFromNonExistingSet, + ); } get supportedFees() { @@ -133,6 +146,9 @@ export class PancakeswapV3 } async initializePricing(blockNumber: number) { + // Init listening to new pools creation + await this.factory.initialize(blockNumber); + if (!this.dexHelper.config.isSlave) { const cleanExpiredNotExistingPoolsKeys = async () => { const maxTimestamp = @@ -151,6 +167,44 @@ export class PancakeswapV3 } } + /* + * When a non existing pool is queried, it's blacklisted for an arbitrary long period in order to prevent issuing too many rpc calls + * Once the pool is created, it gets immediately flagged + */ + onPoolCreatedDeleteFromNonExistingSet: OnPoolCreatedCallback = async ({ + token0, + token1, + fee, + }) => { + const logPrefix = '[onPoolCreatedDeleteFromNonExistingSet]'; + const [_token0, _token1] = this._sortTokens(token0, token1); + const poolKey = `${_token0}_${_token1}_${fee}`; + + // consider doing it only from master pool for less calls to distant cache + + // delete entry locally to let local instance discover the pool + delete this.eventPools[this.getPoolIdentifier(_token0, _token1, fee)]; + + try { + this.logger.info( + `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}`, + ); + // delete pool record from set + const result = await this.dexHelper.cache.zrem( + this.notExistingPoolSetKey, + [poolKey], + ); + this.logger.info( + `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}; result: ${result}`, + ); + } catch (error) { + this.logger.error( + `${logPrefix} ERROR: failed to delete pool from set: set=${this.notExistingPoolSetKey}; key=${poolKey}`, + error, + ); + } + }; + async getPool( srcAddress: Address, destAddress: Address, @@ -265,6 +319,10 @@ export class PancakeswapV3 if (pool !== null) { const allEventPools = Object.values(this.eventPools); + // if pool was created, delete pool record from non existing set + this.dexHelper.cache + .zrem(this.notExistingPoolSetKey, [key]) + .catch(() => {}); this.logger.info( `starting to listen to new non-null pool: ${key}. Already following ${allEventPools // Not that I like this reduce, but since it is done only on initialization, expect this to be ok diff --git a/src/dex/polygon-migrator/config.ts b/src/dex/polygon-migrator/config.ts new file mode 100644 index 000000000..b63235f05 --- /dev/null +++ b/src/dex/polygon-migrator/config.ts @@ -0,0 +1,25 @@ +import { DexParams } from './types'; +import { DexConfigMap } from '../../types'; +import { Network } from '../../constants'; +import { SwapSide } from '@paraswap/core'; + +export const PolygonMigratorConfig: DexConfigMap = { + PolygonMigrator: { + [Network.MAINNET]: { + migratorAddress: '0x29e7df7b6a1b2b07b731457f499e1696c60e2c4e', + polTokenAddress: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6', + maticTokenAddress: '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0', + }, + }, +}; + +export const Adapters: { + [chainId: number]: { + [side: string]: { name: string; index: number }[]; + }; +} = { + [Network.MAINNET]: { + [SwapSide.SELL]: [{ name: 'Adapter04', index: 4 }], + [SwapSide.BUY]: [{ name: 'BuyAdapter', index: 11 }], + }, +}; diff --git a/src/dex/polygon-migrator/constants.ts b/src/dex/polygon-migrator/constants.ts new file mode 100644 index 000000000..ce268298a --- /dev/null +++ b/src/dex/polygon-migrator/constants.ts @@ -0,0 +1 @@ +export const POLYGON_MIGRATION_GAS_COST = 90_000; diff --git a/src/dex/polygon-migrator/polygon-migrator-e2e.test.ts b/src/dex/polygon-migrator/polygon-migrator-e2e.test.ts new file mode 100644 index 000000000..c1f06fd3e --- /dev/null +++ b/src/dex/polygon-migrator/polygon-migrator-e2e.test.ts @@ -0,0 +1,96 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { ContractMethod, Network, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; +import { Holders, Tokens } from '../../../tests/constants-e2e'; +import { testE2E } from '../../../tests/utils-e2e'; + +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[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(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[tokenBSymbol], + tokens[tokenASymbol], + holders[tokenBSymbol], + side === SwapSide.SELL ? tokenBAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); +} + +describe('PolygonMigrator E2E', () => { + const dexKey = 'PolygonMigrator'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'POL'; + const tokenBSymbol: string = 'MATIC'; + + const tokenAAmount: string = '10000000000000000000'; + const tokenBAmount: string = '20000000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); +}); diff --git a/src/dex/polygon-migrator/polygon-migrator-integration.test.ts b/src/dex/polygon-migrator/polygon-migrator-integration.test.ts new file mode 100644 index 000000000..8837bad7d --- /dev/null +++ b/src/dex/polygon-migrator/polygon-migrator-integration.test.ts @@ -0,0 +1,139 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { checkConstantPoolPrices } from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import { BI_POWS } from '../../bigint-constants'; +import { PolygonMigrator } from './polygon-migrator'; + +const network = Network.MAINNET; + +const MaticSymbol = 'MATIC'; +const MaticToken = Tokens[network][MaticSymbol]; + +const PolSymbol = 'POL'; +const PolToken = Tokens[network][PolSymbol]; + +const amounts = [0n, BI_POWS[18], 2000000000000000000n]; + +const dexKey = 'PolygonMigrator'; + +describe('PolygonMigrator', function () { + it('getPoolIdentifiers and getPricesVolume MATIC -> POL SELL', async function () { + const dexHelper = new DummyDexHelper(network); + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const polygonMigrator = new PolygonMigrator(network, dexKey, dexHelper); + + const pools = await polygonMigrator.getPoolIdentifiers( + MaticToken, + PolToken, + SwapSide.SELL, + blocknumber, + ); + console.log(`${MaticSymbol} <> ${PolSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await polygonMigrator.getPricesVolume( + MaticToken, + PolToken, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log(`${MaticSymbol} <> ${PolSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + }); + + it('getPoolIdentifiers and getPricesVolume POL -> MATIC SELL', async function () { + const dexHelper = new DummyDexHelper(network); + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const polygonMigrator = new PolygonMigrator(network, dexKey, dexHelper); + + const pools = await polygonMigrator.getPoolIdentifiers( + PolToken, + MaticToken, + SwapSide.SELL, + blocknumber, + ); + console.log(`${PolSymbol} <> ${MaticSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await polygonMigrator.getPricesVolume( + PolToken, + MaticToken, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log(`${PolSymbol} <> ${MaticSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + }); + + it('getPoolIdentifiers and getPricesVolume MATIC -> POL BUY', async function () { + const dexHelper = new DummyDexHelper(network); + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const polygonMigrator = new PolygonMigrator(network, dexKey, dexHelper); + + const pools = await polygonMigrator.getPoolIdentifiers( + MaticToken, + PolToken, + SwapSide.BUY, + blocknumber, + ); + console.log(`${MaticSymbol} <> ${PolSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await polygonMigrator.getPricesVolume( + MaticToken, + PolToken, + amounts, + SwapSide.BUY, + blocknumber, + pools, + ); + console.log(`${MaticSymbol} <> ${PolSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + }); + + it('getPoolIdentifiers and getPricesVolume POL -> MATIC BUY', async function () { + const dexHelper = new DummyDexHelper(network); + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const polygonMigrator = new PolygonMigrator(network, dexKey, dexHelper); + + const pools = await polygonMigrator.getPoolIdentifiers( + PolToken, + MaticToken, + SwapSide.BUY, + blocknumber, + ); + console.log(`${PolSymbol} <> ${MaticSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await polygonMigrator.getPricesVolume( + PolToken, + MaticToken, + amounts, + SwapSide.BUY, + blocknumber, + pools, + ); + console.log(`${PolSymbol} <> ${MaticSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + }); +}); diff --git a/src/dex/polygon-migrator/polygon-migrator.ts b/src/dex/polygon-migrator/polygon-migrator.ts new file mode 100644 index 000000000..e4cba83a5 --- /dev/null +++ b/src/dex/polygon-migrator/polygon-migrator.ts @@ -0,0 +1,167 @@ +import { SimpleExchange } from '../simple-exchange'; +import { IDex } from '../idex'; +import { + DexParams, + PolygonMigrationData, + PolygonMigratorFunctions, +} from './types'; +import { Network, SwapSide } from '../../constants'; +import { getDexKeysWithNetwork } from '../../utils'; +import { Adapters, PolygonMigratorConfig } from './config'; +import { + AdapterExchangeParam, + Address, + ExchangePrices, + Logger, + PoolLiquidity, + PoolPrices, + SimpleExchangeParam, + Token, +} from '../../types'; +import { IDexHelper } from '../../dex-helper'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { BI_POWS } from '../../bigint-constants'; +import { POLYGON_MIGRATION_GAS_COST } from './constants'; +import PolygonMigrationAbi from '../../abi/polygon-migration/PolygonMigration.abi.json'; +import { Interface } from 'ethers/lib/utils'; + +export class PolygonMigrator + extends SimpleExchange + implements IDex +{ + readonly hasConstantPriceLargeAmounts = true; + readonly isFeeOnTransferSupported = false; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(PolygonMigratorConfig); + + logger: Logger; + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + readonly migratorAddress: string = PolygonMigratorConfig[dexKey][network] + .migratorAddress, + readonly polTokenAddress: string = PolygonMigratorConfig[dexKey][network] + .polTokenAddress, + readonly maticTokenAddress: string = PolygonMigratorConfig[dexKey][network] + .maticTokenAddress, + protected unitPrice = BI_POWS[18], + protected adapters = Adapters[network] || {}, + protected migratorInterface = new Interface(PolygonMigrationAbi), + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey); + } + + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return this.adapters[side] || null; + } + + isMatic(tokenAddress: Address) { + return this.maticTokenAddress.toLowerCase() === tokenAddress.toLowerCase(); + } + + isPol(tokenAddress: Address) { + return this.polTokenAddress.toLowerCase() === tokenAddress.toLowerCase(); + } + + isAppropriatePair(srcToken: Token, destToken: Token) { + return ( + (this.isMatic(srcToken.address) && this.isPol(destToken.address)) || + (this.isMatic(destToken.address) && this.isPol(srcToken.address)) + ); + } + + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + if (this.isAppropriatePair(srcToken, destToken)) { + return [`${this.dexKey}_${srcToken.address}_${destToken.address}`]; + } + + return []; + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + ): Promise> { + if (!this.isAppropriatePair(srcToken, destToken)) { + return null; + } + + return [ + { + prices: amounts, + unit: this.unitPrice, + gasCost: POLYGON_MIGRATION_GAS_COST, + exchange: this.dexKey, + poolAddresses: [this.migratorAddress], + data: null, + }, + ]; + } + + // 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: PolygonMigrationData, + side: SwapSide, + ): AdapterExchangeParam { + return { + targetExchange: this.migratorAddress, + payload: '0x', + networkFee: '0', + }; + } + + async getSimpleParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: PolygonMigrationData, + side: SwapSide, + ): Promise { + const swapData = this.migratorInterface.encodeFunctionData( + this.isMatic(srcToken) + ? PolygonMigratorFunctions.migrate + : PolygonMigratorFunctions.unmigrate, + [srcAmount], + ); + + return this.buildSimpleParamWithoutWETHConversion( + srcToken, + srcAmount, + destToken, + destAmount, + swapData, + this.migratorAddress, + ); + } + + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + return []; + } +} diff --git a/src/dex/polygon-migrator/types.ts b/src/dex/polygon-migrator/types.ts new file mode 100644 index 000000000..d4d23377d --- /dev/null +++ b/src/dex/polygon-migrator/types.ts @@ -0,0 +1,14 @@ +import { Address } from '../../types'; + +export type PolygonMigrationData = null; + +export type DexParams = { + migratorAddress: Address; + polTokenAddress: Address; + maticTokenAddress: Address; +}; + +export enum PolygonMigratorFunctions { + migrate = 'migrate', + unmigrate = 'unmigrate', +} 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..26fe455ba --- /dev/null +++ b/src/dex/quick-perps/pool.ts @@ -0,0 +1,407 @@ +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.logger.warn(`State is generated in RPC fallback call`); + 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.getDapiNameHashMultiCallInput(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 api3ServerV1Prep: { + [address: string]: { + proxy: Address; + api3ServerV1: Address; + }; + } = {}; + const getFeedIdRequests: MultiCallInput[] = []; + + for (let token of tokens) { + const [api3ServerAddressRes, dapiNameHashRes] = configResults.slice( + ...multicallSlices.shift()!, + ); + const serverV1Address = + Api3FeedSubscriber.decodeApi3ServerV1Result(api3ServerAddressRes); + const dapiNameHash = + Api3FeedSubscriber.decodeDapiNameHash(dapiNameHashRes); + getFeedIdRequests.push( + Api3FeedSubscriber.getFeedIdFromDapiNameHash( + serverV1Address, + dapiNameHash, + ), + ); + api3ServerV1Prep[token] = { + proxy: priceFeeds.shift(), + api3ServerV1: serverV1Address, + }; + } + + 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); + + const feedIdResults = ( + await multiContract.methods + .aggregate(getFeedIdRequests) + .call({}, blockNumber) + ).returnData; + + const api3ServerV1: { + [address: string]: { + proxy: Address; + api3ServerV1: Address; + dataFeedId: string; + }; + } = {}; + for (let [i, token] of tokens.entries()) { + const { proxy, api3ServerV1: api3ServerAddress } = + api3ServerV1Prep[token]; + const dataFeedId = Api3FeedSubscriber.decodeFeedIdFromDapiNameHash( + feedIdResults[i], + ); + api3ServerV1[token] = { + proxy, + api3ServerV1: api3ServerAddress, + dataFeedId, + }; + } + + 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..16cf510aa --- /dev/null +++ b/src/dex/quick-perps/quick-perps-events.test.ts @@ -0,0 +1,100 @@ +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, + 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..0cbefeea7 --- /dev/null +++ b/src/dex/quick-perps/quick-perps.ts @@ -0,0 +1,302 @@ +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; + try { + 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], + }, + ]; + } catch (e) { + this.logger.error( + `Error_getPrices ${srcToken.symbol || srcToken.address}, ${ + destToken.symbol || destToken.address + }, ${side}: `, + e, + ); + return null; + } + } + + // 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/simple-exchange.ts b/src/dex/simple-exchange.ts index 79c1360b1..282c58a67 100644 --- a/src/dex/simple-exchange.ts +++ b/src/dex/simple-exchange.ts @@ -9,7 +9,7 @@ import augustusABI from '../abi/augustus.json'; import { isETHAddress } from '../utils'; import { MAX_UINT } from '../constants'; import Web3 from 'web3'; -import { IDexHelper } from '../dex-helper'; +import { ICache, IDexHelper } from '../dex-helper'; import { AbiItem } from 'web3-utils'; /* @@ -36,13 +36,15 @@ export class SimpleExchange { protected augustusAddress: Address; protected augustusInterface: Interface; private provider: Web3; + private cache: ICache; protected network: number; protected dexmapKey: string; readonly cacheStateKey: string; + private readonly cacheApprovesKey: string; - constructor(dexHelper: IDexHelper, public dexKey: string) { + constructor(protected readonly dexHelper: IDexHelper, public dexKey: string) { this.simpleSwapHelper = new Interface(SimpleSwapHelperABI); this.erc20Interface = new Interface(ERC20ABI); this.erc20Contract = new dexHelper.web3Provider.eth.Contract( @@ -54,12 +56,17 @@ export class SimpleExchange { this.augustusAddress = dexHelper.config.data.augustusAddress; this.augustusInterface = new Interface(augustusABI); this.provider = dexHelper.web3Provider; + this.cache = dexHelper.cache; this.dexmapKey = `${CACHE_PREFIX}_${this.network}_${this.dexKey}_poolconfigs`.toLowerCase(); this.cacheStateKey = `${CACHE_PREFIX}_${this.network}_${this.dexKey}_states`.toLowerCase(); + + // if there's anything else to cache, this name could be more abstract + this.cacheApprovesKey = + `${CACHE_PREFIX}_${this.network}_approves`.toLowerCase(); } private async hasAugustusAllowance( @@ -68,9 +75,35 @@ export class SimpleExchange { amount: string, ): Promise { if (token.toLowerCase() === ETHER_ADDRESS.toLowerCase()) return true; + const cacheKey = `${token}_${target}`; - const allowanceData = this.erc20Interface.encodeFunctionData('allowance', [ + // as approve is given to an infinite amount, we can cache only the target and token address + const isCachedApproved = await this.cache.sismember( + this.cacheApprovesKey, + cacheKey, + ); + + if (isCachedApproved) return true; + + const allowance = await this.getAllowance( this.augustusAddress, + token, + target, + ); + const isApproved = BigInt(allowance) >= BigInt(amount); + + if (isApproved) await this.cache.sadd(this.cacheApprovesKey, cacheKey); + + return isApproved; + } + + private async getAllowance( + spender: Address, + token: Address, + target: Address, + ): Promise { + const allowanceData = this.erc20Interface.encodeFunctionData('allowance', [ + spender, target, ]); @@ -83,7 +116,8 @@ export class SimpleExchange { 'allowance', allowanceRaw, ); - return BigInt(allowance.toString()) >= BigInt(amount); + + return allowance.toString(); } protected async getApproveSimpleParam( @@ -123,6 +157,7 @@ export class SimpleExchange { swapCallee: Address, spender?: Address, networkFee: NumberAsString = '0', + preCalls?: Omit, ): Promise { const approveParam = await this.getApproveSimpleParam( src, @@ -134,10 +169,25 @@ export class SimpleExchange { ).toString(); return { - callees: [...approveParam.callees, swapCallee], - calldata: [...approveParam.calldata, swapCallData], - values: [...approveParam.values, swapValue], + callees: [ + ...(preCalls?.callees || []), + ...approveParam.callees, + swapCallee, + ], + calldata: [ + ...(preCalls?.calldata || []), + ...approveParam.calldata, + swapCallData, + ], + values: [...(preCalls?.values || []), ...approveParam.values, swapValue], networkFee, }; } + + protected isWETH(tokenAddress: string) { + const weth = + this.dexHelper.config.data.wrappedNativeTokenAddress.toLowerCase(); + + return tokenAddress.toLowerCase() === weth; + } } diff --git a/src/dex/smardex/config.ts b/src/dex/smardex/config.ts new file mode 100644 index 000000000..6bb90caa1 --- /dev/null +++ b/src/dex/smardex/config.ts @@ -0,0 +1,67 @@ +import { DexParams } from './types'; +import { DexConfigMap, AdapterMappings } from '../../types'; +import { Network, SwapSide } from '../../constants'; + +const GATEWAY_SUBGRAPH = 'https://subgraph.smardex.io'; +const MAINNET_INIT_HASH = + '0xc762a0f9885cc92b9fd8eef224b75997682b634460611bc0f2138986e20b653f'; +const LAYER2_INIT_HASH = + '0x33bee911475f015247aeb1eebe149d1c6d2669be54126c29d85df6b0abb4c4e9'; + +export const SmardexConfig: DexConfigMap = { + Smardex: { + [Network.MAINNET]: { + factoryAddress: '0xB878DC600550367e14220d4916Ff678fB284214F', + router: '0xC33984ABcAe20f47a754eF78f6526FeF266c0C6F', + initCode: MAINNET_INIT_HASH, + subgraphURL: `${GATEWAY_SUBGRAPH}/ethereum`, + }, + [Network.ARBITRUM]: { + factoryAddress: '0x41A00e3FbE7F479A99bA6822704d9c5dEB611F22', + router: '0xDA3970a20cdc2B1269fc96C4E8D300E0fdDB7b3D', + initCode: LAYER2_INIT_HASH, + subgraphURL: `${GATEWAY_SUBGRAPH}/arbitrum`, + }, + [Network.BSC]: { + factoryAddress: '0xA8EF6FEa013034E62E2C4A9Ec1CDb059fE23Af33', + router: '0xaB3699B71e89a53c529eC037C3389B5A2Caf545A', + initCode: LAYER2_INIT_HASH, + subgraphURL: `${GATEWAY_SUBGRAPH}/bsc`, + }, + [Network.POLYGON]: { + factoryAddress: '0x9A1e1681f6D59Ca051776410465AfAda6384398f', + router: '0xedD758D17175Dc9131992ebd02F55Cc4ebeb7B7c', + initCode: LAYER2_INIT_HASH, + subgraphURL: `${GATEWAY_SUBGRAPH}/polygon`, + }, + [Network.BASE]: { + factoryAddress: '0xdd4536dD9636564D891c919416880a3e250f975A', + router: '0xF03D133627364E5eDDaB8134faB3A030cf7b3020', + initCode: LAYER2_INIT_HASH, + subgraphURL: `${GATEWAY_SUBGRAPH}/base`, + }, + }, +}; + +export const Adapters: Record = { + [Network.MAINNET]: { + [SwapSide.SELL]: [{ name: 'Adapter04', index: 6 }], + [SwapSide.BUY]: [{ name: 'BuyAdapter02', index: 2 }], + }, + [Network.ARBITRUM]: { + [SwapSide.SELL]: [{ name: 'ArbitrumAdapter02', index: 9 }], + [SwapSide.BUY]: [{ name: 'ArbitrumBuyAdapter', index: 9 }], + }, + [Network.BSC]: { + [SwapSide.SELL]: [{ name: 'BscAdapter02', index: 8 }], + [SwapSide.BUY]: [{ name: 'BscBuyAdapter', index: 7 }], + }, + [Network.POLYGON]: { + [SwapSide.SELL]: [{ name: 'PolygonAdapter02', index: 9 }], + [SwapSide.BUY]: [{ name: 'PolygonBuyAdapter', index: 8 }], + }, + [Network.BASE]: { + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 8 }], + [SwapSide.BUY]: [{ name: 'BaseBuyAdapter', index: 5 }], + }, +}; diff --git a/src/dex/smardex/constants.ts b/src/dex/smardex/constants.ts new file mode 100644 index 000000000..4ddee903e --- /dev/null +++ b/src/dex/smardex/constants.ts @@ -0,0 +1,35 @@ +// event Sync (uint256 reserve0, uint256 reserve1, uint256 fictiveReserve0, uint256 fictiveReserve1, uint256 priceAverage0, uint256 priceAverage1) +import { SmardexFees } from './types'; + +export enum TOPICS { + SYNC_EVENT = '0x2a368c7f33bb86e2d999940a3989d849031aff29b750f67947e6b8e8c3d2ffd6', + SWAP_EVENT = '0xa4228e1eb11eb9b31069d9ed20e7af9a010ca1a02d4855cee54e08e188fcc32c', + FEES_EVENT = '0x64f84976d9c917a44796104a59950fdbd9b3c16a5dd348b546d738301f6bd068', +} + +export enum SmardexRouterFunctions { + sellExactEth = 'swapExactETHForTokens', + sellExactToken = 'swapExactTokensForETH', + swapExactIn = 'swapExactTokensForTokens', + buyExactEth = 'swapTokensForExactETH', + buyExactToken = 'swapETHForExactTokens', + swapExactOut = 'swapTokensForExactTokens', +} + +export const directSmardexFunctionName = [ + SmardexRouterFunctions.sellExactEth, + SmardexRouterFunctions.sellExactToken, + SmardexRouterFunctions.swapExactIn, + SmardexRouterFunctions.buyExactEth, + SmardexRouterFunctions.buyExactToken, + SmardexRouterFunctions.swapExactOut, +]; + +export const SUBGRAPH_TIMEOUT = 20 * 1000; + +export const DefaultSmardexPoolGasCost = 130 * 1000; + +export const FEES_LAYER_ONE: SmardexFees = { + feesLP: 500n, + feesPool: 200n, +}; diff --git a/src/dex/smardex/sdk/constants.ts b/src/dex/smardex/sdk/constants.ts new file mode 100644 index 000000000..d8d61944f --- /dev/null +++ b/src/dex/smardex/sdk/constants.ts @@ -0,0 +1,10 @@ +// Protocol's fees constants +export const FEES_BASE = 1000000n; + +// constant for function 'updatePriceAverage' +export const LATENCY_OFFSET_SECONDS = 20; + +export enum TradeType { + EXACT_INPUT, + EXACT_OUTPUT, +} diff --git a/src/dex/smardex/sdk/core.ts b/src/dex/smardex/sdk/core.ts new file mode 100644 index 000000000..8dd0ac9bc --- /dev/null +++ b/src/dex/smardex/sdk/core.ts @@ -0,0 +1,832 @@ +import { FEES_BASE, LATENCY_OFFSET_SECONDS } from './constants'; +import { ratioApproxEq, sqrt } from './utils'; +import SmardexError from './errors'; +import type { CurrencyAmount, Pair } from './types'; + +// compute first trade amountIn using arbitrage feature +function computeFirstTradeQtyIn( + amountIn: bigint, + reserveInFic: bigint, + reserveOutFic: bigint, + priceAverageIn: bigint, + priceAverageOut: bigint, + feesLP: bigint, + feesPool: bigint, +): bigint { + // default value + let firstAmountIn = amountIn; + + // if trade is in the good direction + if (reserveOutFic * priceAverageIn > reserveInFic * priceAverageOut) { + // pre-compute all operands + const feesTotalReversed = FEES_BASE - feesLP - feesPool; + const toSub = reserveInFic * (FEES_BASE + feesTotalReversed - feesPool); + const toDiv = (feesTotalReversed + feesLP) * 2n; + const inSqrt = + ((reserveInFic * reserveOutFic * 4n) / priceAverageOut) * + priceAverageIn * + feesTotalReversed * + (FEES_BASE - feesPool) + + reserveInFic * reserveInFic * feesLP * feesLP; + + // reverse sqrt check to only compute sqrt if really needed + if (inSqrt < (amountIn * toDiv + toSub) ** 2n) { + firstAmountIn = (sqrt(inSqrt) - toSub) / toDiv; + } + } + + return firstAmountIn; +} + +// compute first trade amountOut using arbitrage feature +function computeFirstTradeQtyOut( + amountOut: bigint, + reserveInFic: bigint, + reserveOutFic: bigint, + priceAverageIn: bigint, + priceAverageOut: bigint, + feesLP: bigint, + feesPool: bigint, +): bigint { + // default value + let firstAmountOut = amountOut; + + // if trade is in the good direction + if (reserveOutFic * priceAverageIn > reserveInFic * priceAverageOut) { + // pre-compute all operands + const feesTotalReversed = FEES_BASE - feesLP - feesPool; + const reserveOutFicPredictedFees = + (reserveInFic * feesLP * priceAverageOut) / priceAverageIn; + const toAdd = + reserveOutFic * feesTotalReversed * 2n + reserveOutFicPredictedFees; + const toDiv = feesTotalReversed * 2n; + const inSqrt = + (reserveOutFic * + reserveOutFicPredictedFees * + 4n * + feesTotalReversed * + (FEES_BASE - feesPool)) / + feesLP + + reserveOutFicPredictedFees ** 2n; + + // reverse sqrt check to only compute sqrt if really needed + if (inSqrt > (toAdd - amountOut * toDiv) ** 2n) { + firstAmountOut = (toAdd - sqrt(inSqrt)) / toDiv; + } + } + + return firstAmountOut; +} + +// apply uniswap k const rule. amountIn -> amountOut +// return [amountOut, newResIn, newResOut, newResInFic, newResOutFic] +function applyKConstRuleOut( + amountIn: bigint, + reserveIn: bigint, + reserveOut: bigint, + reserveInFic: bigint, + reserveOutFic: bigint, + feesLP: bigint, + feesPool: bigint, +): [bigint, bigint, bigint, bigint, bigint] { + // k const rule + const feesTotalReversed = FEES_BASE - feesLP - feesPool; + const amountInWithFee = amountIn * feesTotalReversed; + const numerator = amountInWithFee * reserveOutFic; + const denominator = reserveInFic * FEES_BASE + amountInWithFee; + + if (denominator === 0n) { + throw new SmardexError('SMARDEX_K_ERROR'); + } + + const amountOut = numerator / denominator; + + // update new reserves and add lp-fees to pools + const amountInWithFeeLp = (amountIn * feesLP + amountInWithFee) / FEES_BASE; + const newResIn = reserveIn + amountInWithFeeLp; + const newResInFic = reserveInFic + amountInWithFeeLp; + const newResOut = reserveOut - amountOut; + const newResOutFic = reserveOutFic - amountOut; + + return [amountOut, newResIn, newResOut, newResInFic, newResOutFic]; +} + +// apply uniswap k const rule. amountOut -> amountIn +// returns [amountIn, newResIn, newResOut, newResInFic, newResOutFic] +function applyKConstRuleIn( + amountOut: bigint, + reserveIn: bigint, + reserveOut: bigint, + reserveInFic: bigint, + reserveOutFic: bigint, + feesLP: bigint, + feesPool: bigint, +): [bigint, bigint, bigint, bigint, bigint] { + // k const rule + const feesTotalReversed = FEES_BASE - feesLP - feesPool; + const numerator = reserveInFic * amountOut * FEES_BASE; + const denominator = (reserveOutFic - amountOut) * feesTotalReversed; + + if (denominator === 0n) { + throw new SmardexError('SMARDEX_K_ERROR'); + } + + const amountIn = numerator / denominator + 1n; + + // update new reserves + const amountInWithFeeLp = + ((feesTotalReversed + feesLP) * amountIn) / FEES_BASE; + const newResIn = reserveIn + amountInWithFeeLp; + const newResInFic = reserveInFic + amountInWithFeeLp; + const newResOut = reserveOut - amountOut; + const newResOutFic = reserveOutFic - amountOut; + + return [amountIn, newResIn, newResOut, newResInFic, newResOutFic]; +} + +/** + * Compute fictive reserves based on current reserves state + * + * @param {bigint} reserveIn the reserves of input token. + * @param {bigint} reserveOut the reserves of output token. + * @param {bigint} reserveInFic the fictive reserves of input token. + * @param {bigint} reserveOutFic the fictive reserves of output token. + * @returns {Array} [ficIn, ficOut] + */ +export function computeReserveFic( + reserveIn: bigint, + reserveOut: bigint, + reserveInFic: bigint, + reserveOutFic: bigint, +): [bigint, bigint] { + if (reserveOut * reserveInFic < reserveIn * reserveOutFic) { + const temp = + (((reserveOut * reserveOut) / reserveOutFic) * reserveInFic) / reserveIn; + const newResFicIn = + (temp * reserveInFic) / reserveOutFic + + (reserveOut * reserveInFic) / reserveOutFic; + const newResFicOut = reserveOut + temp; + + return [newResFicIn / 4n, newResFicOut / 4n]; + } + + const newResFicIn = (reserveInFic * reserveOut) / reserveOutFic + reserveIn; + const newResFicOut = (reserveIn * reserveOutFic) / reserveInFic + reserveOut; + + return [newResFicIn / 4n, newResFicOut / 4n]; +} + +/** + * Simulate a full trasaction, if you know the "in" token quantity, provide you the "out" and all reserves change + * Use case: you want to receive exactly amountOut and want to know the exact amountIn to send. + * @param {bigint} amountIn the desired input amount of the trade. + * @param {bigint} reserveIn the reserves of input token. + * @param {bigint} reserveOut the reserves of output token. + * @param {bigint} reserveInFic the fictive reserves of input token. + * @param {bigint} reserveOutFic the fictive reserves of output token. + * @param {bigint} priceAverageIn the price average of input token. + * @param {bigint} priceAverageOut the price average of output token. + * @param {bigint} feesLP LP fees + * @param {bigint} feesPool Pool fees + * @returns {Array} [amountOut, newResIn, newResOut, newResInFic, newResOutFic] + */ +export function getAmountOut( + amountIn: bigint, + reserveIn: bigint, + reserveOut: bigint, + reserveInFic: bigint, + reserveOutFic: bigint, + priceAverageIn: bigint, + priceAverageOut: bigint, + feesLP: bigint, + feesPool: bigint, +): [bigint, bigint, bigint, bigint, bigint] { + // if (amountIn <= 0n) { + // throw new SmardexError('INSUFFICIENT_INPUT_AMOUNT'); + // } + + if (reserveIn <= 0n || reserveOut <= 0n) { + throw new SmardexError('INSUFFICIENT_LIQUIDITY'); + } + + if (reserveInFic <= 0n || reserveOutFic <= 0n) { + throw new SmardexError('INSUFFICIENT_LIQUIDITY'); + } + + if (priceAverageIn <= 0n || priceAverageOut <= 0n) { + throw new SmardexError('INSUFFICIENT_PRICE_AVERAGE'); + } + + let reserveInFicUpdated = reserveInFic; + let reserveOutFicUpdated = reserveOutFic; + + const feesTotalReversed = FEES_BASE - feesLP - feesPool; + const amountWithFees = (amountIn * feesTotalReversed) / FEES_BASE; + const firstAmount = computeFirstTradeQtyIn( + amountWithFees, + reserveInFic, + reserveOutFic, + priceAverageIn, + priceAverageOut, + feesLP, + feesPool, + ); + + // if there are 2 trades: 1st trade mustn't re-compute ReserveFic, 2nd should + if ( + firstAmount === amountWithFees && + ratioApproxEq(reserveInFic, reserveOutFic, priceAverageIn, priceAverageOut) + ) { + [reserveInFicUpdated, reserveOutFicUpdated] = computeReserveFic( + reserveIn, + reserveOut, + reserveInFic, + reserveOutFic, + ); + } + + // avoid K constant division by 0 + if (reserveInFicUpdated <= 0n) { + return [ + 0n, + reserveIn, + reserveOut, + reserveInFicUpdated, + reserveOutFicUpdated, + ]; + } + + const firstAmountNoFees = (firstAmount * FEES_BASE) / feesTotalReversed; + + let [amountOut, newResIn, newResOut, newResInFic, newResOutFic] = + applyKConstRuleOut( + firstAmountNoFees, + reserveIn, + reserveOut, + reserveInFicUpdated, + reserveOutFicUpdated, + feesLP, + feesPool, + ); + + // if we need a second trade + if (firstAmount < amountWithFees && firstAmountNoFees < amountIn) { + [newResInFic, newResOutFic] = computeReserveFic( + newResIn, + newResOut, + newResInFic, + newResOutFic, + ); + + // Avoid K constant division by 0 + if (newResInFic <= 0n) { + return [ + 0n, + reserveIn, + reserveOut, + reserveInFicUpdated, + reserveOutFicUpdated, + ]; + } + + let secondAmountOutNoFees: bigint; + + [secondAmountOutNoFees, newResIn, newResOut, newResInFic, newResOutFic] = + applyKConstRuleOut( + amountIn - firstAmountNoFees, + newResIn, + newResOut, + newResInFic, + newResOutFic, + feesLP, + feesPool, + ); + + amountOut += secondAmountOutNoFees; + } + + if ( + newResIn <= 0n || + newResOut <= 0n || + newResInFic <= 0n || + newResOutFic <= 0n + ) { + throw new SmardexError('INSUFFICIENT_LIQUIDITY'); + } + + return [amountOut, newResIn, newResOut, newResInFic, newResOutFic]; +} + +/** + * Simulate a full transaction, if you know the "out" token quantity, provide you the "in" and all reserves change + * Use case: you want to receive exactly amountOut and want to know the exact amountIn to send. + * @param {bigint} amountOut the desired output amount of the trade. + * @param {bigint} reserveIn the reserves of input token. + * @param {bigint} reserveOut the reserves of output token. + * @param {bigint} reserveInFic the fictive reserves of input token. + * @param {bigint} reserveOutFic the fictive reserves of output token. + * @param {bigint} priceAverageIn the price average of input token. + * @param {bigint} priceAverageOut the price average of output token. + * @param {bigint} feesLP LP fees + * @param {bigint} feesPool Pool fees + * @returns {Array} [amountIn, newResIn, newResOut, newResInFic, newResOutFic] + */ +export function getAmountIn( + amountOut: bigint, + reserveIn: bigint, + reserveOut: bigint, + reserveInFic: bigint, + reserveOutFic: bigint, + priceAverageIn: bigint, + priceAverageOut: bigint, + feesLP: bigint, + feesPool: bigint, +): [bigint, bigint, bigint, bigint, bigint] { + // if (amountOut <= 0n) { + // throw new SmardexError('INSUFFICIENT_OUTPUT_AMOUNT'); + // } + + if (reserveIn <= 0n || reserveOut <= 0n) { + throw new SmardexError('INSUFFICIENT_LIQUIDITY'); + } + + if (reserveInFic <= 0n || reserveOutFic <= 0n) { + throw new SmardexError('INSUFFICIENT_LIQUIDITY'); + } + + if (priceAverageIn <= 0n || priceAverageOut <= 0n) { + throw new SmardexError('INSUFFICIENT_PRICE_AVERAGE'); + } + + let reserveInFicUpdated = reserveInFic; + let reserveOutFicUpdated = reserveOutFic; + + const firstAmount = computeFirstTradeQtyOut( + amountOut, + reserveInFic, + reserveOutFic, + priceAverageIn, + priceAverageOut, + feesLP, + feesPool, + ); + + // if there are 2 trades: 1st trade mustn't re-compute ReserveFic, 2nd should + if ( + firstAmount === amountOut && + ratioApproxEq(reserveInFic, reserveOutFic, priceAverageIn, priceAverageOut) + ) { + [reserveInFicUpdated, reserveOutFicUpdated] = computeReserveFic( + reserveIn, + reserveOut, + reserveInFic, + reserveOutFic, + ); + } + + if ( + // Avoid K constant division by 0 + reserveInFic <= 0n || + // Avoid finding an amountIn for an exact amountOut that is equal to 0 + amountOut <= 0n + ) { + return [ + BigInt('0'), + reserveIn, + reserveOut, + reserveInFicUpdated, + reserveOutFicUpdated, + ]; + } + + let [amountIn, newResIn, newResOut, newResInFic, newResOutFic] = + applyKConstRuleIn( + firstAmount, + reserveIn, + reserveOut, + reserveInFicUpdated, + reserveOutFicUpdated, + feesLP, + feesPool, + ); + + // if we need a second trade + if (firstAmount < amountOut) { + // in the second trade ALWAYS recompute fictive reserves + [newResInFic, newResOutFic] = computeReserveFic( + newResIn, + newResOut, + newResInFic, + newResOutFic, + ); + + // Avoid K constant division by 0 + if (newResInFic <= 0n) { + return [ + BigInt('0'), + reserveIn, + reserveOut, + reserveInFicUpdated, + reserveOutFicUpdated, + ]; + } + + let secondAmountIn: bigint; + + [secondAmountIn, newResIn, newResOut, newResInFic, newResOutFic] = + applyKConstRuleIn( + amountOut - firstAmount, + newResIn, + newResOut, + newResInFic, + newResOutFic, + feesLP, + feesPool, + ); + + amountIn += secondAmountIn; + } + + if ( + newResIn <= 0n || + newResOut <= 0n || + newResInFic <= 0n || + newResOutFic <= 0n + ) { + throw new SmardexError('INSUFFICIENT_LIQUIDITY'); + } + + return [amountIn, newResIn, newResOut, newResInFic, newResOutFic]; +} + +/** + * Computes the priceAverageIn and priceAverageOut. + * Use case: you want to send an exact amount of tokenIn and know exactly how much you it will give you of tokenOut. + * Price averages are modified only if current timestamp does not match last timestamp + * @param {bigint} reserveFicIn the fictuve reserves of input token. + * @param {bigint} reserveFicOut the fictuve reserves of output token. + * @param {number} priceAverageLastTimestamp last timestamp in seconds of price average values. + * @param {bigint} priceAverageIn the latest price average of input token. + * @param {bigint} priceAverageOut the latest price average of output token. + * @param {number} currentTimestampInSecond current timestamp in seconds. + * @param {number} maxBlockDiffSeconds: Max block difference in seconds + * @returns {Array} [priceAverageIn, priceAverageOut] + */ +export function getUpdatedPriceAverage( + reserveFicIn: bigint, + reserveFicOut: bigint, + priceAverageLastTimestamp: number, + priceAverageIn: bigint, + priceAverageOut: bigint, + currentTimestampInSecond: number, + maxBlockDiffSeconds: number, +): [bigint, bigint] { + if (currentTimestampInSecond < priceAverageLastTimestamp) { + throw new SmardexError('INVALID_TIMESTAMP', 'SmarDexError'); + } + + // very first time + if ( + priceAverageLastTimestamp === 0 || + priceAverageIn === 0n || + priceAverageOut === 0n + ) { + return [reserveFicIn, reserveFicOut]; + } + + // another tx has been done in the same block + if (priceAverageLastTimestamp === currentTimestampInSecond) { + return [priceAverageIn, priceAverageOut]; + } + + // need to compute new linear-average price + // compute new price: + const timeDiff = Math.min( + currentTimestampInSecond - priceAverageLastTimestamp, + maxBlockDiffSeconds, + ); + + const priceAverageInRet = reserveFicIn; + const priceAverageOutRet = + ((priceAverageOut * + priceAverageInRet * + BigInt(maxBlockDiffSeconds - timeDiff)) / + priceAverageIn + + reserveFicOut * BigInt(timeDiff)) / + BigInt(maxBlockDiffSeconds); + + return [priceAverageInRet, priceAverageOutRet]; +} + +/** + * Computes the amount of tokenOut, at the precision of 1 wei. + * Use case: you want to send an exact amount of tokenIn and know exactly how much you it will give you of tokenOut. + * WARNING: token0 and token1 are pair tokens which addresses hexadecimal's values are sorted as token0 < token1. + * @param {string} token0 the currency address of token0. + * @param {string} token1 the currency address of token1. + * @param {bigint} reserve0 the reserves of token0. + * @param {bigint} reserve1 the reserves of token1. + * @param {bigint} reserve0Fic the fictionnal reserves of token0. + * @param {bigint} reserve1Fic the fictionnal reserves of token1. + * @param {bigint} tokenAmountIn the input amount of the trade. + * @param {bigint} tokenAddressIn address of the input token. + * @param {number} priceAverageLastTimestamp: timestamp in seconds of the latest price average. + * @param {bigint} priceAverage0 latest price average of token0. + * @param {bigint} priceAverage1 latest price average of token1. + * @param {bigint} feesLP LP fees + * @param {bigint} feesPool Pool fees + * @param {number} forcedPriceAverageTimestamp: current timestamp or timestamp of the trade in seconds. + * @param {number} maxBlockDiffSeconds: Max block difference in seconds + * @returns {Object} { currency, amount, amountMax, newResIn, newResOut, newResInFic, newResOutFic } + */ +export function computeAmountOut( + token0: string, + token1: string, + reserve0: bigint, + reserve1: bigint, + reserve0Fic: bigint, + reserve1Fic: bigint, + tokenAmountIn: bigint, + tokenAddressIn: string, + priceAverageLastTimestamp: number, + priceAverage0: bigint, + priceAverage1: bigint, + feesLP: bigint, + feesPool: bigint, + forcedPriceAverageTimestamp: number = Math.ceil(Date.now() / 1000) + + LATENCY_OFFSET_SECONDS, + maxBlockDiffSeconds = 300, +): CurrencyAmount { + if (tokenAddressIn === token0) { + const [newPriceAverage0, newPriceAverage1] = getUpdatedPriceAverage( + reserve0Fic, + reserve1Fic, + priceAverageLastTimestamp, + priceAverage0, + priceAverage1, + forcedPriceAverageTimestamp, + maxBlockDiffSeconds, + ); + + const [amountOut, newRes0, newRes1, newRes0Fic, newRes1Fic] = getAmountOut( + tokenAmountIn, + reserve0, + reserve1, + reserve0Fic, + reserve1Fic, + newPriceAverage0, + newPriceAverage1, + feesLP, + feesPool, + ); + // const [amountMax] = getAmountOut( + // tokenAmountIn, + // reserve0, + // reserve1, + // reserve0Fic.sub(1).lt(0) ? reserve0Fic : reserve0Fic.sub(1), + // reserve1Fic, + // newPriceAverage0, + // newPriceAverage1, + // ); + + return { + currency: token1, + amount: amountOut, + amountMax: amountOut, // TODO is it still useful ? + newRes0, + newRes1, + newRes0Fic, + newRes1Fic, + newPriceAverage0, + newPriceAverage1, + }; + } + + // token1 is tokenIn + const [newPriceAverage1, newPriceAverage0] = getUpdatedPriceAverage( + reserve1Fic, + reserve0Fic, + priceAverageLastTimestamp, + priceAverage1, + priceAverage0, + forcedPriceAverageTimestamp, + maxBlockDiffSeconds, + ); + + const [amountOut, newRes1, newRes0, newRes1Fic, newRes0Fic] = getAmountOut( + tokenAmountIn, + reserve1, + reserve0, + reserve1Fic, + reserve0Fic, + newPriceAverage1, + newPriceAverage0, + feesLP, + feesPool, + ); + // const [amountMax] = getAmountOut( + // tokenAmountIn, + // reserve1, + // reserve0, + // reserve1Fic.sub(1).lt(0) ? reserve1Fic : reserve1Fic.sub(1), + // reserve0Fic, + // newPriceAverage1, + // newPriceAverage0, + // ); + + return { + currency: token0, + amount: amountOut, + amountMax: amountOut, // TODO is it still useful ? + newRes0, + newRes1, + newRes0Fic, + newRes1Fic, + newPriceAverage0, + newPriceAverage1, + forcedPriceAverageTimestamp, + }; +} + +/** + * Computes the amount of tokenIn, at the precision of 1 wei. + * Use case: you want to receive exactly tokenOut amount and want to know the exact tokenIn amount to send. + * WARNING: token0 and token1 are pair tokens which addresses hexadecimal's values are sorted as token0 < token1. + * @param {string} token0 the currency address of token0. + * @param {string} token1 the currency address of token1. + * @param {bigint} reserve0 the reserves of token0. + * @param {bigint} reserve1 the reserves of token1. + * @param {bigint} reserve0Fic the fictionnal reserves of token0. + * @param {bigint} reserve1Fic the fictionnal reserves of token1. + * @param {bigint} tokenAmountOut the output amount of the trade. + * @param {bigint} tokenAddressOut address of the output token. + * @param {number} priceAverageLastTimestamp timestamp in seconds of the latest price average. + * @param {bigint} priceAverage0 latest price average of token0. + * @param {bigint} priceAverage1 latest price average of token1. + * @param {bigint} feesLP LP fees + * @param {bigint} feesPool Pool fees + * @param {number} forcedPriceAverageTimestamp current timestamp or timestamp of the trade in seconds. + * @param {number} maxBlockDiffSeconds: Max block difference in seconds + * @returns {Object} { currency, amount, amountMax, newResIn, newResOut, newResInFic, newResOutFic } + */ +export function computeAmountIn( + token0: string, + token1: string, + reserve0: bigint, + reserve1: bigint, + reserve0Fic: bigint, + reserve1Fic: bigint, + tokenAmountOut: bigint, + tokenAddressOut: string, + priceAverageLastTimestamp: number, + priceAverage0: bigint, + priceAverage1: bigint, + feesLP: bigint, + feesPool: bigint, + forcedPriceAverageTimestamp: number = Math.ceil(Date.now() / 1000) + + LATENCY_OFFSET_SECONDS, + maxBlockDiffSeconds = 300, +): CurrencyAmount { + if (tokenAddressOut === token0) { + const [newPriceAverage1, newPriceAverage0] = getUpdatedPriceAverage( + reserve1Fic, + reserve0Fic, + priceAverageLastTimestamp, + priceAverage1, + priceAverage0, + forcedPriceAverageTimestamp, + maxBlockDiffSeconds, + ); + + const [amountIn, newRes1, newRes0, newRes1Fic, newRes0Fic] = getAmountIn( + tokenAmountOut, + reserve1, + reserve0, + reserve1Fic, + reserve0Fic, + newPriceAverage1, + newPriceAverage0, + feesLP, + feesPool, + ); + // const [amountMax] = getAmountIn( + // tokenAmountOut, + // reserve1, + // reserve0, + // reserve1Fic, + // reserve0Fic.sub(1).lt(0) ? reserve0Fic : reserve0Fic.sub(1), + // newPriceAverage1, + // newPriceAverage0, + // ); + + return { + currency: token1, + amount: amountIn, + amountMax: amountIn, // TODO is it still useful ? + newRes0, + newRes1, + newRes0Fic, + newRes1Fic, + newPriceAverage0, + newPriceAverage1, + }; + } + + // token1 is tokenOut + const [newPriceAverage0, newPriceAverage1] = getUpdatedPriceAverage( + reserve0Fic, + reserve1Fic, + priceAverageLastTimestamp, + priceAverage0, + priceAverage1, + forcedPriceAverageTimestamp, + maxBlockDiffSeconds, + ); + + const [amountIn, newRes0, newRes1, newRes0Fic, newRes1Fic] = getAmountIn( + tokenAmountOut, + reserve0, + reserve1, + reserve0Fic, + reserve1Fic, + newPriceAverage0, + newPriceAverage1, + feesLP, + feesPool, + ); + + // const [amountMax] = getAmountIn( + // tokenAmountOut, + // reserve0, + // reserve1, + // reserve0Fic, + // reserve1Fic.sub(1).lt(0) ? reserve1Fic : reserve1Fic.sub(1), + // newPriceAverage0, + // newPriceAverage1, + // ); + + return { + currency: token0, + amount: amountIn, + amountMax: amountIn, // TODO is it still useful ? + newRes0, + newRes1, + newRes0Fic, + newRes1Fic, + newPriceAverage0, + newPriceAverage1, + forcedPriceAverageTimestamp, + }; +} + +/** + * Extracts the token addresses composing the route ordered in the route's direction starting with inputCurrency. + * @param {Pair[]} pairs array of pairs composing the trade. + * @param {string} inputCurrency the currency from which the route starts. + * @return {string[]} Array of token addresses composing the route ordered in the route's direction starting with inputCurrency. + */ +export function getPathFromInput( + pairs: Pair[], + inputCurrency: string, +): string[] { + const path: string[] = []; + for (let i = 0; i < pairs.length; i += 1) { + const pairCurrencyIn = + path.length === 0 ? inputCurrency : path[path.length - 1]; + const [tokenIn, tokenOut] = + pairCurrencyIn === pairs[i].token0 + ? [pairs[i].token0, pairs[i].token1] + : [pairs[i].token1, pairs[i].token0]; + if (path.length === 0) { + path.push(tokenIn); + } + path.push(tokenOut); + } + return path; +} + +/** + * Extracts the token addresses composing the route ordered in the route's direction ending with outputCurrency. + * @param {Pair[]} pairs array of pairs composing the trade. + * @param {string} outputCurrency the currency for which the route finishes. + * @return {string[]} Array of token addresses composing the route ordered in the route's direction ending with outputCurrency. + */ +export function getPathFromOutput( + pairs: Pair[], + outputCurrency: string, +): string[] { + const path: string[] = []; + for (let i = pairs.length - 1; i >= 0; i -= 1) { + const pairCurrencyOut = + path.length === 0 ? outputCurrency : path[path.length - 1]; + const [tokenOut, tokenIn] = + pairCurrencyOut === pairs[i].token0 + ? [pairs[i].token0, pairs[i].token1] + : [pairs[i].token1, pairs[i].token0]; + if (path.length === 0) { + path.push(tokenOut); + } + path.push(tokenIn); + } + return path.reverse(); +} diff --git a/src/dex/smardex/sdk/errors.ts b/src/dex/smardex/sdk/errors.ts new file mode 100644 index 000000000..7898fa965 --- /dev/null +++ b/src/dex/smardex/sdk/errors.ts @@ -0,0 +1,18 @@ +/** + * SmardexError class + * + * @class SmardexError extends Error + */ +export default class SmardexError extends Error { + /** + * Create SmardexError object + * + * @param {string} message - error message + * @param {string} [errorName=SmarDexSDK] - identifier for the error + * @returns {SmardexError} SmarDex Error object + */ + constructor(message: string, errorName = 'SmarDexSDK') { + super(message); + this.name = errorName; + } +} diff --git a/src/dex/smardex/sdk/types.ts b/src/dex/smardex/sdk/types.ts new file mode 100644 index 000000000..2a91c164e --- /dev/null +++ b/src/dex/smardex/sdk/types.ts @@ -0,0 +1,68 @@ +import { TradeType } from './constants'; + +export interface Pair { + address?: string; + token0: string; + token1: string; + reserve0: bigint; + reserve1: bigint; + reserve0LastFictive: bigint; + reserve1LastFictive: bigint; + priceAverageLastTimestamp: number; + priceAverage0: bigint; + priceAverage1: bigint; + forcedPriceAverageTimestamp?: number; + prevReserveFic0?: bigint; + prevReserveFic1?: bigint; + feesLP: bigint; + feesPool: bigint; +} + +export interface CurrencyAmount { + currency: string; + amount: bigint; + amountMax?: bigint; + newRes0?: bigint; + newRes1?: bigint; + newRes0Fic?: bigint; + newRes1Fic?: bigint; + newPriceAverage0?: bigint; + newPriceAverage1?: bigint; + forcedPriceAverageTimestamp?: number; +} + +export interface BestTradeOptions { + maxNumResults?: number; // how many results to return + maxHops?: number; // the maximum number of hops for the swap + arbitrage?: boolean; // consider arbitrage loops or not +} + +export interface Route { + pairs: Pair[]; + path: string[]; + input: string; + output: string; +} + +export interface Trade { + route: Route; + amountIn: CurrencyAmount; + amountOut: CurrencyAmount; + tradeType: TradeType; + priceImpact?: bigint; + gasFeesUSD?: bigint; + amountInUSD?: bigint; + amountOutUSD?: bigint; +} + +export interface GasEstimateData { + gasPrice: bigint; + gasQuantitiesFirstHop: number; + gasQuantitiesAdditionalHop: number; + nativeTokenPrice: bigint; + nativeTokenDecimals: number; + inputTokenPrice: bigint; + inputTokenDecimals: number; + outputTokenPrice: bigint; + outputTokenDecimals: number; +} diff --git a/src/dex/smardex/sdk/utils.ts b/src/dex/smardex/sdk/utils.ts new file mode 100644 index 000000000..c8627b5f0 --- /dev/null +++ b/src/dex/smardex/sdk/utils.ts @@ -0,0 +1,156 @@ +import { parseUnits } from 'ethers/lib/utils'; + +import { CurrencyAmount, Trade } from './types'; + +// Constants to compute approximate equality +const APPROX_EQ_PRECISION = 1n; +const APPROX_EQ_BASE_PRECISION = 1000000n; + +/** + * Computes logarithm in base 2 for a given positive number. + * Result is rounded down. + * + * @param {bigint} value - value to compute the log2 + * @returns {bigint} the log in base 2 of the value, 0 if given 0. + */ +/* eslint-disable no-param-reassign */ +export function log2(value: bigint): bigint { + let result = 0n; + + if (value >> 128n > 0n) { + value >>= 128n; + result += 128n; + } + + if (value >> 64n > 0n) { + value >>= 64n; + result += 64n; + } + + if (value >> 32n > 0n) { + value >>= 32n; + result += 32n; + } + + if (value >> 16n > 0n) { + value >>= 16n; + result += 16n; + } + + if (value >> 8n > 0n) { + value >>= 8n; + result += 8n; + } + + if (value >> 4n > 0n) { + value >>= 4n; + result += 4n; + } + + if (value >> 2n > 0n) { + value >>= 2n; + result += 2n; + } + + if (value >> 1n > 0n) { + result += 1n; + } + + return result; +} + +/** + * Computes the square root of a number. If the number is not a perfect square, the value is rounded down. + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + * + * @param {bigint} value - value to compute the square root + * @returns {bigint} the square root of the value + */ +export function sqrt(value: bigint): bigint { + if (value === 0n) { + return 0n; + } + + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. + // + // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` + // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` + // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` + // + // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. + let result = 1n << (log2(value) / 2n); + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision + // into the expected uint128 result. + result = (result + value / result) >> 1n; + result = (result + value / result) >> 1n; + result = (result + value / result) >> 1n; + result = (result + value / result) >> 1n; + result = (result + value / result) >> 1n; + result = (result + value / result) >> 1n; + result = (result + value / result) >> 1n; + + return result < value / result ? result : value / result; +} + +/** + * Evaluates the equality of two numbers at a precision of 1/1_000_000 + * + * @param {bigint} x - value to compare + * @param {bigint} y - value to compare + * @returns {boolean} true if numbers are approximatively equal at 1/1_000_000, false otherwise + */ +export function approxEq(x: bigint, y: bigint): true | false { + return x > y + ? x < y + (y * APPROX_EQ_PRECISION) / APPROX_EQ_BASE_PRECISION + : y < x + (x * APPROX_EQ_PRECISION) / APPROX_EQ_BASE_PRECISION; +} + +/** + * Evaluates the equality of two ratio numbers at a precision of 1/1_000_000. xNum / xDen ~= yNum / yDen + * + * @param {bigint} _xNum - first number numerator + * @param {bigint} _xDen - first number denominator + * @param {bigint} _yNum - second number numerator + * @param {bigint} _yDen - second number denominator + * @returns {boolean} true if the two ratios are approximatively equal at 1/1_000_000, false otherwise + */ +export function ratioApproxEq( + _xNum: bigint, + _xDen: bigint, + _yNum: bigint, + _yDen: bigint, +): true | false { + return approxEq(_xNum * _yDen, _xDen * _yNum); +} + +/** + * Computes the ratio of two numbers + * + * @param {bigint} numerator - numerator number + * @param {bigint} denominator - denominator number + * @param {number} decimals - decimals + * @returns {bigint} ratio of the two numbers. returns 0 if denominator is 0 + */ +export function priceRatio( + numerator: bigint, + denominator: bigint, + decimals = 18, +) { + if (denominator === 0n) { + return 0n; + } + + return ( + BigInt(parseUnits(numerator.toString(), decimals).toString()) / denominator + ); +} + +export function abs(value: bigint) { + return value === -0n || value < 0n ? -value : value; +} diff --git a/src/dex/smardex/smardex-e2e.test.ts b/src/dex/smardex/smardex-e2e.test.ts new file mode 100644 index 000000000..79fb2a821 --- /dev/null +++ b/src/dex/smardex/smardex-e2e.test.ts @@ -0,0 +1,275 @@ +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'; + +function testForNetwork( + network: Network, + dexKey: string, + pairs: { name: string; sellAmount: string; buyAmount: string }[][], +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + 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('SmarDex E2E', () => { + const dexKey = 'SmarDex'; + + describe('MAINNET', () => { + const network = Network.MAINNET; + + const pairs = [ + [ + { + name: 'USDT', + sellAmount: '1200000000', + buyAmount: '120000', + }, + { + name: 'SDEX', + sellAmount: '300000000000000000000000', + buyAmount: '30000000000', + }, + ], + [ + { + name: 'SDEX', + sellAmount: '300000000000000000000000', + buyAmount: '30000000000', + }, + { + name: 'ETH', + sellAmount: '11000000000000000', + buyAmount: '1100000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); + + describe('ARBITRUM', () => { + const network = Network.ARBITRUM; + + const pairs = [ + [ + { + name: 'USDC', + sellAmount: '1200000000', + buyAmount: '120000', + }, + { + name: 'SDEX', + sellAmount: '300000000000000000000000', + buyAmount: '30000000000', + }, + ], + [ + { + name: 'SDEX', + sellAmount: '300000000000000000000000', + buyAmount: '11000000000000000', + }, + { + name: 'ETH', + sellAmount: '11000000000000000', + buyAmount: '11000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); + + describe('BSC', () => { + const network = Network.BSC; + + const pairs = [ + [ + { + name: 'USDT', + sellAmount: '1200000000000000000000', + buyAmount: '300000000000000000000000', + }, + { + name: 'SDEX', + sellAmount: '300000000000000000000000', + buyAmount: '120000000000000000000', + }, + ], + [ + { + name: 'SDEX', + sellAmount: '300000000000000000000000', + buyAmount: '50000000000000000', + }, + { + name: 'bBTC', + sellAmount: '50000000000000000', + buyAmount: '300000000000000000000000', + }, + ], + [ + { + name: 'USDT', + sellAmount: '1500000000000000000000', + buyAmount: '20000000000000000000', + }, + { + name: 'BNB', + sellAmount: '20000000000000000000', + buyAmount: '1500000000000000000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); + + describe('POLYGON', () => { + const network = Network.POLYGON; + + const pairs = [ + [ + { + name: 'WETH', + sellAmount: '1500000000000000000', + buyAmount: '250000000000000000000000', + }, + { + name: 'SDEX', + sellAmount: '250000000000000000000000', + buyAmount: '1500000000000000000', + }, + ], + [ + { + name: 'USDC', + sellAmount: '2500000000', + buyAmount: '300000000000000000000000', + }, + { + name: 'SDEX', + sellAmount: '300000000000000000000000', + buyAmount: '2500000000', + }, + ], + [ + { + name: 'USDC', + sellAmount: '2500000000', + buyAmount: '8000000000000000000000', + }, + { + name: 'MATIC', + sellAmount: '8000000000000000000000', + buyAmount: '2500000000', + }, + ], + [ + { + name: 'SDEX', + sellAmount: '190000000000000000000000', + buyAmount: '8000000', + }, + { + name: 'WBTC', + sellAmount: '8000000', + buyAmount: '190000000000000000000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); + + describe('BASE', () => { + const network = Network.BASE; + + const pairs = [ + [ + { + name: 'WETH', + sellAmount: '1500000000000000000', + buyAmount: '2500000000', + }, + { + name: 'USDbC', + sellAmount: '2500000000', + buyAmount: '1500000000000000000', + }, + ], + [ + { + name: 'WETH', + sellAmount: '1500000000000000000', + buyAmount: '250000000000000000000000', + }, + { + name: 'SDEX', + sellAmount: '250000000000000000000000', + buyAmount: '1500000000000000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); +}); diff --git a/src/dex/smardex/smardex-event-pool.ts b/src/dex/smardex/smardex-event-pool.ts new file mode 100644 index 000000000..5be86ca39 --- /dev/null +++ b/src/dex/smardex/smardex-event-pool.ts @@ -0,0 +1,164 @@ +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { SmardexFees, SmardexPoolState } from './types'; +import { AbiCoder, Interface } from '@ethersproject/abi'; +import { IDexHelper } from '../../dex-helper'; +import { Address, Log, Logger, Token } from '../../types'; +import { DeepReadonly } from 'ts-essentials'; +import { FEES_LAYER_ONE, TOPICS } from './constants'; + +const coder = new AbiCoder(); + +export class SmardexEventPool extends StatefulEventSubscriber { + constructor( + protected poolInterface: Interface, + protected dexHelper: IDexHelper, + public poolAddress: Address, + token0: Token, + token1: Token, + logger: Logger, + protected smardexFeesMultiCallEntry?: { + target: string; + callData: string; + }, + protected smardexFeesMulticallDecoder?: (values: any[]) => SmardexFees, + private isLayerOne = true, + ) { + super('Smardex', `${token0.address}_${token1.address}`, dexHelper, logger); + } + + async fetchPairFeesAndLastTimestamp(blockNumber: number): Promise<{ + feesLP: bigint; + feesPool: bigint; + priceAverageLastTimestamp: number; + }> { + let calldata = [ + { + target: this.poolAddress, + callData: this.poolInterface.encodeFunctionData('getPriceAverage', []), + }, + ]; + if (!this.isLayerOne) { + calldata.push(this.smardexFeesMultiCallEntry!); + } + + const data: { returnData: any[] } = + await this.dexHelper.multiContract.methods + .aggregate(calldata) + .call({}, blockNumber); + + const priceAverageLastTimestamp = coder.decode( + ['uint256', 'uint256', 'uint256'], + data.returnData[0], + )[2]; + + const fees = this.isLayerOne + ? FEES_LAYER_ONE + : this.smardexFeesMulticallDecoder!(data.returnData[1]); + return { + feesLP: fees.feesLP, + feesPool: fees.feesPool, + priceAverageLastTimestamp: priceAverageLastTimestamp.toNumber(), + }; + } + + // This methode overrides previous state with new state. + // Problem: Smardex Pair state is updated partially on different events + // This is why we must fetch pair's missing state in Events + protected async processLog( + state: DeepReadonly, + log: Readonly, + ): Promise | null> { + if (!Object.values(TOPICS).includes(log.topics[0] as TOPICS)) return null; + const event = this.poolInterface.parseLog(log); + switch (event.name) { + case 'Sync': + const fetchedSync = await this.fetchPairFeesAndLastTimestamp( + log.blockNumber, + ); + return { + reserves0: event.args.reserve0.toString(), + reserves1: event.args.reserve1.toString(), + fictiveReserves0: event.args.fictiveReserve0.toString(), + fictiveReserves1: event.args.fictiveReserve1.toString(), + priceAverage0: event.args.priceAverage0.toString(), + priceAverage1: event.args.priceAverage1.toString(), + priceAverageLastTimestamp: fetchedSync.priceAverageLastTimestamp, + feesLP: fetchedSync.feesLP, + feesPool: fetchedSync.feesPool, + }; + case 'FeesChanged': // only triggerd on L2 + return { + reserves0: state.reserves0, + reserves1: state.reserves1, + fictiveReserves0: state.fictiveReserves0, + fictiveReserves1: state.fictiveReserves1, + priceAverage0: state.priceAverage0, + priceAverage1: state.priceAverage1, + priceAverageLastTimestamp: state.priceAverageLastTimestamp, + feesLP: BigInt(event.args.feesLP), + feesPool: BigInt(event.args.feesPool), + }; + } + return null; + } + + async generateState( + blockNumber: number | 'latest' = 'latest', + ): Promise> { + const coder = new AbiCoder(); + let calldata = [ + { + target: this.poolAddress, + callData: this.poolInterface.encodeFunctionData('getReserves', []), + }, + { + target: this.poolAddress, + callData: this.poolInterface.encodeFunctionData( + 'getFictiveReserves', + [], + ), + }, + { + target: this.poolAddress, + callData: this.poolInterface.encodeFunctionData('getPriceAverage', []), + }, + ]; + if (!this.isLayerOne) { + calldata.push(this.smardexFeesMultiCallEntry!); + } + + const data: { returnData: any[] } = + await this.dexHelper.multiContract.methods + .aggregate(calldata) + .call({}, blockNumber); + + const [reserves0, reserves1] = coder.decode( + ['uint256', 'uint256'], + data.returnData[0], + ); + + const [fictiveReserve0, fictiveReserve1] = coder.decode( + ['uint256', 'uint256'], + data.returnData[1], + ); + + const [priceAverage0, priceAverage1, priceAverageLastTimestamp] = + coder.decode(['uint256', 'uint256', 'uint256'], data.returnData[2]); + + const fees = this.isLayerOne + ? FEES_LAYER_ONE + : this.smardexFeesMulticallDecoder!(data.returnData[3]); + + return { + reserves0: reserves0.toString(), + reserves1: reserves1.toString(), + fictiveReserves0: fictiveReserve0.toString(), + fictiveReserves1: fictiveReserve1.toString(), + priceAverage0: priceAverage0.toString(), + priceAverage1: priceAverage1.toString(), + priceAverageLastTimestamp: priceAverageLastTimestamp.toNumber(), + feesLP: fees.feesLP, + feesPool: fees.feesPool, + }; + } +} diff --git a/src/dex/smardex/smardex-events.test.ts b/src/dex/smardex/smardex-events.test.ts new file mode 100644 index 000000000..5d758584f --- /dev/null +++ b/src/dex/smardex/smardex-events.test.ts @@ -0,0 +1,297 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +import { Interface } from '@ethersproject/abi'; +import { Tokens } from '../../../tests/constants-e2e'; +dotenv.config(); + +import SmardexPoolLayerOneABI from '../../abi/smardex/layer-1/smardex-pool.json'; +import SmardexPoolLayerTwoABI from '../../abi/smardex/layer-2/smardex-pool.json'; +import { Smardex } from './smardex'; +import { SmardexEventPool } from './smardex-event-pool'; +import { Network } from '../../constants'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { testEventSubscriber } from '../../../tests/utils-events'; +import { SmardexPoolState } from './types'; + +jest.setTimeout(120 * 1000); +const dexKey = 'Smardex'; + +type NetworkConfig = { + name: 'ethereum' | 'polygon' | 'bsc' | 'arbitrum' | 'base'; + network: Network; + tokens: typeof Tokens[Network]; + pools: Array<{ + address: string; + symbol0: string; + symbol1: string; + events: { + [event: string]: number[]; + }; + }>; +}; + +const NETWORK_CONFIGS: NetworkConfig[] = [ + { + name: 'ethereum', + network: Network.MAINNET, + tokens: Tokens[Network.MAINNET], + pools: [ + { + address: '0xd2bf378cea07fe117ffdfd3f5b7e53c2b0b78c05', + symbol0: 'SDEX', + symbol1: 'USDT', + events: { + ['Swap']: [ + 18064045, + 18064060, + 18064194, + 18065266, + 18066464, // the last one contains multiple Swap events + ], + ['Burn']: [17231921, 17762042, 17762668], + ['Mint']: [17739609, 17973926, 18062443], + // ['FeesChanged']: [], // none on L1 + ['Sync']: [ + 18064045, + 18064060, + 18064194, + 18065266, + 18066464, // the last one contains multiple Sync events + ], + ['Transfer']: [18064025, 18064045, 18065266], + }, + }, + + { + address: '0xf3a4B8eFe3e3049F6BC71B47ccB7Ce6665420179', + symbol0: 'SDEX', + symbol1: 'ETH', + events: { + ['Swap']: [ + 18120396, + 18120367, // multiple Swap events + 18119305, + 18118991, + 18120209, + ], + ['Burn']: [18114357, 18089610, 18026115], + ['Mint']: [18115825, 18109975, 18093408], + // ['FeesChanged']: [], // none on L1 + ['Sync']: [ + 18120396, + 18120367, // multiple Sync events + 18119305, + 18118991, + 18120209, + ], + ['Transfer']: [18108643, 18105778, 18105793], + }, + }, + ], + }, + { + name: 'polygon', + network: Network.POLYGON, + tokens: Tokens[Network.POLYGON], + pools: [ + { + address: '0x77476148B72ECB4E9f6A3cDDC5dd437Df1F003F3', + symbol0: 'USDC', + symbol1: 'SDEX', + events: { + ['Swap']: [47436997, 47437089, 47437096, 47445056, 47445066], + ['Burn']: [47423778, 47334674, 46807911], + ['Mint']: [47417315, 47414686, 47288517], + ['FeesChanged']: [45861051], + ['Sync']: [47436997, 47437089, 47437096, 47445056, 47445066], + ['Transfer']: [47423778, 47403369, 47343062], + }, + }, + { + address: '0x7130d1Ab6d9C657240331A4DE3e88b5497Be9cEB', + symbol0: 'WMATIC', + symbol1: 'USDC', + events: { + ['Swap']: [47448993, 47449888, 47449861, 47448993, 47449214], + ['Burn']: [47434291, 47433030, 47426957], + ['Mint']: [47354112, 47268128, 47252146], + ['FeesChanged']: [], // Fees didn't change on this pool + ['Sync']: [47448993, 47449888, 47449861, 47448993, 47449214], + ['Transfer']: [47338250, 47338178, 47333469], + }, + }, + ], + }, + { + name: 'arbitrum', + network: Network.ARBITRUM, + tokens: Tokens[Network.ARBITRUM], + pools: [ + { + address: '0xacCDd9EE1DCd393E346e5d6350230439b4DA09ab', + symbol0: 'SDEX', + symbol1: 'USDC', + events: { + ['Swap']: [130424308, 130422563, 130418994, 130415253, 130382808], + ['Burn']: [129969327, 129138853, 126200497], + ['Mint']: [130028302, 129940404, 129728771], + ['FeesChanged']: [], // Fees didn't change on this pool + ['Sync']: [130424308, 130422563, 130418994, 130415253, 130382808], + ['Transfer']: [130028373, 129918802, 129668897], + }, + }, + { + address: '0xD87899d10Eaa10F3adE05038A38251F758E5C0eb', + symbol0: 'ARB', + symbol1: 'USDC', + events: { + ['Swap']: [130429535, 130419879, 130417306, 130414469, 130416990], + ['Burn']: [130200966, 127933595, 125698828], + ['Mint']: [130263032, 130331095, 128594710], + ['FeesChanged']: [], // Fees didn't change on this pool + ['Sync']: [130429535, 130419879, 130417306, 130414469, 130416990], + ['Transfer']: [130423567, 130423514, 130331095], + }, + }, + ], + }, + { + name: 'bsc', + network: Network.BSC, + tokens: Tokens[Network.BSC], + pools: [ + { + address: '0xe7b89CbD4E833510F393CCfbE7D433EDbb137aB2', + symbol0: 'USDT', + symbol1: 'SDEX', + events: { + ['Swap']: [31678152, 31678000, 31674992, 31670581, 31675013], + ['Burn']: [31660547, 31655719, 31539131], + ['Mint']: [31652485, 31595468, 31571640], + ['FeesChanged']: [30531399], + ['Sync']: [31678152, 31678000, 31674992, 31670581, 31675013], + ['Transfer']: [31660547, 31655777, 31655736], + }, + }, + { + address: '0xf315833053a1187fd5e9813E38BD59937492857a', + symbol0: 'USDT', + symbol1: 'BNB', + events: { + ['Swap']: [31678534, 31675409, 31678534, 31675014, 31674391], + ['Burn']: [31666734, 31586726, 31462652], + ['Mint']: [31662048, 31554796, 31524123], + ['FeesChanged']: [], // Fees didn't change on this pool + ['Sync']: [31678534, 31675409, 31678534, 31675014, 31674391], + ['Transfer']: [31666734, 31632200, 31586970], + }, + }, + ], + }, + { + name: 'base', + network: Network.BASE, + tokens: Tokens[Network.BASE], + pools: [ + { + address: '0xd70e1bab713d84c3a110ded11e41714542e604ba', + symbol0: 'WETH', + symbol1: 'SDEX', + events: { + ['Swap']: [4761865, 4757945, 4757945, 4757897], + ['Burn']: [4692541], + ['Mint']: [4757945, 4737306, 4701199], + ['FeesChanged']: [], // Fees didn't change on this pool + ['Sync']: [4761865, 4757945, 4757945, 4757897], + ['Transfer']: [4757966, 4737315, 4736210], + }, + }, + { + address: '0x5a60c797993ee91f012260d995e1e6c6ce3dda6d', + symbol0: 'WETH', + symbol1: 'USDbC', + events: { + ['Swap']: [4763447, 4762313, 4762290], + ['Burn']: [4716925, 4338322, 4282294], + ['Mint']: [4758649, 4649129, 4645429], + ['FeesChanged']: [], // Fees didn't change on this pool + ['Sync']: [4763447, 4762313, 4762290], + ['Transfer']: [4758649, 4716850, 4645429], + }, + }, + ], + }, +]; + +async function fetchPoolStateFromContractAtBlock( + smardexEventPool: SmardexEventPool, + blockNumber: number, + poolAddress: string, + logger: any, +): Promise { + const message = `Smardex: ${poolAddress} blockNumber ${blockNumber}`; + console.log(`Fetching state ${message}`); + + const state = await smardexEventPool.generateState(blockNumber); + console.log(`Done ${message}`); + + return state; +} + +NETWORK_CONFIGS.forEach(({ name, network, pools, tokens }) => { + describe(`Events Tests on ${name}`, function () { + pools.forEach(({ address, events, symbol0, symbol1 }) => { + describe(`${symbol0} <> ${symbol1}`, function () { + const poolAddress = address; + const token0 = tokens[symbol0]; + const token1 = tokens[symbol1]; + // events in the same block must update the same element + Object.keys(events).forEach((event: string) => { + events[event].forEach((blockNumber: number) => { + it(`${event}:${blockNumber} - should return correct state`, async function () { + const dexHelper = new DummyDexHelper(network); + + const logger = dexHelper.getLogger(dexKey); + const smardex = new Smardex(network, dexKey, dexHelper); + const multicall = smardex.getFeesMultiCallData(poolAddress); + const SmardexPool = new SmardexEventPool( + new Interface( + smardex.isLayer1() + ? SmardexPoolLayerOneABI + : SmardexPoolLayerTwoABI, + ), + dexHelper, + poolAddress, + token0, + token1, + logger, + multicall?.callEntry, + multicall?.callDecoder, + smardex.isLayer1(), + ); + + // It is done in generateState. But here have to make it manually + SmardexPool.poolAddress = poolAddress.toLowerCase(); + SmardexPool.addressesSubscribed[0] = poolAddress.toLowerCase(); + + await testEventSubscriber( + SmardexPool, + SmardexPool.addressesSubscribed, + (_blockNumber: number) => + fetchPoolStateFromContractAtBlock( + SmardexPool, + _blockNumber, + poolAddress, + logger, + ), + blockNumber, + `${dexKey}_${poolAddress}`, + dexHelper.provider, + ); + }); + }); + }); + }); + }); + }); +}); diff --git a/src/dex/smardex/smardex-integration.test.ts b/src/dex/smardex/smardex-integration.test.ts new file mode 100644 index 000000000..57e0f9d75 --- /dev/null +++ b/src/dex/smardex/smardex-integration.test.ts @@ -0,0 +1,258 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { DummyDexHelper } from '../../dex-helper/index'; +import { ETHER_ADDRESS, Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { Smardex } from './smardex'; +import { checkPoolPrices, checkPoolsLiquidity } from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; + +interface NetworkConfig { + name: 'ethereum' | 'bsc' | 'polygon' | 'arbitrum' | 'base'; + network: Network; + tokens: typeof Tokens[Network]; + tokenPairs: { src: string; dest: string; sell: number[]; buy: number[] }[]; +} + +const networkConfigs: Array = [ + { + name: 'ethereum', + network: Network.MAINNET, + tokens: Tokens[Network.MAINNET], + tokenPairs: [ + { + src: 'SDEX', + dest: 'USDT', + sell: [0, 10_000, 20_000, 30_000], + buy: [0, 1_000, 2_000], + }, + { + src: 'SDEX', + dest: 'WETH', + sell: [0, 100_000, 200_000, 300_000], + buy: [0, 1, 2, 3], + }, + { + src: 'SDEX', + dest: 'ETH', + sell: [0, 100_000, 200_000, 300_000], + buy: [0, 1, 2, 3], + }, + { src: 'WETH', dest: 'WBTC', sell: [0, 2, 4, 6], buy: [0, 1, 2, 3] }, + ], + }, + { + name: 'arbitrum', + network: Network.ARBITRUM, + tokens: Tokens[Network.ARBITRUM], + tokenPairs: [ + { + src: 'SDEX', + dest: 'USDC', + sell: [0, 10_000, 20_000, 30_000], + buy: [0, 1_000, 2_000], + }, + { + src: 'SDEX', + dest: 'WETH', + sell: [0, 100_000, 200_000, 300_000], + buy: [0, 1, 2, 3], + }, + { + src: 'SDEX', + dest: 'ETH', + sell: [0, 100_000, 200_000, 300_000], + buy: [0, 1, 2, 3], + }, + { + src: 'SDEX', + dest: 'WBTC', + sell: [0, 100_000, 200_000, 300_000], + buy: [0, 1, 2, 3], + }, + { + src: 'ARB', + dest: 'USDC', + sell: [0, 2_000, 4_000, 6_000], + buy: [0, 1_000, 2_000], + }, + ], + }, + { + name: 'bsc', + network: Network.BSC, + tokens: Tokens[Network.BSC], + tokenPairs: [ + { + src: 'SDEX', + dest: 'USDT', + sell: [0, 10_000, 20_000, 30_000], + buy: [0, 1_000, 2_000], + }, + { + src: 'bBTC', + dest: 'SDEX', + sell: [0, 100_000, 200_000, 300_000], + buy: [0, 1, 2, 3], + }, + { + src: 'USDT', + dest: 'WBNB', + sell: [0, 100_000, 200_000, 300_000], + buy: [0, 10, 20, 30], + }, + { src: 'ETH', dest: 'SDEX', sell: [0, 2, 4, 6], buy: [0, 1, 2, 3] }, + { + src: 'USDT', + dest: 'BNB', + sell: [0, 2_000, 4_000, 6_000], + buy: [0, 10, 20, 30], + }, + ], + }, + { + name: 'polygon', + network: Network.POLYGON, + tokens: Tokens[Network.POLYGON], + tokenPairs: [ + { + src: 'USDC', + dest: 'SDEX', + sell: [0, 10_000, 20_000, 30_000], + buy: [0, 100_000, 200_000], + }, + { + src: 'SDEX', + dest: 'WETH', + sell: [0, 100_000, 200_000, 300_000], + buy: [0, 2, 4, 6, 8, 10], + }, + { + src: 'USDC', + dest: 'WMATIC', + sell: [0, 100_000, 200_000, 300_000], + buy: [0, 20_000, 40_000, 60_000], + }, + { + src: 'USDC', + dest: 'MATIC', + sell: [0, 100_000, 200_000, 300_000], + buy: [0, 20_000, 40_000, 60_000], + }, + { + src: 'WBTC', + dest: 'SDEX', + sell: [0, 1, 2, 3], + buy: [0, 500_000, 1_000_000, 1_500_000], + }, + ], + }, + { + name: 'base', + network: Network.BASE, + tokens: Tokens[Network.BASE], + tokenPairs: [ + { + src: 'WETH', + dest: 'SDEX', + sell: [0, 2, 4, 6, 8, 10], + buy: [0, 100_000, 200_000, 300_000], + }, + { + src: 'WETH', + dest: 'USDbC', + sell: [0, 2, 4, 6, 8, 10], + buy: [0, 10_000, 20_000, 30_000], + }, + ], + }, +]; + +networkConfigs.forEach(({ name, network, tokens, tokenPairs }) => { + describe(`Smardex Integration Tests on ${name}`, () => { + const dexKey = 'Smardex'; + + let dexHelper: DummyDexHelper; + let blocknumber: number; + + beforeEach(async () => { + dexHelper = new DummyDexHelper(network); + blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + }); + + // Help us to test the pool prices and volume for a given amount of tokens to swap + const testPoolPricesVolume = async (swapSide: SwapSide, pair: any) => { + const smardex = new Smardex(network, dexKey, dexHelper); + const currentAmounts = + swapSide === SwapSide.SELL + ? pair.sell.map( + (amount: number) => + BigInt(amount) * BI_POWS[tokens[pair.src].decimals], + ) + : pair.buy.map( + (amount: number) => + BigInt(amount) * BI_POWS[tokens[pair.dest].decimals], + ); + + const pools = await smardex.getPoolIdentifiers( + tokens[pair.src], + tokens[pair.dest], + swapSide, + blocknumber, + ); + console.log(`${pair.src} <> ${pair.dest} Pool Identifiers: `, pools); + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await smardex.getPricesVolume( + tokens[pair.src], + tokens[pair.dest], + currentAmounts, + swapSide, + blocknumber, + pools, + ); + console.log(`${pair.src} <> ${pair.dest} Pool Prices: `, poolPrices); + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, currentAmounts, swapSide, dexKey); + }; + + /** + * For each token pair, test: + * - Compute pool identifiers and prices for a given amount of tokens to swap (SELL and BUY) + * - Get top pools for a token + */ + tokenPairs.forEach(pair => { + describe(`${pair.src} <> ${pair.dest}`, () => { + it('SELL: Compute pool identiers and prices', async () => { + await testPoolPricesVolume(SwapSide.SELL, pair); + }); + + it('BUY: Compute pool identiers and prices', async () => { + await testPoolPricesVolume(SwapSide.BUY, pair); + }); + }); + }); + + it('getTopPoolsForToken', async () => { + const smardex = new Smardex(network, dexKey, dexHelper); + // Get all distinct tokens of the pairs + const distinctTokens = tokenPairs + .reduce((acc, pair) => [...acc, pair.src, pair.dest], [] as string[]) + .filter((value, index, self) => self.indexOf(value) === index) + // Native token doesn't have a pool, avoid it + .filter(token => tokens[token].address !== ETHER_ADDRESS); + // Get top pools for each token and check liquidity + await Promise.all( + distinctTokens.map(async token => + checkPoolsLiquidity( + await smardex.getTopPoolsForToken(tokens[token].address, 10), + tokens[token].address, + dexKey, + ), + ), + ); + }); + }); +}); diff --git a/src/dex/smardex/smardex.ts b/src/dex/smardex/smardex.ts new file mode 100644 index 000000000..3318f4e10 --- /dev/null +++ b/src/dex/smardex/smardex.ts @@ -0,0 +1,770 @@ +import { AbiCoder, Interface } from '@ethersproject/abi'; +import { Contract } from 'web3-eth-contract'; +import _ from 'lodash'; +import { + DEST_TOKEN_PARASWAP_TRANSFERS, + ETHER_ADDRESS, + NULL_ADDRESS, + Network, + SRC_TOKEN_PARASWAP_TRANSFERS, + SwapSide, +} from '../../constants'; +import { + AdapterExchangeParam, + Address, + ExchangePrices, + Logger, + NumberAsString, + PoolLiquidity, + PoolPrices, + SimpleExchangeParam, + Token, + TransferFeeParams, + TxInfo, +} from '../../types'; +import { IDexHelper } from '../../dex-helper/index'; +import { + SmardexFees, + SmardexPair, + SmardexPool, + SmardexPoolOrderedParams, + SmardexPoolState, +} from './types'; +import { getBigIntPow, getDexKeysWithNetwork, isETHAddress } from '../../utils'; +import SmardexFactoryLayerOneABI from '../../abi/smardex/layer-1/smardex-factory.json'; +import SmardexFactoryLayerTwoABI from '../../abi/smardex/layer-2/smardex-factory.json'; + +import SmardexRouterABI from '../../abi/smardex/all/smardex-router.json'; +import { SimpleExchange } from '../simple-exchange'; +import { + SmardexRouterFunctions, + directSmardexFunctionName, + DefaultSmardexPoolGasCost, + SUBGRAPH_TIMEOUT, + FEES_LAYER_ONE, +} from '../smardex/constants'; +import { SmardexData, SmardexParam } from '../smardex/types'; +import { IDex } from '../..'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { Adapters, SmardexConfig } from './config'; +import ParaSwapABI from '../../abi/IParaswap.json'; +import { applyTransferFee } from '../../lib/token-transfer-fee'; +import { computeAmountIn, computeAmountOut } from './sdk/core'; +import { SmardexEventPool } from './smardex-event-pool'; +import SmardexPoolLayerOneABI from '../../abi/smardex/layer-1/smardex-pool.json'; +import SmardexPoolLayerTwoABI from '../../abi/smardex/layer-2/smardex-pool.json'; + +const smardexPoolL1 = new Interface(SmardexPoolLayerOneABI); +const smardexPoolL2 = new Interface(SmardexPoolLayerTwoABI); + +const coder = new AbiCoder(); + +function encodePools(pools: SmardexPool[]): NumberAsString[] { + return pools.map(({ fee, direction, address }) => { + return ( + (BigInt(fee) << 161n) + + ((direction ? 0n : 1n) << 160n) + + BigInt(address) + ).toString(); + }); +} + +export class Smardex + extends SimpleExchange + implements IDex +{ + pairs: { [key: string]: SmardexPair } = {}; + factory: Contract; + + routerInterface: Interface; + exchangeRouterInterface: Interface; + static directFunctionName = directSmardexFunctionName; + + factoryAddress: string; + routerAddress: string; + + protected subgraphURL: string | undefined; + protected initCode: string; + + logger: Logger; + readonly hasConstantPriceLargeAmounts = false; + readonly isFeeOnTransferSupported: boolean = true; + readonly SRC_TOKEN_DEX_TRANSFERS = 1; + readonly DEST_TOKEN_DEX_TRANSFERS = 1; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(SmardexConfig); + + constructor( + protected network: Network, + dexKey: string, + public dexHelper: IDexHelper, + protected adapters = Adapters[network] || {}, + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey); + const config = SmardexConfig[dexKey]; + this.routerAddress = config[network].router!; + this.factoryAddress = config[network].factoryAddress; + this.subgraphURL = config[network].subgraphURL; + this.initCode = config[network].initCode; + const factoryAbi = this.isLayer1() + ? SmardexFactoryLayerOneABI + : SmardexFactoryLayerTwoABI; + this.factory = new dexHelper.web3Provider.eth.Contract( + factoryAbi as any, + this.factoryAddress, + ); + this.routerInterface = new Interface(ParaSwapABI); + this.exchangeRouterInterface = new Interface(SmardexRouterABI); + } + + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return this.adapters[side] ? this.adapters[side] : null; + } + + async getPoolIdentifiers( + _from: Token, + _to: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + const from = this.dexHelper.config.wrapETH(_from); + const to = this.dexHelper.config.wrapETH(_to); + + if (from.address.toLowerCase() === to.address.toLowerCase()) { + return []; + } + + const tokenAddress = [from.address.toLowerCase(), to.address.toLowerCase()] + .sort((a, b) => (BigInt(a) > BigInt(b) ? 1 : -1)) + .join('_'); + + const poolIdentifier = `${this.dexKey}_${tokenAddress}`; + return [poolIdentifier]; + } + + async getPricesVolume( + _from: Token, + _to: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + // list of pool identifiers to use for pricing, if undefined use all pools + limitPools?: string[], + transferFees: TransferFeeParams = { + srcFee: 0, + destFee: 0, + srcDexFee: 0, + destDexFee: 0, + }, + ): Promise | null> { + try { + const from = this.dexHelper.config.wrapETH(_from); + const to = this.dexHelper.config.wrapETH(_to); + + if (from.address.toLowerCase() === to.address.toLowerCase()) { + return null; + } + + const tokenAddress = [ + from.address.toLowerCase(), + to.address.toLowerCase(), + ] + .sort((a, b) => (BigInt(a) > BigInt(b) ? 1 : -1)) + .join('_'); + + const poolIdentifier = `${this.dexKey}_${tokenAddress}`; + + if (limitPools && limitPools.every(p => p !== poolIdentifier)) + return null; + + await this.batchCatchUpPairs([[from, to]], blockNumber); + const isSell = side === SwapSide.SELL; + const pairParam = await this.getPairOrderedParams( + from, + to, + blockNumber, + transferFees.srcDexFee, + ); + + if (!pairParam) return null; + + const unitAmount = getBigIntPow(isSell ? from.decimals : to.decimals); + + // SmarDex does not support Fees on Transfer Tokens + const [unitVolumeWithFee, ...amountsWithFee] = applyTransferFee( + [unitAmount, ...amounts], + side, + isSell ? transferFees.srcFee : transferFees.destFee, + isSell ? SRC_TOKEN_PARASWAP_TRANSFERS : DEST_TOKEN_PARASWAP_TRANSFERS, + ); + + const unit = isSell + ? await this.getSellPricePath(unitVolumeWithFee, [pairParam]) + : await this.getBuyPricePath(unitVolumeWithFee, [pairParam]); + + const prices = isSell + ? await Promise.all( + amountsWithFee.map(amount => + this.getSellPricePath(amount, [pairParam]), + ), + ) + : await Promise.all( + amountsWithFee.map(amount => + this.getBuyPricePath(amount, [pairParam]), + ), + ); + + const [unitOutWithFee, ...outputsWithFee] = applyTransferFee( + [unit, ...prices], + side, + // This part is confusing, because we treat differently SELL and BUY fees + // If Buy, we should apply transfer fee on srcToken on top of dexFee applied earlier + // But for Sell we should apply only one dexFee + isSell ? transferFees.destDexFee : transferFees.srcFee, + isSell ? this.DEST_TOKEN_DEX_TRANSFERS : SRC_TOKEN_PARASWAP_TRANSFERS, + ); + + // As uniswapv2 just has one pool per token pair + return [ + { + prices: outputsWithFee, + unit: unitOutWithFee, + data: { + deadline: Math.floor(new Date().getTime()) + 120, + receiver: this.augustusAddress, + router: this.routerAddress, + path: [from.address.toLowerCase(), to.address.toLowerCase()], + factory: this.factoryAddress, + initCode: this.initCode, + pools: [ + { + address: pairParam.exchange, + fee: 0, // Smardex does not support Fees on Transfer Tokens + direction: + pairParam.fromToken.toLocaleLowerCase() === + pairParam.token0.toLocaleLowerCase(), + }, + ], + }, + exchange: this.dexKey, + poolIdentifier, + gasCost: DefaultSmardexPoolGasCost, + poolAddresses: [pairParam.exchange], + }, + ]; + } catch (e) { + if (blockNumber === 0) + this.logger.error( + `Error_getPricesVolume: Aurelius block manager not yet instantiated`, + ); + this.logger.error(`Error_getPrices:`, e); + return null; + } + } + + async getBuyPricePath( + amount: bigint, + params: SmardexPoolOrderedParams[], + ): Promise { + let price = amount; + for (const param of params.reverse()) { + price = await this.getBuyPrice(param, price); + } + return price; + } + + async getSellPricePath( + amount: bigint, + params: SmardexPoolOrderedParams[], + ): Promise { + let price = amount; + for (const param of params) { + price = await this.getSellPrice(param, price); + } + return price; + } + + async getBuyPrice( + priceParams: SmardexPoolOrderedParams, + destAmount: bigint, + ): Promise { + const amountIn = computeAmountIn( + priceParams.token0, + priceParams.token1, + priceParams.reserves0, + priceParams.reserves1, + priceParams.fictiveReserves0, + priceParams.fictiveReserves1, + destAmount, + priceParams.toToken, + +priceParams.priceAverageLastTimestamp, + priceParams.priceAverage0, + priceParams.priceAverage1, + priceParams.feesLP, + priceParams.feesPool, + ).amount; + + return BigInt(amountIn.toString()); + } + + async getSellPrice( + priceParams: SmardexPoolOrderedParams, + srcAmount: bigint, + ): Promise { + const amountOut = computeAmountOut( + priceParams.token0, + priceParams.token1, + priceParams.reserves0, + priceParams.reserves1, + priceParams.fictiveReserves0, + priceParams.fictiveReserves1, + srcAmount, + priceParams.fromToken, + +priceParams.priceAverageLastTimestamp, + priceParams.priceAverage0, + priceParams.priceAverage1, + priceParams.feesLP, + priceParams.feesPool, + ).amount; + + return BigInt(amountOut.toString()); + } + + // Returns estimated gas cost of calldata for this DEX in multiSwap + getCalldataGasCost(_poolPrices: PoolPrices): number | number[] { + return ( + CALLDATA_GAS_COST.DEX_OVERHEAD + + CALLDATA_GAS_COST.LENGTH_SMALL + + // ParentStruct header + CALLDATA_GAS_COST.OFFSET_SMALL + + // ParentStruct -> weth + CALLDATA_GAS_COST.ADDRESS + + // ParentStruct -> pools[] header + CALLDATA_GAS_COST.OFFSET_SMALL + + // ParentStruct -> pools[] + CALLDATA_GAS_COST.LENGTH_SMALL + + // ParentStruct -> pools[0] + CALLDATA_GAS_COST.wordNonZeroBytes(22) + ); + } + + async findPair(from: Token, to: Token) { + if (from.address.toLowerCase() === to.address.toLowerCase()) return null; + const [token0, token1] = + BigInt(from.address.toLowerCase()) < BigInt(to.address.toLowerCase()) + ? [from, to] + : [to, from]; + + const key = `${token0.address.toLowerCase()}-${token1.address.toLowerCase()}`; + let pair = this.pairs[key]; + if (pair) return pair; + const exchange = await this.factory.methods + .getPair(token0.address, token1.address) + .call(); + if (exchange === NULL_ADDRESS) { + pair = { token0, token1 }; + } else { + pair = { token0, token1, exchange }; + } + this.pairs[key] = pair; + return pair; + } + + async batchCatchUpPairs(pairs: [Token, Token][], blockNumber: number) { + if (!blockNumber) return; + const pairsToFetch: SmardexPair[] = []; + for (const _pair of pairs) { + const pair = await this.findPair(_pair[0], _pair[1]); + if (!(pair && pair.exchange)) continue; + if (!pair.pool) { + pairsToFetch.push(pair); + } else if (!pair.pool.getState(blockNumber)) { + pairsToFetch.push(pair); + } + } + + if (!pairsToFetch.length) return; + + const reserves = await this.getManyPoolReserves(pairsToFetch, blockNumber); + + if (reserves.length !== pairsToFetch.length) { + this.logger.error( + `Error_getManyPoolReserves didn't get any pool reserves`, + ); + } + + for (let i = 0; i < pairsToFetch.length; i++) { + const pairState = reserves[i]; + const pair = pairsToFetch[i]; + if (!pair.pool) { + await this.addPool( + pair, + pairState.reserves0, + pairState.reserves1, + pairState.fictiveReserves0, + pairState.fictiveReserves1, + pairState.priceAverage0, + pairState.priceAverage1, + pairState.feesLP, + pairState.feesPool, + blockNumber, + pairState.priceAverageLastTimestamp, + ); + } else pair.pool.setState(pairState, blockNumber); + } + } + + // On Smardex the fees are upgradable on layer 2. + public getFeesMultiCallData(pairAddress: string) { + if (this.isLayer1()) { + return null; + } + const callEntry = { + target: pairAddress, + callData: smardexPoolL2.encodeFunctionData('getPairFees'), + }; + const callDecoder = (values: any[]): SmardexFees => { + const feesData = smardexPoolL2.decodeFunctionResult( + 'getPairFees', + values, + ); + return { + feesLP: feesData.feesLP_.toBigInt(), + feesPool: feesData.feesPool_.toBigInt(), + }; + }; + return { + callEntry, + callDecoder, + }; + } + + protected async addPool( + pair: SmardexPair, + reserves0: string, + reserves1: string, + fictiveReserves0: string, + fictiveReserves1: string, + priceAverage0: string, + priceAverage1: string, + feesLp: bigint, + feesPool: bigint, + blockNumber: number, + priceAverageLastTimestamp: number, + ) { + const multiCallFeeData = this.getFeesMultiCallData(pair.exchange!); + pair.pool = new SmardexEventPool( + this.isLayer1() ? smardexPoolL1 : smardexPoolL2, + this.dexHelper, + pair.exchange!, + pair.token0, + pair.token1, + this.logger, + // For layer 2 we need to fetch the fees + multiCallFeeData?.callEntry, + multiCallFeeData?.callDecoder, + this.isLayer1(), + ); + pair.pool.addressesSubscribed.push(pair.exchange!); + + await pair.pool.initialize(blockNumber, { + state: { + reserves0, + reserves1, + fictiveReserves0, + fictiveReserves1, + priceAverage0, + priceAverage1, + feesLP: feesLp, + feesPool, + priceAverageLastTimestamp, + }, + }); + } + + async getManyPoolReserves( + pairs: SmardexPair[], + blockNumber: number, + ): Promise { + try { + const multiCallFeeData = pairs.map(pair => + this.getFeesMultiCallData(pair.exchange!), + ); + const calldata = pairs + .map((pair, i) => { + let calldata = [ + { + target: pair.exchange!, + callData: smardexPoolL1.encodeFunctionData('getReserves'), + }, + { + target: pair.exchange!, + callData: smardexPoolL1.encodeFunctionData('getFictiveReserves'), + }, + { + target: pair.exchange!, + callData: smardexPoolL1.encodeFunctionData('getPriceAverage'), + }, + ]; + // Fetch fees only on layer 2 + !this.isLayer1() && calldata.push(multiCallFeeData[i]!.callEntry); + return calldata; + }) + .flat(); + + const data: { returnData: any[] } = + await this.dexHelper.multiContract.methods + .aggregate(calldata) + .call({}, blockNumber); + + const returnData = _.chunk(data.returnData, 4); + return pairs.map((_pair: SmardexPair, i) => ({ + reserves0: coder + .decode(['uint256', 'uint256'], returnData[i][0])[0] + .toString(), + reserves1: coder + .decode(['uint256', 'uint256'], returnData[i][0])[1] + .toString(), + fictiveReserves0: coder + .decode(['uint256', 'uint256'], returnData[i][1])[0] + .toString(), + fictiveReserves1: coder + .decode(['uint256', 'uint256'], returnData[i][1])[1] + .toString(), + priceAverage0: coder + .decode(['uint256', 'uint256', 'uint256'], returnData[i][2])[0] + .toString(), + priceAverage1: coder + .decode(['uint256', 'uint256', 'uint256'], returnData[i][2])[1] + .toString(), + priceAverageLastTimestamp: coder + .decode(['uint256', 'uint256', 'uint256'], returnData[i][2])[2] + .toString(), + feesLP: this.isLayer1() + ? FEES_LAYER_ONE.feesLP + : multiCallFeeData[i]!.callDecoder(returnData[i][3]).feesLP, + feesPool: this.isLayer1() + ? FEES_LAYER_ONE.feesPool + : multiCallFeeData[i]!.callDecoder(returnData[i][3]).feesPool, + })); + } catch (e) { + this.logger.error( + `Error_getManyPoolReserves could not get reserves with error:`, + e, + ); + return []; + } + } + + protected fixPath(path: Address[], srcToken: Address, destToken: Address) { + return path.map((token: string, i: number) => { + if ( + (i === 0 && srcToken.toLowerCase() === ETHER_ADDRESS.toLowerCase()) || + (i === path.length - 1 && + destToken.toLowerCase() === ETHER_ADDRESS.toLowerCase()) + ) + return ETHER_ADDRESS; + return token; + }); + } + + async getPairOrderedParams( + from: Token, + to: Token, + blockNumber: number, + tokenDexTransferFee: number, + ): Promise { + const pair = await this.findPair(from, to); + 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) ${ + from.symbol || from.address + } ${to.symbol || to.address}`, + ); + return null; + } + // const fee = (pairState.feesPool + tokenDexTransferFee).toString(); + + return { + fromToken: from.address, + toToken: to.address, + token0: pair.token0.address, + token1: pair.token1.address, + reserves0: BigInt(pairState.reserves0), + reserves1: BigInt(pairState.reserves1), + fictiveReserves0: BigInt(pairState.fictiveReserves0), + fictiveReserves1: BigInt(pairState.fictiveReserves1), + priceAverage0: BigInt(pairState.priceAverage0), + priceAverage1: BigInt(pairState.priceAverage1), + priceAverageLastTimestamp: pairState.priceAverageLastTimestamp, + exchange: pair.exchange, + feesLP: BigInt(pairState.feesLP), + feesPool: BigInt(pairState.feesPool), + }; + } + + getWETHAddress(srcToken: Address, destToken: Address, weth?: Address) { + if (!isETHAddress(srcToken) && !isETHAddress(destToken)) + return NULL_ADDRESS; + return weth || this.dexHelper.config.data.wrappedNativeTokenAddress; + } + + getAdapterParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + toAmount: NumberAsString, + data: SmardexData, + side: SwapSide, + ): AdapterExchangeParam { + const payload = this.abiCoder.encodeParameter( + { + ParentStruct: { + path: 'address[]', + receiver: 'address', + }, + }, + { + path: data.path, + receiver: data.receiver, + }, + ); + return { + targetExchange: data.router, + payload, + networkFee: '0', + }; + } + + async getSimpleParam( + src: Address, + dest: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + data: SmardexData, + side: SwapSide, + ): Promise { + let routerMethod: any; + let routerArgs: any; + if (side === SwapSide.SELL) { + routerMethod = isETHAddress(src) + ? SmardexRouterFunctions.sellExactEth + : SmardexRouterFunctions.swapExactIn; + routerMethod = isETHAddress(dest) + ? SmardexRouterFunctions.sellExactToken + : routerMethod; + + routerArgs = isETHAddress(src) + ? [destAmount, data.path, data.receiver, data.deadline] + : [srcAmount, destAmount, data.path, data.receiver, data.deadline]; + } else { + routerMethod = isETHAddress(src) + ? SmardexRouterFunctions.buyExactToken + : SmardexRouterFunctions.swapExactOut; + routerMethod = isETHAddress(dest) + ? SmardexRouterFunctions.buyExactEth + : routerMethod; + + routerArgs = isETHAddress(src) + ? [destAmount, data.path, data.receiver, data.deadline] + : [destAmount, srcAmount, data.path, data.receiver, data.deadline]; + } + + const swapData = this.exchangeRouterInterface.encodeFunctionData( + routerMethod, + routerArgs, + ); + return this.buildSimpleParamWithoutWETHConversion( + src, + srcAmount, + dest, + destAmount, + swapData, + data.router, + ); + } + + isLayer1(): boolean { + return this.network === Network.MAINNET; + } + + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + if (!this.subgraphURL) return []; + const query = ` + query ($token: Bytes!, $limit: Int) { + pools0: pairs(first: $limit, orderBy: reserveUSD, orderDirection: desc, where: {token0: $token, reserve0_gt: 1, reserve1_gt: 1}) { + id + token0 { + id + decimals + } + token1 { + id + decimals + } + reserveUSD + } + pools1: pairs(first: $limit, orderBy: reserveUSD, orderDirection: desc, where: {token1: $token, reserve0_gt: 1, reserve1_gt: 1}) { + id + token0 { + id + decimals + } + token1 { + id + decimals + } + reserveUSD + } + }`; + + const { data } = await this.dexHelper.httpRequest.post( + this.subgraphURL, + { + query, + variables: { token: tokenAddress.toLowerCase(), limit }, + }, + SUBGRAPH_TIMEOUT, + { 'x-api-key': this.dexHelper.config.data.smardexSubgraphAuthToken! }, + ); + + if (!(data && data.pools0 && data.pools1)) + throw new Error("Couldn't fetch the pools from the subgraph"); + const pools0 = _.map(data.pools0, pool => ({ + exchange: this.dexKey, + address: pool.id.toLowerCase(), + connectorTokens: [ + { + address: pool.token1.id.toLowerCase(), + decimals: parseInt(pool.token1.decimals), + }, + ], + liquidityUSD: parseFloat(pool.reserveUSD), + })); + + const pools1 = _.map(data.pools1, pool => ({ + exchange: this.dexKey, + address: pool.id.toLowerCase(), + connectorTokens: [ + { + address: pool.token0.id.toLowerCase(), + decimals: parseInt(pool.token0.decimals), + }, + ], + liquidityUSD: parseFloat(pool.reserveUSD), + })); + + return _.slice( + _.sortBy(_.concat(pools0, pools1), [pool => -1 * pool.liquidityUSD]), + 0, + limit, + ); + } +} diff --git a/src/dex/smardex/types.ts b/src/dex/smardex/types.ts new file mode 100644 index 000000000..a64dadda0 --- /dev/null +++ b/src/dex/smardex/types.ts @@ -0,0 +1,69 @@ +import { Address, NumberAsString } from '../../types'; +import { + UniswapV2Data, + DexParams as UniswapV2DexParams, + UniswapPool, +} from '../uniswap-v2/types'; +import { UniswapV2Pair } from '../uniswap-v2/uniswap-v2'; +import { SmardexEventPool } from './smardex-event-pool'; + +export interface SmardexPoolState extends SmardexFees { + reserves0: string; + reserves1: string; + fictiveReserves0: string; + fictiveReserves1: string; + priceAverage0: string; + priceAverage1: string; + priceAverageLastTimestamp: number; +} + +// export interface SmardexData extends UniswapV2Data { +export interface SmardexData extends Omit { + deadline: number; + receiver: Address; +} + +export type SellOnSmardexParam = [ + amountIn: NumberAsString, + amountOutMin: NumberAsString, + path: Address[], + receiver: Address, + deadline: number, +]; + +export type BuyOnSmardexParam = [ + amountIn: NumberAsString, + amountOutMin: NumberAsString, + path: Address[], + receiver: Address, + deadline: number, +]; + +export type SmardexParam = SellOnSmardexParam | BuyOnSmardexParam; + +export type DexParams = Omit; + +export type SmardexFees = { + feesLP: bigint; + feesPool: bigint; +}; +export interface SmardexPoolOrderedParams extends SmardexFees { + fromToken: string; + toToken: string; + token0: string; + token1: string; + reserves0: bigint; + reserves1: bigint; + fictiveReserves0: bigint; + fictiveReserves1: bigint; + priceAverage0: bigint; + priceAverage1: bigint; + priceAverageLastTimestamp: number; + exchange: string; +} + +export interface SmardexPair extends Omit { + pool?: SmardexEventPool; +} + +export type SmardexPool = UniswapPool; diff --git a/src/dex/solidly-v3/config.ts b/src/dex/solidly-v3/config.ts new file mode 100644 index 000000000..51f91c4db --- /dev/null +++ b/src/dex/solidly-v3/config.ts @@ -0,0 +1,64 @@ +import { DexParams } from './types'; +import { DexConfigMap, AdapterMappings } from '../../types'; +import { Network, SwapSide } from '../../constants'; +import { Address } from '../../types'; + +const SUPPORTED_TICK_SPACINGS = [1n, 10n, 50n, 100n]; + +// Pools that will be initialized on app startup +// They are added for testing +export const PoolsToPreload: DexConfigMap< + { token0: Address; token1: Address }[] +> = { + SolidlyV3: { + [Network.MAINNET]: [ + { + token0: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'.toLowerCase(), + token1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'.toLowerCase(), + }, + { + token0: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0'.toLowerCase(), + token1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'.toLowerCase(), + }, + { + token0: '0x514910771AF9Ca656af840dff83E8264EcF986CA'.toLowerCase(), + token1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'.toLowerCase(), + }, + { + token0: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599'.toLowerCase(), + token1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'.toLowerCase(), + }, + { + token0: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'.toLowerCase(), + token1: '0xdAC17F958D2ee523a2206206994597C13D831ec7'.toLowerCase(), + }, + { + token0: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'.toLowerCase(), + token1: '0xdAC17F958D2ee523a2206206994597C13D831ec7'.toLowerCase(), + }, + ], + }, +}; + +export const SolidlyV3Config: DexConfigMap = { + SolidlyV3: { + [Network.MAINNET]: { + factory: '0x70Fe4a44EA505cFa3A57b95cF2862D4fd5F0f687', + quoter: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e', + supportedTickSpacings: SUPPORTED_TICK_SPACINGS, + stateMulticall: '0xb229563028302AA693EEaD62F80CC331aEDE4e26', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe9b68c5f77858eecac2e651646e208175e9b1359d68d0e14fc69f8c54e5010bf`, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/solidlylabs/solidly-v3', + }, + }, +}; + +export const Adapters: Record = { + [Network.MAINNET]: { + [SwapSide.SELL]: [{ name: 'Adapter04', index: 7 }], + [SwapSide.BUY]: [{ name: 'BuyAdapter02', index: 3 }], + }, +}; diff --git a/src/dex/solidly-v3/constants.ts b/src/dex/solidly-v3/constants.ts new file mode 100644 index 000000000..56c78908e --- /dev/null +++ b/src/dex/solidly-v3/constants.ts @@ -0,0 +1,42 @@ +export const UNISWAPV3_TICK_GAS_COST = 10_000; // Ceiled +export const UNISWAPV3_TICK_BASE_OVERHEAD = 50_000; +export const UNISWAPV3_POOL_SEARCH_OVERHEAD = 0; + +// This is used for price calculation. If out of scope, return 0n +export const TICK_BITMAP_TO_USE = 4n; + +// This is used to check if the state is still valid. +export const TICK_BITMAP_BUFFER = 8n; + +export const MAX_PRICING_COMPUTATION_STEPS_ALLOWED = 128; + +export const UNISWAPV3_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'; + +export const UNISWAPV3_EFFICIENCY_FACTOR = 3; + +export const ZERO_TICK_INFO = { + liquidityGross: 0n, + liquidityNet: 0n, + initialized: false, +}; + +export const ZERO_ORACLE_OBSERVATION = { + blockTimestamp: 0n, + tickCumulative: 0n, + secondsPerLiquidityCumulativeX128: 0n, + initialized: false, +}; + +export const OUT_OF_RANGE_ERROR_POSTFIX = `INVALID_TICK_BIT_MAP_RANGES`; + +export const DEFAULT_POOL_INIT_CODE_HASH = `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`; + +export enum DirectMethods { + directSell = 'directUniV3Swap', + directBuy = 'directUniV3Buy', +} +export const MIN_SQRT_RATIO = BigInt('4295128739'); +export const MAX_SQRT_RATIO = BigInt( + '1461446703485210103287273052203988822378723970342', +); diff --git a/src/dex/solidly-v3/contract-math/BitMath.ts b/src/dex/solidly-v3/contract-math/BitMath.ts new file mode 100644 index 000000000..d7a2e6d9a --- /dev/null +++ b/src/dex/solidly-v3/contract-math/BitMath.ts @@ -0,0 +1,90 @@ +import { + BI_MAX_UINT128, + BI_MAX_UINT16, + BI_MAX_UINT32, + BI_MAX_UINT64, + BI_MAX_UINT8, +} from '../../../bigint-constants'; +import { _require } from '../../../utils'; + +export class BitMath { + static mostSignificantBit(x: bigint): bigint { + _require(x > 0, '', { x }, 'x > 0'); + let r = 0n; + + if (x >= 0x100000000000000000000000000000000n) { + x >>= 128n; + r += 128n; + } + if (x >= 0x10000000000000000n) { + x >>= 64n; + r += 64n; + } + if (x >= 0x100000000n) { + x >>= 32n; + r += 32n; + } + if (x >= 0x10000n) { + x >>= 16n; + r += 16n; + } + if (x >= 0x100n) { + x >>= 8n; + r += 8n; + } + if (x >= 0x10n) { + x >>= 4n; + r += 4n; + } + if (x >= 0x4n) { + x >>= 2n; + r += 2n; + } + if (x >= 0x2n) r += 1n; + + return r; + } + + static leastSignificantBit(x: bigint): bigint { + _require(x > 0, '', { x }, 'x > 0'); + + let r = 255n; + if ((x & BI_MAX_UINT128) > 0n) { + r -= 128n; + } else { + x >>= 128n; + } + if ((x & BI_MAX_UINT64) > 0n) { + r -= 64n; + } else { + x >>= 64n; + } + if ((x & BI_MAX_UINT32) > 0n) { + r -= 32n; + } else { + x >>= 32n; + } + if ((x & BI_MAX_UINT16) > 0n) { + r -= 16n; + } else { + x >>= 16n; + } + if ((x & BI_MAX_UINT8) > 0n) { + r -= 8n; + } else { + x >>= 8n; + } + if ((x & 0xfn) > 0n) { + r -= 4n; + } else { + x >>= 4n; + } + if ((x & 0x3n) > 0n) { + r -= 2n; + } else { + x >>= 2n; + } + if ((x & 0x1n) > 0n) r -= 1n; + return r; + } +} diff --git a/src/dex/solidly-v3/contract-math/FixedPoint128.ts b/src/dex/solidly-v3/contract-math/FixedPoint128.ts new file mode 100644 index 000000000..2058307bd --- /dev/null +++ b/src/dex/solidly-v3/contract-math/FixedPoint128.ts @@ -0,0 +1,3 @@ +export class FixedPoint128 { + static readonly Q128 = 0x100000000000000000000000000000000n; +} diff --git a/src/dex/solidly-v3/contract-math/FixedPoint96.ts b/src/dex/solidly-v3/contract-math/FixedPoint96.ts new file mode 100644 index 000000000..1a551dcb9 --- /dev/null +++ b/src/dex/solidly-v3/contract-math/FixedPoint96.ts @@ -0,0 +1,4 @@ +export class FixedPoint96 { + static readonly RESOLUTION = 96n; + static readonly Q96 = 0x1000000000000000000000000n; +} diff --git a/src/dex/solidly-v3/contract-math/FullMath.ts b/src/dex/solidly-v3/contract-math/FullMath.ts new file mode 100644 index 000000000..7c6a3bdc3 --- /dev/null +++ b/src/dex/solidly-v3/contract-math/FullMath.ts @@ -0,0 +1,30 @@ +import { BI_MAX_UINT256 } from '../../../bigint-constants'; +import { _require } from '../../../utils'; + +export class FullMath { + static mulDiv(a: bigint, b: bigint, denominator: bigint) { + const result = (a * b) / denominator; + + _require( + result <= BI_MAX_UINT256, + '', + { result, BI_MAX_UINT: BI_MAX_UINT256 }, + 'result <= BI_MAX_UINT', + ); + + return result; + } + + static mulDivRoundingUp(a: bigint, b: bigint, denominator: bigint) { + const result = (a * b + denominator - 1n) / denominator; + + _require( + result <= BI_MAX_UINT256, + '', + { result, BI_MAX_UINT: BI_MAX_UINT256 }, + 'result <= BI_MAX_UINT', + ); + + return result; + } +} diff --git a/src/dex/solidly-v3/contract-math/LiquidityMath.ts b/src/dex/solidly-v3/contract-math/LiquidityMath.ts new file mode 100644 index 000000000..a495e55cc --- /dev/null +++ b/src/dex/solidly-v3/contract-math/LiquidityMath.ts @@ -0,0 +1,17 @@ +import { _require } from '../../../utils'; + +export class LiquidityMath { + static addDelta(x: bigint, y: bigint): bigint { + let z; + if (y < 0) { + const _y = BigInt.asUintN(128, -y); + z = x - _y; + _require(z < x, 'LS', { z, x, y, _y }, 'z < x'); + } else { + const _y = BigInt.asUintN(128, y); + z = x + _y; + _require(z >= x, 'LA', { z, x, y, _y }, 'z >= x'); + } + return z; + } +} diff --git a/src/dex/solidly-v3/contract-math/SqrtPriceMath.ts b/src/dex/solidly-v3/contract-math/SqrtPriceMath.ts new file mode 100644 index 000000000..31b801d73 --- /dev/null +++ b/src/dex/solidly-v3/contract-math/SqrtPriceMath.ts @@ -0,0 +1,226 @@ +import { BI_MAX_UINT160 } from '../../../bigint-constants'; +import { FixedPoint96 } from './FixedPoint96'; +import { FullMath } from './FullMath'; +import { UnsafeMath } from './UnsafeMath'; +import { _require } from '../../../utils'; + +export class SqrtPriceMath { + static getNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96: bigint, + liquidity: bigint, + amount: bigint, + add: boolean, + ): bigint { + if (amount === 0n) return sqrtPX96; + const numerator1 = + BigInt.asUintN(256, liquidity) << FixedPoint96.RESOLUTION; + + const product = amount * sqrtPX96; + if (add) { + if (product / amount === sqrtPX96) { + const denominator = numerator1 + product; + if (denominator >= numerator1) { + return BigInt.asUintN( + 160, + FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator), + ); + } + } + return BigInt.asUintN( + 160, + UnsafeMath.divRoundingUp(numerator1, numerator1 / sqrtPX96 + amount), + ); + } else { + _require( + product / amount === sqrtPX96 && numerator1 > product, + '', + { product, amount, sqrtPX96, numerator1 }, + 'product / amount === sqrtPX96 && numerator1 > product', + ); + const denominator = numerator1 - product; + return BigInt.asUintN( + 160, + FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator), + ); + } + } + + static getNextSqrtPriceFromAmount1RoundingDown( + sqrtPX96: bigint, + liquidity: bigint, + amount: bigint, + add: boolean, + ): bigint { + if (add) { + const quotient = + amount <= BI_MAX_UINT160 + ? (amount << FixedPoint96.RESOLUTION) / liquidity + : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity); + return BigInt.asUintN(160, BigInt.asUintN(256, sqrtPX96) + quotient); + } else { + const quotient = + amount <= BI_MAX_UINT160 + ? UnsafeMath.divRoundingUp( + amount << FixedPoint96.RESOLUTION, + liquidity, + ) + : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity); + + _require( + sqrtPX96 > quotient, + '', + { sqrtPX96, quotient }, + 'sqrtPX96 > quotient', + ); + return BigInt.asUintN(160, sqrtPX96 - quotient); + } + } + + static getNextSqrtPriceFromInput( + sqrtPX96: bigint, + liquidity: bigint, + amountIn: bigint, + zeroForOne: boolean, + ): bigint { + _require(sqrtPX96 > 0n, '', { sqrtPX96 }, 'sqrtPX96 > 0n'); + _require(liquidity > 0n, '', { liquidity }, 'liquidity > 0n'); + + return zeroForOne + ? SqrtPriceMath.getNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96, + liquidity, + amountIn, + true, + ) + : SqrtPriceMath.getNextSqrtPriceFromAmount1RoundingDown( + sqrtPX96, + liquidity, + amountIn, + true, + ); + } + + static getNextSqrtPriceFromOutput( + sqrtPX96: bigint, + liquidity: bigint, + amountOut: bigint, + zeroForOne: boolean, + ): bigint { + _require(sqrtPX96 > 0n, '', { sqrtPX96 }, 'sqrtPX96 > 0n'); + _require(liquidity > 0n, '', { liquidity }, 'liquidity > 0n'); + + return zeroForOne + ? SqrtPriceMath.getNextSqrtPriceFromAmount1RoundingDown( + sqrtPX96, + liquidity, + amountOut, + false, + ) + : SqrtPriceMath.getNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96, + liquidity, + amountOut, + false, + ); + } + + static getAmount0Delta( + sqrtRatioAX96: bigint, + sqrtRatioBX96: bigint, + liquidity: bigint, + roundUp: boolean, + ) { + if (sqrtRatioAX96 > sqrtRatioBX96) { + [sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96]; + } + + const numerator1 = + BigInt.asUintN(256, liquidity) << FixedPoint96.RESOLUTION; + const numerator2 = sqrtRatioBX96 - sqrtRatioAX96; + + _require(sqrtRatioAX96 > 0, '', { sqrtRatioAX96 }, 'sqrtRatioAX96 > 0'); + + return roundUp + ? UnsafeMath.divRoundingUp( + FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), + sqrtRatioAX96, + ) + : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + } + + static getAmount1Delta( + sqrtRatioAX96: bigint, + sqrtRatioBX96: bigint, + liquidity: bigint, + roundUp: boolean, + ) { + if (sqrtRatioAX96 > sqrtRatioBX96) + [sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96]; + + return roundUp + ? FullMath.mulDivRoundingUp( + liquidity, + sqrtRatioBX96 - sqrtRatioAX96, + FixedPoint96.Q96, + ) + : FullMath.mulDiv( + liquidity, + sqrtRatioBX96 - sqrtRatioAX96, + FixedPoint96.Q96, + ); + } + + // Overloaded with different argument numbers + static _getAmount0DeltaO( + sqrtRatioAX96: bigint, + sqrtRatioBX96: bigint, + liquidity: bigint, + ) { + return liquidity < 0 + ? -BigInt.asIntN( + 256, + SqrtPriceMath.getAmount0Delta( + sqrtRatioAX96, + sqrtRatioBX96, + BigInt.asUintN(128, -liquidity), + false, + ), + ) + : BigInt.asIntN( + 256, + SqrtPriceMath.getAmount0Delta( + sqrtRatioAX96, + sqrtRatioBX96, + BigInt.asUintN(128, liquidity), + true, + ), + ); + } + + // Overloaded with different argument numbers + static _getAmount1DeltaO( + sqrtRatioAX96: bigint, + sqrtRatioBX96: bigint, + liquidity: bigint, + ) { + return liquidity < 0 + ? -BigInt.asIntN( + 256, + SqrtPriceMath.getAmount1Delta( + sqrtRatioAX96, + sqrtRatioBX96, + BigInt.asUintN(128, -liquidity), + false, + ), + ) + : BigInt.asIntN( + 256, + SqrtPriceMath.getAmount1Delta( + sqrtRatioAX96, + sqrtRatioBX96, + BigInt.asUintN(128, liquidity), + true, + ), + ); + } +} diff --git a/src/dex/solidly-v3/contract-math/SwapMath.ts b/src/dex/solidly-v3/contract-math/SwapMath.ts new file mode 100644 index 000000000..3f19cf8cb --- /dev/null +++ b/src/dex/solidly-v3/contract-math/SwapMath.ts @@ -0,0 +1,139 @@ +import { BI_POWS } from '../../../bigint-constants'; +import { FullMath } from './FullMath'; +import { SqrtPriceMath } from './SqrtPriceMath'; + +export class SwapMath { + static computeSwapStep( + sqrtRatioCurrentX96: bigint, + sqrtRatioTargetX96: bigint, + liquidity: bigint, + amountRemaining: bigint, + feePips: bigint, + ): { + sqrtRatioNextX96: bigint; + amountIn: bigint; + amountOut: bigint; + feeAmount: bigint; + } { + const zeroForOne = sqrtRatioCurrentX96 >= sqrtRatioTargetX96; + const exactIn = amountRemaining >= 0n; + + let sqrtRatioNextX96 = 0n; + let amountIn = 0n; + let amountOut = 0n; + let feeAmount = 0n; + + if (exactIn) { + const amountRemainingLessFee = FullMath.mulDiv( + BigInt.asUintN(256, amountRemaining), + BI_POWS[6] - feePips, + BI_POWS[6], + ); + amountIn = zeroForOne + ? SqrtPriceMath.getAmount0Delta( + sqrtRatioTargetX96, + sqrtRatioCurrentX96, + liquidity, + true, + ) + : SqrtPriceMath.getAmount1Delta( + sqrtRatioCurrentX96, + sqrtRatioTargetX96, + liquidity, + true, + ); + if (amountRemainingLessFee >= amountIn) + sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( + sqrtRatioCurrentX96, + liquidity, + amountRemainingLessFee, + zeroForOne, + ); + } else { + amountOut = zeroForOne + ? SqrtPriceMath.getAmount1Delta( + sqrtRatioTargetX96, + sqrtRatioCurrentX96, + liquidity, + false, + ) + : SqrtPriceMath.getAmount0Delta( + sqrtRatioCurrentX96, + sqrtRatioTargetX96, + liquidity, + false, + ); + if (BigInt.asUintN(256, -amountRemaining) >= amountOut) + sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput( + sqrtRatioCurrentX96, + liquidity, + BigInt.asUintN(256, -amountRemaining), + zeroForOne, + ); + } + + const max = sqrtRatioTargetX96 == sqrtRatioNextX96; + + if (zeroForOne) { + amountIn = + max && exactIn + ? amountIn + : SqrtPriceMath.getAmount0Delta( + sqrtRatioNextX96, + sqrtRatioCurrentX96, + liquidity, + true, + ); + amountOut = + max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount1Delta( + sqrtRatioNextX96, + sqrtRatioCurrentX96, + liquidity, + false, + ); + } else { + amountIn = + max && exactIn + ? amountIn + : SqrtPriceMath.getAmount1Delta( + sqrtRatioCurrentX96, + sqrtRatioNextX96, + liquidity, + true, + ); + amountOut = + max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount0Delta( + sqrtRatioCurrentX96, + sqrtRatioNextX96, + liquidity, + false, + ); + } + + // cap the output amount to not exceed the remaining output amount + if (!exactIn && amountOut > BigInt.asUintN(256, -amountRemaining)) { + amountOut = BigInt.asUintN(256, -amountRemaining); + } + + if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) { + // we didn't reach the target, so take the remainder of the maximum input as fee + feeAmount = BigInt.asUintN(256, amountRemaining) - amountIn; + } else { + feeAmount = FullMath.mulDivRoundingUp( + amountIn, + feePips, + BI_POWS[6] - feePips, + ); + } + + return { sqrtRatioNextX96, amountIn, amountOut, feeAmount }; + } +} diff --git a/src/dex/solidly-v3/contract-math/Tick.ts b/src/dex/solidly-v3/contract-math/Tick.ts new file mode 100644 index 000000000..1fca2bb9d --- /dev/null +++ b/src/dex/solidly-v3/contract-math/Tick.ts @@ -0,0 +1,63 @@ +import { PoolState, TickInfo } from '../types'; +import { LiquidityMath } from './LiquidityMath'; +import { _require } from '../../../utils'; +import { NumberAsString } from '@paraswap/core'; +import { ZERO_TICK_INFO } from '../constants'; + +export class Tick { + static update( + state: Pick, + tick: bigint, + liquidityDelta: bigint, + upper: boolean, + maxLiquidity: bigint, + ): boolean { + let info = state.ticks[Number(tick)]; + + if (info === undefined) { + info = { ...ZERO_TICK_INFO }; + state.ticks[Number(tick)] = info; + } + + const liquidityGrossBefore = info.liquidityGross; + const liquidityGrossAfter = LiquidityMath.addDelta( + liquidityGrossBefore, + liquidityDelta, + ); + + _require( + liquidityGrossAfter <= maxLiquidity, + 'LO', + { liquidityGrossAfter, maxLiquidity }, + 'liquidityGrossAfter <= maxLiquidity', + ); + + const flipped = (liquidityGrossAfter == 0n) != (liquidityGrossBefore == 0n); + + if (liquidityGrossBefore == 0n) { + info.initialized = true; + } + + info.liquidityGross = liquidityGrossAfter; + + info.liquidityNet = upper + ? BigInt.asIntN( + 128, + BigInt.asIntN(256, info.liquidityNet) - liquidityDelta, + ) + : BigInt.asIntN( + 128, + BigInt.asIntN(256, info.liquidityNet) + liquidityDelta, + ); + return flipped; + } + + static clear(state: Pick, tick: bigint) { + delete state.ticks[Number(tick)]; + } + + static cross(ticks: Record, tick: bigint): bigint { + const info = ticks[Number(tick)]; + return info.liquidityNet; + } +} diff --git a/src/dex/solidly-v3/contract-math/TickBitMap.ts b/src/dex/solidly-v3/contract-math/TickBitMap.ts new file mode 100644 index 000000000..405cfa70e --- /dev/null +++ b/src/dex/solidly-v3/contract-math/TickBitMap.ts @@ -0,0 +1,123 @@ +import { BI_MAX_UINT8 } from '../../../bigint-constants'; +import { PoolState } from '../types'; +import { BitMath } from './BitMath'; +import { _require } from '../../../utils'; +import { DeepReadonly } from 'ts-essentials'; +import { + OUT_OF_RANGE_ERROR_POSTFIX, + TICK_BITMAP_BUFFER, + TICK_BITMAP_TO_USE, +} from '../constants'; + +function isWordPosOut( + wordPos: bigint, + startTickBitmap: bigint, + // For pricing we use wider range to check price impact. If function called from event + // it must always be within buffer + isPriceQuery: boolean, +) { + let lowerTickBitmapLimit; + let upperTickBitmapLimit; + + if (isPriceQuery) { + lowerTickBitmapLimit = + startTickBitmap - (TICK_BITMAP_BUFFER + TICK_BITMAP_TO_USE); + upperTickBitmapLimit = + startTickBitmap + (TICK_BITMAP_BUFFER + TICK_BITMAP_TO_USE); + } else { + lowerTickBitmapLimit = startTickBitmap - TICK_BITMAP_BUFFER; + upperTickBitmapLimit = startTickBitmap + TICK_BITMAP_BUFFER; + } + + _require( + wordPos >= lowerTickBitmapLimit && wordPos <= upperTickBitmapLimit, + `wordPos is out of safe state tickBitmap request range: ${OUT_OF_RANGE_ERROR_POSTFIX}`, + { wordPos }, + `wordPos >= LOWER_TICK_REQUEST_LIMIT && wordPos <= UPPER_TICK_REQUEST_LIMIT`, + ); +} + +export class TickBitMap { + static position(tick: bigint): [bigint, bigint] { + return [BigInt.asIntN(16, tick >> 8n), BigInt.asUintN(8, tick % 256n)]; + } + + static flipTick( + state: Pick, + tick: bigint, + tickSpacing: bigint, + ) { + _require( + tick % tickSpacing === 0n, + '', + { tick, tickSpacing }, + 'tick % tickSpacing == 0n,', + ); + const [wordPos, bitPos] = TickBitMap.position(tick / tickSpacing); + const mask = 1n << bitPos; + + // flipTick is used only in _updatePosition which is always state changing event + // Therefore it is never used in price query + isWordPosOut(wordPos, state.startTickBitmap, false); + + const stringWordPos = wordPos.toString(); + if (state.tickBitmap[stringWordPos] === undefined) { + state.tickBitmap[stringWordPos] = 0n; + } + + state.tickBitmap[stringWordPos] ^= mask; + } + + static nextInitializedTickWithinOneWord( + state: DeepReadonly>, + tick: bigint, + tickSpacing: bigint, + lte: boolean, + isPriceQuery: boolean, + ): [bigint, boolean] { + let compressed = tick / tickSpacing; + if (tick < 0n && tick % tickSpacing != 0n) compressed--; + + let next = 0n; + let initialized = false; + + if (lte) { + const [wordPos, bitPos] = TickBitMap.position(compressed); + const mask = (1n << bitPos) - 1n + (1n << bitPos); + + isWordPosOut(wordPos, state.startTickBitmap, isPriceQuery); + let tickBitmapValue = state.tickBitmap[wordPos.toString()]; + tickBitmapValue = tickBitmapValue === undefined ? 0n : tickBitmapValue; + + const masked = tickBitmapValue & mask; + + initialized = masked != 0n; + next = initialized + ? (compressed - + BigInt.asIntN(24, bitPos - BitMath.mostSignificantBit(masked))) * + tickSpacing + : (compressed - BigInt.asIntN(24, bitPos)) * tickSpacing; + } else { + // start from the word of the next tick, since the current tick state doesn't matter + const [wordPos, bitPos] = TickBitMap.position(compressed + 1n); + const mask = ~((1n << bitPos) - 1n); + + isWordPosOut(wordPos, state.startTickBitmap, isPriceQuery); + let tickBitmapValue = state.tickBitmap[wordPos.toString()]; + tickBitmapValue = tickBitmapValue === undefined ? 0n : tickBitmapValue; + + const masked = tickBitmapValue & mask; + + initialized = masked != 0n; + next = initialized + ? (compressed + + 1n + + BigInt.asIntN(24, BitMath.leastSignificantBit(masked) - bitPos)) * + tickSpacing + : (compressed + 1n + BigInt.asIntN(24, BI_MAX_UINT8 - bitPos)) * + tickSpacing; + } + + return [next, initialized]; + } +} diff --git a/src/dex/solidly-v3/contract-math/TickMath.ts b/src/dex/solidly-v3/contract-math/TickMath.ts new file mode 100644 index 000000000..5515ce15c --- /dev/null +++ b/src/dex/solidly-v3/contract-math/TickMath.ts @@ -0,0 +1,211 @@ +import { gt } from 'lodash'; +import { BI_MAX_UINT256 } from '../../../bigint-constants'; +import { _gt } from './utils'; +import { _require } from '../../../utils'; + +export class TickMath { + static readonly MIN_TICK = -887272n; + static readonly MAX_TICK = -TickMath.MIN_TICK; + static readonly MIN_SQRT_RATIO = 4295128739n; + static readonly MAX_SQRT_RATIO = + 1461446703485210103287273052203988822378723970342n; + + static getSqrtRatioAtTick(tick: bigint): bigint { + const absTick = + tick < 0n + ? BigInt.asUintN(256, -BigInt.asIntN(256, tick)) + : BigInt.asUintN(256, BigInt.asIntN(256, tick)); + _require( + absTick <= BigInt.asUintN(256, TickMath.MAX_TICK), + 'T', + { absTick }, + 'absTick <= BigInt.asUintN(256, TickMath.MAX_TICK)', + ); + + let ratio = + (absTick & 0x1n) !== 0n + ? 0xfffcb933bd6fad37aa2d162d1a594001n + : 0x100000000000000000000000000000000n; + if ((absTick & 0x2n) !== 0n) + ratio = (ratio * 0xfff97272373d413259a46990580e213an) >> 128n; + if ((absTick & 0x4n) !== 0n) + ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdccn) >> 128n; + if ((absTick & 0x8n) !== 0n) + ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0n) >> 128n; + if ((absTick & 0x10n) !== 0n) + ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644n) >> 128n; + if ((absTick & 0x20n) !== 0n) + ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0n) >> 128n; + if ((absTick & 0x40n) !== 0n) + ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861n) >> 128n; + if ((absTick & 0x80n) !== 0n) + ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053n) >> 128n; + if ((absTick & 0x100n) !== 0n) + ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4n) >> 128n; + if ((absTick & 0x200n) !== 0n) + ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54n) >> 128n; + if ((absTick & 0x400n) !== 0n) + ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3n) >> 128n; + if ((absTick & 0x800n) !== 0n) + ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9n) >> 128n; + if ((absTick & 0x1000n) !== 0n) + ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825n) >> 128n; + if ((absTick & 0x2000n) !== 0n) + ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5n) >> 128n; + if ((absTick & 0x4000n) !== 0n) + ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7n) >> 128n; + if ((absTick & 0x8000n) !== 0n) + ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6n) >> 128n; + if ((absTick & 0x10000n) !== 0n) + ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9n) >> 128n; + if ((absTick & 0x20000n) !== 0n) + ratio = (ratio * 0x5d6af8dedb81196699c329225ee604n) >> 128n; + if ((absTick & 0x40000n) !== 0n) + ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98n) >> 128n; + if ((absTick & 0x80000n) !== 0n) + ratio = (ratio * 0x48a170391f7dc42444e8fa2n) >> 128n; + + if (tick > 0) ratio = BI_MAX_UINT256 / ratio; + return BigInt.asUintN( + 160, + (ratio >> 32n) + (ratio % (1n << 32n) == 0n ? 0n : 1n), + ); + } + + static getTickAtSqrtRatio(sqrtPriceX96: bigint): bigint { + _require( + sqrtPriceX96 >= TickMath.MIN_SQRT_RATIO && + sqrtPriceX96 < TickMath.MAX_SQRT_RATIO, + 'R', + { sqrtPriceX96 }, + 'sqrtPriceX96 >= TickMath.MIN_SQRT_RATIO && sqrtPriceX96 < TickMath.MAX_SQRT_RATIO', + ); + + let ratio = BigInt.asUintN(256, sqrtPriceX96) << 32n; + + let r = ratio; + let msb = 0n; + + let f = _gt(r, 0xffffffffffffffffffffffffffffffffn) << 7n; + msb = msb | f; + r = r >> f; + + f = _gt(r, 0xffffffffffffffffn) << 6n; + msb = msb | f; + r = r >> f; + + f = _gt(r, 0xffffffffn) << 5n; + msb = msb | f; + r = r >> f; + + f = _gt(r, 0xffffn) << 4n; + msb = msb | f; + r = r >> f; + + f = _gt(r, 0xffn) << 3n; + msb = msb | f; + r = r >> f; + + f = _gt(r, 0xfn) << 2n; + msb = msb | f; + r = r >> f; + + f = _gt(r, 0x3n) << 1n; + msb = msb | f; + r = r >> f; + + f = _gt(r, 0x1n); + msb = msb | f; + + if (msb >= 128n) r = ratio >> (msb - 127n); + else r = ratio << (127n - msb); + + let log_2 = (BigInt.asIntN(256, msb) - 128n) << 64n; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 63n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 62n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 61n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 60n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 59n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 58n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 57n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 56n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 55n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 54n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 53n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 52n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 51n); + r = r >> f; + + r = (r * r) >> 127n; + f = r >> 128n; + log_2 = log_2 | (f << 50n); + + const log_sqrt10001 = log_2 * 255738958999603826347141n; // 128.128 number + + const tickLow = BigInt.asIntN( + 24, + (log_sqrt10001 - 3402992956809132418596140100660247210n) >> 128n, + ); + const tickHi = BigInt.asIntN( + 24, + (log_sqrt10001 + 291339464771989622907027621153398088495n) >> 128n, + ); + + return tickLow === tickHi + ? tickLow + : TickMath.getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 + ? tickHi + : tickLow; + } +} diff --git a/src/dex/solidly-v3/contract-math/UnsafeMath.ts b/src/dex/solidly-v3/contract-math/UnsafeMath.ts new file mode 100644 index 000000000..aebd7c579 --- /dev/null +++ b/src/dex/solidly-v3/contract-math/UnsafeMath.ts @@ -0,0 +1,5 @@ +export class UnsafeMath { + static divRoundingUp(x: bigint, y: bigint) { + return (x + y - 1n) / y; + } +} diff --git a/src/dex/solidly-v3/contract-math/uniswap-v3-math.ts b/src/dex/solidly-v3/contract-math/uniswap-v3-math.ts new file mode 100644 index 000000000..2cefd877a --- /dev/null +++ b/src/dex/solidly-v3/contract-math/uniswap-v3-math.ts @@ -0,0 +1,562 @@ +import _ from 'lodash'; +import { OutputResult, PoolState, Slot0, TickInfo } from '../types'; +import { LiquidityMath } from './LiquidityMath'; +import { SqrtPriceMath } from './SqrtPriceMath'; +import { SwapMath } from './SwapMath'; +import { Tick } from './Tick'; +import { TickBitMap } from './TickBitMap'; +import { TickMath } from './TickMath'; +import { _require } from '../../../utils'; +import { DeepReadonly } from 'ts-essentials'; +import { NumberAsString, SwapSide } from '@paraswap/core'; +import { BI_MAX_INT } from '../../../bigint-constants'; +import { + MAX_PRICING_COMPUTATION_STEPS_ALLOWED, + OUT_OF_RANGE_ERROR_POSTFIX, +} from '../constants'; + +type ModifyPositionParams = { + tickLower: bigint; + tickUpper: bigint; + liquidityDelta: bigint; +}; + +export type PriceComputationState = { + amountSpecifiedRemaining: bigint; + amountCalculated: bigint; + sqrtPriceX96: bigint; + tick: bigint; + protocolFee: bigint; + liquidity: bigint; + isFirstCycleState: boolean; +}; + +export type PriceComputationCache = { + tickCount: number; +}; + +export function _updatePriceComputationObjects< + T extends PriceComputationState | PriceComputationCache, +>(toUpdate: T, updateBy: T) { + for (const k of Object.keys(updateBy) as (keyof T)[]) { + toUpdate[k] = updateBy[k]; + } +} + +function _priceComputationCycles( + poolState: DeepReadonly, + ticksCopy: Record, + slot0Start: Slot0, + state: PriceComputationState, + cache: PriceComputationCache, + sqrtPriceLimitX96: bigint, + zeroForOne: boolean, + exactInput: boolean, +): [ + // result + PriceComputationState, + // Latest calculated full cycle state we can use for bigger amounts + { + latestFullCycleState: PriceComputationState; + latestFullCycleCache: PriceComputationCache; + }, +] { + const latestFullCycleState: PriceComputationState = { ...state }; + + if (cache.tickCount == 0) { + cache.tickCount = 1; + } + const latestFullCycleCache: PriceComputationCache = { ...cache }; + + // We save tick before any change. Later we use this to restore + // state before last step + let lastTicksCopy: { index: number; tick: TickInfo } | undefined; + + let i = 0; + for ( + ; + state.amountSpecifiedRemaining !== 0n && + state.sqrtPriceX96 !== sqrtPriceLimitX96; + ++i + ) { + if ( + latestFullCycleCache.tickCount + i > + MAX_PRICING_COMPUTATION_STEPS_ALLOWED + ) { + state.amountSpecifiedRemaining = 0n; + state.amountCalculated = 0n; + break; + } + + const step = { + sqrtPriceStartX96: 0n, + tickNext: 0n, + initialized: false, + sqrtPriceNextX96: 0n, + amountIn: 0n, + amountOut: 0n, + feeAmount: 0n, + }; + + step.sqrtPriceStartX96 = state.sqrtPriceX96; + + try { + [step.tickNext, step.initialized] = + TickBitMap.nextInitializedTickWithinOneWord( + poolState, + state.tick, + poolState.tickSpacing, + zeroForOne, + true, + ); + } catch (e) { + if ( + e instanceof Error && + e.message.endsWith(OUT_OF_RANGE_ERROR_POSTFIX) + ) { + state.amountSpecifiedRemaining = 0n; + state.amountCalculated = 0n; + break; + } + throw e; + } + + if (step.tickNext < TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK; + } else if (step.tickNext > TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK; + } + + step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext); + + const swapStepResult = SwapMath.computeSwapStep( + state.sqrtPriceX96, + ( + zeroForOne + ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 + : step.sqrtPriceNextX96 > sqrtPriceLimitX96 + ) + ? sqrtPriceLimitX96 + : step.sqrtPriceNextX96, + state.liquidity, + state.amountSpecifiedRemaining, + poolState.slot0.fee, + ); + + state.sqrtPriceX96 = swapStepResult.sqrtRatioNextX96; + step.amountIn = swapStepResult.amountIn; + step.amountOut = swapStepResult.amountOut; + step.feeAmount = swapStepResult.feeAmount; + + if (exactInput) { + state.amountSpecifiedRemaining -= step.amountIn + step.feeAmount; + state.amountCalculated = state.amountCalculated - step.amountOut; + } else { + state.amountSpecifiedRemaining += step.amountOut; + state.amountCalculated = + state.amountCalculated + step.amountIn + step.feeAmount; + } + + if (state.sqrtPriceX96 === step.sqrtPriceNextX96) { + if (step.initialized) { + if (state.amountSpecifiedRemaining === 0n) { + const castTickNext = Number(step.tickNext); + lastTicksCopy = { + index: castTickNext, + tick: { ...ticksCopy[castTickNext] }, + }; + } + + let liquidityNet = Tick.cross(ticksCopy, step.tickNext); + if (zeroForOne) liquidityNet = -liquidityNet; + + state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet); + } + + state.tick = zeroForOne ? step.tickNext - 1n : step.tickNext; + } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { + state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96); + } + + if (state.amountSpecifiedRemaining !== 0n) { + _updatePriceComputationObjects(latestFullCycleState, state); + _updatePriceComputationObjects(latestFullCycleCache, cache); + // If it last cycle, check if ticks were changed and then restore previous state + // for next calculations + } else if (lastTicksCopy !== undefined) { + ticksCopy[lastTicksCopy.index] = lastTicksCopy.tick; + } + } + + if (i > 1) { + latestFullCycleCache.tickCount += i - 1; + } + + if (state.amountSpecifiedRemaining !== 0n) { + state.amountSpecifiedRemaining = 0n; + state.amountCalculated = 0n; + } + + return [state, { latestFullCycleState, latestFullCycleCache }]; +} + +class UniswapV3Math { + queryOutputs( + poolState: DeepReadonly, + // Amounts must increase + amounts: bigint[], + zeroForOne: boolean, + side: SwapSide, + ): OutputResult { + const slot0Start = poolState.slot0; + + const isSell = side === SwapSide.SELL; + + // While calculating, ticks are changing, so to not change the actual state, + // we use copy + const ticksCopy = _.cloneDeep(poolState.ticks); + + const sqrtPriceLimitX96 = zeroForOne + ? TickMath.MIN_SQRT_RATIO + 1n + : TickMath.MAX_SQRT_RATIO - 1n; + + const cache: PriceComputationCache = { + tickCount: 0, + }; + + const state: PriceComputationState = { + // Will be overwritten later + amountSpecifiedRemaining: 0n, + amountCalculated: 0n, + sqrtPriceX96: slot0Start.sqrtPriceX96, + tick: slot0Start.tick, + protocolFee: 0n, + liquidity: poolState.liquidity, + isFirstCycleState: true, + }; + + let isOutOfRange = false; + let previousAmount = 0n; + + const outputs = new Array(amounts.length); + const tickCounts = new Array(amounts.length); + for (const [i, amount] of amounts.entries()) { + if (amount === 0n) { + outputs[i] = 0n; + tickCounts[i] = 0; + continue; + } + + const amountSpecified = isSell + ? BigInt.asIntN(256, amount) + : -BigInt.asIntN(256, amount); + + if (state.isFirstCycleState) { + // Set first non zero amount + state.amountSpecifiedRemaining = amountSpecified; + state.isFirstCycleState = false; + } else { + state.amountSpecifiedRemaining = + amountSpecified - (previousAmount - state.amountSpecifiedRemaining); + } + + const exactInput = amountSpecified > 0n; + + _require( + zeroForOne + ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && + sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO + : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && + sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO, + 'SPL', + { zeroForOne, sqrtPriceLimitX96, slot0Start }, + 'zeroForOne ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO', + ); + + if (!isOutOfRange) { + const [finalState, { latestFullCycleState, latestFullCycleCache }] = + _priceComputationCycles( + poolState, + ticksCopy, + slot0Start, + state, + cache, + sqrtPriceLimitX96, + zeroForOne, + exactInput, + ); + if ( + finalState.amountSpecifiedRemaining === 0n && + finalState.amountCalculated === 0n + ) { + isOutOfRange = true; + outputs[i] = 0n; + tickCounts[i] = 0; + continue; + } + + // We use it on next step to correct state.amountSpecifiedRemaining + previousAmount = amountSpecified; + + // First extract calculated values + const [amount0, amount1] = + zeroForOne === exactInput + ? [ + amountSpecified - finalState.amountSpecifiedRemaining, + finalState.amountCalculated, + ] + : [ + finalState.amountCalculated, + amountSpecified - finalState.amountSpecifiedRemaining, + ]; + + // Update for next amount + _updatePriceComputationObjects(state, latestFullCycleState); + _updatePriceComputationObjects(cache, latestFullCycleCache); + + if (isSell) { + outputs[i] = BigInt.asUintN(256, -(zeroForOne ? amount1 : amount0)); + tickCounts[i] = latestFullCycleCache.tickCount; + continue; + } else { + outputs[i] = zeroForOne + ? BigInt.asUintN(256, amount0) + : BigInt.asUintN(256, amount1); + tickCounts[i] = latestFullCycleCache.tickCount; + continue; + } + } else { + outputs[i] = 0n; + tickCounts[i] = 0; + } + } + + return { + outputs, + tickCounts, + }; + } + + swapFromEvent( + poolState: PoolState, + newSqrtPriceX96: bigint, + newTick: bigint, + newLiquidity: bigint, + zeroForOne: boolean, + ): void { + const slot0Start = poolState.slot0; + + const cache = { + liquidityStart: poolState.liquidity, + blockTimestamp: this._blockTimestamp(poolState), + feeProtocol: 0n, + secondsPerLiquidityCumulativeX128: 0n, + tickCumulative: 0n, + computedLatestObservation: false, + }; + + const state = { + // Because I don't have the exact amount user used, set this number to MAX_NUMBER to proceed + // with calculations. I think it is not a problem since in loop I don't rely on this value + amountSpecifiedRemaining: BI_MAX_INT, + amountCalculated: 0n, + sqrtPriceX96: slot0Start.sqrtPriceX96, + tick: slot0Start.tick, + protocolFee: 0n, + liquidity: cache.liquidityStart, + }; + + // Because I didn't have all variables, adapted loop stop with state.tick !== newTick + // condition. This cycle need only to calculate Tick.cross() function values + // It means that we are interested in cycling only if state.tick !== newTick + // When they become equivalent, we proceed with state updating part as normal + // And if assumptions regarding this cycle are correct, we don't need to process + // the last cycle when state.tick === newTick + while (state.tick !== newTick && state.sqrtPriceX96 !== newSqrtPriceX96) { + const step = { + sqrtPriceStartX96: 0n, + tickNext: 0n, + initialized: false, + sqrtPriceNextX96: 0n, + amountIn: 0n, + amountOut: 0n, + feeAmount: 0n, + }; + + step.sqrtPriceStartX96 = state.sqrtPriceX96; + + [step.tickNext, step.initialized] = + TickBitMap.nextInitializedTickWithinOneWord( + poolState, + state.tick, + poolState.tickSpacing, + zeroForOne, + false, + ); + + if (step.tickNext < TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK; + } else if (step.tickNext > TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK; + } + + step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext); + + const swapStepResult = SwapMath.computeSwapStep( + state.sqrtPriceX96, + ( + zeroForOne + ? step.sqrtPriceNextX96 < newSqrtPriceX96 + : step.sqrtPriceNextX96 > newSqrtPriceX96 + ) + ? newSqrtPriceX96 + : step.sqrtPriceNextX96, + state.liquidity, + state.amountSpecifiedRemaining, + poolState.slot0.fee, + ); + + state.sqrtPriceX96 = swapStepResult.sqrtRatioNextX96; + + if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { + if (step.initialized) { + let liquidityNet = Tick.cross(poolState.ticks, step.tickNext); + + if (zeroForOne) liquidityNet = -liquidityNet; + + state.liquidity = LiquidityMath.addDelta( + state.liquidity, + liquidityNet, + ); + } + + state.tick = zeroForOne ? step.tickNext - 1n : step.tickNext; + } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { + state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96); + } + } + + if (slot0Start.tick !== newTick) { + [poolState.slot0.sqrtPriceX96, poolState.slot0.tick] = [ + newSqrtPriceX96, + newTick, + ]; + } else { + poolState.slot0.sqrtPriceX96 = newSqrtPriceX96; + } + + if (poolState.liquidity !== newLiquidity) + poolState.liquidity = newLiquidity; + } + + _modifyPosition( + state: PoolState, + params: ModifyPositionParams, + ): [bigint, bigint] { + const _slot0 = state.slot0; + + this._updatePosition( + state, + params.tickLower, + params.tickUpper, + params.liquidityDelta, + ); + + let amount0 = 0n; + let amount1 = 0n; + if (params.liquidityDelta !== 0n) { + if (_slot0.tick < params.tickLower) { + amount0 = SqrtPriceMath._getAmount0DeltaO( + TickMath.getSqrtRatioAtTick(params.tickLower), + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta, + ); + } else if (_slot0.tick < params.tickUpper) { + const liquidityBefore = state.liquidity; + + amount0 = SqrtPriceMath._getAmount0DeltaO( + _slot0.sqrtPriceX96, + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta, + ); + amount1 = SqrtPriceMath._getAmount1DeltaO( + TickMath.getSqrtRatioAtTick(params.tickLower), + _slot0.sqrtPriceX96, + params.liquidityDelta, + ); + + state.liquidity = LiquidityMath.addDelta( + liquidityBefore, + params.liquidityDelta, + ); + } else { + amount1 = SqrtPriceMath._getAmount1DeltaO( + TickMath.getSqrtRatioAtTick(params.tickLower), + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta, + ); + } + } + return [amount0, amount1]; + } + + private _isTickToProcess(state: PoolState, tick: bigint): boolean { + return tick >= state.lowestKnownTick && tick <= state.highestKnownTick; + } + + private _updatePosition( + state: PoolState, + tickLower: bigint, + tickUpper: bigint, + liquidityDelta: bigint, + ): void { + // if we need to update the ticks, do it + let flippedLower = false; + let flippedUpper = false; + if (liquidityDelta !== 0n) { + const time = this._blockTimestamp(state); + + if (this._isTickToProcess(state, tickLower)) { + flippedLower = Tick.update( + state, + tickLower, + liquidityDelta, + false, + state.maxLiquidityPerTick, + ); + } + if (this._isTickToProcess(state, tickUpper)) { + flippedUpper = Tick.update( + state, + tickUpper, + liquidityDelta, + true, + state.maxLiquidityPerTick, + ); + } + + if (flippedLower) { + TickBitMap.flipTick(state, tickLower, state.tickSpacing); + } + if (flippedUpper) { + TickBitMap.flipTick(state, tickUpper, state.tickSpacing); + } + } + + // clear any tick data that is no longer needed + if (liquidityDelta < 0n) { + if (flippedLower) { + Tick.clear(state, tickLower); + } + if (flippedUpper) { + Tick.clear(state, tickUpper); + } + } + } + + private _blockTimestamp(state: DeepReadonly) { + return BigInt.asUintN(32, state.blockTimestamp); + } +} + +export const uniswapV3Math = new UniswapV3Math(); diff --git a/src/dex/solidly-v3/contract-math/utils.ts b/src/dex/solidly-v3/contract-math/utils.ts new file mode 100644 index 000000000..6f0960ccd --- /dev/null +++ b/src/dex/solidly-v3/contract-math/utils.ts @@ -0,0 +1,50 @@ +import { NumberAsString } from '@paraswap/core'; +import { + TickBitMapMappingsWithBigNumber, + TickInfo, + TickInfoMappingsWithBigNumber, +} from '../types'; +import { bigIntify } from '../../../utils'; + +export function _mulmod(x: bigint, y: bigint, m: bigint): bigint { + return m === 0n ? 0n : (x * y) % m; +} + +export function _lt(x: bigint, y: bigint) { + return x < y ? 1n : 0n; +} + +export function _gt(x: bigint, y: bigint) { + return x > y ? 1n : 0n; +} + +export function _reduceTickBitmap( + tickBitmap: Record, + tickBitmapToReduce: TickBitMapMappingsWithBigNumber[], +) { + return tickBitmapToReduce.reduce>( + (acc, curr) => { + const { index, value } = curr; + acc[index] = bigIntify(value); + return acc; + }, + tickBitmap, + ); +} + +export function _reduceTicks( + ticks: Record, + ticksToReduce: TickInfoMappingsWithBigNumber[], +) { + return ticksToReduce.reduce>((acc, curr) => { + const { index, value } = curr; + if (value.initialized) { + acc[index] = { + liquidityGross: bigIntify(value.liquidityGross), + liquidityNet: bigIntify(value.liquidityNet), + initialized: value.initialized, + }; + } + return acc; + }, ticks); +} diff --git a/src/dex/solidly-v3/solidly-v3-e2e.test.ts b/src/dex/solidly-v3/solidly-v3-e2e.test.ts new file mode 100644 index 000000000..2bf696de7 --- /dev/null +++ b/src/dex/solidly-v3/solidly-v3-e2e.test.ts @@ -0,0 +1,124 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { testE2E } from '../../../tests/utils-e2e'; +import { + Tokens, + Holders, + NativeTokenSymbols, +} from '../../../tests/constants-e2e'; +import { Network, ContractMethod, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; + +function testForNetwork( + network: Network, + dexKey: string, + pairs: { name: string; sellAmount: string; buyAmount: string }[][], +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + 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('SolidlyV3 E2E', () => { + const dexKey = 'SolidlyV3'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + const pairs = [ + [ + { + name: 'ETH', + sellAmount: '110000000000000000', + buyAmount: '1100000000', + }, + { + name: 'USDC', + sellAmount: '400000000', + buyAmount: '4000000', + }, + ], + [ + { + name: 'WETH', + sellAmount: '1100000000000000000', + buyAmount: '1100000000', + }, + { + name: 'USDC', + sellAmount: '400000000', + buyAmount: '4000000', + }, + ], + [ + { + name: 'USDC', + sellAmount: '400000000', + buyAmount: '4000000', + }, + { + name: 'USDT', + sellAmount: '500000000', + buyAmount: '5000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); +}); diff --git a/src/dex/solidly-v3/solidly-v3-events.test.ts b/src/dex/solidly-v3/solidly-v3-events.test.ts new file mode 100644 index 000000000..6ad725d7a --- /dev/null +++ b/src/dex/solidly-v3/solidly-v3-events.test.ts @@ -0,0 +1,146 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { SolidlyV3EventPool } from './solidly-v3-pool'; +import { Network } from '../../constants'; +import { Address } from '../../types'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { testEventSubscriber } from '../../../tests/utils-events'; +import { PoolState } from './types'; +import { SolidlyV3Config } from './config'; +import { AbiItem } from 'web3-utils'; +import { Interface } from '@ethersproject/abi'; +import StateMulticallABI from '../../abi/solidly-v3/SolidlyV3StateMulticall.abi.json'; +import { decodeStateMultiCallResultWithRelativeBitmaps } from './utils'; +import ERC20ABI from '../../abi/erc20.json'; + +/* + README + ====== + + This test script adds unit tests for SolidlyV3 event based + system. This is done by fetching the state on-chain before the + event block, manually pushing the block logs to the event-subscriber, + comparing the local state with on-chain state. + + Most of the logic for testing is abstracted by `testEventSubscriber`. + You need to do two things to make the tests work: + + 1. Fetch the block numbers where certain events were released. You + can modify the `./scripts/fetch-event-blocknumber.ts` to get the + block numbers for different events. Make sure to get sufficient + number of blockNumbers to cover all possible cases for the event + mutations. + + 2. Complete the implementation for fetchPoolState function. The + function should fetch the on-chain state of the event subscriber + using just the blocknumber. + + The template tests only include the test for a single event + subscriber. There can be cases where multiple event subscribers + exist for a single DEX. In such cases additional tests should be + added. + + You can run this individual test script by running: + `npx jest src/dex//-events.test.ts` + + (This comment should be removed from the final implementation) +*/ + +jest.setTimeout(50 * 1000); +const dexKey = 'SolidlyV3'; +const network = Network.MAINNET; +const config = SolidlyV3Config[dexKey][network]; + +async function fetchPoolStateFromContract( + solidlyV3Pool: SolidlyV3EventPool, + blockNumber: number, + poolAddress: string, +): Promise { + const message = `SolidlyV3: ${poolAddress} blockNumber ${blockNumber}`; + console.log(`Fetching state ${message}`); + // Be careful to not request state prior to contract deployment + // Otherwise need to use manual state sourcing from multicall + // We had that mechanism, but removed it with this commit + // You can restore it, but better just to find block after state multicall + // deployment + const state = solidlyV3Pool.generateState(blockNumber); + console.log(`Done ${message}`); + return state; +} + +// eventName -> blockNumbers +type EventMappings = Record; + +describe('SolidlyV3 Event', function () { + const poolAddress = '0x831BF48183B999fDe45294b14B55199072f0801B'; + const token0 = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; + const token1 = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; + + const blockNumbers: { [eventName: string]: number[] } = { + // topic0 - 0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67 + ['Swap']: [ + 18600853, 18616106, 18616441, 18619851, 18622968, 18625121, 18626415, + 18627297, 18630574, 18718946, 18726197, 18730166, 18758602, + ], + ['Burn']: [18737937], + ['Mint']: [18708230, 18709265, 18737969], + ['Collect']: [18751597], + //topic0 0x0eb63f4a36d6bdeee05aa00020a97d80c3e84f1b5b3ebf345fb67262e62b0f33 + ['SetFee']: [ + 18427614, 18423073, 18423143, 18425324, 18430069, 18442932, 18442934, + ], + ['Flash']: [18758872], + ['CollectProtocol']: [18530529, 18577493], + }; + + describe('SolidlyV3EventPool', function () { + Object.keys(blockNumbers).forEach((event: string) => { + blockNumbers[event].forEach((blockNumber: number) => { + it(`${event}:${blockNumber} - should return correct state`, async function () { + const dexHelper = new DummyDexHelper(network); + // await dexHelper.init(); + + const logger = dexHelper.getLogger(dexKey); + + const solidlyV3Pool = new SolidlyV3EventPool( + dexHelper, + dexKey, + new dexHelper.web3Provider.eth.Contract( + StateMulticallABI as AbiItem[], + config.stateMulticall, + ), + decodeStateMultiCallResultWithRelativeBitmaps, + new Interface(ERC20ABI), + config.factory, + 10n, + token0, + token1, + logger, + undefined, + config.initHash, + ); + + // It is done in generateState. But here have to make it manually + solidlyV3Pool.poolAddress = poolAddress.toLowerCase(); + solidlyV3Pool.addressesSubscribed[0] = poolAddress; + + await testEventSubscriber( + solidlyV3Pool, + solidlyV3Pool.addressesSubscribed, + (_blockNumber: number) => + fetchPoolStateFromContract( + solidlyV3Pool, + _blockNumber, + poolAddress, + ), + blockNumber, + `${dexKey}_${poolAddress}`, + dexHelper.provider, + ); + }); + }); + }); + }); +}); diff --git a/src/dex/solidly-v3/solidly-v3-factory.ts b/src/dex/solidly-v3/solidly-v3-factory.ts new file mode 100644 index 000000000..21a40ab46 --- /dev/null +++ b/src/dex/solidly-v3/solidly-v3-factory.ts @@ -0,0 +1,73 @@ +import { Interface } from '@ethersproject/abi'; +import { DeepReadonly } from 'ts-essentials'; +import FactoryABI from '../../abi/uniswap-v3/UniswapV3Factory.abi.json'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { Address, Log, Logger } from '../../types'; +import { LogDescription } from 'ethers/lib/utils'; +import { FactoryState } from './types'; + +export type OnPoolCreatedCallback = ({ + token0, + token1, + tickSpacing, +}: { + token0: string; + token1: string; + tickSpacing: bigint; +}) => Promise; + +/* + * "Stateless" event subscriber in order to capture "PoolCreated" event on new pools created. + * State is present, but it's a placeholder to actually make the events reach handlers (if there's no previous state - `processBlockLogs` is not called) + */ +export class SolidlyV3Factory extends StatefulEventSubscriber { + handlers: { + [event: string]: (event: any) => Promise; + } = {}; + + logDecoder: (log: Log) => any; + + public readonly factoryIface = new Interface(FactoryABI); + + constructor( + readonly dexHelper: IDexHelper, + parentName: string, + protected readonly factoryAddress: Address, + logger: Logger, + protected readonly onPoolCreated: OnPoolCreatedCallback, + mapKey: string = '', + ) { + super(parentName, `${parentName} Factory`, dexHelper, logger, true, mapKey); + + this.addressesSubscribed = [factoryAddress]; + + this.logDecoder = (log: Log) => this.factoryIface.parseLog(log); + + this.handlers['PoolCreated'] = this.handleNewPool.bind(this); + } + + generateState(): FactoryState { + return {}; + } + + protected async processLog( + _: DeepReadonly, + log: Readonly, + ): Promise { + const event = this.logDecoder(log); + if (event.name in this.handlers) { + await this.handlers[event.name](event); + } + + return {}; + } + + async handleNewPool(event: LogDescription) { + const token0 = event.args.token0.toLowerCase(); + const token1 = event.args.token1.toLowerCase(); + const tickSpacing = BigInt(event.args.tickSpacing); + + await this.onPoolCreated({ token0, token1, tickSpacing }); + } +} diff --git a/src/dex/solidly-v3/solidly-v3-integration.test.ts b/src/dex/solidly-v3/solidly-v3-integration.test.ts new file mode 100644 index 000000000..c5f961dc9 --- /dev/null +++ b/src/dex/solidly-v3/solidly-v3-integration.test.ts @@ -0,0 +1,358 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Interface, Result } from '@ethersproject/abi'; +import { DummyDexHelper, IDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { SolidlyV3 } from './solidly-v3'; +import { MIN_SQRT_RATIO, MAX_SQRT_RATIO } from './constants'; +import { + checkPoolPrices, + checkPoolsLiquidity, + checkConstantPoolPrices, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import { Address } from '@paraswap/core'; +import SolidlyV3PoolABI from '../../abi/solidly-v3/SolidlyV3Pool.abi.json'; + +/* + README + ====== + + This test script adds tests for SolidlyV3 general integration + with the DEX interface. The test cases below are example tests. + It is recommended to add tests which cover SolidlyV3 specific + logic. + + You can run this individual test script by running: + `npx jest src/dex//-integration.test.ts` + + (This comment should be removed from the final implementation) +*/ + +const network = Network.MAINNET; +const dexHelper = new DummyDexHelper(network); + +const WETH = Tokens[network]['WETH']; +const USDC = Tokens[network]['USDC']; + +const amounts = [0n, 1n * BI_POWS[18], 2n * BI_POWS[18]]; +// Sell WETH to receive 1000 USDC, 2000 UDST, and 3000 USDC +const amountsBuy = [ + 0n, + 1000n * BI_POWS[6], + 2000n * BI_POWS[6], + 3000n * BI_POWS[6], +]; + +function getReaderCalldata( + exchangeAddress: string, + readerIface: Interface, + amounts: bigint[], + funcName: string, + zeroForOne: boolean, + sqrtPriceLimitX96: bigint, + exactInput: boolean, +) { + return amounts.map(amount => ({ + target: exchangeAddress, + callData: readerIface.encodeFunctionData(funcName, [ + zeroForOne, + exactInput ? amount.toString() : `-${amount.toString()}`, + sqrtPriceLimitX96.toString(), + ]), + })); +} + +// We are only using this function to decode `quoteSwap` on SolidlyV3Pool. The output param we consider relevant +// depends on `zeroForOne` and `exactInput`. +function decodeReaderResult( + results: Result, + readerIface: Interface, + funcName: string, + zeroForOne: boolean, + exactInput: boolean, +) { + return results.map(result => { + const parsed = readerIface.decodeFunctionResult(funcName, result); + // zeroForOne determines whether we want to get amount0 or amount1 + let index; + if (zeroForOne == exactInput) { + index = 1; + } else { + index = 0; + } + return parsed[index]._hex[0] == '-' + ? BigInt(parsed[index]._hex.slice(1)) + : BigInt(parsed[index]._hex); + }); +} + +async function checkOnChainPricing( + dexHelper: IDexHelper, + blockNumber: number, + poolAddress: string, + prices: bigint[], + tokenIn: Address, + tokenOut: Address, + tickSpacing: bigint, + _amounts: bigint[], + exactInput: boolean, +) { + const readerIface = new Interface(SolidlyV3PoolABI); + + const sum = prices.reduce((acc, curr) => (acc += curr), 0n); + + if (sum === 0n) { + console.log( + `Prices were not calculated for tokenIn=${tokenIn}, tokenOut=${tokenOut}, tickSpacing=${tickSpacing.toString()}. Most likely price impact is too big for requested amount`, + ); + return false; + } + + const readerCallData = getReaderCalldata( + poolAddress, + readerIface, + _amounts.slice(1), + 'quoteSwap', + tokenIn < tokenOut, + tokenIn < tokenOut + ? MIN_SQRT_RATIO + BigInt(1) + : MAX_SQRT_RATIO - BigInt(1), + exactInput, + ); + + let readerResult; + try { + readerResult = ( + await dexHelper.multiContract.methods + .aggregate(readerCallData) + .call({}, blockNumber) + ).returnData; + } catch (e) { + console.log( + `Can not fetch on-chain pricing for tickSpacing ${tickSpacing}. It happens for low liquidity pools`, + e, + ); + return false; + } + + const expectedPrices = [0n].concat( + decodeReaderResult( + readerResult, + readerIface, + 'quoteSwap', + tokenIn < tokenOut, + exactInput, + ), + ); + + console.log('EXPECTED PRICES: ', expectedPrices); + + let firstZeroIndex = prices.slice(1).indexOf(0n); + + // we skipped first, so add +1 on result + firstZeroIndex = firstZeroIndex === -1 ? prices.length : firstZeroIndex; + + // Compare only the ones for which we were able to calculate prices + expect(prices.slice(0, firstZeroIndex)).toEqual( + expectedPrices.slice(0, firstZeroIndex), + ); + return true; +} + +async function testPricingOnNetwork( + solidlyV3: SolidlyV3, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], + funcNameToCheck: string, +) { + const networkTokens = Tokens[network]; + + const pools = await solidlyV3.getPoolIdentifiers( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await solidlyV3.getPricesVolume( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + if (solidlyV3.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey); + } +} + +describe('SolidlyV3', function () { + const dexKey = 'SolidlyV3'; + let blockNumber: number; + let solidlyV3: SolidlyV3; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const solidlyV3 = new SolidlyV3(network, dexKey, dexHelper); + }); + + describe('Mainnet', () => { + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + + // TODO: Put here token Symbol to check against + // Don't forget to update relevant tokens in constant-e2e.ts + const srcTokenSymbol = 'WETH'; + const destTokenSymbol = 'USDC'; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + solidlyV3 = new SolidlyV3(network, dexKey, dexHelper); + if (solidlyV3.initializePricing) { + await solidlyV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const pools = await solidlyV3.getPoolIdentifiers( + WETH, + USDC, + SwapSide.SELL, + blockNumber, + ); + console.log(`WETH <> USDC Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await solidlyV3.getPricesVolume( + WETH, + USDC, + amounts, + SwapSide.SELL, + blockNumber, + pools, + ); + console.log(`WETH <> USDC Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const tickSpacing = + solidlyV3.eventPools[price.poolIdentifier!]!.tickSpacing; + const res = await checkOnChainPricing( + dexHelper, + blockNumber, + solidlyV3.eventPools[price.poolIdentifier!]!.poolAddress, + price.prices, + WETH.address, + USDC.address, + tickSpacing, + amounts, + true, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + const pools = await solidlyV3.getPoolIdentifiers( + WETH, + USDC, + SwapSide.BUY, + blockNumber, + ); + console.log(`WETH <> USDC Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await solidlyV3.getPricesVolume( + WETH, + USDC, + amountsBuy, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log(`WETH <> USDC Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amountsBuy, SwapSide.BUY, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const tickSpacing = + solidlyV3.eventPools[price.poolIdentifier!]!.tickSpacing; + const res = await checkOnChainPricing( + dexHelper, + blockNumber, + solidlyV3.eventPools[price.poolIdentifier!]!.poolAddress, + price.prices, + WETH.address, + USDC.address, + tickSpacing, + amountsBuy, + false, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newSolidlyV3 = new SolidlyV3(network, dexKey, dexHelper); + // if (newSolidlyV3.updatePoolState) { + // await newSolidlyV3.updatePoolState(); + // } + const poolLiquidity = await newSolidlyV3.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newSolidlyV3.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); +}); diff --git a/src/dex/solidly-v3/solidly-v3-pool.ts b/src/dex/solidly-v3/solidly-v3-pool.ts new file mode 100644 index 000000000..e467d7c47 --- /dev/null +++ b/src/dex/solidly-v3/solidly-v3-pool.ts @@ -0,0 +1,494 @@ +import _ from 'lodash'; +import { Contract } from 'web3-eth-contract'; +import { Interface } from '@ethersproject/abi'; +import { ethers } from 'ethers'; +import { assert, DeepReadonly } from 'ts-essentials'; +import { Log, Logger, BlockHeader, Address } from '../../types'; +import { + InitializeStateOptions, + StatefulEventSubscriber, +} from '../../stateful-event-subscriber'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { + PoolState, + DecodedStateMultiCallResultWithRelativeBitmaps, + DecodeStateMultiCallFunc, +} from './types'; +import SolidlyV3PoolABI from '../../abi/solidly-v3/SolidlyV3Pool.abi.json'; +import { bigIntify, catchParseLogError, isSampled } from '../../utils'; +import { uniswapV3Math } from './contract-math/uniswap-v3-math'; +import { MultiCallParams } from '../../lib/multi-wrapper'; +import { + OUT_OF_RANGE_ERROR_POSTFIX, + TICK_BITMAP_BUFFER, + TICK_BITMAP_TO_USE, +} from './constants'; +import { TickBitMap } from './contract-math/TickBitMap'; +import { uint256ToBigInt } from '../../lib/decoders'; +import { decodeStateMultiCallResultWithRelativeBitmaps } from './utils'; +import { _reduceTickBitmap, _reduceTicks } from './contract-math/utils'; + +const FEES_TO_TICK_SPACING: Record = { + 500: 10n, +}; + +export class SolidlyV3EventPool extends StatefulEventSubscriber { + handlers: { + [event: string]: ( + event: any, + pool: PoolState, + log: Log, + blockHeader: Readonly, + ) => PoolState; + } = {}; + + logDecoder: (log: Log) => any; + + readonly token0: Address; + + readonly token1: Address; + + public _poolAddress?: Address; + + private _stateRequestCallData?: MultiCallParams< + bigint | DecodedStateMultiCallResultWithRelativeBitmaps + >[]; + + public readonly poolIface = new Interface(SolidlyV3PoolABI); + + public initFailed = false; + public initRetryAttemptCount = 0; + + // public readonly feeCodeAsString; + public readonly tickSpacingAsString: string; + + constructor( + readonly dexHelper: IDexHelper, + parentName: string, + readonly stateMultiContract: Contract, + readonly decodeStateMultiCallResultWithRelativeBitmaps: + | DecodeStateMultiCallFunc + | undefined, + readonly erc20Interface: Interface, + protected readonly factoryAddress: Address, + public readonly tickSpacing: bigint, + token0: Address, + token1: Address, + logger: Logger, + mapKey: string = '', + readonly poolInitCodeHash: string, + ) { + super( + parentName, + `${token0}_${token1}_${tickSpacing}`, + dexHelper, + logger, + true, + mapKey, + ); + // this.feeCodeAsString = feeCode.toString(); + this.tickSpacingAsString = tickSpacing.toString(); + this.token0 = token0.toLowerCase(); + this.token1 = token1.toLowerCase(); + this.logDecoder = (log: Log) => this.poolIface.parseLog(log); + this.addressesSubscribed = new Array
(1); + + // Add handlers + this.handlers['Swap'] = this.handleSwapEvent.bind(this); + this.handlers['Burn'] = this.handleBurnEvent.bind(this); + this.handlers['Mint'] = this.handleMintEvent.bind(this); + + // Wen need them to keep balance of the pool up to date + this.handlers['Collect'] = this.handleCollectEvent.bind(this); + // Almost the same as Collect, but for pool owners + this.handlers['CollectProtocol'] = this.handleCollectEvent.bind(this); + this.handlers['Flash'] = this.handleFlashEvent.bind(this); + this.handlers['SetFee'] = this.handleSetFeeEvent.bind(this); + } + + get poolAddress() { + if (this._poolAddress === undefined) { + this._poolAddress = this._computePoolAddress( + this.token0, + this.token1, + this.tickSpacing, + ); + } + return this._poolAddress; + } + + set poolAddress(address: Address) { + this._poolAddress = address.toLowerCase(); + } + + async initialize( + blockNumber: number, + options?: InitializeStateOptions, + ) { + await super.initialize(blockNumber, options); + } + + protected async processBlockLogs( + state: DeepReadonly, + logs: Readonly[], + blockHeader: Readonly, + ): Promise | null> { + const newState = await super.processBlockLogs(state, logs, blockHeader); + if (newState && !newState.isValid) { + return await this.generateState(blockHeader.number); + } + return newState; + } + + protected processLog( + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ): DeepReadonly | null { + try { + const event = this.logDecoder(log); + + const uniswapV3EventLoggingSampleRate = + this.dexHelper.config.data.uniswapV3EventLoggingSampleRate; + if ( + !this.dexHelper.config.isSlave && + uniswapV3EventLoggingSampleRate && + isSampled(uniswapV3EventLoggingSampleRate) + ) { + this.logger.info( + `event=${event.name} - block=${ + blockHeader.number + }. Log sampled at rate ${uniswapV3EventLoggingSampleRate * 100}%`, + ); + } + + if (event.name in this.handlers) { + // Because we have observations in array which is mutable by nature, there is a + // ts compile error: https://stackoverflow.com/questions/53412934/disable-allowing-assigning-readonly-types-to-non-readonly-types + // And there is no good workaround, so turn off the type checker for this line + const _state = _.cloneDeep(state) as PoolState; + try { + return this.handlers[event.name](event, _state, log, blockHeader); + } catch (e) { + if ( + e instanceof Error && + e.message.endsWith(OUT_OF_RANGE_ERROR_POSTFIX) + ) { + this.logger.warn( + `${this.parentName}: Pool ${this.poolAddress} on ${ + this.dexHelper.config.data.network + } is out of TickBitmap requested range. Re-query the state. ${JSON.stringify( + event, + )}`, + e, + ); + } else { + this.logger.error( + `${this.parentName}: Pool ${this.poolAddress}, ` + + `network=${this.dexHelper.config.data.network}: Unexpected ` + + `error while handling event on blockNumber=${blockHeader.number}, ` + + `blockHash=${blockHeader.hash} and parentHash=${ + blockHeader.parentHash + } for UniswapV3, ${JSON.stringify(event)}`, + e, + ); + } + _state.isValid = false; + return _state; + } + } + } catch (e) { + catchParseLogError(e, this.logger); + } + return null; // ignore unrecognized event + } + + private _getStateRequestCallData() { + if (!this._stateRequestCallData) { + const callData: MultiCallParams< + bigint | DecodedStateMultiCallResultWithRelativeBitmaps + >[] = [ + { + 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.stateMultiContract.options.address, + callData: this.stateMultiContract.methods + .getFullStateWithRelativeBitmaps( + this.factoryAddress, + this.token0, + this.token1, + this.tickSpacing, + this.getBitmapRangeToRequest(), + this.getBitmapRangeToRequest(), + ) + .encodeABI(), + decodeFunction: + this.decodeStateMultiCallResultWithRelativeBitmaps !== undefined + ? this.decodeStateMultiCallResultWithRelativeBitmaps + : decodeStateMultiCallResultWithRelativeBitmaps, + }, + ]; + + this._stateRequestCallData = callData; + } + return this._stateRequestCallData; + } + + getBitmapRangeToRequest() { + return TICK_BITMAP_TO_USE + TICK_BITMAP_BUFFER; + } + + async generateState(blockNumber: number): Promise> { + const callData = this._getStateRequestCallData(); + + const [resBalance0, resBalance1, resState] = + await this.dexHelper.multiWrapper.tryAggregate< + bigint | DecodedStateMultiCallResultWithRelativeBitmaps + >( + false, + callData, + blockNumber, + this.dexHelper.multiWrapper.defaultBatchSize, + false, + ); + + // Quite ugly solution, but this is the one that fits to current flow. + // I think UniswapV3 callbacks subscriptions are complexified for no reason. + // Need to be revisited later + assert(resState.success, 'Pool does not exist'); + + const [balance0, balance1, _state] = [ + resBalance0.returnData, + resBalance1.returnData, + resState.returnData, + ] as [bigint, bigint, DecodedStateMultiCallResultWithRelativeBitmaps]; + + const tickBitmap = {}; + const ticks = {}; + + _reduceTickBitmap(tickBitmap, _state.tickBitmap); + _reduceTicks(ticks, _state.ticks); + + const currentTick = bigIntify(_state.slot0.tick); + const tickSpacing = bigIntify(_state.tickSpacing); + const fee = bigIntify(_state.slot0.fee); + + const startTickBitmap = TickBitMap.position(currentTick / tickSpacing)[0]; + const requestedRange = this.getBitmapRangeToRequest(); + + return { + pool: _state.pool, + blockTimestamp: bigIntify(_state.blockTimestamp), + slot0: { + sqrtPriceX96: bigIntify(_state.slot0.sqrtPriceX96), + tick: currentTick, + fee: bigIntify(_state.slot0.fee), + }, + liquidity: bigIntify(_state.liquidity), + tickSpacing, + maxLiquidityPerTick: bigIntify(_state.maxLiquidityPerTick), + tickBitmap, + ticks, + isValid: true, + startTickBitmap, + lowestKnownTick: + (BigInt.asIntN(24, startTickBitmap - requestedRange) << 8n) * + tickSpacing, + highestKnownTick: + ((BigInt.asIntN(24, startTickBitmap + requestedRange) << 8n) + + BigInt.asIntN(24, 255n)) * + tickSpacing, + balance0, + balance1, + }; + } + + handleSwapEvent( + event: any, + pool: PoolState, + log: Log, + blockHeader: BlockHeader, + ) { + const newSqrtPriceX96 = bigIntify(event.args.sqrtPriceX96); + const amount0 = bigIntify(event.args.amount0); + const amount1 = bigIntify(event.args.amount1); + const newTick = bigIntify(event.args.tick); + const newLiquidity = bigIntify(event.args.liquidity); + pool.blockTimestamp = bigIntify(blockHeader.timestamp); + + if (amount0 <= 0n && amount1 <= 0n) { + this.logger.error( + `${this.parentName}: amount0 <= 0n && amount1 <= 0n for ` + + `${this.poolAddress} and ${blockHeader.number}. Check why it happened`, + ); + pool.isValid = false; + return pool; + } else { + const zeroForOne = amount0 > 0n; + + uniswapV3Math.swapFromEvent( + pool, + newSqrtPriceX96, + newTick, + newLiquidity, + zeroForOne, + ); + + if (zeroForOne) { + if (amount1 < 0n) { + pool.balance1 -= BigInt.asUintN(256, -amount1); + } else { + this.logger.error( + `In swapEvent for pool ${pool.pool} received incorrect values ${zeroForOne} and ${amount1}`, + ); + pool.isValid = false; + } + // This is not correct fully, because pool may get more tokens then it needs, but + // it is not accounted in internal state, it should be good enough + pool.balance0 += BigInt.asUintN(256, amount0); + } else { + if (amount0 < 0n) { + pool.balance0 -= BigInt.asUintN(256, -amount0); + } else { + this.logger.error( + `In swapEvent for pool ${pool.pool} received incorrect values ${zeroForOne} and ${amount0}`, + ); + pool.isValid = false; + } + pool.balance1 += BigInt.asUintN(256, amount1); + } + + return pool; + } + } + + handleBurnEvent( + event: any, + pool: PoolState, + log: Log, + blockHeader: BlockHeader, + ) { + const amount = bigIntify(event.args.amount); + const tickLower = bigIntify(event.args.tickLower); + const tickUpper = bigIntify(event.args.tickUpper); + pool.blockTimestamp = bigIntify(blockHeader.timestamp); + + uniswapV3Math._modifyPosition(pool, { + tickLower, + tickUpper, + liquidityDelta: -BigInt.asIntN(128, BigInt.asIntN(256, amount)), + }); + + // From this transaction I conclude that there is no balance change from + // Burn event: https://dashboard.tenderly.co/tx/mainnet/0xfccf5341147ac3ad0e66452273d12dfc3219e81f8fb369a6cdecfb24b9b9d078/logs + // And it aligns with UniswapV3 doc: + // https://github.com/Uniswap/v3-core/blob/05c10bf6d547d6121622ac51c457f93775e1df09/contracts/interfaces/pool/IUniswapV3PoolActions.sol#L59 + // It just updates positions and tokensOwed which may be requested calling collect + // So, we don't need to update pool.balances0 and pool.balances1 here + + return pool; + } + + handleMintEvent( + event: any, + pool: PoolState, + log: Log, + blockHeader: BlockHeader, + ) { + const amount = bigIntify(event.args.amount); + const tickLower = bigIntify(event.args.tickLower); + const tickUpper = bigIntify(event.args.tickUpper); + const amount0 = bigIntify(event.args.amount0); + const amount1 = bigIntify(event.args.amount1); + pool.blockTimestamp = bigIntify(blockHeader.timestamp); + + uniswapV3Math._modifyPosition(pool, { + tickLower, + tickUpper, + liquidityDelta: amount, + }); + + pool.balance0 += amount0; + pool.balance1 += amount1; + + return pool; + } + + handleCollectEvent( + event: any, + pool: PoolState, + log: Log, + blockHeader: BlockHeader, + ) { + const amount0 = bigIntify(event.args.amount0); + const amount1 = bigIntify(event.args.amount1); + pool.balance0 -= amount0; + pool.balance1 -= amount1; + pool.blockTimestamp = bigIntify(blockHeader.timestamp); + + return pool; + } + + handleFlashEvent( + event: any, + pool: PoolState, + log: Log, + blockHeader: BlockHeader, + ) { + const paid0 = bigIntify(event.args.paid0); + const paid1 = bigIntify(event.args.paid1); + pool.balance0 += paid0; + pool.balance1 += paid1; + pool.blockTimestamp = bigIntify(blockHeader.timestamp); + + return pool; + } + + handleSetFeeEvent( + event: any, + pool: PoolState, + log: Log, + blockHeader: BlockHeader, + ) { + const feeNew = bigIntify(event.args.feeNew); + pool.slot0.fee = feeNew; + pool.blockTimestamp = bigIntify(blockHeader.timestamp); + + return pool; + } + + private _computePoolAddress( + token0: Address, + token1: Address, + tickSpacing: bigint, + ): Address { + // https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/PoolAddress.sol + if (token0 > token1) [token0, token1] = [token1, token0]; + + const encodedKey = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode( + ['address', 'address', 'int24'], + // [token0, token1, BigInt.asUintN(24, fee)], + [token0, token1, BigInt.asUintN(24, tickSpacing)], + ), + ); + + return ethers.utils.getCreate2Address( + this.factoryAddress, + encodedKey, + this.poolInitCodeHash, + ); + } +} diff --git a/src/dex/solidly-v3/solidly-v3.ts b/src/dex/solidly-v3/solidly-v3.ts new file mode 100644 index 000000000..78fb4efbf --- /dev/null +++ b/src/dex/solidly-v3/solidly-v3.ts @@ -0,0 +1,988 @@ +import { Interface } from '@ethersproject/abi'; +import _ from 'lodash'; +import { pack } from '@ethersproject/solidity'; +import { + Token, + Address, + ExchangePrices, + AdapterExchangeParam, + SimpleExchangeParam, + PoolLiquidity, + Logger, + NumberAsString, + PoolPrices, + PreprocessTransactionOptions, + ExchangeTxInfo, +} from '../../types'; +import { SwapSide, Network, CACHE_PREFIX } from '../../constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { getBigIntPow, getDexKeysWithNetwork, isTruthy } from '../../utils'; +import { IDex } from '../../dex/idex'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { + DexParams, + OutputResult, + PoolState, + SolidlyV3Data, + SolidlyV3SimpleSwapParams, + UniswapV3Param, +} from './types'; +import { + getLocalDeadlineAsFriendlyPlaceholder, + SimpleExchange, +} from '../simple-exchange'; +import { SolidlyV3Config, Adapters, PoolsToPreload } from './config'; +import { SolidlyV3EventPool } from './solidly-v3-pool'; +import UniswapV3QuoterV2ABI from '../../abi/uniswap-v3/UniswapV3QuoterV2.abi.json'; +import DirectSwapABI from '../../abi/DirectSwap.json'; +import SolidlyV3StateMulticallABI from '../../abi/solidly-v3/SolidlyV3StateMulticall.abi.json'; +import SolidlyV3PoolABI from '../../abi/solidly-v3/SolidlyV3Pool.abi.json'; +import { + UNISWAPV3_EFFICIENCY_FACTOR, + UNISWAPV3_POOL_SEARCH_OVERHEAD, + UNISWAPV3_TICK_BASE_OVERHEAD, + UNISWAPV3_TICK_GAS_COST, +} from './constants'; +import { assert, DeepReadonly } from 'ts-essentials'; +import { uniswapV3Math } from './contract-math/uniswap-v3-math'; +import { Contract } from 'web3-eth-contract'; +import { AbiItem } from 'web3-utils'; +import { OptimalSwapExchange } from '@paraswap/core'; +import { TickMath } from './contract-math/TickMath'; +import { OnPoolCreatedCallback, SolidlyV3Factory } from './solidly-v3-factory'; + +type PoolPairsInfo = { + token0: Address; + token1: Address; + fee: string; +}; + +const UNISWAPV3_CLEAN_NOT_EXISTING_POOL_TTL_MS = 60 * 60 * 24 * 1000; // 24 hours +const UNISWAPV3_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS = 30 * 60 * 1000; // Once in 30 minutes + +export class SolidlyV3 + extends SimpleExchange + implements IDex +{ + readonly isFeeOnTransferSupported: boolean = false; + readonly eventPools: Record = {}; + + readonly hasConstantPriceLargeAmounts = false; + readonly needWrapNative = true; + + readonly directSwapIface = new Interface(DirectSwapABI); + + intervalTask?: NodeJS.Timeout; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyV3Config, ['SolidlyV3'])); + + logger: Logger; + + private stateMultiContract: Contract; + + private notExistingPoolSetKey: string; + + private readonly factory: SolidlyV3Factory; + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + protected adapters = Adapters[network] || {}, + readonly poolIface = new Interface(SolidlyV3PoolABI), + readonly quoterIface = new Interface(UniswapV3QuoterV2ABI), + protected config = SolidlyV3Config[dexKey][network], + protected poolsToPreload = PoolsToPreload[dexKey]?.[network] || [], + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey + '-' + network); + this.stateMultiContract = new this.dexHelper.web3Provider.eth.Contract( + this.config.stateMultiCallAbi !== undefined + ? this.config.stateMultiCallAbi + : (SolidlyV3StateMulticallABI as AbiItem[]), + this.config.stateMulticall, + ); + + // To receive revert reasons + this.dexHelper.web3Provider.eth.handleRevert = false; + + // Normalize once all config addresses and use across all scenarios + this.config = this._toLowerForAllConfigAddresses(); + + this.factory = new SolidlyV3Factory( + dexHelper, + dexKey, + this.config.factory, + this.logger, + this.onPoolCreatedDeleteFromNonExistingSet, + ); + + this.notExistingPoolSetKey = + `${CACHE_PREFIX}_${network}_${dexKey}_not_existings_pool_set`.toLowerCase(); + } + + get supportedTickSpacings() { + return this.config.supportedTickSpacings; + } + + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return this.adapters[side] ? this.adapters[side] : null; + } + + getPoolIdentifier( + srcAddress: Address, + destAddress: Address, + tickSpacing: bigint, + ) { + const tokenAddresses = this._sortTokens(srcAddress, destAddress).join('_'); + return `${this.dexKey}_${tokenAddresses}_${tickSpacing}`; + } + + async initializePricing(blockNumber: number) { + // Init listening to new pools creation + await this.factory.initialize(blockNumber); + + // This is only for testing, because cold pool fetching is goes out of + // FETCH_POOL_INDENTIFIER_TIMEOUT range + await Promise.all( + this.poolsToPreload.map(async pool => + Promise.all( + this.config.supportedTickSpacings.map(async tickSpacing => + this.getPool(pool.token0, pool.token1, tickSpacing, blockNumber), + ), + ), + ), + ); + + if (!this.dexHelper.config.isSlave) { + const cleanExpiredNotExistingPoolsKeys = async () => { + const maxTimestamp = + Date.now() - UNISWAPV3_CLEAN_NOT_EXISTING_POOL_TTL_MS; + await this.dexHelper.cache.zremrangebyscore( + this.notExistingPoolSetKey, + 0, + maxTimestamp, + ); + }; + + this.intervalTask = setInterval( + cleanExpiredNotExistingPoolsKeys.bind(this), + UNISWAPV3_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS, + ); + } + } + + /* + * When a non existing pool is queried, it's blacklisted for an arbitrary long period in order to prevent issuing too many rpc calls + * Once the pool is created, it gets immediately flagged + */ + onPoolCreatedDeleteFromNonExistingSet: OnPoolCreatedCallback = async ({ + token0, + token1, + tickSpacing, + }) => { + const logPrefix = '[onPoolCreatedDeleteFromNonExistingSet]'; + const [_token0, _token1] = this._sortTokens(token0, token1); + const poolKey = `${_token0}_${_token1}_${tickSpacing}`; + + // consider doing it only from master pool for less calls to distant cache + + // delete entry locally to let local instance discover the pool + delete this.eventPools[ + this.getPoolIdentifier(_token0, _token1, tickSpacing) + ]; + + try { + this.logger.info( + `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}`, + ); + // delete pool record from set + const result = await this.dexHelper.cache.zrem( + this.notExistingPoolSetKey, + [poolKey], + ); + this.logger.info( + `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}; result: ${result}`, + ); + } catch (error) { + this.logger.error( + `${logPrefix} ERROR: failed to delete pool from set: set=${this.notExistingPoolSetKey}; key=${poolKey}`, + error, + ); + } + }; + + async getPool( + srcAddress: Address, + destAddress: Address, + tickSpacing: bigint, + blockNumber: number, + ): Promise { + let pool = this.eventPools[ + this.getPoolIdentifier(srcAddress, destAddress, tickSpacing) + ] as SolidlyV3EventPool | null | undefined; + + if (pool === null) return null; + + if (pool) { + if (!pool.initFailed) { + return pool; + } else { + // if init failed then prefer to early return pool with empty state to fallback to rpc call + if ( + ++pool.initRetryAttemptCount % this.config.initRetryFrequency !== + 0 + ) { + return pool; + } + // else pursue with re-try initialization + } + } + + const [token0, token1] = this._sortTokens(srcAddress, destAddress); + + const key = `${token0}_${token1}_${tickSpacing}`.toLowerCase(); + + if (!pool) { + const notExistingPoolScore = await this.dexHelper.cache.zscore( + this.notExistingPoolSetKey, + key, + ); + + const poolDoesNotExist = notExistingPoolScore !== null; + + if (poolDoesNotExist) { + this.eventPools[ + this.getPoolIdentifier(srcAddress, destAddress, tickSpacing) + ] = null; + return null; + } + + await this.dexHelper.cache.hset( + this.dexmapKey, + key, + JSON.stringify({ + token0, + token1, + fee: tickSpacing.toString(), + }), + ); + } + + this.logger.trace(`starting to listen to new pool: ${key}`); + pool = + pool || + new SolidlyV3EventPool( + this.dexHelper, + this.dexKey, + this.stateMultiContract, + this.config.decodeStateMultiCallResultWithRelativeBitmaps, + this.erc20Interface, + this.config.factory, + tickSpacing, + token0, + token1, + this.logger, + this.cacheStateKey, + this.config.initHash, + ); + + try { + await pool.initialize(blockNumber, { + initCallback: (state: DeepReadonly) => { + //really hacky, we need to push poolAddress so that we subscribeToLogs in StatefulEventSubscriber + pool!.addressesSubscribed[0] = state.pool; + pool!.poolAddress = state.pool; + pool!.initFailed = false; + pool!.initRetryAttemptCount = 0; + }, + }); + } catch (e) { + if (e instanceof Error && e.message.endsWith('Pool does not exist')) { + // no need to await we want the set to have the pool key but it's not blocking + this.dexHelper.cache.zadd( + this.notExistingPoolSetKey, + [Date.now(), key], + 'NX', + ); + + // Pool does not exist for this feeCode, so we can set it to null + // to prevent more requests for this pool + pool = null; + this.logger.trace( + `${this.dexHelper}: Pool: srcAddress=${srcAddress}, destAddress=${destAddress}, fee=${tickSpacing} not found`, + e, + ); + } else { + // 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}, fee=${tickSpacing} pool fallback to rpc and retry every ${this.config.initRetryFrequency} times, initRetryAttemptCount=${pool.initRetryAttemptCount}`, + e, + ); + pool.initFailed = true; + } + } + + if (pool !== null) { + const allEventPools = Object.values(this.eventPools); + this.logger.info( + `starting to listen to new non-null pool: ${key}. Already following ${allEventPools + // Not that I like this reduce, but since it is done only on initialization, expect this to be ok + .reduce( + (acc, curr) => (curr !== null ? ++acc : acc), + 0, + )} non-null pools or ${allEventPools.length} total pools`, + ); + } + + this.eventPools[ + this.getPoolIdentifier(srcAddress, destAddress, tickSpacing) + ] = pool; + return pool; + } + + async addMasterPool(poolKey: string, blockNumber: number): Promise { + const _pairs = await this.dexHelper.cache.hget(this.dexmapKey, poolKey); + if (!_pairs) { + this.logger.warn( + `did not find poolConfig in for key ${this.dexmapKey} ${poolKey}`, + ); + return false; + } + + const poolInfo: PoolPairsInfo = JSON.parse(_pairs); + + const pool = await this.getPool( + poolInfo.token0, + poolInfo.token1, + BigInt(poolInfo.fee), + blockNumber, + ); + + if (!pool) { + return false; + } + + return true; + } + + 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); + + const [_srcAddress, _destAddress] = this._getLoweredAddresses( + _srcToken, + _destToken, + ); + + if (_srcAddress === _destAddress) return []; + + const pools = ( + await Promise.all( + this.supportedTickSpacings.map(async tickSpacing => + this.getPool(_srcAddress, _destAddress, tickSpacing, blockNumber), + ), + ) + ).filter(pool => pool); + + if (pools.length === 0) return []; + + return pools.map(pool => + this.getPoolIdentifier(_srcAddress, _destAddress, pool!.tickSpacing), + ); + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + ): Promise> { + try { + const _srcToken = this.dexHelper.config.wrapETH(srcToken); + const _destToken = this.dexHelper.config.wrapETH(destToken); + + const [_srcAddress, _destAddress] = this._getLoweredAddresses( + _srcToken, + _destToken, + ); + + if (_srcAddress === _destAddress) return null; + + let selectedPools: SolidlyV3EventPool[] = []; + + if (!limitPools) { + selectedPools = ( + await Promise.all( + this.supportedTickSpacings.map(async tickSpacing => { + const locallyFoundPool = + this.eventPools[ + this.getPoolIdentifier(_srcAddress, _destAddress, tickSpacing) + ]; + if (locallyFoundPool) return locallyFoundPool; + + const newlyFetchedPool = await this.getPool( + _srcAddress, + _destAddress, + tickSpacing, + blockNumber, + ); + return newlyFetchedPool; + }), + ) + ).filter(isTruthy); + } else { + const pairIdentifierWithoutFee = this.getPoolIdentifier( + _srcAddress, + _destAddress, + 0n, + // Trim from 0 fee postfix, so it become comparable + ).slice(0, -1); + + const poolIdentifiers = limitPools.filter(identifier => + identifier.startsWith(pairIdentifierWithoutFee), + ); + + selectedPools = ( + await Promise.all( + poolIdentifiers.map(async identifier => { + let locallyFoundPool = this.eventPools[identifier]; + if (locallyFoundPool) return locallyFoundPool; + + const [, srcAddress, destAddress, tickSpacing] = + identifier.split('_'); + const newlyFetchedPool = await this.getPool( + srcAddress, + destAddress, + BigInt(tickSpacing), + blockNumber, + ); + return newlyFetchedPool; + }), + ) + ).filter(isTruthy); + } + + if (selectedPools.length === 0) return null; + + const poolsToUse = selectedPools.reduce( + (acc, pool) => { + let state = pool.getState(blockNumber); + if (state === null) { + throw new Error('Unable to retrieve pool state.'); + } else { + acc.poolWithState.push(pool); + } + return acc; + }, + { + poolWithState: [] as SolidlyV3EventPool[], + poolWithoutState: [] as SolidlyV3EventPool[], + }, + ); + + const states = poolsToUse.poolWithState.map( + p => p.getState(blockNumber)!, + ); + + const unitAmount = getBigIntPow( + side == SwapSide.SELL ? _srcToken.decimals : _destToken.decimals, + ); + + const _amounts = [...amounts.slice(1)]; + + const [token0] = this._sortTokens(_srcAddress, _destAddress); + + const zeroForOne = token0 === _srcAddress ? true : false; + + const result = await Promise.all( + poolsToUse.poolWithState.map(async (pool, i) => { + const state = states[i]; + + if (state.liquidity <= 0n) { + if (state.liquidity < 0) { + this.logger.error( + `${this.dexKey}-${this.network}: ${pool.poolAddress} pool has negative liquidity: ${state.liquidity}. Find with key: ${pool.mapKey}`, + ); + } + this.logger.trace(`pool have 0 liquidity`); + return null; + } + + const balanceDestToken = + _destAddress === pool.token0 ? state.balance0 : state.balance1; + + const unitResult = this._getOutputs( + state, + [unitAmount], + zeroForOne, + side, + balanceDestToken, + ); + const pricesResult = this._getOutputs( + state, + _amounts, + zeroForOne, + side, + balanceDestToken, + ); + + if (!unitResult || !pricesResult) { + this.logger.debug('Prices or unit is not calculated'); + return null; + } + + const prices = [0n, ...pricesResult.outputs]; + const gasCost = [ + 0, + ...pricesResult.outputs.map((p, index) => { + if (p == 0n) { + return 0; + } else { + return ( + UNISWAPV3_POOL_SEARCH_OVERHEAD + + UNISWAPV3_TICK_BASE_OVERHEAD + + pricesResult.tickCounts[index] * UNISWAPV3_TICK_GAS_COST + ); + } + }), + ]; + return { + unit: unitResult.outputs[0], + prices, + data: { + zeroForOne, + poolAddress: pool.poolAddress, + }, + poolIdentifier: this.getPoolIdentifier( + pool.token0, + pool.token1, + pool.tickSpacing, + ), + exchange: this.dexKey, + gasCost: gasCost, + poolAddresses: [pool.poolAddress], + }; + }), + ); + + const notNullResult = result.filter( + res => res !== null, + ) as ExchangePrices; + + return notNullResult; + } catch (e) { + this.logger.error( + `Error_getPricesVolume ${srcToken.symbol || srcToken.address}, ${ + destToken.symbol || destToken.address + }, ${side}:`, + e, + ); + return null; + } + } + + getAdapterParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: SolidlyV3Data, + side: SwapSide, + ): AdapterExchangeParam { + const sqrtPriceLimitX96 = data.zeroForOne + ? (TickMath.MIN_SQRT_RATIO + BigInt(1)).toString() + : (TickMath.MAX_SQRT_RATIO - BigInt(1)).toString(); + + const payload = this.abiCoder.encodeParameter( + { + ParentStruct: { + recipient: 'address', + zeroForOne: 'bool', + sqrtPriceLimitX96: 'uint160', + }, + }, + { + recipient: this.augustusAddress, + zeroForOne: data.zeroForOne, + sqrtPriceLimitX96, + }, + ); + + return { + targetExchange: data.poolAddress, + payload, + networkFee: '0', + }; + } + + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + const gasCost = + CALLDATA_GAS_COST.DEX_OVERHEAD + + CALLDATA_GAS_COST.LENGTH_SMALL + + // ParentStruct header + CALLDATA_GAS_COST.OFFSET_SMALL + + // ParentStruct -> path header + CALLDATA_GAS_COST.OFFSET_SMALL + + // ParentStruct -> deadline + CALLDATA_GAS_COST.TIMESTAMP + + // ParentStruct -> path (20+3+20 = 43 = 32+11 bytes) + CALLDATA_GAS_COST.LENGTH_SMALL + + CALLDATA_GAS_COST.FULL_WORD + + CALLDATA_GAS_COST.wordNonZeroBytes(11); + const arr = new Array(poolPrices.prices.length); + poolPrices.prices.forEach((p, index) => { + if (p == 0n) { + arr[index] = 0; + } else { + arr[index] = gasCost; + } + }); + return arr; + } + + getTokenFromAddress(address: Address): Token { + // In this Dex decimals are not used + return { address, decimals: 0 }; + } + + async preProcessTransaction( + optimalSwapExchange: OptimalSwapExchange, + srcToken: Token, + _0: Token, + _1: SwapSide, + options: PreprocessTransactionOptions, + ): Promise<[OptimalSwapExchange, ExchangeTxInfo]> { + if (!options.isDirectMethod) { + return [ + optimalSwapExchange, + { + deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()), + }, + ]; + } + + assert( + optimalSwapExchange.data !== undefined, + `preProcessTransaction: data field is missing`, + ); + + let isApproved: boolean | undefined; + + try { + this.erc20Contract.options.address = + this.dexHelper.config.wrapETH(srcToken).address; + const allowance = await this.erc20Contract.methods + .allowance(this.augustusAddress, optimalSwapExchange.exchange) + .call(undefined, 'latest'); + isApproved = + BigInt(allowance.toString()) >= BigInt(optimalSwapExchange.srcAmount); + } catch (e) { + this.logger.error( + `preProcessTransaction failed to retrieve allowance info: `, + e, + ); + } + + return [ + { + ...optimalSwapExchange, + data: { + ...optimalSwapExchange.data, + isApproved, + }, + }, + { + deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()), + }, + ]; + } + + async getSimpleParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: SolidlyV3Data, + side: SwapSide, + ): Promise { + const swapFunction = this.poolIface.getFunction( + 'swap(address,bool,int256,uint160)', + ); + + const swapFunctionParams: SolidlyV3SimpleSwapParams = { + recipient: this.augustusAddress, + zeroForOne: data.zeroForOne, + amountSpecified: + side === SwapSide.SELL + ? srcAmount.toString() + : (-destAmount).toString(), + sqrtPriceLimitX96: data.zeroForOne + ? (TickMath.MIN_SQRT_RATIO + BigInt(1)).toString() + : (TickMath.MAX_SQRT_RATIO - BigInt(1)).toString(), + }; + const swapData = this.poolIface.encodeFunctionData(swapFunction, [ + swapFunctionParams.recipient, + swapFunctionParams.zeroForOne, + swapFunctionParams.amountSpecified, + swapFunctionParams.sqrtPriceLimitX96, + ]); + + return this.buildSimpleParamWithoutWETHConversion( + srcToken, + srcAmount, + destToken, + destAmount, + swapData, + data.poolAddress, + ); + } + + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + const _tokenAddress = tokenAddress.toLowerCase(); + + const res = await this._querySubgraph( + `query ($token: Bytes!, $count: Int) { + pools0: pools(first: $count, orderBy: totalValueLockedUSD, orderDirection: desc, where: {token0: $token, liquidity_gt: "0"}) { + id + token0 { + id + decimals + } + token1 { + id + decimals + } + totalValueLockedUSD + } + pools1: pools(first: $count, orderBy: totalValueLockedUSD, orderDirection: desc, where: {token1: $token, liquidity_gt: "0"}) { + id + token0 { + id + decimals + } + token1 { + id + decimals + } + totalValueLockedUSD + } + }`, + { + token: _tokenAddress, + count: limit, + }, + ); + + if (!(res && res.pools0 && res.pools1)) { + this.logger.error( + `Error_${this.dexKey}_Subgraph: couldn't fetch the pools from the subgraph`, + ); + return []; + } + + const pools0 = _.map(res.pools0, pool => ({ + exchange: this.dexKey, + address: pool.id.toLowerCase(), + connectorTokens: [ + { + address: pool.token1.id.toLowerCase(), + decimals: parseInt(pool.token1.decimals), + }, + ], + liquidityUSD: + parseFloat(pool.totalValueLockedUSD) * UNISWAPV3_EFFICIENCY_FACTOR, + })); + + const pools1 = _.map(res.pools1, pool => ({ + exchange: this.dexKey, + address: pool.id.toLowerCase(), + connectorTokens: [ + { + address: pool.token0.id.toLowerCase(), + decimals: parseInt(pool.token0.decimals), + }, + ], + liquidityUSD: + parseFloat(pool.totalValueLockedUSD) * UNISWAPV3_EFFICIENCY_FACTOR, + })); + + const pools = _.slice( + _.sortBy(_.concat(pools0, pools1), [pool => -1 * pool.liquidityUSD]), + 0, + limit, + ); + return pools; + } + + private async _getPoolsFromIdentifiers( + poolIdentifiers: string[], + blockNumber: number, + ): Promise { + const pools = await Promise.all( + poolIdentifiers.map(async identifier => { + const [, srcAddress, destAddress, fee] = identifier.split('_'); + return this.getPool(srcAddress, destAddress, BigInt(fee), blockNumber); + }), + ); + return pools.filter(pool => pool) as SolidlyV3EventPool[]; + } + + private _getLoweredAddresses(srcToken: Token, destToken: Token) { + return [srcToken.address.toLowerCase(), destToken.address.toLowerCase()]; + } + + private _sortTokens(srcAddress: Address, destAddress: Address) { + return [srcAddress, destAddress].sort((a, b) => (a < b ? -1 : 1)); + } + + private _toLowerForAllConfigAddresses() { + // If new config property will be added, the TS will throw compile error + const newConfig: DexParams = { + quoter: this.config.quoter.toLowerCase(), + factory: this.config.factory.toLowerCase(), + supportedTickSpacings: this.config.supportedTickSpacings, + stateMulticall: this.config.stateMulticall.toLowerCase(), + chunksCount: this.config.chunksCount, + initRetryFrequency: this.config.initRetryFrequency, + deployer: this.config.deployer?.toLowerCase(), + initHash: this.config.initHash, + subgraphURL: this.config.subgraphURL, + stateMultiCallAbi: this.config.stateMultiCallAbi, + decodeStateMultiCallResultWithRelativeBitmaps: + this.config.decodeStateMultiCallResultWithRelativeBitmaps, + }; + return newConfig; + } + + private _getOutputs( + state: DeepReadonly, + amounts: bigint[], + zeroForOne: boolean, + side: SwapSide, + destTokenBalance: bigint, + ): OutputResult | null { + try { + const outputsResult = uniswapV3Math.queryOutputs( + state, + amounts, + zeroForOne, + side, + ); + + if (side === SwapSide.SELL) { + if (outputsResult.outputs[0] > destTokenBalance) { + return null; + } + + for (let i = 0; i < outputsResult.outputs.length; i++) { + if (outputsResult.outputs[i] > destTokenBalance) { + outputsResult.outputs[i] = 0n; + outputsResult.tickCounts[i] = 0; + } + } + } else { + if (amounts[0] > destTokenBalance) { + return null; + } + + // This may be improved by first checking outputs and requesting outputs + // only for amounts that makes more sense, but I don't think this is really + // important now + for (let i = 0; i < amounts.length; i++) { + if (amounts[i] > destTokenBalance) { + outputsResult.outputs[i] = 0n; + outputsResult.tickCounts[i] = 0; + } + } + } + + return outputsResult; + } catch (e) { + this.logger.debug( + `${this.dexKey}: received error in _getOutputs while calculating outputs`, + e, + ); + return null; + } + } + + private async _querySubgraph( + query: string, + variables: Object, + timeout = 30000, + ) { + try { + const res = await this.dexHelper.httpRequest.post( + this.config.subgraphURL, + { query, variables }, + undefined, + { timeout: timeout }, + ); + return res.data; + } catch (e) { + this.logger.error(`${this.dexKey}: can not query subgraph: `, e); + return {}; + } + } + + private _encodePath( + path: { + tokenIn: Address; + tokenOut: Address; + fee: NumberAsString; + }[], + side: SwapSide, + ): string { + if (path.length === 0) { + this.logger.error( + `${this.dexKey}: Received invalid path=${path} for side=${side} to encode`, + ); + return '0x'; + } + + const { _path, types } = path.reduce( + ( + { _path, types }: { _path: string[]; types: string[] }, + curr, + index, + ): { _path: string[]; types: string[] } => { + if (index === 0) { + return { + types: ['address', 'uint24', 'address'], + _path: [curr.tokenIn, curr.fee, curr.tokenOut], + }; + } else { + return { + types: [...types, 'uint24', 'address'], + _path: [..._path, curr.fee, curr.tokenOut], + }; + } + }, + { _path: [], types: [] }, + ); + + return side === SwapSide.BUY + ? pack(types.reverse(), _path.reverse()) + : pack(types, _path); + } + + releaseResources() { + if (this.intervalTask !== undefined) { + clearInterval(this.intervalTask); + this.intervalTask = undefined; + } + } +} diff --git a/src/dex/solidly-v3/types.ts b/src/dex/solidly-v3/types.ts new file mode 100644 index 000000000..ca846e35a --- /dev/null +++ b/src/dex/solidly-v3/types.ts @@ -0,0 +1,158 @@ +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 FactoryState = Record; + +export type TickInfo = { + liquidityGross: bigint; + liquidityNet: bigint; + initialized: boolean; +}; + +export type Slot0 = { + sqrtPriceX96: bigint; + tick: bigint; + fee: bigint; +}; + +export type PoolState = { + pool: string; + blockTimestamp: bigint; + tickSpacing: bigint; + slot0: Slot0; + liquidity: bigint; + maxLiquidityPerTick: bigint; + tickBitmap: Record; + ticks: Record; + isValid: boolean; + startTickBitmap: bigint; + lowestKnownTick: bigint; + highestKnownTick: bigint; + balance0: bigint; + balance1: bigint; +}; + +export type SolidlyV3Data = { + zeroForOne: boolean; + poolAddress: string; + isApproved?: boolean; +}; + +export type DecodeStateMultiCallFunc = ( + result: MultiResult | BytesLike, +) => DecodedStateMultiCallResultWithRelativeBitmaps; + +export type DexParams = { + quoter: Address; + factory: Address; + stateMulticall: Address; + supportedTickSpacings: bigint[]; + chunksCount: number; + initRetryFrequency: number; + deployer?: Address; + subgraphURL: string; + initHash: string; + stateMultiCallAbi?: AbiItem[]; + decodeStateMultiCallResultWithRelativeBitmaps?: DecodeStateMultiCallFunc; +}; + +export type UniswapV3SimpleSwapSellParam = { + path: string; + recipient: Address; + deadline: string; + amountIn: NumberAsString; + amountOutMinimum: NumberAsString; +}; + +export type UniswapV3SimpleSwapBuyParam = { + path: string; + recipient: Address; + deadline: string; + amountOut: NumberAsString; + amountInMaximum: NumberAsString; +}; + +export type UniswapV3SimpleSwapParams = + | UniswapV3SimpleSwapSellParam + | UniswapV3SimpleSwapBuyParam; + +export type SolidlyV3SimpleSwapParams = { + recipient: string; + zeroForOne: boolean; + amountSpecified: NumberAsString; + sqrtPriceLimitX96: NumberAsString; +}; + +export type UniswapV3Param = [ + fromToken: Address, + toToken: Address, + exchange: Address, + fromAmount: NumberAsString, + toAmount: NumberAsString, + expectedAmount: NumberAsString, + feePercent: NumberAsString, + deadline: NumberAsString, + partner: Address, + isApproved: boolean, + beneficiary: Address, + path: string, + permit: string, + uuid: string, +]; + +export enum UniswapV3Functions { + exactInput = 'exactInput', + exactOutput = 'exactOutput', +} + +export type TickInfoMappings = { + index: number; + value: TickInfo; +}; + +export type TickBitMapMappings = { + index: number; + value: bigint; +}; + +export type OutputResult = { + outputs: bigint[]; + tickCounts: number[]; +}; + +// Just rewrote every type with BigNumber basically + +export type TickBitMapMappingsWithBigNumber = { + index: number; + value: BigNumber; +}; + +export type TickInfoWithBigNumber = { + initialized: boolean; + liquidityGross: BigNumber; + liquidityNet: BigNumber; +}; + +export type TickInfoMappingsWithBigNumber = { + index: number; + value: TickInfoWithBigNumber; +}; + +export type DecodedStateMultiCallResultWithRelativeBitmaps = { + pool: Address; + blockTimestamp: BigNumber; + slot0: { + sqrtPriceX96: BigNumber; + tick: number; + fee: number; + unlocked: boolean; + }; + liquidity: BigNumber; + tickSpacing: number; + maxLiquidityPerTick: BigNumber; + tickBitmap: TickBitMapMappingsWithBigNumber[]; + ticks: TickInfoMappingsWithBigNumber[]; +}; diff --git a/src/dex/solidly-v3/utils.ts b/src/dex/solidly-v3/utils.ts new file mode 100644 index 000000000..c5a39349a --- /dev/null +++ b/src/dex/solidly-v3/utils.ts @@ -0,0 +1,71 @@ +import { BytesLike, ethers } from 'ethers'; +import { assert } from 'ts-essentials'; +import { extractSuccessAndValue } from '../../lib/decoders'; +import { MultiResult } from '../../lib/multi-wrapper'; +import { DexConfigMap } from '../../types'; +import { + DexParams, + DecodedStateMultiCallResultWithRelativeBitmaps, +} from './types'; + +export function getUniswapV3DexKey(UniswapV3Config: DexConfigMap) { + const UniswapV3Keys = Object.keys(UniswapV3Config); + if (UniswapV3Keys.length !== 1) { + throw new Error( + `UniswapV3 key in UniswapV3Config is not unique. Update relevant places (optimizer) or fix config issue. Received: ${JSON.stringify( + UniswapV3Config, + (_0, value) => (typeof value === 'bigint' ? value.toString() : value), + )}`, + ); + } + + return UniswapV3Keys[0].toLowerCase(); +} + +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, + uint24 fee, + bool unlocked, + ) slot0, + uint128 liquidity, + int24 tickSpacing, + uint128 maxLiquidityPerTick, + tuple( + int16 index, + uint256 value, + )[] tickBitmap, + tuple( + int24 index, + tuple( + uint128 liquidityGross, + int128 liquidityNet, + 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/solidly/config.ts b/src/dex/solidly/config.ts index f8e364db3..0a9bb9f12 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,69 @@ 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, + }, + }, + Velocimeter: { + [Network.FANTOM]: { + factoryAddress: '0x472f3C3c9608fe0aE8d702f3f8A2d12c410C881A', + router: '0x93d2611EB8b85bE4FDEa9D94Ce9913D90072eC0f', + initCode: + '0xac4013aa7118234c1dd1f9cc4cdd3933d5a426224bc691c1bde3d8930a7e6151', // PairFactory.pairCodeHash + feeCode: 0, // dynamic fees + poolGasCost: 180 * 1000, // just same as other forks + // no subgraph + }, + [Network.BASE]: { + factoryAddress: '0xe21Aac7F113Bd5DC2389e4d8a8db854a87fD6951', + router: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + initCode: + '0xac4013aa7118234c1dd1f9cc4cdd3933d5a426224bc691c1bde3d8930a7e6151', // PairFactory.pairCodeHash + feeCode: 0, // dynamic fees + poolGasCost: 180 * 1000, // just same as other forks + // no subgraph + }, + }, + Usdfi: { + [Network.BSC]: { + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/tbotteam/usdfi-dexv2', + factoryAddress: '0xB3863573d9f25e6a84895d4685a408db7a488416', + router: '0xc2b5a8082D2E1867A9CBBF41b625E3ae9dF81f8b', + initCode: + '0x1d770cc32abcf060a45b0de3f0afbd8594effe9f6d836f93d19c05d76b4b4dfa', + poolGasCost: 180 * 1000, + feeCode: 0, // dynamic fees + }, + }, }; export const Adapters: Record = { @@ -132,13 +217,13 @@ 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, velocimeter }, [Network.OPTIMISM]: { [SwapSide.SELL]: [{ name: 'OptimismAdapter01', index: 8 }], // velodrome }, [Network.BSC]: { - [SwapSide.SELL]: [{ name: 'BscAdapter02', index: 1 }], // thena + cone + [SwapSide.SELL]: [{ name: 'BscAdapter02', index: 1 }], // thena + cone, usdFi }, [Network.MAINNET]: { [SwapSide.SELL]: [{ name: 'Adapter04', index: 1 }], // solidly @@ -147,6 +232,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, velocimeter }, }; diff --git a/src/dex/solidly/forks-override/aerodrome.ts b/src/dex/solidly/forks-override/aerodrome.ts new file mode 100644 index 000000000..505ed4c71 --- /dev/null +++ b/src/dex/solidly/forks-override/aerodrome.ts @@ -0,0 +1,53 @@ +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/chronos.ts b/src/dex/solidly/forks-override/chronos.ts index 845b46e01..34d3d8fe2 100644 --- a/src/dex/solidly/forks-override/chronos.ts +++ b/src/dex/solidly/forks-override/chronos.ts @@ -27,7 +27,7 @@ type ChronosSubgraphPool = { token0: { id: string; decimals: string }; reserve0: string; reserve1: string; - token1: { id: string; decimals: string; }; + token1: { id: string; decimals: string }; }; export class Chronos extends Solidly { @@ -114,35 +114,43 @@ export class Chronos extends Solidly { if (!(data && data.pools0 && data.pools1)) throw new Error("Couldn't fetch the pools from the subgraph"); - const pools0 = await this.prepareSubgraphPools(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(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, - })); + const pools0 = await this.prepareSubgraphPools( + 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( + 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]), @@ -154,7 +162,8 @@ export class Chronos extends Solidly { private async prepareSubgraphPools( pools: ChronosSubgraphPool[], iterator: ( - pool: ChronosSubgraphPool, { + pool: ChronosSubgraphPool, + { address0, address1, decimals0, @@ -164,43 +173,59 @@ export class Chronos extends Solidly { liquidityUSDToken0, liquidityUSDToken1, }: { - address0: string, - address1: string, - decimals0: number, - decimals1: number, - reserve0: bigint, - reserve1: bigint, - liquidityUSDToken0: number, - liquidityUSDToken1: number, - }) => PoolLiquidity + 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: ChronosSubgraphPool, - ) => { - const address0 = pool.token0.id.toLowerCase(); - const address1 = pool.token1.id.toLowerCase(); - - const decimals0 = parseInt(pool.token0.decimals); - const decimals1 = parseInt(pool.token1.decimals); - - const reserve0 = BigInt(new BigNumber(pool.reserve0).multipliedBy(10 ** decimals0).toFixed()); - const reserve1 = BigInt(new BigNumber(pool.reserve1).multipliedBy(10 ** decimals1).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, - }, - ); - })); + return Promise.all( + pools.map(async (pool: ChronosSubgraphPool) => { + const address0 = pool.token0.id.toLowerCase(); + const address1 = pool.token1.id.toLowerCase(); + + const decimals0 = parseInt(pool.token0.decimals); + const decimals1 = parseInt(pool.token1.decimals); + + const reserve0 = BigInt( + new BigNumber(pool.reserve0).multipliedBy(10 ** decimals0).toFixed(), + ); + const reserve1 = BigInt( + new BigNumber(pool.reserve1).multipliedBy(10 ** decimals1).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/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/usdfi.ts b/src/dex/solidly/forks-override/usdfi.ts new file mode 100644 index 000000000..6ebad08ae --- /dev/null +++ b/src/dex/solidly/forks-override/usdfi.ts @@ -0,0 +1,10 @@ +import { Network } from '../../../constants'; +import { getDexKeysWithNetwork } from '../../../utils'; +import { SolidlyConfig } from '../config'; +import _ from 'lodash'; +import { SpiritSwapV2 } from './spiritSwapV2'; + +export class Usdfi extends SpiritSwapV2 { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyConfig, ['Usdfi'])); +} diff --git a/src/dex/solidly/forks-override/velocimeter.ts b/src/dex/solidly/forks-override/velocimeter.ts new file mode 100644 index 000000000..878a1f2d7 --- /dev/null +++ b/src/dex/solidly/forks-override/velocimeter.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 velocimeterFactoryABI = [ + { + inputs: [{ internalType: '_pair', name: '_stable', type: 'address' }], + name: 'getFee', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +]; + +const velocimeterFactoryIface = new Interface(velocimeterFactoryABI); + +export class Velocimeter extends Solidly { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyConfig, ['Velocimeter'])); + + 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: velocimeterFactoryIface.encodeFunctionData('getFee', [ + pair.exchange, + ]), + }; + const callDecoder = (values: any[]) => + parseInt( + velocimeterFactoryIface + .decodeFunctionResult('getFee', values)[0] + .toString(), + ); + + return { + callEntry, + callDecoder, + }; + } +} 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..d6abdc5b5 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,50 @@ 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('Velocimeter', () => { + const dexKey = 'Velocimeter'; + const network = Network.FANTOM; + + const tokenASymbol: string = 'lzUSDC'; + const tokenBSymbol: string = 'axlUSDC'; + + const tokenAAmount: string = '1111100'; + const tokenBAmount: string = '1000000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); describe('Mainnet', () => { @@ -689,6 +817,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', () => { @@ -975,6 +1126,26 @@ describe('Solidly E2E', () => { }), ); }); + + describe('Usdfi', () => { + const dexKey = 'Usdfi'; + const tokenASymbol: string = 'FRAX'; + const tokenBSymbol: string = 'frxETH'; + + const tokenAAmount: string = '100000000000000000'; + const tokenBAmount: string = '1111100000'; + const nativeTokenAmount = '100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); describe('Avalanche', () => { @@ -1179,5 +1350,209 @@ 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, + ); + }); + }); + }); + }), + ); + }); + + describe('Velocimeter', () => { + const dexKey = 'Velocimeter'; + + const tokenASymbol: string = 'USDbC'; + const tokenBSymbol: string = 'DAI'; + + const tokenAAmount: string = '1111100000'; + const tokenBAmount: string = '1111100000'; + const nativeTokenAmount = '100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); }); diff --git a/src/dex/solidly/solidly-integration.test.ts b/src/dex/solidly/solidly-integration.test.ts index 06bc8243a..efe1a1c1b 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,12 @@ 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'; +import { Velocimeter } from './forks-override/velocimeter'; +import { Usdfi } from './forks-override/usdfi'; const amounts18 = [0n, BI_POWS[18], 2000000000000000000n]; const amounts6 = [0n, BI_POWS[6], 2000000n]; @@ -43,7 +50,7 @@ function decodeReaderResult( const constructCheckOnChainPricing = (dexHelper: DummyDexHelper) => async ( - soldily: Solidly, + solidly: Solidly, funcName: string, blockNumber: number, prices: bigint[], @@ -60,7 +67,7 @@ const constructCheckOnChainPricing = funcName, tokenIn, ); - console.log('readerCallData', readerCallData); + const readerResult = ( await dexHelper.multiContract.methods .aggregate(readerCallData) @@ -70,6 +77,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 +92,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 +104,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 +117,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 +135,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 +149,7 @@ describe('Solidly integration tests', () => { }); it('getTopPoolsForToken', async function () { - const poolLiquidity = await soldily.getTopPoolsForToken( + const poolLiquidity = await solidly.getTopPoolsForToken( tokenA.address, 10, ); @@ -160,7 +169,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 +182,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 +199,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 +213,7 @@ describe('Solidly integration tests', () => { }); it('getTopPoolsForToken', async function () { - const poolLiquidity = await soldily.getTopPoolsForToken( + const poolLiquidity = await solidly.getTopPoolsForToken( tokenA.address, 10, ); @@ -260,13 +269,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 +333,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,28 +357,22 @@ describe('Solidly integration tests', () => { }); }); }); - }); - - describe('Polygon', () => { - const network = Network.POLYGON; - const dexHelper = new DummyDexHelper(network); - const checkOnChainPricing = constructCheckOnChainPricing(dexHelper); - describe('Dystopia', function () { - const dexKey = 'Dystopia'; - const dystopia = new Solidly(network, dexKey, dexHelper); + describe('Equalizer', () => { + const dexKey = 'Equalizer'; + const equalizer = new Equalizer(network, dexKey, dexHelper); describe('UniswapV2 like pool', function () { - const TokenASymbol = 'WETH'; + const TokenASymbol = 'WFTM'; const tokenA = Tokens[network][TokenASymbol]; - const TokenBSymbol = 'WMATIC'; + 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 dystopia.getPoolIdentifiers( + const pools = await equalizer.getPoolIdentifiers( tokenA, tokenB, SwapSide.SELL, @@ -382,7 +385,7 @@ describe('Solidly integration tests', () => { expect(pools.length).toBeGreaterThan(0); - const poolPrices = await dystopia.getPricesVolume( + const poolPrices = await equalizer.getPricesVolume( tokenA, tokenB, amounts, @@ -400,41 +403,31 @@ 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, + equalizer, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); } }); - - it('getTopPoolsForToken', async function () { - const poolLiquidity = await dystopia.getTopPoolsForToken( - tokenA.address, - 10, - ); - console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); - - checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); - }); }); describe('Curve like stable pool', function () { - const TokenASymbol = 'DAI'; // 'USDT'; + const TokenASymbol = 'FUSDT'; const tokenA = Tokens[network][TokenASymbol]; const TokenBSymbol = 'USDC'; const tokenB = Tokens[network][TokenBSymbol]; - const amounts = amounts18; // amounts6; + const amounts = amounts6; // amounts6; it('getPoolIdentifiers and getPricesVolume', async function () { const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); - const pools = await dystopia.getPoolIdentifiers( + const pools = await equalizer.getPoolIdentifiers( tokenA, tokenB, SwapSide.SELL, @@ -447,7 +440,7 @@ describe('Solidly integration tests', () => { expect(pools.length).toBeGreaterThan(0); - const poolPrices = await dystopia.getPricesVolume( + const poolPrices = await equalizer.getPricesVolume( tokenA, tokenB, amounts, @@ -464,52 +457,92 @@ 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, + equalizer, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); } }); + }); - it('getTopPoolsForToken', async function () { - const poolLiquidity = await dystopia.getTopPoolsForToken( - tokenA.address, - 10, + 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, ); - console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); - checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + 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('BSC', () => { - const network = Network.BSC; - const dexHelper = new DummyDexHelper(network); - const checkOnChainPricing = constructCheckOnChainPricing(dexHelper); - describe('Cone', function () { - const dexKey = 'Cone'; - const cone = new Cone(network, dexKey, dexHelper); + describe('Velocimeter', () => { + const dexKey = 'Velocimeter'; + const velocimeter = new Velocimeter(network, dexKey, dexHelper); describe('UniswapV2 like pool', function () { - const TokenASymbol = 'WBNB'; + const TokenASymbol = 'WFTM'; const tokenA = Tokens[network][TokenASymbol]; - const TokenBSymbol = 'BUSD'; + const TokenBSymbol = 'lzUSDC'; const tokenB = Tokens[network][TokenBSymbol]; const amounts = amounts18; it('getPoolIdentifiers and getPricesVolume', async function () { const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); - const pools = await cone.getPoolIdentifiers( + const pools = await velocimeter.getPoolIdentifiers( tokenA, tokenB, SwapSide.SELL, @@ -522,7 +555,7 @@ describe('Solidly integration tests', () => { expect(pools.length).toBeGreaterThan(0); - const poolPrices = await cone.getPricesVolume( + const poolPrices = await velocimeter.getPricesVolume( tokenA, tokenB, amounts, @@ -540,41 +573,31 @@ 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, + velocimeter, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); } }); - - it('getTopPoolsForToken', async function () { - const poolLiquidity = await cone.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 TokenASymbol = 'lzUSDC'; const tokenA = Tokens[network][TokenASymbol]; - const TokenBSymbol = 'BUSD'; + const TokenBSymbol = 'axlUSDC'; const tokenB = Tokens[network][TokenBSymbol]; - const amounts = amounts6; + const amounts = amounts6; // amounts6; it('getPoolIdentifiers and getPricesVolume', async function () { const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); - const pools = await cone.getPoolIdentifiers( + const pools = await velocimeter.getPoolIdentifiers( tokenA, tokenB, SwapSide.SELL, @@ -587,7 +610,7 @@ describe('Solidly integration tests', () => { expect(pools.length).toBeGreaterThan(0); - const poolPrices = await cone.getPricesVolume( + const poolPrices = await velocimeter.getPricesVolume( tokenA, tokenB, amounts, @@ -604,52 +627,98 @@ 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, + velocimeter, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); } }); + }); - it('getTopPoolsForToken', async function () { - const poolLiquidity = await cone.getTopPoolsForToken( - tokenA.address, - 10, + describe('FTM -> FVM', () => { + const TokenASymbol = 'WFTM'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'FVM'; + 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 velocimeter.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, ); - console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); - checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velocimeter.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( + velocimeter, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } }); }); }); }); - describe('Arbitrum', () => { - const network = Network.ARBITRUM; + describe('Polygon', () => { + const network = Network.POLYGON; const dexHelper = new DummyDexHelper(network); const checkOnChainPricing = constructCheckOnChainPricing(dexHelper); - describe('Chronos', function () { - const dexKey = 'Chronos'; - const chronos = new Chronos(network, dexKey, dexHelper); + describe('Dystopia', function () { + const dexKey = 'Dystopia'; + const dystopia = new Solidly(network, dexKey, dexHelper); describe('UniswapV2 like pool', function () { - const TokenASymbol = 'USDC'; + const TokenASymbol = 'WETH'; const tokenA = Tokens[network][TokenASymbol]; - const TokenBSymbol = 'WETH'; + const TokenBSymbol = 'WMATIC'; const tokenB = Tokens[network][TokenBSymbol]; const amounts = amounts18; it('getPoolIdentifiers and getPricesVolume', async function () { const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); - const pools = await chronos.getPoolIdentifiers( + const pools = await dystopia.getPoolIdentifiers( tokenA, tokenB, SwapSide.SELL, @@ -662,7 +731,7 @@ describe('Solidly integration tests', () => { expect(pools.length).toBeGreaterThan(0); - const poolPrices = await chronos.getPricesVolume( + const poolPrices = await dystopia.getPricesVolume( tokenA, tokenB, amounts, @@ -680,13 +749,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, + dystopia, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -694,7 +763,7 @@ describe('Solidly integration tests', () => { }); it('getTopPoolsForToken', async function () { - const poolLiquidity = await chronos.getTopPoolsForToken( + const poolLiquidity = await dystopia.getTopPoolsForToken( tokenA.address, 10, ); @@ -705,16 +774,16 @@ describe('Solidly integration tests', () => { }); describe('Curve like stable pool', function () { - const TokenASymbol = 'USDT'; + const TokenASymbol = 'DAI'; // 'USDT'; const tokenA = Tokens[network][TokenASymbol]; const TokenBSymbol = 'USDC'; const tokenB = Tokens[network][TokenBSymbol]; - const amounts = amounts6; + const amounts = amounts18; // amounts6; it('getPoolIdentifiers and getPricesVolume', async function () { const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); - const pools = await chronos.getPoolIdentifiers( + const pools = await dystopia.getPoolIdentifiers( tokenA, tokenB, SwapSide.SELL, @@ -727,7 +796,7 @@ describe('Solidly integration tests', () => { expect(pools.length).toBeGreaterThan(0); - const poolPrices = await chronos.getPricesVolume( + const poolPrices = await dystopia.getPricesVolume( tokenA, tokenB, amounts, @@ -744,13 +813,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, + dystopia, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -758,7 +827,7 @@ describe('Solidly integration tests', () => { }); it('getTopPoolsForToken', async function () { - const poolLiquidity = await chronos.getTopPoolsForToken( + const poolLiquidity = await dystopia.getTopPoolsForToken( tokenA.address, 10, ); @@ -769,4 +838,908 @@ describe('Solidly integration tests', () => { }); }); }); + + describe('BSC', () => { + const network = Network.BSC; + const dexHelper = new DummyDexHelper(network); + const checkOnChainPricing = constructCheckOnChainPricing(dexHelper); + + describe('Cone', function () { + const dexKey = 'Cone'; + const cone = new Cone(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'WBNB'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'BUSD'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await cone.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await cone.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( + cone, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await cone.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 = 'BUSD'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts6; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await cone.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await cone.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( + cone, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await cone.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); + }); + + describe('Usdfi', function () { + const dexKey = 'Usdfi'; + const usdfi = new Usdfi(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'USDFI'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'USDT'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await usdfi.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await usdfi.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( + usdfi, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await usdfi.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 = 'BUSD'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts6; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await usdfi.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await usdfi.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( + usdfi, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await usdfi.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); + + describe('FRAX -> frxETH', () => { + const TokenASymbol = 'FRAX'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'frxETH'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await usdfi.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await usdfi.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( + usdfi, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + }); + }); + + describe('Arbitrum', () => { + const network = Network.ARBITRUM; + const dexHelper = new DummyDexHelper(network); + const checkOnChainPricing = constructCheckOnChainPricing(dexHelper); + + describe('Chronos', function () { + const dexKey = 'Chronos'; + const chronos = new Chronos(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', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await chronos.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await chronos.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( + chronos, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await chronos.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 = 'USDC'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts6; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await chronos.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await chronos.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( + chronos, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await chronos.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); + }); + + 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, + ); + } + }); + }); + }); + + describe('Velocimeter', () => { + const dexKey = 'Velocimeter'; + const velocimeter = new Velocimeter(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'WETH'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'USDbC'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await velocimeter.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velocimeter.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( + velocimeter, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + + describe('Curve like stable pool', function () { + const TokenASymbol = 'USDbC'; + 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 velocimeter.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velocimeter.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( + velocimeter, + '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..ff96dd475 100644 --- a/src/dex/solidly/solidly.ts +++ b/src/dex/solidly/solidly.ts @@ -66,12 +66,18 @@ export class Solidly extends UniswapV2 { getDexKeysWithNetwork( _.omit(SolidlyConfig, [ 'Velodrome', + 'VelodromeV2', + 'Aerodrome', 'SpiritSwapV2', 'Cone', 'SolidlyV2', 'Thena', 'SoliSnek', 'Chronos', + 'Ramses', + 'Equalizer', + 'Velocimeter', + 'Usdfi', ]), ); @@ -520,6 +526,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/swaap-v2/config.ts b/src/dex/swaap-v2/config.ts index 78f9f6222..5a43286c6 100644 --- a/src/dex/swaap-v2/config.ts +++ b/src/dex/swaap-v2/config.ts @@ -6,8 +6,7 @@ export const SwaapV2Config: DexConfigMap = { SwaapV2: { [Network.MAINNET]: {}, [Network.POLYGON]: {}, - // Arbitrum will be supported later - // [Network.ARBITRUM]: {}, + [Network.ARBITRUM]: {}, }, }; @@ -20,4 +19,8 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'PolygonAdapter02', index: 8 }], [SwapSide.BUY]: [{ name: 'PolygonBuyAdapter', index: 7 }], }, + [Network.ARBITRUM]: { + [SwapSide.SELL]: [{ name: 'ArbitrumAdapter02', index: 6 }], + [SwapSide.BUY]: [{ name: 'ArbitrumBuyAdapter', index: 8 }], + }, }; diff --git a/src/dex/swaap-v2/constants.ts b/src/dex/swaap-v2/constants.ts index 3274d6560..8c6684abf 100644 --- a/src/dex/swaap-v2/constants.ts +++ b/src/dex/swaap-v2/constants.ts @@ -20,15 +20,19 @@ export const SWAAP_RFQ_PRICES_ENDPOINT = 'prices'; export const SWAAP_RFQ_QUOTE_ENDPOINT = 'quote'; +export const SWAAP_NOTIFY_ENDPOINT = 'notify'; + export const SWAAP_RFQ_TOKENS_ENDPOINT = 'tokens'; -export const SWAAP_BLACKLIST_TTL_S = 60 * 60 * 24; // 24 hours +export const SWAAP_403_TTL_S = 60 * 60 * 24; // 24 hours + +export const SWAAP_429_TTL_S = 60 * 60 * 1; // 1 hour -export const SWAAP_RESTRICT_TTL_S = 60 * 30; // 30 minutes +export const SWAAP_POOL_RESTRICT_TTL_S = 60 * 30; // 30 minutes -export const SWAAP_RESTRICTED_CACHE_KEY = 'restricted'; +export const STABLE_SWAP_GAS_COST_ESTIMATION = 130_000; -export const GAS_COST_ESTIMATION = 170_000; +export const VOLATILE_SWAP_GAS_COST_ESTIMATION = 150_000; export const BATCH_SWAP_SELECTOR = '0x945bcec9'; @@ -40,3 +44,9 @@ export const SWAAP_ORDER_TYPE_BUY = 2; export const SWAAP_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION = new BigNumber('0.001'); + +export const SWAAP_NOTIFY_TIMEOUT_MS = 2000; + +export const SWAAP_NOTIFICATION_ORIGIN = 'paraswap'; + +export const SWAAP_BANNED_CODE = 1; diff --git a/src/dex/swaap-v2/rate-fetcher.ts b/src/dex/swaap-v2/rate-fetcher.ts index 2adc9b208..d47b30313 100644 --- a/src/dex/swaap-v2/rate-fetcher.ts +++ b/src/dex/swaap-v2/rate-fetcher.ts @@ -11,14 +11,21 @@ import { SwaapV2OrderType, SwaapV2TokensResponse, TokensMap, + SwaapV2NotificationRequest, + SwaapV2NotificationResponse, } from './types'; import { priceLevelsResponseValidator, getQuoteResponseValidator, getTokensResponseValidator, + notifyResponseValidator, } from './validators'; import { normalizeTokenAddress } from './utils'; -import { SWAAP_RFQ_QUOTE_TIMEOUT_MS } from './constants'; +import { + SWAAP_RFQ_QUOTE_TIMEOUT_MS, + SWAAP_NOTIFY_TIMEOUT_MS, + SWAAP_NOTIFICATION_ORIGIN, +} from './constants'; import { RequestConfig } from '../../dex-helper/irequest-wrapper'; export class RateFetcher { @@ -211,4 +218,47 @@ export class RateFetcher { throw e; } } + + async notify( + code: number, + message: string, + requestParameters: RequestConfig, + ): Promise { + const _payload: SwaapV2NotificationRequest = { + origin: SWAAP_NOTIFICATION_ORIGIN, + code: code, + message: message, + }; + + try { + let payload: RequestConfig = { + data: _payload, + ...requestParameters, + timeout: SWAAP_NOTIFY_TIMEOUT_MS, + }; + + this.logger.info( + 'Notify Request:', + JSON.stringify(payload).replace(/(?:\r\n|\r|\n)/g, ' '), + ); + const { data } = await this.dexHelper.httpRequest.request( + payload, + ); + this.logger.info( + 'Notify Response: ', + JSON.stringify(data).replace(/(?:\r\n|\r|\n)/g, ' '), + ); + const notifyResp = validateAndCast( + data, + notifyResponseValidator, + ); + + return { + success: notifyResp.success, + }; + } catch (e) { + this.logger.error(e); + throw e; + } + } } diff --git a/src/dex/swaap-v2/stable-coins.ts b/src/dex/swaap-v2/stable-coins.ts new file mode 100644 index 000000000..a634f39c9 --- /dev/null +++ b/src/dex/swaap-v2/stable-coins.ts @@ -0,0 +1,25 @@ +import { Network } from '../../constants'; + +// addresses must be consistant with utils.normalizeTokenAddress +export const STABLE_COINS: { + [network: number]: { [symbol: string]: boolean }; +} = { + [Network.MAINNET]: { + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': true, // USDT + '0xdac17f958d2ee523a2206206994597c13d831ec7': true, // USDC + '0x6b175474e89094c44da98b954eedeac495271d0f': true, // DAI + '0x853d955acef822db058eb8505911ed77f175b99e': true, // FRAX + }, + [Network.POLYGON]: { + '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359': true, // USDC + '0x2791bca1f2de4661ed88a30c99a7a9449aa84174': true, // USDC.e + '0xc2132d05d31c914a87c6611c10748aeb04b58e8f': true, // USDT + '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063': true, // DAI + }, + [Network.ARBITRUM]: { + '0xaf88d065e77c8cc2239327c5edb3a432268e5831': true, // USDC + '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8': true, // USDC.e + '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9': true, // USDT + '0xda10009cbd5d07dd0cecc66161fc93d7c9000da1': true, // DAI + }, +}; diff --git a/src/dex/swaap-v2/swaap-v2-e2e.test.ts b/src/dex/swaap-v2/swaap-v2-e2e.test.ts index e02a61f9a..ddbed8204 100644 --- a/src/dex/swaap-v2/swaap-v2-e2e.test.ts +++ b/src/dex/swaap-v2/swaap-v2-e2e.test.ts @@ -224,46 +224,79 @@ describe('SwaapV2 E2E', () => { ); }); - // Arbitrum will be supported later - // describe('Arbitrum', () => { - // const network = Network.ARBITRUM; - // - // const tokenASymbol: string = 'ETH'; - // const tokenBSymbol: string = 'DAI'; - // - // const tokenAAmount: string = '100000000'; - // const tokenBAmount: string = '1000000000000000000'; - // const nativeTokenAmount = '1000000000000000000'; - // - // testForNetwork( - // network, - // dexKey, - // tokenASymbol, - // tokenBSymbol, - // tokenAAmount, - // tokenBAmount, - // nativeTokenAmount, - // ); - // }); + describe('Arbitrum', () => { + const network = Network.ARBITRUM; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + + const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = [ + [ + { + name: 'WETH', + sellAmount: '10000000000000000', + buyAmount: '1000000000000000000', + }, + { + name: 'DAI', + sellAmount: '1000000000000000000', + buyAmount: '10000000000000000', + }, + ], + ]; - // Mainnet will be supported later - // describe('Mainnet', () => { - // const network = Network.MAINNET; - // const tokenASymbol: string = 'USDC'; - // const tokenBSymbol: string = 'USDT'; - // - // const tokenAAmount: string = '100000000'; - // const tokenBAmount: string = '1000000000000000000'; - // const nativeTokenAmount = '1000000000000000000'; - // - // testForNetwork( - // network, - // dexKey, - // tokenASymbol, - // tokenBSymbol, - // tokenAAmount, - // tokenBAmount, - // nativeTokenAmount, - // ); - // }); + 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, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + 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, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + }); + }); + }); + }), + ); + }); }); diff --git a/src/dex/swaap-v2/swaap-v2.ts b/src/dex/swaap-v2/swaap-v2.ts index 1b32a5103..569cbcdcc 100644 --- a/src/dex/swaap-v2/swaap-v2.ts +++ b/src/dex/swaap-v2/swaap-v2.ts @@ -11,7 +11,13 @@ import { OptimalSwapExchange, PreprocessTransactionOptions, } from '../../types'; -import { SwapSide, Network, MAX_INT, MAX_UINT } from '../../constants'; +import { + SwapSide, + Network, + MAX_INT, + MAX_UINT, + CACHE_PREFIX, +} from '../../constants'; import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; import { getDexKeysWithNetwork, isAxiosError } from '../../utils'; import { IDex } from '../idex'; @@ -23,6 +29,7 @@ import { SwaapV2APIParameters, SwaapV2QuoteError, TokensMap, + SwaapV2NotificationResponse, } from './types'; import { SimpleExchange } from '../simple-exchange'; import { Adapters, SwaapV2Config } from './config'; @@ -38,13 +45,14 @@ import { SWAAP_RFQ_QUOTE_ENDPOINT, SWAAP_RFQ_API_PRICES_POLLING_INTERVAL_MS, SWAAP_RFQ_PRICES_CACHES_TTL_S, - GAS_COST_ESTIMATION, + STABLE_SWAP_GAS_COST_ESTIMATION, + VOLATILE_SWAP_GAS_COST_ESTIMATION, BATCH_SWAP_SELECTOR, CALLER_SLOT, - SWAAP_BLACKLIST_TTL_S, + SWAAP_403_TTL_S, + SWAAP_429_TTL_S, SWAAP_RFQ_TOKENS_ENDPOINT, - SWAAP_RESTRICT_TTL_S, - SWAAP_RESTRICTED_CACHE_KEY, + SWAAP_POOL_RESTRICT_TTL_S, SWAAP_RFQ_API_TOKENS_POLLING_INTERVAL_MS, SWAAP_RFQ_TOKENS_CACHES_TTL_S, SWAAP_PRICES_CACHE_KEY, @@ -52,10 +60,20 @@ import { SWAAP_ORDER_TYPE_SELL, SWAAP_ORDER_TYPE_BUY, SWAAP_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION, + SWAAP_BANNED_CODE, + SWAAP_NOTIFY_ENDPOINT, } from './constants'; -import { getPoolIdentifier, normalizeTokenAddress, getPairName } from './utils'; +import { + getPoolIdentifier, + normalizeTokenAddress, + getPairName, + isStablePair, +} from './utils'; import { Method } from '../../dex-helper/irequest-wrapper'; -import { SlippageCheckError, TooStrictSlippageCheckError } from '../generic-rfq/types'; +import { + SlippageCheckError, + TooStrictSlippageCheckError, +} from '../generic-rfq/types'; import { BI_MAX_UINT256 } from '../../bigint-constants'; const BLACKLISTED = 'blacklisted'; @@ -68,6 +86,7 @@ export class SwaapV2 extends SimpleExchange implements IDex { private rateFetcher: RateFetcher; private swaapV2AuthToken: string; private tokensMap: TokensMap = {}; + private runtimeMMsRestrictHashMapKey: string; public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = getDexKeysWithNetwork(SwaapV2Config); @@ -110,6 +129,9 @@ export class SwaapV2 extends SimpleExchange implements IDex { }, }, ); + + this.runtimeMMsRestrictHashMapKey = + `${CACHE_PREFIX}_${this.dexKey}_${this.network}_restricted_mms`.toLowerCase(); } async initializePricing(blockNumber: number): Promise { @@ -295,22 +317,22 @@ export class SwaapV2 extends SimpleExchange implements IDex { blockNumber: number, limitPools?: string[], ): Promise> { + const normalizedSrcToken = this.normalizeToken(srcToken); + const normalizedDestToken = this.normalizeToken(destToken); + + const requestedPoolIdentifier: string = getPoolIdentifier( + this.dexKey, + normalizedSrcToken.address, + normalizedDestToken.address, + ); + try { - if (await this.isRestricted()) { + if (await this.isRestrictedPool(requestedPoolIdentifier)) { return null; } this.tokensMap = (await this.getCachedTokens()) || {}; - const normalizedSrcToken = this.normalizeToken(srcToken); - const normalizedDestToken = this.normalizeToken(destToken); - - const requestedPoolIdentifier: string = getPoolIdentifier( - this.dexKey, - normalizedSrcToken.address, - normalizedDestToken.address, - ); - if (normalizedSrcToken.address === normalizedDestToken.address) { return null; } @@ -388,8 +410,16 @@ export class SwaapV2 extends SimpleExchange implements IDex { return null; } + const gasCost = isStablePair( + this.network, + normalizedSrcToken.address, + normalizedDestToken.address, + ) + ? STABLE_SWAP_GAS_COST_ESTIMATION + : VOLATILE_SWAP_GAS_COST_ESTIMATION; + return { - gasCost: GAS_COST_ESTIMATION, + gasCost: gasCost, exchange: this.dexKey, data: {}, prices, @@ -558,16 +588,18 @@ export class SwaapV2 extends SimpleExchange implements IDex { { deadline: minDeadline }, ]; } catch (e) { - if ( - isAxiosError(e) && - (e.response?.status === 403 || e.response?.status === 429) - ) { - await this.setBlacklist(options.txOrigin); + if (isAxiosError(e) && e.response?.status === 403) { + await this.setBlacklist(options.txOrigin, SWAAP_403_TTL_S); + this.logger.warn( + `${this.dexKey}-${this.network}: Encountered blacklisted user=${options.txOrigin}. Adding to local blacklist cache`, + ); + } else if (isAxiosError(e) && e.response?.status === 429) { + await this.setBlacklist(options.txOrigin, SWAAP_429_TTL_S); this.logger.warn( `${this.dexKey}-${this.network}: Encountered restricted user=${options.txOrigin}. Adding to local blacklist cache`, ); } else { - if(e instanceof TooStrictSlippageCheckError) { + if (e instanceof TooStrictSlippageCheckError) { this.logger.warn( `${this.dexKey}-${this.network}: failed to build transaction on side ${side} with too strict slippage. Skipping restriction`, ); @@ -575,7 +607,16 @@ export class SwaapV2 extends SimpleExchange implements IDex { this.logger.warn( `${this.dexKey}-${this.network}: protocol is restricted`, ); - await this.restrict(); + const poolIdentifier = getPoolIdentifier( + this.dexKey, + normalizedSrcToken.address, + normalizedDestToken.address, + ); + var message = 'Unknown error'; + if (e instanceof Error) { + message = `${e.name}: ${e.message}`; + } + await this.restrictPool(message, poolIdentifier); } } @@ -583,25 +624,45 @@ export class SwaapV2 extends SimpleExchange implements IDex { } } - async restrict(ttl: number = SWAAP_RESTRICT_TTL_S): Promise { - await this.dexHelper.cache.setex( - this.dexKey, - this.network, - SWAAP_RESTRICTED_CACHE_KEY, - ttl, - 'true', + async restrictPool(message: string, poolIdentifier: string): Promise { + this.logger.warn( + `${this.dexKey}-${this.network}: ${poolIdentifier} was restricted for ${SWAAP_POOL_RESTRICT_TTL_S} sec. due to fails`, ); - return true; - } - async isRestricted(): Promise { - const result = await this.dexHelper.cache.get( - this.dexKey, - this.network, - SWAAP_RESTRICTED_CACHE_KEY, + // We use timestamp for creation date to later discern if it already expired or not + await this.dexHelper.cache.hset( + this.runtimeMMsRestrictHashMapKey, + poolIdentifier, + Date.now().toString(), ); - return result === 'true'; + this.rateFetcher + .notify(SWAAP_BANNED_CODE, message, this.getNotifyReqParams()) + .then((answer: SwaapV2NotificationResponse) => { + if (answer.success) { + this.logger.info(`Successfully notified Swaap API`); + } else { + this.logger.error(`Swaap API was not successfully notified`); + } + }) + .catch(e => { + // Must never happen + this.logger.error(`Swaap API notification failed: ${e}`); + }); + } + + async isRestrictedPool(poolIdentifier: string): Promise { + const expirationThreshold = Date.now() - SWAAP_POOL_RESTRICT_TTL_S * 1000; + const createdAt = await this.dexHelper.cache.hget( + this.runtimeMMsRestrictHashMapKey, + poolIdentifier, + ); + const wasNotRestricted = createdAt === null; + if (wasNotRestricted) { + return false; + } + const restrictionExpired = +createdAt < expirationThreshold; + return !restrictionExpired; } async isBlacklisted(txOrigin: Address): Promise { @@ -619,7 +680,7 @@ export class SwaapV2 extends SimpleExchange implements IDex { async setBlacklist( txOrigin: Address, - ttl: number = SWAAP_BLACKLIST_TTL_S, + ttl: number = SWAAP_403_TTL_S, ): Promise { await this.dexHelper.cache.setex( this.dexKey, @@ -863,10 +924,6 @@ export class SwaapV2 extends SimpleExchange implements IDex { tokenAddress: Address, limit: number, ): Promise { - if (await this.isRestricted()) { - return []; - } - this.tokensMap = (await this.getCachedTokens()) || {}; const normalizedTokenAddress = normalizeTokenAddress(tokenAddress); @@ -877,12 +934,22 @@ export class SwaapV2 extends SimpleExchange implements IDex { } return Object.keys(pLevels) - .filter((pair: string) => { + .filter(async (pair: string) => { const { base, quote } = pLevels[pair]; - return ( - normalizedTokenAddress === base || normalizedTokenAddress === quote + const levelDoesNotIncludeToken = + normalizedTokenAddress !== base && normalizedTokenAddress !== quote; + if (levelDoesNotIncludeToken) { + return false; + } + + const poolIdentifier: string = getPoolIdentifier( + this.dexKey, + base, + quote, ); + const isRestrictedPool = await this.isRestrictedPool(poolIdentifier); + return !isRestrictedPool; }) .map((pair: string) => { return { @@ -921,6 +988,10 @@ export class SwaapV2 extends SimpleExchange implements IDex { return this.getAPIReqParams(SWAAP_RFQ_QUOTE_ENDPOINT, 'POST'); } + getNotifyReqParams(): SwaapV2APIParameters { + return this.getAPIReqParams(SWAAP_NOTIFY_ENDPOINT, 'POST'); + } + getTokensReqParams(): SwaapV2APIParameters { return this.getAPIReqParams(SWAAP_RFQ_TOKENS_ENDPOINT, 'GET'); } diff --git a/src/dex/swaap-v2/types.ts b/src/dex/swaap-v2/types.ts index 1c7572b83..1ac7a9d9f 100644 --- a/src/dex/swaap-v2/types.ts +++ b/src/dex/swaap-v2/types.ts @@ -93,3 +93,13 @@ export type SwaapV2APIParameters = { export type TokensMap = { [address: string]: Token; }; + +export type SwaapV2NotificationRequest = { + origin: string; + code: number; + message: string; +}; + +export type SwaapV2NotificationResponse = { + success: boolean; +}; diff --git a/src/dex/swaap-v2/utils.ts b/src/dex/swaap-v2/utils.ts index 77a8a8063..ee7572b7d 100644 --- a/src/dex/swaap-v2/utils.ts +++ b/src/dex/swaap-v2/utils.ts @@ -1,32 +1,27 @@ -import { Address } from '../../types'; -import { CACHE_PREFIX, ETHER_ADDRESS, NULL_ADDRESS } from '../../constants'; +import { Address, Token } from '../../types'; +import { ETHER_ADDRESS, NULL_ADDRESS } from '../../constants'; +import { Network } from '../../constants'; +import { STABLE_COINS } from './stable-coins'; export const getIdentifierPrefix = ( dexKey: string, - srcAddress: Address, - destAddress: Address, + tokenA: Address, + tokenB: Address, ) => { - return `${dexKey}_${getPairName(srcAddress, destAddress)}`.toLowerCase(); + return `${dexKey}_${getPairName(tokenA, tokenB)}`.toLowerCase(); }; -export const getPairName = (srcAddress: Address, destAddress: Address) => { - const sortedAddresses = - srcAddress < destAddress - ? [srcAddress, destAddress] - : [destAddress, srcAddress]; +export const getPairName = (tokenA: Address, tokenB: Address) => { + const sortedAddresses = tokenA < tokenB ? [tokenA, tokenB] : [tokenB, tokenA]; return `${sortedAddresses[0]}_${sortedAddresses[1]}`.toLowerCase(); }; export const getPoolIdentifier = ( dexKey: string, - srcAddress: Address, - destAddress: Address, + tokenA: Address, + tokenB: Address, ) => { - return `${getIdentifierPrefix( - dexKey, - srcAddress, - destAddress, - )}`.toLowerCase(); + return `${getIdentifierPrefix(dexKey, tokenA, tokenB)}`.toLowerCase(); }; export const normalizeTokenAddress = (address: string): string => { @@ -34,3 +29,18 @@ export const normalizeTokenAddress = (address: string): string => { ? NULL_ADDRESS : address.toLowerCase(); }; + +export const isStablePair = ( + network: Network, + normalizedSrcTokenAddress: string, + normalizedDestTokenAddress: string, +): boolean => { + const networkStableCoins = STABLE_COINS[network]; + if (networkStableCoins === undefined) { + return false; + } + return ( + networkStableCoins[normalizedSrcTokenAddress] === true && + networkStableCoins[normalizedDestTokenAddress] === true + ); +}; diff --git a/src/dex/swaap-v2/validators.ts b/src/dex/swaap-v2/validators.ts index 69e239ec9..c065ed7bc 100644 --- a/src/dex/swaap-v2/validators.ts +++ b/src/dex/swaap-v2/validators.ts @@ -126,3 +126,9 @@ export const getQuoteResponseValidator = joi success: joi.boolean().required(), }) .unknown(true); + +export const notifyResponseValidator = joi + .object({ + success: joi.boolean().required(), + }) + .unknown(true); diff --git a/src/dex/swell/config.ts b/src/dex/swell/config.ts new file mode 100644 index 000000000..869eebc4f --- /dev/null +++ b/src/dex/swell/config.ts @@ -0,0 +1,27 @@ +import { Network, SwapSide } from '../../constants'; +import { DexConfigMap } from '../../types'; + +type DexParams = { + swETH: string; +}; + +export const SwellConfig: DexConfigMap = { + Swell: { + [Network.MAINNET]: { + swETH: '0xf951E335afb289353dc249e82926178EaC7DEd78', + }, + }, +}; + +export const Adapters: { + [chainId: number]: { [side: string]: { name: string; index: number }[] }; +} = { + [Network.MAINNET]: { + [SwapSide.SELL]: [ + { + name: 'Adapter05', + index: 1, + }, + ], + }, +}; diff --git a/src/dex/swell/swell-e2e.test.ts b/src/dex/swell/swell-e2e.test.ts new file mode 100644 index 000000000..7f4e83ee1 --- /dev/null +++ b/src/dex/swell/swell-e2e.test.ts @@ -0,0 +1,53 @@ +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('Swell', () => { + const network = Network.MAINNET; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const dexKey = 'Swell'; + + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ].forEach(contractMethod => { + it(`${contractMethod} - ETH -> SWETH`, async () => { + await testE2E( + tokens.ETH, + tokens.SWETH, + holders.ETH, + '1000000000000000000', + SwapSide.SELL, + dexKey, + contractMethod, + network, + provider, + ); + }); + + it(`${contractMethod} - WETH -> SWETH`, async () => { + await testE2E( + tokens.WETH, + tokens.SWETH, + holders.WETH, + '1000000000000000000', + SwapSide.SELL, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); +}); diff --git a/src/dex/swell/swell.ts b/src/dex/swell/swell.ts new file mode 100644 index 000000000..76915a220 --- /dev/null +++ b/src/dex/swell/swell.ts @@ -0,0 +1,234 @@ +import { Interface, JsonFragment } from '@ethersproject/abi'; +import { NumberAsString, SwapSide } from '@paraswap/core'; +import { + AdapterExchangeParam, + Address, + ExchangePrices, + Logger, + PoolLiquidity, + PoolPrices, + SimpleExchangeParam, + Token, + TransferFeeParams, +} from '../../types'; +import { IDex } from '../idex'; +import SWETH_ABI from '../../abi/swETH.json'; +import { ETHER_ADDRESS, Network } from '../../constants'; +import { IDexHelper } from '../../dex-helper'; +import { SimpleExchange } from '../simple-exchange'; +import { BI_POWS } from '../../bigint-constants'; +import { AsyncOrSync } from 'ts-essentials'; +import { getOnChainState } from './utils'; +import { SwethPool } from './sweth-pool'; +import { getDexKeysWithNetwork, isETHAddress } from '../../utils'; +import { WethFunctions } from '../weth/types'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import _ from 'lodash'; +import { SwellConfig, Adapters } from './config'; + +export enum swETHFunctions { + deposit = 'deposit', +} + +export type SwellData = {}; +export type SwellParams = {}; + +export class Swell + extends SimpleExchange + implements IDex +{ + static dexKeys = ['Swell']; + swETHInterface: Interface; + needWrapNative = false; + hasConstantPriceLargeAmounts: boolean = true; + swETHAddress: string; + eventPool: SwethPool; + logger: Logger; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SwellConfig, ['Swell'])); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + protected config = SwellConfig[dexKey][network], + protected adapters = Adapters[network], + ) { + super(dexHelper, 'Swell'); + + this.network = dexHelper.config.data.network; + this.swETHInterface = new Interface(SWETH_ABI as JsonFragment[]); + this.swETHAddress = this.config.swETH.toLowerCase(); + this.logger = dexHelper.getLogger(this.dexKey); + this.eventPool = new SwethPool( + this.dexKey, + dexHelper, + this.swETHAddress, + this.swETHInterface, + this.logger, + ); + } + + async initializePricing(blockNumber: number) { + const poolState = await getOnChainState( + this.dexHelper.multiContract, + this.swETHAddress, + this.swETHInterface, + blockNumber, + ); + + await this.eventPool.initialize(blockNumber, { + state: poolState, + }); + } + + getPoolIdentifierKey(): string { + return `${ETHER_ADDRESS}_${this.swETHAddress}`.toLowerCase(); + } + + isEligibleSwap( + srcToken: Token | string, + destToken: Token | string, + side: SwapSide, + ): boolean { + if (side === SwapSide.BUY) return false; + + const srcTokenAddress = ( + typeof srcToken === 'string' ? srcToken : srcToken.address + ).toLowerCase(); + const destTokenAddress = ( + typeof destToken === 'string' ? destToken : destToken.address + ).toLowerCase(); + + return ( + (isETHAddress(srcTokenAddress) || this.isWETH(srcTokenAddress)) && + destTokenAddress === this.swETHAddress + ); + } + + assertEligibility( + srcToken: Token | string, + destToken: Token | string, + side: SwapSide, + ) { + if (!this.isEligibleSwap(srcToken, destToken, side)) { + throw new Error('Only eth/weth -> swETH swaps are supported'); + } + } + + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + return this.isEligibleSwap(srcToken, destToken, side) + ? [this.getPoolIdentifierKey()] + : []; + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amountsIn: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[] | undefined, + transferFees?: TransferFeeParams | undefined, + isFirstSwap?: boolean | undefined, + ): Promise | null> { + if (!this.isEligibleSwap(srcToken, destToken, side)) return null; + if (this.eventPool.getState(blockNumber) === null) return null; + + const unitIn = BI_POWS[18]; + const unitOut = this.eventPool.getPrice(blockNumber, unitIn); + const amountsOut = amountsIn.map(amountIn => + this.eventPool.getPrice(blockNumber, amountIn), + ); + + return [ + { + prices: amountsOut, + unit: unitOut, + data: {}, + exchange: this.dexKey, + poolIdentifier: this.getPoolIdentifierKey(), + gasCost: 120_000, + poolAddresses: [this.swETHAddress], + }, + ]; + } + + getAdapterParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + data: SwellData, + side: SwapSide, + ): AdapterExchangeParam { + this.assertEligibility(srcToken, destToken, side); + + return { + targetExchange: this.swETHAddress, // not used contract side + payload: '0x', + networkFee: '0', + }; + } + + async getSimpleParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + data: SwellData, + side: SwapSide, + ): Promise { + this.assertEligibility(srcToken, destToken, side); + + const callees = []; + const calldata = []; + const values = []; + + if (this.isWETH(srcToken)) { + // note: apparently ERC20 ABI contains wETH fns (deposit() and withdraw()) + const wethUnwrapData = this.erc20Interface.encodeFunctionData( + WethFunctions.withdraw, + [srcAmount], + ); + callees.push(this.dexHelper.config.data.wrappedNativeTokenAddress); + calldata.push(wethUnwrapData); + values.push('0'); + } + + const swapData = this.swETHInterface.encodeFunctionData( + swETHFunctions.deposit, + [], + ); + + callees.push(this.swETHAddress); + calldata.push(swapData); + values.push(srcAmount); + + return { + callees, + calldata, + values, + networkFee: '0', + }; + } + + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + return CALLDATA_GAS_COST.DEX_OVERHEAD + CALLDATA_GAS_COST.LENGTH_SMALL; + } + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return this.adapters?.[side] || null; + } + getTopPoolsForToken( + tokenAddress: string, + limit: number, + ): AsyncOrSync { + return []; + } +} diff --git a/src/dex/swell/sweth-pool.ts b/src/dex/swell/sweth-pool.ts new file mode 100644 index 000000000..b708919df --- /dev/null +++ b/src/dex/swell/sweth-pool.ts @@ -0,0 +1,58 @@ +import { Interface } from '@ethersproject/abi'; +import { IDexHelper } from '../../dex-helper'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { Address, Log, Logger } from '../../types'; +import { AsyncOrSync, DeepReadonly } from 'ts-essentials'; +import { SWETHPoolState } from './type'; +import { getOnChainState } from './utils'; +import { BI_POWS } from '../../bigint-constants'; + +export class SwethPool extends StatefulEventSubscriber { + decoder = (log: Log) => this.poolInterface.parseLog(log); + + constructor( + parentName: string, + protected dexHelper: IDexHelper, + private poolAddress: Address, + private poolInterface: Interface, + logger: Logger, + ) { + super(parentName, 'sweth', dexHelper, logger); + this.addressesSubscribed = [poolAddress]; + } + + protected processLog( + state: DeepReadonly, + log: Readonly, + ): AsyncOrSync | null> { + const event = this.decoder(log); + if (event.name === 'Reprice') + return { + swETHToETHRateFixed: BigInt(event.args.newSwETHToETHRate), + }; + + return null; + } + + async generateState( + blockNumber: number | 'latest' = 'latest', + ): Promise> { + const state = await getOnChainState( + this.dexHelper.multiContract, + this.poolAddress, + this.poolInterface, + blockNumber, + ); + + return state; + } + + getPrice(blockNumber: number, ethAmount: bigint): bigint { + const state = this.getState(blockNumber); + if (!state) throw new Error('Cannot compute price'); + const { swETHToETHRateFixed } = state; + + // calculation in contract are made with UD60x18 precision + return (ethAmount * BI_POWS[18]) / swETHToETHRateFixed; + } +} diff --git a/src/dex/swell/type.ts b/src/dex/swell/type.ts new file mode 100644 index 000000000..481ddcd83 --- /dev/null +++ b/src/dex/swell/type.ts @@ -0,0 +1,3 @@ +export type SWETHPoolState = { + swETHToETHRateFixed: bigint; +}; diff --git a/src/dex/swell/utils.ts b/src/dex/swell/utils.ts new file mode 100644 index 000000000..23c1345ba --- /dev/null +++ b/src/dex/swell/utils.ts @@ -0,0 +1,29 @@ +import { Contract } from 'web3-eth-contract'; +import { SWETHPoolState } from './type'; +import { Interface, AbiCoder } from '@ethersproject/abi'; + +const coder = new AbiCoder(); + +export async function getOnChainState( + multiContract: Contract, + poolAddress: string, + poolInterface: Interface, + blockNumber: number | 'latest', +): Promise { + const data: { returnData: any[] } = await multiContract.methods + .aggregate([ + { + target: poolAddress, + callData: poolInterface.encodeFunctionData('swETHToETHRate', []), + }, + ]) + .call({}, blockNumber); + + const decodedData = coder.decode(['uint256'], data.returnData[0]); + + const swETHToETHRateFixed = BigInt(decodedData[0].toString()); + + return { + swETHToETHRateFixed, + }; +} diff --git a/src/dex/trader-joe-v2.1.ts b/src/dex/trader-joe-v2.1.ts index 23b21394f..32eb07dfa 100644 --- a/src/dex/trader-joe-v2.1.ts +++ b/src/dex/trader-joe-v2.1.ts @@ -15,6 +15,7 @@ const TRADERJOE_V2_1_ROUTER_ADDRESS: { [network: number]: Address } = { [Network.AVALANCHE]: '0xb4315e873dBcf96Ffd0acd8EA43f689D8c20fB30', [Network.ARBITRUM]: '0xb4315e873dBcf96Ffd0acd8EA43f689D8c20fB30', [Network.BSC]: '0xb4315e873dBcf96Ffd0acd8EA43f689D8c20fB30', + [Network.MAINNET]: '0x9A93a421b74F1c5755b83dD2C211614dC419C44b', }; type RouterPath = [ diff --git a/src/dex/uniswap-v2/config.ts b/src/dex/uniswap-v2/config.ts index 34fb93bc6..2749cdeac 100644 --- a/src/dex/uniswap-v2/config.ts +++ b/src/dex/uniswap-v2/config.ts @@ -117,6 +117,20 @@ export const Adapters: { }, ], }, + [Network.BASE]: { + [SwapSide.SELL]: [ + { + name: 'BaseAdapter01', + index: 6, + }, + ], + [SwapSide.BUY]: [ + { + name: 'BaseBuyAdapter', + index: 4, + }, + ], + }, }; export const UniswapV2Config: DexConfigMap = { @@ -136,6 +150,48 @@ export const UniswapV2Config: DexConfigMap = { poolGasCost: 80 * 1000, feeCode: 30, }, + [Network.ARBITRUM]: { + factoryAddress: '0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9', + initCode: + '0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f', + poolGasCost: 80 * 1000, + feeCode: 30, + }, + [Network.AVALANCHE]: { + factoryAddress: '0x9e5A52f57b3038F1B8EeE45F28b3C1967e22799C', + initCode: + '0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f', + poolGasCost: 80 * 1000, + feeCode: 30, + }, + [Network.BSC]: { + factoryAddress: '0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6', + initCode: + '0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f', + poolGasCost: 80 * 1000, + feeCode: 30, + }, + [Network.BASE]: { + factoryAddress: '0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6', + initCode: + '0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f', + poolGasCost: 80 * 1000, + feeCode: 30, + }, + [Network.OPTIMISM]: { + factoryAddress: '0x0c3c1c532F1e39EdF36BE9Fe0bE1410313E074Bf', + initCode: + '0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f', + poolGasCost: 80 * 1000, + feeCode: 30, + }, + [Network.POLYGON]: { + factoryAddress: '0x9e5A52f57b3038F1B8EeE45F28b3C1967e22799C', + initCode: + '0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f', + poolGasCost: 80 * 1000, + feeCode: 30, + }, }, ApeSwap: { [Network.BSC]: { @@ -696,4 +752,13 @@ export const UniswapV2Config: DexConfigMap = { feeCode: 30, }, }, + BaseSwap: { + [Network.BASE]: { + factoryAddress: '0xFDa619b6d20975be80A10332cD39b9a4b0FAa8BB', + initCode: + '0xb618a2730fae167f5f8ac7bd659dd8436d571872655bcb6fd11f2158c8a64a3b', + poolGasCost: 90 * 1000, + feeCode: 25, + }, + }, }; diff --git a/src/dex/uniswap-v2/constants.ts b/src/dex/uniswap-v2/constants.ts index 80c5d890a..6a654568c 100644 --- a/src/dex/uniswap-v2/constants.ts +++ b/src/dex/uniswap-v2/constants.ts @@ -48,10 +48,13 @@ export const UniswapForksWithNetwork = transformToNetworkMap({ export const UniswapV2Alias: { [network: number]: string } = { [Network.MAINNET]: 'uniswapv2', [Network.ROPSTEN]: 'uniswapv2', - [Network.BSC]: 'pancakeswap', - [Network.POLYGON]: 'quickswap', - [Network.AVALANCHE]: 'pangolinswap', + [Network.BSC]: 'uniswapv2', + [Network.POLYGON]: 'uniswapv2', + [Network.AVALANCHE]: 'uniswapv2', + [Network.ARBITRUM]: 'uniswapv2', + [Network.OPTIMISM]: 'uniswapv2', + [Network.BASE]: 'uniswapv2', + + // use only to handle UniswapForkOptimized build with this dex [Network.FANTOM]: 'spookyswap', - [Network.ARBITRUM]: 'sushiswap', - [Network.OPTIMISM]: 'zipswap', }; 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-avalanche.test.ts b/src/dex/uniswap-v2/uniswap-v2-e2e-avalanche.test.ts index 7eed0311c..c0e207916 100644 --- a/src/dex/uniswap-v2/uniswap-v2-e2e-avalanche.test.ts +++ b/src/dex/uniswap-v2/uniswap-v2-e2e-avalanche.test.ts @@ -241,6 +241,34 @@ describe('UniswapV2 E2E Avalanche', () => { provider, ); }); + + it('AMPL -> MIM', async () => { + await testE2E( + tokens.AMPL, + tokens.MIM, + holders.AMPL, + '1000000000', + SwapSide.SELL, + dexKey, + ContractMethod.simpleSwap, + network, + provider, + ); + }); + + it('MIM -> AMPL', async () => { + await testE2E( + tokens.MIM, + tokens.AMPL, + holders.MIM, + '1000000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.simpleSwap, + network, + provider, + ); + }); }); describe('multiSwap', () => { 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-v2/uniswap-v2-factory.ts b/src/dex/uniswap-v2/uniswap-v2-factory.ts new file mode 100644 index 000000000..3fe4d167b --- /dev/null +++ b/src/dex/uniswap-v2/uniswap-v2-factory.ts @@ -0,0 +1,71 @@ +import { Interface } from '@ethersproject/abi'; +import { DeepReadonly } from 'ts-essentials'; +import uniswapV2factoryABI from '../../abi/uniswap-v2/uniswap-v2-factory.json'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { Address, Log, Logger } from '../../types'; +import { LogDescription } from 'ethers/lib/utils'; + +export type FactoryState = Record; + +export type OnPoolCreatedCallback = ({ + token0, + token1, +}: { + token0: string; + token1: string; +}) => Promise; + +/* + * "Stateless" event subscriber in order to capture "PoolCreated" event on new pools created. + * State is present, but it's a placeholder to actually make the events reach handlers (if there's no previous state - `processBlockLogs` is not called) + */ +export class UniswapV2Factory extends StatefulEventSubscriber { + handlers: { + [event: string]: (event: any) => Promise; + } = {}; + + logDecoder: (log: Log) => any; + + public readonly factoryIface = new Interface(uniswapV2factoryABI); + + constructor( + readonly dexHelper: IDexHelper, + parentName: string, + protected readonly factoryAddress: Address, + logger: Logger, + protected readonly onPoolCreated: OnPoolCreatedCallback, + mapKey: string = '', + ) { + super(parentName, `${parentName} Factory`, dexHelper, logger, true, mapKey); + + this.addressesSubscribed = [factoryAddress]; + + this.logDecoder = (log: Log) => this.factoryIface.parseLog(log); + + this.handlers['PairCreated'] = this.handleNewPool.bind(this); + } + + generateState(): FactoryState { + return {}; + } + + protected async processLog( + _: DeepReadonly, + log: Readonly, + ): Promise { + const event = this.logDecoder(log); + if (event.name in this.handlers) { + await this.handlers[event.name](event); + } + + return {}; + } + + async handleNewPool(event: LogDescription) { + const token0 = event.args.token0.toLowerCase(); + const token1 = event.args.token1.toLowerCase(); + + await this.onPoolCreated({ token0, token1 }); + } +} diff --git a/src/dex/uniswap-v2/uniswap-v2.ts b/src/dex/uniswap-v2/uniswap-v2.ts index 134a3b40c..45be5a2e8 100644 --- a/src/dex/uniswap-v2/uniswap-v2.ts +++ b/src/dex/uniswap-v2/uniswap-v2.ts @@ -52,6 +52,21 @@ import { Contract } from 'web3-eth-contract'; import { UniswapV2Config, Adapters } from './config'; import { Uniswapv2ConstantProductPool } from './uniswap-v2-constant-product-pool'; import { applyTransferFee } from '../../lib/token-transfer-fee'; +import _rebaseTokens from '../../rebase-tokens.json'; + +const rebaseTokens = _rebaseTokens as { chainId: number; address: string }[]; + +const rebaseTokensSetsByChain = rebaseTokens.reduce<{ + [chainId: number]: Set; +}>((acc, curr) => { + if (!acc[curr.chainId]) { + acc[curr.chainId] = new Set(); + } + + acc[curr.chainId].add(curr.address.toLowerCase()); + + return acc; +}, {}); const DefaultUniswapV2PoolGasCost = 90 * 1000; @@ -68,7 +83,7 @@ interface UniswapV2PoolState { feeCode: number; } -const uniswapV2Iface = new Interface(uniswapV2ABI); +const uniswapV2PoolIface = new Interface(uniswapV2ABI); const erc20iface = new Interface(erc20ABI); const coder = new AbiCoder(); @@ -104,7 +119,7 @@ export class UniswapV2EventPool extends StatefulEventSubscriber number, - private iface: Interface = uniswapV2Iface, + private iface: Interface = uniswapV2PoolIface, ) { super( parentName, @@ -219,7 +234,7 @@ export class UniswapV2 protected poolGasCost: number = (UniswapV2Config[dexKey] && UniswapV2Config[dexKey][network].poolGasCost) ?? DefaultUniswapV2PoolGasCost, - protected decoderIface: Interface = uniswapV2Iface, + protected decoderIface: Interface = uniswapV2PoolIface, protected adapters = (UniswapV2Config[dexKey] && UniswapV2Config[dexKey][network].adapters) ?? Adapters[network], @@ -772,6 +787,27 @@ export class UniswapV2 side === SwapSide.SELL ? UniswapV2Functions.swap : UniswapV2Functions.buy, [src, srcAmount, destAmount, weth, pools], ); + + const hasRebaseTokenSrc = rebaseTokensSetsByChain[this.network]?.has( + src.toLowerCase(), + ); + const hasRebaseTokenDest = rebaseTokensSetsByChain[this.network]?.has( + dest.toLowerCase(), + ); + + const maybeSyncCall = + hasRebaseTokenSrc || hasRebaseTokenDest + ? { + callees: [ + hasRebaseTokenSrc + ? data.pools[0].address + : data.pools[data.pools.length - 1].address, + ], + calldata: [uniswapV2PoolIface.encodeFunctionData('sync')], + values: ['0'], + } + : undefined; + return this.buildSimpleParamWithoutWETHConversion( src, srcAmount, @@ -779,6 +815,9 @@ export class UniswapV2 destAmount, swapData, data.router, + data.router, + '0', + maybeSyncCall, ); } diff --git a/src/dex/uniswap-v3/config.ts b/src/dex/uniswap-v3/config.ts index c1c74165f..12920b5f6 100644 --- a/src/dex/uniswap-v3/config.ts +++ b/src/dex/uniswap-v3/config.ts @@ -2,6 +2,10 @@ 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'; +import { RamsesV2EventPool } from './forks/ramses-v2/ramses-v2-pool'; const SUPPORTED_FEES = [10000n, 3000n, 500n, 100n]; @@ -103,6 +107,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, + eventPoolImplementation: RamsesV2EventPool, + decodeStateMultiCallResultWithRelativeBitmaps, + initHash: + '0x1565b129f2d1790f12d45301b9b084335626f0c92410bc43130763b69971135d', + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/ramsesexchange/concentrated-liquidity-graph', + }, }, 'QuickSwapV3.1': { [Network.ZKEVM]: { @@ -119,6 +275,34 @@ export const UniswapV3Config: DexConfigMap = { 'https://api.studio.thegraph.com/query/44554/uniswap-v3/version/latest', }, }, + Retro: { + [Network.POLYGON]: { + factory: '0x91e1B99072f238352f59e58de875691e20Dc19c1', + quoter: '0xfe08be075758935cb6cb9318d1fbb60920416d4e', + router: '0x1891783cb3497Fdad1F25C933225243c2c7c4102', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x6Dc993Fe1e945A640576B4Dca81281d8e998DF71', + uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0x817e07951f93017a93327ac8cc31e946540203a19e1ecc37bc1761965c2d1090`, + subgraphURL: 'https://api.thegraph.com/subgraphs/name/ruvlol/univ3-test', + }, + }, + BaseswapV3: { + [Network.BASE]: { + factory: '0x38015D05f4fEC8AFe15D7cc0386a126574e8077B', + quoter: '0x4fDBD73aD4B1DDde594BF05497C15f76308eFfb9', + router: '0x1B8eea9315bE495187D873DA7773a874545D9D48', + supportedFees: [10000n, 2500n, 450n, 80n], + stateMulticall: '0x7160f736c52e1e78e92FD4eE4D73e21A7Cf4F950', + uniswapMulticall: '0x091e99cb1C49331a94dD62755D168E941AbD0693', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: 'https://api.thegraph.com/subgraphs/name/baseswapfi/v3-base', + }, + }, }; export const Adapters: Record = { @@ -154,4 +338,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/ramses-v2-pool.ts b/src/dex/uniswap-v3/forks/ramses-v2/ramses-v2-pool.ts new file mode 100644 index 000000000..5889cc06b --- /dev/null +++ b/src/dex/uniswap-v3/forks/ramses-v2/ramses-v2-pool.ts @@ -0,0 +1,156 @@ +import { UniswapV3EventPool } from '../../uniswap-v3-pool'; +import { + DecodedStateMultiCallResultWithRelativeBitmaps, + DecodeStateMultiCallFunc, + PoolState, +} from '../../types'; +import { assert } from 'ts-essentials'; +import { _reduceTickBitmap, _reduceTicks } from '../../contract-math/utils'; +import { bigIntify } from '../../../../utils'; +import { TickBitMap } from '../../contract-math/TickBitMap'; +import { uint24ToBigInt } from '../../../../lib/decoders'; +import { Interface } from 'ethers/lib/utils'; +import RamsesV2PoolABI from '../../../../abi/ramses-v2/RamsesV2Pool.abi.json'; +import { IDexHelper } from '../../../../dex-helper'; +import { Contract } from 'web3-eth-contract'; +import { Address, Logger } from '../../../../types'; + +export class RamsesV2EventPool extends UniswapV3EventPool { + public readonly poolIface = new Interface(RamsesV2PoolABI); + + constructor( + readonly dexHelper: IDexHelper, + parentName: string, + readonly stateMultiContract: Contract, + readonly decodeStateMultiCallResultWithRelativeBitmaps: + | DecodeStateMultiCallFunc + | undefined, + readonly erc20Interface: Interface, + protected readonly factoryAddress: Address, + public feeCode: bigint, + token0: Address, + token1: Address, + logger: Logger, + mapKey: string = '', + readonly poolInitCodeHash: string, + ) { + super( + dexHelper, + parentName, + stateMultiContract, + decodeStateMultiCallResultWithRelativeBitmaps, + erc20Interface, + factoryAddress, + feeCode, + token0, + token1, + logger, + mapKey, + poolInitCodeHash, + ); + + this.handlers['FeeAdjustment'] = this.handleFeeAdjustmentEvent.bind(this); + } + + handleFeeAdjustmentEvent(event: any, pool: PoolState): PoolState { + const newFee = bigIntify(event.args.newFee); + + pool.fee = newFee; + + return pool; + } + + async generateState(blockNumber: number): Promise> { + const callData = this._getStateRequestCallData(); + + const calldataWithFee = [ + ...callData, + { + target: this.poolAddress, + callData: this.poolIface.encodeFunctionData('currentFee'), + decodeFunction: uint24ToBigInt, + }, + ]; + + this._stateRequestCallData = calldataWithFee; + + const [resBalance0, resBalance1, resState, resCurrentFee] = + await this.dexHelper.multiWrapper.tryAggregate< + bigint | DecodedStateMultiCallResultWithRelativeBitmaps + >( + false, + calldataWithFee, + blockNumber, + this.dexHelper.multiWrapper.defaultBatchSize, + false, + ); + + assert(resState.success, 'Pool does not exist'); + + const [balance0, balance1, _state, fee] = [ + resBalance0.returnData, + resBalance1.returnData, + resState.returnData, + resCurrentFee.returnData, + ] as [ + bigint, + bigint, + DecodedStateMultiCallResultWithRelativeBitmaps, + bigint, + ]; + + const tickBitmap = {}; + const ticks = {}; + + _reduceTickBitmap(tickBitmap, _state.tickBitmap); + _reduceTicks(ticks, _state.ticks); + + const observations = { + [_state.slot0.observationIndex]: { + blockTimestamp: bigIntify(_state.observation.blockTimestamp), + tickCumulative: bigIntify(_state.observation.tickCumulative), + secondsPerLiquidityCumulativeX128: bigIntify( + _state.observation.secondsPerLiquidityCumulativeX128, + ), + initialized: _state.observation.initialized, + }, + }; + + const currentTick = bigIntify(_state.slot0.tick); + const tickSpacing = bigIntify(_state.tickSpacing); + + const startTickBitmap = TickBitMap.position(currentTick / tickSpacing)[0]; + const requestedRange = this.getBitmapRangeToRequest(); + + return { + pool: _state.pool, + blockTimestamp: bigIntify(_state.blockTimestamp), + slot0: { + sqrtPriceX96: bigIntify(_state.slot0.sqrtPriceX96), + tick: currentTick, + observationIndex: +_state.slot0.observationIndex, + observationCardinality: +_state.slot0.observationCardinality, + observationCardinalityNext: +_state.slot0.observationCardinalityNext, + feeProtocol: bigIntify(_state.slot0.feeProtocol), + }, + liquidity: bigIntify(_state.liquidity), + fee, + tickSpacing, + maxLiquidityPerTick: bigIntify(_state.maxLiquidityPerTick), + tickBitmap, + ticks, + observations, + isValid: true, + startTickBitmap, + lowestKnownTick: + (BigInt.asIntN(24, startTickBitmap - requestedRange) << 8n) * + tickSpacing, + highestKnownTick: + ((BigInt.asIntN(24, startTickBitmap + requestedRange) << 8n) + + BigInt.asIntN(24, 255n)) * + tickSpacing, + balance0, + balance1, + }; + } +} 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..967675843 100644 --- a/src/dex/uniswap-v3/types.ts +++ b/src/dex/uniswap-v3/types.ts @@ -1,6 +1,9 @@ -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'; +import { UniswapV3EventPool } from './uniswap-v3-pool'; export type OracleObservation = { blockTimestamp: bigint; @@ -51,15 +54,22 @@ export type PoolState = { balance1: bigint; }; +export type FactoryState = Record; + export type UniswapV3Data = { path: { tokenIn: Address; tokenOut: Address; fee: NumberAsString; + currentFee?: NumberAsString; }[]; isApproved?: boolean; }; +export type DecodeStateMultiCallFunc = ( + result: MultiResult | BytesLike, +) => DecodedStateMultiCallResultWithRelativeBitmaps; + export type DexParams = { router: Address; quoter: Address; @@ -72,6 +82,9 @@ export type DexParams = { deployer?: Address; subgraphURL: string; initHash: string; + stateMultiCallAbi?: AbiItem[]; + eventPoolImplementation?: typeof UniswapV3EventPool; + 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..1c30feb42 100644 --- a/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts +++ b/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts @@ -11,403 +11,1152 @@ 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, + ); + }); + }); + + describe('Retro', () => { + const dexKey = 'Retro'; + + describe('POLYGON', () => { + const network = Network.POLYGON; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '1000000000'; + const tokenBAmount: string = '100000000'; + const nativeTokenAmount = '1100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + }); + + describe('BaseswapV3 E2E', () => { + const dexKey = 'BaseswapV3'; + describe('Base', () => { + const network = Network.BASE; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDbC'; + + const tokenAAmount: string = '1000000'; + const tokenBAmount: string = '1000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); }); diff --git a/src/dex/uniswap-v3/uniswap-v3-events.test.ts b/src/dex/uniswap-v3/uniswap-v3-events.test.ts index 1b769ecd2..306ebee60 100644 --- a/src/dex/uniswap-v3/uniswap-v3-events.test.ts +++ b/src/dex/uniswap-v3/uniswap-v3-events.test.ts @@ -13,6 +13,7 @@ import { Interface } from '@ethersproject/abi'; import ERC20ABI from '../../abi/erc20.json'; import StateMulticallABI from '../../abi/uniswap-v3/UniswapV3StateMulticall.abi.json'; import { AbiItem } from 'web3-utils'; +import { decodeStateMultiCallResultWithRelativeBitmaps } from './utils'; jest.setTimeout(300 * 1000); const dexKey = 'UniswapV3'; @@ -100,6 +101,7 @@ describe('UniswapV3 Event', function () { StateMulticallABI as AbiItem[], config.stateMulticall, ), + decodeStateMultiCallResultWithRelativeBitmaps, new Interface(ERC20ABI), config.factory, poolFeeCode, @@ -155,6 +157,7 @@ describe('UniswapV3 Event', function () { StateMulticallABI as AbiItem[], config.stateMulticall, ), + decodeStateMultiCallResultWithRelativeBitmaps, new Interface(ERC20ABI), _config.factory, _feeCode, diff --git a/src/dex/uniswap-v3/uniswap-v3-factory.ts b/src/dex/uniswap-v3/uniswap-v3-factory.ts new file mode 100644 index 000000000..372020de4 --- /dev/null +++ b/src/dex/uniswap-v3/uniswap-v3-factory.ts @@ -0,0 +1,73 @@ +import { Interface } from '@ethersproject/abi'; +import { DeepReadonly } from 'ts-essentials'; +import FactoryABI from '../../abi/uniswap-v3/UniswapV3Factory.abi.json'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { Address, Log, Logger } from '../../types'; +import { LogDescription } from 'ethers/lib/utils'; +import { FactoryState } from './types'; + +export type OnPoolCreatedCallback = ({ + token0, + token1, + fee, +}: { + token0: string; + token1: string; + fee: bigint; +}) => Promise; + +/* + * "Stateless" event subscriber in order to capture "PoolCreated" event on new pools created. + * State is present, but it's a placeholder to actually make the events reach handlers (if there's no previous state - `processBlockLogs` is not called) + */ +export class UniswapV3Factory extends StatefulEventSubscriber { + handlers: { + [event: string]: (event: any) => Promise; + } = {}; + + logDecoder: (log: Log) => any; + + public readonly factoryIface = new Interface(FactoryABI); + + constructor( + readonly dexHelper: IDexHelper, + parentName: string, + protected readonly factoryAddress: Address, + logger: Logger, + protected readonly onPoolCreated: OnPoolCreatedCallback, + mapKey: string = '', + ) { + super(parentName, `${parentName} Factory`, dexHelper, logger, true, mapKey); + + this.addressesSubscribed = [factoryAddress]; + + this.logDecoder = (log: Log) => this.factoryIface.parseLog(log); + + this.handlers['PoolCreated'] = this.handleNewPool.bind(this); + } + + generateState(): FactoryState { + return {}; + } + + protected async processLog( + _: DeepReadonly, + log: Readonly, + ): Promise { + const event = this.logDecoder(log); + if (event.name in this.handlers) { + await this.handlers[event.name](event); + } + + return {}; + } + + async handleNewPool(event: LogDescription) { + const token0 = event.args.token0.toLowerCase(); + const token1 = event.args.token1.toLowerCase(); + const fee = event.args.fee; + + await this.onPoolCreated({ token0, token1, fee }); + } +} diff --git a/src/dex/uniswap-v3/uniswap-v3-integration.test.ts b/src/dex/uniswap-v3/uniswap-v3-integration.test.ts index 35dee3c93..d197b4cf9 100644 --- a/src/dex/uniswap-v3/uniswap-v3-integration.test.ts +++ b/src/dex/uniswap-v3/uniswap-v3-integration.test.ts @@ -3,14 +3,15 @@ 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'; +import { UniswapV3Config } from './config'; const network = Network.POLYGON; const TokenASymbol = 'USDC'; @@ -28,10 +29,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 +43,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 +60,11 @@ function decodeReaderResult( } async function checkOnChainPricing( + dexHelper: IDexHelper, uniswapV3: UniswapV3, funcName: string, blockNumber: number, + exchangeAddress: string, prices: bigint[], tokenIn: Address, tokenOut: Address, @@ -76,7 +72,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 +113,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 +128,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 +139,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 +174,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 +222,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 +334,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 +446,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 +475,1098 @@ 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); + }); + }); +}); + +describe('Retro', () => { + const dexKey = 'Retro'; + + describe('Polygon', () => { + let blockNumber: number; + let retro: UniswapV3; + + const network = Network.POLYGON; + 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(); + retro = new UniswapV3(network, dexKey, dexHelper); + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await retro.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await retro.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 = retro.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + retro, + 'quoteExactInputSingle', + blockNumber, + '0xfe08be075758935cb6cb9318d1fbb60920416d4e', + 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 retro.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await retro.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 = retro.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + retro, + 'quoteExactOutputSingle', + blockNumber, + '0xfe08be075758935cb6cb9318d1fbb60920416d4e', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await retro.getTopPoolsForToken(TokenB.address, 10); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, TokenB.address, dexKey); + }); + }); +}); + +describe('BaseswapV3', function () { + const dexKey = 'BaseswapV3'; + + describe('Base', function () { + const network = Network.BASE; + const dexHelper = new DummyDexHelper(network); + + let blockNumber: number; + let baseswapV3: UniswapV3; + + const TokenASymbol = 'USDC'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'WETH'; + const TokenB = Tokens[network][TokenBSymbol]; + + const QuoterV2 = UniswapV3Config[dexKey][network].quoter; + + const amountsBuy = [ + 0n, + 6000000n, + 12000000n, + 18000000n, + 24000000n, + 30000000n, + 36000000n, + 42000000n, + 48000000n, + 54000000n, + 60000000n, + 66000000n, + 72000000n, + 78000000n, + 84000000n, + 90000000n, + 96000000n, + 102000000n, + 108000000n, + 114000000n, + 120000000n, + 126000000n, + 132000000n, + 138000000n, + 144000000n, + 150000000n, + 156000000n, + 162000000n, + 168000000n, + 174000000n, + 180000000n, + 186000000n, + 192000000n, + 198000000n, + 204000000n, + 210000000n, + 216000000n, + 222000000n, + 228000000n, + 234000000n, + 240000000n, + 246000000n, + 252000000n, + 258000000n, + 264000000n, + 270000000n, + 276000000n, + 282000000n, + 288000000n, + 294000000n, + 300000000n, + ]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + baseswapV3 = new UniswapV3(network, dexKey, dexHelper); + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const pools = await baseswapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await baseswapV3.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 = baseswapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + baseswapV3, + 'quoteExactInputSingle', + blockNumber, + QuoterV2, + 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 amountsBuy = [ + 0n, + 1n * BI_POWS[18], + 2n * BI_POWS[18], + 3n * BI_POWS[18], + ]; + + const pools = await baseswapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await baseswapV3.getPricesVolume( + TokenA, + TokenB, + amountsBuy, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amountsBuy, SwapSide.BUY, dexKey); + + // Check if onchain pricing equals to calculated ones + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = baseswapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + baseswapV3, + 'quoteExactOutputSingle', + blockNumber, + QuoterV2, + price.prices, + TokenA.address, + TokenB.address, + fee, + amountsBuy, + ); + if (res === false) falseChecksCounter++; + }), + ); + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getPoolIdentifiers and getPricesVolume SELL stable pairs', async function () { + const TokenASymbol = 'USDbC'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'USDC'; + const TokenB = Tokens[network][TokenBSymbol]; + + const pools = await baseswapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await baseswapV3.getPricesVolume( + TokenA, + TokenB, + amountsBuy, + SwapSide.SELL, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices( + poolPrices!.filter(pp => pp.unit !== 0n), + amountsBuy, + SwapSide.SELL, + dexKey, + ); + + // Check if onchain pricing equals to calculated ones + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = baseswapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + baseswapV3, + 'quoteExactInputSingle', + blockNumber, + QuoterV2, + price.prices, + TokenA.address, + TokenB.address, + fee, + amountsBuy, + ); + if (res === false) falseChecksCounter++; + }), + ); + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getPoolIdentifiers and getPricesVolume BUY stable pairs', async function () { + const TokenASymbol = 'DAI'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'USDC'; + const TokenB = Tokens[network][TokenBSymbol]; + + const pools = await baseswapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await baseswapV3.getPricesVolume( + TokenA, + TokenB, + amountsBuy, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices( + poolPrices!.filter(pp => pp.unit !== 0n), + amountsBuy, + SwapSide.BUY, + dexKey, + ); + + // Check if onchain pricing equals to calculated ones + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = baseswapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + baseswapV3, + 'quoteExactOutputSingle', + blockNumber, + QuoterV2, + price.prices, + TokenA.address, + TokenB.address, + fee, + amountsBuy, + ); + if (res === false) falseChecksCounter++; + }), + ); + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await baseswapV3.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..9f3401710 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'; @@ -45,7 +46,7 @@ export class UniswapV3EventPool extends StatefulEventSubscriber { private _poolAddress?: Address; - private _stateRequestCallData?: MultiCallParams< + protected _stateRequestCallData?: MultiCallParams< bigint | DecodedStateMultiCallResultWithRelativeBitmaps >[]; @@ -54,15 +55,18 @@ export class UniswapV3EventPool extends StatefulEventSubscriber { public initFailed = false; public initRetryAttemptCount = 0; - public readonly feeCodeAsString; + public feeCodeAsString; constructor( readonly dexHelper: IDexHelper, parentName: string, readonly stateMultiContract: Contract, + readonly decodeStateMultiCallResultWithRelativeBitmaps: + | DecodeStateMultiCallFunc + | undefined, readonly erc20Interface: Interface, protected readonly factoryAddress: Address, - public readonly feeCode: bigint, + public feeCode: bigint, token0: Address, token1: Address, logger: Logger, @@ -195,7 +199,7 @@ export class UniswapV3EventPool extends StatefulEventSubscriber { return null; // ignore unrecognized event } - private _getStateRequestCallData() { + protected _getStateRequestCallData() { if (!this._stateRequestCallData) { const callData: MultiCallParams< bigint | DecodedStateMultiCallResultWithRelativeBitmaps @@ -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 486f29cae..df81fa69c 100644 --- a/src/dex/uniswap-v3/uniswap-v3.ts +++ b/src/dex/uniswap-v3/uniswap-v3.ts @@ -2,20 +2,20 @@ import { defaultAbiCoder, Interface } from '@ethersproject/abi'; import _ from 'lodash'; import { pack } from '@ethersproject/solidity'; import { - Token, + AdapterExchangeParam, Address, ExchangePrices, - AdapterExchangeParam, - SimpleExchangeParam, - PoolLiquidity, + ExchangeTxInfo, Logger, NumberAsString, + PoolLiquidity, PoolPrices, - TxInfo, PreprocessTransactionOptions, - ExchangeTxInfo, + SimpleExchangeParam, + Token, + TxInfo, } from '../../types'; -import { SwapSide, Network, CACHE_PREFIX } from '../../constants'; +import { CACHE_PREFIX, Network, SwapSide } from '../../constants'; import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; import { getBigIntPow, @@ -39,10 +39,10 @@ import { getLocalDeadlineAsFriendlyPlaceholder, SimpleExchange, } from '../simple-exchange'; -import { UniswapV3Config, Adapters, PoolsToPreload } from './config'; +import { Adapters, PoolsToPreload, UniswapV3Config } from './config'; import { UniswapV3EventPool } from './uniswap-v3-pool'; import UniswapV3RouterABI from '../../abi/uniswap-v3/UniswapV3Router.abi.json'; -import UniswapV3QuoterABI from '../../abi/uniswap-v3/UniswapV3Quoter.abi.json'; +import UniswapV3QuoterV2ABI from '../../abi/uniswap-v3/UniswapV3QuoterV2.abi.json'; import UniswapV3MultiABI from '../../abi/uniswap-v3/UniswapMulti.abi.json'; import DirectSwapABI from '../../abi/DirectSwap.json'; import UniswapV3StateMulticallABI from '../../abi/uniswap-v3/UniswapV3StateMulticall.abi.json'; @@ -64,6 +64,7 @@ import { DEFAULT_ID_ERC20_AS_STRING, } from '../../lib/tokens/types'; import { OptimalSwapExchange } from '@paraswap/core'; +import { OnPoolCreatedCallback, UniswapV3Factory } from './uniswap-v3-factory'; type PoolPairsInfo = { token0: Address; @@ -71,14 +72,15 @@ type PoolPairsInfo = { fee: string; }; -const UNISWAPV3_CLEAN_NOT_EXISTING_POOL_TTL_MS = 60 * 60 * 24 * 1000; // 24 hours -const UNISWAPV3_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS = 30 * 60 * 1000; // Once in 30 minutes +const UNISWAPV3_CLEAN_NOT_EXISTING_POOL_TTL_MS = 3 * 24 * 60 * 60 * 1000; // 3 days +const UNISWAPV3_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS = 24 * 60 * 60 * 1000; // Once in a day const UNISWAPV3_QUOTE_GASLIMIT = 200_000; export class UniswapV3 extends SimpleExchange implements IDex { + private readonly factory: UniswapV3Factory; readonly isFeeOnTransferSupported: boolean = false; readonly eventPools: Record = {}; @@ -91,7 +93,15 @@ export class UniswapV3 public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = getDexKeysWithNetwork( - _.pick(UniswapV3Config, ['UniswapV3', 'QuickSwapV3.1']), + _.pick(UniswapV3Config, [ + 'UniswapV3', + 'SushiSwapV3', + 'QuickSwapV3.1', + 'RamsesV2', + 'ChronosV3', + 'Retro', + 'BaseswapV3', + ]), ); logger: Logger; @@ -107,7 +117,7 @@ export class UniswapV3 protected dexHelper: IDexHelper, protected adapters = Adapters[network] || {}, readonly routerIface = new Interface(UniswapV3RouterABI), - readonly quoterIface = new Interface(UniswapV3QuoterABI), + readonly quoterIface = new Interface(UniswapV3QuoterV2ABI), protected config = UniswapV3Config[dexKey][network], protected poolsToPreload = PoolsToPreload[dexKey]?.[network] || [], ) { @@ -118,7 +128,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, ); @@ -130,6 +142,14 @@ export class UniswapV3 this.notExistingPoolSetKey = `${CACHE_PREFIX}_${network}_${dexKey}_not_existings_pool_set`.toLowerCase(); + + this.factory = new UniswapV3Factory( + dexHelper, + dexKey, + this.config.factory, + this.logger, + this.onPoolCreatedDeleteFromNonExistingSet, + ); } get supportedFees() { @@ -146,6 +166,9 @@ export class UniswapV3 } async initializePricing(blockNumber: number) { + // Init listening to new pools creation + await this.factory.initialize(blockNumber); + // This is only for testing, because cold pool fetching is goes out of // FETCH_POOL_INDENTIFIER_TIMEOUT range await Promise.all( @@ -176,6 +199,44 @@ export class UniswapV3 } } + /* + * When a non existing pool is queried, it's blacklisted for an arbitrary long period in order to prevent issuing too many rpc calls + * Once the pool is created, it gets immediately flagged + */ + onPoolCreatedDeleteFromNonExistingSet: OnPoolCreatedCallback = async ({ + token0, + token1, + fee, + }) => { + const logPrefix = '[onPoolCreatedDeleteFromNonExistingSet]'; + const [_token0, _token1] = this._sortTokens(token0, token1); + const poolKey = `${_token0}_${_token1}_${fee}`; + + // consider doing it only from master pool for less calls to distant cache + + // delete entry locally to let local instance discover the pool + delete this.eventPools[this.getPoolIdentifier(_token0, _token1, fee)]; + + try { + this.logger.info( + `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}`, + ); + // delete pool record from set + const result = await this.dexHelper.cache.zrem( + this.notExistingPoolSetKey, + [poolKey], + ); + this.logger.info( + `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}; result: ${result}`, + ); + } catch (error) { + this.logger.error( + `${logPrefix} ERROR: failed to delete pool from set: set=${this.notExistingPoolSetKey}; key=${poolKey}`, + error, + ); + } + }; + async getPool( srcAddress: Address, destAddress: Address, @@ -233,12 +294,17 @@ export class UniswapV3 } this.logger.trace(`starting to listen to new pool: ${key}`); + const poolImplementation = + this.config.eventPoolImplementation !== undefined + ? this.config.eventPoolImplementation + : UniswapV3EventPool; pool = pool || - new UniswapV3EventPool( + new poolImplementation( this.dexHelper, this.dexKey, this.stateMultiContract, + this.config.decodeStateMultiCallResultWithRelativeBitmaps, this.erc20Interface, this.config.factory, fee, @@ -288,6 +354,10 @@ export class UniswapV3 if (pool !== null) { const allEventPools = Object.values(this.eventPools); + // if pool was created, delete pool record from non existing set + this.dexHelper.cache + .zrem(this.notExistingPoolSetKey, [key]) + .catch(() => {}); this.logger.info( `starting to listen to new non-null pool: ${key}. Already following ${allEventPools // Not that I like this reduce, but since it is done only on initialization, expect this to be ok @@ -365,6 +435,7 @@ export class UniswapV3 amounts: bigint[], side: SwapSide, pools: UniswapV3EventPool[], + states: PoolState[], ): Promise | null> { if (pools.length === 0) { return null; @@ -462,7 +533,7 @@ export class UniswapV3 }; let i = 0; - const result = pools.map(pool => { + const result = pools.map((pool, index) => { const _rates = _amounts.map(() => decode(i++)); const unit: bigint = _rates[0]; @@ -482,6 +553,7 @@ export class UniswapV3 tokenIn: from.address, tokenOut: to.address, fee: pool.feeCodeAsString, + currentFee: states[index]?.fee.toString(), }, ], exchange: pool.poolAddress, @@ -593,16 +665,17 @@ export class UniswapV3 }, ); + const states = poolsToUse.poolWithState.map( + p => p.getState(blockNumber)!, + ); + const rpcResultsPromise = this.getPricingFromRpc( _srcToken, _destToken, amounts, side, this.network === Network.ZKEVM ? [] : poolsToUse.poolWithoutState, - ); - - const states = poolsToUse.poolWithState.map( - p => p.getState(blockNumber)!, + this.network === Network.ZKEVM ? [] : states, ); const unitAmount = getBigIntPow( @@ -676,6 +749,7 @@ export class UniswapV3 tokenIn: _srcAddress, tokenOut: _destAddress, fee: pool.feeCode.toString(), + currentFee: state.fee.toString(), }, ], }, @@ -1055,6 +1129,10 @@ export class UniswapV3 deployer: this.config.deployer?.toLowerCase(), initHash: this.config.initHash, subgraphURL: this.config.subgraphURL, + stateMultiCallAbi: this.config.stateMultiCallAbi, + eventPoolImplementation: this.config.eventPoolImplementation, + 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/wombat/config.ts b/src/dex/wombat/config.ts new file mode 100644 index 000000000..986d36fbe --- /dev/null +++ b/src/dex/wombat/config.ts @@ -0,0 +1,77 @@ +import { DexParams } from './types'; +import { DexConfigMap, AdapterMappings } from '../../types'; +import { Network, SwapSide } from '../../constants'; + +export const WombatConfig: DexConfigMap = { + Wombat: { + [Network.BSC]: { + bmwAddress: '0x489833311676B566f888119c29bd997Dc6C95830', + }, + [Network.ARBITRUM]: { + bmwAddress: '0x62A83C6791A3d7950D823BB71a38e47252b6b6F4', + }, + [Network.MAINNET]: { + bmwAddress: '0xC9bFC3eFeFe4CF96877009F75a61F5c1937e5d1a', + }, + [Network.AVALANCHE]: { + bmwAddress: '0x6521a549834F5E6d253CD2e5F4fbe4048f86cd7b', + }, + [Network.BASE]: { + bmwAddress: '0x6521a549834F5E6d253CD2e5F4fbe4048f86cd7b', + }, + [Network.OPTIMISM]: { + bmwAddress: '0x25C9dd8a3774EF7C918cd28ff59cF9e29504C914', + }, + }, +}; + +export const Adapters: Record = { + [Network.BSC]: { + [SwapSide.SELL]: [ + { + name: 'BscAdapter02', + index: 7, + }, + ], + }, + [Network.ARBITRUM]: { + [SwapSide.SELL]: [ + { + name: 'ArbitrumAdapter02', + index: 8, + }, + ], + }, + [Network.MAINNET]: { + [SwapSide.SELL]: [ + { + name: 'Adapter03', + index: 15, + }, + ], + }, + [Network.AVALANCHE]: { + [SwapSide.SELL]: [ + { + name: 'AvalancheAdapter02', + index: 7, + }, + ], + }, + [Network.BASE]: { + [SwapSide.SELL]: [ + { + name: 'BaseAdapter01', + index: 7, + }, + ], + }, + [Network.OPTIMISM]: { + [SwapSide.SELL]: [ + { + name: 'OptimismAdapter01', + index: 12, + }, + ], + }, +}; diff --git a/src/dex/wombat/types.ts b/src/dex/wombat/types.ts new file mode 100644 index 000000000..3bac57e66 --- /dev/null +++ b/src/dex/wombat/types.ts @@ -0,0 +1,55 @@ +import { Address } from '../../types'; + +// State-related types + +export type BmwState = { + pools: Address[]; +}; + +export type PoolState = { + // poolState is the state of event + // subscriber. This should be the minimum + // set of parameters required to compute + // pool prices. + params: PoolParams; + underlyingAddresses: Address[]; + asset: { [underlyingAddress: string]: AssetState }; +}; + +export type AssetState = { + address: Address; + paused: boolean; + cash: bigint; + liability: bigint; + underlyingTokenDecimals: number; + relativePrice?: bigint; +}; + +export type PoolParams = { + paused: boolean; + ampFactor: bigint; + haircutRate: bigint; + startCovRatio: bigint; + endCovRatio: bigint; +}; + +export type WombatData = { + // TODO: WombatData 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! + exchange: Address; +}; + +export type DexParams = { + // DexParams is set of parameters that can be used to initiate a DEX fork. + bmwAddress: Address; + // routerAddress?: Address; +}; + +export type MulticallResultOutputs = + | boolean + | bigint + | Address + | number + | undefined; diff --git a/src/dex/wombat/utils.ts b/src/dex/wombat/utils.ts new file mode 100644 index 000000000..ef67d5de6 --- /dev/null +++ b/src/dex/wombat/utils.ts @@ -0,0 +1,82 @@ +import { BI_POWS } from '../../bigint-constants'; +import { MultiResult } from '../../lib/multi-wrapper'; +import { BytesLike } from 'ethers/lib/utils'; +import { generalDecoder } from '../../lib/decoders'; + +export const WAD = BI_POWS[18]; + +export function wmul(x: bigint, y: bigint): bigint { + return (x * y + WAD / 2n) / WAD; +} + +export function wdiv(x: bigint, y: bigint): bigint { + return (x * WAD + y / 2n) / y; +} + +// Convert x to WAD (18 decimals) from d decimals. +export function toWad(x: bigint, d: bigint): bigint { + if (d < 18n) { + return x * 10n ** (18n - d); + } else if (d > 18n) { + return x / 10n ** (d - 18n); + } + return x; +} +// Convert x from WAD (18 decimals) to d decimals. +export function fromWad(x: bigint, d: bigint): bigint { + if (d < 18n) { + return x / 10n ** (18n - d); + } else if (d > 18n) { + return x * 10n ** (d - 18n); + } + return x; +} + +// Babylonian Method with initial guess (typecast as int) +export function sqrt(y: bigint, guess: bigint): bigint { + let z = 0n; + if (y > 3) { + if (guess > 0 && guess <= y) { + z = guess; + } else if (guess < 0 && -guess <= y) { + z = -guess; + } else { + z = y; + } + let x = (y / z + z) / 2n; + while (x != z) { + z = x; + x = (y / x + x) / 2n; + } + } else if (y != 0n) { + z = 1n; + } + + return z; +} + +export const uint120ToBigInt = ( + result: MultiResult | BytesLike, +): bigint => { + return generalDecoder(result, ['uint120'], 0n, value => value[0].toBigInt()); +}; + +export function convertUint256ToInt256(uint256Value: bigint): bigint { + const isNegative = + (uint256Value & + BigInt( + '0x8000000000000000000000000000000000000000000000000000000000000000', + )) !== + BigInt(0); + + if (isNegative) { + return ( + uint256Value - + BigInt( + '0x10000000000000000000000000000000000000000000000000000000000000000', + ) + ); + } else { + return uint256Value; + } +} diff --git a/src/dex/wombat/wombat-bmw.ts b/src/dex/wombat/wombat-bmw.ts new file mode 100644 index 000000000..cd63b212d --- /dev/null +++ b/src/dex/wombat/wombat-bmw.ts @@ -0,0 +1,186 @@ +import { Interface } from '@ethersproject/abi'; +import { BytesLike } from 'ethers'; +import { LogDescription } from 'ethers/lib/utils'; +import { Contract } from 'web3-eth-contract'; +import { DeepReadonly } from 'ts-essentials'; +import { Address, Log, Logger, MultiCallInput } from '../../types'; +import { catchParseLogError } from '../../utils'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { IDexHelper } from '../../dex-helper'; +import { BmwState } from './types'; +import BmwABI from '../../abi/wombat/bmw.json'; +import AssetABI from '../../abi/wombat/asset.json'; +import PoolABI from '../../abi/wombat/pool-v2.json'; + +export class WombatBmw extends StatefulEventSubscriber { + static readonly bmwInterface = new Interface(BmwABI); + static readonly assetInterface = new Interface(AssetABI); + static readonly poolInterface = new Interface(PoolABI); + + private readonly logDecoder: (log: Log) => any; + private bmwContract: Contract; + private handlers: { + [event: string]: ( + event: any, + state: DeepReadonly, + log: Readonly, + ) => Promise | null>; + } = {}; + + constructor( + dexKey: string, + name: string, + protected network: number, + protected dexHelper: IDexHelper, + logger: Logger, + protected bmwAddress: Address, + protected onAssetAdded: ( + pool: Address, + blockNumber: number, + ) => Promise, + ) { + super( + `${dexKey}-${name}`, + `${dexKey}-${network}-${name}`, + dexHelper, + logger, + ); + + this.logDecoder = (log: Log) => WombatBmw.bmwInterface.parseLog(log); + this.addressesSubscribed = [this.bmwAddress]; + this.bmwContract = new this.dexHelper.web3Provider.eth.Contract( + BmwABI as any, + this.bmwAddress, + ); + + // users-actions handlers + this.handlers['Add'] = this.handleAdd.bind(this); + } + + /** + * The function is called every time any of the subscribed + * addresses release log. The function accepts the current + * state, updates the state according to the log, and returns + * the updated state. + * @param state - Current state of event subscriber + * @param log - Log released by one of the subscribed addresses + * @returns Updates state of the event subscriber after the log + */ + protected async processLog( + state: DeepReadonly, + log: Readonly, + ): Promise | null> { + try { + const event = this.logDecoder(log); + if (event.name in this.handlers) { + return await this.handlers[event.name](event, state, log); + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + return null; + } + + /** + * The function generates state using on-chain calls. This + * function is called to regenerate state if the event based + * system fails to fetch events and the local state is no + * more correct. + * should be generated + * @returns state of the event subscriber at blocknumber + */ + async generateState(blockNumber: number): Promise> { + const bmwState: BmwState = { pools: [] }; + let inputs: MultiCallInput[] = []; + + const poolLength = await this.bmwContract.methods + .poolLength() + .call({}, blockNumber); + for (let i = 0; i < poolLength; i++) { + inputs.push({ + target: this.bmwAddress, + callData: WombatBmw.bmwInterface.encodeFunctionData('poolInfo', [i]), + }); + } + let returnData = ( + await this.dexHelper.multiContract.methods + .aggregate(inputs) + .call({}, blockNumber) + ).returnData; + + const lpTokens = returnData.map((data: BytesLike) => + WombatBmw.bmwInterface + .decodeFunctionResult('poolInfo', data)[0] + .toLowerCase(), + ); + + inputs = []; + for (const lpToken of lpTokens) { + inputs.push({ + target: lpToken, + callData: WombatBmw.assetInterface.encodeFunctionData('pool'), + }); + } + + returnData = ( + await this.dexHelper.multiContract.methods + .aggregate(inputs) + .call({}, blockNumber) + ).returnData; + + const poolAddressSet = new Set
(); + let i = 0; + for (const lpToken of lpTokens) { + const pool = WombatBmw.assetInterface + .decodeFunctionResult('pool', returnData[i++])[0] + .toLowerCase(); + + poolAddressSet.add(pool); + } + + const promises: Promise[] = []; + poolAddressSet.forEach(pool => { + bmwState.pools.push(pool); + promises.push(this.onAssetAdded(pool, blockNumber)); + }); + await Promise.all(promises); + + return bmwState; + } + + async handleAdd( + event: LogDescription, + state: DeepReadonly, + log: Readonly, + ): Promise | null> { + const lpToken = event.args.lpToken.toString().toLowerCase(); + let multiCallInputs: MultiCallInput[] = []; + + multiCallInputs.push({ + target: lpToken, + callData: WombatBmw.assetInterface.encodeFunctionData('pool'), + }); + + const returnData = ( + await this.dexHelper.multiContract.methods + .aggregate(multiCallInputs) + .call({}, log.blockNumber) + ).returnData; + + const pool = WombatBmw.assetInterface + .decodeFunctionResult('pool', returnData[0])[0] + .toLowerCase(); + + await this.onAssetAdded(pool, log.blockNumber); + if (!state.pools.includes(pool)) { + return { + pools: [...state.pools, pool], + }; + } else { + return { + pools: state.pools, + }; + } + } +} diff --git a/src/dex/wombat/wombat-e2e.test.ts b/src/dex/wombat/wombat-e2e.test.ts new file mode 100644 index 000000000..91a447027 --- /dev/null +++ b/src/dex/wombat/wombat-e2e.test.ts @@ -0,0 +1,271 @@ +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'; + +type Pairs = { name: string; sellAmount: string; buyAmount: string }[][]; + +function testForNetwork( + network: Network, + dexKey: string, + pairs: Pairs, + slippage?: number | undefined, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + 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, + undefined, + undefined, + undefined, + slippage, + ); + }); + + 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, + undefined, + undefined, + undefined, + slippage, + ); + }); + }); + }); + }); + }), + ); +} + +describe('Wombat E2E', () => { + const dexKey = 'Wombat'; + + describe('BSC', () => { + const network = Network.BSC; + + const pairs: Pairs = [ + [ + { + name: 'USDC', + sellAmount: '1000000000', + buyAmount: '1000000000', + }, + { + name: 'USDT', + sellAmount: '1000000000', + buyAmount: '1000000000', + }, + ], + [ + { + name: 'BNB', + sellAmount: '1000000000000000000', + buyAmount: '1000000000000000000', + }, + { + name: 'BNBx', + sellAmount: '1000000000000000000', + buyAmount: '1000000000000000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); + + describe('Arbitrum', () => { + const network = Network.ARBITRUM; + + const pairs: Pairs = [ + [ + { + name: 'USDC', + sellAmount: '100000000', + buyAmount: '100000000', + }, + { + name: 'USDT', + sellAmount: '100000000', + buyAmount: '100000000', + }, + ], + [ + { + name: 'ETH', + sellAmount: '10000000000000000', + buyAmount: '10000000000000000', + }, + { + name: 'wstETH', + sellAmount: '10000000000000000', + buyAmount: '10000000000000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); + + describe('Ethereum', () => { + const network = Network.MAINNET; + + const pairs: Pairs = [ + [ + { + name: 'USDC', + sellAmount: '100000000', + buyAmount: '100000000', + }, + { + name: 'USDT', + sellAmount: '100000000', + buyAmount: '100000000', + }, + ], + [ + { + name: 'ETH', + sellAmount: '1000000000000000000', + buyAmount: '1000000000000000000', + }, + { + name: 'frxETH', + sellAmount: '1000000000000000000', + buyAmount: '1000000000000000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); + + describe('Avalanche', () => { + const network = Network.AVALANCHE; + + const pairs: Pairs = [ + [ + { + name: 'USDC', + sellAmount: '10000000', + buyAmount: '10000000', + }, + { + name: 'USDT', + sellAmount: '10000000', + buyAmount: '10000000', + }, + ], + [ + { + name: 'AVAX', + sellAmount: '100000000000000000000', + buyAmount: '100000000000000000000', + }, + { + name: 'sAVAX', + sellAmount: '100000000000000000000', + buyAmount: '100000000000000000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); + + describe('Base', () => { + const network = Network.BASE; + + const pairs: Pairs = [ + [ + { + name: 'USDC', + sellAmount: '100000000', + buyAmount: '100000000', + }, + { + name: 'USDbC', + sellAmount: '100000000', + buyAmount: '100000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); + + describe('Optimism', () => { + const network = Network.OPTIMISM; + + const pairs: Pairs = [ + [ + { + name: 'USDC', + sellAmount: '100000000', + buyAmount: '100000000', + }, + { + name: 'USDT', + sellAmount: '100000000', + buyAmount: '100000000', + }, + ], + [ + { + name: 'ETH', + sellAmount: '100000000000000000', + buyAmount: '100000000000000000', + }, + { + name: 'frxETH', + sellAmount: '100000000000000000', + buyAmount: '100000000000000000', + }, + ], + ]; + + testForNetwork(network, dexKey, pairs); + }); +}); diff --git a/src/dex/wombat/wombat-events.test.ts b/src/dex/wombat/wombat-events.test.ts new file mode 100644 index 000000000..720f0cd0a --- /dev/null +++ b/src/dex/wombat/wombat-events.test.ts @@ -0,0 +1,147 @@ +import dotenv from 'dotenv'; + +dotenv.config(); + +import { DeepReadonly } from 'ts-essentials'; +import { WombatPool } from './wombat-pool'; +import { Network } from '../../constants'; +import { Address } from '../../types'; +import { DummyDexHelper } from '../../dex-helper'; +import { BmwState, PoolState } from './types'; +import { WombatConfig } from './config'; +import { Wombat } from './wombat'; +import { WombatBmw } from './wombat-bmw'; +import { testEventSubscriber } from '../../../tests/utils-events'; + +jest.setTimeout(100 * 1000); + +async function fetchPoolState( + wombatPool: WombatPool, + blockNumber: number, +): Promise> { + return await wombatPool.generateState(blockNumber); +} + +async function fetchBmwState( + wombatBmw: WombatBmw, + blockNumber: number, +): Promise> { + return await wombatBmw.generateState(blockNumber); +} + +// eventName -> blockNumbers +type EventMappings = Record; + +describe('Wombat EventPool BSC', function () { + const dexKey = 'Wombat'; + const network = Network.BSC; + const dexHelper = new DummyDexHelper(network); + const logger = dexHelper.getLogger(dexKey); + const v2Pool = '0x9498563e47D7CFdFa22B818bb8112781036c201C'; // stable guild pool + const v3Pool = '0x1ee15673e07105Bcf360139fa8CafeBDd7754BeF'; // cross chain pool + const eventsToTest: Record = { + [v2Pool]: { + // topic0: 0xf5dd9317b9e63ac316ce44acc85f670b54b339cfa3e9076e1dd55065b922314b + ['Deposit']: [34556359, 34556365, 34558365], + // topic0: 0xfb80d861da582b723be2d19507ce3e03851820c464abea89156ec77e089b1ad9 + ['Withdraw']: [34208250, 34262795, 34549292], + // topic0: 0x54787c404bb33c88e86f4baf88183a3b0141d0a848e6a9f7a13b66ae3a9b73d1 + ['Swap']: [34588909, 34590323, 34593343, 34600568, 34602207], + ['SetAmpFactor']: [], + ['SetHaircutRate']: [], + // topic0: 0x0bb5715f0f217c2fe9a0c877ea87d474380c641102f3440ee2a4c8b9d9790918 + ['AssetAdded']: [28050075], + // topic0: 0x0fa1e4606af435f32f05b3804033d2933e691fab32ee74d2db6fa82d2741f1ea + ['AssetRemoved']: [34463362], + // topic0: 0x4941e18a2bcbb0f9fa0081238f26793a8ad8c202b913ae8bf5f7e523f68ff137 + ['FillPool']: [], + ['Paused']: [34342956], + // topic0: 0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa + ['Unpaused']: [34463362], + // topic0: 0xdcb65c0553aaa433aadd180404ff195259c48f78aa50f877ebcb4bb215129a4e + ['PausedAsset']: [34463362], + ['UnpausedAsset']: [], + }, + [v3Pool]: { + // topic0: 0xf5dd9317b9e63ac316ce44acc85f670b54b339cfa3e9076e1dd55065b922314b + ['Deposit']: [34558302, 34564573, 34597601], + // // topic0: 0xfb80d861da582b723be2d19507ce3e03851820c464abea89156ec77e089b1ad9 + ['Withdraw']: [34567962, 34569797, 34596990], + // // topic0: 0x7fa01e8d24e5a6ec56e00b4ff8ee7ed97e7650a7846ec494bbaa5d65f1be9ea4 + ['SwapV2']: [34609776, 34610336, 34610364, 34611146, 34611872], + // topic0: 0x0fa1e4606af435f32f05b3804033d2933e691fab32ee74d2db6fa82d2741f1ea + ['AssetRemoved']: [34463362], + }, + }; + + for (const [poolAddress, events] of Object.entries(eventsToTest)) { + describe(`Events for ${poolAddress}`, () => { + for (const [eventName, blockNumbers] of Object.entries(events)) { + describe(`${eventName}`, () => { + for (const blockNumber of blockNumbers) { + it(`State after ${blockNumber}`, async function () { + const wombatPool = new WombatPool( + dexKey, + poolAddress, + network, + dexHelper, + logger, + poolAddress.toLowerCase(), + ); + await wombatPool.initialize(blockNumber - 1); + + await testEventSubscriber( + wombatPool, + wombatPool.addressesSubscribed, + (_blockNumber: number) => + fetchPoolState(wombatPool, _blockNumber), + blockNumber, + `${dexKey}_${poolAddress}`, + dexHelper.provider, + ); + }); + } + }); + } + }); + } +}); +describe('Wombat BMW ARB', function () { + const dexKey = 'Wombat'; + const network = Network.ARBITRUM; + const dexHelper = new DummyDexHelper(network); + const bmwAddress = WombatConfig.Wombat[network].bmwAddress; + const eventsToTest: Record = { + [bmwAddress]: { + // topic0: 0xec85b1d1f037ff3a8722aaf5d4d8e7d93c7ff10c056430c18d76a9ec23aa397e + ['Add']: [158157042, 162173973], + }, + }; + + const wombat = new Wombat(network, dexKey, dexHelper); + + for (const [poolAddress, events] of Object.entries(eventsToTest)) { + describe(`Events for ${poolAddress}`, () => { + for (const [eventName, blockNumbers] of Object.entries(events)) { + describe(`${eventName}`, () => { + for (const blockNumber of blockNumbers) { + it(`State after ${blockNumber}`, async function () { + if (wombat.initializePricing) { + await wombat.initializePricing(blockNumber); + } + await testEventSubscriber( + wombat.bmw, + wombat.bmw.addressesSubscribed, + (_blockNumber: number) => + fetchBmwState(wombat.bmw, _blockNumber), + blockNumber, + `${dexKey}_${bmwAddress}`, + dexHelper.provider, + ); + }); + } + }); + } + }); + } +}); diff --git a/src/dex/wombat/wombat-integration.test.ts b/src/dex/wombat/wombat-integration.test.ts new file mode 100644 index 000000000..fd13aca1f --- /dev/null +++ b/src/dex/wombat/wombat-integration.test.ts @@ -0,0 +1,217 @@ +import dotenv from 'dotenv'; + +dotenv.config(); + +import { Interface, Result } from '@ethersproject/abi'; +import { DummyDexHelper } from '../../dex-helper'; +import { Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { Wombat } from './wombat'; +import { checkPoolPrices, checkConstantPoolPrices } from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import { Token } from '../../types'; +import { Address } from '@paraswap/core'; +import { WombatConfig } from './config'; + +function getReaderCalldata( + exchangeAddress: string, + readerIface: Interface, + side: SwapSide, + fromToken: Token, + toToken: Token, + amounts: bigint[], + funcName: string, +) { + return amounts.map(amount => ({ + target: exchangeAddress, + callData: readerIface.encodeFunctionData( + funcName, + side == SwapSide.SELL + ? [fromToken.address, toToken.address, amount] + : [toToken.address, fromToken.address, -amount], + ), + })); +} + +function decodeReaderResult( + results: Result, + readerIface: Interface, + funcName: string, +) { + return results.map(result => { + const parsed = readerIface.decodeFunctionResult(funcName, result); + return BigInt(parsed[0]._hex); + }); +} + +async function checkOnChainPricing( + wombat: Wombat, + pool: Address, + fromToken: Token, + toToken: Token, + side: SwapSide, + funcName: string, + blockNumber: number, + prices: bigint[], + amounts: bigint[], +) { + const exchangeAddress = pool; // TODO: Put here the real exchange address + + const readerIface = Wombat.poolInterface; + + const readerCallData = getReaderCalldata( + exchangeAddress, + readerIface, + side, + fromToken, + toToken, + amounts.slice(1), + funcName, + ); + const readerResult = ( + await wombat.dexHelper.multiContract.methods + .aggregate(readerCallData) + .call({}, blockNumber) + ).returnData; + + const expectedPrices = [0n].concat( + decodeReaderResult(readerResult, readerIface, funcName), + ); + + expect(prices).toEqual(expectedPrices); +} + +async function testPricingOnNetwork( + wombat: Wombat, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], + funcNameToCheck: string, +) { + const networkTokens = Tokens[network]; + + const pools = await wombat.getPoolIdentifiers( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await wombat.getPricesVolume( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + if (wombat.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey); + } + + // Check if onchain pricing equals to calculated ones + await checkOnChainPricing( + wombat, + poolPrices![0].data.exchange, + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + funcNameToCheck, + blockNumber, + poolPrices![0].prices, + amounts, + ); +} + +describe('Wombat', function () { + const dexKey = 'Wombat'; + let blockNumber: number; + let wombat: Wombat; + + Object.keys(WombatConfig[dexKey]).forEach(key => { + describe(`network ${key}`, () => { + const network = Number(key) as Network; + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + + const srcTokenSymbol = 'USDC'; + const destTokenSymbol = network == Network.BASE ? 'USDbC' : 'USDT'; + + let 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(); + wombat = new Wombat(network, dexKey, dexHelper); + }); + + // it('getTopPoolsForToken', async function () { + // // We have to check without calling initializePricing, because + // // pool-tracker is not calling that function + // if (wombat.updatePoolState) { + // await wombat.updatePoolState(); + // } + // const poolLiquidity = await wombat.getTopPoolsForToken( + // tokens[srcTokenSymbol].address, + // 10, + // ); + // console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + // + // if (!wombat.hasConstantPriceLargeAmounts) { + // checkPoolsLiquidity( + // poolLiquidity, + // Tokens[network][srcTokenSymbol].address, + // dexKey, + // ); + // } + // }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + if (wombat.initializePricing) { + await wombat.initializePricing(blockNumber); + } + await testPricingOnNetwork( + wombat, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + 'quotePotentialSwap', + ); + }); + }); + }); +}); diff --git a/src/dex/wombat/wombat-pool.ts b/src/dex/wombat/wombat-pool.ts new file mode 100644 index 000000000..5ab5f91f1 --- /dev/null +++ b/src/dex/wombat/wombat-pool.ts @@ -0,0 +1,688 @@ +import { Interface } from '@ethersproject/abi'; +import { AsyncOrSync, DeepReadonly } from 'ts-essentials'; +import { + Address, + Log, + Logger, + MultiCallInput, + MultiCallOutput, +} from '../../types'; +import { catchParseLogError } from '../../utils'; +import { + InitializeStateOptions, + StatefulEventSubscriber, +} from '../../stateful-event-subscriber'; +import { IDexHelper } from '../../dex-helper'; +import PoolV2ABI from '../../abi/wombat/pool-v2.json'; +import PoolV3ABI from '../../abi/wombat/pool-v3.json'; +import AssetABI from '../../abi/wombat/asset.json'; +import { convertUint256ToInt256, toWad, WAD, wdiv, wmul } from './utils'; +import { BlockHeader } from 'web3-eth'; +import { AssetState, PoolState } from './types'; + +const CLEAR_RPC_CALL_COUNT_INTERVAL = 1000 * 60 * 5; // 5 minutes + +export class WombatPool extends StatefulEventSubscriber { + static readonly poolV2Interface = new Interface(PoolV2ABI); + static readonly poolV3Interface = new Interface(PoolV3ABI); + static readonly assetInterface = new Interface(AssetABI); + + private rpcCallCount = 0; + + private handlers: { + [event: string]: ( + event: any, + state: DeepReadonly, + log: Readonly, + ) => AsyncOrSync | null>; + } = {}; + private poolInterface: Interface = WombatPool.poolV2Interface; + private eventRefetched: string[]; + + blankState: PoolState = { + asset: {}, + underlyingAddresses: [], + params: { + paused: false, + ampFactor: 0n, + haircutRate: 0n, + startCovRatio: 0n, + endCovRatio: 0n, + }, + }; + + constructor( + dexKey: string, + name: string, + protected network: number, + protected dexHelper: IDexHelper, + logger: Logger, + protected poolAddress: Address, + ) { + super(dexKey, `${network}_${name}`, dexHelper, logger); + + this.addressesSubscribed = [this.poolAddress]; + + // users-actions handlers + this.handlers['Swap'] = this.handleSwap.bind(this); + this.handlers['SwapV2'] = this.handleSwapV2.bind(this); + + // admin-actions handlers + this.handlers['SetAmpFactor'] = this.handleSetAmpFactor.bind(this); + this.handlers['SetHaircutRate'] = this.handleSetHaircutRate.bind(this); + this.handlers['AssetAdded'] = this.handleAssetAdded.bind(this); + this.handlers['AssetRemoved'] = this.handleAssetRemoved.bind(this); + this.handlers['FillPool'] = this.handleFillPool.bind(this); + this.handlers['Paused'] = this.handlePaused.bind(this); + this.handlers['Unpaused'] = this.handleUnpaused.bind(this); + this.handlers['PausedAsset'] = this.handlePausedAsset.bind(this); + this.handlers['UnpausedAsset'] = this.handleUnpausedAsset.bind(this); + + this.eventRefetched = ['Deposit', 'Withdraw']; + + setInterval(() => { + this.rpcCallCount = 0; + }, CLEAR_RPC_CALL_COUNT_INTERVAL); + } + + async initialize( + blockNumber: number, + options?: InitializeStateOptions, + ) { + const poolContract = new this.dexHelper.web3Provider.eth.Contract( + PoolV3ABI as any, + this.poolAddress, + ); + try { + await poolContract.methods.withdrawalHaircutRate().call({}, blockNumber); + this.poolInterface = WombatPool.poolV3Interface; + } catch (e) { + this.logger.info( + `no public variable withdrawalHaircutRate in pool ${this.poolAddress}, this is v2 pool`, + ); + } + await super.initialize(blockNumber, options); + } + + protected async processBlockLogs( + state: DeepReadonly, + logs: Readonly[], + blockHeader: Readonly, + ): Promise | null> { + let nextState: DeepReadonly | null = null; + for (const log of logs) { + try { + const event = this.poolInterface.parseLog(log); + if (this.eventRefetched.includes(event.name)) { + // if the state is refetched, no need to deal with the left logs in the block + return await this.generateState(log.blockNumber); + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + const retState: DeepReadonly | null = await this.processLog( + nextState || state, + log, + blockHeader, + ); + + if (retState) { + nextState = retState; + } + } + return nextState; + } + + /** + * The function is called every time any of the subscribed + * addresses release log. The function accepts the current + * state, updates the state according to the log, and returns + * the updated state. + * @param state - Current state of event subscriber + * @param log - Log released by one of the subscribed addresses + * @param blockHeader + * @returns Updates state of the event subscriber after the log + */ + protected async processLog( + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ): Promise | null> { + try { + const event = this.poolInterface.parseLog(log); + if (event.name in this.handlers) { + return await this.handlers[event.name](event, state, log); + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + return null; + } + + /** + * The function generates state using on-chain calls. This + * function is called to regenerate state if the event based + * system fails to fetch events and the local state is no + * more correct. + * @param blockNumber - Blocknumber for which the state should + * should be generated + * @returns state of the event subscriber at blocknumber + */ + async generateState(blockNumber: number): Promise> { + this.rpcCallCount++; + + if (this.rpcCallCount > 1) { + this.logger.warn( + `WombatPool generateState excessive RPC call. Name: ${ + this.name + }. Called ${this.rpcCallCount} times during ${ + CLEAR_RPC_CALL_COUNT_INTERVAL / 1000 + }s interval`, + ); + } + let multiCallInputs: MultiCallInput[] = []; + + // 1.A generate pool params requests + // paused + multiCallInputs.push({ + target: this.poolAddress, + callData: this.poolInterface.encodeFunctionData('paused'), + }); + // ampFactor + multiCallInputs.push({ + target: this.poolAddress, + callData: this.poolInterface.encodeFunctionData('ampFactor'), + }); + // haircutRate + multiCallInputs.push({ + target: this.poolAddress, + callData: this.poolInterface.encodeFunctionData('haircutRate'), + }); + // startCovRatio + multiCallInputs.push({ + target: this.poolAddress, + callData: this.poolInterface.encodeFunctionData('startCovRatio'), + }); + // endCovRatio + multiCallInputs.push({ + target: this.poolAddress, + callData: this.poolInterface.encodeFunctionData('endCovRatio'), + }); + // tokens + multiCallInputs.push({ + target: this.poolAddress, + callData: this.poolInterface.encodeFunctionData('getTokens'), + }); + + // 1.B. invoke multicall + let returnData: MultiCallOutput[] = []; + if (multiCallInputs.length) { + returnData = ( + await this.dexHelper.multiContract.methods + .aggregate(multiCallInputs) + .call({}, blockNumber) + ).returnData; + } + + let i = 0; + // 1.C. decode pool params + const paused = Boolean( + this.poolInterface.decodeFunctionResult('paused', returnData[i++])[0], + ); + const ampFactor = BigInt( + this.poolInterface.decodeFunctionResult('ampFactor', returnData[i++])[0], + ); + const haircutRate = BigInt( + this.poolInterface.decodeFunctionResult( + 'haircutRate', + returnData[i++], + )[0], + ); + const startCovRatio = BigInt( + this.poolInterface.decodeFunctionResult( + 'startCovRatio', + returnData[i++], + )[0], + ); + const endCovRatio = BigInt( + this.poolInterface.decodeFunctionResult( + 'endCovRatio', + returnData[i++], + )[0], + ); + + const tokens = this.poolInterface + .decodeFunctionResult('getTokens', returnData[i++])[0] + .map((tokenAddress: Address) => tokenAddress.toLowerCase()); + + const poolState: PoolState = { + params: { + paused, + ampFactor, + haircutRate, + startCovRatio, + endCovRatio, + }, + underlyingAddresses: tokens, + asset: {}, + }; + + // 2.A. generate requests for getting asset addresses + multiCallInputs = tokens.map((tokenAddress: Address) => ({ + target: this.poolAddress, + callData: this.poolInterface.encodeFunctionData('addressOfAsset', [ + tokenAddress, + ]), + })); + + // 2.B. invoke multicall + returnData = ( + await this.dexHelper.multiContract.methods + .aggregate(multiCallInputs) + .call({}, blockNumber) + ).returnData; + + // 2.C. decode asset addresses + const assets: Address[] = returnData.map(data => { + return this.poolInterface.decodeFunctionResult('addressOfAsset', data)[0]; + }); + + // 3. get asset states + const assetState = await this.getAssetState( + this.poolAddress, + assets.map(asset => asset.toLowerCase()), + tokens, + blockNumber, + ); + const isMainPool = + assetState.filter(asset => asset.relativePrice === undefined).length > 0; + for (let j = 0; j < tokens.length; j++) { + if (isMainPool) { + assetState[j].relativePrice = undefined; + } + poolState.asset[tokens[j]] = assetState[j]; + } + + return poolState; + } + + handleSwap( + event: any, + state: DeepReadonly, + log: Readonly, + ): AsyncOrSync | null> { + const fromTokenAddress = event.args.fromToken.toString().toLowerCase(); + const fromAmount = BigInt(event.args.fromAmount.toString()); + const toTokenAddress = event.args.toToken.toString().toLowerCase(); + const toAmount = BigInt(event.args.toAmount.toString()); + + if ( + !state.underlyingAddresses.includes(fromTokenAddress) || + !state.underlyingAddresses.includes(toTokenAddress) + ) { + return null; + } + + let idealToAmount; + const fromAmountInt256 = convertUint256ToInt256(fromAmount); + if (fromAmountInt256 > 0) { + idealToAmount = wdiv(toAmount, WAD - state.params.haircutRate); + } else { + this.logger.warn( + `pool ${this.poolAddress} swap from a negative amount ${fromAmountInt256} at block ${log.blockNumber} should not happen`, + ); + const haircut = wmul(-fromAmount, state.params.haircutRate); + idealToAmount = toAmount + haircut; + } + + const fromAmountInWad = toWad( + fromAmount, + BigInt(state.asset[fromTokenAddress].underlyingTokenDecimals), + ); + const toAmountInWad = toWad( + idealToAmount, + BigInt(state.asset[toTokenAddress].underlyingTokenDecimals), + ); + + return { + ...state, + asset: { + ...state.asset, + [fromTokenAddress]: { + ...state.asset[fromTokenAddress], + cash: state.asset[fromTokenAddress].cash + fromAmountInWad, + }, + [toTokenAddress]: { + ...state.asset[toTokenAddress], + cash: state.asset[toTokenAddress].cash - toAmountInWad, + }, + }, + }; + } + + handleSwapV2( + event: any, + state: DeepReadonly, + _log: Readonly, + ): AsyncOrSync | null> { + const fromTokenAddress = event.args.fromToken.toString().toLowerCase(); + const fromAmount = BigInt(event.args.fromAmount.toString()); + const toTokenAddress = event.args.toToken.toString().toLowerCase(); + const toAmount = BigInt(event.args.toAmount.toString()); + const toTokenFee = BigInt(event.args.toTokenFee.toString()); + + if ( + !state.underlyingAddresses.includes(fromTokenAddress) || + !state.underlyingAddresses.includes(toTokenAddress) + ) { + return null; + } + + const fromAmountInWad = toWad( + fromAmount, + BigInt(state.asset[fromTokenAddress].underlyingTokenDecimals), + ); + const toAmountInWad = toWad( + toAmount + toTokenFee, + BigInt(state.asset[toTokenAddress].underlyingTokenDecimals), + ); + + return { + ...state, + asset: { + ...state.asset, + [fromTokenAddress]: { + ...state.asset[fromTokenAddress], + cash: state.asset[fromTokenAddress].cash + fromAmountInWad, + }, + [toTokenAddress]: { + ...state.asset[toTokenAddress], + cash: state.asset[toTokenAddress].cash - toAmountInWad, + }, + }, + }; + } + + handleSetAmpFactor( + event: any, + state: DeepReadonly, + _log: Readonly, + ): AsyncOrSync | null> { + const ampFactor = BigInt(event.args.value.toString()); + + return { + ...state, + params: { + ...state.params, + ampFactor, + }, + }; + } + + handleSetHaircutRate( + event: any, + state: DeepReadonly, + _log: Readonly, + ): AsyncOrSync | null> { + const haircutRate = BigInt(event.args.value.toString()); + + return { + ...state, + params: { + ...state.params, + haircutRate, + }, + }; + } + + async handleAssetAdded( + event: any, + state: DeepReadonly, + log: Readonly, + ): Promise | null> { + const token: Address = event.args.token.toString().toLowerCase(); + const asset: Address = event.args.asset.toString().toLowerCase(); + if (state.underlyingAddresses.includes(token)) { + return null; + } + + const assetState = await this.getAssetState( + this.poolAddress, + [asset], + [token], + log.blockNumber, + ); + const poolState = { + ...state, + underlyingAddresses: [...state.underlyingAddresses, token], + asset: { + ...state.asset, + [token]: assetState[0], + }, + }; + + const isMainPool = + Object.values(poolState.asset).filter( + asset => asset.relativePrice === undefined, + ).length > 0; + if (isMainPool) { + for (const underlyingAddress of poolState.underlyingAddresses) { + poolState.asset[underlyingAddress] = { + ...poolState.asset[underlyingAddress], + relativePrice: undefined, + }; + } + } + return poolState; + } + + async handleAssetRemoved( + event: any, + state: DeepReadonly, + _log: Readonly, + ): Promise | null> { + const token = event.args.token.toString().toLowerCase(); + if (!state.underlyingAddresses.includes(token)) { + return null; + } + + return { + ...state, + underlyingAddresses: state.underlyingAddresses.filter( + underlyingAddress => underlyingAddress !== token, + ), + asset: { + ...state.asset, + [token]: undefined, + }, + }; + } + + async handleFillPool( + event: any, + state: DeepReadonly, + _log: Readonly, + ): Promise | null> { + const token = event.args.token.toString().toLowerCase(); + const amount = BigInt(event.args.amount.toString()); + if (!state.underlyingAddresses.includes(token)) { + return null; + } + + return { + ...state, + asset: { + ...state.asset, + [token]: { + ...state.asset[token], + cash: state.asset[token].cash + amount, + }, + }, + }; + } + + async handlePaused( + event: any, + state: DeepReadonly, + _log: Readonly, + ): Promise | null> { + return { + ...state, + params: { + ...state.params, + paused: true, + }, + }; + } + + async handleUnpaused( + event: any, + state: DeepReadonly, + _log: Readonly, + ): Promise | null> { + return { + ...state, + params: { + ...state.params, + paused: false, + }, + }; + } + + async handlePausedAsset( + event: any, + state: DeepReadonly, + _log: Readonly, + ): Promise | null> { + const token = event.args.token.toString(); + if (!state.underlyingAddresses.includes(token)) { + return null; + } + + return { + ...state, + asset: { + ...state.asset, + [token]: { + ...state.asset[token], + paused: true, + }, + }, + }; + } + + async handleUnpausedAsset( + event: any, + state: DeepReadonly, + _log: Readonly, + ): Promise | null> { + const token = event.args.token.toString(); + if (!state.underlyingAddresses.includes(token)) { + return null; + } + + return { + ...state, + asset: { + ...state.asset, + [token]: { + ...state.asset[token], + paused: true, + }, + }, + }; + } + + private async getAssetState( + pool: Address, + assets: Address[], + tokens: Address[], + blockNumber: number, + ): Promise { + const assetStates: AssetState[] = []; + const multiCallInputs: MultiCallInput[] = []; + + const methods = [ + 'cash', + 'liability', + 'underlyingTokenDecimals', + 'getRelativePrice', + ]; + + for (let i = 0; i < assets.length; i++) { + const asset = assets[i]; + const token = tokens[i]; + + multiCallInputs.push({ + target: pool, + callData: this.poolInterface.encodeFunctionData('isPaused', [token]), + }); + + for (const method of methods) { + multiCallInputs.push({ + target: asset, + callData: WombatPool.assetInterface.encodeFunctionData(method), + }); + } + } + + const returnData = await this.dexHelper.multiContract.methods + .tryAggregate(false, multiCallInputs) + .call({}, blockNumber); + + for ( + let i = 0; + i < assets.length * (methods.length + 1); + i += methods.length + 1 + ) { + const paused = returnData[i].success + ? Boolean( + this.poolInterface.decodeFunctionResult( + 'isPaused', + returnData[i].returnData, + )[0], + ) + : false; + const cash = BigInt( + WombatPool.assetInterface.decodeFunctionResult( + methods[0], + returnData[i + 1].returnData, + )[0], + ); + const liability = BigInt( + WombatPool.assetInterface.decodeFunctionResult( + methods[1], + returnData[i + 2].returnData, + )[0], + ); + const underlyingTokenDecimals = + WombatPool.assetInterface.decodeFunctionResult( + methods[2], + returnData[i + 3].returnData, + )[0]; + + let relativePrice: bigint | undefined; + if (returnData[i + 4].success) { + relativePrice = BigInt( + WombatPool.assetInterface.decodeFunctionResult( + methods[3], + returnData[i + 4].returnData, + )[0], + ); + } + + assetStates.push({ + address: assets[i / (methods.length + 1)], + paused, + cash, + liability, + underlyingTokenDecimals, + relativePrice, + }); + } + + return assetStates; + } +} diff --git a/src/dex/wombat/wombat-quoter.ts b/src/dex/wombat/wombat-quoter.ts new file mode 100644 index 000000000..c7cb12ca1 --- /dev/null +++ b/src/dex/wombat/wombat-quoter.ts @@ -0,0 +1,269 @@ +import { AssetState, PoolParams } from './types'; +import { fromWad, sqrt, toWad, WAD, wdiv, wmul } from './utils'; + +export class WombatQuoter { + private readonly ampFactor: bigint; + private readonly haircutRate: bigint; + private readonly startCovRatio: bigint; + private readonly endCovRatio: bigint; + + constructor(poolParams: PoolParams) { + this.ampFactor = poolParams.ampFactor; + this.haircutRate = poolParams.haircutRate; + this.startCovRatio = poolParams.startCovRatio; + this.endCovRatio = poolParams.endCovRatio; + } + + public getQuote( + fromAsset: AssetState, + toAsset: AssetState, + fromAmount: bigint, + ): bigint { + if (fromAmount === 0n || fromAsset.paused) { + return 0n; + } + + fromAmount = toWad(fromAmount, BigInt(fromAsset.underlyingTokenDecimals)); + + try { + const { actualToAmount, haircut } = this._highCovRatioPoolQuoteFrom( + fromAsset, + toAsset, + fromAmount, + ); + const toCash = toAsset.cash - actualToAmount - haircut; + + if (wdiv(toCash, toAsset.liability) < WAD / 100n) { + return 0n; + } + + return fromWad(actualToAmount, BigInt(toAsset.underlyingTokenDecimals)); + } catch (e) { + return 0n; + } + } + + private _highCovRatioPoolQuoteFrom( + fromAsset: AssetState, + toAsset: AssetState, + fromAmount: bigint, + ): { actualToAmount: bigint; haircut: bigint } { + let { actualToAmount, haircut } = this._quoteFrom( + fromAsset, + toAsset, + fromAmount, + ); + + if (fromAmount > 0n) { + const fromCash = fromAsset.cash; + const fromLiability = fromAsset.liability; + + const finalFromAssetCovRatio = wdiv(fromCash + fromAmount, fromLiability); + if (finalFromAssetCovRatio > this.startCovRatio) { + // charge high cov ratio fee + const highCovRatioFee = wmul( + this._highCovRatioFee( + wdiv(fromCash, fromLiability), + finalFromAssetCovRatio, + ), + actualToAmount, + ); + + actualToAmount -= highCovRatioFee; + haircut += highCovRatioFee; + } + } else { + // reverse quote + const toCash = toAsset.cash; + const toLiability = toAsset.liability; + + const finalToAssetCovRatio = wdiv(toCash + actualToAmount, toLiability); + if (finalToAssetCovRatio <= this.startCovRatio) { + // happy path: no high cov ratio fee is charged + return { actualToAmount, haircut }; + } else if (wdiv(toCash, toLiability) >= this.endCovRatio) { + // the to-asset exceeds it's cov ratio limit, further swap to increase cov ratio is impossible + throw new Error('WOMBAT_COV_RATIO_LIMIT_EXCEEDED'); + } + + // reverse quote: cov ratio of the to-asset exceed endCovRatio. direct reverse quote is not supported + // we binary search for a upper bound + actualToAmount = this._findUpperBound(toAsset, fromAsset, -fromAmount); + const result = this._highCovRatioPoolQuoteFrom( + toAsset, + fromAsset, + actualToAmount, + ); + haircut = result.haircut; + } + + return { actualToAmount, haircut }; + } + + private _highCovRatioFee( + initCovRatio: bigint, + finalCovRatio: bigint, + ): bigint { + const startCovRatio = this.startCovRatio!; + const endCovRatio = this.endCovRatio!; + if (finalCovRatio > endCovRatio) { + // invalid swap + throw new Error('WOMBAT_COV_RATIO_LIMIT_EXCEEDED'); + } else if ( + finalCovRatio <= startCovRatio || + finalCovRatio <= initCovRatio + ) { + return 0n; + } + + // 1. Calculate the area of fee(r) = (r - startCovRatio) / (endCovRatio - startCovRatio) + // when r increase from initCovRatio to finalCovRatio + // 2. Then multiply it by (endCovRatio - startCovRatio) / (finalCovRatio - initCovRatio) + // to get the average fee over the range + const a = + initCovRatio <= startCovRatio + ? 0n + : (initCovRatio - startCovRatio) * (initCovRatio - startCovRatio); + const b = (finalCovRatio - startCovRatio) * (finalCovRatio - startCovRatio); + return wdiv( + (b - a) / (finalCovRatio - initCovRatio) / BigInt(2), + endCovRatio - startCovRatio, + ); + } + + private _quoteFrom( + fromAsset: AssetState, + toAsset: AssetState, + fromAmount: bigint, + ): { actualToAmount: bigint; haircut: bigint } { + if (fromAmount < 0n) { + fromAmount = wdiv(fromAmount, WAD - this.haircutRate); + } + + let fromCash = fromAsset.cash; + const toCash = toAsset.cash; + let fromLiability = fromAsset.liability; + const toLiability = toAsset.liability; + + const scaleFactor = this._quoteFactor(fromAsset, toAsset); + if (scaleFactor !== WAD) { + fromCash = (fromCash * scaleFactor) / WAD; + fromLiability = (fromLiability * scaleFactor) / WAD; + fromAmount = (fromAmount * scaleFactor) / WAD; + } + + const idealToAmount = WombatQuoter.swapQuoteFunc( + fromCash, + toCash, + fromLiability, + toLiability, + fromAmount, + this.ampFactor, + ); + if ( + (fromAmount > 0n && toCash < idealToAmount) || + (fromAmount < 0n && fromCash < -fromAmount) + ) { + throw new Error('WOMBAT_CASH_NOT_ENOUGH'); + } + + let actualToAmount, haircut; + if (fromAmount > 0) { + haircut = wmul(idealToAmount, this.haircutRate); + actualToAmount = idealToAmount - haircut; + } else { + actualToAmount = idealToAmount; + haircut = wmul(-fromAmount, this.haircutRate); + } + + return { actualToAmount, haircut }; + } + + private _findUpperBound( + fromAsset: AssetState, + toAsset: AssetState, + toAmount: bigint, + ): bigint { + const decimals = BigInt(fromAsset.underlyingTokenDecimals); + const toWadFactor = toWad(1n, decimals); + // the search value uses the same number of digits as the token + let high = fromWad( + wmul(fromAsset.liability, this.endCovRatio!) - fromAsset.cash, + decimals, + ); + let low = 1n; + + // verify `high` is a valid upper bound + const { actualToAmount: quote } = this._highCovRatioPoolQuoteFrom( + fromAsset, + toAsset, + high * toWadFactor, + ); + if (quote < toAmount) { + throw new Error('WOMBAT_COV_RATIO_LIMIT_EXCEEDED'); + } + + // Note: we might limit the maximum number of rounds if the request is always rejected by the RPC server + while (low < high) { + const mid = (low + high) / BigInt(2); + const { actualToAmount: quote } = this._highCovRatioPoolQuoteFrom( + fromAsset, + toAsset, + mid * toWadFactor, + ); + if (quote >= toAmount) { + high = mid; + } else { + low = mid + 1n; + } + } + return high * toWadFactor; + } + + private _quoteFactor(fromAsset: AssetState, toAsset: AssetState): bigint { + if (!fromAsset.relativePrice || !toAsset.relativePrice) { + return WAD; + } + return (WAD * fromAsset.relativePrice) / toAsset.relativePrice; + } + + static solveQuad(b: bigint, c: bigint): bigint { + return (sqrt(b * b + c * 4n * WAD, b) - b) / 2n; + } + + static swapQuoteFunc( + aX: bigint, + aY: bigint, + lX: bigint, + lY: bigint, + dX: bigint, + a: bigint, + ): bigint { + if (lX == 0n || lY == 0n) { + // in case div of 0, CORE_UNDERFLOW + return 0n; + } + + // int256 D = Ax + Ay - A.wmul((Lx * Lx) / Ax + (Ly * Ly) / Ay); // flattened _invariantFunc + const d = aX + aY - wmul(a, (lX * lX) / aX + (lY * lY) / aY); + // int256 rx_ = (Ax + Dx).wdiv(Lx); + const rX = wdiv(aX + dX, lX); + // int256 b = (Lx * (rx_ - A.wdiv(rx_))) / Ly - D.wdiv(Ly); // flattened _coefficientFunc + const b = (lX * (rX - wdiv(a, rX))) / lY - wdiv(d, lY); + // int256 ry_ = _solveQuad(b, A); + const rY = WombatQuoter.solveQuad(b, a); + // int256 Dy = Ly.wmul(ry_) - Ay; + const dY = wmul(lY, rY) - aY; + + // if (Dy < 0) { + // quote = uint256(-Dy); + // } else { + // quote = uint256(Dy); + // } + if (dY < 0n) { + return -dY; + } else { + return dY; + } + } +} diff --git a/src/dex/wombat/wombat.ts b/src/dex/wombat/wombat.ts new file mode 100644 index 000000000..a119b7c21 --- /dev/null +++ b/src/dex/wombat/wombat.ts @@ -0,0 +1,427 @@ +import { AsyncOrSync, DeepReadonly } from 'ts-essentials'; +import { Interface } from '@ethersproject/abi'; +import { SwapSide } from '@paraswap/core'; + +import { + AdapterExchangeParam, + Address, + ExchangePrices, + Logger, + PoolLiquidity, + PoolPrices, + SimpleExchangeParam, + Token, +} from '../../types'; +import { Network } from '../../constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { getBigIntPow, getDexKeysWithNetwork } from '../../utils'; +import { IDex } from '../idex'; +import { IDexHelper } from '../../dex-helper'; +import { DexParams, PoolState, WombatData } from './types'; +import { + getLocalDeadlineAsFriendlyPlaceholder, + SimpleExchange, +} from '../simple-exchange'; +import { Adapters, WombatConfig } from './config'; +import PoolABI from '../../abi/wombat/pool-v2.json'; +import AssetABI from '../../abi/wombat/asset.json'; +import ERC20ABI from '../../abi/erc20.json'; +import { WombatQuoter } from './wombat-quoter'; +import { WombatBmw } from './wombat-bmw'; +import { fromWad } from './utils'; +import { WombatPool } from './wombat-pool'; +import { StatePollingManager } from '../../lib/stateful-rpc-poller/state-polling-manager'; + +export class Wombat extends SimpleExchange implements IDex { + // contract interfaces + static readonly erc20Interface = new Interface(ERC20ABI); + static readonly poolInterface = new Interface(PoolABI); + static readonly assetInterface = new Interface(AssetABI); + protected pollingManager: StatePollingManager; + + protected config: DexParams; + protected poolLiquidityUSD?: { [poolAddress: string]: number }; + public bmw: WombatBmw; + public pools: { [poolAddress: string]: WombatPool } = {}; + + readonly isStatePollingDex = true; + + readonly hasConstantPriceLargeAmounts = false; + readonly needWrapNative = true; + + readonly isFeeOnTransferSupported = false; + + public static dexKeysWithNetwork: { + key: string; + networks: Network[]; + }[] = getDexKeysWithNetwork(WombatConfig); + + logger: Logger; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + protected adapters = Adapters[network] || {}, // TODO: add any additional optional params to support other fork DEXes + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey); + this.config = WombatConfig[dexKey][network]; + this.pollingManager = StatePollingManager.getInstance(dexHelper); + + this.bmw = new WombatBmw( + dexKey, + this.config.bmwAddress, + network, + dexHelper, + this.logger, + this.config.bmwAddress, + this.onAssetAdded.bind(this), + ); + } + + async init(blockNumber: number) { + if (!this.bmw.isInitialized) { + await this.bmw.initialize(blockNumber); + } + } + + // Initialize pricing is called once in the start of + // pricing service. It is intended to setup the integration + // for pricing requests. It is optional for a DEX to + // implement this function + async initializePricing(blockNumber: number) { + await this.init(blockNumber); + const bmwState = this.bmw.getState(blockNumber); + if (!bmwState) { + throw new Error('initializePricing: bmwState still null after init'); + } + } + + onAssetAdded = async (pool: Address, blockNumber: number): Promise => { + if (!this.pools[pool]) { + this.pools[pool] = new WombatPool( + this.dexKey, + this.getPoolIdentifier(pool), + this.network, + this.dexHelper, + this.logger, + pool, + ); + await this.pools[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] ? this.adapters[side] : null; + } + + // Returns list of pool identifiers that can be used + // for a given swap. poolIdentifiers must be unique + // across DEXes. It is recommended to use + // ${dexKey}_${poolAddress} as a poolIdentifier + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + if (side === SwapSide.BUY) return []; + return ( + await this.findPools( + this.dexHelper.config.wrapETH(srcToken).address.toLowerCase(), + this.dexHelper.config.wrapETH(destToken).address.toLowerCase(), + blockNumber, + ) + ).map(p => this.getPoolIdentifier(p)); + } + + protected getPoolIdentifier(poolAddress: Address): string { + return `${this.dexKey}_${poolAddress}`; + } + + // 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) return null; // Buy side not implemented yet + if (!this.pools) { + this.logger.error(`Missing pools for ${this.dexKey} in getPricesVolume`); + return null; + } + const srcTokenAddress = this.dexHelper.config + .wrapETH(srcToken) + .address.toLowerCase(); + const destTokenAddress = this.dexHelper.config + .wrapETH(destToken) + .address.toLowerCase(); + if (srcTokenAddress === destTokenAddress) return null; + + const pools = ( + await this.findPools(srcTokenAddress, destTokenAddress, blockNumber) + ).filter( + poolAddress => + !limitPools || limitPools.includes(this.getPoolIdentifier(poolAddress)), + ); + + const promises = []; + for (const poolAddress of pools) { + let state = await this.pools![poolAddress].getState(blockNumber); + if (!state) { + this.logger.warn( + `State of pool ${poolAddress} is null in getPricesVolume, skipping...`, + ); + continue; + } + const [unit, ...prices] = this.computePrices( + srcTokenAddress, + destTokenAddress, + [getBigIntPow(srcToken.decimals), ...amounts], + side, + state, + ); + promises.push({ + prices, + unit, + data: { + exchange: poolAddress, + }, + poolAddresses: [poolAddress], + exchange: this.dexKey, + /** @todo specify gas cost */ + gasCost: 260 * 1000, + poolIdentifier: this.getPoolIdentifier(poolAddress), + }); + } + + return await Promise.all(promises); + } + + // take PoolState to compute price + protected computePrices( + srcTokenAddress: Address, + destTokenAddress: Address, + amounts: bigint[], + side: SwapSide, + state: DeepReadonly, + ): bigint[] { + const srcAsset = state.asset[srcTokenAddress]; + const destAsset = state.asset[destTokenAddress]; + const quoter = new WombatQuoter(state.params); + + return amounts.map(fromAmount => { + return side === SwapSide.SELL + ? quoter.getQuote(srcAsset, destAsset, fromAmount) + : quoter.getQuote(destAsset, srcAsset, -fromAmount); + }); + } + + protected async findPools( + srcTokenAddress: Address, + destTokenAddress: Address, + blockNumber: number, + ): Promise { + const pools: Address[] = []; + for (const [poolAddress, pool] of Object.entries(this.pools)) { + const state = await pool.getState(blockNumber); + if (!state) { + continue; + } + + if ( + state && + !state.params.paused && + state.asset[srcTokenAddress] && + state.asset[destTokenAddress] + ) { + pools.push(poolAddress); + } + } + + return pools; + } + + // Returns estimated gas cost of calldata for this DEX in multiSwap + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + // TODO: update if there is any payload in getAdapterParam + return CALLDATA_GAS_COST.DEX_NO_PAYLOAD; + } + + getAdapterParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: WombatData, + side: SwapSide, + ): AdapterExchangeParam { + if (side === SwapSide.BUY) throw new Error(`Buy not supported`); + + const { exchange } = data; + + return { + targetExchange: exchange, + payload: '0x', + networkFee: '0', + }; + } + + // Encode call data used by simpleSwap like routers + // Used for simpleSwap & simpleBuy + // Hint: this.buildSimpleParamWithoutWETHConversion + // could be useful + async getSimpleParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: WombatData, + side: SwapSide, + ): Promise { + if (side === SwapSide.BUY) throw new Error(`Buy not supported`); + const { exchange } = data; + + // Encode here the transaction arguments + const swapData = Wombat.poolInterface.encodeFunctionData('swap', [ + srcToken, + destToken, + srcAmount, + destAmount, + this.augustusAddress, + getLocalDeadlineAsFriendlyPlaceholder(), + ]); + + return this.buildSimpleParamWithoutWETHConversion( + srcToken, + srcAmount, + destToken, + destAmount, + swapData, + exchange, + ); + } + + // This is called once before getTopPoolsForToken is + // called for multiple tokens. This can be helpful to + // update common state required for calculating + // getTopPoolsForToken. For example, poolLiquidityUSD. + async updatePoolState(): Promise { + const blockNumber = await this.dexHelper.provider.getBlockNumber(); + await this.init(blockNumber); + const bmwState = this.bmw.getState(blockNumber); + if (!bmwState) { + throw new Error('updatePoolState: bmwState still null after init'); + } + + // All tokens are USD stablecoins so to estimate liquidity can just add + // the cash balances of all the tokens + const poolLiquidityUSD: { [poolAddress: string]: number } = {}; + const usdPromises = []; + const poolStates: { [poolAddress: string]: DeepReadonly } = {}; + const poolStateObjs = await Promise.all( + Object.values(this.pools).map(pool => pool.getState(blockNumber)), + ); + + for (const [poolAddress, _] of Object.entries(this.pools)) { + const index = Object.keys(this.pools).indexOf(poolAddress); + let state = poolStateObjs[index]; + if (!state) { + this.logger.warn( + `State of ${poolAddress} is null in updatePoolState, skipping...`, + ); + continue; + } + poolStates[poolAddress] = state; + for (const [tokenAddress, assetState] of Object.entries(state.asset)) { + usdPromises.push( + this.dexHelper.getTokenUSDPrice( + { + address: tokenAddress, + decimals: assetState.underlyingTokenDecimals, + }, + fromWad( + assetState.cash, + BigInt(assetState.underlyingTokenDecimals), + ), + ), + ); + } + } + const usdValues = await Promise.all(usdPromises); + + for (const [poolAddress, poolState] of Object.entries(poolStates)) { + poolLiquidityUSD[poolAddress] = 0; + for (let i = 0; i < poolState.underlyingAddresses.length; i++) { + poolLiquidityUSD[poolAddress] += usdValues[i]; + } + } + + this.poolLiquidityUSD = poolLiquidityUSD; + } + + // Returns list of top pools based on liquidity. Max + // limit number pools should be returned. + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + // getTopPoolsForToken shouldn't use block manager + // TODO: update getTopPoolsForToken to work without block manager + return []; + // if (!this.poolLiquidityUSD) await this.updatePoolState(); + // tokenAddress = ( + // isETHAddress(tokenAddress) + // ? this.dexHelper.config.data.wrappedNativeTokenAddress + // : tokenAddress + // ).toLowerCase(); + // const pools: string[] = []; + // const poolStates: { [poolAddress: string]: DeepReadonly } = {}; + // for (const [poolAddress, eventPool] of Object.entries(this.pools)) { + // let state = await eventPool.getState(); + // if (!state) { + // this.logger.warn( + // `State of ${poolAddress} is null in getTopPoolsForToken, skipping...`, + // ); + // continue; + // } + // if ( + // state.value.underlyingAddresses.includes(tokenAddress) && + // this.poolLiquidityUSD![poolAddress] + // ) { + // poolStates[poolAddress] = state.value; + // pools.push(poolAddress); + // } + // } + + // // sort by liquidity + // pools.sort((a, b) => this.poolLiquidityUSD![b] - this.poolLiquidityUSD![a]); + // return pools.slice(0, limit).map(poolAddress => ({ + // exchange: this.dexKey, + // address: poolAddress, + // // other tokens in the same pool + // connectorTokens: poolStates[poolAddress].underlyingAddresses + // .filter(t => t !== tokenAddress) + // .map(t => ({ + // decimals: poolStates[poolAddress].asset[t].underlyingTokenDecimals, + // address: t, + // })), + // liquidityUSD: this.poolLiquidityUSD![poolAddress], + // })); + } + + // This is optional function in case if your implementation has acquired any resources + // you need to release for graceful shutdown. For example, it may be any interval timer + releaseResources(): AsyncOrSync { + // TODO: complete me! + } +} 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/implementations/local-paraswap-sdk.ts b/src/implementations/local-paraswap-sdk.ts index d1b573beb..46e1770a9 100644 --- a/src/implementations/local-paraswap-sdk.ts +++ b/src/implementations/local-paraswap-sdk.ts @@ -19,6 +19,7 @@ import { SwapSide, NULL_ADDRESS, ContractMethod } from '../constants'; import { LimitOrderExchange } from '../dex/limit-order-exchange'; import { v4 as uuid } from 'uuid'; import { DirectContractMethods } from '@paraswap/core/build/constants'; +import { ParaSwapVersion } from '@paraswap/core'; export interface IParaSwapSDK { getPrices( @@ -177,6 +178,7 @@ export class LocalParaswapSDK implements IParaSwapSDK { gasCost: '0', others: [], side, + version: ParaSwapVersion.V5, tokenTransferProxy: this.dexHelper.config.data.tokenTransferProxyAddress, contractAddress: this.dexHelper.config.data.augustusAddress, }; diff --git a/src/lib/api3-feed.ts b/src/lib/api3-feed.ts new file mode 100644 index 000000000..c0a7b90e8 --- /dev/null +++ b/src/lib/api3-feed.ts @@ -0,0 +1,175 @@ +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 getDapiNameHashMultiCallInput(proxy: Address): MultiCallInput { + return { + target: proxy, + callData: + Api3FeedSubscriber.proxyInterface.encodeFunctionData('dapiNameHash'), + }; + } + + static getDataFeedId(proxy: Address): MultiCallInput { + return { + target: proxy, + callData: + Api3FeedSubscriber.proxyInterface.encodeFunctionData('dataFeedId'), + }; + } + + static getFeedIdFromDapiNameHash( + api3Server: Address, + dapiNameHash: string, + ): MultiCallInput { + return { + target: api3Server, + callData: Api3FeedSubscriber.api3ServerV1Iface.encodeFunctionData( + 'dapiNameHashToDataFeedId', + [dapiNameHash], + ), + }; + } + + static decodeFeedIdFromDapiNameHash(multicallOutput: MultiCallOutput) { + return Api3FeedSubscriber.api3ServerV1Iface.decodeFunctionResult( + 'dapiNameHashToDataFeedId', + multicallOutput, + )[0]; + } + + static decodeDapiNameHash(multicallOutput: MultiCallOutput) { + return Api3FeedSubscriber.proxyInterface.decodeFunctionResult( + 'dapiNameHash', + multicallOutput, + )[0]; + } + + 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/lib/fetcher/fetcher.ts b/src/lib/fetcher/fetcher.ts index 278d18e26..449a368df 100644 --- a/src/lib/fetcher/fetcher.ts +++ b/src/lib/fetcher/fetcher.ts @@ -1,7 +1,7 @@ -import { IRequestWrapper } from '../../dex-helper'; +import { isFunction } from 'lodash'; import { Logger } from 'log4js'; +import { IRequestWrapper } from '../../dex-helper'; import { RequestConfig, Response } from '../../dex-helper/irequest-wrapper'; -import { isFunction } from 'lodash'; const FETCH_TIMEOUT_MS = 10 * 1000; const FETCH_FAIL_MAX_ATTEMPT = 5; diff --git a/src/rebase-tokens.json b/src/rebase-tokens.json new file mode 100644 index 000000000..2fbef4167 --- /dev/null +++ b/src/rebase-tokens.json @@ -0,0 +1,17 @@ +[ + { + "symbol": "AMPL", + "chainId": 1, + "address": "0xd46ba6d942050d489dbd938a2c909a5d5039a161" + }, + { + "symbol": "AMPL", + "chainId": 56, + "address": "0xDB021b1B247fe2F1fa57e0A87C748Cc1E321F07F" + }, + { + "symbol": "AMPL", + "chainId": 43114, + "address": "0x027dbca046ca156de9622cd1e2d907d375e53aa7" + } +] diff --git a/src/router/buy.ts b/src/router/buy.ts index 4bb1759f3..6063cd1ce 100644 --- a/src/router/buy.ts +++ b/src/router/buy.ts @@ -42,7 +42,7 @@ export class Buy extends PayloadEncoder implements IRouter { referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -69,13 +69,9 @@ export class Buy extends PayloadEncoder implements IRouter { encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - SwapSide.BUY, - ), + encodeFeePercent(partnerFeePercent, takeSurplus, SwapSide.BUY), ]; const buyData: ContractBuyData = { diff --git a/src/router/directswap.ts b/src/router/directswap.ts index 2af9245bd..a0a44edc2 100644 --- a/src/router/directswap.ts +++ b/src/router/directswap.ts @@ -29,7 +29,7 @@ export class DirectSwap implements IRouter { referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -76,13 +76,9 @@ export class DirectSwap implements IRouter { encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - priceRoute.side, - ), + encodeFeePercent(partnerFeePercent, takeSurplus, priceRoute.side), ]; return dex.getDirectParam!( diff --git a/src/router/irouter.ts b/src/router/irouter.ts index 01e1ff8f0..b17283e14 100644 --- a/src/router/irouter.ts +++ b/src/router/irouter.ts @@ -9,7 +9,7 @@ export interface IRouter { referrerAddress: Address | undefined, partner: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, diff --git a/src/router/megaswap.ts b/src/router/megaswap.ts index 58433b270..ce8cbee10 100644 --- a/src/router/megaswap.ts +++ b/src/router/megaswap.ts @@ -42,7 +42,7 @@ export class MegaSwap extends PayloadEncoder implements IRouter { referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -58,13 +58,9 @@ export class MegaSwap extends PayloadEncoder implements IRouter { encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - SwapSide.SELL, - ), + encodeFeePercent(partnerFeePercent, takeSurplus, SwapSide.SELL), ]; const sellData: ContractMegaSwapSellData = { diff --git a/src/router/multiswap.ts b/src/router/multiswap.ts index d90695d0c..429e8b3c0 100644 --- a/src/router/multiswap.ts +++ b/src/router/multiswap.ts @@ -45,7 +45,7 @@ export class MultiSwap referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -66,13 +66,9 @@ export class MultiSwap encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - SwapSide.SELL, - ), + encodeFeePercent(partnerFeePercent, takeSurplus, SwapSide.SELL), ]; const sellData: ContractSellData = { diff --git a/src/router/payload-encoder.ts b/src/router/payload-encoder.ts index 3a9a9bc93..cf057846c 100644 --- a/src/router/payload-encoder.ts +++ b/src/router/payload-encoder.ts @@ -29,14 +29,14 @@ const HALF_SPLIT = '5000'; export function encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }: { partnerAddress: string; partnerFeePercent: string; - positiveSlippageToUser: boolean; + takeSurplus: boolean; }): string { const isPartnerTakeNoFeeNoPos = - +partnerFeePercent === 0 && positiveSlippageToUser == true; + +partnerFeePercent === 0 && takeSurplus == false; // nullify partner address to fallback default circuit contract without partner/referrer (no harm as no fee taken at all) const partner = isPartnerTakeNoFeeNoPos ? NULL_ADDRESS : partnerAddress; @@ -51,11 +51,11 @@ export function encodePartnerAddressForFeeLogic({ export function encodeFeePercent( partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, side: SwapSide, ) { const isNoFeeAndPositiveSlippageToPartner = - positiveSlippageToUser === false && BigInt(partnerFeePercent) === 0n; + takeSurplus === true && BigInt(partnerFeePercent) === 0n; let fee = isNoFeeAndPositiveSlippageToPartner ? BigInt(HALF_SPLIT) @@ -70,9 +70,9 @@ export function encodeFeePercent( fee |= OneShift17; } - // Set 14th bit if positiveSlippageToUser is true + // Set 14th bit if takeSurplus is false // Upd: not used onchain anymore but better to keep to prevent collisions and ensure continuity of analytics - if (positiveSlippageToUser) fee |= OneShift14; + if (!takeSurplus) fee |= OneShift14; // Set 15th bit to take fee from srcToken if (side === SwapSide.BUY && !isNoFeeAndPositiveSlippageToPartner) diff --git a/src/router/simpleswap.ts b/src/router/simpleswap.ts index 1b8e9e108..6047de047 100644 --- a/src/router/simpleswap.ts +++ b/src/router/simpleswap.ts @@ -223,7 +223,7 @@ export abstract class SimpleRouterBase referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -272,7 +272,7 @@ export abstract class SimpleRouter extends SimpleRouterBase { referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -292,13 +292,9 @@ export abstract class SimpleRouter extends SimpleRouterBase { encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - this.side, - ), + encodeFeePercent(partnerFeePercent, takeSurplus, this.side), ]; const sellData: ConstractSimpleData = { diff --git a/src/router/simpleswapnft.ts b/src/router/simpleswapnft.ts index 1fce10a08..fa33bab0f 100644 --- a/src/router/simpleswapnft.ts +++ b/src/router/simpleswapnft.ts @@ -60,7 +60,7 @@ export class SimpleBuyNFT extends SimpleRouterBase { referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -102,13 +102,9 @@ export class SimpleBuyNFT extends SimpleRouterBase { encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - SwapSide.BUY, - ), + encodeFeePercent(partnerFeePercent, takeSurplus, SwapSide.BUY), ]; const buyData: ContractSimpleBuyNFTData = { 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/transaction-builder.ts b/src/transaction-builder.ts index 41da59942..db64867ea 100644 --- a/src/transaction-builder.ts +++ b/src/transaction-builder.ts @@ -17,7 +17,7 @@ export class TransactionBuilder { referrerAddress, partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, gasPrice, maxFeePerGas, maxPriorityFeePerGas, @@ -33,7 +33,7 @@ export class TransactionBuilder { referrerAddress?: Address; partnerAddress: Address; partnerFeePercent: string; - positiveSlippageToUser?: boolean; + takeSurplus?: boolean; gasPrice?: string; // // @TODO: improve types? so that either gasPrice or ALL of max.*FeePerGas MUST be returned? maxFeePerGas?: string; maxPriorityFeePerGas?: string; @@ -53,7 +53,7 @@ export class TransactionBuilder { referrerAddress, partnerAddress, partnerFeePercent, - positiveSlippageToUser ?? true, + takeSurplus ?? false, _beneficiary, permit || '0x', deadline, diff --git a/src/types.ts b/src/types.ts index 6d63f0ba2..c89e0d544 100644 --- a/src/types.ts +++ b/src/types.ts @@ -276,6 +276,9 @@ export type Config = { hashFlowDisabledMMs: string[]; uniswapV3EventLoggingSampleRate?: number; swaapV2AuthToken?: string; + dexalotAuthToken?: string; + smardexSubgraphAuthToken?: string; + forceRpcFallbackDexs: string[]; }; export type BigIntAsString = string; @@ -290,6 +293,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 ed2b08e90..354ee37fb 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -23,10 +23,18 @@ export const Tokens: { address: ETHER_ADDRESS, decimals: 18, }, + SWETH: { + address: '0xf951e335afb289353dc249e82926178eac7ded78', + decimals: 18, + }, REQ: { address: '0x8f8221aFbB33998d8584A2B05749bA73c37a938a', decimals: 18, }, + AMPL: { + address: '0xd46ba6d942050d489dbd938a2c909a5d5039a161', + decimals: 9, + }, USDC: { address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', decimals: 6, @@ -61,10 +69,18 @@ export const Tokens: { address: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', decimals: 18, }, + SDEX: { + address: '0x5DE8ab7E27f6E7A1fFf3E5B337584Aa43961BEeF', + decimals: 18, + }, wstETH: { address: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', decimals: 18, }, + frxETH: { + address: '0x5E8422345238F34275888049021821E8E08CAa1f', + decimals: 18, + }, WETH: { address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', decimals: 18, @@ -307,6 +323,26 @@ export const Tokens: { address: '0x004626a008b1acdc4c74ab51644093b155e59a23', decimals: 18, }, + GHO: { + address: '0x40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f', + decimals: 18, + }, + crvUSD: { + address: '0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E', + decimals: 18, + }, + wibBTC: { + address: '0x8751d4196027d4e6da63716fa7786b5174f04c15', + decimals: 18, + }, + MATIC: { + address: '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0', + decimals: 18, + }, + POL: { + address: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6', + decimals: 19, + }, }, [Network.ROPSTEN]: { DAI: { @@ -445,6 +481,10 @@ export const Tokens: { address: '0x004626a008b1acdc4c74ab51644093b155e59a23', decimals: 18, }, + SDEX: { + address: '0x6899fAcE15c14348E1759371049ab64A3a06bFA6', + decimals: 18, + }, }, [Network.FANTOM]: { FTM: { address: ETHER_ADDRESS, decimals: 18 }, @@ -464,6 +504,10 @@ export const Tokens: { address: '0x049d68029688eabf473097a2fc38ef61633a3c7a', decimals: 6, }, + EQUAL: { + address: '0x3fd3a0c85b70754efc07ac9ac0cbbdce664865a6', + decimals: 18, + }, POPS: { address: '0x9dE4b40bDcE50Ec6a1A668bF85997BbBD324069a', decimals: 18, @@ -512,6 +556,18 @@ export const Tokens: { address: '0xe578C856933D8e1082740bf7661e379Aa2A30b26', decimals: 6, }, + axlUSDC: { + address: '0x1B6382DBDEa11d97f24495C9A90b7c88469134a4', + decimals: 6, + }, + lzUSDC: { + address: '0x28a92dde19D9989F39A49905d7C9C2FAc7799bDf', + decimals: 6, + }, + FVM: { + address: '0x07BB65fAaC502d4996532F834A1B7ba5dC32Ff96', + decimals: 18, + }, }, [Network.BSC]: { POPS: { @@ -526,6 +582,10 @@ export const Tokens: { address: '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c', decimals: 18, }, + BNBx: { + address: '0x1bdd3Cf7F79cfB8EdbB955f20ad99211551BA275', + decimals: 18, + }, BUSD: { address: '0xe9e7cea3dedca5984780bafc599bd69add087d56', decimals: 18, @@ -571,6 +631,26 @@ export const Tokens: { address: '0x4268B8F0B87b6Eae5d897996E6b845ddbD99Adf3', decimals: 6, }, + FRAX: { + address: '0x90C97F71E18723b0Cf0dfa30ee176Ab653E89F40', + decimals: 18, + }, + frxETH: { + address: '0x64048A7eEcF3a2F1BA9e144aAc3D7dB6e58F555e', + decimals: 18, + }, + USDFI: { + address: '0x11A38e06699b238D6D9A0C7A01f3AC63a07ad318', + decimals: 18, + }, + XRP: { + address: '0x1d2f0da169ceb9fc7b3144628db156f3f6c60dbe', + decimals: 18, + }, + SDEX: { + address: '0xFdc66A08B0d0Dc44c17bbd471B88f49F50CdD20F', + decimals: 18, + }, }, [Network.AVALANCHE]: { USDCe: { @@ -722,8 +802,16 @@ export const Tokens: { address: '0x502580fc390606b47fc3b741d6d49909383c28a9', decimals: 18, }, + AMPL: { + address: '0x027dbcA046ca156De9622cD1e2D907d375e53aa7', + decimals: 9, + }, }, [Network.ARBITRUM]: { + SEN: { + address: '0x154388a4650D63acC823e06Ef9e47C1eDdD3cBb2', + decimals: 18, + }, DAI: { address: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', decimals: 18, @@ -813,6 +901,22 @@ export const Tokens: { address: '0x004626a008b1acdc4c74ab51644093b155e59a23', decimals: 18, }, + GRAIL: { + address: '0x3d9907f9a368ad0a51be60f7da3b97cf940982d8', + decimals: 18, + }, + wstETH: { + address: '0x5979D7b546E38E414F7E9822514be443A4800529', + decimals: 18, + }, + RDPX: { + address: '0x32eb7902d4134bf98a28b963d26de779af92a212', + decimals: 18, + }, + SDEX: { + address: '0xabD587f2607542723b17f14d00d99b987C29b074', + decimals: 18, + }, }, [Network.OPTIMISM]: { DAI: { @@ -872,6 +976,71 @@ export const Tokens: { address: '0x004626a008b1acdc4c74ab51644093b155e59a23', decimals: 18, }, + frxETH: { + address: '0x6806411765Af15Bddd26f8f544A34cC40cb9838B', + decimals: 18, + }, + }, + [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, + }, + SDEX: { + address: '0xFd4330b0312fdEEC6d4225075b82E00493FF2e3f', + decimals: 18, + }, + ETH: { address: ETHER_ADDRESS, decimals: 18 }, }, }; @@ -880,7 +1049,8 @@ export const Holders: { } = { [Network.MAINNET]: { ETH: '0x176F3DAb24a159341c0509bB36B833E7fdd0a132', - USDC: '0x79E2Ba942B0e8fDB6ff3d406e930289d10B49ADe', + USDC: '0x7713974908be4bed47172370115e8b1219f4a5f0', + AMPL: '0x223592a191ECfC7FDC38a9256c3BD96E771539A9', WBTC: '0x1cb17a66dc606a52785f69f08f4256526abd4943', sBTC: '0xA2e3475D13776C6E42ff37B47286827d959B2195', BADGER: '0x34e2741a3f8483dbe5231f61c005110ff4b9f50a', @@ -938,6 +1108,13 @@ export const Holders: { PSP: '0xE5E5440a1CE69C5cf67BFFA74d185e57c31b43E5', agEUR: '0xa116f421ff82a9704428259fd8cc63347127b777', stEUR: '0xfda462548ce04282f4b6d6619823a7c64fdc0185', + crvUSD: '0xA920De414eA4Ab66b97dA1bFE9e6EcA7d4219635', + GHO: '0x844Dc85EdD8492A56228D293cfEbb823EF3E10EC', + wibBTC: '0xFbdCA68601f835b27790D98bbb8eC7f05FDEaA9B', + MATIC: '0x7073783eee7e9b3e6e4ddac4d7f49dc46044dd9a', + POL: '0x57B6Ad484ccdd902C4419424bA648ba6Ed45dc68', + SDEX: '0xB0470cF15B22a6A32c49a7C20E3821B944A76058', + frxETH: '0x9df2322bdAEC46627100C999E6dDdD27837fec6e', }, [Network.ROPSTEN]: { ETH: '0x43262A12d8610AA70C15DbaeAC321d51613c9071', @@ -973,12 +1150,13 @@ export const Holders: { amUSDT: '0x832b11846a27b3ba25d68ae80c39fab155d18c49', amUSDC: '0x6e7f19cd23049c7118e14470e2bf85d2e26ee0ae', MAI: '0x9a8cf02f3e56c664ce75e395d0e4f3dc3dafe138', + SDEX: '0xB0470cF15B22a6A32c49a7C20E3821B944A76058', }, [Network.FANTOM]: { DAI: '0x370f4b2dcf75c94d8d4450b493661a9c6170d0b5', FTM: '0x431e81E5dfB5A24541b5Ff8762bDEF3f32F96354', WFTM: '0x3e923747ca2675e096d812c3b24846ac39aed645', - USDC: '0xe48793b1533b351ae184e1c3119d0955dde7b330', + USDC: '0xf53feaeb035361c046e5669745695e450ebb4028', FUSDT: '0x9ade1c17d25246c405604344f89E8F23F8c1c632', POPS: '0x4b78b52e7de4d8b7d367297cb8a87c1875a9d591', aFanUSDT: '0x8EBc96fF91A30059E447bFC7C0a7394f8A5793E6', @@ -994,6 +1172,10 @@ export const Holders: { ETH: '0xf48883940b4056801de30f12b934dcea90133ee6', GUSDC: '0x894d774a293f8aa3d23d67815d4cadb5319c1094', GDAI: '0x0e2ed73f9c1409e2b36fe6c46e60d4557b7c2ac0', + EQUAL: '0x8b187ea19c93091a4d6b426b71871648182b5fac', + FVM: '0x07BB65fAaC502d4996532F834A1B7ba5dC32Ff96', + lzUSDC: '0x1e38e2e0e7df3be6592867d0ac2713a4dbda8350', + axlUSDC: '0xccf932cd565c21d2e516c8ff3a4f244eea27e09a', }, [Network.BSC]: { DAI: '0xf68a4b64162906eff0ff6ae34e2bb1cd42fef62d', @@ -1009,6 +1191,11 @@ export const Holders: { anyBTC: '0x4ffef8e8a75c20ab0ddf96c50d2457277d27923c', nUSD: '0x28ec0b36f0819ecb5005cab836f4ed5a2eca4d13', axlUSD: '0xc03fbeda9069b22a120ae6a09349a0b5eea5570a', + FRAX: '0xEB4576fE753DAB07635c0Bb6c8f0A355e1Db5d31', + frxETH: '0xf324adC872005197A6f7DAE214d3b63aa0C3625F', + USDFI: '0x2E00D722e091836B39Db3e4dcE6eE51c90c5B221', + SDEX: '0xB0470cF15B22a6A32c49a7C20E3821B944A76058', + BNBx: '0xFF4606bd3884554CDbDabd9B6e25E2faD4f6fc54', }, [Network.AVALANCHE]: { AVAX: '0xD6216fC19DB775Df9774a6E33526131dA7D19a2c', @@ -1018,8 +1205,8 @@ export const Holders: { BETS: '0x8cc2284c90d05578633418f9cde104f402375a65', HATCHY: '0x14ec295ec8def851ec6e2959df872dd24e422631', USDCe: '0x3a2434c698f8d79af1f5a9e43013157ca8b11a66', - USDC: '0xbf14db80d9275fb721383a77c00ae180fc40ae98', - USDTe: '0x84d34f4f83a87596cd3fb6887cff8f17bf5a7b83', + USDC: '0x0d0707963952f2fba59dd06f2b425ace40b492fe', + USDTe: '0x693b75eeD71dFA1BE188Fdb53472c8fC51c5A0A0', WETHe: '0xD291B51f7a1a1F4917D085F2a7731A447E4aF82D', POPS: '0x5268c2331658cb0b2858cfa9db27d8f22f5434bc', ETH: '0x9852e84b5AA485683d8AeE7B0332e42442763b75', @@ -1034,23 +1221,27 @@ export const Holders: { TSD: '0x691A89db352B72dDb249bFe16503494eC0D920A4', THO: '0xc40d16c47394a506d451475c8a7c46c1175c1da1', aAvaUSDT: '0x50B1Ba98Cf117c9682048D56628B294ebbAA4ec2', - USDT: '0x4aeFa39caEAdD662aE31ab0CE7c8C2c9c0a013E8', + USDT: '0x0d0707963952f2fba59dd06f2b425ace40b492fe', aAvaWAVAX: '0x1B18Df70863636AEe4BfBAb6F7C70ceBCA9bA404', oldFRAX: '0x4e3376018add04ebe4c46bf6f924ddec8c67aa7b', newFRAX: '0x4e3376018add04ebe4c46bf6f924ddec8c67aa7b', nETH: '0xcf2ef00e75558512ae735679ea5df62ad2056786', avWETH: '0x92d78e32b990d10aeca0875dc5585f1a6f958179', YUSD: '0x6c1a5ef2acde1fd2fc68def440d2c1eb35bae24a', + BTCb: '0x84c06d3c27821d0136f66306f5028d43ceac268d', + AMPL: '0xfcaA5ea7F8eb0631BcA72C345025C0A5a6D93f0E', }, [Network.ARBITRUM]: { + SEN: '0xcb19b6b4971bd4206bab176c75b1efe3e28ee5a8', + RDPX: '0x2fa6f21ecfe274f594f470c376f5bdd061e08a37', ARB: '0xb65edba80a3d81903ecd499c8eb9cf0e19096bd0', ETH: '0xF977814e90dA44bFA03b6295A0616a897441aceC', DAI: '0x07d7f291e731a41d3f0ea4f1ae5b6d920ffb3fe0', WETH: '0xc31e54c7a869b9fcbecc14363cf510d1c41fa443', USDCe: '0x62383739d68dd0f844103db8dfb05a7eded5bbe6', - USDC: '0xa843392198862f98d17e3aa1421b08f2c2020cff', + USDC: '0xb38e8c17e38363af6ebdcb3dae12e0243582891d', OHM: '0xebce5f29ff5ca9aa330ebdf7ec6b5f474bff271e', - USDT: '0x62383739d68dd0f844103db8dfb05a7eded5bbe6', + USDT: '0xf977814e90da44bfa03b6295a0616a897441acec', POPS: '0x4b78b52e7de4d8b7d367297cb8a87c1875a9d591', FRAX: '0x59bf0545fca0e5ad48e13da269facd2e8c886ba4', nUSD: '0x9dd329f5411466d9e0c488ff72519ca9fef0cb40', @@ -1065,14 +1256,16 @@ export const Holders: { ZYB: '0x3ec0eddcd1e25025077327886a78133589082fb2', WBTC: '0xd9d611c6943585bc0e18e51034af8fa28778f7da', RDNT: '0x62383739d68dd0f844103db8dfb05a7eded5bbe6', + SDEX: '0xb0470cf15b22a6a32c49a7c20e3821b944a76058', + wstETH: '0x916792f7734089470de27297903bed8a4630b26d', }, [Network.OPTIMISM]: { ETH: '0x9ef21bE1C270AA1c3c3d750F458442397fBFFCB6', DAI: '0x1337bedc9d22ecbe766df105c9623922a27963ec', WETH: '0x68F5C0A2DE713a54991E01858Fd27a3832401849', POPS: '0x3cbd9044aaabef08ce93a68448e093cff405ad76', - USDC: '0xEBb8EA128BbdFf9a1780A4902A9380022371d466', - USDT: '0xEBb8EA128BbdFf9a1780A4902A9380022371d466', + USDC: '0xdecc0c09c3b5f6e92ef4184125d5648a66e35298', + USDT: '0xf977814e90da44bfa03b6295a0616a897441acec', OP: '0xEBb8EA128BbdFf9a1780A4902A9380022371d466', aOptWETH: '0x7B7D80C40415F744864f051B806b466e2fbB8E68', aOptUSDC: '0x8c0Fcf914E90fF5d7f2D02c1576BF4245FaD2B7F', @@ -1082,6 +1275,26 @@ export const Holders: { wstETH: '0xf7626459234e9249808a06aa08dc6b67c8e0a2fc', rETH: '0x4c2e69e58b14de9afedfb94319519ce34e087283', WBTC: '0xb9c8f0d3254007ee4b98970b94544e473cd610ec', + frxETH: '0x4d4edf8291d169f975b99914b6ab3326abb45938', + }, + [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: '0xcc46564eb2063b60cd457da49d09dca9544dfeae', + DAI: '0x20f03e26968b179025f65c1f4afadfd3959c8d03', + BAL: '0x854b004700885a61107b458f11ecc169a019b764', + GOLD: '0x1374c25b3710758c326ee0c70ec48b595d5ccf8c', + SDEX: '0xa5d378c05192e3f1f365d6298921879c4d51c5a3', }, }; @@ -1107,4 +1320,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..7b5ba0a32 100644 --- a/tests/tenderly-simulation.ts +++ b/tests/tenderly-simulation.ts @@ -1,5 +1,7 @@ -import axios from 'axios'; +/* eslint-disable no-console */ +import { Provider } from '@ethersproject/providers'; import { Address } from '@paraswap/core'; +import axios from 'axios'; 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; @@ -31,6 +76,7 @@ export class TenderlySimulation { } try { + await process.nextTick(() => {}); // https://stackoverflow.com/questions/69169492/async-external-function-leaves-open-handles-jest-supertest-express let res = await axios.post( `https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_ID}/project/${TENDERLY_PROJECT}/fork`, { @@ -64,6 +110,7 @@ export class TenderlySimulation { }; try { if (stateOverrides) { + await process.nextTick(() => {}); // https://stackoverflow.com/questions/69169492/async-external-function-leaves-open-handles-jest-supertest-express const result = await axios.post( ` https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_ID}/project/${TENDERLY_PROJECT}/contracts/encode-states`, @@ -88,6 +135,7 @@ export class TenderlySimulation { ); } + await process.nextTick(() => {}); // https://stackoverflow.com/questions/69169492/async-external-function-leaves-open-handles-jest-supertest-express const { data } = await axios.post( `https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT_ID}/project/${TENDERLY_PROJECT}/fork/${this.forkId}/simulate`, _params, @@ -104,13 +152,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 +166,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..ba76b010a 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,13 +28,14 @@ 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'; import { GIFTER_ADDRESS } from './constants-e2e'; import { generateDeployBytecode, sleep } from './utils'; import { assert } from 'ts-essentials'; +import * as util from 'util'; export const testingEndpoint = process.env.E2E_TEST_ENDPOINT; @@ -80,8 +85,9 @@ const MULTISIG: { [nid: number]: string } = { [Network.POLYGON]: '0x46DF4eb6f7A3B0AdF526f6955b15d3fE02c618b7', [Network.FANTOM]: '0xECaB2dac955b94e49Ec09D6d68672d3B397BbdAd', [Network.AVALANCHE]: '0x1e2ECA5e812D08D2A7F8664D69035163ff5BfEC2', - [Network.OPTIMISM]: '0xf01121e808F782d7F34E857c27dA31AD1f151b39', + [Network.OPTIMISM]: '0x3b28A6f6291f7e8277751f2911Ac49C585d049f6', [Network.ARBITRUM]: '0x90DfD8a6454CFE19be39EaB42ac93CD850c7f339', + [Network.BASE]: '0x6C674c8Df1aC663b822c4B6A56B4E5e889379AE0', }; class APIParaswapSDK implements IParaSwapSDK { @@ -268,16 +274,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 +300,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 +320,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 +395,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 +625,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 { diff --git a/yarn.lock b/yarn.lock index eb77e24a1..6bef98c33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -41,6 +41,11 @@ js-sha3 "^0.7.0" lodash "^4.17.11" +"@adraffy/ens-normalize@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" + integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== + "@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -336,6 +341,11 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@bgd-labs/aave-address-book@2.21.1": + version "2.21.1" + resolved "https://registry.yarnpkg.com/@bgd-labs/aave-address-book/-/aave-address-book-2.21.1.tgz#004aa244d715d785079029f6b61d5ece6bcff563" + integrity sha512-q22AThlSRgEgRkwWiK1ts13oO3epZkARmdJzhezv6Fv3PNa5M95uoqABjNldWmpUyadhy2UtujGzXdOQ6MSS9Q== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -797,6 +807,20 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@hashflow/contracts-evm@2.0.10": + version "2.0.10" + resolved "https://registry.yarnpkg.com/@hashflow/contracts-evm/-/contracts-evm-2.0.10.tgz#60babe34e2685b055dec56307167550993f4f0aa" + integrity sha512-Hxvd9cOn21klYHZhCFGdKaVGawaeOPo5ej1BVersjP9/HBFw+8O/lYqrmcykth69kHSpzf3tUBnsm7ef9Yx9Hg== + dependencies: + "@openzeppelin/contracts" "4.9.2" + +"@hashflow/contracts-evm@2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@hashflow/contracts-evm/-/contracts-evm-2.0.8.tgz#89177de2758e0ee1a2dc72a01ae81423f794e36f" + integrity sha512-TZznNsLShvVcTblU0nVgVWVs4dg7Ao0CJF0N02dkdGeRvLZqrcyIeDk7vn+ulcYS8rKHP+BplszVlDelGoCEVg== + dependencies: + "@openzeppelin/contracts" "4.9.2" + "@hashflow/governance@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@hashflow/governance/-/governance-1.0.4.tgz#6ef8805226652e59417bbdf4ec1421c5d2335775" @@ -804,11 +828,25 @@ dependencies: "@openzeppelin/contracts" "^4.7.3" +"@hashflow/hashverse-contracts-evm@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@hashflow/hashverse-contracts-evm/-/hashverse-contracts-evm-1.0.0.tgz#2689a2dfc576a88a70f244a839f1d76bcf198520" + integrity sha512-ZFSdSFF9XvfWCCkJGzimYJFsXFhD0uyz5nOCnjSk7dX3o3lncUvTWTWa3XZfzcp6WfPmIoEmOY4OIf8+xOA/AA== + dependencies: + "@hashflow/contracts-evm" "2.0.8" + "@openzeppelin/contracts" "4.9.2" + "@openzeppelin/contracts-upgradeable" "4.9.2" + "@hashflow/hashverse-contracts@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@hashflow/hashverse-contracts/-/hashverse-contracts-1.0.2.tgz#f1eec8f8796ef8c79af5388273cae076c0d9fc0c" integrity sha512-u3hkcrNCuVW9oCkfn7Em9FDHutGpKzXjQyyGVdcGbKPDqm7e0QCnfHLpqOMDlQhp7sDByo8gHDoaHRYhjnKOlg== +"@hashflow/hashverse-contracts@1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@hashflow/hashverse-contracts/-/hashverse-contracts-1.3.1.tgz#7d769f4cb46398dc2cb0fadfb40e5305319eb1ce" + integrity sha512-kYUNsRnSzr12XiHYrXfPw+9mvlygl+BUFXwPAfymEnvC8igiN/2EOHjkFvgbtZq8A0COgZYibL69FRQICDs2JQ== + "@hashflow/protocol@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@hashflow/protocol/-/protocol-1.0.3.tgz#59e1620941767ae1919eadaf4c8542b4fac0a03a" @@ -817,7 +855,7 @@ "@openzeppelin/contracts" "^4.7.3" "@openzeppelin/contracts-upgradeable" "^4.7.3" -"@hashflow/sdk@1.2.4", "@hashflow/sdk@^1.0.1": +"@hashflow/sdk@1.2.4": version "1.2.4" resolved "https://registry.yarnpkg.com/@hashflow/sdk/-/sdk-1.2.4.tgz#edf2d7208d04e6bee1bf69aeaa7df68156db30b1" integrity sha512-gEhRudo8e3x3lwzdsfZfhXQHE0C63sH8tcHIT2K/L6sEPUkIz7jbTkfQFaiDVcpGjvUDlCKCEjqGHWE8btnk7Q== @@ -830,14 +868,25 @@ "@hashflow/hashverse-contracts" "1.0.2" "@hashflow/protocol" "1.0.3" -"@hashflow/taker-js@0.0.2": - version "0.0.2" - resolved "https://registry.yarnpkg.com/@hashflow/taker-js/-/taker-js-0.0.2.tgz#cadcd396ab29ce2fab1cb94f8a2b6f9bc8658c54" - integrity sha512-3Yly8Eglvs06OpVbnBcNDBfPSmkEiJRSA8HsBwbPrfppagFOnzLCs6Z0U4A8Ztdnic4rbWVokb7r5YEJ0TxgWg== +"@hashflow/sdk@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@hashflow/sdk/-/sdk-2.2.1.tgz#a805879791300d7b8afc16430d9faeab8e97a1bb" + integrity sha512-qXQrkEIaU98woAIA/pkvWE4UC8+5PT+S3ISwjenlL0uCrFEDMvYQX8k8Qep8IO2bD7B2OaRU6g2IMCvdaLDa8A== + dependencies: + "@hashflow/contracts-evm" "2.0.10" + "@hashflow/governance" "1.0.4" + "@hashflow/hashverse-contracts" "1.3.1" + "@hashflow/hashverse-contracts-evm" "1.0.0" + "@hashflow/protocol" "1.0.3" + +"@hashflow/taker-js@0.3.4": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@hashflow/taker-js/-/taker-js-0.3.4.tgz#254a3ca5e14bce00c920ffe4cdb3f743020c6b8f" + integrity sha512-/lJmW/gZndUZNyBv1sZR/g+H8VqsZDOQ4QsbUL93RrBeAHBd00nLXyr2Wgg5FlpZzTyMz8ea5+3BDB6K/0Xi2w== dependencies: - "@hashflow/sdk" "^1.0.1" + "@hashflow/sdk" "^2.2.0" axios "^0.27.2" - ethers "^5.7.0" + ethers "^6.7.0" "@humanwhocodes/config-array@^0.11.8": version "0.11.8" @@ -1136,11 +1185,23 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + "@noble/hashes@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.2.tgz#e9e035b9b166ca0af657a7848eb2718f0f22f183" integrity sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA== +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/hashes@~1.1.1": version "1.1.5" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.5.tgz#1a0377f3b9020efe2fae03290bd2a12140c95c11" @@ -1375,6 +1436,11 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.7.3.tgz#f1d606e2827d409053f3e908ba4eb8adb1dd6995" integrity sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A== +"@openzeppelin/contracts-upgradeable@4.9.2": + version "4.9.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.2.tgz#a817c75688f8daede420052fbcb34e52482e769e" + integrity sha512-siviV3PZV/fHfPaoIC51rf1Jb6iElkYWnNYZ0leO23/ukXuvOyoC/ahy8jqiV7g+++9Nuo3n/rk5ajSN/+d/Sg== + "@openzeppelin/contracts-upgradeable@^4.7.3": version "4.8.1" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.1.tgz#363f7dd08f25f8f77e16d374350c3d6b43340a7a" @@ -1385,6 +1451,11 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e" integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw== +"@openzeppelin/contracts@4.9.2": + version "4.9.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.2.tgz#1cb2d5e4d3360141a17dbc45094a8cad6aac16c1" + integrity sha512-mO+y6JaqXjWeMh9glYVzVu8HYPGknAAnWyxTRhGeckOruyXQMNnlcW6w/Dx9ftLeIQk6N+ZJFuVmTwF7lEIFrg== + "@openzeppelin/contracts@^4.7.3": version "4.8.1" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.1.tgz#709cfc4bbb3ca9f4460d60101f15dac6b7a2d5e4" @@ -1395,6 +1466,11 @@ resolved "https://registry.yarnpkg.com/@paraswap/core/-/core-1.1.0.tgz#5ec7415be69dc657a9d82b0fde4e20f632cca1b6" integrity sha512-ecnX8ezlhYWFwolZxYEz+K+RfLr8xaxQqiJKlxJ8Yf00tXTGxDGn6/Acy00t4+9Kv0apewd7++J33eJt9yNfwg== +"@paraswap/core@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@paraswap/core/-/core-2.0.0.tgz#be62161abec67db3cef22498a1631b12aa2d81e1" + integrity sha512-zhXSjX2dbYaRxYZt9LhzOCW/qeP0CLyvaFU7e8MkWahu/IAhyvKc/45/STDgNgTjPw1MHjNHbmi5a5pVqXyXBg== + "@paraswap/sdk@6.2.1": version "6.2.1" resolved "https://registry.yarnpkg.com/@paraswap/sdk/-/sdk-6.2.1.tgz#cbac091ae1a8f080edd64234465ef127fdbd21f6" @@ -1763,6 +1839,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.3.tgz#78a6d7ec962b596fc2d2ec102c4dd3ef073fea6a" integrity sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A== +"@types/node@18.15.13": + version "18.15.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" + integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== + "@types/node@^12.12.6": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" @@ -2016,6 +2097,11 @@ aes-js@3.0.0: resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -3799,7 +3885,7 @@ ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.5: ethereum-cryptography "^0.1.3" rlp "^2.2.4" -ethers@^5.6.5, ethers@^5.7.0: +ethers@^5.7.0, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -3835,6 +3921,19 @@ ethers@^5.6.5, ethers@^5.7.0: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" +ethers@^6.7.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.9.0.tgz#a4534bdcdfde306aee94ef32f3d5c70d7e33fcb9" + integrity sha512-pmfNyQzc2mseLe91FnT2vmNaTt8dDzhxZ/xItAV7uGsF4dI4ek2ufMu3rAkgQETL/TIs0GS5A+U05g9QyWnv3Q== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "18.15.13" + aes-js "4.0.0-beta.5" + tslib "2.4.0" + ws "8.5.0" + ethers@~4.0.4: version "4.0.49" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894" @@ -7354,16 +7453,16 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@2.4.0, tslib@^2.0.3: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + tslib@^1.8.1, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.3: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== - tsort@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/tsort/-/tsort-0.0.1.tgz#e2280f5e817f8bf4275657fd0f9aebd44f5a2786" @@ -7993,6 +8092,11 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + ws@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"