OP-4758: fix generateSwapCallData (#214)

This commit is contained in:
Dmitry
2023-11-27 15:56:34 +03:00
committed by GitHub
parent 2600688017
commit d38d8bd715
4 changed files with 108 additions and 66 deletions

42
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@orionprotocol/sdk", "name": "@orionprotocol/sdk",
"version": "0.20.22", "version": "0.20.23-rc1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@orionprotocol/sdk", "name": "@orionprotocol/sdk",
"version": "0.20.22", "version": "0.20.23-rc1",
"hasInstallScript": true, "hasInstallScript": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@@ -14,6 +14,7 @@
"@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/providers": "^5.7.2", "@ethersproject/providers": "^5.7.2",
"@orionprotocol/contracts": "1.22.3", "@orionprotocol/contracts": "1.22.3",
"@types/lodash.clonedeep": "^4.5.9",
"bignumber.js": "^9.1.1", "bignumber.js": "^9.1.1",
"bson-objectid": "^2.0.4", "bson-objectid": "^2.0.4",
"buffer": "^6.0.3", "buffer": "^6.0.3",
@@ -21,6 +22,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"isomorphic-ws": "^5.0.0", "isomorphic-ws": "^5.0.0",
"just-clone": "^6.2.0", "just-clone": "^6.2.0",
"lodash.clonedeep": "^4.5.0",
"merge-anything": "^5.1.7", "merge-anything": "^5.1.7",
"neverthrow": "^6.0.0", "neverthrow": "^6.0.0",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
@@ -2651,6 +2653,19 @@
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==",
"dev": true "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": { "node_modules/@types/mime": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@@ -8787,6 +8802,11 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true "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": { "node_modules/lodash.memoize": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -13692,6 +13712,19 @@
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==",
"dev": true "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": { "@types/mime": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@@ -18165,6 +18198,11 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true "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": { "lodash.memoize": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@orionprotocol/sdk", "name": "@orionprotocol/sdk",
"version": "0.20.22", "version": "0.20.23-rc4",
"description": "Orion Protocol SDK", "description": "Orion Protocol SDK",
"main": "./lib/index.cjs", "main": "./lib/index.cjs",
"module": "./lib/index.js", "module": "./lib/index.js",
@@ -89,6 +89,7 @@
"@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/providers": "^5.7.2", "@ethersproject/providers": "^5.7.2",
"@orionprotocol/contracts": "1.22.3", "@orionprotocol/contracts": "1.22.3",
"@types/lodash.clonedeep": "^4.5.9",
"bignumber.js": "^9.1.1", "bignumber.js": "^9.1.1",
"bson-objectid": "^2.0.4", "bson-objectid": "^2.0.4",
"buffer": "^6.0.3", "buffer": "^6.0.3",
@@ -96,6 +97,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"isomorphic-ws": "^5.0.0", "isomorphic-ws": "^5.0.0",
"just-clone": "^6.2.0", "just-clone": "^6.2.0",
"lodash.clonedeep": "^4.5.0",
"merge-anything": "^5.1.7", "merge-anything": "^5.1.7",
"neverthrow": "^6.0.0", "neverthrow": "^6.0.0",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",

View File

@@ -1,44 +1,45 @@
import type { LibValidator } from "@orionprotocol/contracts/lib/ethers-v6/Exchange.js"; import type { LibValidator } from '@orionprotocol/contracts/lib/ethers-v6/Exchange.js';
import { ethers, type BigNumberish, type BytesLike, JsonRpcProvider, ZeroAddress } from "ethers"; import { ethers, ZeroAddress } from 'ethers';
import { safeGet, SafeArray } from "../../utils/safeGetters.js"; import type { AddressLike, JsonRpcProvider, BigNumberish, BytesLike } from 'ethers';
import { simpleFetch } from "simple-typed-fetch"; import cloneDeep from 'lodash.clonedeep';
import type Unit from "../index.js"; import { safeGet, SafeArray } from '../../utils/safeGetters.js';
import { generateUni2Calls, generateUni2Call } from "./callGenerators/uniswapV2.js"; import { simpleFetch } from 'simple-typed-fetch';
import type Unit from '../index.js';
import { generateUni2Calls, generateUni2Call } from './callGenerators/uniswapV2.js';
import { import {
generateUni3Calls, generateUni3Calls,
generateOrion3Calls, generateOrion3Calls,
generateUni3Call, generateUni3Call,
generateOrion3Call, generateOrion3Call,
} from "./callGenerators/uniswapV3.js"; } from './callGenerators/uniswapV3.js';
import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from "./callGenerators/utils.js"; import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from './callGenerators/utils.js';
import { generateTransferCall } from "./callGenerators/erc20.js"; import { generateTransferCall } from './callGenerators/erc20.js';
import { generateCurveStableSwapCall } from "./callGenerators/curve.js"; import { generateCurveStableSwapCall } from './callGenerators/curve.js';
import type { SingleSwap } from "../../types.js"; import type { SingleSwap } from '../../types.js';
import type { AddressLike } from "ethers"; import { addressLikeToString } from '../../utils/addressLikeToString.js';
import { addressLikeToString } from "../../utils/addressLikeToString.js"; import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from './callGenerators/weth.js';
import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from "./callGenerators/weth.js"; import { getWalletBalance } from '../../utils/getBalance.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 = { export type GenerateSwapCalldataWithUnitParams = {
amount: BigNumberish; amount: BigNumberish
minReturnAmount: BigNumberish; minReturnAmount: BigNumberish
receiverAddress: string; receiverAddress: string
path: ArrayLike<SingleSwap>; path: ArrayLike<SingleSwap>
unit: Unit; unit: Unit
}; };
export type GenerateSwapCalldataParams = { export type GenerateSwapCalldataParams = {
amount: BigNumberish; amount: BigNumberish
minReturnAmount: BigNumberish; minReturnAmount: BigNumberish
receiverAddress: string; receiverAddress: string
useContractBalance: boolean; useContractBalance: boolean
path: ArrayLike<SingleSwap>; path: ArrayLike<SingleSwap>
wethAddress: AddressLike; wethAddress: AddressLike
curveRegistryAddress: AddressLike; curveRegistryAddress: AddressLike
swapExecutorContractAddress: AddressLike; swapExecutorContractAddress: AddressLike
provider: JsonRpcProvider; provider: JsonRpcProvider
}; };
export async function generateSwapCalldataWithUnit({ export async function generateSwapCalldataWithUnit({
@@ -48,32 +49,33 @@ export async function generateSwapCalldataWithUnit({
path: arrayLikePath, path: arrayLikePath,
unit, unit,
}: GenerateSwapCalldataWithUnitParams): Promise<{ }: GenerateSwapCalldataWithUnitParams): Promise<{
calldata: string; calldata: string
swapDescription: LibValidator.SwapDescriptionStruct; swapDescription: LibValidator.SwapDescriptionStruct
}> { }> {
if (arrayLikePath == undefined || arrayLikePath.length == 0) { if (arrayLikePath == undefined || arrayLikePath.length == 0) {
throw new Error("Empty path"); throw new Error('Empty path');
} }
const wethAddress = safeGet(unit.contracts, "WETH"); const wethAddress = safeGet(unit.contracts, 'WETH');
const curveRegistryAddress = safeGet(unit.contracts, "curveRegistry"); const curveRegistryAddress = safeGet(unit.contracts, 'curveRegistry');
const { assetToAddress, swapExecutorContractAddress } = await simpleFetch( const { assetToAddress, swapExecutorContractAddress } = await simpleFetch(
unit.blockchainService.getInfo unit.blockchainService.getInfo
)(); )();
let path = SafeArray.from(arrayLikePath); const arrayLikePathCopy = cloneDeep(arrayLikePath);
let path = SafeArray.from(arrayLikePathCopy);
const walletBalance = await getWalletBalance( const walletBalance = await getWalletBalance(
assetToAddress[path.first().assetIn] ?? path.first().assetIn.toLowerCase(), assetToAddress[path.first().assetIn] ?? path.first().assetIn.toLowerCase(),
receiverAddress, receiverAddress,
unit.provider unit.provider
); );
path = SafeArray.from(arrayLikePath).map((swapInfo) => { path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => {
swapInfo.assetIn = assetToAddress[swapInfo.assetIn] ?? swapInfo.assetIn.toLowerCase(); swapInfo.assetIn = assetToAddress[swapInfo.assetIn] ?? swapInfo.assetIn.toLowerCase();
swapInfo.assetOut = assetToAddress[swapInfo.assetOut] ?? swapInfo.assetOut.toLowerCase(); swapInfo.assetOut = assetToAddress[swapInfo.assetOut] ?? swapInfo.assetOut.toLowerCase();
return swapInfo; return swapInfo;
}); });
return generateSwapCalldata({ return await generateSwapCalldata({
amount, amount,
minReturnAmount, minReturnAmount,
receiverAddress, receiverAddress,
@@ -106,8 +108,8 @@ export async function generateSwapCalldata({
const dstToken = path.last().assetOut; const dstToken = path.last().assetOut;
let swapDescription: LibValidator.SwapDescriptionStruct = { let swapDescription: LibValidator.SwapDescriptionStruct = {
srcToken: srcToken, srcToken,
dstToken: dstToken, dstToken,
srcReceiver: swapExecutorContractAddress, srcReceiver: swapExecutorContractAddress,
dstReceiver: receiverAddress, dstReceiver: receiverAddress,
amount, amount,
@@ -172,27 +174,27 @@ async function processSingleFactorySwaps(
) { ) {
let calls: BytesLike[] = []; let calls: BytesLike[] = [];
switch (factory) { switch (factory) {
case "OrionV2": { case 'OrionV2': {
swapDescription.srcReceiver = path.first().pool; swapDescription.srcReceiver = path.first().pool;
calls = await generateUni2Calls(path, swapExecutorContractAddress); calls = await generateUni2Calls(path, swapExecutorContractAddress);
break; break;
} }
case "UniswapV2": { case 'UniswapV2': {
swapDescription.srcReceiver = path.first().pool; swapDescription.srcReceiver = path.first().pool;
calls = await generateUni2Calls(path, swapExecutorContractAddress); calls = await generateUni2Calls(path, swapExecutorContractAddress);
break; break;
} }
case "UniswapV3": { case 'UniswapV3': {
calls = await generateUni3Calls(path, amount, swapExecutorContractAddress, provider); calls = await generateUni3Calls(path, amount, swapExecutorContractAddress, provider);
break; break;
} }
case "OrionV3": { case 'OrionV3': {
calls = await generateOrion3Calls(path, amount, swapExecutorContractAddress, provider); calls = await generateOrion3Calls(path, amount, swapExecutorContractAddress, provider);
break; break;
} }
case "Curve": { case 'Curve': {
if (path.length > 1) { 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( calls = await generateCurveStableSwapCall(
amount, amount,
@@ -219,41 +221,41 @@ async function processMultiFactorySwaps(
curveRegistryAddress: string, curveRegistryAddress: string,
provider: JsonRpcProvider provider: JsonRpcProvider
) { ) {
let calls: BytesLike[] = []; const calls: BytesLike[] = [];
if (swapDescription.srcToken === ZeroAddress) { if (swapDescription.srcToken === ZeroAddress) {
const wrapCall = await generateWrapAndTransferCall(swapExecutorContractAddress, { value: amount }); const wrapCall = await generateWrapAndTransferCall(swapExecutorContractAddress, { value: amount });
calls.push(wrapCall); calls.push(wrapCall);
} }
for (const swap of path) { for (const swap of path) {
switch (swap.factory) { switch (swap.factory) {
case "OrionV2": { case 'OrionV2': {
let transferCall = await generateTransferCall(swap.assetIn, swap.pool, 0); let transferCall = await generateTransferCall(swap.assetIn, swap.pool, 0);
transferCall = pathCallWithBalance(transferCall, swap.assetIn); transferCall = pathCallWithBalance(transferCall, swap.assetIn);
const uni2Call = await generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress); const uni2Call = await generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
calls.push(transferCall, uni2Call); calls.push(transferCall, uni2Call);
break; break;
} }
case "UniswapV2": { case 'UniswapV2': {
let transferCall = await generateTransferCall(swap.assetIn, swap.pool, 0); let transferCall = await generateTransferCall(swap.assetIn, swap.pool, 0);
transferCall = pathCallWithBalance(transferCall, swap.assetIn); transferCall = pathCallWithBalance(transferCall, swap.assetIn);
const uni2Call = await generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress); const uni2Call = await generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
calls.push(transferCall, uni2Call); calls.push(transferCall, uni2Call);
break; break;
} }
case "UniswapV3": { case 'UniswapV3': {
let uni3Call = await generateUni3Call(swap, 0, swapExecutorContractAddress, provider); let uni3Call = await generateUni3Call(swap, 0, swapExecutorContractAddress, provider);
uni3Call = pathCallWithBalance(uni3Call, swap.assetIn); uni3Call = pathCallWithBalance(uni3Call, swap.assetIn);
calls.push(uni3Call); calls.push(uni3Call);
break; break;
} }
case "OrionV3": { case 'OrionV3': {
let orion3Call = await generateOrion3Call(swap, 0, swapExecutorContractAddress, provider); let orion3Call = await generateOrion3Call(swap, 0, swapExecutorContractAddress, provider);
orion3Call = pathCallWithBalance(orion3Call, swap.assetIn); orion3Call = pathCallWithBalance(orion3Call, swap.assetIn);
calls.push(orion3Call); calls.push(orion3Call);
break; break;
} }
case "Curve": { case 'Curve': {
let curveCalls = await generateCurveStableSwapCall( const curveCalls = await generateCurveStableSwapCall(
amount, amount,
swapExecutorContractAddress, swapExecutorContractAddress,
swap, swap,

View File

@@ -1,5 +1,4 @@
export class SafeArray<T> extends Array<T> { export class SafeArray<T> extends Array<T> {
public static override from<T>(array: ArrayLike<T>): SafeArray<T> { public static override from<T>(array: ArrayLike<T>): SafeArray<T> {
return new SafeArray(array); return new SafeArray(array);
} }
@@ -9,9 +8,10 @@ export class SafeArray<T> extends Array<T> {
for (const index in array) { for (const index in array) {
const value = array[index] const value = array[index]
if (value === undefined) { 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<T> extends Array<T> {
return [...this]; return [...this];
} }
public override map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): SafeArray<U> { public override map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: unknown): SafeArray<U> {
return new SafeArray(super.map(callbackfn, thisArg)); return new SafeArray(super.map(callbackfn, thisArg));
} }
public override filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): SafeArray<T> { public override filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: unknown): SafeArray<T> {
return new SafeArray(super.filter(callbackfn, thisArg)); return new SafeArray(super.filter(callbackfn, thisArg));
} }
@@ -53,8 +53,8 @@ export function safeGet<V>(obj: Partial<Record<string, V>>, key: string, errorMe
const prefix = 'Requirement not met'; const prefix = 'Requirement not met';
export function must(condition: unknown, message?: string | (() => string)): asserts condition { export function must(condition: unknown, message?: string | (() => string)): asserts condition {
if (condition) return; if (condition) return;
const provided = typeof message === 'function' ? message() : message; const provided = typeof message === 'function' ? message() : message;
const value = provided ? `${prefix}: ${provided}` : prefix; const value = provided ? `${prefix}: ${provided}` : prefix;
throw new Error(value); throw new Error(value);
} }