Skip to content

Commit

Permalink
Merge pull request #186 from oraichain/fix/check-balance-ibc
Browse files Browse the repository at this point in the history
[Review] - Fix Check Balance IBC Universal-Swap
  • Loading branch information
haunv3 authored Mar 1, 2024
2 parents 02052e9 + 9aad9d9 commit a570f5f
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 53 deletions.
2 changes: 1 addition & 1 deletion packages/universal-swap/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oraichain/oraidex-universal-swap",
"version": "1.0.54",
"version": "1.0.55",
"main": "build/index.js",
"files": [
"build/"
Expand Down
55 changes: 31 additions & 24 deletions packages/universal-swap/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ export class UniversalSwapHandler {
// TODO: write test cases
async swapAndTransferToOtherNetworks(universalSwapType: UniversalSwapType) {
let encodedObjects: EncodeObject[];
const { originalToToken, simulateAmount, sender } = this.swapData;
const { originalToToken, originalFromToken, simulateAmount, sender } = this.swapData;
if (!this.config.cosmosWallet)
throw generateError("Cannot transfer and swap if the cosmos wallet is not initialized");
// we get cosmwasm client on Oraichain because this is checking channel balance on Oraichain
Expand Down Expand Up @@ -398,8 +398,14 @@ export class UniversalSwapHandler {
throw generateError(`Universal swap type ${universalSwapType} is wrong. Should not call this function!`);
}
const ibcInfo = this.getIbcInfo("Oraichain", originalToToken.chainId);
const ics20Client = new CwIcs20LatestQueryClient(client, this.getCwIcs20ContractAddr());
await checkBalanceChannelIbc(ibcInfo, originalToToken, simulateAmount, ics20Client);
await checkBalanceChannelIbc(
ibcInfo,
originalFromToken,
originalToToken,
simulateAmount,
client,
this.getCwIcs20ContractAddr()
);

// handle sign and broadcast transactions
return client.signAndBroadcast(sender.cosmos, encodedObjects, "auto");
Expand Down Expand Up @@ -427,27 +433,6 @@ export class UniversalSwapHandler {
{}
);

await checkBalanceIBCOraichain(
originalToToken,
originalFromToken,
fromAmount,
simulateAmount,
client,
this.getCwIcs20ContractAddr()
);

const routerClient = new OraiswapRouterQueryClient(client, network.router);
const isSufficient = await checkFeeRelayer({
originalFromToken,
fromAmount,
relayerFee,
routerClient
});
if (!isSufficient)
throw generateError(
`Your swap amount ${fromAmount} cannot cover the fees for this transaction. Please try again with a higher swap amount`
);

// normal case, we will transfer evm to ibc like normal when two tokens can not be swapped on evm
// first case: BNB (bsc) <-> USDT (bsc), then swappable
// 2nd case: BNB (bsc) -> USDT (oraichain), then find USDT on bsc. We have that and also have route => swappable
Expand Down Expand Up @@ -485,6 +470,28 @@ export class UniversalSwapHandler {
if (isEvmSwappable(swappableData) && isSupportedNoPoolSwapEvm(originalFromToken.coinGeckoId)) {
return this.evmSwap(evmSwapData);
}

await checkBalanceIBCOraichain(
originalToToken,
originalFromToken,
fromAmount,
simulateAmount,
client,
this.getCwIcs20ContractAddr()
);

const routerClient = new OraiswapRouterQueryClient(client, network.router);
const isSufficient = await checkFeeRelayer({
originalFromToken,
fromAmount,
relayerFee,
routerClient
});
if (!isSufficient)
throw generateError(
`Your swap amount ${fromAmount} cannot cover the fees for this transaction. Please try again with a higher swap amount`
);

return this.transferEvmToIBC(swapRoute);
}

Expand Down
58 changes: 36 additions & 22 deletions packages/universal-swap/src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ import {
isInPairList,
BigDecimal,
NEUTARO_INFO,
USDC_INFO
USDC_INFO,
network,
ORAIX_ETH_CONTRACT
} from "@oraichain/oraidex-common";
import { OraiBridgeRouteData, SimulateResponse, SwapDirection, SwapRoute, UniversalSwapConfig } from "./types";
import {
AssetInfo,
OraiswapRouterClient,
OraiswapRouterQueryClient,
OraiswapRouterReadOnlyInterface,
OraiswapTokenQueryClient
} from "@oraichain/oraidex-contracts-sdk";
Expand Down Expand Up @@ -98,7 +100,15 @@ export const swapEvmRoutes: {
[`${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]
[`${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]
}
};

Expand Down Expand Up @@ -389,17 +399,10 @@ export const simulateSwap = async (query: {
console.log("operations: ", operations);
try {
let finalAmount = amount;
let isSimulatingRatio = false;
// hard-code for tron because the WTRX/USDT pool is having a simulation problem (returning zero / error when simulating too small value of WTRX)
if (fromInfo.coinGeckoId === "tron" && amount === toAmount(1, fromInfo.decimals).toString()) {
finalAmount = toAmount(10, fromInfo.decimals).toString();
isSimulatingRatio = true;
}
const data = await routerClient.simulateSwapOperations({
offerAmount: finalAmount,
operations
});
if (!isSimulatingRatio) return data;
return { amount: data.amount.substring(0, data.amount.length - 1) };
} catch (error) {
throw new Error(`Error when trying to simulate swap using router v2: ${JSON.stringify(error)}`);
Expand Down Expand Up @@ -549,15 +552,18 @@ export const checkFeeRelayerNotOrai = async (query: {
// verify balance
export const checkBalanceChannelIbc = async (
ibcInfo: IBCInfo,
fromToken: TokenItemType,
toToken: TokenItemType,
toSimulateAmount: string,
ics20Client: CwIcs20LatestReadOnlyInterface
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;
try {
const { balance: channelBalance } = await ics20Client.channelWithKey({
Expand All @@ -574,18 +580,27 @@ export const checkBalanceChannelIbc = async (
if ("native" in balance) {
const pairMapping = await ics20Client.pairMapping({ key: pairKey });
const trueBalance = toDisplay(balance.native.amount, pairMapping.pair_mapping.remote_decimals);
const _toAmount = toDisplay(toSimulateAmount, toToken.decimals);
if (trueBalance < _toAmount) {
throw generateError(`pair key is not enough balance!`);
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!`);
}
} catch (error) {
// console.log({ CheckBalanceChannelIbcErrors: error });
throw generateError(
`Error in checking balance channel ibc: ${{
CheckBalanceChannelIbcErrors: error
}}`
);
throw generateError(`Error in checking balance channel ibc: ${JSON.stringify(error)}`);
}
};

Expand All @@ -610,7 +625,6 @@ export const checkBalanceIBCOraichain = async (
client: CosmWasmClient,
ibcWasmContract: string
) => {
const ics20Client = new CwIcs20LatestQueryClient(client, ibcWasmContract);
// 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;
Expand All @@ -627,7 +641,7 @@ export const checkBalanceIBCOraichain = async (
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, to, toSimulateAmount, ics20Client);
await checkBalanceChannelIbc(ibcInfo, from, to, toSimulateAmount, client, ibcWasmContract);
}
};

Expand Down
53 changes: 53 additions & 0 deletions packages/universal-swap/src/universal-demos/from-cosmos-to-evm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import "dotenv/config";
import { CosmosWalletImpl } from "./offline-wallet";
import { UniversalSwapHandler } from "../handler";
import {
ORAI_ETH_CONTRACT,
cosmosTokens,
flattenTokens,
generateError,
toAmount,
USDT_CONTRACT
} from "@oraichain/oraidex-common";

const oraichainToEvm = async (chainId: "Oraichain") => {
const wallet = new CosmosWalletImpl(process.env.MNEMONIC);

const sender = await wallet.getKeplrAddr(chainId);
const fromAmount = 90;
let originalFromToken = cosmosTokens.find(
(t) => t.chainId === chainId && t.contractAddress && t.contractAddress === USDT_CONTRACT
);

let originalToToken = flattenTokens.find(
(t) => t.chainId === "0x01" && t.contractAddress && t.contractAddress === ORAI_ETH_CONTRACT
);
const evmAddress = "0xf2846a1E4dAFaeA38C1660a618277d67605bd2B5";
if (!originalFromToken) throw generateError("Could not find original from token");
if (!originalToToken) throw generateError("Could not find original to token");
const universalHandler = new UniversalSwapHandler(
{
originalFromToken,
originalToToken,
sender: { cosmos: sender, evm: evmAddress },
relayerFee: {
relayerAmount: "1000000",
relayerDecimals: 6
},
fromAmount,
simulateAmount: toAmount(fromAmount, originalToToken.decimals).toString()
},
{ cosmosWallet: wallet, ibcInfoTestMode: false }
);

try {
const result = await universalHandler.processUniversalSwap();
console.log("result: ", result);
} catch (error) {
console.log("error: ", error);
}
};

(() => {
return oraichainToEvm("Oraichain");
})();
26 changes: 20 additions & 6 deletions packages/universal-swap/tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,20 +546,34 @@ describe("test universal swap handler functions", () => {
}
);

it.each<[TokenItemType, string, boolean]>([
[flattenTokens.find((t) => t.chainId === "0x38" && t.coinGeckoId === "airight")!, channel, true],
[flattenTokens.find((t) => t.chainId === "0x38" && t.coinGeckoId === "airight")!, channel, false]
])("test-universal-swap-checkBalanceChannelIbc-%", async (toToken, channel, willThrow) => {
it.each<[TokenItemType, TokenItemType, string, string, boolean]>([
[
flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "tether")!,
flattenTokens.find((t) => t.chainId === "0x01" && t.coinGeckoId === "oraichain-token")!,
simulateAmount,
channel,
true
],
[
flattenTokens.find((t) => t.chainId === "Oraichain" && t.coinGeckoId === "oraichain-token")!,
flattenTokens.find((t) => t.chainId === "0x38" && t.coinGeckoId === "oraichain-token")!,
simulateAmount,
channel,
false
]
])("test-universal-swap-checkBalanceChannelIbc-%", async (fromToken, toToken, amount, channel, willThrow) => {
try {
await checkBalanceChannelIbc(
{
source: oraiPort,
channel: channel,
timeout: 3600
},
fromToken,
toToken,
simulateAmount,
ics20Contract
amount,
client,
IBC_WASM_CONTRACT_TEST
);
expect(willThrow).toEqual(false);
} catch (error) {
Expand Down

0 comments on commit a570f5f

Please sign in to comment.