From 7d1177d01c9c235abc164801652b0b6b715a801b Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Wed, 27 Sep 2023 16:55:08 +0400 Subject: [PATCH 01/13] Added cross dex swap support --- package-lock.json | 12 +- package.json | 4 +- src/Unit/Exchange/callGenerators/curve.ts | 27 ++ src/Unit/Exchange/callGenerators/erc20.ts | 35 ++ src/Unit/Exchange/callGenerators/uniswapV2.ts | 56 +++ src/Unit/Exchange/callGenerators/uniswapV3.ts | 92 +++++ src/Unit/Exchange/callGenerators/utils.ts | 101 +++++ src/Unit/Exchange/generateSwapCalldata.ts | 368 ++++++++---------- .../BlockchainService/schemas/infoSchema.ts | 2 +- 9 files changed, 475 insertions(+), 222 deletions(-) create mode 100644 src/Unit/Exchange/callGenerators/curve.ts create mode 100644 src/Unit/Exchange/callGenerators/erc20.ts create mode 100644 src/Unit/Exchange/callGenerators/uniswapV2.ts create mode 100644 src/Unit/Exchange/callGenerators/uniswapV3.ts create mode 100644 src/Unit/Exchange/callGenerators/utils.ts diff --git a/package-lock.json b/package-lock.json index 23406c1..b70a5e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.88", + "version": "0.19.89", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@orionprotocol/sdk", - "version": "0.19.88", + "version": "0.19.89", "hasInstallScript": true, "license": "ISC", "dependencies": { "@babel/runtime": "^7.21.0", "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@orionprotocol/contracts": "1.17.0", + "@orionprotocol/contracts": "1.18.0", "bignumber.js": "^9.1.1", "bson-objectid": "^2.0.4", "buffer": "^6.0.3", @@ -2619,9 +2619,9 @@ } }, "node_modules/@orionprotocol/contracts": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.17.0.tgz", - "integrity": "sha512-Ur61rlBXloIDC98nqDUG3vR/Ql1gSPXJb05SBy4kVyHrriVAx7wpHxnKRhz+VJo1R31j/1g8kA4DaDfBdtqRHA==" + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.18.0.tgz", + "integrity": "sha512-Wo+cjcaM5QOFjtuWt9GVNKxmvUzgbglqRmtX9Bki5btZe1ov+Ib5IXhAooT6+H+3pa5Ko4rvYVi1gKiAvaQJ4Q==" }, "node_modules/@sinclair/typebox": { "version": "0.27.8", diff --git a/package.json b/package.json index da99f7a..72b0b06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.88", + "version": "0.19.89", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", @@ -87,7 +87,7 @@ "@babel/runtime": "^7.21.0", "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@orionprotocol/contracts": "1.17.0", + "@orionprotocol/contracts": "1.18.0", "bignumber.js": "^9.1.1", "bson-objectid": "^2.0.4", "buffer": "^6.0.3", diff --git a/src/Unit/Exchange/callGenerators/curve.ts b/src/Unit/Exchange/callGenerators/curve.ts new file mode 100644 index 0000000..422ef0f --- /dev/null +++ b/src/Unit/Exchange/callGenerators/curve.ts @@ -0,0 +1,27 @@ +import { SwapExecutor__factory, CurveRegistry__factory } from "@orionprotocol/contracts/lib/ethers-v5/index.js" +import type { BigNumberish, providers } from "ethers" +import type { SwapInfo } from "../generateSwapCalldata.js" + +export async function generateCurveStableSwapCall( + amount: BigNumberish, + to: string, + swap: SwapInfo, + provider: providers.JsonRpcProvider, + curveRegistry: string +) { + const executorInterface = SwapExecutor__factory.createInterface() + const registry = CurveRegistry__factory.connect(curveRegistry, provider) + const { pool, assetIn, assetOut } = swap + const [i, j,] = await registry.get_coin_indices(pool, assetIn, assetOut) + + let calldata = executorInterface.encodeFunctionData('curveSwapStableAmountIn', [ + pool, + assetOut, + i, + j, + to, + amount, + ]) + + return calldata +} \ No newline at end of file diff --git a/src/Unit/Exchange/callGenerators/erc20.ts b/src/Unit/Exchange/callGenerators/erc20.ts new file mode 100644 index 0000000..0bc3085 --- /dev/null +++ b/src/Unit/Exchange/callGenerators/erc20.ts @@ -0,0 +1,35 @@ +import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v5/index.js" +import type { BigNumberish } from "ethers" +import { type CallParams, addCallParams } from "./utils.js" + +export async function generateTransferCall( + token: string, + target: string, + amount: BigNumberish, + callParams?: CallParams +) { + const executorInterface = SwapExecutor__factory.createInterface() + const calldata = executorInterface.encodeFunctionData('safeTransfer', [ + token, + target, + amount + ]) + + return addCallParams(calldata, callParams) +} + +export async function generateApproveCall( + token: string, + target: string, + amount: BigNumberish, + callParams?: CallParams +) { + const executorInterface = SwapExecutor__factory.createInterface() + const calldata = executorInterface.encodeFunctionData('safeApprove', [ + token, + target, + amount + ]) + + return addCallParams(calldata, callParams) +} \ No newline at end of file diff --git a/src/Unit/Exchange/callGenerators/uniswapV2.ts b/src/Unit/Exchange/callGenerators/uniswapV2.ts new file mode 100644 index 0000000..37f57a5 --- /dev/null +++ b/src/Unit/Exchange/callGenerators/uniswapV2.ts @@ -0,0 +1,56 @@ +import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v5/index.js" +import { SafeArray } from "../../../utils/safeGetters.js" +import { BigNumber } from "ethers" +import type { BytesLike, BigNumberish } from "ethers" +import { defaultAbiCoder, concat } from "ethers/lib/utils.js" +import type { SwapInfo } from "../generateSwapCalldata.js" +import { addCallParams, generateCalls } from "./utils.js" + +export async function generateUni2Calls( + path: SafeArray, + recipient: string +) { + const executorInterface = SwapExecutor__factory.createInterface() + const calls: BytesLike[] = [] + if (path.length > 1) { + for (let i = 0; i < path.length - 1; ++i) { + const currentSwap = path.get(i) + const nextSwap = path.get(i + 1) + + const call = await generateUni2Call( + currentSwap.pool, + currentSwap.assetIn, + currentSwap.assetOut, + nextSwap.pool + ) + calls.push(call) + } + } + const lastSwap = path.last(); + const calldata = executorInterface.encodeFunctionData('swapUniV2', [ + lastSwap.pool, + lastSwap.assetIn, + lastSwap.assetOut, + defaultAbiCoder.encode(['uint256'], [concat(['0x03', recipient])]), + ]) + calls.push(addCallParams(calldata)) + + return generateCalls(calls) +} + +export async function generateUni2Call( + pool: string, + assetIn: string, + assetOut: string, + recipient: string, + fee: BigNumberish = BigNumber.from(3), +) { + const executorInterface = SwapExecutor__factory.createInterface() + const calldata = executorInterface.encodeFunctionData('swapUniV2', [ + pool, + assetIn, + assetOut, + defaultAbiCoder.encode(['uint256'], [concat([BigNumber.from(fee).toHexString(), recipient])]), + ]) + return addCallParams(calldata) +} \ No newline at end of file diff --git a/src/Unit/Exchange/callGenerators/uniswapV3.ts b/src/Unit/Exchange/callGenerators/uniswapV3.ts new file mode 100644 index 0000000..18abb49 --- /dev/null +++ b/src/Unit/Exchange/callGenerators/uniswapV3.ts @@ -0,0 +1,92 @@ +import { SwapExecutor__factory, UniswapV3Pool__factory } from "@orionprotocol/contracts/lib/ethers-v5/index.js" +import { type BigNumberish, providers, type BytesLike, ethers } from "ethers" +import { SafeArray } from "../../../utils/safeGetters.js" +import type { SwapInfo } from "../generateSwapCalldata.js" +import { addCallParams, generateCalls } from "./utils.js" + +export async function generateUni3Call( + swap: SwapInfo, + amount: BigNumberish | undefined, + recipient: string, + provider: providers.JsonRpcProvider +) { + if (typeof amount === 'undefined') amount = 0 + + const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider) + const executorInterface = SwapExecutor__factory.createInterface() + let calldata = executorInterface.encodeFunctionData('uniswapV3SingleSwapTo', [encodedPool, recipient, amount]) + + return calldata +} + +export async function generateOrion3Call( + swap: SwapInfo, + amount: BigNumberish | undefined, + recipient: string, + provider: providers.JsonRpcProvider +) { + if (typeof amount === 'undefined') amount = 0 + + const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider) + const executorInterface = SwapExecutor__factory.createInterface() + let calldata = executorInterface.encodeFunctionData('orionV3SingleSwapTo', [encodedPool, recipient, amount]) + + return calldata +} + +export async function generateUni3Calls( + path: SafeArray, + amount: BigNumberish, + recipient: string, + provider: providers.JsonRpcProvider +) { + const encodedPools: BytesLike[] = [] + for (const swap of path) { + const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider) + encodedPools.push(encodedPool) + } + const executorInterface = SwapExecutor__factory.createInterface() + let calldata = executorInterface.encodeFunctionData('uniswapV3SwapTo', [encodedPools, recipient, amount]) + calldata = addCallParams(calldata) + + return generateCalls([calldata]) +} + +export async function generateOrion3Calls( + path: SafeArray, + amount: BigNumberish, + recipient: string, + provider: providers.JsonRpcProvider +) { + const encodedPools: BytesLike[] = [] + for (const swap of path) { + const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider) + encodedPools.push(encodedPool) + } + const executorInterface = SwapExecutor__factory.createInterface() + let calldata = executorInterface.encodeFunctionData('orionV3SwapTo', [encodedPools, recipient, amount]) + calldata = addCallParams(calldata) + + return generateCalls([calldata]) +} + +export async function encodePoolV3( + poolAddress: string, + assetInAddress: string, + assetOutAddress: string, + provider: providers.JsonRpcProvider +) { + const pool = UniswapV3Pool__factory.connect(poolAddress, provider) + const token0 = await pool.token0() + const zeroForOne = token0.toLowerCase() === assetInAddress.toLowerCase() + const unwrapWETH = assetOutAddress === ethers.constants.AddressZero + + let encodedPool = ethers.utils.solidityPack(['uint256'], [pool.address]) + encodedPool = ethers.utils.hexDataSlice(encodedPool, 1) + let firstByte = 0 + if (unwrapWETH) firstByte += 32 + if (!zeroForOne) firstByte += 128 + const encodedFirstByte = ethers.utils.solidityPack(['uint8'], [firstByte]) + encodedPool = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, encodedPool])) + return encodedPool +} \ No newline at end of file diff --git a/src/Unit/Exchange/callGenerators/utils.ts b/src/Unit/Exchange/callGenerators/utils.ts new file mode 100644 index 0000000..955d2a5 --- /dev/null +++ b/src/Unit/Exchange/callGenerators/utils.ts @@ -0,0 +1,101 @@ +import type { PromiseOrValue } from "@orionprotocol/contracts/lib/ethers-v5/common.js" +import { ERC20__factory, SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v5/index.js" +import { type BytesLike, ethers, BigNumber, type BigNumberish, providers } from "ethers" + +const EXECUTOR_SWAP_FUNCTION = 'func_70LYiww' + +export type CallParams = { + isMandatory?: boolean, + target?: string, + gaslimit?: BigNumber, + value?: BigNumber +} + +export type PatchParams = { + skipOnZeroAmount?: boolean, + skipCallDataPatching?: boolean, + skipValuePatching?: boolean +} + +export function pathCallWithBalance( + calldata: BytesLike, + tokenAddress: string, + patchParams: PatchParams = { skipCallDataPatching: false, skipValuePatching: true } +) { + const executorInterface = SwapExecutor__factory.createInterface() + const skipMaskAndOffset = createPatchMask(calldata, patchParams) + calldata = executorInterface.encodeFunctionData("patchCallWithTokenBalance", [ + calldata, + skipMaskAndOffset, + tokenAddress, + ethers.constants.MaxUint256]) + return addCallParams(calldata) +} + +export function addCallParams( + calldata: BytesLike, + callParams?: CallParams +) { + let firstByte = 0 + if (callParams) { + if (callParams.value !== undefined) { + firstByte += 16 // 00010000 + const encodedValue = ethers.utils.solidityPack(['uint128'], [callParams.value]) + calldata = ethers.utils.hexlify(ethers.utils.concat([encodedValue, calldata])) + } + if (callParams.target !== undefined) { + firstByte += 32 // 00100000 + const encodedAddress = ethers.utils.solidityPack(['address'], [callParams.target]) + calldata = ethers.utils.hexlify(ethers.utils.concat([encodedAddress, calldata])) + } + if (callParams.gaslimit !== undefined) { + firstByte += 64 // 01000000 + const encodedGaslimit = ethers.utils.solidityPack(['uint32'], [callParams.gaslimit]) + calldata = ethers.utils.hexlify(ethers.utils.concat([encodedGaslimit, calldata])) + } + if (callParams.isMandatory !== undefined) firstByte += 128 // 10000000 + } + + const encodedFirstByte = ethers.utils.solidityPack(['uint8'], [firstByte]) + calldata = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, calldata])) + return calldata +} + +export function createPatchMask(calldata: BytesLike, patchParams?: PatchParams) { + let firstByte = 0 + let mask = ethers.utils.solidityPack(["uint256"], [(calldata.length - 4) / 2 - 32]) + mask = ethers.utils.hexDataSlice(mask, 1) + if (patchParams) { + if (patchParams.skipOnZeroAmount !== undefined && patchParams.skipOnZeroAmount === false) { + firstByte += 32 + console.log(firstByte) + } + if (patchParams.skipCallDataPatching !== undefined && patchParams.skipCallDataPatching) { + firstByte += 64 + console.log(firstByte) + } + if (patchParams.skipValuePatching !== undefined && patchParams.skipValuePatching) { + firstByte += 128 + console.log(firstByte) + } + } + const encodedFirstByte = ethers.utils.solidityPack(["uint8"], [firstByte]) + mask = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, mask])) + console.log(mask) + return mask +} + +export function generateCalls(calls: BytesLike[]) { + const executorInterface = SwapExecutor__factory.createInterface() + return '0x' + executorInterface.encodeFunctionData(EXECUTOR_SWAP_FUNCTION, [ethers.constants.AddressZero, calls]).slice(74) +} + +export async function exchangeToNativeDecimals(token: PromiseOrValue, amount: BigNumberish, provider: providers.JsonRpcProvider) { + token = await token + let decimals = 18 + if (token !== ethers.constants.AddressZero) { + const contract = ERC20__factory.connect(token, provider) + decimals = await contract.decimals() + } + return BigNumber.from(amount).mul(BigNumber.from(10).pow(decimals)).div(BigNumber.from(10).pow(8)) +} \ No newline at end of file diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index c781c28..eb512ba 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -1,26 +1,22 @@ import type { ExchangeWithGenericSwap } from '@orionprotocol/contracts/lib/ethers-v5/Exchange.js'; -import { UniswapV3Pool__factory, ERC20__factory, SwapExecutor__factory, CurveRegistry__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js'; -import { BigNumber, ethers, type BigNumberish } from 'ethers'; -import { concat, defaultAbiCoder, type BytesLike } from 'ethers/lib/utils.js'; +import { ERC20__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js'; +import { type BytesLike, ethers, type BigNumberish, providers } from 'ethers'; import { safeGet, SafeArray } from '../../utils/safeGetters.js'; -import type Unit from '../index.js'; import { simpleFetch } from 'simple-typed-fetch'; -import type { PromiseOrValue } from '@orionprotocol/contracts/lib/ethers-v5/common.js'; +import type Unit from '../index.js'; +import { generateUni2Calls, generateUni2Call } from './callGenerators/uniswapV2.js'; +import { generateUni3Calls, generateOrion3Calls, generateUni3Call, generateOrion3Call } from './callGenerators/uniswapV3.js'; +import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from './callGenerators/utils.js'; +import { generateApproveCall, generateTransferCall } from './callGenerators/erc20.js'; +import { generateCurveStableSwapCall } from './callGenerators/curve.js'; -const EXECUTOR_SWAP_FUNCTION = 'func_70LYiww' +export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3" export type SwapInfo = { pool: string assetIn: string assetOut: string - factory: string -} - -export type CallParams = { - isMandatory?: boolean - target?: string - gaslimit?: BigNumber - value?: BigNumber + factory: Factory } export type GenerateSwapCalldataParams = { @@ -35,81 +31,124 @@ export default async function generateSwapCalldata({ amount, minReturnAmount, receiverAddress, - path: path_, + path: arrayLikePath, unit }: GenerateSwapCalldataParams ): Promise<{ calldata: string, swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct }> { - if (path_ == undefined || path_.length == 0) { + if (arrayLikePath == undefined || arrayLikePath.length == 0) { throw new Error('Empty path'); } const wethAddress = safeGet(unit.contracts, 'WETH') const curveRegistryAddress = safeGet(unit.contracts, 'curveRegistry') const { assetToAddress, swapExecutorContractAddress, exchangeContractAddress } = await simpleFetch(unit.blockchainService.getInfo)(); - let path = SafeArray.from(path_).map((swapInfo) => { + let path = SafeArray.from(arrayLikePath).map((swapInfo) => { swapInfo.assetIn = safeGet(assetToAddress, swapInfo.assetIn); swapInfo.assetOut = safeGet(assetToAddress, swapInfo.assetOut); return swapInfo; }) - const factory = path.first().factory - if (!path.every(swapInfo => swapInfo.factory === factory)) { - throw new Error('Supporting only swaps with single factory'); - } - const swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct = { - srcToken: path.first().assetIn, - dstToken: path.last().assetOut, - srcReceiver: swapExecutorContractAddress ?? '', + + const { factory, assetIn: srcToken } = path.first() + const dstToken = path.last().assetOut + + let swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct = { + srcToken: srcToken, + dstToken: dstToken, + srcReceiver: swapExecutorContractAddress, dstReceiver: receiverAddress, amount, minReturnAmount, flags: 0 } + const amountNativeDecimals = await exchangeToNativeDecimals(srcToken, amount, unit.provider); - const exchangeToNativeDecimals = async (token: PromiseOrValue) => { - token = await token - let decimals = 18 - if (token !== ethers.constants.AddressZero) { - const contract = ERC20__factory.connect(token, unit.provider) - decimals = await contract.decimals() - } - return BigNumber.from(amount).mul(BigNumber.from(10).pow(decimals)).div(BigNumber.from(10).pow(8)) - } - const amountNativeDecimals = await exchangeToNativeDecimals(swapDescription.srcToken); - - path = SafeArray.from(path_).map((swapInfo) => { + path = SafeArray.from(arrayLikePath).map((swapInfo) => { if (swapInfo.assetIn == ethers.constants.AddressZero) swapInfo.assetIn = wethAddress if (swapInfo.assetOut == ethers.constants.AddressZero) swapInfo.assetOut = wethAddress return swapInfo; }); + const isSingleFactorySwap = path.every(swapInfo => swapInfo.factory === factory) + let calldata: BytesLike + if (isSingleFactorySwap) { + ({ swapDescription, calldata } = await processSingleFactorySwaps( + factory, + swapDescription, + path, + exchangeContractAddress, + amountNativeDecimals, + swapExecutorContractAddress, + curveRegistryAddress, + unit.provider + )) + } else { + ({ swapDescription, calldata } = await processMultiFactorySwaps( + swapDescription, + path, + exchangeContractAddress, + amountNativeDecimals, + swapExecutorContractAddress, + curveRegistryAddress, + unit.provider + )) + } - let calldata: string + return { swapDescription, calldata } +} + +async function processSingleFactorySwaps( + factory: Factory, + swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct, + path: SafeArray, + recipient: string, + amount: BigNumberish, + swapExecutorContractAddress: string, + curveRegistryAddress: string, + provider: providers.JsonRpcProvider +) { + let calldata: BytesLike switch (factory) { case 'OrionV2': { swapDescription.srcReceiver = path.first().pool - calldata = await generateUni2Calls(exchangeContractAddress, path); + calldata = await generateUni2Calls(path, recipient); break; } case 'UniswapV2': { swapDescription.srcReceiver = path.first().pool - calldata = await generateUni2Calls(exchangeContractAddress, path); + calldata = await generateUni2Calls(path, recipient); break; } case 'UniswapV3': { - calldata = await generateUni3Calls(amountNativeDecimals, exchangeContractAddress, path, unit.provider) + calldata = await generateUni3Calls(path, amount, recipient, provider) break; } case 'OrionV3': { - calldata = await generateOrion3Calls(amountNativeDecimals, exchangeContractAddress, path, unit.provider) + calldata = await generateOrion3Calls(path, amount, recipient, provider) break; } case 'Curve': { - calldata = await generateCurveStableSwapCalls( - amountNativeDecimals, - exchangeContractAddress, - swapExecutorContractAddress ?? '', - path, - unit.provider, + if (path.length > 1) { + throw new Error('Supporting only single stable swap on curve') + } + const { pool, assetIn } = path.first() + const firstToken = ERC20__factory.connect(assetIn, provider) + const executorAllowance = await firstToken.allowance(swapExecutorContractAddress, pool) + const calls: BytesLike[] = [] + if (executorAllowance.lt(amount)) { + const approveCall = await generateApproveCall( + assetIn, + pool, + ethers.constants.MaxUint256 + ) + calls.push(approveCall) + } + let curveCall = await generateCurveStableSwapCall( + amount, + recipient, + path.first(), + provider, curveRegistryAddress ); + calls.push(curveCall) + calldata = await generateCalls(calls) break; } default: { @@ -119,174 +158,77 @@ export default async function generateSwapCalldata({ return { swapDescription, calldata } } -export async function generateUni2Calls( - exchangeAddress: string, - path: SafeArray -) { - const executorInterface = SwapExecutor__factory.createInterface() - const calls: BytesLike[] = [] - if (path.length > 1) { - for (let i = 0; i < path.length - 1; ++i) { - const currentSwap = path.get(i) - const nextSwap = path.get(i + 1) - - const calldata = executorInterface.encodeFunctionData('swapUniV2', [ - currentSwap.pool, - currentSwap.assetIn, - currentSwap.assetOut, - defaultAbiCoder.encode(['uint256'], [concat(['0x03', nextSwap.pool])]), - ] - ) - calls.push(addCallParams(calldata)) - } - } - const lastSwap = path.last(); - const calldata = executorInterface.encodeFunctionData('swapUniV2', [ - lastSwap.pool, - lastSwap.assetIn, - lastSwap.assetOut, - defaultAbiCoder.encode(['uint256'], [concat(['0x03', exchangeAddress])]), - ]) - calls.push(addCallParams(calldata)) - - return await generateCalls(calls) -} - -async function generateUni3Calls( - amount: BigNumberish, - exchangeContractAddress: string, +async function processMultiFactorySwaps( + swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct, path: SafeArray, - provider: ethers.providers.JsonRpcProvider + recipient: string, + amount: BigNumberish, + swapExecutorContractAddress: string, + curveRegistryAddress: string, + provider: providers.JsonRpcProvider ) { - const encodedPools: BytesLike[] = [] + let calls: BytesLike[] = [] for (const swap of path) { - const pool = UniswapV3Pool__factory.connect(swap.pool, provider) - const token0 = await pool.token0() - const zeroForOne = token0.toLowerCase() === swap.assetIn.toLowerCase() - const unwrapWETH = swap.assetOut === ethers.constants.AddressZero - - let encodedPool = ethers.utils.solidityPack(['uint256'], [pool.address]) - encodedPool = ethers.utils.hexDataSlice(encodedPool, 1) - let firstByte = 0 - if (unwrapWETH) firstByte += 32 - if (!zeroForOne) firstByte += 128 - const encodedFirstByte = ethers.utils.solidityPack(['uint8'], [firstByte]) - encodedPool = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, encodedPool])) - encodedPools.push(encodedPool) - } - const executorInterface = SwapExecutor__factory.createInterface() - let calldata = executorInterface.encodeFunctionData('uniswapV3SwapTo', [encodedPools, exchangeContractAddress, amount]) - calldata = addCallParams(calldata) - - return await generateCalls([calldata]) -} - -async function generateOrion3Calls( - amount: BigNumberish, - exchangeContractAddress: string, - path: SafeArray, - provider: ethers.providers.JsonRpcProvider -) { - const encodedPools: BytesLike[] = [] - for (const swap of path) { - const pool = UniswapV3Pool__factory.connect(swap.pool, provider) - const token0 = await pool.token0() - const zeroForOne = token0.toLowerCase() === swap.assetIn.toLowerCase() - const unwrapWETH = swap.assetOut === ethers.constants.AddressZero - - let encodedPool = ethers.utils.solidityPack(['uint256'], [pool.address]) - encodedPool = ethers.utils.hexDataSlice(encodedPool, 1) - let firstByte = 0 - if (unwrapWETH) firstByte += 32 - if (!zeroForOne) firstByte += 128 - const encodedFirstByte = ethers.utils.solidityPack(['uint8'], [firstByte]) - encodedPool = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, encodedPool])) - encodedPools.push(encodedPool) - } - const executorInterface = SwapExecutor__factory.createInterface() - let calldata = executorInterface.encodeFunctionData('orionV3SwapTo', [encodedPools, exchangeContractAddress, amount]) - calldata = addCallParams(calldata) - - return await generateCalls([calldata]) -} - -async function generateCurveStableSwapCalls( - amount: BigNumberish, - exchangeContractAddress: string, - executorAddress: string, - path: SafeArray, - provider: ethers.providers.JsonRpcProvider, - curveRegistry: string -) { - if (path.length > 1) { - throw new Error('Supporting only single stable swap on curve') - } - const executorInterface = SwapExecutor__factory.createInterface() - const registry = CurveRegistry__factory.connect(curveRegistry, provider) - - const swap = path.first() - const firstToken = ERC20__factory.connect(swap.assetIn, provider) - const { pool, assetIn, assetOut } = swap - const [i, j,] = await registry.get_coin_indices(pool, assetIn, assetOut) - - const executorAllowance = await firstToken.allowance(executorAddress, swap.pool) - const calls: BytesLike[] = [] - if (executorAllowance.lt(amount)) { - const calldata = addCallParams( - executorInterface.encodeFunctionData('safeApprove', [ - swap.assetIn, - swap.pool, - ethers.constants.MaxUint256 - ]) - ) - calls.push(calldata) - } - let calldata = executorInterface.encodeFunctionData('curveSwapStableAmountIn', [ - pool, - assetOut, - i, - j, - amount, - 0, - exchangeContractAddress]) - - calldata = addCallParams(calldata) - calls.push(calldata) - - return await generateCalls(calls) -} - -// Adds additional byte to single swap with settings -function addCallParams( - calldata: BytesLike, - callParams?: CallParams -) { - let firstByte = 0 - if (callParams) { - if (callParams.value !== undefined) { - firstByte += 16 // 00010000 - const encodedValue = ethers.utils.solidityPack(['uint128'], [callParams.value]) - calldata = ethers.utils.hexlify(ethers.utils.concat([encodedValue, calldata])) + switch (swap.factory) { + case 'OrionV2': { + let transferCall = await generateTransferCall(swap.assetIn, swap.pool, 0) + transferCall = pathCallWithBalance(transferCall, swap.assetIn) + const uni2Call = await generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress) + calls = calls.concat([transferCall, uni2Call]) + break; + } + case 'UniswapV2': { + let transferCall = await generateTransferCall(swap.assetIn, swap.pool, 0) + transferCall = pathCallWithBalance(transferCall, swap.assetIn) + const uni2Call = await generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress) + calls = calls.concat([transferCall, uni2Call]) + break; + } + case 'UniswapV3': { + let uni3Call = await generateUni3Call(swap, 0, swapExecutorContractAddress, provider) + uni3Call = pathCallWithBalance(uni3Call, swap.assetIn) + calls.push(uni3Call) + break; + } + case 'OrionV3': { + let orion3Call = await generateOrion3Call(swap, 0, swapExecutorContractAddress, provider) + orion3Call = pathCallWithBalance(orion3Call, swap.assetIn) + calls.push(orion3Call) + break; + } + case 'Curve': { + const { pool, assetIn } = swap + const firstToken = ERC20__factory.connect(assetIn, provider) + const executorAllowance = await firstToken.allowance(swapExecutorContractAddress, pool) + if (executorAllowance.lt(amount)) { + const approveCall = await generateApproveCall( + assetIn, + pool, + ethers.constants.MaxUint256 + ) + calls.push(approveCall) + } + let curveCall = await generateCurveStableSwapCall( + amount, + swapExecutorContractAddress, + swap, + provider, + curveRegistryAddress + ); + curveCall = pathCallWithBalance(curveCall, swap.assetIn) + calls.push(curveCall) + break; + } + default: { + throw new Error(`Factory ${swap.factory} is not supported`) + } } - if (callParams.target !== undefined) { - firstByte += 32 // 00100000 - const encodedAddress = ethers.utils.solidityPack(['address'], [callParams.target]) - calldata = ethers.utils.hexlify(ethers.utils.concat([encodedAddress, calldata])) - } - if (callParams.gaslimit !== undefined) { - firstByte += 64 // 01000000 - const encodedGaslimit = ethers.utils.solidityPack(['uint32'], [callParams.gaslimit]) - calldata = ethers.utils.hexlify(ethers.utils.concat([encodedGaslimit, calldata])) - } - if (callParams.isMandatory !== undefined) firstByte += 128 // 10000000 } + const dstToken = await swapDescription.dstToken + let finalTransferCall = await generateTransferCall(dstToken, recipient, 0) + finalTransferCall = pathCallWithBalance(finalTransferCall, dstToken) + calls.push(finalTransferCall) + const calldata = await generateCalls(calls) - const encodedFirstByte = ethers.utils.solidityPack(['uint8'], [firstByte]) - calldata = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, calldata])) - return calldata -} - -async function generateCalls(calls: BytesLike[]) { - const executorInterface = SwapExecutor__factory.createInterface() - return '0x' + executorInterface.encodeFunctionData(EXECUTOR_SWAP_FUNCTION, [ethers.constants.AddressZero, calls]).slice(74) -} + return { swapDescription, calldata } +} \ No newline at end of file diff --git a/src/services/BlockchainService/schemas/infoSchema.ts b/src/services/BlockchainService/schemas/infoSchema.ts index 2c9ef32..472a895 100644 --- a/src/services/BlockchainService/schemas/infoSchema.ts +++ b/src/services/BlockchainService/schemas/infoSchema.ts @@ -11,7 +11,7 @@ const infoSchema = z.object({ chainId: z.number(), chainName: z.string(), exchangeContractAddress: z.string(), - swapExecutorContractAddress: z.string().optional(), + swapExecutorContractAddress: z.string(), oracleContractAddress: z.string(), matcherAddress: z.string(), orderFeePercent: z.number(), From 24d2a14f535b8bc48f37cbb8980553734c883713 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Wed, 27 Sep 2023 18:21:35 +0400 Subject: [PATCH 02/13] bump --- package.json | 2 +- src/Unit/Exchange/callGenerators/curve.ts | 3 ++- src/Unit/Exchange/callGenerators/uniswapV3.ts | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 72b0b06..74c9320 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.89", + "version": "0.19.89-rc0", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Unit/Exchange/callGenerators/curve.ts b/src/Unit/Exchange/callGenerators/curve.ts index 422ef0f..5bb68de 100644 --- a/src/Unit/Exchange/callGenerators/curve.ts +++ b/src/Unit/Exchange/callGenerators/curve.ts @@ -1,6 +1,7 @@ import { SwapExecutor__factory, CurveRegistry__factory } from "@orionprotocol/contracts/lib/ethers-v5/index.js" import type { BigNumberish, providers } from "ethers" import type { SwapInfo } from "../generateSwapCalldata.js" +import { addCallParams } from "./utils.js" export async function generateCurveStableSwapCall( amount: BigNumberish, @@ -23,5 +24,5 @@ export async function generateCurveStableSwapCall( amount, ]) - return calldata + return addCallParams(calldata) } \ No newline at end of file diff --git a/src/Unit/Exchange/callGenerators/uniswapV3.ts b/src/Unit/Exchange/callGenerators/uniswapV3.ts index 18abb49..21fadd8 100644 --- a/src/Unit/Exchange/callGenerators/uniswapV3.ts +++ b/src/Unit/Exchange/callGenerators/uniswapV3.ts @@ -16,7 +16,7 @@ export async function generateUni3Call( const executorInterface = SwapExecutor__factory.createInterface() let calldata = executorInterface.encodeFunctionData('uniswapV3SingleSwapTo', [encodedPool, recipient, amount]) - return calldata + return addCallParams(calldata) } export async function generateOrion3Call( @@ -31,7 +31,7 @@ export async function generateOrion3Call( const executorInterface = SwapExecutor__factory.createInterface() let calldata = executorInterface.encodeFunctionData('orionV3SingleSwapTo', [encodedPool, recipient, amount]) - return calldata + return addCallParams(calldata) } export async function generateUni3Calls( From f78266818fe84f7860340a1cc7da5a28309218ae Mon Sep 17 00:00:00 2001 From: Mikhail Gladchenko Date: Thu, 28 Sep 2023 14:50:43 +0100 Subject: [PATCH 03/13] feature: up version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a3b59e..2b47430 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.92-rc0", + "version": "0.19.92-rc100", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", From 1134865abf04b6ad09e6be0a2f1f5895d0e12ec7 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 28 Sep 2023 18:35:59 +0400 Subject: [PATCH 04/13] Renamed type SwapInfo to SingleSwap --- package.json | 2 +- src/Unit/Exchange/generateSwapCalldata.ts | 26 +++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 2b47430..4bb08a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.92-rc100", + "version": "0.19.92-rc101", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index eb512ba..7358acb 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -12,7 +12,7 @@ import { generateCurveStableSwapCall } from './callGenerators/curve.js'; export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3" -export type SwapInfo = { +export type SingleSwap = { pool: string assetIn: string assetOut: string @@ -23,7 +23,7 @@ export type GenerateSwapCalldataParams = { amount: BigNumberish minReturnAmount: BigNumberish receiverAddress: string - path: ArrayLike + path: ArrayLike unit: Unit } @@ -41,10 +41,10 @@ export default async function generateSwapCalldata({ const wethAddress = safeGet(unit.contracts, 'WETH') const curveRegistryAddress = safeGet(unit.contracts, 'curveRegistry') const { assetToAddress, swapExecutorContractAddress, exchangeContractAddress } = await simpleFetch(unit.blockchainService.getInfo)(); - let path = SafeArray.from(arrayLikePath).map((swapInfo) => { - swapInfo.assetIn = safeGet(assetToAddress, swapInfo.assetIn); - swapInfo.assetOut = safeGet(assetToAddress, swapInfo.assetOut); - return swapInfo; + let path = SafeArray.from(arrayLikePath).map((singleSwap) => { + singleSwap.assetIn = safeGet(assetToAddress, singleSwap.assetIn); + singleSwap.assetOut = safeGet(assetToAddress, singleSwap.assetOut); + return singleSwap; }) const { factory, assetIn: srcToken } = path.first() @@ -61,12 +61,12 @@ export default async function generateSwapCalldata({ } const amountNativeDecimals = await exchangeToNativeDecimals(srcToken, amount, unit.provider); - path = SafeArray.from(arrayLikePath).map((swapInfo) => { - if (swapInfo.assetIn == ethers.constants.AddressZero) swapInfo.assetIn = wethAddress - if (swapInfo.assetOut == ethers.constants.AddressZero) swapInfo.assetOut = wethAddress - return swapInfo; + path = SafeArray.from(arrayLikePath).map((singleSwap) => { + if (singleSwap.assetIn == ethers.constants.AddressZero) singleSwap.assetIn = wethAddress + if (singleSwap.assetOut == ethers.constants.AddressZero) singleSwap.assetOut = wethAddress + return singleSwap; }); - const isSingleFactorySwap = path.every(swapInfo => swapInfo.factory === factory) + const isSingleFactorySwap = path.every(singleSwap => singleSwap.factory === factory) let calldata: BytesLike if (isSingleFactorySwap) { ({ swapDescription, calldata } = await processSingleFactorySwaps( @@ -97,7 +97,7 @@ export default async function generateSwapCalldata({ async function processSingleFactorySwaps( factory: Factory, swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct, - path: SafeArray, + path: SafeArray, recipient: string, amount: BigNumberish, swapExecutorContractAddress: string, @@ -160,7 +160,7 @@ async function processSingleFactorySwaps( async function processMultiFactorySwaps( swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct, - path: SafeArray, + path: SafeArray, recipient: string, amount: BigNumberish, swapExecutorContractAddress: string, From 3c183f6cff8a9d43bf864439c42bf8e8843c4309 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 28 Sep 2023 18:45:40 +0400 Subject: [PATCH 05/13] Renamed type SwapInfo to SingleSwap --- src/Unit/Exchange/callGenerators/curve.ts | 4 ++-- src/Unit/Exchange/callGenerators/uniswapV2.ts | 4 ++-- src/Unit/Exchange/callGenerators/uniswapV3.ts | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Unit/Exchange/callGenerators/curve.ts b/src/Unit/Exchange/callGenerators/curve.ts index 5bb68de..42e2123 100644 --- a/src/Unit/Exchange/callGenerators/curve.ts +++ b/src/Unit/Exchange/callGenerators/curve.ts @@ -1,12 +1,12 @@ import { SwapExecutor__factory, CurveRegistry__factory } from "@orionprotocol/contracts/lib/ethers-v5/index.js" import type { BigNumberish, providers } from "ethers" -import type { SwapInfo } from "../generateSwapCalldata.js" +import type { SingleSwap } from "../generateSwapCalldata.js" import { addCallParams } from "./utils.js" export async function generateCurveStableSwapCall( amount: BigNumberish, to: string, - swap: SwapInfo, + swap: SingleSwap, provider: providers.JsonRpcProvider, curveRegistry: string ) { diff --git a/src/Unit/Exchange/callGenerators/uniswapV2.ts b/src/Unit/Exchange/callGenerators/uniswapV2.ts index 37f57a5..bec62de 100644 --- a/src/Unit/Exchange/callGenerators/uniswapV2.ts +++ b/src/Unit/Exchange/callGenerators/uniswapV2.ts @@ -3,11 +3,11 @@ import { SafeArray } from "../../../utils/safeGetters.js" import { BigNumber } from "ethers" import type { BytesLike, BigNumberish } from "ethers" import { defaultAbiCoder, concat } from "ethers/lib/utils.js" -import type { SwapInfo } from "../generateSwapCalldata.js" +import type { SingleSwap } from "../generateSwapCalldata.js" import { addCallParams, generateCalls } from "./utils.js" export async function generateUni2Calls( - path: SafeArray, + path: SafeArray, recipient: string ) { const executorInterface = SwapExecutor__factory.createInterface() diff --git a/src/Unit/Exchange/callGenerators/uniswapV3.ts b/src/Unit/Exchange/callGenerators/uniswapV3.ts index 21fadd8..f18e05a 100644 --- a/src/Unit/Exchange/callGenerators/uniswapV3.ts +++ b/src/Unit/Exchange/callGenerators/uniswapV3.ts @@ -1,11 +1,11 @@ import { SwapExecutor__factory, UniswapV3Pool__factory } from "@orionprotocol/contracts/lib/ethers-v5/index.js" import { type BigNumberish, providers, type BytesLike, ethers } from "ethers" import { SafeArray } from "../../../utils/safeGetters.js" -import type { SwapInfo } from "../generateSwapCalldata.js" +import type { SingleSwap } from "../generateSwapCalldata.js" import { addCallParams, generateCalls } from "./utils.js" export async function generateUni3Call( - swap: SwapInfo, + swap: SingleSwap, amount: BigNumberish | undefined, recipient: string, provider: providers.JsonRpcProvider @@ -20,7 +20,7 @@ export async function generateUni3Call( } export async function generateOrion3Call( - swap: SwapInfo, + swap: SingleSwap, amount: BigNumberish | undefined, recipient: string, provider: providers.JsonRpcProvider @@ -35,7 +35,7 @@ export async function generateOrion3Call( } export async function generateUni3Calls( - path: SafeArray, + path: SafeArray, amount: BigNumberish, recipient: string, provider: providers.JsonRpcProvider @@ -53,7 +53,7 @@ export async function generateUni3Calls( } export async function generateOrion3Calls( - path: SafeArray, + path: SafeArray, amount: BigNumberish, recipient: string, provider: providers.JsonRpcProvider From 823e9a8ad5207ce77c13a9adb370ecab806848cf Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 28 Sep 2023 18:56:25 +0400 Subject: [PATCH 06/13] Added Factory type to ws path --- package.json | 2 +- src/services/Aggregator/ws/index.ts | 3 ++- src/types.ts | 10 ++-------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 4bb08a6..707d1bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.92-rc101", + "version": "0.19.92-rc102", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/services/Aggregator/ws/index.ts b/src/services/Aggregator/ws/index.ts index 00f5ec8..352bb36 100644 --- a/src/services/Aggregator/ws/index.ts +++ b/src/services/Aggregator/ws/index.ts @@ -17,6 +17,7 @@ import unsubscriptionDoneSchema from './schemas/unsubscriptionDoneSchema.js'; import assetPairConfigSchema from './schemas/assetPairConfigSchema.js'; import type { fullOrderSchema, orderUpdateSchema } from './schemas/addressUpdateSchema.js'; import { objectKeys } from '../../../utils/objectKeys.js'; +import type { Factory } from '../../../Unit/Exchange/generateSwapCalldata.js'; // import assertError from '../../../utils/assertError.js'; // import errorSchema from './schemas/errorSchema'; @@ -507,7 +508,7 @@ class AggregatorWS { pool: path.p, assetIn: path.ai, assetOut: path.ao, - factory: path.f, + factory: path.f as Factory, })), poolOptimal: json.po, ...(json.oi) && { diff --git a/src/types.ts b/src/types.ts index 64449e8..d7a7b3f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,6 +4,7 @@ import type subOrderStatuses from './constants/subOrderStatuses.js'; import type positionStatuses from './constants/positionStatuses.js'; import type { knownEnvs } from './config/schemas/index.js'; import type getHistory from './Orion/bridge/getHistory.js'; +import type { SingleSwap } from './Unit/Exchange/generateSwapCalldata.js'; export type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial; @@ -165,13 +166,6 @@ export type SwapInfoAlternative = { availableAmountOut?: number | undefined } -type ExchangeContractPath = { - pool: string - assetIn: string - assetOut: string - factory: string -} - export type SwapInfoBase = { swapRequestId: string assetIn: string @@ -182,7 +176,7 @@ export type SwapInfoBase = { minAmountOut: number path: string[] - exchangeContractPath: ExchangeContractPath[] + exchangeContractPath: SingleSwap[] exchanges?: string[] | undefined poolOptimal: boolean From 21015e921945545cd552d59ffb1e0251593c80f2 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 28 Sep 2023 19:48:12 +0400 Subject: [PATCH 07/13] Added factory schema --- package.json | 2 +- src/Unit/Exchange/callGenerators/curve.ts | 2 +- src/Unit/Exchange/callGenerators/uniswapV2.ts | 2 +- src/Unit/Exchange/callGenerators/uniswapV3.ts | 2 +- src/Unit/Exchange/generateSwapCalldata.ts | 8 ++------ src/constants/factories.ts | 1 + src/services/Aggregator/ws/schemas/swapInfoSchema.ts | 4 +++- src/types.ts | 11 ++++++++++- 8 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 src/constants/factories.ts diff --git a/package.json b/package.json index 707d1bd..242b3be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.92-rc102", + "version": "0.19.92-rc103", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Unit/Exchange/callGenerators/curve.ts b/src/Unit/Exchange/callGenerators/curve.ts index 42e2123..2ce2b0a 100644 --- a/src/Unit/Exchange/callGenerators/curve.ts +++ b/src/Unit/Exchange/callGenerators/curve.ts @@ -1,7 +1,7 @@ import { SwapExecutor__factory, CurveRegistry__factory } from "@orionprotocol/contracts/lib/ethers-v5/index.js" import type { BigNumberish, providers } from "ethers" -import type { SingleSwap } from "../generateSwapCalldata.js" import { addCallParams } from "./utils.js" +import type { SingleSwap } from "../../../types.js" export async function generateCurveStableSwapCall( amount: BigNumberish, diff --git a/src/Unit/Exchange/callGenerators/uniswapV2.ts b/src/Unit/Exchange/callGenerators/uniswapV2.ts index bec62de..fe276d7 100644 --- a/src/Unit/Exchange/callGenerators/uniswapV2.ts +++ b/src/Unit/Exchange/callGenerators/uniswapV2.ts @@ -3,8 +3,8 @@ import { SafeArray } from "../../../utils/safeGetters.js" import { BigNumber } from "ethers" import type { BytesLike, BigNumberish } from "ethers" import { defaultAbiCoder, concat } from "ethers/lib/utils.js" -import type { SingleSwap } from "../generateSwapCalldata.js" import { addCallParams, generateCalls } from "./utils.js" +import type { SingleSwap } from "../../../types.js" export async function generateUni2Calls( path: SafeArray, diff --git a/src/Unit/Exchange/callGenerators/uniswapV3.ts b/src/Unit/Exchange/callGenerators/uniswapV3.ts index f18e05a..2dd8b95 100644 --- a/src/Unit/Exchange/callGenerators/uniswapV3.ts +++ b/src/Unit/Exchange/callGenerators/uniswapV3.ts @@ -1,8 +1,8 @@ import { SwapExecutor__factory, UniswapV3Pool__factory } from "@orionprotocol/contracts/lib/ethers-v5/index.js" import { type BigNumberish, providers, type BytesLike, ethers } from "ethers" import { SafeArray } from "../../../utils/safeGetters.js" -import type { SingleSwap } from "../generateSwapCalldata.js" import { addCallParams, generateCalls } from "./utils.js" +import type { SingleSwap } from "../../../types.js" export async function generateUni3Call( swap: SingleSwap, diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index 7358acb..f0b70a3 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -9,15 +9,11 @@ import { generateUni3Calls, generateOrion3Calls, generateUni3Call, generateOrion import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from './callGenerators/utils.js'; import { generateApproveCall, generateTransferCall } from './callGenerators/erc20.js'; import { generateCurveStableSwapCall } from './callGenerators/curve.js'; +import type { SingleSwap } from '../../types.js'; export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3" -export type SingleSwap = { - pool: string - assetIn: string - assetOut: string - factory: Factory -} + export type GenerateSwapCalldataParams = { amount: BigNumberish diff --git a/src/constants/factories.ts b/src/constants/factories.ts new file mode 100644 index 0000000..200d3e2 --- /dev/null +++ b/src/constants/factories.ts @@ -0,0 +1 @@ +export default ["UniswapV2", "UniswapV3", "Curve", "OrionV2", "OrionV3"] as const diff --git a/src/services/Aggregator/ws/schemas/swapInfoSchema.ts b/src/services/Aggregator/ws/schemas/swapInfoSchema.ts index 288838b..aa79db8 100644 --- a/src/services/Aggregator/ws/schemas/swapInfoSchema.ts +++ b/src/services/Aggregator/ws/schemas/swapInfoSchema.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import MessageType from '../MessageType.js'; import baseMessageSchema from './baseMessageSchema.js'; +import factories from '../../../../constants/factories.js'; const alternativeSchema = z.object({ // execution alternatives e: z.string().array(), // exchanges @@ -11,6 +12,7 @@ const alternativeSchema = z.object({ // execution alternatives aa: z.number().optional(), // available amount in aao: z.number().optional(), // available amount out }); +const factorySchema = z.enum(factories); const swapInfoSchemaBase = baseMessageSchema.extend({ T: z.literal(MessageType.SWAP_INFO), S: z.string(), // swap request id @@ -37,7 +39,7 @@ const swapInfoSchemaBase = baseMessageSchema.extend({ p: z.string(), // pool address ai: z.string().toUpperCase(), // asset in ao: z.string().toUpperCase(), // asset out - f: z.string(), // factory + f: factorySchema, // factory })) }); diff --git a/src/types.ts b/src/types.ts index d7a7b3f..6f3afc4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ +import factories from './constants/factories.js'; import type { BigNumber } from 'bignumber.js'; import type subOrderStatuses from './constants/subOrderStatuses.js'; import type positionStatuses from './constants/positionStatuses.js'; import type { knownEnvs } from './config/schemas/index.js'; import type getHistory from './Orion/bridge/getHistory.js'; -import type { SingleSwap } from './Unit/Exchange/generateSwapCalldata.js'; export type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial; @@ -166,6 +166,15 @@ export type SwapInfoAlternative = { availableAmountOut?: number | undefined } +export type Factory = typeof factories[number] + +export type SingleSwap = { + pool: string + assetIn: string + assetOut: string + factory: Factory +} + export type SwapInfoBase = { swapRequestId: string assetIn: string From cad6b1e943b9f4f27ac7b1d561b04243f8f42464 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 28 Sep 2023 19:49:49 +0400 Subject: [PATCH 08/13] Removed type assertion --- src/services/Aggregator/ws/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/Aggregator/ws/index.ts b/src/services/Aggregator/ws/index.ts index 352bb36..a3e0049 100644 --- a/src/services/Aggregator/ws/index.ts +++ b/src/services/Aggregator/ws/index.ts @@ -508,7 +508,7 @@ class AggregatorWS { pool: path.p, assetIn: path.ai, assetOut: path.ao, - factory: path.f as Factory, + factory: path.f, })), poolOptimal: json.po, ...(json.oi) && { From f35e2ef338a73eb1975b76856ec14c10ea808fe1 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 28 Sep 2023 19:50:18 +0400 Subject: [PATCH 09/13] bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 242b3be..f9b995b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.92-rc103", + "version": "0.19.92-rc104", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", From 2a089b3c4b39220cebc502e32950351aab5937f3 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Thu, 28 Sep 2023 19:57:58 +0400 Subject: [PATCH 10/13] Removed unused import --- src/services/Aggregator/ws/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/services/Aggregator/ws/index.ts b/src/services/Aggregator/ws/index.ts index a3e0049..a50d62b 100644 --- a/src/services/Aggregator/ws/index.ts +++ b/src/services/Aggregator/ws/index.ts @@ -17,9 +17,6 @@ import unsubscriptionDoneSchema from './schemas/unsubscriptionDoneSchema.js'; import assetPairConfigSchema from './schemas/assetPairConfigSchema.js'; import type { fullOrderSchema, orderUpdateSchema } from './schemas/addressUpdateSchema.js'; import { objectKeys } from '../../../utils/objectKeys.js'; -import type { Factory } from '../../../Unit/Exchange/generateSwapCalldata.js'; -// import assertError from '../../../utils/assertError.js'; -// import errorSchema from './schemas/errorSchema'; const UNSUBSCRIBE = 'u'; const SERVER_PING_INTERVAL = 30000; From c45179ceb9ad65130e02ebd420d1345f8db1072b Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Tue, 3 Oct 2023 22:33:11 +0400 Subject: [PATCH 11/13] Export factories array --- package.json | 2 +- src/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f9b995b..6f7421e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.92-rc104", + "version": "0.19.92-rc105", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/index.ts b/src/index.ts index 04f68e7..a23d913 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ BigNumber.config({ EXPONENTIAL_AT: 1e+9 }); export * as config from './config/index.js'; export { default as Unit } from './Unit/index.js'; export { default as Orion } from './Orion/index.js'; +export { default as factories} from './constants/factories.js'; export * as utils from './utils/index.js'; export * as services from './services/index.js'; export * as crypt from './crypt/index.js'; From 20cf407de2d248277b15de6384766aa2691cec85 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Tue, 10 Oct 2023 19:26:28 +0400 Subject: [PATCH 12/13] Minor review fixes --- src/Unit/Exchange/callGenerators/uniswapV3.ts | 4 ++-- src/Unit/Exchange/callGenerators/utils.ts | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Unit/Exchange/callGenerators/uniswapV3.ts b/src/Unit/Exchange/callGenerators/uniswapV3.ts index d600e9d..2ef3f04 100644 --- a/src/Unit/Exchange/callGenerators/uniswapV3.ts +++ b/src/Unit/Exchange/callGenerators/uniswapV3.ts @@ -25,11 +25,11 @@ export async function generateOrion3Call( recipient: string, provider: JsonRpcProvider ) { - if (typeof amount === 'undefined') amount = 0 + if (amount === undefined) amount = 0 const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider) const executorInterface = SwapExecutor__factory.createInterface() - let calldata = executorInterface.encodeFunctionData('orionV3SingleSwapTo', [encodedPool, recipient, amount]) + const calldata = executorInterface.encodeFunctionData('orionV3SingleSwapTo', [encodedPool, recipient, amount]) return addCallParams(calldata) } diff --git a/src/Unit/Exchange/callGenerators/utils.ts b/src/Unit/Exchange/callGenerators/utils.ts index 15a8172..e49217b 100644 --- a/src/Unit/Exchange/callGenerators/utils.ts +++ b/src/Unit/Exchange/callGenerators/utils.ts @@ -63,25 +63,21 @@ export function addCallParams( export function createPatchMask(calldata: BytesLike, patchParams?: PatchParams) { let firstByte = 0 - let mask = ethers.solidityPacked(["uint256"], [(calldata.length - 4) / 2 - 32]) + let mask = ethers.solidityPacked(["uint256"], [(calldata.length - 4) / 2 - 32]) //finding offset of last 32 bytes slot in calldata mask = ethers.dataSlice(mask, 1) if (patchParams) { if (patchParams.skipOnZeroAmount !== undefined && patchParams.skipOnZeroAmount === false) { firstByte += 32 - console.log(firstByte) } if (patchParams.skipCallDataPatching !== undefined && patchParams.skipCallDataPatching) { firstByte += 64 - console.log(firstByte) } if (patchParams.skipValuePatching !== undefined && patchParams.skipValuePatching) { firstByte += 128 - console.log(firstByte) } } const encodedFirstByte = ethers.solidityPacked(["uint8"], [firstByte]) mask = ethers.hexlify(ethers.concat([encodedFirstByte, mask])) - console.log(mask) return mask } From 1b791462de27d84df255ab88a04e47a008f30b92 Mon Sep 17 00:00:00 2001 From: lomonoshka Date: Tue, 10 Oct 2023 19:29:28 +0400 Subject: [PATCH 13/13] bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7aeeb2..15c7cc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.20.6", + "version": "0.20.7-rc1", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js",