diff --git a/.vscode/settings.json b/.vscode/settings.json index 074400f6..26a47bda 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,8 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" +}, "editor.formatOnSave": true, "editor.formatOnPaste": false, "prettier.useEditorConfig": false, diff --git a/package.json b/package.json index a3150d05..c99d1ad0 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "postinstall": "patch-package", "test": "yarn build && jest", + "test-coverage": "yarn build && jest --coverage", "docs": "typedoc --entryPointStrategy expand --name 'Oraidex SDK' --readme none --tsconfig packages/contracts-sdk/tsconfig.json packages/contracts-sdk/src", "clean": "lerna clean --yes && lerna exec -- rimraf build/ dist/ cache/", "build": "lerna run build --concurrency 1", @@ -51,6 +52,7 @@ "husky": "^9.0.11", "jest": "^29.7.0", "lerna": "^8.1.2", + "mockttp": "^3.10.2", "nx": "18.1.2", "patch-package": "^8.0.0", "rimraf": "^5.0.5", diff --git a/packages/oraidex-common/src/index.ts b/packages/oraidex-common/src/index.ts index 54548c6b..3e88a871 100644 --- a/packages/oraidex-common/src/index.ts +++ b/packages/oraidex-common/src/index.ts @@ -1,13 +1,14 @@ -export * from "./token"; -export * from "./network"; -export * from "./ibc-info"; -export * from "./helper"; +export * from "./axios-request"; +export * from "./bigdecimal"; +export * from "./config/chainInfosWithIcon"; export * from "./constant"; +export * from "./helper"; +export * from "./ibc-info"; +export * from "./interface"; +export * from "./network"; export * from "./pairs"; -export * from "./wallet"; -export * from "./typechain-types"; +export * from "./testing"; +export * from "./token"; export * from "./tx"; -export * from "./bigdecimal"; -export * from "./interface"; -export * from "./config/chainInfosWithIcon"; -export * from "./axios-request"; +export * from "./typechain-types"; +export * from "./wallet"; diff --git a/packages/oraidex-common/src/testing/index.ts b/packages/oraidex-common/src/testing/index.ts new file mode 100644 index 00000000..96068566 --- /dev/null +++ b/packages/oraidex-common/src/testing/index.ts @@ -0,0 +1 @@ +export * from "./jsonrpc-mock-common"; diff --git a/packages/oraidex-common/src/testing/jsonrpc-mock-common.ts b/packages/oraidex-common/src/testing/jsonrpc-mock-common.ts new file mode 100644 index 00000000..f8bcf8f0 --- /dev/null +++ b/packages/oraidex-common/src/testing/jsonrpc-mock-common.ts @@ -0,0 +1,150 @@ +import { fromHex, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding"; +import { QuerySmartContractStateRequest, QuerySmartContractStateResponse } from "cosmjs-types/cosmwasm/wasm/v1/query"; +import * as mockttp from "mockttp"; + +export const buildAbciQueryResponse = (expectedResult: any) => { + return { + response: { + code: 0, + log: "", + info: "", + index: "0", + key: null, + value: expectedResult, + proofOps: null, + height: "1", + codespace: "" + } + }; +}; + +export const buildCosmWasmAbciQueryResponse = (expectedResult: any) => { + return buildAbciQueryResponse( + toBase64(QuerySmartContractStateResponse.encode({ data: toUtf8(JSON.stringify(expectedResult)) }).finish()) + ); +}; + +export const mockAccountInfo = buildAbciQueryResponse( + "Cp8BCiAvY29zbW9zLmF1dGgudjFiZXRhMS5CYXNlQWNjb3VudBJ7CitvcmFpMWc0aDY0eWp0MGZ2enY1djJqOHR5Zm5wZTVrbW5ldGVqdmZnczdnEkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/7NDxzUr4iFC8PmBZllh5P6RdDtLgnvL32OVolC+2tGGKwQIN0R" +); + +export const mockSimulate = buildAbciQueryResponse( + "CgQQp6kDEroJCiAKHgocL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBLEBVt7ImV2ZW50cyI6W3sidHlwZSI6ImNvaW5fcmVjZWl2ZWQiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJyZWNlaXZlciIsInZhbHVlIjoib3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZyJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiIxb3JhaSJ9XX0seyJ0eXBlIjoiY29pbl9zcGVudCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InNwZW5kZXIiLCJ2YWx1ZSI6Im9yYWkxZzRoNjR5anQwZnZ6djV2Mmo4dHlmbnBlNWttbmV0ZWp2ZmdzN2cifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiMW9yYWkifV19LHsidHlwZSI6Im1lc3NhZ2UiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJhY3Rpb24iLCJ2YWx1ZSI6Ii9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoib3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZyJ9LHsia2V5IjoibW9kdWxlIiwidmFsdWUiOiJiYW5rIn1dfSx7InR5cGUiOiJ0cmFuc2ZlciIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InJlY2lwaWVudCIsInZhbHVlIjoib3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZyJ9LHsia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJvcmFpMWc0aDY0eWp0MGZ2enY1djJqOHR5Zm5wZTVrbW5ldGVqdmZnczdnIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjFvcmFpIn1dfV19XRoxCgdtZXNzYWdlEiYKBmFjdGlvbhIcL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBpVCgpjb2luX3NwZW50EjYKB3NwZW5kZXISK29yYWkxZzRoNjR5anQwZnZ6djV2Mmo4dHlmbnBlNWttbmV0ZWp2ZmdzN2cSDwoGYW1vdW50EgUxb3JhaRpZCg1jb2luX3JlY2VpdmVkEjcKCHJlY2VpdmVyEitvcmFpMWc0aDY0eWp0MGZ2enY1djJqOHR5Zm5wZTVrbW5ldGVqdmZnczdnEg8KBmFtb3VudBIFMW9yYWkajAEKCHRyYW5zZmVyEjgKCXJlY2lwaWVudBIrb3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZxI1CgZzZW5kZXISK29yYWkxZzRoNjR5anQwZnZ6djV2Mmo4dHlmbnBlNWttbmV0ZWp2ZmdzN2cSDwoGYW1vdW50EgUxb3JhaRpACgdtZXNzYWdlEjUKBnNlbmRlchIrb3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZxoZCgdtZXNzYWdlEg4KBm1vZHVsZRIEYmFuaw==" +); + +export const mockStatus = { + node_info: { + protocol_version: { p2p: "8", block: "11", app: "0" }, + id: "6ffa64f7e7d78421d43eafa800fb11df3ce9c176", + listen_addr: "tcp://0.0.0.0:26656", + network: "Oraichain", + version: "0.34.29", + channels: "40202122233038606100", + moniker: "mainnet-sentry6", + other: { tx_index: "on", rpc_address: "tcp://0.0.0.0:26657" } + }, + sync_info: { + latest_block_hash: "FB98CF714E844680D2F933B630364FFCB665A1525E75AF4E2AE56BC3E10780E2", + latest_app_hash: "5BED49716F7A9CC54BCDF6F02BBD055546260DEE45F90C4E22665945FF46A1ED", + latest_block_height: "17608708", + latest_block_time: "2024-04-03T19:24:02.1973058Z", + earliest_block_hash: "9C3C6F0A72142BCCE9D52883522D86D7ACDD4B5DB12059C2070AD0847268CE63", + earliest_app_hash: "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + earliest_block_height: "1", + earliest_block_time: "2021-02-23T17:06:00.124468946Z", + catching_up: false + }, + validator_info: { + address: "DB71D3EE07AB06B394915C186C48939887FBDF03", + pub_key: { type: "tendermint/PubKeyEd25519", value: "QJxO4ljISgXoTU5e48pip/bff80xwLw+RxaDdjM05G0=" }, + voting_power: "0" + } +}; + +export const mockResponse = { + code: 0, + data: "", + log: "[]", + codespace: "", + hash: "2F59A5318ED6350976244EAD25CB78ACFEBC1B9C9A96809269A793EDFB529065" +}; + +export const mockTxSearch = { + txs: [ + { + hash: "2F59A5318ED6350976244EAD25CB78ACFEBC1B9C9A96809269A793EDFB529065", + height: "1", + index: 0, + tx_result: { + code: 0, + data: "", + log: "", + info: "", + gas_wanted: "76215", + gas_used: "68664", + events: [], + codespace: "" + }, + timestamp: "2024-04-03T19:24:03Z", + tx: "CogBCoUBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmUKK29yYWkxZzRoNjR5anQwZnZ6djV2Mmo4dHlmbnBlNWttbmV0ZWp2ZmdzN2cSK29yYWkxZzRoNjR5anQwZnZ6djV2Mmo4dHlmbnBlNWttbmV0ZWp2ZmdzN2caCQoEb3JhaRIBMRJlClEKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiED/s0PHNSviIULw+YFmWWHk/pF0O0uCe8vfY5WiUL7a0YSBAoCCAEY3RESEAoKCgRvcmFpEgI3NxC30wQaQBfHtIpbY6nu7qqMlhqtIgRK4ID/uoJ2vzPPBapPZNUuRUeTjoisSNSi4eUh26+edx5aiY3dbV+vlVdeaptOxkA=" + } + ], + total_count: "1" +}; + +export const mockJsonRpcServer = async () => { + const mockServer = mockttp.getLocal(); + await mockServer.start(); + await mockServer + .forJsonRpcRequest({ + method: "abci_query", + params: { path: "/cosmos.auth.v1beta1.Query/Account" } + }) + .thenSendJsonRpcResult(mockAccountInfo); + + await mockServer + .forJsonRpcRequest({ + method: "status" + }) + .thenSendJsonRpcResult(mockStatus); + + await mockServer + .forJsonRpcRequest({ + method: "abci_query", + params: { path: "/cosmos.tx.v1beta1.Service/Simulate" } + }) + .thenSendJsonRpcResult(mockSimulate); + + await mockServer + .forJsonRpcRequest({ + method: "broadcast_tx_sync" + }) + .thenSendJsonRpcResult(mockResponse); + + await mockServer + .forJsonRpcRequest({ + method: "broadcast_tx_async" + }) + .thenSendJsonRpcResult(mockResponse); + + await mockServer + .forJsonRpcRequest({ + method: "tx_search" + }) + .thenSendJsonRpcResult(mockTxSearch); + + return mockServer; +}; + +export const matchCosmWasmQueryRequest = async ( + request: mockttp.CompletedRequest, + wasmQueryFilter: (queryData: any, queryRequest: QuerySmartContractStateRequest) => boolean +): Promise => { + const bodyJson: any = await request.body.getJson(); + if (bodyJson.method !== "abci_query") return false; + if (!bodyJson.params.path) return false; + if (bodyJson.params.path !== "/cosmwasm.wasm.v1.Query/SmartContractState") return false; + const queryRequest = QuerySmartContractStateRequest.decode(fromHex(bodyJson.params.data)); + const queryData = JSON.parse(fromUtf8(queryRequest.queryData)); + return wasmQueryFilter(queryData, queryRequest); +}; diff --git a/packages/oraidex-common/src/wallet.ts b/packages/oraidex-common/src/wallet.ts index ddf86b9e..5ef571c1 100644 --- a/packages/oraidex-common/src/wallet.ts +++ b/packages/oraidex-common/src/wallet.ts @@ -1,17 +1,16 @@ -import { OfflineSigner } from "@cosmjs/proto-signing"; -import { CosmosChainId, EvmChainId, NetworkChainId, Networks } from "./network"; import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { EncodeObject, OfflineSigner } from "@cosmjs/proto-signing"; import { SigningStargateClient, SigningStargateClientOptions } from "@cosmjs/stargate"; -import { ethToTronAddress, tronToEthAddress } from "./helper"; -import { TokenItemType } from "./token"; -import { ethers } from "ethers"; -import { IERC20Upgradeable__factory } from "./typechain-types"; -import { JsonRpcSigner } from "@ethersproject/providers"; -import { TronWeb } from "./tronweb"; -import { EncodeObject } from "@cosmjs/proto-signing"; import { Tendermint37Client } from "@cosmjs/tendermint-rpc"; +import { JsonRpcSigner } from "@ethersproject/providers"; import { Stargate } from "@injectivelabs/sdk-ts"; +import { ethers } from "ethers"; import { BROADCAST_POLL_INTERVAL } from "./constant"; +import { ethToTronAddress, tronToEthAddress } from "./helper"; +import { CosmosChainId, EvmChainId, NetworkChainId, Networks } from "./network"; +import { TokenItemType } from "./token"; +import { TronWeb } from "./tronweb"; +import { IERC20Upgradeable__factory } from "./typechain-types"; export interface EvmResponse { transactionHash: string; diff --git a/packages/universal-swap/src/handler.ts b/packages/universal-swap/src/handler.ts index 8805a89e..45ad29e3 100644 --- a/packages/universal-swap/src/handler.ts +++ b/packages/universal-swap/src/handler.ts @@ -1,59 +1,47 @@ -import { Coin, EncodeObject, coin } from "@cosmjs/proto-signing"; -import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; import { ExecuteInstruction, ExecuteResult, toBinary } from "@cosmjs/cosmwasm-stargate"; +import { EncodeObject, coin } from "@cosmjs/proto-signing"; +import { GasPrice } from "@cosmjs/stargate"; import { TransferBackMsg } from "@oraichain/common-contracts-sdk/build/CwIcs20Latest.types"; import { - TokenItemType, - NetworkChainId, + BigDecimal, + Bridge__factory, + CoinGeckoId, + CosmosChainId, + EvmResponse, IBCInfo, + IBC_WASM_CONTRACT, + IBC_WASM_CONTRACT_TEST, + NetworkChainId, + ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX, + TokenItemType, + UNISWAP_ROUTER_DEADLINE, + buildMultipleExecuteMessages, + calculateMinReceive, calculateTimeoutTimestamp, + checkValidateAddressWithNetwork, + ethToTronAddress, + findToTokenOnOraiBridge, generateError, + getCosmosGasPrice, getEncodedExecuteContractMsgs, - toAmount, - // buildMultipleExecuteMessages, - parseTokenInfo, - calculateMinReceive, - handleSentFunds, - tronToEthAddress, - ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX, - oraichain2oraib, - CosmosChainId, - findToTokenOnOraiBridge, + getTokenOnOraichain, getTokenOnSpecificChainId, - UNISWAP_ROUTER_DEADLINE, gravityContracts, - Bridge__factory, - IUniswapV2Router02__factory, - ethToTronAddress, + handleSentFunds, + ibcInfosOld, network, - EvmResponse, - getTokenOnOraichain, - getCosmosGasPrice, - CoinGeckoId, - IBC_WASM_CONTRACT, - IBC_WASM_CONTRACT_TEST, + oraichain2oraib, + // buildMultipleExecuteMessages, + parseTokenInfo, + toAmount, tokenMap, - AmountDetails, - buildMultipleExecuteMessages, - ibcInfosOld, - checkValidateAddressWithNetwork, - BigDecimal + tronToEthAddress } from "@oraichain/oraidex-common"; +import { OraiswapRouterQueryClient } from "@oraichain/oraidex-contracts-sdk"; +import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; import { ethers } from "ethers"; import { UniversalSwapHelper } from "./helper"; -import { - ConvertReverse, - ConvertType, - SmartRouteSwapOperations, - Type, - UniversalSwapConfig, - UniversalSwapData, - UniversalSwapType -} from "./types"; -import { GasPrice } from "@cosmjs/stargate"; -import { Height } from "cosmjs-types/ibc/core/client/v1/client"; -import { CwIcs20LatestQueryClient } from "@oraichain/common-contracts-sdk"; -import { OraiswapRouterQueryClient } from "@oraichain/oraidex-contracts-sdk"; +import { SmartRouteSwapOperations, UniversalSwapConfig, UniversalSwapData, UniversalSwapType } from "./types"; export class UniversalSwapHandler { constructor(public swapData: UniversalSwapData, public config: UniversalSwapConfig) {} @@ -292,7 +280,6 @@ export class UniversalSwapHandler { return [...msgExecuteSwap, ...msgExecuteTransfer]; } - // TODO: write test cases async swap(): Promise { const messages = this.generateMsgsSwap(); const { client } = await this.config.cosmosWallet.getCosmWasmClient( @@ -303,7 +290,6 @@ export class UniversalSwapHandler { return result; } - // TODO: write test cases public async evmSwap(data: { fromToken: TokenItemType; toTokenContractAddr: string; @@ -324,9 +310,9 @@ export class UniversalSwapHandler { tronAddress }); const finalFromAmount = toAmount(fromAmount, fromToken.decimals).toString(); - const gravityContractAddr = ethers.utils.getAddress(gravityContracts[fromToken.chainId]); - const checkSumAddress = ethers.utils.getAddress(finalTransferAddress); - const gravityContract = Bridge__factory.connect(gravityContractAddr, signer); + const gravityContractAddr = this.getAddressWithEthers(gravityContracts[fromToken.chainId]); + const checkSumAddress = this.getAddressWithEthers(finalTransferAddress); + const gravityContract = this.connectBridgeFactory(gravityContractAddr, signer); const routerV2Addr = await gravityContract.swapRouter(); const minimumReceive = BigInt(calculateMinReceive(simulatePrice, finalFromAmount, slippage, fromToken.decimals)); let result: ethers.ContractTransaction; @@ -344,14 +330,14 @@ export class UniversalSwapHandler { // Case 1: bridge from native bnb / eth case if (!fromToken.contractAddress) { result = await gravityContract.bridgeFromETH( - ethers.utils.getAddress(toTokenContractAddr), + this.getAddressWithEthers(toTokenContractAddr), minimumReceive, // use destination, { value: finalFromAmount } ); } else if (!toTokenContractAddr) { // Case 2: swap to native eth / bnb. Get evm route so that we can swap from token -> native eth / bnb - const routerV2 = IUniswapV2Router02__factory.connect(routerV2Addr, signer); + const routerV2 = UniversalSwapHelper.connectFactoryRouteUniswapV2(routerV2Addr, signer); const evmRoute = UniversalSwapHelper.getEvmSwapRoute(fromToken.chainId, fromToken.contractAddress); result = await routerV2.swapExactTokensForETH( @@ -364,8 +350,8 @@ export class UniversalSwapHandler { } else { // Case 3: swap erc20 token to another erc20 token with a given destination (possibly sent to Oraichain or other networks) result = await gravityContract.bridgeFromERC20( - ethers.utils.getAddress(fromToken.contractAddress), - ethers.utils.getAddress(toTokenContractAddr), + this.getAddressWithEthers(fromToken.contractAddress), + this.getAddressWithEthers(toTokenContractAddr), finalFromAmount, minimumReceive, // use destination @@ -375,7 +361,6 @@ export class UniversalSwapHandler { return { transactionHash: result.hash }; } - // TODO: write test cases public async transferToGravity(to: string): Promise { const token = this.swapData.originalFromToken; let from = this.swapData.sender.evm; @@ -403,14 +388,21 @@ export class UniversalSwapHandler { // if you call this function on evm, you have to switch network before calling. Otherwise, unexpected errors may happen if (!gravityContractAddr || !from || !to) throw generateError("OraiBridge contract addr or from or to is not specified. Cannot transfer!"); - const gravityContract = Bridge__factory.connect(gravityContractAddr, evmWallet.getSigner()); + const gravityContract = this.connectBridgeFactory(gravityContractAddr, evmWallet.getSigner()); const result = await gravityContract.sendToCosmos(token.contractAddress, to, amountVal, { from }); const res = await result.wait(); return { transactionHash: res.transactionHash }; } } - // TODO: write test cases + public connectBridgeFactory = (gravityContractAddr, signer) => { + return Bridge__factory.connect(gravityContractAddr, signer); + }; + + public getAddressWithEthers = (addr) => { + return ethers.utils.getAddress(addr); + }; + transferEvmToIBC = async (swapRoute: string): Promise => { const from = this.swapData.originalFromToken; const fromAmount = this.swapData.fromAmount; @@ -453,7 +445,6 @@ export class UniversalSwapHandler { ); } - // TODO: write test cases async swapAndTransferToOtherNetworks(universalSwapType: UniversalSwapType) { let encodedObjects: EncodeObject[]; const { originalToToken, originalFromToken, simulateAmount, sender } = this.swapData; @@ -508,7 +499,6 @@ export class UniversalSwapHandler { return client.signAndBroadcast(sender.cosmos, encodedObjects, "auto"); } - // TODO: write test cases // transfer evm to ibc async transferAndSwap(swapRoute: string): Promise { const { @@ -597,7 +587,6 @@ export class UniversalSwapHandler { // this method allows swapping from cosmos networks to arbitrary networks using ibc wasm hooks // Oraichain will be use as a proxy - // TODO: write test cases async swapCosmosToOtherNetwork(destinationReceiver: string) { const { originalFromToken, originalToToken, sender } = this.swapData; // guard check to see if from token has a pool on Oraichain or not. If not then return error diff --git a/packages/universal-swap/src/helper.ts b/packages/universal-swap/src/helper.ts index e4186b96..7616ab55 100644 --- a/packages/universal-swap/src/helper.ts +++ b/packages/universal-swap/src/helper.ts @@ -1,53 +1,64 @@ +import { CosmWasmClient, ExecuteInstruction, toBinary } from "@cosmjs/cosmwasm-stargate"; +import { Coin } from "@cosmjs/proto-signing"; +import { Amount, CwIcs20LatestQueryClient, Uint128 } from "@oraichain/common-contracts-sdk"; import { - CoinGeckoId, - WRAP_BNB_CONTRACT, - USDT_BSC_CONTRACT, - USDT_TRON_CONTRACT, - ORAI_ETH_CONTRACT, - ORAI_BSC_CONTRACT, AIRI_BSC_CONTRACT, - WRAP_ETH_CONTRACT, - USDC_ETH_CONTRACT, - USDT_ETH_CONTRACT, - EvmChainId, - proxyContractInfo, + AmountDetails, + BigDecimal, + CoinGeckoId, CosmosChainId, - NetworkChainId, + EvmChainId, IBCInfo, - generateError, - ibcInfos, - oraib2oraichain, + IUniswapV2Router02__factory, KWT_BSC_CONTRACT, MILKY_BSC_CONTRACT, + NEUTARO_INFO, + NetworkChainId, + ORAI_BSC_CONTRACT, + ORAI_ETH_CONTRACT, + ORAI_INFO, + PAIRS, TokenItemType, - parseTokenInfoRawDenom, + USDC_ETH_CONTRACT, + USDC_INFO, + USDT_BSC_CONTRACT, + USDT_ETH_CONTRACT, + USDT_TRON_CONTRACT, + WRAP_BNB_CONTRACT, + WRAP_ETH_CONTRACT, + cosmosTokens, + generateError, + getAxios, + getSubAmountDetails, getTokenOnOraichain, - isEthAddress, - PAIRS, - ORAI_INFO, - parseTokenInfo, - toAmount, - toDisplay, getTokenOnSpecificChainId, - IUniswapV2Router02__factory, - cosmosTokens, - StargateMsg, + handleSentFunds, + ibcInfos, + isEthAddress, isInPairList, - BigDecimal, - NEUTARO_INFO, - USDC_INFO, network, - ORAIX_ETH_CONTRACT, - AmountDetails, - handleSentFunds, - tokenMap, + oraib2oraichain, oraib2oraichainTest, - getSubAmountDetails, - evmChains, - getAxios, + parseAssetInfo, parseAssetInfoFromContractAddrOrDenom, - parseAssetInfo + parseTokenInfo, + parseTokenInfoRawDenom, + proxyContractInfo, + toAmount, + toDisplay, + tokenMap } from "@oraichain/oraidex-common"; +import { + AssetInfo, + OraiswapRouterQueryClient, + OraiswapRouterReadOnlyInterface, + OraiswapTokenQueryClient, + SwapOperation +} from "@oraichain/oraidex-contracts-sdk"; +import { ethers } from "ethers"; +import { isEqual } from "lodash"; +import { parseToIbcHookMemo, parseToIbcWasmMemo } from "./proto/proto-gen"; +import { swapFromTokens, swapToTokens } from "./swap-filter"; import { ConvertReverse, ConvertType, @@ -55,26 +66,10 @@ import { SimulateResponse, SmartRouterResponse, SmartRouterResponseAPI, - SmartRouteSwapOperations, SwapDirection, SwapRoute, - Type, - UniversalSwapConfig + Type } from "./types"; -import { - AssetInfo, - OraiswapRouterQueryClient, - OraiswapRouterReadOnlyInterface, - OraiswapTokenQueryClient, - SwapOperation -} from "@oraichain/oraidex-contracts-sdk"; -import { isEqual } from "lodash"; -import { ethers } from "ethers"; -import { Amount, CwIcs20LatestQueryClient, Uint128 } from "@oraichain/common-contracts-sdk"; -import { CosmWasmClient, ExecuteInstruction, toBinary } from "@cosmjs/cosmwasm-stargate"; -import { swapFromTokens, swapToTokens } from "./swap-filter"; -import { parseToIbcHookMemo, parseToIbcWasmMemo } from "./proto/proto-gen"; -import { Coin } from "@cosmjs/proto-signing"; const caseSwapNativeAndWrapNative = (fromCoingecko, toCoingecko) => { const arr = ["ethereum", "weth"]; @@ -574,6 +569,14 @@ export class UniversalSwapHelper { } }; + static getJsonRpcProvider = (rpcProvider) => { + return new ethers.providers.JsonRpcProvider(rpcProvider); + }; + + static connectFactoryRouteUniswapV2 = (routerAddr, signer) => { + return IUniswapV2Router02__factory.connect(routerAddr, signer); + }; + static simulateSwapEvm = async (query: { fromInfo: TokenItemType; toInfo: TokenItemType; @@ -596,12 +599,9 @@ export class UniversalSwapHelper { } try { // get proxy contract object so that we can query the corresponding router address - const provider = new ethers.providers.JsonRpcProvider(fromInfo.rpc); + const provider = this.getJsonRpcProvider(fromInfo.rpc); const toTokenInfoOnSameChainId = getTokenOnSpecificChainId(toInfo.coinGeckoId, fromInfo.chainId); - const swapRouterV2 = IUniswapV2Router02__factory.connect( - proxyContractInfo[fromInfo.chainId].routerAddr, - provider - ); + const swapRouterV2 = this.connectFactoryRouteUniswapV2(proxyContractInfo[fromInfo.chainId].routerAddr, provider); const route = UniversalSwapHelper.getEvmSwapRoute( fromInfo.chainId, fromInfo.contractAddress, @@ -1028,7 +1028,6 @@ export class UniversalSwapHelper { return msg; }; } - // evm swap helpers /** * @deprecated. Use UniversalSwapHelper.isSupportedNoPoolSwapEvm diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index 9c4cb47a..54e1881e 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -1,50 +1,64 @@ +import { CosmWasmClient, toBinary } from "@cosmjs/cosmwasm-stargate"; +import { fromUtf8, toUtf8 } from "@cosmjs/encoding"; +import { DirectSecp256k1HdWallet, EncodeObject, OfflineSigner } from "@cosmjs/proto-signing"; +import { JsonRpcProvider, JsonRpcSigner } from "@ethersproject/providers"; +import { CwIcs20LatestClient } from "@oraichain/common-contracts-sdk"; +import { CWSimulateApp, GenericError, IbcOrder, IbcPacket, SimulateCosmWasmClient } from "@oraichain/cw-simulate"; import { + AIRI_BSC_CONTRACT, + AIRI_CONTRACT, + CoinGeckoId, CosmosWallet, EvmWallet, + IBC_TRANSFER_TIMEOUT, + IBC_WASM_CONTRACT, + IBC_WASM_CONTRACT_TEST, + NEUTARO_INFO, NetworkChainId, + Networks, + ORAIXOCH_INFO, + ORAIX_INFO, + ORAI_BRIDGE_EVM_DENOM_PREFIX, + ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX, + ORAI_INFO, OSMOSIS_ORAICHAIN_DENOM, + ROUTER_V2_CONTRACT, + TokenItemType, + USDC_CONTRACT, + USDC_ETH_CONTRACT, + USDC_INFO, + USDT_BSC_CONTRACT, + USDT_CONTRACT, + USDT_ETH_CONTRACT, + WRAP_BNB_CONTRACT, + buildCosmWasmAbciQueryResponse, flattenTokens, + matchCosmWasmQueryRequest, + mockJsonRpcServer, + mockResponse, + network, oraichain2osmosis, oraichainTokens, - USDT_CONTRACT, - ROUTER_V2_CONTRACT, + toAmount, toDisplay, - ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX, - TokenItemType, - CosmosChainId, - CoinGeckoId, - AIRI_CONTRACT, - IBC_WASM_CONTRACT, - ORAI_BRIDGE_EVM_DENOM_PREFIX, - AIRI_BSC_CONTRACT, - IBC_TRANSFER_TIMEOUT, - toTokenInfo, - IBC_WASM_CONTRACT_TEST, - USDC_CONTRACT + toTokenInfo } from "@oraichain/oraidex-common"; import * as dexCommonHelper from "@oraichain/oraidex-common/build/helper"; // import like this to enable jest.spyOn & avoid redefine property error -import { DirectSecp256k1HdWallet, EncodeObject, OfflineSigner } from "@cosmjs/proto-signing"; -import { JsonRpcProvider, JsonRpcSigner } from "@ethersproject/providers"; -import TronWeb from "tronweb"; -import Long from "long"; -import { TronWeb as _TronWeb } from "@oraichain/oraidex-common/build/tronweb"; -import { fromUtf8, toUtf8 } from "@cosmjs/encoding"; -import { toBinary } from "@cosmjs/cosmwasm-stargate"; import { ibcInfos, oraichain2oraib } from "@oraichain/oraidex-common/build/ibc-info"; +import { TronWeb as _TronWeb } from "@oraichain/oraidex-common/build/tronweb"; +import * as oraidexArtifacts from "@oraichain/oraidex-contracts-build"; import { OraiswapFactoryClient, OraiswapOracleClient, OraiswapRouterClient, OraiswapRouterQueryClient, - OraiswapTokenClient + OraiswapTokenClient, + SwapOperation } from "@oraichain/oraidex-contracts-sdk"; -import { CWSimulateApp, GenericError, IbcOrder, IbcPacket, SimulateCosmWasmClient } from "@oraichain/cw-simulate"; -import { CwIcs20LatestClient } from "@oraichain/common-contracts-sdk"; import bech32 from "bech32"; -import { UniversalSwapConfig, UniversalSwapData, UniversalSwapType } from "../src/types"; -import { deployIcs20Token, deployToken, testSenderAddress } from "./test-common"; -import * as oraidexArtifacts from "@oraichain/oraidex-contracts-build"; import { readFileSync } from "fs"; +import Long from "long"; +import TronWeb from "tronweb"; import { UniversalSwapHandler } from "../src/handler"; import { UniversalSwapHelper, @@ -57,6 +71,8 @@ import { handleSimulateSwap, simulateSwap } from "../src/helper"; +import { SwapDirection, UniversalSwapConfig, UniversalSwapData, UniversalSwapType } from "../src/types"; +import { deployIcs20Token, deployToken, testSenderAddress } from "./test-common"; describe("test universal swap handler functions", () => { const client = new SimulateCosmWasmClient({ @@ -71,7 +87,10 @@ describe("test universal swap handler functions", () => { const airiIbcDenom: string = "oraib0x7e2A35C746F2f7C240B664F1Da4DD100141AE71F"; const bobAddress = "orai1ur2vsjrjarygawpdwtqteaazfchvw4fg6uql76"; const oraiAddress = "orai12zyu8w93h0q2lcnt50g3fn0w3yqnhy4fvawaqz"; + const evmAddress = "0x8c7E0A841269a01c0Ab389Ce8Fb3Cf150A94E797"; + const tronAddress = "TEu6u8JLCFs6x1w5s8WosNqYqVx2JMC5hQ"; const cosmosSenderAddress = bech32.encode("cosmos", bech32.decode(oraiAddress).words); + const nobleSenderAddress = bech32.encode("noble", bech32.decode(oraiAddress).words); let ics20Contract: CwIcs20LatestClient; let factoryContract: OraiswapFactoryClient; @@ -425,6 +444,61 @@ describe("test universal swap handler functions", () => { } ); + it.each<[TokenItemType, TokenItemType, boolean, number]>([ + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "oraichain-token")!, + false, + 1 + ], + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "osmosis-1" && t.coinGeckoId === "osmosis")!, + false, + 2 + ], + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "cosmoshub-4" && t.coinGeckoId === "cosmos")!, + false, + 2 + ] + ])("test-combineSwapMsgOraichain()-for-mock-%s", async (fromToken, toToken, willThrow, expectLength) => { + const universalSwap = new FakeUniversalSwapHandler({ + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + sender: { + cosmos: "orai1234" + } + }); + + if (toToken.chainId === "Oraichain") { + jest.spyOn(dexCommonHelper, "checkValidateAddressWithNetwork").mockReturnValue({ + isValid: true, + network: toToken.chainId + }); + jest.spyOn(dexCommonHelper, "getEncodedExecuteContractMsgs").mockReturnValue([ + { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: {} + } + ]); + } + + jest + .spyOn(universalSwap.config.cosmosWallet!, "getKeplrAddr") + .mockReturnValue(new Promise((resolve) => resolve("orai1234" as any))); + + try { + const res = await universalSwap.combineSwapMsgOraichain("0"); + expect(res.length).toBe(expectLength); + expect(willThrow).toBe(false); + } catch (error) { + expect(willThrow).toBe(true); + } + }); + it.each<[string, string, string, boolean]>([ ["oraichain-token", "Oraichain", "0", true], ["oraichain-token", "Oraichain", "1000000", false], @@ -598,6 +672,69 @@ describe("test universal swap handler functions", () => { } }); + it.each<[TokenItemType, TokenItemType, string, string, boolean]>([ + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "oraichain-token")!, + flattenTokens.find((t) => t.chainId === "0x01" && t.coinGeckoId === "oraichain-token")!, + simulateAmount, + channel, + false + ], + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "oraichain-token")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "oraichain-token")!, + toAmount(10).toString(), + channel, + true + ] + ])( + "test-universal-swap-checkBalanceChannelIbc-with-mock-%", + async (fromToken, toToken, amount, channel, willThrow) => { + const mockServer = await mockJsonRpcServer(); + await mockServer + .forJsonRpcRequest() + .matching(async (request) => + matchCosmWasmQueryRequest(request, (queryData) => { + return "channel_with_key" in queryData; + }) + ) + .thenSendJsonRpcResult( + buildCosmWasmAbciQueryResponse({ + balance: { native: { denom: fromToken.denom, amount: toAmount(1).toString() } } + }) + ); + await mockServer + .forJsonRpcRequest() + .matching(async (request) => + matchCosmWasmQueryRequest(request, (queryData) => { + return "pair_mapping" in queryData; + }) + ) + .thenSendJsonRpcResult(buildCosmWasmAbciQueryResponse({ pair_mapping: { remote_decimals: 6 } })); + try { + const client = await CosmWasmClient.connect(mockServer.url); + await checkBalanceChannelIbc( + { + source: "wasm." + IBC_WASM_CONTRACT, + channel: channel, + timeout: 3600 + }, + fromToken, + toToken, + amount, + client, + IBC_WASM_CONTRACT + ); + expect(willThrow).toEqual(false); + } catch (error) { + expect(willThrow).toEqual(true); + } finally { + await mockServer.stop(); + } + }, + 50000 + ); + it.each([ [oraichainTokens.find((t) => t.coinGeckoId === "airight")!, 10000000], [oraichainTokens.find((t) => t.coinGeckoId === "oraichain-token")!, 0] @@ -654,6 +791,133 @@ describe("test universal swap handler functions", () => { } ); + it.each<[TokenItemType, string, SwapDirection, number]>([ + [flattenTokens.find((t) => t.coinGeckoId === "oraichain-token")!, "", SwapDirection.From, 36], + [flattenTokens.find((t) => t.coinGeckoId === "oraichain-token")!, "", SwapDirection.To, 33], + [flattenTokens.find((t) => t.coinGeckoId === "airight")!, "", SwapDirection.From, 35], + [flattenTokens.find((t) => t.coinGeckoId === "airight")!, "orai123", SwapDirection.From, 0], + [flattenTokens.find((t) => t.coinGeckoId === "airight")!, "orai", SwapDirection.From, 7], + [flattenTokens.find((t) => t.coinGeckoId === "airight")!, "", SwapDirection.To, 33] + ])( + "test-universal-swap-filterNonPoolEvmTokens", + async (token: TokenItemType, searchTokenName: string, direction: SwapDirection, expectedLength: number) => { + const res = UniversalSwapHelper.filterNonPoolEvmTokens( + token.chainId, + token.coinGeckoId, + token.denom, + searchTokenName, + direction + ); + expect(res.length).toBe(expectedLength); + } + ); + + it.each<[TokenItemType, TokenItemType, SwapOperation[]]>([ + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "oraichain-token")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "oraidex")!, + [ + { + orai_swap: { + offer_asset_info: USDC_INFO, + ask_asset_info: ORAIX_INFO + } + } + ] + ], + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "neutaro")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "oraidex")!, + [ + { + orai_swap: { + offer_asset_info: NEUTARO_INFO, + ask_asset_info: USDC_INFO + } + }, + { + orai_swap: { + offer_asset_info: USDC_INFO, + ask_asset_info: ORAIX_INFO + } + } + ] + ] + ])("test-buildSwapMsgsFromSmartRoute()-for-native-token-%s", async (fromToken, toToken, swapOps) => { + const universalSwap = new FakeUniversalSwapHandler({ + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken + }); + const routerContract = network.router; + const routes = [ + { + swapAmount: "10000", + returnAmount: "10000", + swapOps + } + ]; + const result = universalSwap.buildSwapMsgsFromSmartRoute(routes, fromToken, cosmosSenderAddress, routerContract); + expect(result).toHaveLength(1); + expect(result[0].contractAddress).toBe(routerContract); + expect(result[0].msg.execute_swap_operations.operations).toEqual(swapOps); + expect(result[0].msg.execute_swap_operations.to).toEqual(cosmosSenderAddress); + }); + + it.each<[TokenItemType, TokenItemType, SwapOperation[], string]>([ + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "usd-coin")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "oraidex")!, + [ + { + orai_swap: { + offer_asset_info: USDC_INFO, + ask_asset_info: ORAIX_INFO + } + } + ], + "10000" + ], + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "och")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "oraidex")!, + [ + { + orai_swap: { + offer_asset_info: ORAIXOCH_INFO, + ask_asset_info: ORAI_INFO + } + }, + { + orai_swap: { + offer_asset_info: ORAI_INFO, + ask_asset_info: ORAIX_INFO + } + } + ], + "10000" + ] + ])("test-buildSwapMsgsFromSmartRoute()-for-cw20-token-%s", async (fromToken, toToken, swapOps, swapAmount) => { + const universalSwap = new FakeUniversalSwapHandler({ + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken + }); + const routerContract = network.router; + const routes = [ + { + swapAmount, + returnAmount: "9999", + swapOps + } + ]; + const result = universalSwap.buildSwapMsgsFromSmartRoute(routes, fromToken, cosmosSenderAddress, routerContract); + expect(result).toHaveLength(1); + expect(result[0].contractAddress).toBe(fromToken.contractAddress); + expect(result[0].msg.send.contract).toBe(routerContract); + expect(result[0].msg.send.amount).toBe(swapAmount); + }); + it.each<[UniversalSwapType, string]>([ ["oraichain-to-oraichain", "swap"], ["oraichain-to-evm", "swapAndTransferToOtherNetworks"], @@ -998,6 +1262,32 @@ describe("test universal swap handler functions", () => { } ); + it.each<[TokenItemType, TokenItemType, string, number]>([ + [ + flattenTokens.find((t) => t.chainId === "0x01" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "0x38" && t.coinGeckoId === "tether")!, + "1000000", + 1 + ], + [ + flattenTokens.find((t) => t.chainId === "0x01" && t.coinGeckoId === "oraichain-token")!, + flattenTokens.find((t) => t.chainId === "0x38" && t.coinGeckoId === "tether")!, + "2000000", + 2 + ] + ])("test-simulateSwapEvm-with-mock", async (fromInfo, toInfo, amount, displayAmount) => { + //@ts-ignore + jest.spyOn(UniversalSwapHelper, "getJsonRpcProvider").mockReturnValue(true); + + //@ts-ignore + jest.spyOn(UniversalSwapHelper, "connectFactoryRouteUniswapV2").mockReturnValue({ + getAmountsOut: jest.fn().mockResolvedValue([[amount]]) + }); + + const res = await UniversalSwapHelper.simulateSwapEvm({ fromInfo, toInfo, amount }); + expect(res.displayAmount).toBe(displayAmount); + }); + it.each<[boolean, boolean, boolean, string]>([ [false, false, false, "1"], [false, true, false, "2"], @@ -1044,6 +1334,614 @@ describe("test universal swap handler functions", () => { expect(ibcInfo.source).toEqual(`wasm.${ibcWasmContract}`); }); + it("test-processUniversalSwap-swap()-for-%s", async () => { + const generateMsgsSwapMock = jest.fn(() => ["msg1", "msg2"]); + const executeMultipleMock = jest.fn(() => Promise.resolve("executeMultipleMock")); + const getCosmWasmClientMock = jest.fn(() => Promise.resolve({ client: { executeMultiple: executeMultipleMock } })); + const cosmosWalletMock = { getCosmWasmClient: getCosmWasmClientMock }; + const networks = { rpc: network.rpc, fee: { gasPrice: network.fee.gasPrice, denom: network.denom } }; + const fromToken = flattenTokens.find((item) => item.coinGeckoId === "airight" && item.chainId === "Oraichain")!; + const toToken = flattenTokens.find((item) => item.coinGeckoId === "tether" && item.chainId === "Oraichain")!; + const sender = { cosmos: "orai1234" }; + const universalSwap = new FakeUniversalSwapHandler( + { + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + sender + }, + { + cosmosWallet: cosmosWalletMock as any + } + ); + universalSwap.generateMsgsSwap = generateMsgsSwapMock as any; + + await universalSwap.swap(); + + expect(generateMsgsSwapMock).toHaveBeenCalled(); + expect(getCosmWasmClientMock).toHaveBeenCalledWith( + { chainId: "Oraichain", rpc: networks.rpc }, + { gasPrice: expect.any(Object) } + ); + + expect(executeMultipleMock).toHaveBeenCalledWith(sender.cosmos, ["msg1", "msg2"], "auto"); + }); + + it.each<[TokenItemType, TokenItemType, string]>([ + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "cosmoshub-4" && t.coinGeckoId === "cosmos")!, + "oraichain-to-cosmos" + ], + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "0x38" && t.coinGeckoId === "tether")!, + "oraichain-to-evm" + ] + ])("test-processUniversalSwap-swapAndTransferToOtherNetworks()-for-%s", async (fromToken, toToken, swapRoute) => { + const networks = { rpc: network.rpc, fee: { gasPrice: network.fee.gasPrice, denom: network.denom } }; + const combineSwapMsgOraichain = jest.fn(() => ["msg1", "msg2"]); + const signAndBroadcastMock = jest.fn(() => Promise.resolve("signAndBroadcastMock")); + const getCosmWasmClientMock = jest.fn(() => + Promise.resolve({ + client: { signAndBroadcast: signAndBroadcastMock } + }) + ); + const cosmosWalletMock = { + getCosmWasmClient: getCosmWasmClientMock, + getKeplrAddr: () => { + return "orai1234"; + } + }; + + const sender = { + cosmos: "orai1234", + evm: "0x1234" + }; + const universalSwap = new FakeUniversalSwapHandler( + { + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + sender + }, + { + cosmosWallet: cosmosWalletMock as any + } + ); + + if (swapRoute === "oraichain-to-cosmos") universalSwap.combineSwapMsgOraichain = combineSwapMsgOraichain as any; + if (swapRoute === "oraichain-to-evm") universalSwap.combineMsgEvm = combineSwapMsgOraichain as any; + + await universalSwap.swapAndTransferToOtherNetworks(swapRoute as UniversalSwapType); + + expect(combineSwapMsgOraichain).toHaveBeenCalled(); + expect(getCosmWasmClientMock).toHaveBeenCalledWith( + { chainId: network.chainId, rpc: networks.rpc }, + { gasPrice: expect.any(Object) } + ); + expect(signAndBroadcastMock).toHaveBeenCalledWith(sender.cosmos, ["msg1", "msg2"], "auto"); + }); + + it.each<[TokenItemType, TokenItemType, string]>([ + [ + flattenTokens.find((t) => t.chainId === "cosmoshub-4" && t.coinGeckoId === "cosmos")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + cosmosSenderAddress + ], + [ + flattenTokens.find((t) => t.chainId === "noble-1" && t.coinGeckoId === "usd-coin")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + nobleSenderAddress + ] + ])("test-processUniversalSwap-swapCosmosToOtherNetwork()-for-%s", async (fromToken, toToken, destinationReceiver) => { + const signAndBroadcastMock = jest.fn(() => Promise.resolve("signAndBroadcastMock")); + const spy = jest.spyOn(UniversalSwapHelper, "getIbcInfo"); + spy.mockReturnValue(ibcInfos[fromToken.chainId][toToken.chainId]); + const getCosmWasmClientMock = jest.fn(() => + Promise.resolve({ + client: { signAndBroadcast: signAndBroadcastMock } + }) + ); + const cosmosWalletMock = { + getCosmWasmClient: getCosmWasmClientMock, + getKeplrAddr: () => { + return destinationReceiver; + } + }; + const sender = { + cosmos: destinationReceiver, + evm: "0x1234" + }; + const universalSwap = new FakeUniversalSwapHandler( + { + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + sender + }, + { + cosmosWallet: cosmosWalletMock as any + } + ); + await universalSwap.swapCosmosToOtherNetwork(destinationReceiver); + expect(spy).toHaveBeenCalled(); + expect(getCosmWasmClientMock).toHaveBeenCalledWith( + { chainId: fromToken.chainId, rpc: fromToken.rpc }, + { gasPrice: expect.any(Object) } + ); + expect(signAndBroadcastMock).toHaveBeenCalled(); + }); + + it.each<[TokenItemType, TokenItemType, boolean]>([ + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "oraichain-token")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + false + ] + ])( + "test-universal-swap-func-swap()-with-mock-%", + async (fromToken, toToken, willThrow) => { + const mockServer = await mockJsonRpcServer(); + const executeMultipleMock = jest.fn(() => + mockServer + .forJsonRpcRequest({ + method: "broadcast_tx_sync" + }) + .thenSendJsonRpcResult(mockResponse) + ); + const getCosmWasmClientMock = jest.fn(() => + Promise.resolve({ client: { executeMultiple: executeMultipleMock } }) + ); + const cosmosWalletMock = { getCosmWasmClient: getCosmWasmClientMock }; + const universalSwap = new FakeUniversalSwapHandler( + { + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + sender: { cosmos: "orai1234" } + }, + { + cosmosWallet: cosmosWalletMock as any + } + ); + try { + await universalSwap.swap(); + expect(willThrow).toEqual(false); + } catch (error) { + expect(willThrow).toEqual(true); + } finally { + await mockServer.stop(); + } + }, + 50000 + ); + + it.each<[TokenItemType, TokenItemType, string, boolean]>([ + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "cosmoshub-4" && t.coinGeckoId === "cosmos")!, + "oraichain-to-cosmos", + false + ], + [ + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "0x38" && t.coinGeckoId === "tether")!, + "oraichain-to-evm", + false + ] + ])( + "test-universal-swap-func-swapAndTransferToOtherNetworks()-with-mock-%", + async (fromToken, toToken, swapRoute, willThrow) => { + const mockServer = await mockJsonRpcServer(); + const signAndBroadcastMock = jest.fn(() => + mockServer + .forJsonRpcRequest({ + method: "broadcast_tx_sync" + }) + .thenSendJsonRpcResult(mockResponse) + ); + + const getCosmWasmClientMock = jest.fn(() => + Promise.resolve({ + client: { signAndBroadcast: signAndBroadcastMock } + }) + ); + const cosmosWalletMock = { + getCosmWasmClient: getCosmWasmClientMock, + getKeplrAddr: () => { + return "orai1234"; + } + }; + + const sender = { + cosmos: "orai1234", + evm: "0x1234" + }; + + const universalSwap = new FakeUniversalSwapHandler( + { + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + sender + }, + { + cosmosWallet: cosmosWalletMock as any + } + ); + + try { + await universalSwap.swapAndTransferToOtherNetworks(swapRoute as UniversalSwapType); + expect(willThrow).toEqual(false); + } catch (error) { + expect(willThrow).toEqual(true); + } finally { + await mockServer.stop(); + } + }, + 50000 + ); + + it.each<[TokenItemType, TokenItemType, string, boolean]>([ + [ + flattenTokens.find((t) => t.chainId === "cosmoshub-4" && t.coinGeckoId === "cosmos")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + cosmosSenderAddress, + false + ], + [ + flattenTokens.find((t) => t.chainId === "noble-1" && t.coinGeckoId === "usd-coin")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + nobleSenderAddress, + false + ] + ])( + "test-processUniversalSwap-swapCosmosToOtherNetwork()-with-mock-%s", + async (fromToken, toToken, destinationReceiver, willThrow) => { + const mockServer = await mockJsonRpcServer(); + const spy = jest.spyOn(UniversalSwapHelper, "getIbcInfo"); + spy.mockReturnValue(ibcInfos[fromToken.chainId][toToken.chainId]); + const signAndBroadcastMock = jest.fn(() => + mockServer + .forJsonRpcRequest({ + method: "broadcast_tx_sync" + }) + .thenSendJsonRpcResult(mockResponse) + ); + + const getCosmWasmClientMock = jest.fn(() => + Promise.resolve({ + client: { signAndBroadcast: signAndBroadcastMock } + }) + ); + const cosmosWalletMock = { + getCosmWasmClient: getCosmWasmClientMock, + getKeplrAddr: () => { + return destinationReceiver; + } + }; + const sender = { + cosmos: destinationReceiver, + evm: "0x1234" + }; + const universalSwap = new FakeUniversalSwapHandler( + { + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + sender + }, + { + cosmosWallet: cosmosWalletMock as any + } + ); + + try { + await universalSwap.swapCosmosToOtherNetwork(destinationReceiver); + expect(willThrow).toEqual(false); + } catch (error) { + expect(willThrow).toEqual(true); + } finally { + await mockServer.stop(); + } + } + ); + + it.each<[TokenItemType, TokenItemType, boolean, boolean]>([ + [ + flattenTokens.find((t) => t.chainId === "0x01" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + false, + false + ], + [ + flattenTokens.find((t) => t.chainId === "0x2b6653dc" && t.coinGeckoId === "tron")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + true, + false + ] + ])("test-transferToGravity()-for-%s", async (fromToken, toToken, isTron, willThrow) => { + const evmWalletMock = { + isTron: (chainId) => Number(chainId) == Networks.tron, + checkTron: jest.fn().mockReturnValue(true), + checkEthereum: jest.fn().mockReturnValue(true), + submitTronSmartContract: jest.fn().mockResolvedValue({ transactionHash: "mockTransactionHash" }), + ethToTronAddress: jest.fn().mockReturnValue(tronAddress), + tronToEthAddress: jest.fn().mockReturnValue(evmAddress), + getSigner: jest.fn().mockReturnValue("getSigner") + }; + + const sender = { + cosmos: "orai1234", + evm: evmAddress, + tron: tronAddress + }; + + const universalSwap = new FakeUniversalSwapHandler( + { + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + sender + }, + { + evmWallet: evmWalletMock as any + } + ); + + const spy = jest.spyOn(universalSwap, "connectBridgeFactory"); + if (!isTron) { + //@ts-ignore + spy.mockReturnValue({ + sendToCosmos: jest.fn().mockResolvedValue({ + hash: "mockHash", + blockNumber: 1, + blockHash: "mockBlockHash", + timestamp: 1, + confirmations: 1, + from: "mockFromAddress", + raw: "mockRawTransaction", + wait: jest.fn().mockResolvedValue("mockTransactionHash") + }) + }); + } + + try { + await universalSwap.transferToGravity(evmAddress); + if (!isTron) expect(spy).toHaveBeenCalled(); + expect(willThrow).toBe(false); + } catch (error) { + expect(willThrow).toBe(true); + } + }); + + it.each<[TokenItemType, TokenItemType, boolean]>([ + [ + flattenTokens.find((t) => t.chainId === "0x38" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "0x01" && t.coinGeckoId === "tether")!, + false + ], + [ + flattenTokens.find((t) => t.chainId === "0x38" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "airight")!, + false + ], + [ + flattenTokens.find((t) => t.chainId === "0x2b6653dc" && t.coinGeckoId === "tron")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + false + ] + ])("test-transferAndSwap()-for-%s", async (fromToken, toToken, willThrow) => { + const evmWalletMock = { + getFinalEvmAddress: jest.fn().mockReturnValue(fromToken.chainId === "0x2b6653dc" ? tronAddress : evmAddress), + isTron: (chainId) => Number(chainId) == Networks.tron, + switchNetwork: jest.fn().mockResolvedValue(true) + }; + + let mockValue = false; + if (fromToken.chainId === toToken.chainId) mockValue = true; + jest.spyOn(UniversalSwapHelper, "isEvmSwappable").mockReturnValue(mockValue); + jest.spyOn(UniversalSwapHelper, "isSupportedNoPoolSwapEvm").mockReturnValue(mockValue); + jest + .spyOn(UniversalSwapHelper, "checkBalanceIBCOraichain") + .mockReturnValue(new Promise((resolve) => resolve("checkBalanceIBCOraichain" as any))); + jest.spyOn(UniversalSwapHelper, "checkFeeRelayer").mockReturnValue(new Promise((resolve) => resolve(true))); + + const getCosmWasmClientMock = jest.fn(() => Promise.resolve({ client: {} })); + const cosmosWalletMock = { getCosmWasmClient: getCosmWasmClientMock }; + + const sender = { + cosmos: "orai1234", + evm: evmAddress, + tron: tronAddress + }; + + const universalSwap = new FakeUniversalSwapHandler( + { + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + sender + }, + { + evmWallet: evmWalletMock as any, + cosmosWallet: cosmosWalletMock as any + } + ); + + jest + .spyOn(universalSwap, "transferEvmToIBC") + .mockReturnValue(new Promise((resolve) => resolve({ transactionHash: "transactionHash" }))); + + try { + const res = await universalSwap.transferAndSwap(fromToken.chainId === "0x2b6653dc" ? tronAddress : evmAddress); + expect(res.transactionHash).toBe("transactionHash"); + expect(willThrow).toBe(false); + } catch (error) { + expect(willThrow).toBe(true); + } + }); + + it.each<[TokenItemType, TokenItemType, boolean, boolean]>([ + [ + flattenTokens.find((t) => t.chainId === "0x01" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + false, + false + ], + [ + flattenTokens.find((t) => t.chainId === "0x2b6653dc" && t.coinGeckoId === "tron")!, + flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!, + true, + false + ] + ])("test-transferEvmToIBC()-for-%s", async (fromToken, toToken, isTron, willThrow) => { + const evmWalletMock = { + checkOrIncreaseAllowance: jest.fn().mockReturnValue(true), + getFinalEvmAddress: jest.fn().mockReturnValue(isTron ? tronAddress : evmAddress) + }; + const sender = { + cosmos: "orai1234", + evm: evmAddress, + tron: tronAddress + }; + const universalSwap = new FakeUniversalSwapHandler( + { + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + sender + }, + { + evmWallet: evmWalletMock as any + } + ); + + jest + .spyOn(universalSwap, "transferToGravity") + .mockReturnValue(new Promise((resolve) => resolve({ transactionHash: "transactionHash" }))); + + try { + const res = await universalSwap.transferEvmToIBC(isTron ? tronAddress : evmAddress); + expect(res.transactionHash).toBe("transactionHash"); + expect(willThrow).toBe(false); + } catch (error) { + expect(willThrow).toBe(true); + } + }); + + it.each<[TokenItemType, TokenItemType, string, string[], boolean]>([ + [ + flattenTokens.find((t) => t.chainId === "0x01" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "0x01" && t.coinGeckoId === "usd-coin")!, + "", + [USDC_ETH_CONTRACT, USDT_ETH_CONTRACT], + false + ], + [ + flattenTokens.find((t) => t.chainId === "0x38" && t.coinGeckoId === "tether")!, + flattenTokens.find((t) => t.chainId === "0x38" && t.coinGeckoId === "airight")!, + "", + [USDT_BSC_CONTRACT, WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT], + false + ] + ])("test-evmSwap()-for-%s", async (fromToken, toToken, toTokenContractAddr, evmSwapRoute, willThrow) => { + const finalEvmAddress = fromToken.chainId === "0x2b6653dc" ? tronAddress : evmAddress; + const evmWalletMock = { + checkOrIncreaseAllowance: jest.fn().mockReturnValue(true), + getFinalEvmAddress: jest.fn().mockReturnValue(finalEvmAddress), + getSigner: jest.fn().mockReturnValue("getSigner") + }; + + const sender = { + cosmos: "orai1234", + evm: evmAddress, + tron: tronAddress + }; + const universalSwap = new FakeUniversalSwapHandler( + { + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + sender + }, + { + evmWallet: evmWalletMock as any + } + ); + + const spyGetAddressWithEthers = jest.spyOn(universalSwap, "getAddressWithEthers"); + spyGetAddressWithEthers.mockReturnValue(evmAddress); + + const spyConnectBridgeFactory = jest.spyOn(universalSwap, "connectBridgeFactory"); + + const mockResponse = { + hash: "mockHash", + blockNumber: 1, + blockHash: "mockBlockHash", + timestamp: 1, + confirmations: 1, + from: "mockFromAddress", + raw: "mockRawTransaction" + }; + //@ts-ignore + spyConnectBridgeFactory.mockReturnValue({ + sendToCosmos: jest.fn().mockResolvedValue({ + ...mockResponse, + wait: jest.fn().mockResolvedValue("mockWaitSendToCosmos") + }), + swapRouter: jest.fn().mockResolvedValue("mockSwapRouter"), + bridgeFromETH: jest.fn().mockResolvedValue("mockBridgeFromETH"), + bridgeFromERC20: jest.fn().mockResolvedValue({ + ...mockResponse, + wait: jest.fn().mockResolvedValue("mockWaitBridgeFromERC20") + }) + }); + jest.spyOn(dexCommonHelper, "calculateMinReceive").mockReturnValue(minimumReceive); + jest + .spyOn(universalSwap, "transferToGravity") + .mockReturnValue(new Promise((resolve) => resolve({ transactionHash: "transactionHash" }))); + + //@ts-ignore + jest.spyOn(UniversalSwapHelper, "connectFactoryRouteUniswapV2").mockReturnValue({ + swapExactTokensForETH: jest.fn().mockResolvedValue({ + ...mockResponse, + wait: jest.fn().mockResolvedValue("mockSwapExactTokensForETH") + }) + }); + + //@ts-ignore + jest.spyOn(UniversalSwapHelper, "getEvmSwapRoute").mockResolvedValueOnce(evmSwapRoute); + + try { + await universalSwap.evmSwap({ + fromToken, + toTokenContractAddr, + fromAmount: 0, + address: { + metamaskAddress: evmAddress, + tronAddress: tronAddress + }, + slippage: 1, + destination: "", + simulatePrice: "1000" + }); + + if (!fromToken.contractAddress) { + expect(spyConnectBridgeFactory).toHaveBeenCalled(); + } else if (!toTokenContractAddr) { + expect(UniversalSwapHelper.getEvmSwapRoute).toHaveBeenCalled(); + expect(UniversalSwapHelper.connectFactoryRouteUniswapV2).toHaveBeenCalled(); + } else { + expect(spyConnectBridgeFactory).toHaveBeenCalled(); + } + expect(willThrow).toBe(false); + } catch (error) { + expect(willThrow).toBe(true); + } + }); + // it("test-swap()", async () => { // const universalSwap = new FakeUniversalSwapHandler({ // ...universalSwapData diff --git a/packages/universal-swap/tests/jsonrpc-mock.spec.ts b/packages/universal-swap/tests/jsonrpc-mock.spec.ts new file mode 100644 index 00000000..3aa45600 --- /dev/null +++ b/packages/universal-swap/tests/jsonrpc-mock.spec.ts @@ -0,0 +1,41 @@ +import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; +import { GasPrice, SigningStargateClient } from "@cosmjs/stargate"; +import { ORAI, mockJsonRpcServer } from "@oraichain/oraidex-common"; +import "dotenv/config"; +import * as mockttp from "mockttp"; +// configuration + +describe("test-nock", () => { + let server: mockttp.Mockttp; + beforeEach(async () => { + server = await mockJsonRpcServer(); + }); + afterEach(async () => { + await server.stop(); + }); + it("test-mock-rpc-post", async () => { + const wallet = await DirectSecp256k1HdWallet.generate(12, { prefix: ORAI }); + const accounts = await wallet.getAccounts(); + const stargate = await SigningStargateClient.connectWithSigner(server.url, wallet, { + gasPrice: GasPrice.fromString("0.001orai") + }); + const result = await stargate.sendTokens( + accounts[0].address, + accounts[0].address, + [{ amount: "1", denom: ORAI }], + "auto" + ); + expect(result.transactionHash).toEqual("2F59A5318ED6350976244EAD25CB78ACFEBC1B9C9A96809269A793EDFB529065"); + expect(result.height).toEqual(1); + }); + + it("test-mock-rpc-getAccount", async () => { + const wallet = await DirectSecp256k1HdWallet.generate(12, { prefix: ORAI }); + const accounts = await wallet.getAccounts(); + const stargate = await SigningStargateClient.connectWithSigner(server.url, wallet, { + gasPrice: GasPrice.fromString("0.001orai") + }); + const result = await stargate.getAccount(accounts[0].address); + expect(result.address).toEqual("orai1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejvfgs7g"); + }); +}); diff --git a/packages/universal-swap/tests/test-common.ts b/packages/universal-swap/tests/test-common.ts index 4ee07b8a..9ffc4ca2 100644 --- a/packages/universal-swap/tests/test-common.ts +++ b/packages/universal-swap/tests/test-common.ts @@ -1,8 +1,8 @@ +import * as commonArtifacts from "@oraichain/common-contracts-build"; +import { Cw20Coin, CwIcs20LatestClient } from "@oraichain/common-contracts-sdk"; import { SimulateCosmWasmClient } from "@oraichain/cw-simulate"; -import { OraiswapTokenClient } from "@oraichain/oraidex-contracts-sdk"; -import { CwIcs20LatestClient , Cw20Coin } from "@oraichain/common-contracts-sdk"; import * as oraidexArtifacts from "@oraichain/oraidex-contracts-build"; -import * as commonArtifacts from "@oraichain/common-contracts-build"; +import { OraiswapTokenClient } from "@oraichain/oraidex-contracts-sdk"; export const testSenderAddress = "orai1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejvfgs7g"; diff --git a/yarn.lock b/yarn.lock index d0f33f15..c6b89440 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6219,7 +6219,14 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async@^2.6.2: +async-mutex@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482" + integrity sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA== + dependencies: + tslib "^2.4.0" + +async@^2.6.2, async@^2.6.4: version "2.6.4" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== @@ -12329,7 +12336,7 @@ mkdirp-classic@^0.5.2: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@^0.5.4, mkdirp@^0.5.5: +mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@^0.5.6: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -12433,6 +12440,52 @@ mockttp@^3.1.0: uuid "^8.3.2" ws "^8.8.0" +mockttp@^3.10.2: + version "3.10.2" + resolved "https://registry.yarnpkg.com/mockttp/-/mockttp-3.10.2.tgz#c4226f20d9c2528549863137515b2c3d4c8f0c3d" + integrity sha512-0tBe/r5VN1LaybYDNGR0Vgl1WSdAzgNDyH/lQ34MY6fzmTStGB3r9K5l+4YtFENVBfopsW77GRl1D81CuXwyvw== + dependencies: + "@graphql-tools/schema" "^8.5.0" + "@graphql-tools/utils" "^8.8.0" + "@httptoolkit/httpolyglot" "^2.2.1" + "@httptoolkit/subscriptions-transport-ws" "^0.11.2" + "@httptoolkit/websocket-stream" "^6.0.1" + "@types/cors" "^2.8.6" + "@types/node" "*" + async-mutex "^0.5.0" + base64-arraybuffer "^0.1.5" + body-parser "^1.15.2" + cacheable-lookup "^6.0.0" + common-tags "^1.8.0" + connect "^3.7.0" + cors "^2.8.4" + cors-gate "^1.1.3" + cross-fetch "^3.1.5" + destroyable-server "^1.0.0" + express "^4.14.0" + graphql "^14.0.2 || ^15.5" + graphql-http "^1.22.0" + graphql-subscriptions "^1.1.0" + graphql-tag "^2.12.6" + http-encoding "^1.5.1" + http2-wrapper "^2.2.1" + https-proxy-agent "^5.0.1" + isomorphic-ws "^4.0.1" + lodash "^4.16.4" + lru-cache "^7.14.0" + native-duplexpair "^1.0.0" + node-forge "^1.2.1" + pac-proxy-agent "^7.0.0" + parse-multipart-data "^1.4.0" + performance-now "^2.1.0" + portfinder "^1.0.32" + read-tls-client-hello "^1.0.0" + semver "^7.5.3" + socks-proxy-agent "^7.0.0" + typed-error "^3.0.2" + uuid "^8.3.2" + ws "^8.8.0" + modify-values@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" @@ -13622,6 +13675,15 @@ portfinder@1.0.28: debug "^3.1.1" mkdirp "^0.5.5" +portfinder@^1.0.32: + version "1.0.32" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81" + integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg== + dependencies: + async "^2.6.4" + debug "^3.2.7" + mkdirp "^0.5.6" + possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f"