Merge pull request #218 from orionprotocol/fix/returnEthValue

Fix/return eth value
This commit is contained in:
lomonoshka
2023-11-30 15:09:40 +04:00
committed by GitHub
3 changed files with 212 additions and 79 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@orionprotocol/sdk", "name": "@orionprotocol/sdk",
"version": "0.20.26", "version": "0.20.26-rc3",
"description": "Orion Protocol SDK", "description": "Orion Protocol SDK",
"main": "./lib/index.cjs", "main": "./lib/index.cjs",
"module": "./lib/index.js", "module": "./lib/index.js",

View File

@@ -1,73 +1,72 @@
import type { LibValidator } from '@orionprotocol/contracts/lib/ethers-v6/Exchange.js'; import type { LibValidator } from "@orionprotocol/contracts/lib/ethers-v6/Exchange.js";
import { ethers, ZeroAddress } from 'ethers'; import { ethers, ZeroAddress } from "ethers";
import type { AddressLike, JsonRpcProvider, BigNumberish, BytesLike } from 'ethers'; import type { AddressLike, JsonRpcProvider, BigNumberish, BytesLike } from "ethers";
import cloneDeep from 'lodash.clonedeep'; import cloneDeep from "lodash.clonedeep";
import { safeGet, SafeArray } from '../../utils/safeGetters.js'; import { safeGet, SafeArray } from "../../utils/safeGetters.js";
import { simpleFetch } from 'simple-typed-fetch'; import { simpleFetch } from "simple-typed-fetch";
import type Unit from '../index.js'; import type Unit from "../index.js";
import { generateUni2Calls, generateUni2Call } from './callGenerators/uniswapV2.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 { 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 { getExchangeBalance, 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 initiatorAddress: string;
path: ArrayLike<SingleSwap> receiverAddress: string;
unit: Unit path: ArrayLike<SingleSwap>;
unit: Unit;
}; };
export type GenerateSwapCalldataParams = { export type GenerateSwapCalldataParams = {
amount: BigNumberish amount: BigNumberish;
minReturnAmount: BigNumberish minReturnAmount: BigNumberish;
receiverAddress: string initiatorAddress: string;
useContractBalance: boolean receiverAddress: string;
path: ArrayLike<SingleSwap> path: ArrayLike<SingleSwap>;
wethAddress: AddressLike exchangeContractAddress: AddressLike;
curveRegistryAddress: AddressLike wethAddress: AddressLike;
swapExecutorContractAddress: AddressLike curveRegistryAddress: AddressLike;
provider: JsonRpcProvider swapExecutorContractAddress: AddressLike;
provider: JsonRpcProvider;
}; };
export async function generateSwapCalldataWithUnit({ export async function generateSwapCalldataWithUnit({
amount, amount,
minReturnAmount, minReturnAmount,
initiatorAddress,
receiverAddress, receiverAddress,
path: arrayLikePath, path: arrayLikePath,
unit, unit,
}: GenerateSwapCalldataWithUnitParams): Promise<{ }: GenerateSwapCalldataWithUnitParams): Promise<{
calldata: string calldata: string;
swapDescription: LibValidator.SwapDescriptionStruct swapDescription: LibValidator.SwapDescriptionStruct;
value: bigint;
}> { }> {
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, exchangeContractAddress } = await simpleFetch(
unit.blockchainService.getInfo unit.blockchainService.getInfo
)(); )();
const arrayLikePathCopy = cloneDeep(arrayLikePath); const arrayLikePathCopy = cloneDeep(arrayLikePath);
let path = SafeArray.from(arrayLikePathCopy); let path = SafeArray.from(arrayLikePathCopy);
const walletBalance = await getWalletBalance(
assetToAddress[path.first().assetIn] ?? path.first().assetIn.toLowerCase(),
receiverAddress,
unit.provider
);
path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => { path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => {
swapInfo.assetIn = assetToAddress[swapInfo.assetIn] ?? swapInfo.assetIn.toLowerCase(); swapInfo.assetIn = assetToAddress[swapInfo.assetIn] ?? swapInfo.assetIn.toLowerCase();
@@ -79,8 +78,9 @@ export async function generateSwapCalldataWithUnit({
amount, amount,
minReturnAmount, minReturnAmount,
receiverAddress, receiverAddress,
useContractBalance: walletBalance < await exchangeToNativeDecimals(path.first().assetIn, amount, unit.provider), initiatorAddress,
path, path,
exchangeContractAddress,
wethAddress, wethAddress,
curveRegistryAddress, curveRegistryAddress,
swapExecutorContractAddress, swapExecutorContractAddress,
@@ -91,21 +91,26 @@ export async function generateSwapCalldataWithUnit({
export async function generateSwapCalldata({ export async function generateSwapCalldata({
amount, amount,
minReturnAmount, minReturnAmount,
initiatorAddress,
receiverAddress, receiverAddress,
useContractBalance,
path: arrayLikePath, path: arrayLikePath,
exchangeContractAddress,
wethAddress: wethAddressLike, wethAddress: wethAddressLike,
curveRegistryAddress: curveRegistryAddressLike, curveRegistryAddress: curveRegistryAddressLike,
swapExecutorContractAddress: swapExecutorContractAddressLike, swapExecutorContractAddress: swapExecutorContractAddressLike,
provider, provider,
}: GenerateSwapCalldataParams) { }: GenerateSwapCalldataParams): Promise<{
calldata: string;
swapDescription: LibValidator.SwapDescriptionStruct;
value: bigint;
}> {
const wethAddress = await addressLikeToString(wethAddressLike); const wethAddress = await addressLikeToString(wethAddressLike);
const curveRegistryAddress = await addressLikeToString(curveRegistryAddressLike); const curveRegistryAddress = await addressLikeToString(curveRegistryAddressLike);
const swapExecutorContractAddress = await addressLikeToString(swapExecutorContractAddressLike); const swapExecutorContractAddress = await addressLikeToString(swapExecutorContractAddressLike);
let path = SafeArray.from(arrayLikePath); let path = SafeArray.from(arrayLikePath);
const { factory, assetIn: srcToken } = path.first(); const { factory, assetIn: srcToken } = path.first();
const dstToken = path.last().assetOut; const { assetOut: dstToken } = path.last();
let swapDescription: LibValidator.SwapDescriptionStruct = { let swapDescription: LibValidator.SwapDescriptionStruct = {
srcToken, srcToken,
@@ -156,11 +161,25 @@ export async function generateSwapCalldata({
)); ));
const calldata = generateCalls(calls); const calldata = generateCalls(calls);
if (useContractBalance) { const initiatorWalletBalance = await getWalletBalance(srcToken, initiatorAddress, provider);
const initiatorExchangeBalance = await getExchangeBalance(
srcToken,
initiatorAddress,
exchangeContractAddress,
provider,
true
);
const useExchangeBalance =
initiatorExchangeBalance !== 0n && (srcToken === ZeroAddress || initiatorWalletBalance < amountNativeDecimals);
if (useExchangeBalance) {
swapDescription.flags = 1n << 255n; swapDescription.flags = 1n << 255n;
} }
let value = 0n;
if (srcToken === ZeroAddress && initiatorExchangeBalance < amountNativeDecimals) {
value = amountNativeDecimals - initiatorExchangeBalance;
}
return { swapDescription, calldata }; return { swapDescription, calldata, value };
} }
async function processSingleFactorySwaps( async function processSingleFactorySwaps(
@@ -174,27 +193,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,
@@ -224,33 +243,33 @@ async function processMultiFactorySwaps(
let calls: BytesLike[] = []; let calls: BytesLike[] = [];
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( let curveCalls = await generateCurveStableSwapCall(
amount, amount,
swapExecutorContractAddress, swapExecutorContractAddress,

View File

@@ -1,9 +1,11 @@
import { ERC20__factory, type Exchange } from '@orionprotocol/contracts/lib/ethers-v6/index.js'; import { ERC20__factory, Exchange__factory, type Exchange } from "@orionprotocol/contracts/lib/ethers-v6/index.js";
import type { BigNumber } from 'bignumber.js'; import type { BigNumber } from "bignumber.js";
import { ethers } from 'ethers'; import { ZeroAddress, ethers } from "ethers";
import { INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION } from '../constants/index.js'; import { INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION } from "../constants/index.js";
import type { Aggregator } from '../services/Aggregator/index.js'; import type { Aggregator } from "../services/Aggregator/index.js";
import denormalizeNumber from './denormalizeNumber.js'; import denormalizeNumber from "./denormalizeNumber.js";
import type { AddressLike } from "ethers";
import { addressLikeToString } from "./addressLikeToString.js";
export default async function getBalance( export default async function getBalance(
aggregator: Aggregator, aggregator: Aggregator,
@@ -11,7 +13,7 @@ export default async function getBalance(
assetAddress: string, assetAddress: string,
walletAddress: string, walletAddress: string,
exchangeContract: Exchange, exchangeContract: Exchange,
provider: ethers.Provider, provider: ethers.Provider
) { ) {
const assetIsNativeCryptocurrency = assetAddress === ethers.ZeroAddress; const assetIsNativeCryptocurrency = assetAddress === ethers.ZeroAddress;
@@ -30,7 +32,10 @@ export default async function getBalance(
denormalizedAssetInWalletBalance = denormalizeNumber(assetWalletBalance, BigInt(NATIVE_CURRENCY_PRECISION)); denormalizedAssetInWalletBalance = denormalizeNumber(assetWalletBalance, BigInt(NATIVE_CURRENCY_PRECISION));
} }
const assetContractBalance = await exchangeContract.getBalance(assetAddress, walletAddress); const assetContractBalance = await exchangeContract.getBalance(assetAddress, walletAddress);
const denormalizedAssetInContractBalance = denormalizeNumber(assetContractBalance, BigInt(INTERNAL_PROTOCOL_PRECISION)); const denormalizedAssetInContractBalance = denormalizeNumber(
assetContractBalance,
BigInt(INTERNAL_PROTOCOL_PRECISION)
);
const denormalizedAssetLockedBalanceResult = await aggregator.getLockedBalance(walletAddress, assetName); const denormalizedAssetLockedBalanceResult = await aggregator.getLockedBalance(walletAddress, assetName);
if (denormalizedAssetLockedBalanceResult.isErr()) { if (denormalizedAssetLockedBalanceResult.isErr()) {
throw new Error(denormalizedAssetLockedBalanceResult.error.message); throw new Error(denormalizedAssetLockedBalanceResult.error.message);
@@ -42,21 +47,130 @@ export default async function getBalance(
}; };
} }
export async function getWalletBalance( async function getExchangeBalanceERC20(
assetAddress: string, tokenAddress: AddressLike,
walletAddress: string, walletAddress: AddressLike,
exchangeAddress: AddressLike,
provider: ethers.Provider, provider: ethers.Provider,
convertToNativeDecimals = true
) { ) {
const assetIsNativeCryptocurrency = assetAddress === ethers.ZeroAddress; walletAddress = await addressLikeToString(walletAddress);
exchangeAddress = await addressLikeToString(exchangeAddress);
tokenAddress = await addressLikeToString(tokenAddress);
let assetWalletBalance: bigint | undefined; const exchange = <Exchange>Exchange__factory.connect(exchangeAddress, provider)
const exchangeBalance = await exchange.getBalance(tokenAddress, walletAddress);
if (!assetIsNativeCryptocurrency) { if (convertToNativeDecimals) {
const assetContract = ERC20__factory.connect(assetAddress, provider); const tokenContract = ERC20__factory.connect(tokenAddress, provider);
assetWalletBalance = await assetContract.balanceOf(walletAddress); const decimals = await tokenContract.decimals();
} else { const convertedExchangeBalance = (exchangeBalance * BigInt(10) ** decimals) / BigInt(10) ** 8n;
assetWalletBalance = await provider.getBalance(walletAddress); return convertedExchangeBalance;
} }
return assetWalletBalance return exchangeBalance;
}
async function getExchangeBalanceNative(
walletAddress: AddressLike,
exchangeAddress: AddressLike,
provider: ethers.Provider,
convertToNativeDecimals = true
) {
walletAddress = await addressLikeToString(walletAddress);
exchangeAddress = await addressLikeToString(exchangeAddress);
const exchange = <Exchange>Exchange__factory.connect(exchangeAddress, provider)
const exchangeBalance = await exchange.getBalance(ZeroAddress, walletAddress);
if (convertToNativeDecimals) {
const convertedExchangeBalance = (exchangeBalance * BigInt(10) ** 18n) / BigInt(10) ** 8n;
return convertedExchangeBalance;
}
return exchangeBalance;
}
export async function getExchangeBalance(
tokenAddress: AddressLike,
walletAddress: AddressLike,
exchangeAddress: AddressLike,
provider: ethers.Provider,
convertToNativeDecimals = true
) {
walletAddress = await addressLikeToString(walletAddress);
tokenAddress = await addressLikeToString(tokenAddress);
if (typeof tokenAddress === "string" && tokenAddress === ZeroAddress) {
return getExchangeBalanceNative(walletAddress, exchangeAddress, provider, convertToNativeDecimals);
} else {
return getExchangeBalanceERC20(tokenAddress, walletAddress, exchangeAddress, provider, convertToNativeDecimals);
}
}
async function getWalletBalanceERC20(
tokenAddress: AddressLike,
walletAddress: AddressLike,
provider: ethers.Provider,
convertToExchangeDecimals = false
) {
walletAddress = await addressLikeToString(walletAddress);
tokenAddress = await addressLikeToString(tokenAddress);
const tokenContract = ERC20__factory.connect(tokenAddress, provider);
let walletBalance = await tokenContract.balanceOf(walletAddress);
if (convertToExchangeDecimals) {
const tokenContract = ERC20__factory.connect(tokenAddress, provider);
const decimals = await tokenContract.decimals();
const convertedNativeBalance = (walletBalance * BigInt(10) ** 8n) / BigInt(10) ** decimals;
return convertedNativeBalance;
}
return walletBalance;
}
async function getWalletBalanceNative(
walletAddress: AddressLike,
provider: ethers.Provider,
convertToExchangeDecimals = false
) {
walletAddress = await addressLikeToString(walletAddress);
const nativeBalance = await provider.getBalance(walletAddress);
if (convertToExchangeDecimals) {
const convertedNativeBalance = (nativeBalance * BigInt(10) ** 8n) / BigInt(10) ** 18n;
return convertedNativeBalance;
}
return nativeBalance;
}
export async function getWalletBalance(
tokenAddress: AddressLike,
walletAddress: AddressLike,
provider: ethers.Provider,
convertToExchangeDecimals = false
) {
if (typeof tokenAddress === "string" && tokenAddress === ZeroAddress) {
return getWalletBalanceNative(walletAddress, provider, convertToExchangeDecimals);
} else {
return getWalletBalanceERC20(tokenAddress, walletAddress, provider, convertToExchangeDecimals);
}
}
export async function getTotalBalance(
tokenAddress: AddressLike,
walletAddress: AddressLike,
exchangeAddress: AddressLike,
provider: ethers.Provider,
convertToNativeDecimals = true
) {
const walletBalance = await getWalletBalance(tokenAddress, walletAddress, provider, !convertToNativeDecimals);
const exchangeBalance = await getExchangeBalance(
tokenAddress,
walletAddress,
exchangeAddress,
provider,
convertToNativeDecimals
);
return walletBalance + exchangeBalance;
} }