From 0998fbd6c3e7844519855b4303c8ece731c57a56 Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Sat, 30 Mar 2024 17:15:00 -0700 Subject: [PATCH 01/22] feat: use @swc/test to reduce test time --- jest.config.js | 5 +-- package.json | 6 ++-- packages/universal-swap/src/helper.ts | 24 ++++++------- packages/universal-swap/src/types.ts | 3 +- packages/universal-swap/tests/index.spec.ts | 40 +++++++++------------ yarn.lock | 18 +++++++++- 6 files changed, 53 insertions(+), 43 deletions(-) diff --git a/jest.config.js b/jest.config.js index 3c3b8fb5..fbb242e1 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,8 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ +// jest.config.js module.exports = { transform: { - "^.+\\.ts?$": ["ts-jest", { isolatedModules: true }] + "^.+packages/universal-swap/src/helper\\.ts$": "ts-jest", // use ts-jest for tests that have spyOn mocking + "^.+\\.ts?$": ["@swc/jest"] }, testEnvironment: "node", modulePathIgnorePatterns: ["/dist/", "/packages/ibc-routing"], diff --git a/package.json b/package.json index 2fa2c47e..a3150d05 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,11 @@ "@oraichain/common-contracts-sdk": "1.0.31" }, "devDependencies": { + "@babel/traverse": "7.24.1", "@cosmjs/encoding": "0.31.3", "@oraichain/cw-simulate": "^2.8.68", + "@swc/core": "^1.4.11", + "@swc/jest": "^0.2.36", "@types/jest": "^29.5.12", "@types/lodash": "^4.17.0", "@types/node": "^20.11.30", @@ -55,8 +58,7 @@ "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", "typedoc": "^0.25.12", - "typescript": "5.3.2", - "@babel/traverse": "7.24.1" + "typescript": "5.3.2" }, "version": "1.0.1" } diff --git a/packages/universal-swap/src/helper.ts b/packages/universal-swap/src/helper.ts index f6d021bc..85d127ff 100644 --- a/packages/universal-swap/src/helper.ts +++ b/packages/universal-swap/src/helper.ts @@ -63,7 +63,7 @@ import { import { SwapOperation } from "@oraichain/oraidex-contracts-sdk"; import { isEqual } from "lodash"; import { ethers } from "ethers"; -import { Amount, CwIcs20LatestQueryClient, CwIcs20LatestReadOnlyInterface } from "@oraichain/common-contracts-sdk"; +import { Amount, CwIcs20LatestQueryClient } 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"; @@ -691,13 +691,13 @@ export const checkBalanceIBCOraichain = async ( } }; -export function filterNonPoolEvmTokens( +export const filterNonPoolEvmTokens = ( chainId: string, coingeckoId: CoinGeckoId, denom: string, searchTokenName: string, direction: SwapDirection // direction = to means we are filtering to tokens -) { +) => { // basic filter. Dont include itself & only collect tokens with searched letters const listTokens = direction === SwapDirection.From ? swapFromTokens : swapToTokens; let filteredToTokens = listTokens.filter( @@ -732,13 +732,13 @@ export function filterNonPoolEvmTokens( if (isSupportedNoPoolSwapEvm(t.coinGeckoId)) return t.chainId === chainId; return true; }); -} +}; -export function generateConvertErc20Cw20Message( +export const generateConvertErc20Cw20Message = ( amounts: AmountDetails, tokenInfo: TokenItemType, sender?: string -): ExecuteInstruction[] { +): ExecuteInstruction[] => { if (!tokenInfo.evmDenoms) return []; const subAmounts = getSubAmountDetails(amounts, tokenInfo); // we convert all mapped tokens to cw20 to unify the token @@ -757,14 +757,14 @@ export function generateConvertErc20Cw20Message( } } return []; -} +}; -export function generateConvertCw20Erc20Message( +export const generateConvertCw20Erc20Message = ( amounts: AmountDetails, tokenInfo: TokenItemType, sender: string, sendCoin: Coin -): ExecuteInstruction[] { +): ExecuteInstruction[] => { if (!tokenInfo.evmDenoms) return []; // we convert all mapped tokens to cw20 to unify the token for (const denom of tokenInfo.evmDenoms) { @@ -795,9 +795,9 @@ export function generateConvertCw20Erc20Message( } } return []; -} +}; -export function generateConvertMsgs(data: ConvertType): ExecuteInstruction { +export const generateConvertMsgs = (data: ConvertType): ExecuteInstruction => { const { type, sender, inputToken, inputAmount } = data; let funds: Coin[] | null; // for withdraw & provide liquidity methods, we need to interact with the oraiswap pair contract @@ -870,4 +870,4 @@ export function generateConvertMsgs(data: ConvertType): ExecuteInstruction { }; return msg; -} +}; diff --git a/packages/universal-swap/src/types.ts b/packages/universal-swap/src/types.ts index 5b8d6e6c..cbb7642c 100644 --- a/packages/universal-swap/src/types.ts +++ b/packages/universal-swap/src/types.ts @@ -1,6 +1,5 @@ -import { CwIcs20LatestClient, CwIcs20LatestReadOnlyInterface } from "@oraichain/common-contracts-sdk"; import { AmountDetails, CosmosWallet, EvmWallet, TokenItemType } from "@oraichain/oraidex-common"; -import { OraiswapRouterInterface, OraiswapRouterReadOnlyInterface, Uint128 } from "@oraichain/oraidex-contracts-sdk"; +import { Uint128 } from "@oraichain/oraidex-contracts-sdk"; export type UniversalSwapType = | "other-networks-to-oraichain" diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index 725ac865..bf019f81 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -23,16 +23,14 @@ import { USDC_CONTRACT } 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 * as dexCommonNetwork from "@oraichain/oraidex-common/build/network"; // import like this to enable jest.spyOn & avoid redefine property error import * as universalHelper from "../src/helper"; -import { UniversalSwapHandler } from "../src/index"; -import { AccountData, DirectSecp256k1HdWallet, EncodeObject, OfflineSigner } from "@cosmjs/proto-signing"; +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 { SigningCosmWasmClient, SigningCosmWasmClientOptions, toBinary } from "@cosmjs/cosmwasm-stargate"; +import { toBinary } from "@cosmjs/cosmwasm-stargate"; import { ibcInfos, oraichain2oraib } from "@oraichain/oraidex-common/build/ibc-info"; import { OraiswapFactoryClient, @@ -45,20 +43,10 @@ import { CWSimulateApp, GenericError, IbcOrder, IbcPacket, SimulateCosmWasmClien import { CwIcs20LatestClient } from "@oraichain/common-contracts-sdk"; import bech32 from "bech32"; import { UniversalSwapConfig, UniversalSwapData, UniversalSwapType } from "../src/types"; -import { - checkBalanceChannelIbc, - checkBalanceIBCOraichain, - getBalanceIBCOraichain, - getIbcInfo, - handleSimulateSwap, - simulateSwap, - checkFeeRelayer, - checkFeeRelayerNotOrai -} from "../src/helper"; import { deployIcs20Token, deployToken, testSenderAddress } from "./test-common"; import * as oraidexArtifacts from "@oraichain/oraidex-contracts-build"; import { readFileSync } from "fs"; -import { SigningStargateClientOptions } from "@cosmjs/stargate"; +import { UniversalSwapHandler } from "../src/handler"; describe("test universal swap handler functions", () => { const client = new SimulateCosmWasmClient({ @@ -333,7 +321,7 @@ describe("test universal swap handler functions", () => { contract: IBC_WASM_CONTRACT, amount: simulateAmount, msg: toBinary({ - local_channel_id: getIbcInfo("Oraichain", "noble-1").channel, + local_channel_id: universalHelper.getIbcInfo("Oraichain", "noble-1").channel, remote_address: "noble1234", remote_denom: "uusdc", timeout: IBC_TRANSFER_TIMEOUT, @@ -441,7 +429,7 @@ describe("test universal swap handler functions", () => { ); // TODO: run tests without mocking to simulate actual swap logic jest.spyOn(universalHelper, "simulateSwap").mockResolvedValue({ amount: relayerFeeAmount }); - const result = await checkFeeRelayer({ + const result = await universalHelper.checkFeeRelayer({ originalFromToken: originalFromToken as TokenItemType, fromAmount: 1, relayerFee: { @@ -463,7 +451,7 @@ describe("test universal swap handler functions", () => { const originalFromToken = oraichainTokens.find((item) => item.coinGeckoId === fromDenom); // TODO: run tests without mocking to simulate actual swap jest.spyOn(universalHelper, "simulateSwap").mockResolvedValue({ amount: mockSimulateAmount }); - const result = await checkFeeRelayerNotOrai({ + const result = await universalHelper.checkFeeRelayerNotOrai({ fromTokenInOrai: originalFromToken as TokenItemType, fromAmount: 1, relayerAmount: mockRelayerFee, @@ -563,7 +551,7 @@ describe("test universal swap handler functions", () => { ] ])("test-universal-swap-checkBalanceChannelIbc-%", async (fromToken, toToken, amount, channel, willThrow) => { try { - await checkBalanceChannelIbc( + await universalHelper.checkBalanceChannelIbc( { source: oraiPort, channel: channel, @@ -589,7 +577,11 @@ describe("test universal swap handler functions", () => { if (mockToken.contractAddress) { if (mockToken.coinGeckoId === "airight") mockToken.contractAddress = airiToken.contractAddress; } - const { balance } = await getBalanceIBCOraichain(mockToken, ics20Contract.client, ics20Contract.contractAddress); + const { balance } = await universalHelper.getBalanceIBCOraichain( + mockToken, + ics20Contract.client, + ics20Contract.contractAddress + ); expect(balance).toEqual(expectedBalance); }); @@ -622,7 +614,7 @@ describe("test universal swap handler functions", () => { jest .spyOn(universalHelper, "getBalanceIBCOraichain") .mockReturnValue(new Promise((resolve) => resolve({ balance: +toAmount }))); - checkBalanceIBCOraichain( + universalHelper.checkBalanceIBCOraichain( from, to, fromAmount, @@ -813,7 +805,7 @@ describe("test universal swap handler functions", () => { ...universalSwapData, originalToToken: flattenTokens.find((t) => t.coinGeckoId === toCoingeckoId)! }); - const ibcInfo = getIbcInfo("Oraichain", "oraibridge-subnet-2"); + const ibcInfo = universalHelper.getIbcInfo("Oraichain", "oraibridge-subnet-2"); const toAddress = "foobar"; const ibcMemo = ""; const msg = universalSwap.generateMsgsIbcWasm(ibcInfo, toAddress, "john doe", ibcMemo)!; @@ -956,7 +948,7 @@ describe("test universal swap handler functions", () => { jest.spyOn(routerClient, "simulateSwapOperations").mockReturnValue(new Promise((resolve) => resolve({ amount }))); const [fromInfo, toInfo] = [toTokenInfo(fromToken!), toTokenInfo(toToken!)]; const query = { fromInfo, toInfo, amount, routerClient }; - const simulateData = await simulateSwap(query); + const simulateData = await universalHelper.simulateSwap(query); expect(simulateData.amount).toEqual(expectedSimulateData); } ); @@ -975,7 +967,7 @@ describe("test universal swap handler functions", () => { const isEvmSwappableSpy = jest.spyOn(universalHelper, "isEvmSwappable"); isSupportedNoPoolSwapEvmSpy.mockReturnValue(isSupportedNoPoolSwapEvmRes); isEvmSwappableSpy.mockReturnValue(isEvmSwappableRes); - const simulateData = await handleSimulateSwap({ + const simulateData = await universalHelper.handleSimulateSwap({ originalFromInfo: oraichainTokens[0], originalToInfo: oraichainTokens[0], originalAmount: 0, diff --git a/yarn.lock b/yarn.lock index 48cf04ba..bf7c4bf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2369,6 +2369,13 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/create-cache-key-function@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" + integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== + dependencies: + "@jest/types" "^29.6.3" + "@jest/environment@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" @@ -4725,7 +4732,7 @@ resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.11.tgz#92fd6d4e2d70bbd4fda438f02310d998db8c7b7c" integrity sha512-0xRFW6K9UZQH2NVC/0pVB0GJXS45lY24f+6XaPBF1YnMHd8A8GoHl7ugyM5yNUTe2AKhSgk5fJV00EJt/XBtdQ== -"@swc/core@^1.3.82": +"@swc/core@^1.3.82", "@swc/core@^1.4.11": version "1.4.11" resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.4.11.tgz#e91f488df9242584cc6f1b034419f8302aeb0c85" integrity sha512-WKEakMZxkVwRdgMN4AMJ9K5nysY8g8npgQPczmjBeNK5In7QEAZAJwnyccrWwJZU0XjVeHn2uj+XbOKdDW17rg== @@ -4749,6 +4756,15 @@ resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== +"@swc/jest@^0.2.36": + version "0.2.36" + resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.36.tgz#2797450a30d28b471997a17e901ccad946fe693e" + integrity sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw== + dependencies: + "@jest/create-cache-key-function" "^29.7.0" + "@swc/counter" "^0.1.3" + jsonc-parser "^3.2.0" + "@swc/types@^0.1.5": version "0.1.6" resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.6.tgz#2f13f748995b247d146de2784d3eb7195410faba" From 54d635ae5ba1dacad7edab3465d420f342763ad8 Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Sat, 30 Mar 2024 17:44:40 -0700 Subject: [PATCH 02/22] chore: parseTxToMsgsAndEvents use stored json not fetch from real api --- packages/oraidex-common/tests/helper.spec.ts | 11 +- .../tests/indexed-tx-raw-log.json | 469 ++++++++++++++++++ .../oraidex-common/tests/indexed-tx-tx.json | 1 + 3 files changed, 476 insertions(+), 5 deletions(-) create mode 100644 packages/oraidex-common/tests/indexed-tx-raw-log.json create mode 100644 packages/oraidex-common/tests/indexed-tx-tx.json diff --git a/packages/oraidex-common/tests/helper.spec.ts b/packages/oraidex-common/tests/helper.spec.ts index 2667ea78..2af25887 100644 --- a/packages/oraidex-common/tests/helper.spec.ts +++ b/packages/oraidex-common/tests/helper.spec.ts @@ -34,6 +34,8 @@ import { import { CoinGeckoId, NetworkChainId } from "../src/network"; import { isFactoryV1 } from "../src/pairs"; import { AmountDetails, TokenItemType, cosmosTokens, flattenTokens, oraichainTokens } from "../src/token"; +import fs from "fs"; +import path from "path"; describe("should helper functions in helper run exactly", () => { const amounts: AmountDetails = { @@ -388,11 +390,10 @@ describe("should helper functions in helper run exactly", () => { expect(reuslt).toEqual([]); // case 2: real tx with multiple msgs and multiple contract calls - const client = await StargateClient.connect("wss://rpc.orai.io"); - const indexedTx = await client.getTx("9B435E4014DEBA5AB80D4BB8F52D766A6C14BFCAC21F821CDB96F4ABB4E29B17"); - client.disconnect(); - - const data = parseTxToMsgsAndEvents(indexedTx!); + // got data from tx hash 9B435E4014DEBA5AB80D4BB8F52D766A6C14BFCAC21F821CDB96F4ABB4E29B17 Oraichain + const rawLog = fs.readFileSync(path.join(__dirname, "indexed-tx-raw-log.json")).toString(); + const tx = Buffer.from(fs.readFileSync(path.join(__dirname, "indexed-tx-tx.json")).toString(), "base64"); + const data = parseTxToMsgsAndEvents({ rawLog, tx } as any); expect(data.length).toEqual(2); expect(data[0].message).toMatchObject({ sender: "orai16hv74w3eu3ek0muqpgp4fekhrqgpzl3hd3qeqk", diff --git a/packages/oraidex-common/tests/indexed-tx-raw-log.json b/packages/oraidex-common/tests/indexed-tx-raw-log.json new file mode 100644 index 00000000..5f3193cd --- /dev/null +++ b/packages/oraidex-common/tests/indexed-tx-raw-log.json @@ -0,0 +1,469 @@ +[ + { + "events": [ + { + "type": "execute", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "_contract_address", "value": "orai1lplapmgqnelqn253stz6kmvm3ulgdaytn89a8mz9y85xq8wd684s6xl3lt" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" } + ] + }, + { + "type": "message", + "attributes": [ + { "key": "action", "value": "/cosmwasm.wasm.v1.MsgExecuteContract" }, + { "key": "module", "value": "wasm" }, + { "key": "sender", "value": "orai16hv74w3eu3ek0muqpgp4fekhrqgpzl3hd3qeqk" } + ] + }, + { + "type": "wasm", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "action", "value": "execute_orderbook_pair" }, + { + "key": "pair", + "value": "orai1lplapmgqnelqn253stz6kmvm3ulgdaytn89a8mz9y85xq8wd684s6xl3lt - orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" + }, + { "key": "total_matched_orders", "value": "2" }, + { "key": "executor_reward", "value": "[]" }, + { "key": "_contract_address", "value": "orai1lplapmgqnelqn253stz6kmvm3ulgdaytn89a8mz9y85xq8wd684s6xl3lt" }, + { "key": "action", "value": "transfer" }, + { "key": "amount", "value": "10089600" }, + { "key": "from", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "to", "value": "orai1rvs4kypz8vygvfds0mzzssg36d9nevwv65qe5a" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" }, + { "key": "action", "value": "transfer" }, + { "key": "amount", "value": "5216324" }, + { "key": "from", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "to", "value": "orai1sdyqpt0y29eg2fzse85cd7r0klahqufujvaeve" } + ] + }, + { + "type": "wasm-matched_order", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "PartialFilled" }, + { "key": "bidder_addr", "value": "orai1rvs4kypz8vygvfds0mzzssg36d9nevwv65qe5a" }, + { "key": "order_id", "value": "3808595" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "355243371" }, + { "key": "filled_offer_amount", "value": "159665374" }, + { "key": "ask_amount", "value": "684476630" }, + { "key": "filled_ask_amount", "value": "307640405" }, + { "key": "reward_fee", "value": "10099" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "5241900" }, + { "key": "filled_ask_this_round", "value": "10099999" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1sdyqpt0y29eg2fzse85cd7r0klahqufujvaeve" }, + { "key": "order_id", "value": "3809108" }, + { "key": "direction", "value": "Sell" }, + { "key": "offer_amount", "value": "10100000" }, + { "key": "filled_offer_amount", "value": "10099999" }, + { "key": "ask_amount", "value": "5221700" }, + { "key": "filled_ask_amount", "value": "5221700" }, + { "key": "reward_fee", "value": "5221" }, + { "key": "relayer_fee", "value": "155" }, + { "key": "filled_offer_this_round", "value": "10099999" }, + { "key": "filled_ask_this_round", "value": "5221700" } + ] + } + ] + }, + { + "msg_index": 1, + "events": [ + { + "type": "coin_received", + "attributes": [ + { "key": "receiver", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "amount", "value": "92968937orai" } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { "key": "spender", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "amount", "value": "92968937orai" } + ] + }, + { + "type": "execute", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" } + ] + }, + { + "type": "message", + "attributes": [ + { "key": "action", "value": "/cosmwasm.wasm.v1.MsgExecuteContract" }, + { "key": "module", "value": "wasm" }, + { "key": "sender", "value": "orai16hv74w3eu3ek0muqpgp4fekhrqgpzl3hd3qeqk" } + ] + }, + { + "type": "transfer", + "attributes": [ + { "key": "recipient", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "sender", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "amount", "value": "92968937orai" } + ] + }, + { + "type": "wasm", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "action", "value": "execute_orderbook_pair" }, + { "key": "pair", "value": "orai - orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" }, + { "key": "total_matched_orders", "value": "25" }, + { "key": "executor_reward", "value": "[\"1883243orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh\"]" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" }, + { "key": "action", "value": "transfer" }, + { "key": "amount", "value": "1766572681" }, + { "key": "from", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "to", "value": "orai1ufd8cw6g3el5568mee9vrd7t58suqn8vvnew34" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" }, + { "key": "action", "value": "transfer" }, + { "key": "amount", "value": "1883243" }, + { "key": "from", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "to", "value": "orai16stq6f4pnrfpz75n9ujv6qg3czcfa4qyjux5en" } + ] + }, + { + "type": "wasm-matched_order", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809077" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "5889977" }, + { "key": "filled_offer_amount", "value": "5889977" }, + { "key": "ask_amount", "value": "309784" }, + { "key": "filled_ask_amount", "value": "309784" }, + { "key": "reward_fee", "value": "309" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "5889977" }, + { "key": "filled_ask_this_round", "value": "309784" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809078" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "11779954" }, + { "key": "filled_offer_amount", "value": "11779954" }, + { "key": "ask_amount", "value": "619600" }, + { "key": "filled_ask_amount", "value": "619600" }, + { "key": "reward_fee", "value": "619" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "11779954" }, + { "key": "filled_ask_this_round", "value": "619600" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809079" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "17669931" }, + { "key": "filled_offer_amount", "value": "17669931" }, + { "key": "ask_amount", "value": "929449" }, + { "key": "filled_ask_amount", "value": "929449" }, + { "key": "reward_fee", "value": "929" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "17669931" }, + { "key": "filled_ask_this_round", "value": "929449" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809080" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "23559909" }, + { "key": "filled_offer_amount", "value": "23559909" }, + { "key": "ask_amount", "value": "1239331" }, + { "key": "filled_ask_amount", "value": "1239331" }, + { "key": "reward_fee", "value": "1239" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "23559909" }, + { "key": "filled_ask_this_round", "value": "1239331" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809081" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "29449886" }, + { "key": "filled_offer_amount", "value": "29449886" }, + { "key": "ask_amount", "value": "1549245" }, + { "key": "filled_ask_amount", "value": "1549245" }, + { "key": "reward_fee", "value": "1549" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "29449886" }, + { "key": "filled_ask_this_round", "value": "1549245" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809082" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "35339863" }, + { "key": "filled_offer_amount", "value": "35339863" }, + { "key": "ask_amount", "value": "1859192" }, + { "key": "filled_ask_amount", "value": "1859192" }, + { "key": "reward_fee", "value": "1859" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "35339863" }, + { "key": "filled_ask_this_round", "value": "1859192" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809083" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "41229840" }, + { "key": "filled_offer_amount", "value": "41229840" }, + { "key": "ask_amount", "value": "2169171" }, + { "key": "filled_ask_amount", "value": "2169171" }, + { "key": "reward_fee", "value": "2169" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "41229840" }, + { "key": "filled_ask_this_round", "value": "2169171" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809084" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "47119817" }, + { "key": "filled_offer_amount", "value": "47119817" }, + { "key": "ask_amount", "value": "2479183" }, + { "key": "filled_ask_amount", "value": "2479183" }, + { "key": "reward_fee", "value": "2479" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "47119817" }, + { "key": "filled_ask_this_round", "value": "2479183" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809085" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "53009794" }, + { "key": "filled_offer_amount", "value": "53009794" }, + { "key": "ask_amount", "value": "2789228" }, + { "key": "filled_ask_amount", "value": "2789228" }, + { "key": "reward_fee", "value": "2789" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "53009794" }, + { "key": "filled_ask_this_round", "value": "2789228" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809086" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "58899771" }, + { "key": "filled_offer_amount", "value": "58899771" }, + { "key": "ask_amount", "value": "3099305" }, + { "key": "filled_ask_amount", "value": "3099305" }, + { "key": "reward_fee", "value": "3099" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "58899771" }, + { "key": "filled_ask_this_round", "value": "3099305" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809087" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "64789748" }, + { "key": "filled_offer_amount", "value": "64789748" }, + { "key": "ask_amount", "value": "3409415" }, + { "key": "filled_ask_amount", "value": "3409415" }, + { "key": "reward_fee", "value": "3409" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "64789748" }, + { "key": "filled_ask_this_round", "value": "3409415" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809088" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "70679726" }, + { "key": "filled_offer_amount", "value": "70679726" }, + { "key": "ask_amount", "value": "3719558" }, + { "key": "filled_ask_amount", "value": "3719558" }, + { "key": "reward_fee", "value": "3719" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "70679726" }, + { "key": "filled_ask_this_round", "value": "3719558" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809089" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "76569703" }, + { "key": "filled_offer_amount", "value": "76569703" }, + { "key": "ask_amount", "value": "4029733" }, + { "key": "filled_ask_amount", "value": "4029733" }, + { "key": "reward_fee", "value": "4029" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "76569703" }, + { "key": "filled_ask_this_round", "value": "4029733" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809069" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "124264565" }, + { "key": "filled_offer_amount", "value": "124264565" }, + { "key": "ask_amount", "value": "6539841" }, + { "key": "filled_ask_amount", "value": "6539840" }, + { "key": "reward_fee", "value": "3435" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "65279075" }, + { "key": "filled_ask_this_round", "value": "3435531" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809090" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "82459680" }, + { "key": "filled_offer_amount", "value": "82459680" }, + { "key": "ask_amount", "value": "4339941" }, + { "key": "filled_ask_amount", "value": "4339941" }, + { "key": "reward_fee", "value": "4339" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "82459680" }, + { "key": "filled_ask_this_round", "value": "4339941" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809070" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "129235147" }, + { "key": "filled_offer_amount", "value": "129235147" }, + { "key": "ask_amount", "value": "6801793" }, + { "key": "filled_ask_amount", "value": "6801793" }, + { "key": "reward_fee", "value": "6801" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "129235147" }, + { "key": "filled_ask_this_round", "value": "6801793" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809091" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "88349657" }, + { "key": "filled_offer_amount", "value": "88349657" }, + { "key": "ask_amount", "value": "4650182" }, + { "key": "filled_ask_amount", "value": "4650182" }, + { "key": "reward_fee", "value": "4650" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "88349657" }, + { "key": "filled_ask_this_round", "value": "4650182" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809071" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "134205730" }, + { "key": "filled_offer_amount", "value": "134205730" }, + { "key": "ask_amount", "value": "7063772" }, + { "key": "filled_ask_amount", "value": "7063772" }, + { "key": "reward_fee", "value": "7063" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "134205730" }, + { "key": "filled_ask_this_round", "value": "7063772" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809092" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "94239634" }, + { "key": "filled_offer_amount", "value": "94239634" }, + { "key": "ask_amount", "value": "4960455" }, + { "key": "filled_ask_amount", "value": "4960455" }, + { "key": "reward_fee", "value": "4960" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "94239634" }, + { "key": "filled_ask_this_round", "value": "4960455" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809072" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "139176312" }, + { "key": "filled_offer_amount", "value": "139176312" }, + { "key": "ask_amount", "value": "7325778" }, + { "key": "filled_ask_amount", "value": "7325778" }, + { "key": "reward_fee", "value": "7325" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "139176312" }, + { "key": "filled_ask_this_round", "value": "7325778" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809093" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "100129611" }, + { "key": "filled_offer_amount", "value": "100129611" }, + { "key": "ask_amount", "value": "5270761" }, + { "key": "filled_ask_amount", "value": "5270761" }, + { "key": "reward_fee", "value": "5270" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "100129611" }, + { "key": "filled_ask_this_round", "value": "5270761" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809073" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "144146895" }, + { "key": "filled_offer_amount", "value": "144146895" }, + { "key": "ask_amount", "value": "7587813" }, + { "key": "filled_ask_amount", "value": "7587813" }, + { "key": "reward_fee", "value": "7587" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "144146895" }, + { "key": "filled_ask_this_round", "value": "7587813" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809094" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "106019588" }, + { "key": "filled_offer_amount", "value": "106019588" }, + { "key": "ask_amount", "value": "5581099" }, + { "key": "filled_ask_amount", "value": "5581099" }, + { "key": "reward_fee", "value": "5581" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "106019588" }, + { "key": "filled_ask_this_round", "value": "5581099" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809074" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "149117477" }, + { "key": "filled_offer_amount", "value": "149117477" }, + { "key": "ask_amount", "value": "7849875" }, + { "key": "filled_ask_amount", "value": "7849875" }, + { "key": "reward_fee", "value": "7849" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "149117477" }, + { "key": "filled_ask_this_round", "value": "7849875" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "PartialFilled" }, + { "key": "bidder_addr", "value": "orai1ufd8cw6g3el5568mee9vrd7t58suqn8vvnew34" }, + { "key": "order_id", "value": "3809107" }, + { "key": "direction", "value": "Sell" }, + { "key": "offer_amount", "value": "200000000" }, + { "key": "filled_offer_amount", "value": "93069194" }, + { "key": "ask_amount", "value": "3799037200" }, + { "key": "filled_ask_amount", "value": "1768346725" }, + { "key": "reward_fee", "value": "1768346" }, + { "key": "relayer_fee", "value": "5698" }, + { "key": "filled_offer_this_round", "value": "93069194" }, + { "key": "filled_ask_this_round", "value": "1768346725" } + ] + } + ] + } +] diff --git a/packages/oraidex-common/tests/indexed-tx-tx.json b/packages/oraidex-common/tests/indexed-tx-tx.json new file mode 100644 index 00000000..a9e8b542 --- /dev/null +++ b/packages/oraidex-common/tests/indexed-tx-tx.json @@ -0,0 +1 @@ +"CsAFCvsCCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS0gIKK29yYWkxNmh2NzR3M2V1M2VrMG11cXBncDRmZWtocnFncHpsM2hkM3FlcWsSP29yYWkxbnQ1OGdjdTRlNjN2N2s1NXBobnIzZ2F5bTl0dmszcTRhcHF6cWNjanV3cHBnanV5ank2c3hrOHl6cBrhAXsiZXhlY3V0ZV9vcmRlcl9ib29rX3BhaXIiOnsiYXNzZXRfaW5mb3MiOlt7InRva2VuIjp7ImNvbnRyYWN0X2FkZHIiOiJvcmFpMWxwbGFwbWdxbmVscW4yNTNzdHo2a212bTN1bGdkYXl0bjg5YThtejl5ODV4cTh3ZDY4NHM2eGwzbHQifX0seyJ0b2tlbiI6eyJjb250cmFjdF9hZGRyIjoib3JhaTEyaHpqeGZoNzd3bDU3MmdkemN0MmZ4djJhcnhjd2g2Z3lrYzdxaCJ9fV0sImxpbWl0IjoxMDB9fQq/AgokL2Nvc213YXNtLndhc20udjEuTXNnRXhlY3V0ZUNvbnRyYWN0EpYCCitvcmFpMTZodjc0dzNldTNlazBtdXFwZ3A0ZmVraHJxZ3B6bDNoZDNxZXFrEj9vcmFpMW50NThnY3U0ZTYzdjdrNTVwaG5yM2dheW05dHZrM3E0YXBxenFjY2p1d3BwZ2p1eWp5NnN4azh5enAapQF7ImV4ZWN1dGVfb3JkZXJfYm9va19wYWlyIjp7ImFzc2V0X2luZm9zIjpbeyJuYXRpdmVfdG9rZW4iOnsiZGVub20iOiJvcmFpIn19LHsidG9rZW4iOnsiY29udHJhY3RfYWRkciI6Im9yYWkxMmh6anhmaDc3d2w1NzJnZHpjdDJmeHYyYXJ4Y3doNmd5a2M3cWgifX1dLCJsaW1pdCI6MTAwfX0SZwpRCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAmXkJCfyrJuMvuO0gCHubPxMloMQxSdPyy0SZAWF+4ubEgQKAggBGLcPEhIKDAoEb3JhaRIEMTE1MhDBokYaQBfox2B/mwb3tt33UHXPPOo42HZSy4V7DZ/TiPYWjGe0R8P5O5TavytfG7llLqeXh5SR+XrvKivlAz1YG1y2sXo=" \ No newline at end of file From cfa6b4481322310f2a08a325988c6449dd02051d Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Sat, 30 Mar 2024 17:51:25 -0700 Subject: [PATCH 03/22] test: switch back to ts-jest --- jest.config.js | 3 +-- packages/universal-swap/tests/helper.spec.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/jest.config.js b/jest.config.js index fbb242e1..72e6733c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,8 +1,7 @@ // jest.config.js module.exports = { transform: { - "^.+packages/universal-swap/src/helper\\.ts$": "ts-jest", // use ts-jest for tests that have spyOn mocking - "^.+\\.ts?$": ["@swc/jest"] + "^.+\\.ts?$": "ts-jest" }, testEnvironment: "node", modulePathIgnorePatterns: ["/dist/", "/packages/ibc-routing"], diff --git a/packages/universal-swap/tests/helper.spec.ts b/packages/universal-swap/tests/helper.spec.ts index f08eb267..1b368fac 100644 --- a/packages/universal-swap/tests/helper.spec.ts +++ b/packages/universal-swap/tests/helper.spec.ts @@ -56,7 +56,7 @@ import { } from "../src/helper"; import { SwapRoute, UniversalSwapType } from "../src/types"; import { AssetInfo } from "@oraichain/oraidex-contracts-sdk"; -import { SwapOperation } from "@oraichain/oraidex-contracts-sdk/build/OraiswapRouter.types"; +import { SwapOperation } from "@oraichain/oraidex-contracts-sdk"; import { parseToIbcHookMemo, parseToIbcWasmMemo } from "../src/proto/proto-gen"; import { Coin, coin } from "@cosmjs/proto-signing"; From 0f84a222e99374d70b60d8232cf689492066ce7f Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Sat, 30 Mar 2024 17:55:25 -0700 Subject: [PATCH 04/22] test ts-jest test time --- packages/oraidex-common/tests/helper.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/oraidex-common/tests/helper.spec.ts b/packages/oraidex-common/tests/helper.spec.ts index 2af25887..9c74459b 100644 --- a/packages/oraidex-common/tests/helper.spec.ts +++ b/packages/oraidex-common/tests/helper.spec.ts @@ -390,7 +390,7 @@ describe("should helper functions in helper run exactly", () => { expect(reuslt).toEqual([]); // case 2: real tx with multiple msgs and multiple contract calls - // got data from tx hash 9B435E4014DEBA5AB80D4BB8F52D766A6C14BFCAC21F821CDB96F4ABB4E29B17 Oraichain + // got data from tx hash 9B435E4014DEBA5AB80D4BB8F52D766A6C14BFCAC21F821CDB96F4ABB4E29B17 Oraichain. const rawLog = fs.readFileSync(path.join(__dirname, "indexed-tx-raw-log.json")).toString(); const tx = Buffer.from(fs.readFileSync(path.join(__dirname, "indexed-tx-tx.json")).toString(), "base64"); const data = parseTxToMsgsAndEvents({ rawLog, tx } as any); From 6c1860f311a74e86852177394cb5f255c17a3a8d Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Sat, 30 Mar 2024 17:59:12 -0700 Subject: [PATCH 05/22] change to swc jest --- jest.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 72e6733c..fbb242e1 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,8 @@ // jest.config.js module.exports = { transform: { - "^.+\\.ts?$": "ts-jest" + "^.+packages/universal-swap/src/helper\\.ts$": "ts-jest", // use ts-jest for tests that have spyOn mocking + "^.+\\.ts?$": ["@swc/jest"] }, testEnvironment: "node", modulePathIgnorePatterns: ["/dist/", "/packages/ibc-routing"], From 42edfcf0054cecaa6f142b3474c980b7a806f31d Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Tue, 2 Apr 2024 19:24:00 -0700 Subject: [PATCH 06/22] optimize: use @swc/test full reduce test time --- jest.config.js | 1 - packages/universal-swap/src/handler.ts | 3 +- packages/universal-swap/src/helper.ts | 1647 ++++++++++--------- packages/universal-swap/tests/index.spec.ts | 38 +- 4 files changed, 934 insertions(+), 755 deletions(-) diff --git a/jest.config.js b/jest.config.js index fbb242e1..7988c582 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,6 @@ // jest.config.js module.exports = { transform: { - "^.+packages/universal-swap/src/helper\\.ts$": "ts-jest", // use ts-jest for tests that have spyOn mocking "^.+\\.ts?$": ["@swc/jest"] }, testEnvironment: "node", diff --git a/packages/universal-swap/src/handler.ts b/packages/universal-swap/src/handler.ts index cbbfcdbc..3c5fa07a 100644 --- a/packages/universal-swap/src/handler.ts +++ b/packages/universal-swap/src/handler.ts @@ -39,6 +39,7 @@ import { } from "@oraichain/oraidex-common"; import { ethers } from "ethers"; import { + UniversalSwapHelper, addOraiBridgeRoute, checkBalanceChannelIbc, checkBalanceIBCOraichain, @@ -639,7 +640,7 @@ export class UniversalSwapHandler { tronAddress: tron }); - const { swapRoute, universalSwapType } = addOraiBridgeRoute( + const { swapRoute, universalSwapType } = UniversalSwapHelper.addOraiBridgeRoute( cosmos, this.swapData.originalFromToken, this.swapData.originalToToken, diff --git a/packages/universal-swap/src/helper.ts b/packages/universal-swap/src/helper.ts index 00d9d39b..b8c5d515 100644 --- a/packages/universal-swap/src/helper.ts +++ b/packages/universal-swap/src/helper.ts @@ -74,805 +74,982 @@ const caseSwapNativeAndWrapNative = (fromCoingecko, toCoingecko) => { const arr = ["ethereum", "weth"]; return arr.includes(fromCoingecko) && arr.includes(toCoingecko); }; -// evm swap helpers -export const isSupportedNoPoolSwapEvm = (coingeckoId: CoinGeckoId) => { - switch (coingeckoId) { - case "wbnb": - case "binancecoin": - case "ethereum": - return true; - default: - return false; - } -}; -export const isEvmNetworkNativeSwapSupported = (chainId: NetworkChainId) => { - switch (chainId) { - case "0x01": - case "0x38": - return true; - default: - return false; - } +const splitOnce = (s: string, seperator: string) => { + const i = s.indexOf(seperator); + // cannot find seperator then return string + if (i === -1) return [s]; + return [s.slice(0, i), s.slice(i + 1)]; }; -export const swapEvmRoutes: { - [network: string]: { - [pair: string]: string[]; +export class UniversalSwapHelper { + // evm swap helpers + static isSupportedNoPoolSwapEvm = (coingeckoId: CoinGeckoId) => { + switch (coingeckoId) { + case "wbnb": + case "binancecoin": + case "ethereum": + return true; + default: + return false; + } }; -} = { - "0x38": { - [`${WRAP_BNB_CONTRACT}-${USDT_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, USDT_BSC_CONTRACT], - [`${WRAP_BNB_CONTRACT}-${USDT_TRON_CONTRACT}`]: [WRAP_BNB_CONTRACT, USDT_BSC_CONTRACT], - [`${WRAP_BNB_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], - [`${WRAP_BNB_CONTRACT}-${ORAI_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], - [`${WRAP_BNB_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT], - [`${USDT_BSC_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [USDT_BSC_CONTRACT, WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT], - [`${USDT_BSC_CONTRACT}-${ORAI_BSC_CONTRACT}`]: [USDT_BSC_CONTRACT, WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], - [`${ORAI_BSC_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [ORAI_BSC_CONTRACT, WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT] - }, - "0x01": { - [`${WRAP_ETH_CONTRACT}-${USDC_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, USDC_ETH_CONTRACT], - [`${WRAP_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], - [`${WRAP_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, USDT_ETH_CONTRACT], - // TODO: hardcode fix eth -> weth (oraichain) - [`${WRAP_ETH_CONTRACT}-${WRAP_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, WRAP_ETH_CONTRACT], - [`${USDC_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [USDC_ETH_CONTRACT, USDT_ETH_CONTRACT], - [`${USDC_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [USDC_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], - [`${USDT_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [USDT_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT] - - // [`${WRAP_ETH_CONTRACT}-${ORAIX_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, ORAIX_ETH_CONTRACT] - // [`${ORAIX_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], - // [`${ORAIX_ETH_CONTRACT}-${USDC_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, USDC_ETH_CONTRACT], - // [`${ORAIX_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, USDT_ETH_CONTRACT] - } -}; -export const buildSwapRouterKey = (fromContractAddr: string, toContractAddr: string) => { - return `${fromContractAddr}-${toContractAddr}`; -}; + static isEvmNetworkNativeSwapSupported = (chainId: NetworkChainId) => { + switch (chainId) { + case "0x01": + case "0x38": + return true; + default: + return false; + } + }; -export const getEvmSwapRoute = ( - chainId: string, - fromContractAddr?: string, - toContractAddr?: string -): string[] | undefined => { - if (!isEvmNetworkNativeSwapSupported(chainId as EvmChainId)) return undefined; - if (!fromContractAddr && !toContractAddr) return undefined; - const chainRoutes = swapEvmRoutes[chainId]; - const fromAddr = fromContractAddr || proxyContractInfo[chainId].wrapNativeAddr; - const toAddr = toContractAddr || proxyContractInfo[chainId].wrapNativeAddr; - - // in case from / to contract addr is empty aka native eth or bnb without contract addr then we fallback to swap route with wrapped token - // because uniswap & pancakeswap do not support simulating with native directly - let route: string[] | undefined = chainRoutes[buildSwapRouterKey(fromAddr, toContractAddr)]; - if (route) return route; - // because the route can go both ways. Eg: WBNB->AIRI, if we want to swap AIRI->WBNB, then first we find route WBNB->AIRI, then we reverse the route - route = chainRoutes[buildSwapRouterKey(toAddr, fromContractAddr)]; - if (route) { - return [].concat(route).reverse(); - } - return undefined; -}; + static swapEvmRoutes: { + [network: string]: { + [pair: string]: string[]; + }; + } = { + "0x38": { + [`${WRAP_BNB_CONTRACT}-${USDT_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, USDT_BSC_CONTRACT], + [`${WRAP_BNB_CONTRACT}-${USDT_TRON_CONTRACT}`]: [WRAP_BNB_CONTRACT, USDT_BSC_CONTRACT], + [`${WRAP_BNB_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], + [`${WRAP_BNB_CONTRACT}-${ORAI_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], + [`${WRAP_BNB_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT], + [`${USDT_BSC_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [USDT_BSC_CONTRACT, WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT], + [`${USDT_BSC_CONTRACT}-${ORAI_BSC_CONTRACT}`]: [USDT_BSC_CONTRACT, WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], + [`${ORAI_BSC_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [ORAI_BSC_CONTRACT, WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT] + }, + "0x01": { + [`${WRAP_ETH_CONTRACT}-${USDC_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, USDC_ETH_CONTRACT], + [`${WRAP_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], + [`${WRAP_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, USDT_ETH_CONTRACT], + // TODO: hardcode fix eth -> weth (oraichain) + [`${WRAP_ETH_CONTRACT}-${WRAP_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, WRAP_ETH_CONTRACT], + [`${USDC_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [USDC_ETH_CONTRACT, USDT_ETH_CONTRACT], + [`${USDC_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [USDC_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], + [`${USDT_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [USDT_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT] -// static functions -export const isEvmSwappable = (data: { - fromChainId: string; - toChainId: string; - fromContractAddr?: string; - toContractAddr?: string; -}): boolean => { - const { fromChainId, fromContractAddr, toChainId, toContractAddr } = data; - // cant swap if they are not on the same evm chain - if (fromChainId !== toChainId) return false; - // cant swap on evm if chain id is not eth or bsc - if (fromChainId !== "0x01" && fromChainId !== "0x38") return false; - // if the tokens do not have contract addresses then we skip - // if (!fromContractAddr || !toContractAddr) return false; - // only swappable if there's a route to swap from -> to - if (!getEvmSwapRoute(fromChainId, fromContractAddr, toContractAddr)) return false; - return true; -}; + // [`${WRAP_ETH_CONTRACT}-${ORAIX_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, ORAIX_ETH_CONTRACT] + // [`${ORAIX_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], + // [`${ORAIX_ETH_CONTRACT}-${USDC_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, USDC_ETH_CONTRACT], + // [`${ORAIX_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, USDT_ETH_CONTRACT] + } + }; -// ibc helpers -export const getIbcInfo = (fromChainId: CosmosChainId, toChainId: NetworkChainId): IBCInfo => { - if (!ibcInfos[fromChainId]) throw generateError("Cannot find ibc info"); - const ibcInfo = ibcInfos[fromChainId][toChainId]; - if (!ibcInfo) throw generateError(`Cannot find ibc info from ${fromChainId} to ${toChainId}`); - return ibcInfo; -}; + static buildSwapRouterKey = (fromContractAddr: string, toContractAddr: string) => { + return `${fromContractAddr}-${toContractAddr}`; + }; -export const buildIbcWasmPairKey = (ibcPort: string, ibcChannel: string, denom: string) => { - return `${ibcPort}/${ibcChannel}/${denom}`; -}; + static getEvmSwapRoute = ( + chainId: string, + fromContractAddr?: string, + toContractAddr?: string + ): string[] | undefined => { + if (!UniversalSwapHelper.isEvmNetworkNativeSwapSupported(chainId as EvmChainId)) return undefined; + if (!fromContractAddr && !toContractAddr) return undefined; + const chainRoutes = UniversalSwapHelper.swapEvmRoutes[chainId]; + const fromAddr = fromContractAddr || proxyContractInfo[chainId].wrapNativeAddr; + const toAddr = toContractAddr || proxyContractInfo[chainId].wrapNativeAddr; -/** - * This function converts the destination address (from BSC / ETH -> Oraichain) to an appropriate format based on the BSC / ETH token contract address - * @param oraiAddress - receiver address on Oraichain - * @param contractAddress - BSC / ETH token contract address - * @returns converted receiver address - */ -export const getSourceReceiver = ( - oraiAddress: string, - contractAddress?: string, - isSourceReceiverTest?: boolean -): string => { - let sourceReceiver = `${oraib2oraichain}/${oraiAddress}`; - // TODO: test retire v2 (change structure memo evm -> oraichain) - if (isSourceReceiverTest) { - sourceReceiver = `${oraib2oraichainTest}/${oraiAddress}`; - } - - // we only support the old oraibridge ibc channel <--> Oraichain for MILKY & KWT - if (contractAddress === KWT_BSC_CONTRACT || contractAddress === MILKY_BSC_CONTRACT) { - sourceReceiver = oraiAddress; - } - return sourceReceiver; -}; + // in case from / to contract addr is empty aka native eth or bnb without contract addr then we fallback to swap route with wrapped token + // because uniswap & pancakeswap do not support simulating with native directly + let route: string[] | undefined = chainRoutes[UniversalSwapHelper.buildSwapRouterKey(fromAddr, toContractAddr)]; + if (route) return route; + // because the route can go both ways. Eg: WBNB->AIRI, if we want to swap AIRI->WBNB, then first we find route WBNB->AIRI, then we reverse the route + route = chainRoutes[UniversalSwapHelper.buildSwapRouterKey(toAddr, fromContractAddr)]; + if (route) { + return [].concat(route).reverse(); + } + return undefined; + }; -/** - * This function receives fromToken and toToken as parameters to generate the destination memo for the receiver address - * @param from - from token - * @param to - to token - * @param destReceiver - destination destReceiver - * @returns destination in the format /: - */ -export const getRoute = ( - fromToken?: TokenItemType, - toToken?: TokenItemType, - destReceiver?: string, - receiverOnOrai?: string -): SwapRoute => { - if (!fromToken || !toToken || !destReceiver) - return { swapRoute: "", universalSwapType: "other-networks-to-oraichain" }; - // this is the simplest case. Both tokens on the same Oraichain network => simple swap with to token denom - if (fromToken.chainId === "Oraichain" && toToken.chainId === "Oraichain") { - return { swapRoute: "", universalSwapType: "oraichain-to-oraichain" }; - } - // we dont need to have any swapRoute for this case - if (fromToken.chainId === "Oraichain") { - if (cosmosTokens.some((t) => t.chainId === toToken.chainId)) - return { swapRoute: "", universalSwapType: "oraichain-to-cosmos" }; - return { swapRoute: "", universalSwapType: "oraichain-to-evm" }; - } - // TODO: support 1-step swap for kwt - if (fromToken.chainId === "kawaii_6886-1" || fromToken.chainId === "0x1ae6") { - throw new Error(`chain id ${fromToken.chainId} is currently not supported in universal swap`); - } - // cosmos to others case where from token is a cosmos token - // we have 2 cases: 1) Cosmos to Oraichain, 2) Cosmos to cosmos or evm - let toDenom = parseTokenInfoRawDenom(toToken); - if (!toToken.cosmosBased && evmChains.map((e) => e.chainId).includes(toToken.chainId)) - toDenom = toToken.prefix + toDenom; - - if (cosmosTokens.some((t) => t.chainId === fromToken.chainId)) { - let swapRoute = ""; - const dstChannel = toToken.chainId == "Oraichain" ? "" : ibcInfos["Oraichain"][toToken.chainId].channel; - // if from chain is noble, use ibc wasm instead of ibc hooks - if (fromToken.chainId == "noble-1") { - swapRoute = parseToIbcWasmMemo(destReceiver, dstChannel, toDenom); - } else { - swapRoute = parseToIbcHookMemo(receiverOnOrai, destReceiver, dstChannel, toDenom); + // static functions + static isEvmSwappable = (data: { + fromChainId: string; + toChainId: string; + fromContractAddr?: string; + toContractAddr?: string; + }): boolean => { + const { fromChainId, fromContractAddr, toChainId, toContractAddr } = data; + // cant swap if they are not on the same evm chain + if (fromChainId !== toChainId) return false; + // cant swap on evm if chain id is not eth or bsc + if (fromChainId !== "0x01" && fromChainId !== "0x38") return false; + // if the tokens do not have contract addresses then we skip + // if (!fromContractAddr || !toContractAddr) return false; + // only swappable if there's a route to swap from -> to + if (!UniversalSwapHelper.getEvmSwapRoute(fromChainId, fromContractAddr, toContractAddr)) return false; + return true; + }; + + // ibc helpers + static getIbcInfo = (fromChainId: CosmosChainId, toChainId: NetworkChainId): IBCInfo => { + if (!ibcInfos[fromChainId]) throw generateError("Cannot find ibc info"); + const ibcInfo = ibcInfos[fromChainId][toChainId]; + if (!ibcInfo) throw generateError(`Cannot find ibc info from ${fromChainId} to ${toChainId}`); + return ibcInfo; + }; + + static buildIbcWasmPairKey = (ibcPort: string, ibcChannel: string, denom: string) => { + return `${ibcPort}/${ibcChannel}/${denom}`; + }; + + /** + * This function converts the destination address (from BSC / ETH -> Oraichain) to an appropriate format based on the BSC / ETH token contract address + * @param oraiAddress - receiver address on Oraichain + * @param contractAddress - BSC / ETH token contract address + * @returns converted receiver address + */ + static getSourceReceiver = ( + oraiAddress: string, + contractAddress?: string, + isSourceReceiverTest?: boolean + ): string => { + let sourceReceiver = `${oraib2oraichain}/${oraiAddress}`; + // TODO: test retire v2 (change structure memo evm -> oraichain) + if (isSourceReceiverTest) { + sourceReceiver = `${oraib2oraichainTest}/${oraiAddress}`; } - return { swapRoute, universalSwapType: "cosmos-to-others" }; - } + // we only support the old oraibridge ibc channel <--> Oraichain for MILKY & KWT + if (contractAddress === KWT_BSC_CONTRACT || contractAddress === MILKY_BSC_CONTRACT) { + sourceReceiver = oraiAddress; + } + return sourceReceiver; + }; - if (toToken.chainId === "Oraichain") { - // if from token is 0x38 & to token chain id is Oraichain & from token is kawaii or milky - if (["0x38"].includes(fromToken.chainId) && ["milky-token", "kawaii-islands"].includes(fromToken.coinGeckoId)) + /** + * This function receives fromToken and toToken as parameters to generate the destination memo for the receiver address + * @param from - from token + * @param to - to token + * @param destReceiver - destination destReceiver + * @returns destination in the format /: + */ + static getRoute = ( + fromToken?: TokenItemType, + toToken?: TokenItemType, + destReceiver?: string, + receiverOnOrai?: string + ): SwapRoute => { + if (!fromToken || !toToken || !destReceiver) return { swapRoute: "", universalSwapType: "other-networks-to-oraichain" }; - // if to token chain id is Oraichain, then we dont need to care about ibc msg case - // first case, two tokens are the same, only different in network => simple swap - if (fromToken.coinGeckoId === toToken.coinGeckoId) - return { swapRoute: parseToIbcWasmMemo(destReceiver, "", ""), universalSwapType: "other-networks-to-oraichain" }; - // if they are not the same then we set dest denom - return { - // swapRoute: `${destReceiver}:${parseTokenInfoRawDenom(toToken)}`, - swapRoute: parseToIbcWasmMemo(destReceiver, "", toDenom), - universalSwapType: "other-networks-to-oraichain" - }; - } - // the remaining cases where we have to process ibc msg - const ibcInfo: IBCInfo = ibcInfos["Oraichain"][toToken.chainId]; // we get ibc channel that transfers toToken from Oraichain to the toToken chain - // getTokenOnOraichain is called to get the ibc denom / cw20 denom on Oraichain so that we can create an ibc msg using it - let receiverPrefix = ""; - // TODO: no need to use to token on Oraichain. Can simply use the swapRoute token directly. Fix this requires fixing the logic on ibc wasm as well - const toTokenOnOraichain = getTokenOnOraichain(toToken.coinGeckoId); - if (!toTokenOnOraichain) + // this is the simplest case. Both tokens on the same Oraichain network => simple swap with to token denom + if (fromToken.chainId === "Oraichain" && toToken.chainId === "Oraichain") { + return { swapRoute: "", universalSwapType: "oraichain-to-oraichain" }; + } + // we dont need to have any swapRoute for this case + if (fromToken.chainId === "Oraichain") { + if (cosmosTokens.some((t) => t.chainId === toToken.chainId)) + return { swapRoute: "", universalSwapType: "oraichain-to-cosmos" }; + return { swapRoute: "", universalSwapType: "oraichain-to-evm" }; + } + // TODO: support 1-step swap for kwt + if (fromToken.chainId === "kawaii_6886-1" || fromToken.chainId === "0x1ae6") { + throw new Error(`chain id ${fromToken.chainId} is currently not supported in universal swap`); + } + // cosmos to others case where from token is a cosmos token + // we have 2 cases: 1) Cosmos to Oraichain, 2) Cosmos to cosmos or evm + let toDenom = parseTokenInfoRawDenom(toToken); + if (!toToken.cosmosBased && evmChains.map((e) => e.chainId).includes(toToken.chainId)) + toDenom = toToken.prefix + toDenom; + + if (cosmosTokens.some((t) => t.chainId === fromToken.chainId)) { + let swapRoute = ""; + const dstChannel = toToken.chainId == "Oraichain" ? "" : ibcInfos["Oraichain"][toToken.chainId].channel; + // if from chain is noble, use ibc wasm instead of ibc hooks + if (fromToken.chainId == "noble-1") { + swapRoute = parseToIbcWasmMemo(destReceiver, dstChannel, toDenom); + } else { + swapRoute = parseToIbcHookMemo(receiverOnOrai, destReceiver, dstChannel, toDenom); + } + + return { swapRoute, universalSwapType: "cosmos-to-others" }; + } + + if (toToken.chainId === "Oraichain") { + // if from token is 0x38 & to token chain id is Oraichain & from token is kawaii or milky + if (["0x38"].includes(fromToken.chainId) && ["milky-token", "kawaii-islands"].includes(fromToken.coinGeckoId)) + return { swapRoute: "", universalSwapType: "other-networks-to-oraichain" }; + // if to token chain id is Oraichain, then we dont need to care about ibc msg case + // first case, two tokens are the same, only different in network => simple swap + if (fromToken.coinGeckoId === toToken.coinGeckoId) + return { + swapRoute: parseToIbcWasmMemo(destReceiver, "", ""), + universalSwapType: "other-networks-to-oraichain" + }; + // if they are not the same then we set dest denom + return { + // swapRoute: `${destReceiver}:${parseTokenInfoRawDenom(toToken)}`, + swapRoute: parseToIbcWasmMemo(destReceiver, "", toDenom), + universalSwapType: "other-networks-to-oraichain" + }; + } + // the remaining cases where we have to process ibc msg + const ibcInfo: IBCInfo = ibcInfos["Oraichain"][toToken.chainId]; // we get ibc channel that transfers toToken from Oraichain to the toToken chain + // getTokenOnOraichain is called to get the ibc denom / cw20 denom on Oraichain so that we can create an ibc msg using it + let receiverPrefix = ""; + // TODO: no need to use to token on Oraichain. Can simply use the swapRoute token directly. Fix this requires fixing the logic on ibc wasm as well + const toTokenOnOraichain = getTokenOnOraichain(toToken.coinGeckoId); + if (!toTokenOnOraichain) + return { + swapRoute: "", + universalSwapType: "other-networks-to-oraichain" + }; + if (isEthAddress(destReceiver)) receiverPrefix = toToken.prefix; return { - swapRoute: "", + // swapRoute: `${ibcInfo.channel}/${receiverPrefix}${destReceiver}:${parseTokenInfoRawDenom(toToken)}`, + swapRoute: parseToIbcWasmMemo(`${receiverPrefix}${destReceiver}`, ibcInfo.channel, toDenom), universalSwapType: "other-networks-to-oraichain" }; - if (isEthAddress(destReceiver)) receiverPrefix = toToken.prefix; - return { - // swapRoute: `${ibcInfo.channel}/${receiverPrefix}${destReceiver}:${parseTokenInfoRawDenom(toToken)}`, - swapRoute: parseToIbcWasmMemo(`${receiverPrefix}${destReceiver}`, ibcInfo.channel, toDenom), - universalSwapType: "other-networks-to-oraichain" }; -}; -export const addOraiBridgeRoute = ( - sourceReceiver: string, - fromToken: TokenItemType, - toToken: TokenItemType, - destReceiver?: string, - isSourceReceiverTest?: boolean -): SwapRoute => { - // TODO: recheck cosmos address undefined (other-chain -> oraichain) - if (!sourceReceiver) throw generateError(`Cannot get source if the sourceReceiver is empty!`); - const source = getSourceReceiver(sourceReceiver, fromToken.contractAddress, isSourceReceiverTest); - - const { swapRoute, universalSwapType } = getRoute(fromToken, toToken, destReceiver); - if (swapRoute.length > 0) return { swapRoute: `${source}:${swapRoute}`, universalSwapType }; - return { swapRoute: source, universalSwapType }; -}; + static addOraiBridgeRoute = ( + sourceReceiver: string, + fromToken: TokenItemType, + toToken: TokenItemType, + destReceiver?: string, + isSourceReceiverTest?: boolean + ): SwapRoute => { + // TODO: recheck cosmos address undefined (other-chain -> oraichain) + if (!sourceReceiver) throw generateError(`Cannot get source if the sourceReceiver is empty!`); + const source = UniversalSwapHelper.getSourceReceiver( + sourceReceiver, + fromToken.contractAddress, + isSourceReceiverTest + ); -export const splitOnce = (s: string, seperator: string) => { - const i = s.indexOf(seperator); - // cannot find seperator then return string - if (i === -1) return [s]; - return [s.slice(0, i), s.slice(i + 1)]; -}; + const { swapRoute, universalSwapType } = UniversalSwapHelper.getRoute(fromToken, toToken, destReceiver); + if (swapRoute.length > 0) return { swapRoute: `${source}:${swapRoute}`, universalSwapType }; + return { swapRoute: source, universalSwapType }; + }; -/** - * cases: - * /: - * : - * - * :/: - * : - * :: - * */ -export const unmarshalOraiBridgeRoute = (destination: string) => { - const routeData: OraiBridgeRouteData = { - oraiBridgeChannel: "", - oraiReceiver: "", - finalDestinationChannel: "", - finalReceiver: "", - tokenIdentifier: "" + /** + * cases: + * /: + * : + * + * :/: + * : + * :: + * */ + static unmarshalOraiBridgeRoute = (destination: string) => { + const routeData: OraiBridgeRouteData = { + oraiBridgeChannel: "", + oraiReceiver: "", + finalDestinationChannel: "", + finalReceiver: "", + tokenIdentifier: "" + }; + const splittedDestination = splitOnce(destination, ":"); + if (splittedDestination.length === 0 || splittedDestination.length > 2) + throw generateError(`The destination data is malformed: ${destination}`); + const firstDestination = splittedDestination[0].split("/"); + if (firstDestination.length === 2) { + routeData.oraiBridgeChannel = firstDestination[0]; + routeData.oraiReceiver = firstDestination[1]; + } else if (firstDestination.length === 1) { + routeData.oraiReceiver = firstDestination[0]; + } else throw generateError(`First destination ${JSON.stringify(firstDestination)} of ${destination} is malformed`); + // there's nothing we need to parse anymore + if (splittedDestination.length === 1) return routeData; + const finalDestinationData = splittedDestination[1].split(":"); + if (finalDestinationData.length === 1) routeData.finalReceiver = finalDestinationData[0]; + else if (finalDestinationData.length === 2) { + routeData.tokenIdentifier = finalDestinationData[1]; + const splittedFinalDestinationData = finalDestinationData[0].split("/"); + if (splittedFinalDestinationData.length === 1) routeData.finalReceiver = splittedFinalDestinationData[0]; + else if (splittedFinalDestinationData.length === 2) { + routeData.finalDestinationChannel = splittedFinalDestinationData[0]; + routeData.finalReceiver = splittedFinalDestinationData[1]; + } else + throw generateError( + `splitted final destination data ${JSON.stringify(splittedFinalDestinationData)} is malformed` + ); + } else throw generateError(`Final destination data ${JSON.stringify(finalDestinationData)} is malformed`); + return routeData; }; - const splittedDestination = splitOnce(destination, ":"); - if (splittedDestination.length === 0 || splittedDestination.length > 2) - throw generateError(`The destination data is malformed: ${destination}`); - const firstDestination = splittedDestination[0].split("/"); - if (firstDestination.length === 2) { - routeData.oraiBridgeChannel = firstDestination[0]; - routeData.oraiReceiver = firstDestination[1]; - } else if (firstDestination.length === 1) { - routeData.oraiReceiver = firstDestination[0]; - } else throw generateError(`First destination ${JSON.stringify(firstDestination)} of ${destination} is malformed`); - // there's nothing we need to parse anymore - if (splittedDestination.length === 1) return routeData; - const finalDestinationData = splittedDestination[1].split(":"); - if (finalDestinationData.length === 1) routeData.finalReceiver = finalDestinationData[0]; - else if (finalDestinationData.length === 2) { - routeData.tokenIdentifier = finalDestinationData[1]; - const splittedFinalDestinationData = finalDestinationData[0].split("/"); - if (splittedFinalDestinationData.length === 1) routeData.finalReceiver = splittedFinalDestinationData[0]; - else if (splittedFinalDestinationData.length === 2) { - routeData.finalDestinationChannel = splittedFinalDestinationData[0]; - routeData.finalReceiver = splittedFinalDestinationData[1]; - } else - throw generateError( - `splitted final destination data ${JSON.stringify(splittedFinalDestinationData)} is malformed` - ); - } else throw generateError(`Final destination data ${JSON.stringify(finalDestinationData)} is malformed`); - return routeData; -}; -export const generateSwapRoute = (offerAsset: AssetInfo, askAsset: AssetInfo, swapRoute: AssetInfo[]) => { - const swaps = []; - if (swapRoute.length === 0) { - swaps.push({ - orai_swap: { - offer_asset_info: offerAsset, - ask_asset_info: askAsset - } - }); - } else { - swaps.push({ - orai_swap: { - offer_asset_info: offerAsset, - ask_asset_info: swapRoute[0] + static generateSwapRoute = (offerAsset: AssetInfo, askAsset: AssetInfo, swapRoute: AssetInfo[]) => { + const swaps = []; + if (swapRoute.length === 0) { + swaps.push({ + orai_swap: { + offer_asset_info: offerAsset, + ask_asset_info: askAsset + } + }); + } else { + swaps.push({ + orai_swap: { + offer_asset_info: offerAsset, + ask_asset_info: swapRoute[0] + } + }); + for (let i = 0; i < swapRoute.length - 1; i++) { + swaps.push({ + orai_swap: { + offer_asset_info: swapRoute[i], + ask_asset_info: swapRoute[i + 1] + } + }); } - }); - for (let i = 0; i < swapRoute.length - 1; i++) { swaps.push({ orai_swap: { - offer_asset_info: swapRoute[i], - ask_asset_info: swapRoute[i + 1] + offer_asset_info: swapRoute[swapRoute.length - 1], + ask_asset_info: askAsset } }); } - swaps.push({ - orai_swap: { - offer_asset_info: swapRoute[swapRoute.length - 1], - ask_asset_info: askAsset - } + return swaps; + }; + + // generate messages + static generateSwapOperationMsgs = (offerInfo: AssetInfo, askInfo: AssetInfo): SwapOperation[] => { + const pairExist = PAIRS.some((pair) => { + const assetInfos = pair.asset_infos; + return ( + (isEqual(assetInfos[0], offerInfo) && isEqual(assetInfos[1], askInfo)) || + (isEqual(assetInfos[1], offerInfo) && isEqual(assetInfos[0], askInfo)) + ); }); - } - return swaps; -}; -// generate messages -export const generateSwapOperationMsgs = (offerInfo: AssetInfo, askInfo: AssetInfo): SwapOperation[] => { - const pairExist = PAIRS.some((pair) => { - const assetInfos = pair.asset_infos; - return ( - (isEqual(assetInfos[0], offerInfo) && isEqual(assetInfos[1], askInfo)) || - (isEqual(assetInfos[1], offerInfo) && isEqual(assetInfos[0], askInfo)) - ); - }); - - if (pairExist) return generateSwapRoute(offerInfo, askInfo, []); - // TODO: hardcode NTMPI -> USDC -> ORAI -> X - if (isEqual(offerInfo, NEUTARO_INFO)) { - const swapRoute = isEqual(askInfo, ORAI_INFO) ? [USDC_INFO] : [USDC_INFO, ORAI_INFO]; - return generateSwapRoute(offerInfo, askInfo, swapRoute); - } - - // TODO: X -> ORAI -> USDC -> NTMPI - if (isEqual(askInfo, NEUTARO_INFO)) { - const swapRoute = isEqual(offerInfo, ORAI_INFO) ? [USDC_INFO] : [ORAI_INFO, USDC_INFO]; - return generateSwapRoute(offerInfo, askInfo, swapRoute); - } - - // Default case: ORAI_INFO - return generateSwapRoute(offerInfo, askInfo, [ORAI_INFO]); -}; + if (pairExist) return UniversalSwapHelper.generateSwapRoute(offerInfo, askInfo, []); + // TODO: hardcode NTMPI -> USDC -> ORAI -> X + if (isEqual(offerInfo, NEUTARO_INFO)) { + const swapRoute = isEqual(askInfo, ORAI_INFO) ? [USDC_INFO] : [USDC_INFO, ORAI_INFO]; + return UniversalSwapHelper.generateSwapRoute(offerInfo, askInfo, swapRoute); + } -// simulate swap functions -export const simulateSwap = async (query: { - fromInfo: TokenItemType; - toInfo: TokenItemType; - amount: string; - routerClient: OraiswapRouterReadOnlyInterface; -}) => { - const { amount, fromInfo, toInfo, routerClient } = query; - - // check for universal-swap 2 tokens that have same coingeckoId, should return simulate data with average ratio 1-1. - if (fromInfo.coinGeckoId === toInfo.coinGeckoId) { - return { - amount - }; - } - - // check if they have pairs. If not then we go through ORAI - const { info: offerInfo } = parseTokenInfo(fromInfo, amount); - const { info: askInfo } = parseTokenInfo(toInfo); - const operations = generateSwapOperationMsgs(offerInfo, askInfo); - console.log("operations: ", operations); - try { - let finalAmount = amount; - const data = await routerClient.simulateSwapOperations({ - offerAmount: finalAmount, - operations - }); - return data; - } catch (error) { - throw new Error(`Error when trying to simulate swap using router v2: ${JSON.stringify(error)}`); - } -}; + // TODO: X -> ORAI -> USDC -> NTMPI + if (isEqual(askInfo, NEUTARO_INFO)) { + const swapRoute = isEqual(offerInfo, ORAI_INFO) ? [USDC_INFO] : [ORAI_INFO, USDC_INFO]; + return UniversalSwapHelper.generateSwapRoute(offerInfo, askInfo, swapRoute); + } -export const simulateSwapEvm = async (query: { - fromInfo: TokenItemType; - toInfo: TokenItemType; - amount: string; -}): Promise => { - const { amount, fromInfo, toInfo } = query; - // check swap native and wrap native - const isCheckSwapNativeAndWrapNative = caseSwapNativeAndWrapNative(fromInfo.coinGeckoId, toInfo.coinGeckoId); - - // check for universal-swap 2 tokens that have same coingeckoId, should return simulate data with average ratio 1-1. - if (fromInfo.coinGeckoId === toInfo.coinGeckoId || isCheckSwapNativeAndWrapNative) { + // Default case: ORAI_INFO + return UniversalSwapHelper.generateSwapRoute(offerInfo, askInfo, [ORAI_INFO]); + }; + + // simulate swap functions + static simulateSwap = async (query: { + fromInfo: TokenItemType; + toInfo: TokenItemType; + amount: string; + routerClient: OraiswapRouterReadOnlyInterface; + }) => { + const { amount, fromInfo, toInfo, routerClient } = query; + + // check for universal-swap 2 tokens that have same coingeckoId, should return simulate data with average ratio 1-1. + if (fromInfo.coinGeckoId === toInfo.coinGeckoId) { + return { + amount + }; + } + + // check if they have pairs. If not then we go through ORAI + const { info: offerInfo } = parseTokenInfo(fromInfo, amount); + const { info: askInfo } = parseTokenInfo(toInfo); + const operations = UniversalSwapHelper.generateSwapOperationMsgs(offerInfo, askInfo); + console.log("operations: ", operations); + try { + let finalAmount = amount; + const data = await routerClient.simulateSwapOperations({ + offerAmount: finalAmount, + operations + }); + return data; + } catch (error) { + throw new Error(`Error when trying to simulate swap using router v2: ${JSON.stringify(error)}`); + } + }; + + static simulateSwapEvm = async (query: { + fromInfo: TokenItemType; + toInfo: TokenItemType; + amount: string; + }): Promise => { + const { amount, fromInfo, toInfo } = query; + // check swap native and wrap native + const isCheckSwapNativeAndWrapNative = caseSwapNativeAndWrapNative(fromInfo.coinGeckoId, toInfo.coinGeckoId); + + // check for universal-swap 2 tokens that have same coingeckoId, should return simulate data with average ratio 1-1. + if (fromInfo.coinGeckoId === toInfo.coinGeckoId || isCheckSwapNativeAndWrapNative) { + return { + // amount: toDisplay(amount, fromInfo.decimals, toInfo.decimals).toString(), + amount: new BigDecimal(amount) + .mul(10n ** BigInt(toInfo.decimals)) + .div(10n ** BigInt(fromInfo.decimals)) + .toString(), + displayAmount: toDisplay(amount, fromInfo.decimals) + }; + } + try { + // get proxy contract object so that we can query the corresponding router address + const provider = new ethers.providers.JsonRpcProvider(fromInfo.rpc); + const toTokenInfoOnSameChainId = getTokenOnSpecificChainId(toInfo.coinGeckoId, fromInfo.chainId); + const swapRouterV2 = IUniswapV2Router02__factory.connect( + proxyContractInfo[fromInfo.chainId].routerAddr, + provider + ); + const route = UniversalSwapHelper.getEvmSwapRoute( + fromInfo.chainId, + fromInfo.contractAddress, + toTokenInfoOnSameChainId.contractAddress + ); + const outs = await swapRouterV2.getAmountsOut(amount, route); + if (outs.length === 0) throw new Error("There is no output amounts after simulating evm swap"); + const simulateAmount = outs.slice(-1)[0].toString(); + return { + // to display to reset the simulate amount to correct display type (swap simulate from -> same chain id to, so we use same chain id toToken decimals) + // then toAmount with actual toInfo decimals so that it has the same decimals as other tokens displayed + amount: simulateAmount, + displayAmount: toDisplay(simulateAmount, toTokenInfoOnSameChainId.decimals) // get the final out amount, which is the token out amount we want + }; + } catch (ex) { + console.log("error simulating evm: ", ex); + } + }; + + static handleSimulateSwap = async (query: { + originalFromInfo: TokenItemType; + originalToInfo: TokenItemType; + originalAmount: number; + routerClient: OraiswapRouterReadOnlyInterface; + }): Promise => { + // if the from token info is on bsc or eth, then we simulate using uniswap / pancake router + // otherwise, simulate like normal + if ( + UniversalSwapHelper.isSupportedNoPoolSwapEvm(query.originalFromInfo.coinGeckoId) || + UniversalSwapHelper.isEvmSwappable({ + fromChainId: query.originalFromInfo.chainId, + toChainId: query.originalToInfo.chainId, + fromContractAddr: query.originalFromInfo.contractAddress, + toContractAddr: query.originalToInfo.contractAddress + }) + ) { + // reset previous amount calculation since now we need to deal with original from & to info, not oraichain token info + const { amount, displayAmount } = await UniversalSwapHelper.simulateSwapEvm({ + fromInfo: query.originalFromInfo, + toInfo: query.originalToInfo, + amount: toAmount(query.originalAmount, query.originalFromInfo.decimals).toString() + }); + console.log("amount, display amount: ", { amount, displayAmount }); + return { amount, displayAmount }; + } + const fromInfo = getTokenOnOraichain(query.originalFromInfo.coinGeckoId); + const toInfo = getTokenOnOraichain(query.originalToInfo.coinGeckoId); + if (!fromInfo || !toInfo) + throw new Error( + `Cannot find token on Oraichain for token ${query.originalFromInfo.coinGeckoId} and ${query.originalToInfo.coinGeckoId}` + ); + const { amount } = await UniversalSwapHelper.simulateSwap({ + fromInfo, + toInfo, + amount: toAmount(query.originalAmount, fromInfo.decimals).toString(), + routerClient: query.routerClient + }); return { - // amount: toDisplay(amount, fromInfo.decimals, toInfo.decimals).toString(), - amount: new BigDecimal(amount) - .mul(10n ** BigInt(toInfo.decimals)) - .div(10n ** BigInt(fromInfo.decimals)) - .toString(), - displayAmount: toDisplay(amount, fromInfo.decimals) + amount, + displayAmount: toDisplay(amount, getTokenOnOraichain(toInfo.coinGeckoId)?.decimals) }; - } - try { - // get proxy contract object so that we can query the corresponding router address - const provider = new ethers.providers.JsonRpcProvider(fromInfo.rpc); - const toTokenInfoOnSameChainId = getTokenOnSpecificChainId(toInfo.coinGeckoId, fromInfo.chainId); - const swapRouterV2 = IUniswapV2Router02__factory.connect(proxyContractInfo[fromInfo.chainId].routerAddr, provider); - const route = getEvmSwapRoute(fromInfo.chainId, fromInfo.contractAddress, toTokenInfoOnSameChainId.contractAddress); - const outs = await swapRouterV2.getAmountsOut(amount, route); - if (outs.length === 0) throw new Error("There is no output amounts after simulating evm swap"); - const simulateAmount = outs.slice(-1)[0].toString(); - return { - // to display to reset the simulate amount to correct display type (swap simulate from -> same chain id to, so we use same chain id toToken decimals) - // then toAmount with actual toInfo decimals so that it has the same decimals as other tokens displayed - amount: simulateAmount, - displayAmount: toDisplay(simulateAmount, toTokenInfoOnSameChainId.decimals) // get the final out amount, which is the token out amount we want + }; + + static checkFeeRelayer = async (query: { + originalFromToken: TokenItemType; + relayerFee: { + relayerAmount: string; + relayerDecimals: number; }; - } catch (ex) { - console.log("error simulating evm: ", ex); - } -}; + fromAmount: number; + routerClient: OraiswapRouterReadOnlyInterface; + }): Promise => { + const { originalFromToken, relayerFee, fromAmount, routerClient } = query; + if (!relayerFee || !parseInt(relayerFee.relayerAmount)) return true; + const relayerDisplay = toDisplay(relayerFee.relayerAmount, relayerFee.relayerDecimals); + + // From Token is orai + if (originalFromToken.coinGeckoId === "oraichain-token") { + if (relayerDisplay >= fromAmount) return false; + return true; + } -export const handleSimulateSwap = async (query: { - originalFromInfo: TokenItemType; - originalToInfo: TokenItemType; - originalAmount: number; - routerClient: OraiswapRouterReadOnlyInterface; -}): Promise => { - // if the from token info is on bsc or eth, then we simulate using uniswap / pancake router - // otherwise, simulate like normal - if ( - isSupportedNoPoolSwapEvm(query.originalFromInfo.coinGeckoId) || - isEvmSwappable({ - fromChainId: query.originalFromInfo.chainId, - toChainId: query.originalToInfo.chainId, - fromContractAddr: query.originalFromInfo.contractAddress, - toContractAddr: query.originalToInfo.contractAddress - }) - ) { - // reset previous amount calculation since now we need to deal with original from & to info, not oraichain token info - const { amount, displayAmount } = await simulateSwapEvm({ - fromInfo: query.originalFromInfo, - toInfo: query.originalToInfo, - amount: toAmount(query.originalAmount, query.originalFromInfo.decimals).toString() + return UniversalSwapHelper.checkFeeRelayerNotOrai({ + fromTokenInOrai: getTokenOnOraichain(originalFromToken.coinGeckoId), + fromAmount, + relayerAmount: relayerFee.relayerAmount, + routerClient }); - console.log("amount, display amount: ", { amount, displayAmount }); - return { amount, displayAmount }; - } - const fromInfo = getTokenOnOraichain(query.originalFromInfo.coinGeckoId); - const toInfo = getTokenOnOraichain(query.originalToInfo.coinGeckoId); - if (!fromInfo || !toInfo) - throw new Error( - `Cannot find token on Oraichain for token ${query.originalFromInfo.coinGeckoId} and ${query.originalToInfo.coinGeckoId}` - ); - const { amount } = await simulateSwap({ - fromInfo, - toInfo, - amount: toAmount(query.originalAmount, fromInfo.decimals).toString(), - routerClient: query.routerClient - }); - return { - amount, - displayAmount: toDisplay(amount, getTokenOnOraichain(toInfo.coinGeckoId)?.decimals) }; -}; -export const checkFeeRelayer = async (query: { - originalFromToken: TokenItemType; - relayerFee: { + static checkFeeRelayerNotOrai = async (query: { + fromTokenInOrai: TokenItemType; + fromAmount: number; relayerAmount: string; - relayerDecimals: number; - }; - fromAmount: number; - routerClient: OraiswapRouterReadOnlyInterface; -}): Promise => { - const { originalFromToken, relayerFee, fromAmount, routerClient } = query; - if (!relayerFee || !parseInt(relayerFee.relayerAmount)) return true; - const relayerDisplay = toDisplay(relayerFee.relayerAmount, relayerFee.relayerDecimals); - - // From Token is orai - if (originalFromToken.coinGeckoId === "oraichain-token") { - if (relayerDisplay >= fromAmount) return false; - return true; - } - - return checkFeeRelayerNotOrai({ - fromTokenInOrai: getTokenOnOraichain(originalFromToken.coinGeckoId), - fromAmount, - relayerAmount: relayerFee.relayerAmount, - routerClient - }); -}; - -export const checkFeeRelayerNotOrai = async (query: { - fromTokenInOrai: TokenItemType; - fromAmount: number; - relayerAmount: string; - routerClient: OraiswapRouterReadOnlyInterface; -}): Promise => { - const { fromTokenInOrai, fromAmount, routerClient, relayerAmount } = query; - if (!fromTokenInOrai) return true; - if (fromTokenInOrai.chainId !== "Oraichain") - throw generateError( - "From token on Oraichain is not on Oraichain. The developers have made a mistake. Please notify them!" - ); - // estimate exchange token when From Token not orai. Only need to swap & check if it is swappable with ORAI. Otherwise, we ignore the fees - if (isInPairList(fromTokenInOrai.denom) || isInPairList(fromTokenInOrai.contractAddress)) { - const oraiToken = getTokenOnOraichain("oraichain-token"); - const { amount } = await simulateSwap({ - fromInfo: fromTokenInOrai, - toInfo: oraiToken, - amount: toAmount(fromAmount, fromTokenInOrai.decimals).toString(), - routerClient: routerClient - }); - const amountDisplay = toDisplay(amount, fromTokenInOrai.decimals); - const relayerAmountDisplay = toDisplay(relayerAmount); - if (relayerAmountDisplay > amountDisplay) return false; + routerClient: OraiswapRouterReadOnlyInterface; + }): Promise => { + const { fromTokenInOrai, fromAmount, routerClient, relayerAmount } = query; + if (!fromTokenInOrai) return true; + if (fromTokenInOrai.chainId !== "Oraichain") + throw generateError( + "From token on Oraichain is not on Oraichain. The developers have made a mistake. Please notify them!" + ); + // estimate exchange token when From Token not orai. Only need to swap & check if it is swappable with ORAI. Otherwise, we ignore the fees + if (isInPairList(fromTokenInOrai.denom) || isInPairList(fromTokenInOrai.contractAddress)) { + const oraiToken = getTokenOnOraichain("oraichain-token"); + const { amount } = await UniversalSwapHelper.simulateSwap({ + fromInfo: fromTokenInOrai, + toInfo: oraiToken, + amount: toAmount(fromAmount, fromTokenInOrai.decimals).toString(), + routerClient: routerClient + }); + const amountDisplay = toDisplay(amount, fromTokenInOrai.decimals); + const relayerAmountDisplay = toDisplay(relayerAmount); + if (relayerAmountDisplay > amountDisplay) return false; + return true; + } return true; - } - return true; -}; + }; -// verify balance -export const checkBalanceChannelIbc = async ( - ibcInfo: IBCInfo, - fromToken: TokenItemType, - toToken: TokenItemType, - toSimulateAmount: string, - client: CosmWasmClient, - ibcWasmContract: string -) => { - try { - let pairKey = buildIbcWasmPairKey(ibcInfo.source, ibcInfo.channel, toToken.denom); - if (toToken.prefix && toToken.contractAddress) { - pairKey = buildIbcWasmPairKey(ibcInfo.source, ibcInfo.channel, `${toToken.prefix}${toToken.contractAddress}`); - } - const ics20Client = new CwIcs20LatestQueryClient(client, ibcWasmContract); - let balance: Amount; + // verify balance + static checkBalanceChannelIbc = async ( + ibcInfo: IBCInfo, + fromToken: TokenItemType, + toToken: TokenItemType, + toSimulateAmount: string, + client: CosmWasmClient, + ibcWasmContract: string + ) => { try { - const { balance: channelBalance } = await ics20Client.channelWithKey({ - channelId: ibcInfo.channel, - denom: pairKey - }); - balance = channelBalance; + let pairKey = UniversalSwapHelper.buildIbcWasmPairKey(ibcInfo.source, ibcInfo.channel, toToken.denom); + if (toToken.prefix && toToken.contractAddress) { + pairKey = UniversalSwapHelper.buildIbcWasmPairKey( + ibcInfo.source, + ibcInfo.channel, + `${toToken.prefix}${toToken.contractAddress}` + ); + } + const ics20Client = new CwIcs20LatestQueryClient(client, ibcWasmContract); + let balance: Amount; + try { + const { balance: channelBalance } = await ics20Client.channelWithKey({ + channelId: ibcInfo.channel, + denom: pairKey + }); + balance = channelBalance; + } catch (error) { + // do nothing because the given channel and key doesnt exist + // console.log("error querying channel with key: ", error); + return; + } + + if ("native" in balance) { + const pairMapping = await ics20Client.pairMapping({ key: pairKey }); + const trueBalance = toDisplay(balance.native.amount, pairMapping.pair_mapping.remote_decimals); + let _toAmount = toDisplay(toSimulateAmount, toToken.decimals); + if (fromToken.coinGeckoId !== toToken.coinGeckoId) { + const fromTokenInfo = getTokenOnOraichain(fromToken.coinGeckoId); + const toTokenInfo = getTokenOnOraichain(toToken.coinGeckoId); + const routerClient = new OraiswapRouterQueryClient(client, network.router); + if (!fromTokenInfo || !toTokenInfo) + throw generateError( + `Error in checking balance channel ibc: cannot simulate from: ${fromToken.coinGeckoId} to: ${toToken.coinGeckoId}` + ); + const { amount } = await UniversalSwapHelper.simulateSwap({ + fromInfo: fromTokenInfo, + toInfo: toTokenInfo, + amount: toAmount(_toAmount, fromTokenInfo.decimals).toString(), + routerClient + }); + _toAmount = toDisplay(amount, fromTokenInfo.decimals); + } + if (trueBalance < _toAmount) throw generateError(`pair key is not enough balance!`); + } } catch (error) { - // do nothing because the given channel and key doesnt exist - // console.log("error querying channel with key: ", error); - return; + throw generateError(`Error in checking balance channel ibc: ${JSON.stringify(error)}`); } + }; - if ("native" in balance) { - const pairMapping = await ics20Client.pairMapping({ key: pairKey }); - const trueBalance = toDisplay(balance.native.amount, pairMapping.pair_mapping.remote_decimals); - let _toAmount = toDisplay(toSimulateAmount, toToken.decimals); - if (fromToken.coinGeckoId !== toToken.coinGeckoId) { - const fromTokenInfo = getTokenOnOraichain(fromToken.coinGeckoId); - const toTokenInfo = getTokenOnOraichain(toToken.coinGeckoId); - const routerClient = new OraiswapRouterQueryClient(client, network.router); - if (!fromTokenInfo || !toTokenInfo) - throw generateError( - `Error in checking balance channel ibc: cannot simulate from: ${fromToken.coinGeckoId} to: ${toToken.coinGeckoId}` - ); - const { amount } = await simulateSwap({ - fromInfo: fromTokenInfo, - toInfo: toTokenInfo, - amount: toAmount(_toAmount, fromTokenInfo.decimals).toString(), - routerClient - }); - _toAmount = toDisplay(amount, fromTokenInfo.decimals); - } - if (trueBalance < _toAmount) throw generateError(`pair key is not enough balance!`); + static getBalanceIBCOraichain = async (token: TokenItemType, client: CosmWasmClient, ibcWasmContract: string) => { + if (!token) return { balance: 0 }; + if (token.contractAddress) { + const cw20Token = new OraiswapTokenQueryClient(client, token.contractAddress); + const { balance } = await cw20Token.balance({ address: ibcWasmContract }); + return { balance: toDisplay(balance, token.decimals) }; } - } catch (error) { - throw generateError(`Error in checking balance channel ibc: ${JSON.stringify(error)}`); - } -}; + const { amount } = await client.getBalance(ibcWasmContract, token.denom); + return { balance: toDisplay(amount, token.decimals) }; + }; -export const getBalanceIBCOraichain = async (token: TokenItemType, client: CosmWasmClient, ibcWasmContract: string) => { - if (!token) return { balance: 0 }; - if (token.contractAddress) { - const cw20Token = new OraiswapTokenQueryClient(client, token.contractAddress); - const { balance } = await cw20Token.balance({ address: ibcWasmContract }); - return { balance: toDisplay(balance, token.decimals) }; - } - const { amount } = await client.getBalance(ibcWasmContract, token.denom); - return { balance: toDisplay(amount, token.decimals) }; -}; + // ORAI ( ETH ) -> check ORAI (ORAICHAIN - compare from amount with cw20 / native amount) (fromAmount) -> check AIRI - compare to amount with channel balance (ORAICHAIN) (toAmount) -> AIRI (BSC) + // ORAI ( ETH ) -> check ORAI (ORAICHAIN) - compare from amount with cw20 / native amount) (fromAmount) -> check wTRX - compare to amount with channel balance (ORAICHAIN) (toAmount) -> wTRX (TRON) + static checkBalanceIBCOraichain = async ( + to: TokenItemType, + from: TokenItemType, + fromAmount: number, + toSimulateAmount: string, + client: CosmWasmClient, + ibcWasmContract: string + ) => { + // ORAI ( ETH ) -> check ORAI (ORAICHAIN) -> ORAI (BSC) + // no need to check this case because users will swap directly. This case should be impossible because it is only called when transferring from evm to other networks + if (from.chainId === "Oraichain" && to.chainId === from.chainId) return; + // always check from token in ibc wasm should have enough tokens to swap / send to destination + const token = getTokenOnOraichain(from.coinGeckoId); + if (!token) return; + let ibcWasmContractAddr = ibcWasmContract; + // TODO: check balance with kawaii token and milky token + if (["kawaii-islands", "milky-token"].includes(from.coinGeckoId) && ["0x38"].includes(from.chainId)) { + ibcWasmContractAddr = network.converter; + } + const { balance } = await UniversalSwapHelper.getBalanceIBCOraichain(token, client, ibcWasmContractAddr); + if (balance < fromAmount) { + throw generateError( + `The bridge contract does not have enough balance to process this bridge transaction. Wanted ${fromAmount}, have ${balance}` + ); + } + // if to token is evm, then we need to evaluate channel state balance of ibc wasm + if (to.chainId === "0x01" || to.chainId === "0x38" || to.chainId === "0x2b6653dc") { + const ibcInfo: IBCInfo | undefined = UniversalSwapHelper.getIbcInfo("Oraichain", to.chainId); + if (!ibcInfo) throw generateError("IBC Info error when checking ibc balance"); + await UniversalSwapHelper.checkBalanceChannelIbc(ibcInfo, from, to, toSimulateAmount, client, ibcWasmContract); + } + }; -// ORAI ( ETH ) -> check ORAI (ORAICHAIN - compare from amount with cw20 / native amount) (fromAmount) -> check AIRI - compare to amount with channel balance (ORAICHAIN) (toAmount) -> AIRI (BSC) -// ORAI ( ETH ) -> check ORAI (ORAICHAIN) - compare from amount with cw20 / native amount) (fromAmount) -> check wTRX - compare to amount with channel balance (ORAICHAIN) (toAmount) -> wTRX (TRON) -export const checkBalanceIBCOraichain = async ( - to: TokenItemType, - from: TokenItemType, - fromAmount: number, - toSimulateAmount: string, - client: CosmWasmClient, - ibcWasmContract: string -) => { - // ORAI ( ETH ) -> check ORAI (ORAICHAIN) -> ORAI (BSC) - // no need to check this case because users will swap directly. This case should be impossible because it is only called when transferring from evm to other networks - if (from.chainId === "Oraichain" && to.chainId === from.chainId) return; - // always check from token in ibc wasm should have enough tokens to swap / send to destination - const token = getTokenOnOraichain(from.coinGeckoId); - if (!token) return; - let ibcWasmContractAddr = ibcWasmContract; - // TODO: check balance with kawaii token and milky token - if (["kawaii-islands", "milky-token"].includes(from.coinGeckoId) && ["0x38"].includes(from.chainId)) { - ibcWasmContractAddr = network.converter; - } - const { balance } = await getBalanceIBCOraichain(token, client, ibcWasmContractAddr); - if (balance < fromAmount) { - throw generateError( - `The bridge contract does not have enough balance to process this bridge transaction. Wanted ${fromAmount}, have ${balance}` + static filterNonPoolEvmTokens = ( + chainId: string, + coingeckoId: CoinGeckoId, + denom: string, + searchTokenName: string, + direction: SwapDirection // direction = to means we are filtering to tokens + ) => { + // basic filter. Dont include itself & only collect tokens with searched letters + const listTokens = direction === SwapDirection.From ? swapFromTokens : swapToTokens; + let filteredToTokens = listTokens.filter( + (token) => token.denom !== denom && token.name.toLowerCase().includes(searchTokenName.toLowerCase()) ); - } - // if to token is evm, then we need to evaluate channel state balance of ibc wasm - if (to.chainId === "0x01" || to.chainId === "0x38" || to.chainId === "0x2b6653dc") { - const ibcInfo: IBCInfo | undefined = getIbcInfo("Oraichain", to.chainId); - if (!ibcInfo) throw generateError("IBC Info error when checking ibc balance"); - await checkBalanceChannelIbc(ibcInfo, from, to, toSimulateAmount, client, ibcWasmContract); - } -}; + // special case for tokens not having a pool on Oraichain + if (UniversalSwapHelper.isSupportedNoPoolSwapEvm(coingeckoId)) { + const swappableTokens = Object.keys(UniversalSwapHelper.swapEvmRoutes[chainId]).map((key) => key.split("-")[1]); + const filteredTokens = filteredToTokens.filter((token) => swappableTokens.includes(token.contractAddress)); -export const filterNonPoolEvmTokens = ( - chainId: string, - coingeckoId: CoinGeckoId, - denom: string, - searchTokenName: string, - direction: SwapDirection // direction = to means we are filtering to tokens -) => { - // basic filter. Dont include itself & only collect tokens with searched letters - const listTokens = direction === SwapDirection.From ? swapFromTokens : swapToTokens; - let filteredToTokens = listTokens.filter( - (token) => token.denom !== denom && token.name.toLowerCase().includes(searchTokenName.toLowerCase()) - ); - // special case for tokens not having a pool on Oraichain - if (isSupportedNoPoolSwapEvm(coingeckoId)) { - const swappableTokens = Object.keys(swapEvmRoutes[chainId]).map((key) => key.split("-")[1]); - const filteredTokens = filteredToTokens.filter((token) => swappableTokens.includes(token.contractAddress)); - - // tokens that dont have a pool on Oraichain like WETH or WBNB cannot be swapped from a token on Oraichain - if (direction === SwapDirection.To) - return [...new Set(filteredTokens.concat(filteredTokens.map((token) => getTokenOnOraichain(token.coinGeckoId))))]; - filteredToTokens = filteredTokens; - } - // special case filter. Tokens on networks other than supported evm cannot swap to tokens, so we need to remove them - if (!isEvmNetworkNativeSwapSupported(chainId as NetworkChainId)) - return filteredToTokens.filter((t) => { - // one-directional swap. non-pool tokens of evm network can swap be swapped with tokens on Oraichain, but not vice versa - const isSupported = isSupportedNoPoolSwapEvm(t.coinGeckoId); - if (direction === SwapDirection.To) return !isSupported; - if (isSupported) { - // if we cannot find any matched token then we dont include it in the list since it cannot be swapped - const sameChainId = getTokenOnSpecificChainId(coingeckoId, t.chainId as NetworkChainId); - if (!sameChainId) return false; + // tokens that dont have a pool on Oraichain like WETH or WBNB cannot be swapped from a token on Oraichain + if (direction === SwapDirection.To) + return [ + ...new Set(filteredTokens.concat(filteredTokens.map((token) => getTokenOnOraichain(token.coinGeckoId)))) + ]; + filteredToTokens = filteredTokens; + } + // special case filter. Tokens on networks other than supported evm cannot swap to tokens, so we need to remove them + if (!UniversalSwapHelper.isEvmNetworkNativeSwapSupported(chainId as NetworkChainId)) + return filteredToTokens.filter((t) => { + // one-directional swap. non-pool tokens of evm network can swap be swapped with tokens on Oraichain, but not vice versa + const isSupported = UniversalSwapHelper.isSupportedNoPoolSwapEvm(t.coinGeckoId); + if (direction === SwapDirection.To) return !isSupported; + if (isSupported) { + // if we cannot find any matched token then we dont include it in the list since it cannot be swapped + const sameChainId = getTokenOnSpecificChainId(coingeckoId, t.chainId as NetworkChainId); + if (!sameChainId) return false; + return true; + } return true; - } + }); + return filteredToTokens.filter((t) => { + // filter out to tokens that are on a different network & with no pool because we are not ready to support them yet. TODO: support + if (UniversalSwapHelper.isSupportedNoPoolSwapEvm(t.coinGeckoId)) return t.chainId === chainId; return true; }); - return filteredToTokens.filter((t) => { - // filter out to tokens that are on a different network & with no pool because we are not ready to support them yet. TODO: support - if (isSupportedNoPoolSwapEvm(t.coinGeckoId)) return t.chainId === chainId; - return true; - }); -}; + }; -export const generateConvertErc20Cw20Message = ( - amounts: AmountDetails, - tokenInfo: TokenItemType, - sender?: string -): ExecuteInstruction[] => { - if (!tokenInfo.evmDenoms) return []; - const subAmounts = getSubAmountDetails(amounts, tokenInfo); - // we convert all mapped tokens to cw20 to unify the token - for (const denom in subAmounts) { - const balance = BigInt(subAmounts[denom] ?? "0"); - // reset so we convert using native first - const erc20TokenInfo = tokenMap[denom]; - if (balance > 0) { - const msgConvert: ExecuteInstruction = generateConvertMsgs({ - type: Type.CONVERT_TOKEN, - sender, - inputAmount: balance.toString(), - inputToken: erc20TokenInfo - }); - return [msgConvert]; + static generateConvertErc20Cw20Message = ( + amounts: AmountDetails, + tokenInfo: TokenItemType, + sender?: string + ): ExecuteInstruction[] => { + if (!tokenInfo.evmDenoms) return []; + const subAmounts = getSubAmountDetails(amounts, tokenInfo); + // we convert all mapped tokens to cw20 to unify the token + for (const denom in subAmounts) { + const balance = BigInt(subAmounts[denom] ?? "0"); + // reset so we convert using native first + const erc20TokenInfo = tokenMap[denom]; + if (balance > 0) { + const msgConvert: ExecuteInstruction = UniversalSwapHelper.generateConvertMsgs({ + type: Type.CONVERT_TOKEN, + sender, + inputAmount: balance.toString(), + inputToken: erc20TokenInfo + }); + return [msgConvert]; + } } - } - return []; -}; + return []; + }; -export const generateConvertCw20Erc20Message = ( - amounts: AmountDetails, - tokenInfo: TokenItemType, - sender: string, - sendCoin: Coin -): ExecuteInstruction[] => { - if (!tokenInfo.evmDenoms) return []; - // we convert all mapped tokens to cw20 to unify the token - for (const denom of tokenInfo.evmDenoms) { - // optimize. Only convert if not enough balance & match denom - if (denom !== sendCoin.denom) continue; - - // if this wallet already has enough native ibc bridge balance => no need to convert reverse - if (+amounts[sendCoin.denom] >= +sendCoin.amount) break; - - const balance = amounts[tokenInfo.denom]; - const evmToken = tokenMap[denom]; - - if (balance) { - const outputToken: TokenItemType = { - ...tokenInfo, - denom: evmToken.denom, - contractAddress: undefined, - decimals: evmToken.decimals - }; - const msgConvert = generateConvertMsgs({ - type: Type.CONVERT_TOKEN_REVERSE, - sender, - inputAmount: balance, - inputToken: tokenInfo, - outputToken - }); - return [msgConvert]; - } - } - return []; -}; + static generateConvertCw20Erc20Message = ( + amounts: AmountDetails, + tokenInfo: TokenItemType, + sender: string, + sendCoin: Coin + ): ExecuteInstruction[] => { + if (!tokenInfo.evmDenoms) return []; + // we convert all mapped tokens to cw20 to unify the token + for (const denom of tokenInfo.evmDenoms) { + // optimize. Only convert if not enough balance & match denom + if (denom !== sendCoin.denom) continue; -export const generateConvertMsgs = (data: ConvertType): ExecuteInstruction => { - const { type, sender, inputToken, inputAmount } = data; - let funds: Coin[] | null; - // for withdraw & provide liquidity methods, we need to interact with the oraiswap pair contract - let contractAddr = network.converter; - let input: any; - switch (type) { - case Type.CONVERT_TOKEN: { - // currently only support cw20 token pool - const { info: assetInfo, fund } = parseTokenInfo(inputToken, inputAmount); - // native case - if ("native_token" in assetInfo) { - input = { - convert: {} - }; - funds = handleSentFunds(fund); - } else { - // cw20 case - input = { - send: { - contract: network.converter, - amount: inputAmount, - msg: toBinary({ - convert: {} - }) - } + // if this wallet already has enough native ibc bridge balance => no need to convert reverse + if (+amounts[sendCoin.denom] >= +sendCoin.amount) break; + + const balance = amounts[tokenInfo.denom]; + const evmToken = tokenMap[denom]; + + if (balance) { + const outputToken: TokenItemType = { + ...tokenInfo, + denom: evmToken.denom, + contractAddress: undefined, + decimals: evmToken.decimals }; - contractAddr = assetInfo.token.contract_addr; + const msgConvert = UniversalSwapHelper.generateConvertMsgs({ + type: Type.CONVERT_TOKEN_REVERSE, + sender, + inputAmount: balance, + inputToken: tokenInfo, + outputToken + }); + return [msgConvert]; } - break; } - case Type.CONVERT_TOKEN_REVERSE: { - const { outputToken } = data as ConvertReverse; - - // currently only support cw20 token pool - const { info: assetInfo, fund } = parseTokenInfo(inputToken, inputAmount); - const { info: outputAssetInfo } = parseTokenInfo(outputToken, "0"); - // native case - if ("native_token" in assetInfo) { - input = { - convert_reverse: { - from_asset: outputAssetInfo - } - }; - funds = handleSentFunds(fund); - } else { - // cw20 case - input = { - send: { - contract: network.converter, - amount: inputAmount, - msg: toBinary({ - convert_reverse: { - from: outputAssetInfo - } - }) - } - }; - contractAddr = assetInfo.token.contract_addr; + return []; + }; + + static generateConvertMsgs = (data: ConvertType): ExecuteInstruction => { + const { type, sender, inputToken, inputAmount } = data; + let funds: Coin[] | null; + // for withdraw & provide liquidity methods, we need to interact with the oraiswap pair contract + let contractAddr = network.converter; + let input: any; + switch (type) { + case Type.CONVERT_TOKEN: { + // currently only support cw20 token pool + const { info: assetInfo, fund } = parseTokenInfo(inputToken, inputAmount); + // native case + if ("native_token" in assetInfo) { + input = { + convert: {} + }; + funds = handleSentFunds(fund); + } else { + // cw20 case + input = { + send: { + contract: network.converter, + amount: inputAmount, + msg: toBinary({ + convert: {} + }) + } + }; + contractAddr = assetInfo.token.contract_addr; + } + break; } - break; + case Type.CONVERT_TOKEN_REVERSE: { + const { outputToken } = data as ConvertReverse; + + // currently only support cw20 token pool + const { info: assetInfo, fund } = parseTokenInfo(inputToken, inputAmount); + const { info: outputAssetInfo } = parseTokenInfo(outputToken, "0"); + // native case + if ("native_token" in assetInfo) { + input = { + convert_reverse: { + from_asset: outputAssetInfo + } + }; + funds = handleSentFunds(fund); + } else { + // cw20 case + input = { + send: { + contract: network.converter, + amount: inputAmount, + msg: toBinary({ + convert_reverse: { + from: outputAssetInfo + } + }) + } + }; + contractAddr = assetInfo.token.contract_addr; + } + break; + } + default: + break; } - default: - break; - } - - const msg: ExecuteInstruction = { - contractAddress: contractAddr, - msg: input, - funds + + const msg: ExecuteInstruction = { + contractAddress: contractAddr, + msg: input, + funds + }; + + return msg; }; +} +// evm swap helpers +/** + * @deprecated. Use UniversalSwapHelper.isSupportedNoPoolSwapEvm + */ +export const isSupportedNoPoolSwapEvm = UniversalSwapHelper.isSupportedNoPoolSwapEvm; - return msg; -}; +/** + * @deprecated. + */ +export const isEvmNetworkNativeSwapSupported = UniversalSwapHelper.isEvmNetworkNativeSwapSupported; + +/** + * @deprecated. + */ +export const swapEvmRoutes: { + [network: string]: { + [pair: string]: string[]; + }; +} = UniversalSwapHelper.swapEvmRoutes; + +/** + * @deprecated. + */ +export const buildSwapRouterKey = UniversalSwapHelper.buildSwapRouterKey; + +/** + * @deprecated. + */ +export const getEvmSwapRoute = UniversalSwapHelper.getEvmSwapRoute; + +// static functions +/** + * @deprecated. + */ +export const isEvmSwappable = UniversalSwapHelper.isEvmSwappable; + +// ibc helpers +/** + * @deprecated. + */ +export const getIbcInfo = UniversalSwapHelper.getIbcInfo; + +/** + * @deprecated. + */ +export const buildIbcWasmPairKey = UniversalSwapHelper.buildIbcWasmPairKey; + +/** + * This function converts the destination address (from BSC / ETH -> Oraichain) to an appropriate format based on the BSC / ETH token contract address + * @param oraiAddress - receiver address on Oraichain + * @param contractAddress - BSC / ETH token contract address + * @returns converted receiver address + * @deprecated use UniversalSwapHelper.getSourceReceiver instead + */ +export const getSourceReceiver = UniversalSwapHelper.getSourceReceiver; + +/** + * This function receives fromToken and toToken as parameters to generate the destination memo for the receiver address + * @param from - from token + * @param to - to token + * @param destReceiver - destination destReceiver + * @returns destination in the format /: + * @deprecated use UniversalSwapHelper.getRoute instead + */ +export const getRoute = UniversalSwapHelper.getRoute; + +export const addOraiBridgeRoute = UniversalSwapHelper.addOraiBridgeRoute; + +/** + * cases: + * /: + * : + * + * :/: + * : + * :: + * @deprecated + * */ +export const unmarshalOraiBridgeRoute = UniversalSwapHelper.unmarshalOraiBridgeRoute; + +/** + * @deprecated + */ +export const generateSwapRoute = UniversalSwapHelper.generateSwapRoute; + +// generate messages +/** + * @deprecated + */ +export const generateSwapOperationMsgs = UniversalSwapHelper.generateSwapOperationMsgs; + +// simulate swap functions +/** + * @deprecated + */ +export const simulateSwap = UniversalSwapHelper.simulateSwap; + +/** + * @deprecated + */ +export const simulateSwapEvm = UniversalSwapHelper.simulateSwapEvm; + +/** + * @deprecated + */ +export const handleSimulateSwap = UniversalSwapHelper.handleSimulateSwap; + +/** + * @deprecated + */ +export const checkFeeRelayer = UniversalSwapHelper.checkFeeRelayer; + +/** + * @deprecated + */ +export const checkFeeRelayerNotOrai = UniversalSwapHelper.checkFeeRelayerNotOrai; + +// verify balance +/** + * @deprecated + */ +export const checkBalanceChannelIbc = UniversalSwapHelper.checkBalanceChannelIbc; + +/** + * @deprecated + */ +export const getBalanceIBCOraichain = UniversalSwapHelper.getBalanceIBCOraichain; + +// ORAI ( ETH ) -> check ORAI (ORAICHAIN - compare from amount with cw20 / native amount) (fromAmount) -> check AIRI - compare to amount with channel balance (ORAICHAIN) (toAmount) -> AIRI (BSC) +// ORAI ( ETH ) -> check ORAI (ORAICHAIN) - compare from amount with cw20 / native amount) (fromAmount) -> check wTRX - compare to amount with channel balance (ORAICHAIN) (toAmount) -> wTRX (TRON) +/** + * @deprecated + */ +export const checkBalanceIBCOraichain = UniversalSwapHelper.checkBalanceIBCOraichain; + +/** + * @deprecated + */ +export const filterNonPoolEvmTokens = UniversalSwapHelper.filterNonPoolEvmTokens; + +/** + * @deprecated + */ +export const generateConvertErc20Cw20Message = UniversalSwapHelper.generateConvertErc20Cw20Message; + +/** + * @deprecated + */ +export const generateConvertCw20Erc20Message = UniversalSwapHelper.generateConvertCw20Erc20Message; + +/** + * @deprecated + */ +export const generateConvertMsgs = UniversalSwapHelper.generateConvertMsgs; diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index bf019f81..a85ad617 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -23,7 +23,6 @@ import { USDC_CONTRACT } 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 * as universalHelper from "../src/helper"; import { DirectSecp256k1HdWallet, EncodeObject, OfflineSigner } from "@cosmjs/proto-signing"; import { JsonRpcProvider, JsonRpcSigner } from "@ethersproject/providers"; import TronWeb from "tronweb"; @@ -47,6 +46,7 @@ import { deployIcs20Token, deployToken, testSenderAddress } from "./test-common" import * as oraidexArtifacts from "@oraichain/oraidex-contracts-build"; import { readFileSync } from "fs"; import { UniversalSwapHandler } from "../src/handler"; +import { UniversalSwapHelper } from "../src/helper"; describe("test universal swap handler functions", () => { const client = new SimulateCosmWasmClient({ @@ -321,7 +321,7 @@ describe("test universal swap handler functions", () => { contract: IBC_WASM_CONTRACT, amount: simulateAmount, msg: toBinary({ - local_channel_id: universalHelper.getIbcInfo("Oraichain", "noble-1").channel, + local_channel_id: UniversalSwapHelper.getIbcInfo("Oraichain", "noble-1").channel, remote_address: "noble1234", remote_denom: "uusdc", timeout: IBC_TRANSFER_TIMEOUT, @@ -428,8 +428,8 @@ describe("test universal swap handler functions", () => { (item) => item.coinGeckoId === fromDenom && item.chainId === fromChainId ); // TODO: run tests without mocking to simulate actual swap logic - jest.spyOn(universalHelper, "simulateSwap").mockResolvedValue({ amount: relayerFeeAmount }); - const result = await universalHelper.checkFeeRelayer({ + jest.spyOn(UniversalSwapHelper, "simulateSwap").mockResolvedValue({ amount: relayerFeeAmount }); + const result = await UniversalSwapHelper.checkFeeRelayer({ originalFromToken: originalFromToken as TokenItemType, fromAmount: 1, relayerFee: { @@ -450,8 +450,8 @@ describe("test universal swap handler functions", () => { async (fromDenom, mockSimulateAmount, mockRelayerFee, isSufficient) => { const originalFromToken = oraichainTokens.find((item) => item.coinGeckoId === fromDenom); // TODO: run tests without mocking to simulate actual swap - jest.spyOn(universalHelper, "simulateSwap").mockResolvedValue({ amount: mockSimulateAmount }); - const result = await universalHelper.checkFeeRelayerNotOrai({ + jest.spyOn(UniversalSwapHelper, "simulateSwap").mockResolvedValue({ amount: mockSimulateAmount }); + const result = await UniversalSwapHelper.checkFeeRelayerNotOrai({ fromTokenInOrai: originalFromToken as TokenItemType, fromAmount: 1, relayerAmount: mockRelayerFee, @@ -551,7 +551,7 @@ describe("test universal swap handler functions", () => { ] ])("test-universal-swap-checkBalanceChannelIbc-%", async (fromToken, toToken, amount, channel, willThrow) => { try { - await universalHelper.checkBalanceChannelIbc( + await UniversalSwapHelper.checkBalanceChannelIbc( { source: oraiPort, channel: channel, @@ -577,7 +577,7 @@ describe("test universal swap handler functions", () => { if (mockToken.contractAddress) { if (mockToken.coinGeckoId === "airight") mockToken.contractAddress = airiToken.contractAddress; } - const { balance } = await universalHelper.getBalanceIBCOraichain( + const { balance } = await UniversalSwapHelper.getBalanceIBCOraichain( mockToken, ics20Contract.client, ics20Contract.contractAddress @@ -612,9 +612,9 @@ describe("test universal swap handler functions", () => { async (from: TokenItemType, to: TokenItemType, fromAmount: number, toAmount: string, willThrow: boolean) => { try { jest - .spyOn(universalHelper, "getBalanceIBCOraichain") + .spyOn(UniversalSwapHelper, "getBalanceIBCOraichain") .mockReturnValue(new Promise((resolve) => resolve({ balance: +toAmount }))); - universalHelper.checkBalanceIBCOraichain( + UniversalSwapHelper.checkBalanceIBCOraichain( from, to, fromAmount, @@ -637,6 +637,8 @@ describe("test universal swap handler functions", () => { ])("test-processUniversalSwap", async (universalSwapType, expectedFunction) => { const fromToken = flattenTokens.find((item) => item.coinGeckoId === "airight" && item.chainId === "0x38")!; const toToken = flattenTokens.find((item) => item.coinGeckoId === "tether" && item.chainId === "0x2b6653dc")!; + const spy = jest.spyOn(UniversalSwapHelper, "addOraiBridgeRoute"); + spy.mockReturnValue({ swapRoute: "", universalSwapType }); const universalSwap = new FakeUniversalSwapHandler({ ...universalSwapData, originalFromToken: fromToken, @@ -648,8 +650,8 @@ describe("test universal swap handler functions", () => { .mockResolvedValue("swapAndTransferToOtherNetworks" as any); jest.spyOn(universalSwap, "swapCosmosToOtherNetwork").mockResolvedValue("swapCosmosToOtherNetwork" as any); jest.spyOn(universalSwap, "transferAndSwap").mockResolvedValue("transferAndSwap" as any); - jest.spyOn(universalHelper, "addOraiBridgeRoute").mockReturnValue({ swapRoute: "", universalSwapType }); const result = await universalSwap.processUniversalSwap(); + expect(spy).toHaveBeenCalled(); expect(result).toEqual(expectedFunction); }); @@ -805,7 +807,7 @@ describe("test universal swap handler functions", () => { ...universalSwapData, originalToToken: flattenTokens.find((t) => t.coinGeckoId === toCoingeckoId)! }); - const ibcInfo = universalHelper.getIbcInfo("Oraichain", "oraibridge-subnet-2"); + const ibcInfo = UniversalSwapHelper.getIbcInfo("Oraichain", "oraibridge-subnet-2"); const toAddress = "foobar"; const ibcMemo = ""; const msg = universalSwap.generateMsgsIbcWasm(ibcInfo, toAddress, "john doe", ibcMemo)!; @@ -948,7 +950,7 @@ describe("test universal swap handler functions", () => { jest.spyOn(routerClient, "simulateSwapOperations").mockReturnValue(new Promise((resolve) => resolve({ amount }))); const [fromInfo, toInfo] = [toTokenInfo(fromToken!), toTokenInfo(toToken!)]; const query = { fromInfo, toInfo, amount, routerClient }; - const simulateData = await universalHelper.simulateSwap(query); + const simulateData = await UniversalSwapHelper.simulateSwap(query); expect(simulateData.amount).toEqual(expectedSimulateData); } ); @@ -959,15 +961,15 @@ describe("test universal swap handler functions", () => { [true, false, "2"], [true, true, "2"] ])("test handleSimulateSwap", async (isSupportedNoPoolSwapEvmRes, isEvmSwappableRes, expectedSimulateAmount) => { - const simulateSwapSpy = jest.spyOn(universalHelper, "simulateSwap"); - const simulateSwapEvmSpy = jest.spyOn(universalHelper, "simulateSwapEvm"); + const simulateSwapSpy = jest.spyOn(UniversalSwapHelper, "simulateSwap"); + const simulateSwapEvmSpy = jest.spyOn(UniversalSwapHelper, "simulateSwapEvm"); simulateSwapSpy.mockResolvedValue({ amount: "1" }); simulateSwapEvmSpy.mockResolvedValue({ amount: "2", displayAmount: 2 }); - const isSupportedNoPoolSwapEvmSpy = jest.spyOn(universalHelper, "isSupportedNoPoolSwapEvm"); - const isEvmSwappableSpy = jest.spyOn(universalHelper, "isEvmSwappable"); + const isSupportedNoPoolSwapEvmSpy = jest.spyOn(UniversalSwapHelper, "isSupportedNoPoolSwapEvm"); + const isEvmSwappableSpy = jest.spyOn(UniversalSwapHelper, "isEvmSwappable"); isSupportedNoPoolSwapEvmSpy.mockReturnValue(isSupportedNoPoolSwapEvmRes); isEvmSwappableSpy.mockReturnValue(isEvmSwappableRes); - const simulateData = await universalHelper.handleSimulateSwap({ + const simulateData = await UniversalSwapHelper.handleSimulateSwap({ originalFromInfo: oraichainTokens[0], originalToInfo: oraichainTokens[0], originalAmount: 0, From 2209bf9cfe2c20bc3f8bf4ca9bb3d853e79334c2 Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Tue, 2 Apr 2024 19:25:53 -0700 Subject: [PATCH 07/22] chore: re-enable nx reset to remove cache --- .github/workflows/check.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 712f80f3..5f242766 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -52,9 +52,9 @@ jobs: npm install -g yarn yarn - # - name: Run nx reset workspace - # run: | - # yarn nx reset + - name: Run nx reset workspace + run: | + yarn nx reset - name: Run test run: | From c49ed41ae4b65a092df7221cdc3b456054297f64 Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Tue, 2 Apr 2024 19:32:16 -0700 Subject: [PATCH 08/22] comment nx reset --- .github/workflows/check.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5f242766..712f80f3 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -52,9 +52,9 @@ jobs: npm install -g yarn yarn - - name: Run nx reset workspace - run: | - yarn nx reset + # - name: Run nx reset workspace + # run: | + # yarn nx reset - name: Run test run: | From b8bcf7477bb1a547f1d5689b42d6133803d70301 Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Wed, 3 Apr 2024 17:00:24 -0700 Subject: [PATCH 09/22] feat: add example unit tests using http mock --- package.json | 1 + packages/universal-swap/src/helper.ts | 3 +- .../universal-swap/tests/http-mock.spec.ts | 75 ++++++++++ packages/universal-swap/tests/index.spec.ts | 133 +++++++++++++++++- packages/universal-swap/tests/test-common.ts | 111 ++++++++++++++- yarn.lock | 56 +++++++- 6 files changed, 366 insertions(+), 13 deletions(-) create mode 100644 packages/universal-swap/tests/http-mock.spec.ts diff --git a/package.json b/package.json index a3150d05..b0d4a4f3 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.29.1", "eslint-plugin-security": "^2.1.1", + "http-request-mock": "^1.8.18", "husky": "^9.0.11", "jest": "^29.7.0", "lerna": "^8.1.2", diff --git a/packages/universal-swap/src/helper.ts b/packages/universal-swap/src/helper.ts index b8c5d515..0f3fe275 100644 --- a/packages/universal-swap/src/helper.ts +++ b/packages/universal-swap/src/helper.ts @@ -647,6 +647,7 @@ export class UniversalSwapHelper { const pairMapping = await ics20Client.pairMapping({ key: pairKey }); const trueBalance = toDisplay(balance.native.amount, pairMapping.pair_mapping.remote_decimals); let _toAmount = toDisplay(toSimulateAmount, toToken.decimals); + if (fromToken.coinGeckoId !== toToken.coinGeckoId) { const fromTokenInfo = getTokenOnOraichain(fromToken.coinGeckoId); const toTokenInfo = getTokenOnOraichain(toToken.coinGeckoId); @@ -666,7 +667,7 @@ export class UniversalSwapHelper { if (trueBalance < _toAmount) throw generateError(`pair key is not enough balance!`); } } catch (error) { - throw generateError(`Error in checking balance channel ibc: ${JSON.stringify(error)}`); + throw generateError(`Error in checking balance channel ibc: ${error}`); } }; diff --git a/packages/universal-swap/tests/http-mock.spec.ts b/packages/universal-swap/tests/http-mock.spec.ts new file mode 100644 index 00000000..7f6d3b95 --- /dev/null +++ b/packages/universal-swap/tests/http-mock.spec.ts @@ -0,0 +1,75 @@ +import "dotenv/config"; +import { CosmosWalletImpl } from "../src/universal-demos/offline-wallet"; +import { UniversalSwapHandler } from "../src/handler"; +import { + CoinGeckoId, + KWT_BSC_CONTRACT, + NetworkChainId, + ORAI, + USDC_CONTRACT, + cosmosTokens, + flattenTokens, + generateError, + getTokenOnOraichain, + toAmount +} from "@oraichain/oraidex-common"; +import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; +import { GasPrice, SigningStargateClient } from "@cosmjs/stargate"; +import fs from "fs"; +// configuration +import HttpRequestMock from "http-request-mock"; +import { mockAccountInfo, mockStatus, mockResponse, mockTxSearch, mockSimulate } from "./test-common"; +const mocker = HttpRequestMock.setupForFetch(); + +describe("test-nock", () => { + it("test-mock-rpc-post", async () => { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(process.env.MNEMONIC as any, { prefix: ORAI }); + const accounts = await wallet.getAccounts(); + const stargate = await SigningStargateClient.connectWithSigner("http://rpc.orai.io", wallet, { + gasPrice: GasPrice.fromString("0.001orai") + }); + mocker.mock({ + url: "http://rpc.orai.io", + method: "POST", + response: async (requestInfo) => { + console.log("original request info: ", requestInfo); + switch (requestInfo.body.method) { + case "abci_query": + const path = requestInfo.body.params.path; + if (path === "/cosmos.auth.v1beta1.Query/Account") { + console.log("return mock account info!"); + return mockAccountInfo; + } + if (path === "/cosmos.tx.v1beta1.Service/Simulate") { + console.log("return mock simulate!"); + return mockSimulate; + } + break; + case "status": + console.log("return mock status!"); + return mockStatus; + case "broadcast_tx_sync": + console.log("return mock broadcast!"); + return mockResponse; + case "tx_search": + console.log("return mock tx search!"); + return mockTxSearch; + default: + break; + } + // by default we do original call and return its response + const res = await requestInfo.doOriginalCall(); + // 3. and do something again. + console.log("original response:", JSON.stringify(res.responseJson)); + return res.responseJson; + } + }); + const result = await stargate.sendTokens( + accounts[0].address, + accounts[0].address, + [{ amount: "1", denom: ORAI }], + "auto" + ); + console.log({ result }); + }, 50000); +}); diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index a85ad617..f87bc360 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -20,7 +20,9 @@ import { IBC_TRANSFER_TIMEOUT, toTokenInfo, IBC_WASM_CONTRACT_TEST, - USDC_CONTRACT + USDC_CONTRACT, + ORAI, + toAmount } 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"; @@ -28,8 +30,8 @@ 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 { fromBase64, fromHex, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding"; +import { CosmWasmClient, toBinary } from "@cosmjs/cosmwasm-stargate"; import { ibcInfos, oraichain2oraib } from "@oraichain/oraidex-common/build/ibc-info"; import { OraiswapFactoryClient, @@ -38,15 +40,35 @@ import { OraiswapRouterQueryClient, OraiswapTokenClient } from "@oraichain/oraidex-contracts-sdk"; -import { CWSimulateApp, GenericError, IbcOrder, IbcPacket, SimulateCosmWasmClient } from "@oraichain/cw-simulate"; -import { CwIcs20LatestClient } from "@oraichain/common-contracts-sdk"; +import { + CWSimulateApp, + GenericError, + IbcEndpoint, + IbcOrder, + IbcPacket, + SimulateCosmWasmClient +} from "@oraichain/cw-simulate"; +import { Amount, ChannelInfo, 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 { + deployIcs20Token, + deployToken, + mockResponse, + mockStatus, + mockTxSearch, + testSenderAddress +} from "./test-common"; import * as oraidexArtifacts from "@oraichain/oraidex-contracts-build"; import { readFileSync } from "fs"; import { UniversalSwapHandler } from "../src/handler"; import { UniversalSwapHelper } from "../src/helper"; +import HttpRequestMock from "http-request-mock"; +import { QuerySmartContractStateRequest, QuerySmartContractStateResponse } from "cosmjs-types/cosmwasm/wasm/v1/query"; +import { + ChannelWithKeyResponse, + QueryMsg as Ics20QueryMsg +} from "@oraichain/common-contracts-sdk/build/CwIcs20Latest.types"; describe("test universal swap handler functions", () => { const client = new SimulateCosmWasmClient({ @@ -569,6 +591,105 @@ 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 mocker = HttpRequestMock.setupForFetch(); + const client = await CosmWasmClient.connect("http://rpc.orai.io"); + const jsonRpcResponse = (expectedResult: any) => { + return { + jsonrpc: "2.0", + id: 1, + result: { + response: { + code: 0, + log: "", + info: "", + index: "0", + key: null, + value: toBase64( + QuerySmartContractStateResponse.encode({ data: toUtf8(JSON.stringify(expectedResult)) }).finish() + ), + proofOps: null, + height: "1", + codespace: "" + } + } + }; + }; + try { + mocker.mock({ + url: "http://rpc.orai.io", + method: "POST", + response: async (requestInfo) => { + // console.log("original request info: ", requestInfo); + const dataRaw = requestInfo.body.params.data; + switch (requestInfo.body.method) { + case "abci_query": + const path = requestInfo.body.params.path; + if (path === "/cosmwasm.wasm.v1.Query/SmartContractState") { + const queryRequest = QuerySmartContractStateRequest.decode(fromHex(dataRaw)); + const queryData = JSON.parse(fromUtf8(queryRequest.queryData)); + if ("channel_with_key" in queryData) { + // console.log("query data channel_with_key", queryData); + return jsonRpcResponse({ + balance: { native: { denom: fromToken.denom, amount: toAmount(1).toString() } } + }); + } + if ("pair_mapping" in queryData) { + // console.log("query data pair mapping: ", queryData); + return jsonRpcResponse({ pair_mapping: { remote_decimals: 6 } }); + } + // console.log("query data: ", queryData); + break; + } + case "status": + return mockStatus; + default: + break; + } + // by default we do original call and return its response + const res = await requestInfo.doOriginalCall(); + // 3. and do something again. + console.log("original response:", JSON.stringify(res.responseJson)); + return res.responseJson; + } + }); + await UniversalSwapHelper.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); + } + }, + 50000 + ); + it.each([ [oraichainTokens.find((t) => t.coinGeckoId === "airight")!, 10000000], [oraichainTokens.find((t) => t.coinGeckoId === "oraichain-token")!, 0] diff --git a/packages/universal-swap/tests/test-common.ts b/packages/universal-swap/tests/test-common.ts index 4ee07b8a..aaa2bb88 100644 --- a/packages/universal-swap/tests/test-common.ts +++ b/packages/universal-swap/tests/test-common.ts @@ -1,10 +1,119 @@ import { SimulateCosmWasmClient } from "@oraichain/cw-simulate"; import { OraiswapTokenClient } from "@oraichain/oraidex-contracts-sdk"; -import { CwIcs20LatestClient , Cw20Coin } from "@oraichain/common-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"; export const testSenderAddress = "orai1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejvfgs7g"; +export const mockAccountInfo = { + jsonrpc: "2.0", + id: 955129638521, + result: { + response: { + code: 0, + log: "", + info: "", + index: "0", + key: null, + value: + "Cp8BCiAvY29zbW9zLmF1dGgudjFiZXRhMS5CYXNlQWNjb3VudBJ7CitvcmFpMWc0aDY0eWp0MGZ2enY1djJqOHR5Zm5wZTVrbW5ldGVqdmZnczdnEkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/7NDxzUr4iFC8PmBZllh5P6RdDtLgnvL32OVolC+2tGGKwQIN0R", + proofOps: null, + height: "17608705", + codespace: "" + } + } +}; + +export const mockSimulate = { + jsonrpc: "2.0", + id: 245744838225, + result: { + response: { + code: 0, + log: "", + info: "", + index: "0", + key: null, + value: + "CgQQp6kDEroJCiAKHgocL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBLEBVt7ImV2ZW50cyI6W3sidHlwZSI6ImNvaW5fcmVjZWl2ZWQiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJyZWNlaXZlciIsInZhbHVlIjoib3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZyJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiIxb3JhaSJ9XX0seyJ0eXBlIjoiY29pbl9zcGVudCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InNwZW5kZXIiLCJ2YWx1ZSI6Im9yYWkxZzRoNjR5anQwZnZ6djV2Mmo4dHlmbnBlNWttbmV0ZWp2ZmdzN2cifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiMW9yYWkifV19LHsidHlwZSI6Im1lc3NhZ2UiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJhY3Rpb24iLCJ2YWx1ZSI6Ii9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoib3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZyJ9LHsia2V5IjoibW9kdWxlIiwidmFsdWUiOiJiYW5rIn1dfSx7InR5cGUiOiJ0cmFuc2ZlciIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InJlY2lwaWVudCIsInZhbHVlIjoib3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZyJ9LHsia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJvcmFpMWc0aDY0eWp0MGZ2enY1djJqOHR5Zm5wZTVrbW5ldGVqdmZnczdnIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjFvcmFpIn1dfV19XRoxCgdtZXNzYWdlEiYKBmFjdGlvbhIcL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBpVCgpjb2luX3NwZW50EjYKB3NwZW5kZXISK29yYWkxZzRoNjR5anQwZnZ6djV2Mmo4dHlmbnBlNWttbmV0ZWp2ZmdzN2cSDwoGYW1vdW50EgUxb3JhaRpZCg1jb2luX3JlY2VpdmVkEjcKCHJlY2VpdmVyEitvcmFpMWc0aDY0eWp0MGZ2enY1djJqOHR5Zm5wZTVrbW5ldGVqdmZnczdnEg8KBmFtb3VudBIFMW9yYWkajAEKCHRyYW5zZmVyEjgKCXJlY2lwaWVudBIrb3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZxI1CgZzZW5kZXISK29yYWkxZzRoNjR5anQwZnZ6djV2Mmo4dHlmbnBlNWttbmV0ZWp2ZmdzN2cSDwoGYW1vdW50EgUxb3JhaRpACgdtZXNzYWdlEjUKBnNlbmRlchIrb3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZxoZCgdtZXNzYWdlEg4KBm1vZHVsZRIEYmFuaw==", + proofOps: null, + height: "17608707", + codespace: "" + } + } +}; + +export const mockStatus = { + jsonrpc: "2.0", + id: 765138518489, + result: { + 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 = { + jsonrpc: "2.0", + id: 961599376562, + result: { + code: 0, + data: "", + log: "[]", + codespace: "", + hash: "2F59A5318ED6350976244EAD25CB78ACFEBC1B9C9A96809269A793EDFB529065" + } +}; + +export const mockTxSearch = { + jsonrpc: "2.0", + id: 295798615435, + result: { + txs: [ + { + hash: "2F59A5318ED6350976244EAD25CB78ACFEBC1B9C9A96809269A793EDFB529065", + height: "17608709", + 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 client = new SimulateCosmWasmClient({ chainId: "Oraichain", diff --git a/yarn.lock b/yarn.lock index bf7c4bf5..38625f5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2714,6 +2714,14 @@ pump "^3.0.0" tar-fs "^2.1.1" +"@ngneat/falso@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@ngneat/falso/-/falso-4.0.0.tgz#991be0d3de61344d8f75faf794967d47e7057ac6" + integrity sha512-VCxeCOaoukr+TFyUDVdk4jMQnZiPBS3lLYf0PKbOcTRZdoNCCkIZRhRYczXdn0PGqY6jmDRiaQoZVMwX+KKt2w== + dependencies: + seedrandom "3.0.5" + uuid "8.3.2" + "@noble/curves@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" @@ -5029,6 +5037,13 @@ resolved "https://registry.yarnpkg.com/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz#7b959a4b9643a1e6a1a5fe49032693cc36773501" integrity sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw== +"@types/follow-redirects@^1.14.1": + version "1.14.4" + resolved "https://registry.yarnpkg.com/@types/follow-redirects/-/follow-redirects-1.14.4.tgz#ca054d72ef574c77949fc5fff278b430fcd508ec" + integrity sha512-GWXfsD0Jc1RWiFmMuMFCpXMzi9L7oPDVwxUnZdg89kDNnqsRfUKXEtUYtA98A6lig1WXH/CYY/fvPW9HuN5fTA== + dependencies: + "@types/node" "*" + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -6821,7 +6836,7 @@ chokidar@3.5.3: optionalDependencies: fsevents "~2.3.2" -chokidar@^3.4.0, chokidar@^3.5.1, chokidar@^3.5.3, chokidar@^3.6.0: +chokidar@^3.4.0, chokidar@^3.5.1, chokidar@^3.5.2, chokidar@^3.5.3, chokidar@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== @@ -7065,7 +7080,7 @@ commander@^10.0.1: resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== -commander@^2.19.0, commander@^2.20.0: +commander@^2.19.0, commander@^2.20.0, commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -8701,7 +8716,7 @@ eventemitter3@^3.1.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== -eventemitter3@^4.0.4: +eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -9137,7 +9152,7 @@ flow-parser@0.*: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.232.0.tgz#db93a660e7017bd366290944c3328ca506ca7d2b" integrity sha512-U8vcKyYdM+Kb0tPzfPJ5JyPMU0uXKwHxp0L6BcEc+wBlbTW9qRhOqV5DeGXclgclVvtqQNGEG8Strj/b6c/IxA== -follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.14.8, follow-redirects@^1.14.9, follow-redirects@^1.15.6: +follow-redirects@^1.0.0, follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.14.8, follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -9997,6 +10012,27 @@ http-proxy-agent@^7.0.0: agent-base "^7.1.0" debug "^4.3.4" +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http-request-mock@^1.8.18: + version "1.8.18" + resolved "https://registry.yarnpkg.com/http-request-mock/-/http-request-mock-1.8.18.tgz#aaee25737e94bb333582b0ea5e93f609fb64140c" + integrity sha512-t8ZjIzmI12ruQKdOiBcUE5EOpjUQtEc28I16D9Z9REdXtYpPlmMIDtf8NmuzbuO5COSej0Ft1EEw/fSnvyvBwQ== + dependencies: + "@ngneat/falso" "^4.0.0" + "@types/follow-redirects" "^1.14.1" + chokidar "^3.5.2" + commander "^2.20.3" + follow-redirects "^1.15.0" + http-proxy "^1.18.1" + http-status-codes@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.3.0.tgz#987fefb28c69f92a43aecc77feec2866349a8bfc" @@ -14359,6 +14395,11 @@ require-from-string@^2.0.0, require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resize-observer-polyfill@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" @@ -14628,6 +14669,11 @@ secp256k1@^4.0.1, secp256k1@^4.0.3: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" +seedrandom@3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" + integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== + "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" @@ -16186,7 +16232,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^8.3.2: +uuid@8.3.2, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== From 896728356ebfe1c8ed5d984200d0eff45e1be102 Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Wed, 3 Apr 2024 18:52:07 -0700 Subject: [PATCH 10/22] wip: add jest mock for test checkBalanceChannelIbc --- packages/universal-swap/src/helper.ts | 2 + .../universal-swap/tests/jest-mock.spec.ts | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 packages/universal-swap/tests/jest-mock.spec.ts diff --git a/packages/universal-swap/src/helper.ts b/packages/universal-swap/src/helper.ts index 0f3fe275..56a48fc4 100644 --- a/packages/universal-swap/src/helper.ts +++ b/packages/universal-swap/src/helper.ts @@ -636,8 +636,10 @@ export class UniversalSwapHelper { channelId: ibcInfo.channel, denom: pairKey }); + console.log("balance: ", channelBalance); balance = channelBalance; } catch (error) { + console.log("error: ", error); // do nothing because the given channel and key doesnt exist // console.log("error querying channel with key: ", error); return; diff --git a/packages/universal-swap/tests/jest-mock.spec.ts b/packages/universal-swap/tests/jest-mock.spec.ts new file mode 100644 index 00000000..3365b4b6 --- /dev/null +++ b/packages/universal-swap/tests/jest-mock.spec.ts @@ -0,0 +1,69 @@ +import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { toBase64, toUtf8, fromHex, fromUtf8 } from "@cosmjs/encoding"; +import { TokenItemType, flattenTokens, toAmount, IBC_WASM_CONTRACT, ORAI } from "@oraichain/oraidex-common"; +import { QuerySmartContractStateResponse, QuerySmartContractStateRequest } from "cosmjs-types/cosmwasm/wasm/v1/query"; +import { UniversalSwapHelper } from "../src/helper"; +import { mockStatus } from "./test-common"; +// import {Ics20} from '@oraichain/common-contracts-sdk/build' + +jest.mock("@oraichain/common-contracts-sdk/build", () => { + const originalModule = jest.requireActual("@oraichain/common-contracts-sdk/build"); + class CwIcs20LatestQueryClient { + constructor() {} + channelWithKey = jest + .fn() + .mockResolvedValue({ balance: { native: { denom: ORAI, amount: toAmount(1).toString() } } }); + pairMapping = jest.fn(); + } + return { + ...originalModule, + __esModule: true, + default: CwIcs20LatestQueryClient + }; +}); + +describe("test-jest-mock", () => { + const channel = "channel-29"; + const simulateAmount = "100"; + // afterEach(() => { + // jest.resetAllMocks(); + // }); + 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-jest-mock-%", + async (fromToken, toToken, amount, channel, willThrow) => { + const client = await CosmWasmClient.connect("https://rpc.orai.io"); + try { + await UniversalSwapHelper.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); + } + } + ); +}); From 354c0521ac95164df0644b08b64144b97f1eb9f7 Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Mon, 8 Apr 2024 16:28:30 -0700 Subject: [PATCH 11/22] chore: add mockttp stop --- packages/universal-swap/tests/http-mock.spec.ts | 1 + packages/universal-swap/tests/index.spec.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/universal-swap/tests/http-mock.spec.ts b/packages/universal-swap/tests/http-mock.spec.ts index f299e07d..102334b4 100644 --- a/packages/universal-swap/tests/http-mock.spec.ts +++ b/packages/universal-swap/tests/http-mock.spec.ts @@ -22,5 +22,6 @@ describe("test-nock", () => { console.log({ result }); expect(result.transactionHash).toEqual("2F59A5318ED6350976244EAD25CB78ACFEBC1B9C9A96809269A793EDFB529065"); expect(result.height).toEqual(1); + await server.stop(); }); }); diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index d647b9db..0acadacf 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -628,6 +628,8 @@ describe("test universal swap handler functions", () => { expect(willThrow).toEqual(false); } catch (error) { expect(willThrow).toEqual(true); + } finally { + await mockServer.stop(); } }, 50000 From 1d39d68ec2044a321ceeabd39845b5c47f8dd6d8 Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Mon, 8 Apr 2024 16:35:20 -0700 Subject: [PATCH 12/22] chore: add test-mock-rpc-getAccount --- ...http-mock.spec.ts => jsonrpc-mock.spec.ts} | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) rename packages/universal-swap/tests/{http-mock.spec.ts => jsonrpc-mock.spec.ts} (58%) diff --git a/packages/universal-swap/tests/http-mock.spec.ts b/packages/universal-swap/tests/jsonrpc-mock.spec.ts similarity index 58% rename from packages/universal-swap/tests/http-mock.spec.ts rename to packages/universal-swap/tests/jsonrpc-mock.spec.ts index 102334b4..d733d473 100644 --- a/packages/universal-swap/tests/http-mock.spec.ts +++ b/packages/universal-swap/tests/jsonrpc-mock.spec.ts @@ -2,12 +2,18 @@ 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 server = await mockJsonRpcServer(); - const wallet = await DirectSecp256k1HdWallet.fromMnemonic(process.env.MNEMONIC as any, { prefix: ORAI }); const accounts = await wallet.getAccounts(); const stargate = await SigningStargateClient.connectWithSigner(server.url, wallet, { @@ -19,9 +25,17 @@ describe("test-nock", () => { [{ amount: "1", denom: ORAI }], "auto" ); - console.log({ result }); expect(result.transactionHash).toEqual("2F59A5318ED6350976244EAD25CB78ACFEBC1B9C9A96809269A793EDFB529065"); expect(result.height).toEqual(1); - await server.stop(); + }); + + it("test-mock-rpc-getAccount", async () => { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(process.env.MNEMONIC as any, { 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"); }); }); From 71714610b3918c58e1c4c81218b33d463b8fb2ff Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Mon, 8 Apr 2024 16:37:16 -0700 Subject: [PATCH 13/22] chore: refactor code --- packages/universal-swap/tests/index.spec.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index 0acadacf..667a9d10 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -47,7 +47,7 @@ import { readFileSync } from "fs"; import Long from "long"; import TronWeb from "tronweb"; import { UniversalSwapHandler } from "../src/handler"; -import { UniversalSwapHelper, checkFeeRelayer, checkFeeRelayerNotOrai } from "../src/helper"; +import { UniversalSwapHelper, checkBalanceChannelIbc, checkBalanceIBCOraichain, checkFeeRelayer, checkFeeRelayerNotOrai, getBalanceIBCOraichain, getIbcInfo, handleSimulateSwap, simulateSwap } from "../src/helper"; import { UniversalSwapConfig, UniversalSwapData, UniversalSwapType } from "../src/types"; import { deployIcs20Token, deployToken, testSenderAddress } from "./test-common"; @@ -324,7 +324,7 @@ describe("test universal swap handler functions", () => { contract: IBC_WASM_CONTRACT, amount: simulateAmount, msg: toBinary({ - local_channel_id: UniversalSwapHelper.getIbcInfo("Oraichain", "noble-1").channel, + local_channel_id: getIbcInfo("Oraichain", "noble-1").channel, remote_address: "noble1234", remote_denom: "uusdc", timeout: IBC_TRANSFER_TIMEOUT, @@ -554,7 +554,7 @@ describe("test universal swap handler functions", () => { ] ])("test-universal-swap-checkBalanceChannelIbc-%", async (fromToken, toToken, amount, channel, willThrow) => { try { - await UniversalSwapHelper.checkBalanceChannelIbc( + await checkBalanceChannelIbc( { source: oraiPort, channel: channel, @@ -613,7 +613,7 @@ describe("test universal swap handler functions", () => { .thenSendJsonRpcResult(buildCosmWasmAbciQueryResponse({ pair_mapping: { remote_decimals: 6 } })); try { const client = await CosmWasmClient.connect(mockServer.url); - await UniversalSwapHelper.checkBalanceChannelIbc( + await checkBalanceChannelIbc( { source: "wasm." + IBC_WASM_CONTRACT, channel: channel, @@ -643,7 +643,7 @@ describe("test universal swap handler functions", () => { if (mockToken.contractAddress) { if (mockToken.coinGeckoId === "airight") mockToken.contractAddress = airiToken.contractAddress; } - const { balance } = await UniversalSwapHelper.getBalanceIBCOraichain( + const { balance } = await getBalanceIBCOraichain( mockToken, ics20Contract.client, ics20Contract.contractAddress @@ -680,7 +680,7 @@ describe("test universal swap handler functions", () => { jest .spyOn(UniversalSwapHelper, "getBalanceIBCOraichain") .mockReturnValue(new Promise((resolve) => resolve({ balance: +toAmount }))); - UniversalSwapHelper.checkBalanceIBCOraichain( + checkBalanceIBCOraichain( from, to, fromAmount, @@ -873,7 +873,7 @@ describe("test universal swap handler functions", () => { ...universalSwapData, originalToToken: flattenTokens.find((t) => t.coinGeckoId === toCoingeckoId)! }); - const ibcInfo = UniversalSwapHelper.getIbcInfo("Oraichain", "oraibridge-subnet-2"); + const ibcInfo = getIbcInfo("Oraichain", "oraibridge-subnet-2"); const toAddress = "foobar"; const ibcMemo = ""; const msg = universalSwap.generateMsgsIbcWasm(ibcInfo, toAddress, "john doe", ibcMemo)!; @@ -1016,7 +1016,7 @@ describe("test universal swap handler functions", () => { jest.spyOn(routerClient, "simulateSwapOperations").mockReturnValue(new Promise((resolve) => resolve({ amount }))); const [fromInfo, toInfo] = [toTokenInfo(fromToken!), toTokenInfo(toToken!)]; const query = { fromInfo, toInfo, amount, routerClient }; - const simulateData = await UniversalSwapHelper.simulateSwap(query); + const simulateData = await simulateSwap(query); expect(simulateData.amount).toEqual(expectedSimulateData); } ); @@ -1035,7 +1035,7 @@ describe("test universal swap handler functions", () => { const isEvmSwappableSpy = jest.spyOn(UniversalSwapHelper, "isEvmSwappable"); isSupportedNoPoolSwapEvmSpy.mockReturnValue(isSupportedNoPoolSwapEvmRes); isEvmSwappableSpy.mockReturnValue(isEvmSwappableRes); - const simulateData = await UniversalSwapHelper.handleSimulateSwap({ + const simulateData = await handleSimulateSwap({ originalFromInfo: oraichainTokens[0], originalToInfo: oraichainTokens[0], originalAmount: 0, From dba53bf36f471c8d9d6c6fdeffff7a15a6a80d48 Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Mon, 8 Apr 2024 16:43:24 -0700 Subject: [PATCH 14/22] fix: use generate mnemonic to pass jsonrpc mock test cases cicd --- packages/universal-swap/tests/jsonrpc-mock.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/universal-swap/tests/jsonrpc-mock.spec.ts b/packages/universal-swap/tests/jsonrpc-mock.spec.ts index d733d473..3aa45600 100644 --- a/packages/universal-swap/tests/jsonrpc-mock.spec.ts +++ b/packages/universal-swap/tests/jsonrpc-mock.spec.ts @@ -14,7 +14,7 @@ describe("test-nock", () => { await server.stop(); }); it("test-mock-rpc-post", async () => { - const wallet = await DirectSecp256k1HdWallet.fromMnemonic(process.env.MNEMONIC as any, { prefix: ORAI }); + 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") @@ -30,7 +30,7 @@ describe("test-nock", () => { }); it("test-mock-rpc-getAccount", async () => { - const wallet = await DirectSecp256k1HdWallet.fromMnemonic(process.env.MNEMONIC as any, { prefix: ORAI }); + 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") From 37da7683e7cf32640335cd3ea96e02122e659a10 Mon Sep 17 00:00:00 2001 From: ducphamle2 Date: Thu, 25 Apr 2024 18:13:48 -0700 Subject: [PATCH 15/22] feat: add mock for broadcast async --- packages/oraidex-common/src/testing/jsonrpc-mock-common.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/oraidex-common/src/testing/jsonrpc-mock-common.ts b/packages/oraidex-common/src/testing/jsonrpc-mock-common.ts index 21adaca9..3d41bbc6 100644 --- a/packages/oraidex-common/src/testing/jsonrpc-mock-common.ts +++ b/packages/oraidex-common/src/testing/jsonrpc-mock-common.ts @@ -121,6 +121,12 @@ export const mockJsonRpcServer = async () => { }) .thenSendJsonRpcResult(mockResponse); + await mockServer + .forJsonRpcRequest({ + method: "broadcast_tx_async" + }) + .thenSendJsonRpcResult(mockResponse); + await mockServer .forJsonRpcRequest({ method: "tx_search" From 44f4324fa5d748ebc9ddf832d49140b152efcc15 Mon Sep 17 00:00:00 2001 From: Hau Nguyen Van Date: Thu, 2 May 2024 15:42:20 +0700 Subject: [PATCH 16/22] add unit test universal swap --- packages/universal-swap/tests/index.spec.ts | 108 ++++++++++++++++++-- 1 file changed, 102 insertions(+), 6 deletions(-) diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index 0070ed4d..d23a51c1 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -25,6 +25,7 @@ import { flattenTokens, matchCosmWasmQueryRequest, mockJsonRpcServer, + network, oraichain2osmosis, oraichainTokens, toAmount, @@ -47,7 +48,17 @@ import { readFileSync } from "fs"; import Long from "long"; import TronWeb from "tronweb"; import { UniversalSwapHandler } from "../src/handler"; -import { UniversalSwapHelper, checkBalanceChannelIbc, checkBalanceIBCOraichain, checkFeeRelayer, checkFeeRelayerNotOrai, getBalanceIBCOraichain, getIbcInfo, handleSimulateSwap, simulateSwap } from "../src/helper"; +import { + UniversalSwapHelper, + checkBalanceChannelIbc, + checkBalanceIBCOraichain, + checkFeeRelayer, + checkFeeRelayerNotOrai, + getBalanceIBCOraichain, + getIbcInfo, + handleSimulateSwap, + simulateSwap +} from "../src/helper"; import { UniversalSwapConfig, UniversalSwapData, UniversalSwapType } from "../src/types"; import { deployIcs20Token, deployToken, testSenderAddress } from "./test-common"; @@ -662,11 +673,7 @@ describe("test universal swap handler functions", () => { if (mockToken.contractAddress) { if (mockToken.coinGeckoId === "airight") mockToken.contractAddress = airiToken.contractAddress; } - const { balance } = await getBalanceIBCOraichain( - mockToken, - ics20Contract.client, - ics20Contract.contractAddress - ); + const { balance } = await getBalanceIBCOraichain(mockToken, ics20Contract.client, ics20Contract.contractAddress); expect(balance).toEqual(expectedBalance); }); @@ -1104,6 +1111,95 @@ 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<[string, string, string, string, string]>([ + ["tether", "cosmos", "Oraichain", "cosmoshub-4", "oraichain-to-cosmos"], + ["tether", "tether", "Oraichain", "0x38", "oraichain-to-evm"] + ])( + "test-processUniversalSwap-swapAndTransferToOtherNetworks()-for-%s", + async (fromCoingecko, toCoingecko, fromChainId, toChainId, swapRoute) => { + const fromToken = flattenTokens.find( + (item) => item.coinGeckoId === fromCoingecko && item.chainId === fromChainId + )!; + const toToken = flattenTokens.find((item) => item.coinGeckoId === toCoingecko && item.chainId === toChainId)!; + 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, + fromAmount: 100, + 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: "Oraichain", rpc: networks.rpc }, + { gasPrice: expect.any(Object) } + ); + expect(signAndBroadcastMock).toHaveBeenCalledWith(sender.cosmos, ["msg1", "msg2"], "auto"); + } + ); + // it("test-swap()", async () => { // const universalSwap = new FakeUniversalSwapHandler({ // ...universalSwapData From d550dfca865d1297fd264fd23d78bf3604c0d3c6 Mon Sep 17 00:00:00 2001 From: Hau Nguyen Van Date: Mon, 6 May 2024 09:23:05 +0700 Subject: [PATCH 17/22] add test mock func swap in oraichain --- .../src/testing/jsonrpc-mock-common.ts | 10 +-- packages/universal-swap/src/handler.ts | 74 ++++++++----------- packages/universal-swap/tests/index.spec.ts | 45 +++++++++++ 3 files changed, 81 insertions(+), 48 deletions(-) diff --git a/packages/oraidex-common/src/testing/jsonrpc-mock-common.ts b/packages/oraidex-common/src/testing/jsonrpc-mock-common.ts index 3d41bbc6..f8bcf8f0 100644 --- a/packages/oraidex-common/src/testing/jsonrpc-mock-common.ts +++ b/packages/oraidex-common/src/testing/jsonrpc-mock-common.ts @@ -24,15 +24,15 @@ export const buildCosmWasmAbciQueryResponse = (expectedResult: any) => { ); }; -const mockAccountInfo = buildAbciQueryResponse( +export const mockAccountInfo = buildAbciQueryResponse( "Cp8BCiAvY29zbW9zLmF1dGgudjFiZXRhMS5CYXNlQWNjb3VudBJ7CitvcmFpMWc0aDY0eWp0MGZ2enY1djJqOHR5Zm5wZTVrbW5ldGVqdmZnczdnEkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA/7NDxzUr4iFC8PmBZllh5P6RdDtLgnvL32OVolC+2tGGKwQIN0R" ); -const mockSimulate = buildAbciQueryResponse( +export const mockSimulate = buildAbciQueryResponse( "CgQQp6kDEroJCiAKHgocL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBLEBVt7ImV2ZW50cyI6W3sidHlwZSI6ImNvaW5fcmVjZWl2ZWQiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJyZWNlaXZlciIsInZhbHVlIjoib3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZyJ9LHsia2V5IjoiYW1vdW50IiwidmFsdWUiOiIxb3JhaSJ9XX0seyJ0eXBlIjoiY29pbl9zcGVudCIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InNwZW5kZXIiLCJ2YWx1ZSI6Im9yYWkxZzRoNjR5anQwZnZ6djV2Mmo4dHlmbnBlNWttbmV0ZWp2ZmdzN2cifSx7ImtleSI6ImFtb3VudCIsInZhbHVlIjoiMW9yYWkifV19LHsidHlwZSI6Im1lc3NhZ2UiLCJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJhY3Rpb24iLCJ2YWx1ZSI6Ii9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmQifSx7ImtleSI6InNlbmRlciIsInZhbHVlIjoib3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZyJ9LHsia2V5IjoibW9kdWxlIiwidmFsdWUiOiJiYW5rIn1dfSx7InR5cGUiOiJ0cmFuc2ZlciIsImF0dHJpYnV0ZXMiOlt7ImtleSI6InJlY2lwaWVudCIsInZhbHVlIjoib3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZyJ9LHsia2V5Ijoic2VuZGVyIiwidmFsdWUiOiJvcmFpMWc0aDY0eWp0MGZ2enY1djJqOHR5Zm5wZTVrbW5ldGVqdmZnczdnIn0seyJrZXkiOiJhbW91bnQiLCJ2YWx1ZSI6IjFvcmFpIn1dfV19XRoxCgdtZXNzYWdlEiYKBmFjdGlvbhIcL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZBpVCgpjb2luX3NwZW50EjYKB3NwZW5kZXISK29yYWkxZzRoNjR5anQwZnZ6djV2Mmo4dHlmbnBlNWttbmV0ZWp2ZmdzN2cSDwoGYW1vdW50EgUxb3JhaRpZCg1jb2luX3JlY2VpdmVkEjcKCHJlY2VpdmVyEitvcmFpMWc0aDY0eWp0MGZ2enY1djJqOHR5Zm5wZTVrbW5ldGVqdmZnczdnEg8KBmFtb3VudBIFMW9yYWkajAEKCHRyYW5zZmVyEjgKCXJlY2lwaWVudBIrb3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZxI1CgZzZW5kZXISK29yYWkxZzRoNjR5anQwZnZ6djV2Mmo4dHlmbnBlNWttbmV0ZWp2ZmdzN2cSDwoGYW1vdW50EgUxb3JhaRpACgdtZXNzYWdlEjUKBnNlbmRlchIrb3JhaTFnNGg2NHlqdDBmdnp2NXYyajh0eWZucGU1a21uZXRlanZmZ3M3ZxoZCgdtZXNzYWdlEg4KBm1vZHVsZRIEYmFuaw==" ); -const mockStatus = { +export const mockStatus = { node_info: { protocol_version: { p2p: "8", block: "11", app: "0" }, id: "6ffa64f7e7d78421d43eafa800fb11df3ce9c176", @@ -61,7 +61,7 @@ const mockStatus = { } }; -const mockResponse = { +export const mockResponse = { code: 0, data: "", log: "[]", @@ -69,7 +69,7 @@ const mockResponse = { hash: "2F59A5318ED6350976244EAD25CB78ACFEBC1B9C9A96809269A793EDFB529065" }; -const mockTxSearch = { +export const mockTxSearch = { txs: [ { hash: "2F59A5318ED6350976244EAD25CB78ACFEBC1B9C9A96809269A793EDFB529065", diff --git a/packages/universal-swap/src/handler.ts b/packages/universal-swap/src/handler.ts index 8805a89e..16682314 100644 --- a/packages/universal-swap/src/handler.ts +++ b/packages/universal-swap/src/handler.ts @@ -1,59 +1,48 @@ -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, + IUniswapV2Router02__factory, + 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 +281,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( diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index d23a51c1..9464bec2 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -25,6 +25,7 @@ import { flattenTokens, matchCosmWasmQueryRequest, mockJsonRpcServer, + mockResponse, network, oraichain2osmosis, oraichainTokens, @@ -1200,6 +1201,50 @@ describe("test universal swap handler functions", () => { } ); + 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("test-swap()", async () => { // const universalSwap = new FakeUniversalSwapHandler({ // ...universalSwapData From dff9f4467ce8a0eccabb94c972251f43d691bb5c Mon Sep 17 00:00:00 2001 From: Hau Nguyen Van Date: Mon, 6 May 2024 11:06:05 +0700 Subject: [PATCH 18/22] add test mock func swapAndTransferToOtherNetworks --- packages/universal-swap/tests/index.spec.ts | 66 +++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index 9464bec2..a835391d 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -1245,6 +1245,72 @@ describe("test universal swap handler functions", () => { 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("test-swap()", async () => { // const universalSwap = new FakeUniversalSwapHandler({ // ...universalSwapData From 46818bac8c2281c1a7b7f445c518ea27c5d0238d Mon Sep 17 00:00:00 2001 From: Hau Nguyen Van Date: Mon, 6 May 2024 15:43:42 +0700 Subject: [PATCH 19/22] add testcase swapCosmosToOtherNetwork --- packages/universal-swap/src/handler.ts | 2 - packages/universal-swap/tests/index.spec.ts | 219 +++++++++++++++----- 2 files changed, 168 insertions(+), 53 deletions(-) diff --git a/packages/universal-swap/src/handler.ts b/packages/universal-swap/src/handler.ts index 16682314..fd8b3934 100644 --- a/packages/universal-swap/src/handler.ts +++ b/packages/universal-swap/src/handler.ts @@ -441,7 +441,6 @@ export class UniversalSwapHandler { ); } - // TODO: write test cases async swapAndTransferToOtherNetworks(universalSwapType: UniversalSwapType) { let encodedObjects: EncodeObject[]; const { originalToToken, originalFromToken, simulateAmount, sender } = this.swapData; @@ -585,7 +584,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/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index a835391d..dd1014c3 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -77,6 +77,7 @@ describe("test universal swap handler functions", () => { const bobAddress = "orai1ur2vsjrjarygawpdwtqteaazfchvw4fg6uql76"; const oraiAddress = "orai12zyu8w93h0q2lcnt50g3fn0w3yqnhy4fvawaqz"; const cosmosSenderAddress = bech32.encode("cosmos", bech32.decode(oraiAddress).words); + const nobleSenderAddress = bech32.encode("noble", bech32.decode(oraiAddress).words); let ics20Contract: CwIcs20LatestClient; let factoryContract: OraiswapFactoryClient; @@ -1145,61 +1146,112 @@ describe("test universal swap handler functions", () => { expect(executeMultipleMock).toHaveBeenCalledWith(sender.cosmos, ["msg1", "msg2"], "auto"); }); - it.each<[string, string, string, string, string]>([ - ["tether", "cosmos", "Oraichain", "cosmoshub-4", "oraichain-to-cosmos"], - ["tether", "tether", "Oraichain", "0x38", "oraichain-to-evm"] - ])( - "test-processUniversalSwap-swapAndTransferToOtherNetworks()-for-%s", - async (fromCoingecko, toCoingecko, fromChainId, toChainId, swapRoute) => { - const fromToken = flattenTokens.find( - (item) => item.coinGeckoId === fromCoingecko && item.chainId === fromChainId - )!; - const toToken = flattenTokens.find((item) => item.coinGeckoId === toCoingecko && item.chainId === toChainId)!; - 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"; - } - }; + 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, - fromAmount: 100, - sender - }, - { - cosmosWallet: cosmosWalletMock as any - } - ); + const sender = { + cosmos: "orai1234", + evm: "0x1234" + }; + const universalSwap = new FakeUniversalSwapHandler( + { + ...universalSwapData, + originalFromToken: fromToken, + originalToToken: toToken, + fromAmount: 100, + 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; + 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); + await universalSwap.swapAndTransferToOtherNetworks(swapRoute as UniversalSwapType); - expect(combineSwapMsgOraichain).toHaveBeenCalled(); - expect(getCosmWasmClientMock).toHaveBeenCalledWith( - { chainId: "Oraichain", rpc: networks.rpc }, - { gasPrice: expect.any(Object) } - ); - expect(signAndBroadcastMock).toHaveBeenCalledWith(sender.cosmos, ["msg1", "msg2"], "auto"); - } - ); + 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]>([ [ @@ -1311,6 +1363,71 @@ describe("test universal swap handler functions", () => { 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()-for-%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("test-swap()", async () => { // const universalSwap = new FakeUniversalSwapHandler({ // ...universalSwapData From b035545a13275726da9a13832a230567125c6191 Mon Sep 17 00:00:00 2001 From: Hau Nguyen Van Date: Mon, 6 May 2024 17:47:04 +0700 Subject: [PATCH 20/22] unit test transferToGravity --- packages/universal-swap/src/handler.ts | 9 ++- packages/universal-swap/tests/index.spec.ts | 74 ++++++++++++++++++++- 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/packages/universal-swap/src/handler.ts b/packages/universal-swap/src/handler.ts index fd8b3934..c131d2fe 100644 --- a/packages/universal-swap/src/handler.ts +++ b/packages/universal-swap/src/handler.ts @@ -314,7 +314,7 @@ export class UniversalSwapHandler { 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 gravityContract = this.connectBridgeFactory(gravityContractAddr, signer); const routerV2Addr = await gravityContract.swapRouter(); const minimumReceive = BigInt(calculateMinReceive(simulatePrice, finalFromAmount, slippage, fromToken.decimals)); let result: ethers.ContractTransaction; @@ -363,7 +363,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; @@ -391,13 +390,17 @@ 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 }; } } + connectBridgeFactory = (gravityContractAddr, signer) => { + return Bridge__factory.connect(gravityContractAddr, signer); + }; + // TODO: write test cases transferEvmToIBC = async (swapRoute: string): Promise => { const from = this.swapData.originalFromToken; diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index dd1014c3..98daae4a 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -14,6 +14,7 @@ import { IBC_WASM_CONTRACT, IBC_WASM_CONTRACT_TEST, NetworkChainId, + Networks, ORAI_BRIDGE_EVM_DENOM_PREFIX, ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX, OSMOSIS_ORAICHAIN_DENOM, @@ -76,6 +77,8 @@ 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); @@ -1182,7 +1185,6 @@ describe("test universal swap handler functions", () => { ...universalSwapData, originalFromToken: fromToken, originalToToken: toToken, - fromAmount: 100, sender }, { @@ -1377,7 +1379,7 @@ describe("test universal swap handler functions", () => { false ] ])( - "test-processUniversalSwap-swapCosmosToOtherNetwork()-for-%s", + "test-processUniversalSwap-swapCosmosToOtherNetwork()-with-mock-%s", async (fromToken, toToken, destinationReceiver, willThrow) => { const mockServer = await mockJsonRpcServer(); const spy = jest.spyOn(UniversalSwapHelper, "getIbcInfo"); @@ -1428,6 +1430,74 @@ describe("test universal swap handler functions", () => { } ); + 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("test-swap()", async () => { // const universalSwap = new FakeUniversalSwapHandler({ // ...universalSwapData From d9f92521ec127b82cd21b1c4c3ec607ab7cbb612 Mon Sep 17 00:00:00 2001 From: Hau Nguyen Van Date: Mon, 6 May 2024 23:32:59 +0700 Subject: [PATCH 21/22] add testcase func swap evm --- packages/universal-swap/src/handler.ts | 2 - packages/universal-swap/tests/index.spec.ts | 115 ++++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/packages/universal-swap/src/handler.ts b/packages/universal-swap/src/handler.ts index c131d2fe..90e2fb96 100644 --- a/packages/universal-swap/src/handler.ts +++ b/packages/universal-swap/src/handler.ts @@ -401,7 +401,6 @@ export class UniversalSwapHandler { return Bridge__factory.connect(gravityContractAddr, signer); }; - // TODO: write test cases transferEvmToIBC = async (swapRoute: string): Promise => { const from = this.swapData.originalFromToken; const fromAmount = this.swapData.fromAmount; @@ -498,7 +497,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 { diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index 98daae4a..588b0721 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -1498,6 +1498,121 @@ describe("test universal swap handler functions", () => { } }); + 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("test-swap()", async () => { // const universalSwap = new FakeUniversalSwapHandler({ // ...universalSwapData From ef0df9f873b7aac819f62865a62a1d03719ab8fe Mon Sep 17 00:00:00 2001 From: Hau Nguyen Van Date: Wed, 8 May 2024 15:06:25 +0700 Subject: [PATCH 22/22] add test case handler universal swap --- package.json | 1 + packages/oraidex-common/src/wallet.ts | 17 +- packages/universal-swap/src/handler.ts | 20 +- packages/universal-swap/src/helper.ts | 116 +++---- packages/universal-swap/tests/index.spec.ts | 333 +++++++++++++++++++- 5 files changed, 409 insertions(+), 78 deletions(-) diff --git a/package.json b/package.json index 34ae4b0b..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", 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 90e2fb96..45ad29e3 100644 --- a/packages/universal-swap/src/handler.ts +++ b/packages/universal-swap/src/handler.ts @@ -11,7 +11,6 @@ import { IBCInfo, IBC_WASM_CONTRACT, IBC_WASM_CONTRACT_TEST, - IUniswapV2Router02__factory, NetworkChainId, ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX, TokenItemType, @@ -291,7 +290,6 @@ export class UniversalSwapHandler { return result; } - // TODO: write test cases public async evmSwap(data: { fromToken: TokenItemType; toTokenContractAddr: string; @@ -312,8 +310,8 @@ 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 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)); @@ -332,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( @@ -352,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 @@ -397,10 +395,14 @@ export class UniversalSwapHandler { } } - connectBridgeFactory = (gravityContractAddr, signer) => { + 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; diff --git a/packages/universal-swap/src/helper.ts b/packages/universal-swap/src/helper.ts index 0bf282b7..f501c5af 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, diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index 588b0721..54e1881e 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -13,15 +13,24 @@ import { 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, @@ -43,7 +52,8 @@ import { OraiswapOracleClient, OraiswapRouterClient, OraiswapRouterQueryClient, - OraiswapTokenClient + OraiswapTokenClient, + SwapOperation } from "@oraichain/oraidex-contracts-sdk"; import bech32 from "bech32"; import { readFileSync } from "fs"; @@ -61,7 +71,7 @@ import { handleSimulateSwap, simulateSwap } from "../src/helper"; -import { UniversalSwapConfig, UniversalSwapData, UniversalSwapType } from "../src/types"; +import { SwapDirection, UniversalSwapConfig, UniversalSwapData, UniversalSwapType } from "../src/types"; import { deployIcs20Token, deployToken, testSenderAddress } from "./test-common"; describe("test universal swap handler functions", () => { @@ -434,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], @@ -726,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"], @@ -1070,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"], @@ -1613,6 +1831,117 @@ describe("test universal swap handler functions", () => { } }); + 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