Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix revert handling #48

Merged
merged 8 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/localnet/src/handleOnEVMCalled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ export const handleOnEVMCalled = async ({
return await handleOnRevertEVM({
revertOptions,
err,
amount: 0,
asset: ethers.ZeroAddress,
tss,
isGas: true,
token: "",
provider,
protocolContracts,
exitOnError,
Expand Down
168 changes: 150 additions & 18 deletions packages/localnet/src/handleOnEVMDeposited.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { ethers, NonceManager } from "ethers";
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 ({
Expand All @@ -24,26 +26,24 @@ export const handleOnEVMDeposited = async ({
exitOnError: boolean;
}) => {
log("EVM", "Gateway: 'Deposited' event emitted");
try {
const receiver = args[1];
const amount = args[2];
const asset = args[3];
const message = args[4];

let foreignCoin;
if (asset === ethers.ZeroAddress) {
foreignCoin = foreignCoins.find((coin) => coin.coin_type === "Gas");
} else {
foreignCoin = foreignCoins.find((coin) => coin.asset === asset);
}

if (!foreignCoin) {
logErr("ZetaChain", `Foreign coin not found for asset: ${asset}`);
return;
}
const receiver = args[1];
const amount = args[2];
const asset = args[3];
const message = args[4];
let foreignCoin;
if (asset === ethers.ZeroAddress) {
foreignCoin = foreignCoins.find((coin) => coin.coin_type === "Gas");
} else {
foreignCoin = foreignCoins.find((coin) => coin.asset === asset);
}

const zrc20 = foreignCoin.zrc20_contract_address;
if (!foreignCoin) {
logErr("ZetaChain", `Foreign coin not found for asset: ${asset}`);
return;
}

const zrc20 = foreignCoin.zrc20_contract_address;
try {
const context = {
origin: protocolContracts.gatewayZEVM.target,
sender: await fungibleModuleSigner.getAddress(),
Expand Down Expand Up @@ -85,13 +85,145 @@ export const handleOnEVMDeposited = async ({
} catch (err) {
logErr("ZetaChain", `Error depositing: ${err}`);
const revertOptions = args[5];
const zrc20Contract = new ethers.Contract(zrc20, ZRC20.abi, deployer);
const [gasZRC20, gasFee] = await zrc20Contract.withdrawGasFeeWithGasLimit(
revertOptions[4]
);
let revertAmount;
let revertGasFee = gasFee;
let isGas = true;
let token = null;
if (zrc20 !== gasZRC20) {
token = foreignCoins.find(
(coin) => coin.zrc20_contract_address === zrc20
)?.asset;
isGas = false;
revertGasFee = await swapToCoverGas(
deployer,
zrc20,
gasZRC20,
gasFee,
amount,
await fungibleModuleSigner.getAddress(),
zrc20Contract,
provider,
protocolContracts.wzeta.target,
protocolContracts.uniswapRouterInstance.target
);
}
revertAmount = amount - revertGasFee;
return await handleOnRevertEVM({
revertOptions,
asset,
amount: revertAmount,
err,
tss,
isGas,
token,
provider,
protocolContracts,
exitOnError,
});
}
};

const swapToCoverGas = async (
deployer: any,
zrc20: string,
gasZRC20: string,
gasFee: any,
amount: any,
fungibleModule: any,
zrc20Contract: any,
provider: any,
wzeta: string,
router: string
) => {
/**
* 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<any>} - 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;
};

const uniswapV2Router = new ethers.Contract(
router,
UniswapV2Router02.abi,
deployer
);
deployer.reset();
const approvalTx = await zrc20Contract.approve(router, amount);
await approvalTx.wait();

const path = [zrc20, wzeta, gasZRC20];

const deadline = Math.floor(Date.now() / 1000) + 60 * 20;
const maxZRC20ToSpend = amount;

try {
const swapTx = await uniswapV2Router.swapTokensForExactTokens(
gasFee,
maxZRC20ToSpend,
path,
fungibleModule,
deadline
);

await swapTx.wait();
} catch (swapError) {
logErr("ZetaChain", `Error performing swap on Uniswap: ${swapError}`);
}

const amountInZeta = await getAmounts(
"in",
provider,
gasFee,
wzeta,
gasZRC20,
router,
UniswapV2Router02
);

const amountInZRC20 = await getAmounts(
"in",
provider,
amountInZeta[0],
zrc20,
wzeta,
router,
UniswapV2Router02
);

return amountInZRC20[0];
};
44 changes: 34 additions & 10 deletions packages/localnet/src/handleOnRevertEVM.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import { log, logErr } from "./log";
import { deployOpts } from "./deployOpts";
import { ethers, NonceManager } from "ethers";
import { NonceManager } from "ethers";

export const handleOnRevertEVM = async ({
revertOptions,
asset,
amount,
err,
provider,
tss,
protocolContracts,
isGas,
token,
exitOnError = false,
}: {
revertOptions: any;
err: any;
asset: any;
amount: any;
provider: any;
tss: any;
protocolContracts: any;
isGas: boolean;
token: string;
exitOnError: boolean;
}) => {
const callOnRevert = revertOptions[1];
const revertAddress = revertOptions[0];
const revertMessage = revertOptions[3];
const revertContext = {
asset: ethers.ZeroAddress,
amount: 0,
asset,
amount,
revertMessage,
};
if (callOnRevert) {
Expand All @@ -34,9 +42,26 @@ export const handleOnRevertEVM = async ({
)})`
);
(tss as NonceManager).reset();
const tx = await protocolContracts.gatewayEVM
.connect(tss)
.executeRevert(revertAddress, "0x", revertContext, deployOpts);
let tx;
if (isGas) {
tx = await protocolContracts.gatewayEVM
.connect(tss)
.executeRevert(revertAddress, "0x", revertContext, {
value: amount,
deployOpts,
});
} else {
tx = await protocolContracts.custody
.connect(tss)
.withdrawAndRevert(
revertAddress,
token,
amount,
"0x",
revertContext,
deployOpts
);
}
await tx.wait();
log("EVM", "Gateway: successfully called onRevert");
const logs = await provider.getLogs({
Expand All @@ -47,10 +72,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}`;
Expand Down
8 changes: 6 additions & 2 deletions packages/localnet/src/handleOnRevertZEVM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { logErr } from "./log";
export const handleOnRevertZEVM = async ({
revertOptions,
err,
asset,
amount,
provider,
tss,
log,
Expand All @@ -14,6 +16,8 @@ export const handleOnRevertZEVM = async ({
}: {
revertOptions: any;
err: any;
asset: any;
amount: any;
provider: any;
fungibleModuleSigner: any;
tss: NonceManager;
Expand All @@ -26,8 +30,8 @@ export const handleOnRevertZEVM = async ({
const revertAddress = revertOptions[0];
const revertMessage = revertOptions[3];
const revertContext = {
asset: ethers.ZeroAddress,
amount: 0,
asset,
amount,
revertMessage,
};

Expand Down
2 changes: 2 additions & 0 deletions packages/localnet/src/handleOnZEVMCalled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export const handleOnZEVMCalled = async ({
return await handleOnRevertZEVM({
revertOptions,
err,
amount: 0,
asset: ethers.ZeroAddress,
provider,
fungibleModuleSigner,
tss,
Expand Down
27 changes: 14 additions & 13 deletions packages/localnet/src/handleOnZEVMWithdrawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,26 @@ export const handleOnZEVMWithdrawn = async ({
exitOnError: boolean;
}) => {
log("ZetaChain", "Gateway: 'Withdrawn' event emitted");
const getERC20ByZRC20 = (zrc20: string) => {
const foreignCoin = foreignCoins.find(
(coin: any) => coin.zrc20_contract_address === zrc20
);
if (!foreignCoin) {
logErr("EVM", `Foreign coin not found for ZRC20 address: ${zrc20}`);
return;
}
return foreignCoin.asset;
};
const zrc20 = args[3];
const amount = args[4];
try {
const receiver = args[2];
const zrc20 = args[3];
const amount = args[4];
const message = args[7];
(tss as NonceManager).reset();
const zrc20Contract = new ethers.Contract(zrc20, ZRC20.abi, deployer);
const coinType = await zrc20Contract.COIN_TYPE();
const isGasToken = coinType === 1n;
const isERC20orZETA = coinType === 2n;
const getERC20ByZRC20 = (zrc20: string) => {
const foreignCoin = foreignCoins.find(
(coin: any) => coin.zrc20_contract_address === zrc20
);
if (!foreignCoin) {
logErr("EVM", `Foreign coin not found for ZRC20 address: ${zrc20}`);
return;
}
return foreignCoin.asset;
};
if (message !== "0x") {
// The message is not empty, so this is a withdrawAndCall operation
log("EVM", `Calling ${receiver} with message ${message}`);
Expand All @@ -54,7 +54,6 @@ export const handleOnZEVMWithdrawn = async ({
.execute(receiver, message, { value: amount, ...deployOpts });
await executeTx.wait();
} else {
console.log("!!!");
const erc20 = getERC20ByZRC20(zrc20);
const executeTx = await protocolContracts.custody
.connect(tss)
Expand Down Expand Up @@ -102,6 +101,8 @@ export const handleOnZEVMWithdrawn = async ({
err,
provider,
tss,
asset: getERC20ByZRC20(zrc20),
amount,
log,
fungibleModuleSigner,
protocolContracts,
Expand Down
Loading
Loading