From d38d8bd7153c869ca83bc8427f95bbcd152393b7 Mon Sep 17 00:00:00 2001 From: Dmitry <35160421+TheJuze@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:56:34 +0300 Subject: [PATCH] OP-4758: fix generateSwapCallData (#214) --- package-lock.json | 42 ++++++++- package.json | 4 +- src/Unit/Exchange/generateSwapCalldata.ts | 108 +++++++++++----------- src/utils/safeGetters.ts | 20 ++-- 4 files changed, 108 insertions(+), 66 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46493bf..c31e7e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@orionprotocol/sdk", - "version": "0.20.22", + "version": "0.20.23-rc1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@orionprotocol/sdk", - "version": "0.20.22", + "version": "0.20.23-rc1", "hasInstallScript": true, "license": "ISC", "dependencies": { @@ -14,6 +14,7 @@ "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@orionprotocol/contracts": "1.22.3", + "@types/lodash.clonedeep": "^4.5.9", "bignumber.js": "^9.1.1", "bson-objectid": "^2.0.4", "buffer": "^6.0.3", @@ -21,6 +22,7 @@ "express": "^4.18.2", "isomorphic-ws": "^5.0.0", "just-clone": "^6.2.0", + "lodash.clonedeep": "^4.5.0", "merge-anything": "^5.1.7", "neverthrow": "^6.0.0", "patch-package": "^8.0.0", @@ -2651,6 +2653,19 @@ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + }, + "node_modules/@types/lodash.clonedeep": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz", + "integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -8787,6 +8802,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -13692,6 +13712,19 @@ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + }, + "@types/lodash.clonedeep": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz", + "integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==", + "requires": { + "@types/lodash": "*" + } + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -18165,6 +18198,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", diff --git a/package.json b/package.json index 76f3ce0..0d289b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.20.22", + "version": "0.20.23-rc4", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", @@ -89,6 +89,7 @@ "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@orionprotocol/contracts": "1.22.3", + "@types/lodash.clonedeep": "^4.5.9", "bignumber.js": "^9.1.1", "bson-objectid": "^2.0.4", "buffer": "^6.0.3", @@ -96,6 +97,7 @@ "express": "^4.18.2", "isomorphic-ws": "^5.0.0", "just-clone": "^6.2.0", + "lodash.clonedeep": "^4.5.0", "merge-anything": "^5.1.7", "neverthrow": "^6.0.0", "patch-package": "^8.0.0", diff --git a/src/Unit/Exchange/generateSwapCalldata.ts b/src/Unit/Exchange/generateSwapCalldata.ts index eb59716..5cbd512 100644 --- a/src/Unit/Exchange/generateSwapCalldata.ts +++ b/src/Unit/Exchange/generateSwapCalldata.ts @@ -1,44 +1,45 @@ -import type { LibValidator } from "@orionprotocol/contracts/lib/ethers-v6/Exchange.js"; -import { ethers, type BigNumberish, type BytesLike, JsonRpcProvider, ZeroAddress } from "ethers"; -import { safeGet, SafeArray } from "../../utils/safeGetters.js"; -import { simpleFetch } from "simple-typed-fetch"; -import type Unit from "../index.js"; -import { generateUni2Calls, generateUni2Call } from "./callGenerators/uniswapV2.js"; +import type { LibValidator } from '@orionprotocol/contracts/lib/ethers-v6/Exchange.js'; +import { ethers, ZeroAddress } from 'ethers'; +import type { AddressLike, JsonRpcProvider, BigNumberish, BytesLike } from 'ethers'; +import cloneDeep from 'lodash.clonedeep'; +import { safeGet, SafeArray } from '../../utils/safeGetters.js'; +import { simpleFetch } from 'simple-typed-fetch'; +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 { generateTransferCall } from "./callGenerators/erc20.js"; -import { generateCurveStableSwapCall } from "./callGenerators/curve.js"; -import type { SingleSwap } from "../../types.js"; -import type { AddressLike } from "ethers"; -import { addressLikeToString } from "../../utils/addressLikeToString.js"; -import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from "./callGenerators/weth.js"; -import { getWalletBalance } from "../../utils/getBalance.js"; +} from './callGenerators/uniswapV3.js'; +import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from './callGenerators/utils.js'; +import { generateTransferCall } from './callGenerators/erc20.js'; +import { generateCurveStableSwapCall } from './callGenerators/curve.js'; +import type { SingleSwap } from '../../types.js'; +import { addressLikeToString } from '../../utils/addressLikeToString.js'; +import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from './callGenerators/weth.js'; +import { getWalletBalance } from '../../utils/getBalance.js'; -export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3"; +export type Factory = 'UniswapV2' | 'UniswapV3' | 'Curve' | 'OrionV2' | 'OrionV3'; export type GenerateSwapCalldataWithUnitParams = { - amount: BigNumberish; - minReturnAmount: BigNumberish; - receiverAddress: string; - path: ArrayLike; - unit: Unit; + amount: BigNumberish + minReturnAmount: BigNumberish + receiverAddress: string + path: ArrayLike + unit: Unit }; export type GenerateSwapCalldataParams = { - amount: BigNumberish; - minReturnAmount: BigNumberish; - receiverAddress: string; - useContractBalance: boolean; - path: ArrayLike; - wethAddress: AddressLike; - curveRegistryAddress: AddressLike; - swapExecutorContractAddress: AddressLike; - provider: JsonRpcProvider; + amount: BigNumberish + minReturnAmount: BigNumberish + receiverAddress: string + useContractBalance: boolean + path: ArrayLike + wethAddress: AddressLike + curveRegistryAddress: AddressLike + swapExecutorContractAddress: AddressLike + provider: JsonRpcProvider }; export async function generateSwapCalldataWithUnit({ @@ -48,32 +49,33 @@ export async function generateSwapCalldataWithUnit({ path: arrayLikePath, unit, }: GenerateSwapCalldataWithUnitParams): Promise<{ - calldata: string; - swapDescription: LibValidator.SwapDescriptionStruct; + calldata: string + swapDescription: LibValidator.SwapDescriptionStruct }> { if (arrayLikePath == undefined || arrayLikePath.length == 0) { - throw new Error("Empty path"); + throw new Error('Empty path'); } - const wethAddress = safeGet(unit.contracts, "WETH"); - const curveRegistryAddress = safeGet(unit.contracts, "curveRegistry"); + const wethAddress = safeGet(unit.contracts, 'WETH'); + const curveRegistryAddress = safeGet(unit.contracts, 'curveRegistry'); const { assetToAddress, swapExecutorContractAddress } = await simpleFetch( unit.blockchainService.getInfo )(); - let path = SafeArray.from(arrayLikePath); + const arrayLikePathCopy = cloneDeep(arrayLikePath); + let path = SafeArray.from(arrayLikePathCopy); const walletBalance = await getWalletBalance( assetToAddress[path.first().assetIn] ?? path.first().assetIn.toLowerCase(), receiverAddress, unit.provider ); - path = SafeArray.from(arrayLikePath).map((swapInfo) => { + path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => { swapInfo.assetIn = assetToAddress[swapInfo.assetIn] ?? swapInfo.assetIn.toLowerCase(); swapInfo.assetOut = assetToAddress[swapInfo.assetOut] ?? swapInfo.assetOut.toLowerCase(); return swapInfo; }); - return generateSwapCalldata({ + return await generateSwapCalldata({ amount, minReturnAmount, receiverAddress, @@ -106,8 +108,8 @@ export async function generateSwapCalldata({ const dstToken = path.last().assetOut; let swapDescription: LibValidator.SwapDescriptionStruct = { - srcToken: srcToken, - dstToken: dstToken, + srcToken, + dstToken, srcReceiver: swapExecutorContractAddress, dstReceiver: receiverAddress, amount, @@ -172,27 +174,27 @@ async function processSingleFactorySwaps( ) { let calls: BytesLike[] = []; switch (factory) { - case "OrionV2": { + case 'OrionV2': { swapDescription.srcReceiver = path.first().pool; calls = await generateUni2Calls(path, swapExecutorContractAddress); break; } - case "UniswapV2": { + case 'UniswapV2': { swapDescription.srcReceiver = path.first().pool; calls = await generateUni2Calls(path, swapExecutorContractAddress); break; } - case "UniswapV3": { + case 'UniswapV3': { calls = await generateUni3Calls(path, amount, swapExecutorContractAddress, provider); break; } - case "OrionV3": { + case 'OrionV3': { calls = await generateOrion3Calls(path, amount, swapExecutorContractAddress, provider); break; } - case "Curve": { + case 'Curve': { if (path.length > 1) { - throw new Error("Supporting only single stable swap on curve"); + throw new Error('Supporting only single stable swap on curve'); } calls = await generateCurveStableSwapCall( amount, @@ -219,41 +221,41 @@ async function processMultiFactorySwaps( curveRegistryAddress: string, provider: JsonRpcProvider ) { - let calls: BytesLike[] = []; + const calls: BytesLike[] = []; if (swapDescription.srcToken === ZeroAddress) { const wrapCall = await generateWrapAndTransferCall(swapExecutorContractAddress, { value: amount }); calls.push(wrapCall); } for (const swap of path) { switch (swap.factory) { - case "OrionV2": { + 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.push(transferCall, uni2Call); break; } - case "UniswapV2": { + 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.push(transferCall, uni2Call); break; } - case "UniswapV3": { + case 'UniswapV3': { let uni3Call = await generateUni3Call(swap, 0, swapExecutorContractAddress, provider); uni3Call = pathCallWithBalance(uni3Call, swap.assetIn); calls.push(uni3Call); break; } - case "OrionV3": { + case 'OrionV3': { let orion3Call = await generateOrion3Call(swap, 0, swapExecutorContractAddress, provider); orion3Call = pathCallWithBalance(orion3Call, swap.assetIn); calls.push(orion3Call); break; } - case "Curve": { - let curveCalls = await generateCurveStableSwapCall( + case 'Curve': { + const curveCalls = await generateCurveStableSwapCall( amount, swapExecutorContractAddress, swap, diff --git a/src/utils/safeGetters.ts b/src/utils/safeGetters.ts index 0da1797..44dba35 100644 --- a/src/utils/safeGetters.ts +++ b/src/utils/safeGetters.ts @@ -1,5 +1,4 @@ export class SafeArray extends Array { - public static override from(array: ArrayLike): SafeArray { return new SafeArray(array); } @@ -9,9 +8,10 @@ export class SafeArray extends Array { for (const index in array) { const value = array[index] if (value === undefined) { - throw new Error("Array passed to constructor has undefined values") + throw new Error('Array passed to constructor has undefined values') } - this[index] = value + + this[index] = value; } } @@ -19,11 +19,11 @@ export class SafeArray extends Array { return [...this]; } - public override map(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): SafeArray { + public override map(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: unknown): SafeArray { return new SafeArray(super.map(callbackfn, thisArg)); } - public override filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): SafeArray { + public override filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: unknown): SafeArray { return new SafeArray(super.filter(callbackfn, thisArg)); } @@ -53,8 +53,8 @@ export function safeGet(obj: Partial>, key: string, errorMe const prefix = 'Requirement not met'; export function must(condition: unknown, message?: string | (() => string)): asserts condition { - if (condition) return; - const provided = typeof message === 'function' ? message() : message; - const value = provided ? `${prefix}: ${provided}` : prefix; - throw new Error(value); -} \ No newline at end of file + if (condition) return; + const provided = typeof message === 'function' ? message() : message; + const value = provided ? `${prefix}: ${provided}` : prefix; + throw new Error(value); +}