From a0be0325097bae6dd3b152a3c562928523efa088 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Thu, 17 Oct 2024 19:57:01 +0300 Subject: [PATCH] erc20 revert support --- packages/localnet/src/createToken.ts | 2 +- packages/localnet/src/handleOnEVMCalled.ts | 2 + packages/localnet/src/handleOnEVMDeposited.ts | 108 +++++++++++++++++- packages/localnet/src/handleOnRevertEVM.ts | 46 ++++++-- packages/localnet/src/index.ts | 6 +- packages/tasks/src/localnet.ts | 1 + 6 files changed, 153 insertions(+), 12 deletions(-) diff --git a/packages/localnet/src/createToken.ts b/packages/localnet/src/createToken.ts index 708998e..14bbea1 100644 --- a/packages/localnet/src/createToken.ts +++ b/packages/localnet/src/createToken.ts @@ -98,7 +98,7 @@ export const createToken = async ({ foreignCoins.push({ zrc20_contract_address: zrc20.target, - asset: isGasToken ? ethers.ZeroAddress : (erc20 as any).target, + asset: isGasToken ? "" : (erc20 as any).target, foreign_chain_id: "1", decimals: 18, name: `ZetaChain ZRC-20 ${symbol}`, diff --git a/packages/localnet/src/handleOnEVMCalled.ts b/packages/localnet/src/handleOnEVMCalled.ts index 2365a7b..f0736c5 100644 --- a/packages/localnet/src/handleOnEVMCalled.ts +++ b/packages/localnet/src/handleOnEVMCalled.ts @@ -65,6 +65,8 @@ export const handleOnEVMCalled = async ({ amount: 0, asset: ethers.ZeroAddress, tss, + isGas: true, + token: "", provider, protocolContracts, exitOnError, diff --git a/packages/localnet/src/handleOnEVMDeposited.ts b/packages/localnet/src/handleOnEVMDeposited.ts index c1c9ac4..2ab4b98 100644 --- a/packages/localnet/src/handleOnEVMDeposited.ts +++ b/packages/localnet/src/handleOnEVMDeposited.ts @@ -3,6 +3,7 @@ import { handleOnRevertEVM } from "./handleOnRevertEVM"; import { log, logErr } from "./log"; import { deployOpts } from "./deployOpts"; import * as ZRC20 from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; +import * as UniswapV2Router02 from "@uniswap/v2-periphery/build/UniswapV2Router02.json"; // event Deposited(address indexed sender, address indexed receiver, uint256 amount, address asset, bytes payload, RevertOptions revertOptions); export const handleOnEVMDeposited = async ({ @@ -88,16 +89,119 @@ export const handleOnEVMDeposited = async ({ const [gasZRC20, gasFee] = await zrc20Contract.withdrawGasFeeWithGasLimit( revertOptions[4] ); - const amountReverted = amount - gasFee; + let revertAmount; + let revertGasFee = gasFee; + let isGas = true; + let token = null; + if (zrc20 !== gasZRC20) { + token = foreignCoins.find( + (coin) => coin.zrc20_contract_address === zrc20 + )?.asset; + console.log("token!", token); + isGas = false; + const uniswapV2Router = new ethers.Contract( + protocolContracts.uniswapRouterInstance.target, + UniswapV2Router02.abi, + deployer + ); + deployer.reset(); + const approvalTx = await zrc20Contract.approve( + protocolContracts.uniswapRouterInstance.target, + amount + ); + await approvalTx.wait(); + + const path = [zrc20, protocolContracts.wzeta.target, gasZRC20]; + + const deadline = Math.floor(Date.now() / 1000) + 60 * 20; + const maxZRC20ToSpend = amount; + + try { + const swapTx = await uniswapV2Router.swapTokensForExactTokens( + gasFee, + maxZRC20ToSpend, + path, + await fungibleModuleSigner.getAddress(), + deadline + ); + + const amountInZeta = await getAmounts( + "in", + provider, + gasFee, + protocolContracts.wzeta.target, + gasZRC20, + protocolContracts.uniswapRouterInstance.target, + UniswapV2Router02 + ); + + const amountInZRC20 = await getAmounts( + "in", + provider, + amountInZeta[0], + zrc20, + protocolContracts.wzeta.target, + protocolContracts.uniswapRouterInstance.target, + UniswapV2Router02 + ); + + revertGasFee = amountInZRC20[0]; + + await swapTx.wait(); + } catch (swapError) { + logErr("ZetaChain", `Error performing swap on Uniswap: ${swapError}`); + } + } + revertAmount = amount - revertGasFee; return await handleOnRevertEVM({ revertOptions, asset, - amount: amountReverted, + amount: revertAmount, err, tss, + isGas, + token: "", provider, protocolContracts, exitOnError, }); } }; + +/** + * Retrieves the amounts for swapping tokens using UniswapV2. + * @param {"in" | "out"} direction - The direction of the swap ("in" or "out"). + * @param {any} provider - The ethers provider. + * @param {any} amount - The amount to swap. + * @param {string} tokenA - The address of token A. + * @param {string} tokenB - The address of token B. + * @returns {Promise} - The amounts for the swap. + * @throws Will throw an error if the UniswapV2 router address cannot be retrieved. + */ +const getAmounts = async ( + direction: "in" | "out", + provider: any, + amount: any, + tokenA: string, + tokenB: string, + routerAddress: any, + routerABI: any +) => { + if (!routerAddress) { + throw new Error("Cannot get uniswapV2Router02 address"); + } + + const uniswapRouter = new ethers.Contract( + routerAddress, + routerABI.abi, + provider + ); + + const path = [tokenA, tokenB]; + + const amounts = + direction === "in" + ? await uniswapRouter.getAmountsIn(amount, path) + : await uniswapRouter.getAmountsOut(amount, path); + return amounts; +}; diff --git a/packages/localnet/src/handleOnRevertEVM.ts b/packages/localnet/src/handleOnRevertEVM.ts index 89a4002..3a08d9b 100644 --- a/packages/localnet/src/handleOnRevertEVM.ts +++ b/packages/localnet/src/handleOnRevertEVM.ts @@ -1,6 +1,6 @@ import { log, logErr } from "./log"; import { deployOpts } from "./deployOpts"; -import { ethers, NonceManager } from "ethers"; +import { NonceManager } from "ethers"; export const handleOnRevertEVM = async ({ revertOptions, @@ -10,6 +10,8 @@ export const handleOnRevertEVM = async ({ provider, tss, protocolContracts, + isGas, + token, exitOnError = false, }: { revertOptions: any; @@ -19,6 +21,8 @@ export const handleOnRevertEVM = async ({ provider: any; tss: any; protocolContracts: any; + isGas: boolean; + token: string; exitOnError: boolean; }) => { const callOnRevert = revertOptions[1]; @@ -38,9 +42,36 @@ export const handleOnRevertEVM = async ({ )})` ); (tss as NonceManager).reset(); - const tx = await protocolContracts.gatewayEVM - .connect(tss) - .executeRevert(revertAddress, "0x", revertContext, deployOpts); + let tx; + if (isGas) { + console.log(protocolContracts.gatewayEVM.connect(tss).functions); + tx = await protocolContracts.gatewayEVM + .connect(tss) + .executeRevert(revertAddress, "0x", revertContext, { + value: amount, + deployOpts, + }); + } else { + console.log( + "!!!", + revertAddress, + token, + amount, + "0x", + revertContext, + deployOpts + ); + tx = await protocolContracts.custody // this is failing + .connect(tss) + .withdrawAndRevert( + revertAddress, + token, + amount, + "", + revertContext, + deployOpts + ); + } await tx.wait(); log("EVM", "Gateway: successfully called onRevert"); const logs = await provider.getLogs({ @@ -51,10 +82,9 @@ export const handleOnRevertEVM = async ({ logs.forEach((data: any) => { log("EVM", `Event from onRevert: ${JSON.stringify(data)}`); }); - } catch (err) { - const error = `Gateway: Call onRevert failed: ${err}`; - logErr("EVM", error); - if (exitOnError) throw new Error(error); + } catch (err: any) { + logErr("EVM", `Gateway: Call onRevert failed`, err); + if (exitOnError) throw new Error(err); } } else { const error = `Tx reverted without callOnRevert: ${err}`; diff --git a/packages/localnet/src/index.ts b/packages/localnet/src/index.ts index 3ad06a7..be66be7 100755 --- a/packages/localnet/src/index.ts +++ b/packages/localnet/src/index.ts @@ -20,6 +20,11 @@ const FUNGIBLE_MODULE_ADDRESS = "0x735b14BB79463307AAcBED86DAf3322B1e6226aB"; const foreignCoins: any[] = []; +// A hack to make BigInt serializable +(BigInt as any).prototype["toJSON"] = function () { + return this.toString(); +}; + let protocolContracts: any; let deployer: Signer; let tss: Signer; @@ -195,7 +200,6 @@ const deployProtocolContracts = async ( const { uniswapFactoryInstance, uniswapRouterInstance } = await prepareUniswap(deployer, tss, wzeta); - const { systemContract, gatewayZEVM } = await prepareZetaChain( deployer, wzeta.target, diff --git a/packages/tasks/src/localnet.ts b/packages/tasks/src/localnet.ts index ff0d83e..3490674 100644 --- a/packages/tasks/src/localnet.ts +++ b/packages/tasks/src/localnet.ts @@ -121,6 +121,7 @@ const localnet = async (args: any) => { ZETA: addr.zetaZetaChain, "Fungible Module": addr.fungibleModuleZetaChain, "System Contract": addr.sytemContractZetaChain, + "Uniswap Router": addr.uniswapRouter, ...addr.foreignCoins.reduce((acc: any, coin: any) => { acc[`ZRC-20 ${coin.symbol}`] = coin.zrc20_contract_address; return acc;